137 lines
3.7 KiB
Python
137 lines
3.7 KiB
Python
import os
|
|
from pathlib import Path
|
|
|
|
from fastapi import APIRouter, HTTPException
|
|
from pydantic import BaseModel
|
|
|
|
router = APIRouter()
|
|
|
|
PAGES_DIR = Path(os.environ.get("PAGES_DIR", "/data/pages"))
|
|
SOURCES_DIR = Path(os.environ.get("SOURCES_DIR", "/data/sources"))
|
|
|
|
|
|
class PageMeta(BaseModel):
|
|
name: str
|
|
title: str | None = None
|
|
published: bool = False
|
|
has_source: bool = False
|
|
last_modified: float | None = None
|
|
size: int | None = None
|
|
|
|
|
|
class PageDetail(BaseModel):
|
|
name: str
|
|
markdown: str | None = None
|
|
micron: str | None = None
|
|
|
|
|
|
class SaveRequest(BaseModel):
|
|
markdown: str
|
|
publish: bool = False
|
|
|
|
|
|
def _extract_title(markdown: str) -> str | None:
|
|
for line in markdown.splitlines():
|
|
stripped = line.strip()
|
|
if stripped.startswith("# "):
|
|
return stripped[2:].strip()
|
|
return None
|
|
|
|
|
|
def _list_all_page_names() -> set[str]:
|
|
names: set[str] = set()
|
|
if PAGES_DIR.is_dir():
|
|
for f in PAGES_DIR.iterdir():
|
|
if f.suffix == ".mu" and f.is_file():
|
|
names.add(f.stem)
|
|
if SOURCES_DIR.is_dir():
|
|
for f in SOURCES_DIR.iterdir():
|
|
if f.suffix == ".md" and f.is_file():
|
|
names.add(f.stem)
|
|
return names
|
|
|
|
|
|
def _page_meta(name: str) -> PageMeta:
|
|
mu_path = PAGES_DIR / f"{name}.mu"
|
|
md_path = SOURCES_DIR / f"{name}.md"
|
|
|
|
title = None
|
|
if md_path.is_file():
|
|
title = _extract_title(md_path.read_text(encoding="utf-8"))
|
|
|
|
published = mu_path.is_file()
|
|
last_modified = mu_path.stat().st_mtime if published else None
|
|
size = mu_path.stat().st_size if published else None
|
|
|
|
return PageMeta(
|
|
name=name,
|
|
title=title,
|
|
published=published,
|
|
has_source=md_path.is_file(),
|
|
last_modified=last_modified,
|
|
size=size,
|
|
)
|
|
|
|
|
|
@router.get("/pages", response_model=list[PageMeta])
|
|
async def list_pages():
|
|
return [_page_meta(n) for n in sorted(_list_all_page_names())]
|
|
|
|
|
|
@router.get("/pages/{name}", response_model=PageDetail)
|
|
async def get_page(name: str):
|
|
md_path = SOURCES_DIR / f"{name}.md"
|
|
mu_path = PAGES_DIR / f"{name}.mu"
|
|
|
|
if not md_path.is_file() and not mu_path.is_file():
|
|
raise HTTPException(status_code=404, detail="Page not found")
|
|
|
|
markdown = md_path.read_text(encoding="utf-8") if md_path.is_file() else None
|
|
micron = mu_path.read_text(encoding="utf-8") if mu_path.is_file() else None
|
|
|
|
return PageDetail(name=name, markdown=markdown, micron=micron)
|
|
|
|
|
|
@router.post("/pages/{name}", response_model=PageMeta)
|
|
async def save_page(name: str, req: SaveRequest):
|
|
SOURCES_DIR.mkdir(parents=True, exist_ok=True)
|
|
PAGES_DIR.mkdir(parents=True, exist_ok=True)
|
|
|
|
# Always save markdown source
|
|
md_path = SOURCES_DIR / f"{name}.md"
|
|
md_path.write_text(req.markdown, encoding="utf-8")
|
|
|
|
# Optionally publish
|
|
if req.publish:
|
|
try:
|
|
from md2txt import convert_markdown
|
|
|
|
micron = convert_markdown(
|
|
req.markdown,
|
|
width=80,
|
|
renderer_name="micron",
|
|
)
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=f"Conversion failed: {e}")
|
|
|
|
mu_path = PAGES_DIR / f"{name}.mu"
|
|
mu_path.write_text(micron, encoding="utf-8")
|
|
|
|
return _page_meta(name)
|
|
|
|
|
|
@router.delete("/pages/{name}")
|
|
async def delete_page(name: str):
|
|
md_path = SOURCES_DIR / f"{name}.md"
|
|
mu_path = PAGES_DIR / f"{name}.mu"
|
|
|
|
if not md_path.is_file() and not mu_path.is_file():
|
|
raise HTTPException(status_code=404, detail="Page not found")
|
|
|
|
if md_path.is_file():
|
|
md_path.unlink()
|
|
if mu_path.is_file():
|
|
mu_path.unlink()
|
|
|
|
return {"deleted": name}
|