Add git commit with attribution on every page/story save and delete
This commit is contained in:
+49
-4
@@ -43,6 +43,41 @@ _SAFE_SLUG = re.compile(r"^[a-zA-Z0-9_][a-zA-Z0-9_\-/]*$")
|
||||
_ALLOWED_IMAGE_TYPES = {"image/jpeg", "image/png", "image/webp", "image/gif"}
|
||||
_MAX_IMAGE_BYTES = 10 * 1024 * 1024 # 10 MB
|
||||
|
||||
# Git attribution — on VPS the work tree has no .git dir; bare repo is separate.
|
||||
# Set GIT_DIR to the bare repo path in the systemd service; GIT_WORK_TREE is _ROOT.
|
||||
_GIT_DIR = os.environ.get("GIT_DIR")
|
||||
_git_lock = asyncio.Lock()
|
||||
|
||||
|
||||
async def _git_commit(file_path: Path, handle: str, verb: str) -> None:
|
||||
rel = str(file_path.relative_to(_ROOT))
|
||||
env = os.environ.copy()
|
||||
if _GIT_DIR:
|
||||
env["GIT_DIR"] = _GIT_DIR
|
||||
env["GIT_WORK_TREE"] = str(_ROOT)
|
||||
env.setdefault("GIT_COMMITTER_NAME", "bincio-wiki")
|
||||
env.setdefault("GIT_COMMITTER_EMAIL", "wiki@bincio.wiki")
|
||||
try:
|
||||
async with _git_lock:
|
||||
add = await asyncio.create_subprocess_exec(
|
||||
"git", "add", rel,
|
||||
cwd=str(_ROOT), env=env,
|
||||
stdout=asyncio.subprocess.DEVNULL,
|
||||
stderr=asyncio.subprocess.DEVNULL,
|
||||
)
|
||||
await add.wait()
|
||||
commit = await asyncio.create_subprocess_exec(
|
||||
"git", "commit",
|
||||
"-m", f"{handle}: {verb} {file_path.stem}",
|
||||
"--author", f"{handle} <{handle}@bincio.wiki>",
|
||||
cwd=str(_ROOT), env=env,
|
||||
stdout=asyncio.subprocess.DEVNULL,
|
||||
stderr=asyncio.subprocess.DEVNULL,
|
||||
)
|
||||
await commit.wait()
|
||||
except Exception as e:
|
||||
print(f"[git] commit failed for {rel}: {e}", flush=True)
|
||||
|
||||
|
||||
def _unique_name(directory: Path, filename: str) -> str:
|
||||
stem, suffix = Path(filename).stem, Path(filename).suffix
|
||||
@@ -260,11 +295,16 @@ async def get_page(slug: str, user: User = Depends(require_auth)) -> JSONRespons
|
||||
|
||||
@app.post("/pages/{slug:path}")
|
||||
async def save_page(slug: str, body: PageBody, user: User = Depends(require_auth)) -> JSONResponse:
|
||||
return _save(slug, body, pages_dir)
|
||||
result = _save(slug, body, pages_dir)
|
||||
await _git_commit(_slug_to_path(slug, pages_dir), user.handle, "edited")
|
||||
return result
|
||||
|
||||
@app.delete("/pages/{slug:path}")
|
||||
async def delete_page(slug: str, user: User = Depends(require_auth)) -> JSONResponse:
|
||||
return _delete(slug, pages_dir)
|
||||
path = _slug_to_path(slug, pages_dir)
|
||||
result = _delete(slug, pages_dir)
|
||||
await _git_commit(path, user.handle, "deleted")
|
||||
return result
|
||||
|
||||
|
||||
# ── Story endpoints ───────────────────────────────────────────────────────────
|
||||
@@ -279,11 +319,16 @@ async def get_story(slug: str, user: User = Depends(require_auth)) -> JSONRespon
|
||||
|
||||
@app.post("/stories/{slug:path}")
|
||||
async def save_story(slug: str, body: PageBody, user: User = Depends(require_auth)) -> JSONResponse:
|
||||
return _save(slug, body, stories_dir)
|
||||
result = _save(slug, body, stories_dir)
|
||||
await _git_commit(_slug_to_path(slug, stories_dir), user.handle, "edited")
|
||||
return result
|
||||
|
||||
@app.delete("/stories/{slug:path}")
|
||||
async def delete_story(slug: str, user: User = Depends(require_auth)) -> JSONResponse:
|
||||
return _delete(slug, stories_dir)
|
||||
path = _slug_to_path(slug, stories_dir)
|
||||
result = _delete(slug, stories_dir)
|
||||
await _git_commit(path, user.handle, "deleted")
|
||||
return result
|
||||
|
||||
|
||||
# ── Asset upload ─────────────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user