Initialize bincio_wiki project structure

- Add brutsalvadi/astro-bloomz as git submodule at site/
- Add edit/ FastAPI sidecar (read/write markdown pages, trigger rebuild)
- Add scripts/dev.sh and scripts/build.sh (symlink pages/, run Astro + Pagefind)
- Add .gitignore
This commit is contained in:
brutsalvadi
2026-04-22 22:54:19 +02:00
commit c01def696c
8 changed files with 185 additions and 0 deletions
+16
View File
@@ -0,0 +1,16 @@
# Python
__pycache__/
*.pyc
.venv/
venv/
# Site build output (managed by submodule)
site/dist/
site/node_modules/
site/.astro/
# Claude Code local settings
.claude/
# OS
.DS_Store
+3
View File
@@ -0,0 +1,3 @@
[submodule "site"]
path = site
url = https://github.com/brutsalvadi/astro-bloomz
View File
+3
View File
@@ -0,0 +1,3 @@
fastapi>=0.115.0
uvicorn[standard]>=0.30.0
pydantic>=2.0.0
+106
View File
@@ -0,0 +1,106 @@
"""FastAPI edit sidecar for BincioWiki — reads and writes markdown pages."""
from __future__ import annotations
import os
import re
import subprocess
from pathlib import Path
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from fastapi.middleware.gzip import GZipMiddleware
from fastapi.responses import JSONResponse
from pydantic import BaseModel
# Resolved at startup relative to the project root (one level above this file)
_ROOT = Path(__file__).parent.parent
pages_dir: Path = _ROOT / os.environ.get("WIKI_PAGES_DIR", "pages")
site_dir: Path = _ROOT / "site"
_SAFE_SLUG = re.compile(r"^[a-zA-Z0-9][a-zA-Z0-9_\-/]*$")
app = FastAPI(title="BincioWiki Edit Sidecar", docs_url=None, redoc_url=None)
app.add_middleware(GZipMiddleware, minimum_size=1024)
app.add_middleware(
CORSMiddleware,
allow_origin_regex=r"https?://localhost(:\d+)?",
allow_methods=["GET", "POST", "DELETE"],
allow_headers=["Content-Type"],
)
def _slug_to_path(slug: str) -> Path:
if not _SAFE_SLUG.match(slug):
raise HTTPException(400, "Invalid page slug — only alphanumeric, hyphens, underscores, and slashes allowed")
resolved = (pages_dir / f"{slug}.md").resolve()
if not str(resolved).startswith(str(pages_dir.resolve())):
raise HTTPException(400, "Path traversal detected")
return resolved
class PageBody(BaseModel):
content: str
@app.get("/pages")
async def list_pages() -> JSONResponse:
"""Return all wiki page slugs."""
if not pages_dir.exists():
return JSONResponse({"pages": []})
slugs = [
str(p.relative_to(pages_dir).with_suffix(""))
for p in sorted(pages_dir.rglob("*.md"))
]
return JSONResponse({"pages": slugs})
@app.get("/pages/{slug:path}")
async def get_page(slug: str) -> JSONResponse:
"""Return raw markdown content for a page."""
path = _slug_to_path(slug)
if not path.exists():
raise HTTPException(404, "Page not found")
return JSONResponse({"slug": slug, "content": path.read_text(encoding="utf-8")})
@app.post("/pages/{slug:path}")
async def save_page(slug: str, body: PageBody) -> JSONResponse:
"""Create or update a wiki page."""
path = _slug_to_path(slug)
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(body.content, encoding="utf-8")
return JSONResponse({"slug": slug, "saved": True})
@app.delete("/pages/{slug:path}")
async def delete_page(slug: str) -> JSONResponse:
"""Delete a wiki page."""
path = _slug_to_path(slug)
if not path.exists():
raise HTTPException(404, "Page not found")
path.unlink()
return JSONResponse({"slug": slug, "deleted": True})
@app.post("/rebuild")
async def rebuild() -> JSONResponse:
"""Trigger an astro build of the site."""
try:
result = subprocess.run(
["npm", "run", "build"],
cwd=site_dir,
capture_output=True,
text=True,
timeout=120,
)
return JSONResponse({
"success": result.returncode == 0,
"stdout": result.stdout[-3000:] if result.stdout else "",
"stderr": result.stderr[-3000:] if result.stderr else "",
})
except subprocess.TimeoutExpired:
raise HTTPException(504, "Build timed out after 120s")
except Exception as e:
raise HTTPException(500, str(e))
+29
View File
@@ -0,0 +1,29 @@
#!/usr/bin/env bash
# Build the static wiki site and run Pagefind for search indexing.
# Usage: scripts/build.sh
set -e
cd "$(dirname "$0")/.."
ROOT="$(pwd)"
# Symlink wiki pages into the site's content collection
mkdir -p pages
mkdir -p site/src/content/entries
LINK="$ROOT/site/src/content/entries/_wiki"
if [ ! -L "$LINK" ]; then
ln -sf "$ROOT/pages" "$LINK"
echo "Linked pages/ → site/src/content/entries/_wiki"
fi
# Build Astro site
cd site && npm run build
cd "$ROOT"
# Run Pagefind to index the static output
if command -v pagefind &>/dev/null; then
echo "Running Pagefind..."
pagefind --site site/dist
else
echo "Pagefind not installed — skipping search index (npm install -g pagefind)"
fi
echo "Build complete: site/dist/"
Executable
+27
View File
@@ -0,0 +1,27 @@
#!/usr/bin/env bash
# Start the Astro dev server and optionally the edit sidecar.
# Usage: scripts/dev.sh [--edit]
set -e
cd "$(dirname "$0")/.."
ROOT="$(pwd)"
# Symlink wiki pages into the site's content collection
mkdir -p pages
mkdir -p site/src/content/entries
LINK="$ROOT/site/src/content/entries/_wiki"
if [ ! -L "$LINK" ]; then
ln -sf "$ROOT/pages" "$LINK"
echo "Linked pages/ → site/src/content/entries/_wiki"
fi
# Start edit sidecar if requested
if [[ "$*" == *"--edit"* ]]; then
echo "Starting edit sidecar on :8001..."
(cd "$ROOT" && uvicorn edit.server:app --reload --port 8001) &
SIDECAR_PID=$!
trap "kill $SIDECAR_PID 2>/dev/null" EXIT
fi
# Start Astro dev server
export PUBLIC_EDIT_URL="${WIKI_EDIT_URL:-}"
cd site && npm run dev
Submodule
+1
Submodule site added at 2d51d17f55