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"}
|
_ALLOWED_IMAGE_TYPES = {"image/jpeg", "image/png", "image/webp", "image/gif"}
|
||||||
_MAX_IMAGE_BYTES = 10 * 1024 * 1024 # 10 MB
|
_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:
|
def _unique_name(directory: Path, filename: str) -> str:
|
||||||
stem, suffix = Path(filename).stem, Path(filename).suffix
|
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}")
|
@app.post("/pages/{slug:path}")
|
||||||
async def save_page(slug: str, body: PageBody, user: User = Depends(require_auth)) -> JSONResponse:
|
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}")
|
@app.delete("/pages/{slug:path}")
|
||||||
async def delete_page(slug: str, user: User = Depends(require_auth)) -> JSONResponse:
|
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 ───────────────────────────────────────────────────────────
|
# ── Story endpoints ───────────────────────────────────────────────────────────
|
||||||
@@ -279,11 +319,16 @@ async def get_story(slug: str, user: User = Depends(require_auth)) -> JSONRespon
|
|||||||
|
|
||||||
@app.post("/stories/{slug:path}")
|
@app.post("/stories/{slug:path}")
|
||||||
async def save_story(slug: str, body: PageBody, user: User = Depends(require_auth)) -> JSONResponse:
|
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}")
|
@app.delete("/stories/{slug:path}")
|
||||||
async def delete_story(slug: str, user: User = Depends(require_auth)) -> JSONResponse:
|
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 ─────────────────────────────────────────────────────────────
|
# ── Asset upload ─────────────────────────────────────────────────────────────
|
||||||
|
|||||||
Reference in New Issue
Block a user