Files
devplacepy/devplacepy/routers/admin/auditlog.py
T
retoor ede7a863b7 chore: remove stale agent reports and add PYTHONDONTWRITEBYTECODE export to Makefile
Delete four stale SEO and style agent report JSON/MD files from agents/reports/ that had zero findings or were superseded. Export PYTHONDONTWRITEBYTECODE=1 in the Makefile so all make targets suppress bytecode generation, and add a `tree` target that lists the repository file tree via git ls-tree. Update AGENTS.md and CLAUDE.md documentation to reflect that routers now form a directory tree mirroring URL paths and that bytecode writing is disabled project-wide.
2026-06-13 16:27:41 +00:00

139 lines
4.4 KiB
Python

# retoor <retoor@molodetz.nl>
import logging
from urllib.parse import urlencode
from fastapi import APIRouter, Request
from fastapi.responses import HTMLResponse
from devplacepy.database import get_users_by_uids
from devplacepy.utils import require_admin, time_ago, not_found, pretty_json
from devplacepy.seo import base_seo_context, site_url, website_schema
from devplacepy.responses import respond
from devplacepy.schemas import AuditLogOut, AuditEventOut
from devplacepy.services.audit import query as audit_query
from devplacepy.routers.admin._shared import parse_metadata
logger = logging.getLogger(__name__)
router = APIRouter()
@router.get("/audit-log", response_class=HTMLResponse)
async def admin_audit_log(
request: Request,
page: int = 1,
event_key: str = "",
category: str = "",
actor_role: str = "",
actor_uid: str = "",
origin: str = "",
result: str = "",
q: str = "",
date_from: str = "",
date_to: str = "",
):
admin = require_admin(request)
filters = {
"event_key": event_key,
"category": category,
"actor_role": actor_role,
"actor_uid": actor_uid,
"origin": origin,
"result": result,
"q": q,
"date_from": date_from,
"date_to": date_to,
}
active = {key: value for key, value in filters.items() if value}
rows, pagination = audit_query.list_events(filters, page)
user_uids = [
row["actor_uid"]
for row in rows
if row.get("actor_kind") == "user" and row.get("actor_uid")
]
users_by_uid = get_users_by_uids(user_uids)
entries = []
for row in rows:
entry = dict(row)
entry["actor_user"] = users_by_uid.get(row.get("actor_uid"))
entry["time_ago"] = time_ago(row["created_at"]) if row.get("created_at") else ""
entry["metadata"] = parse_metadata(row.get("metadata"))
entries.append(entry)
filter_qs = urlencode(active)
pagination_query = (filter_qs + "&") if filter_qs else ""
base = site_url(request)
seo_ctx = base_seo_context(
request,
title="Audit Log - Admin",
description="Platform audit trail of every state-changing action.",
robots="noindex,nofollow",
breadcrumbs=[
{"name": "Home", "url": "/feed"},
{"name": "Admin", "url": "/admin"},
{"name": "Audit Log", "url": "/admin/audit-log"},
],
schemas=[website_schema(base)],
)
return respond(
request,
"admin_audit_log.html",
{
**seo_ctx,
"request": request,
"user": admin,
"entries": entries,
"pagination": pagination,
"pagination_query": pagination_query,
"filters": active,
"options": audit_query.filter_options(),
"admin_section": "audit-log",
},
model=AuditLogOut,
)
@router.get("/audit-log/{uid}", response_class=HTMLResponse)
async def admin_audit_event(request: Request, uid: str):
admin = require_admin(request)
data = audit_query.get_event_with_links(uid)
if data is None:
raise not_found("Audit event not found")
event = dict(data["event"])
if event.get("actor_kind") == "user" and event.get("actor_uid"):
event["actor_user"] = get_users_by_uids([event["actor_uid"]]).get(
event["actor_uid"]
)
event["metadata"] = parse_metadata(event.get("metadata"))
event["metadata_pretty"] = (
pretty_json(event["metadata"]) if event["metadata"] else ""
)
event["time_ago"] = (
time_ago(event["created_at"]) if event.get("created_at") else ""
)
base = site_url(request)
seo_ctx = base_seo_context(
request,
title="Audit Event - Admin",
description="A single audit event with related objects.",
robots="noindex,nofollow",
breadcrumbs=[
{"name": "Home", "url": "/feed"},
{"name": "Admin", "url": "/admin"},
{"name": "Audit Log", "url": "/admin/audit-log"},
{"name": "Event", "url": f"/admin/audit-log/{uid}"},
],
schemas=[website_schema(base)],
)
return respond(
request,
"admin_audit_event.html",
{
**seo_ctx,
"request": request,
"user": admin,
"event": event,
"links": data["links"],
"admin_section": "audit-log",
},
model=AuditEventOut,
)