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:
+16
@@ -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
|
||||
@@ -0,0 +1,3 @@
|
||||
[submodule "site"]
|
||||
path = site
|
||||
url = https://github.com/brutsalvadi/astro-bloomz
|
||||
@@ -0,0 +1,3 @@
|
||||
fastapi>=0.115.0
|
||||
uvicorn[standard]>=0.30.0
|
||||
pydantic>=2.0.0
|
||||
+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))
|
||||
Executable
+29
@@ -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
@@ -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
Reference in New Issue
Block a user