Add per-page history endpoint and file-scoped diff filter

This commit is contained in:
brutsalvadi
2026-05-21 22:42:20 +02:00
parent de99b2c58b
commit a68eb28877
2 changed files with 30 additions and 6 deletions
+29 -5
View File
@@ -41,9 +41,10 @@ _SESSION_DOMAIN = os.environ.get("SESSION_DOMAIN") or None
_SESSION_TTL = 30 * 24 * 3600 # 30 days (matches bincio_activity)
_SESSION_COOKIE = "bincio_session"
_SAFE_SLUG = re.compile(r"^[a-zA-Z0-9_][a-zA-Z0-9_\-/]*$")
_SAFE_HANDLE = re.compile(r"^[a-z][a-z0-9_-]{1,19}$")
_SAFE_HASH = re.compile(r"^[0-9a-f]{4,40}$")
_SAFE_SLUG = re.compile(r"^[a-zA-Z0-9_][a-zA-Z0-9_\-/]*$")
_SAFE_HANDLE = re.compile(r"^[a-z][a-z0-9_-]{1,19}$")
_SAFE_HASH = re.compile(r"^[0-9a-f]{4,40}$")
_SAFE_REL_PATH = re.compile(r"^(pages|blog)/[a-zA-Z0-9_][a-zA-Z0-9_\-/]*\.md$")
_ALLOWED_IMAGE_TYPES = {"image/jpeg", "image/png", "image/webp", "image/gif"}
_MAX_IMAGE_BYTES = 10 * 1024 * 1024 # 10 MB
@@ -288,12 +289,15 @@ async def get_wiki_log(user: User = Depends(require_auth)) -> JSONResponse:
@app.get("/api/diff/{commit_hash}")
async def get_diff(commit_hash: str, user: User = Depends(require_auth)) -> JSONResponse:
async def get_diff(commit_hash: str, file: Optional[str] = None, user: User = Depends(require_auth)) -> JSONResponse:
if not _SAFE_HASH.match(commit_hash):
raise HTTPException(status_code=400, detail="invalid hash")
if file is not None and not _SAFE_REL_PATH.match(file):
raise HTTPException(status_code=400, detail="invalid file path")
paths = [file] if file else ["pages/", "blog/"]
env = _git_env()
proc = await asyncio.create_subprocess_exec(
"git", "show", commit_hash, "-p", "--no-color", "--format=", "--", "pages/", "blog/",
"git", "show", commit_hash, "-p", "--no-color", "--format=", "--", *paths,
cwd=str(_ROOT), env=env,
stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE,
)
@@ -303,6 +307,26 @@ async def get_diff(commit_hash: str, user: User = Depends(require_auth)) -> JSON
return JSONResponse({"diff": stdout.decode(errors="replace")})
@app.get("/api/history/{slug:path}")
async def get_history(slug: str, user: User = Depends(require_auth)) -> JSONResponse:
if not _SAFE_SLUG.match(slug):
raise HTTPException(status_code=400, detail="invalid slug")
env = _git_env()
proc = await asyncio.create_subprocess_exec(
"git", "log", "--format=%h|%ar|%aN|%s", "--author=@bincio.wiki", "-n", "50",
"--", f"pages/{slug}.md", f"blog/{slug}.md",
cwd=str(_ROOT), env=env,
stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.DEVNULL,
)
stdout, _ = await proc.communicate()
entries = []
for line in stdout.decode().strip().splitlines():
parts = line.split("|", 3)
if len(parts) == 4:
entries.append({"hash": parts[0], "date": parts[1], "author": parts[2], "message": parts[3]})
return JSONResponse({"log": entries})
@app.get("/api/me")
async def me(user: User = Depends(require_auth)) -> JSONResponse:
return JSONResponse({