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:
+106
@@ -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))
|
||||
Reference in New Issue
Block a user