"""HTMX-driven admin dashboard for users, roles, and thread standards.""" from fastapi import APIRouter, Depends, Form, HTTPException, Request, status from fastapi.responses import HTMLResponse, RedirectResponse, Response from sqlalchemy.ext.asyncio import AsyncSession from app.core.database import get_db from app.crud import audit as audit_crud from app.crud import iso_thread as iso_thread_crud from app.crud import manufacturer as manufacturer_crud from app.crud import part_type as part_type_crud from app.crud import role as role_crud from app.crud import scope_model as scope_model_crud from app.crud import unc_thread as unc_thread_crud from app.crud import user as user_crud from app.deps import get_current_web_user from app.models.unc_thread import UncSeries from app.models.user import User from app.web.templates import templates router = APIRouter(prefix="/admin", include_in_schema=False) def web_require(*perms: str): """Web dependency: allow superusers or holders of all given permissions.""" async def checker(user: User = Depends(get_current_web_user)) -> User: if user.is_superuser or set(perms).issubset(user.permissions): return user raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="You do not have access to this page.", ) return checker @router.get("", response_class=HTMLResponse) async def admin_home(): return RedirectResponse(url="/admin/users", status_code=status.HTTP_302_FOUND) # --- Users ------------------------------------------------------------------- @router.get("/users", response_class=HTMLResponse) async def users_page( request: Request, db: AsyncSession = Depends(get_db), user: User = Depends(web_require("users:read")), ): return templates.TemplateResponse( request, "admin/users.html", { "user": user, "users": await user_crud.list_users(db), "roles": await role_crud.list_roles(db), }, ) @router.post("/users", response_class=HTMLResponse) async def create_user( request: Request, email: str = Form(...), password: str = Form(...), full_name: str = Form(""), is_superuser: bool = Form(False), role_ids: list[int] = Form(default=[]), db: AsyncSession = Depends(get_db), actor: User = Depends(web_require("users:write")), ): if not await user_crud.get_by_email(db, email): created = await user_crud.create( db, email=email, password=password, full_name=full_name or None, is_superuser=is_superuser, role_ids=role_ids, ) await audit_crud.log( db, actor=actor, action="create", resource_type="User", resource_id=created.id, description=audit_crud.describe_create("User", created.id, created.email), ) return templates.TemplateResponse( request, "admin/_users_table.html", { "users": await user_crud.list_users(db), "roles": await role_crud.list_roles(db), }, ) @router.get("/users/{user_id}/edit", response_class=HTMLResponse) async def edit_user_row( request: Request, user_id: int, db: AsyncSession = Depends(get_db), _: User = Depends(web_require("users:write")), ): target = await user_crud.get(db, user_id) if target is None: raise HTTPException(status_code=404, detail="User not found") return templates.TemplateResponse( request, "admin/_user_row_edit.html", {"u": target, "roles": await role_crud.list_roles(db)}, ) @router.get("/users/{user_id}/row", response_class=HTMLResponse) async def user_row( request: Request, user_id: int, db: AsyncSession = Depends(get_db), _: User = Depends(web_require("users:read")), ): target = await user_crud.get(db, user_id) if target is None: raise HTTPException(status_code=404, detail="User not found") return templates.TemplateResponse(request, "admin/_user_row.html", {"u": target}) @router.post("/users/{user_id}", response_class=HTMLResponse) async def update_user( request: Request, user_id: int, full_name: str = Form(""), password: str = Form(""), is_active: bool = Form(False), is_superuser: bool = Form(False), role_ids: list[int] = Form(default=[]), db: AsyncSession = Depends(get_db), actor: User = Depends(web_require("users:write")), ): target = await user_crud.get(db, user_id) if target is None: raise HTTPException(status_code=404, detail="User not found") changes = { "full_name": full_name or None, "password": password or None, "is_active": is_active, "is_superuser": is_superuser, "role_ids": role_ids, } await user_crud.update(db, target, changes) await audit_crud.log( db, actor=actor, action="update", resource_type="User", resource_id=user_id, description=audit_crud.describe_update("User", user_id, changes), ) return templates.TemplateResponse(request, "admin/_user_row.html", {"u": target}) @router.delete("/users/{user_id}") async def delete_user( user_id: int, db: AsyncSession = Depends(get_db), actor: User = Depends(web_require("users:delete")), ): target = await user_crud.get(db, user_id) if target is not None: email = target.email await user_crud.delete(db, target) await audit_crud.log( db, actor=actor, action="delete", resource_type="User", resource_id=user_id, description=audit_crud.describe_delete("User", user_id, email), ) return Response(status_code=status.HTTP_200_OK) # --- Roles ------------------------------------------------------------------- @router.get("/roles", response_class=HTMLResponse) async def roles_page( request: Request, db: AsyncSession = Depends(get_db), user: User = Depends(web_require("roles:read")), ): return templates.TemplateResponse( request, "admin/roles.html", { "user": user, "roles": await role_crud.list_roles(db), "permissions": await role_crud.list_permissions(db), }, ) @router.post("/roles", response_class=HTMLResponse) async def create_role( request: Request, name: str = Form(...), description: str = Form(""), permission_ids: list[int] = Form(default=[]), db: AsyncSession = Depends(get_db), actor: User = Depends(web_require("roles:write")), ): if not await role_crud.get_by_name(db, name): created = await role_crud.create( db, name=name, description=description or None, permission_ids=permission_ids, ) await audit_crud.log( db, actor=actor, action="create", resource_type="Role", resource_id=created.id, description=audit_crud.describe_create("Role", created.id, created.name), ) return templates.TemplateResponse( request, "admin/_roles_table.html", { "roles": await role_crud.list_roles(db), "permissions": await role_crud.list_permissions(db), }, ) @router.get("/roles/{role_id}/edit", response_class=HTMLResponse) async def edit_role_row( request: Request, role_id: int, db: AsyncSession = Depends(get_db), _: User = Depends(web_require("roles:write")), ): role = await role_crud.get(db, role_id) if role is None: raise HTTPException(status_code=404, detail="Role not found") return templates.TemplateResponse( request, "admin/_role_row_edit.html", {"r": role, "permissions": await role_crud.list_permissions(db)}, ) @router.get("/roles/{role_id}/row", response_class=HTMLResponse) async def role_row( request: Request, role_id: int, db: AsyncSession = Depends(get_db), _: User = Depends(web_require("roles:read")), ): role = await role_crud.get(db, role_id) if role is None: raise HTTPException(status_code=404, detail="Role not found") return templates.TemplateResponse(request, "admin/_role_row.html", {"r": role}) @router.post("/roles/{role_id}", response_class=HTMLResponse) async def update_role( request: Request, role_id: int, permission_ids: list[int] = Form(default=[]), db: AsyncSession = Depends(get_db), actor: User = Depends(web_require("roles:write")), ): role = await role_crud.get(db, role_id) if role is None: raise HTTPException(status_code=404, detail="Role not found") changes = {"permission_ids": permission_ids} await role_crud.update(db, role, changes) await audit_crud.log( db, actor=actor, action="update", resource_type="Role", resource_id=role_id, description=audit_crud.describe_update("Role", role_id, changes), ) return templates.TemplateResponse(request, "admin/_role_row.html", {"r": role}) @router.delete("/roles/{role_id}") async def delete_role( role_id: int, db: AsyncSession = Depends(get_db), actor: User = Depends(web_require("roles:write")), ): role = await role_crud.get(db, role_id) if role is not None: name = role.name await role_crud.delete(db, role) await audit_crud.log( db, actor=actor, action="delete", resource_type="Role", resource_id=role_id, description=audit_crud.describe_delete("Role", role_id, name), ) return Response(status_code=status.HTTP_200_OK) # --- ISO Threads ------------------------------------------------------------- @router.get("/iso-threads", response_class=HTMLResponse) async def iso_threads_page( request: Request, db: AsyncSession = Depends(get_db), user: User = Depends(web_require("iso_threads:read")), ): return templates.TemplateResponse( request, "admin/iso_threads.html", {"user": user, "threads": await iso_thread_crud.list_all(db)}, ) @router.post("/iso-threads", response_class=HTMLResponse) async def create_iso_thread( request: Request, diameter_mm: float = Form(...), pitch_mm: float = Form(...), depth_mm: str = Form(""), db: AsyncSession = Depends(get_db), actor: User = Depends(web_require("iso_threads:write")), ): thread = await iso_thread_crud.create( db, diameter_mm=diameter_mm, pitch_mm=pitch_mm, depth_mm=float(depth_mm) if depth_mm.strip() else None, ) await audit_crud.log( db, actor=actor, action="create", resource_type="IsoThread", resource_id=thread.id, description=audit_crud.describe_create("IsoThread", thread.id, f"M{thread.diameter_mm}×{thread.pitch_mm}"), ) return templates.TemplateResponse( request, "admin/_iso_threads_table.html", {"threads": await iso_thread_crud.list_all(db)}, ) @router.get("/iso-threads/{thread_id}/edit", response_class=HTMLResponse) async def edit_iso_thread_row( request: Request, thread_id: int, db: AsyncSession = Depends(get_db), _: User = Depends(web_require("iso_threads:write")), ): thread = await iso_thread_crud.get(db, thread_id) if thread is None: raise HTTPException(status_code=404, detail="ISO thread not found") return templates.TemplateResponse( request, "admin/_iso_thread_row_edit.html", {"t": thread} ) @router.get("/iso-threads/{thread_id}/row", response_class=HTMLResponse) async def iso_thread_row( request: Request, thread_id: int, db: AsyncSession = Depends(get_db), _: User = Depends(web_require("iso_threads:read")), ): thread = await iso_thread_crud.get(db, thread_id) if thread is None: raise HTTPException(status_code=404, detail="ISO thread not found") return templates.TemplateResponse( request, "admin/_iso_thread_row.html", {"t": thread} ) @router.post("/iso-threads/{thread_id}", response_class=HTMLResponse) async def update_iso_thread( request: Request, thread_id: int, diameter_mm: float = Form(...), pitch_mm: float = Form(...), depth_mm: str = Form(""), db: AsyncSession = Depends(get_db), actor: User = Depends(web_require("iso_threads:write")), ): thread = await iso_thread_crud.get(db, thread_id) if thread is None: raise HTTPException(status_code=404, detail="ISO thread not found") changes = { "diameter_mm": diameter_mm, "pitch_mm": pitch_mm, "depth_mm": float(depth_mm) if depth_mm.strip() else None, } await iso_thread_crud.update(db, thread, changes) await audit_crud.log( db, actor=actor, action="update", resource_type="IsoThread", resource_id=thread_id, description=audit_crud.describe_update("IsoThread", thread_id, changes), ) return templates.TemplateResponse( request, "admin/_iso_thread_row.html", {"t": thread} ) @router.delete("/iso-threads/{thread_id}") async def delete_iso_thread( thread_id: int, db: AsyncSession = Depends(get_db), actor: User = Depends(web_require("iso_threads:delete")), ): thread = await iso_thread_crud.get(db, thread_id) if thread is not None: name = f"M{thread.diameter_mm}×{thread.pitch_mm}" await iso_thread_crud.delete(db, thread) await audit_crud.log( db, actor=actor, action="delete", resource_type="IsoThread", resource_id=thread_id, description=audit_crud.describe_delete("IsoThread", thread_id, name), ) return Response(status_code=status.HTTP_200_OK) # --- UNC Threads ------------------------------------------------------------- @router.get("/unc-threads", response_class=HTMLResponse) async def unc_threads_page( request: Request, db: AsyncSession = Depends(get_db), user: User = Depends(web_require("unc_threads:read")), ): return templates.TemplateResponse( request, "admin/unc_threads.html", { "user": user, "threads": await unc_thread_crud.list_all(db), "series_options": list(UncSeries), }, ) @router.post("/unc-threads", response_class=HTMLResponse) async def create_unc_thread( request: Request, diameter_fraction: str = Form(...), tpi: int = Form(...), series: UncSeries = Form(...), depth_in: str = Form(""), db: AsyncSession = Depends(get_db), actor: User = Depends(web_require("unc_threads:write")), ): thread = await unc_thread_crud.create( db, diameter_fraction=diameter_fraction, tpi=tpi, series=series, depth_in=float(depth_in) if depth_in.strip() else None, ) await audit_crud.log( db, actor=actor, action="create", resource_type="UncThread", resource_id=thread.id, description=audit_crud.describe_create("UncThread", thread.id, f"{thread.diameter_fraction}-{thread.tpi} {thread.series.value}"), ) return templates.TemplateResponse( request, "admin/_unc_threads_table.html", {"threads": await unc_thread_crud.list_all(db)}, ) @router.get("/unc-threads/{thread_id}/edit", response_class=HTMLResponse) async def edit_unc_thread_row( request: Request, thread_id: int, db: AsyncSession = Depends(get_db), _: User = Depends(web_require("unc_threads:write")), ): thread = await unc_thread_crud.get(db, thread_id) if thread is None: raise HTTPException(status_code=404, detail="UNC thread not found") return templates.TemplateResponse( request, "admin/_unc_thread_row_edit.html", {"t": thread, "series_options": list(UncSeries)}, ) @router.get("/unc-threads/{thread_id}/row", response_class=HTMLResponse) async def unc_thread_row( request: Request, thread_id: int, db: AsyncSession = Depends(get_db), _: User = Depends(web_require("unc_threads:read")), ): thread = await unc_thread_crud.get(db, thread_id) if thread is None: raise HTTPException(status_code=404, detail="UNC thread not found") return templates.TemplateResponse( request, "admin/_unc_thread_row.html", {"t": thread} ) @router.post("/unc-threads/{thread_id}", response_class=HTMLResponse) async def update_unc_thread( request: Request, thread_id: int, diameter_fraction: str = Form(...), tpi: int = Form(...), series: UncSeries = Form(...), depth_in: str = Form(""), db: AsyncSession = Depends(get_db), actor: User = Depends(web_require("unc_threads:write")), ): thread = await unc_thread_crud.get(db, thread_id) if thread is None: raise HTTPException(status_code=404, detail="UNC thread not found") changes = { "diameter_fraction": diameter_fraction, "tpi": tpi, "series": series, "depth_in": float(depth_in) if depth_in.strip() else None, } await unc_thread_crud.update(db, thread, changes) await audit_crud.log( db, actor=actor, action="update", resource_type="UncThread", resource_id=thread_id, description=audit_crud.describe_update("UncThread", thread_id, changes), ) return templates.TemplateResponse( request, "admin/_unc_thread_row.html", {"t": thread} ) @router.delete("/unc-threads/{thread_id}") async def delete_unc_thread( thread_id: int, db: AsyncSession = Depends(get_db), actor: User = Depends(web_require("unc_threads:delete")), ): thread = await unc_thread_crud.get(db, thread_id) if thread is not None: name = f"{thread.diameter_fraction}-{thread.tpi} {thread.series.value}" await unc_thread_crud.delete(db, thread) await audit_crud.log( db, actor=actor, action="delete", resource_type="UncThread", resource_id=thread_id, description=audit_crud.describe_delete("UncThread", thread_id, name), ) return Response(status_code=status.HTTP_200_OK) # --- Manufacturers ----------------------------------------------------------- @router.get("/manufacturers", response_class=HTMLResponse) async def manufacturers_page( request: Request, db: AsyncSession = Depends(get_db), user: User = Depends(web_require("manufacturers:read")), ): return templates.TemplateResponse( request, "admin/manufacturers.html", {"user": user, "manufacturers": await manufacturer_crud.list_all(db)}, ) @router.post("/manufacturers", response_class=HTMLResponse) async def create_manufacturer( request: Request, name: str = Form(...), url: str = Form(...), logo_url: str = Form(""), db: AsyncSession = Depends(get_db), actor: User = Depends(web_require("manufacturers:write")), ): created = await manufacturer_crud.create( db, name=name, url=url, logo_url=logo_url.strip() or None, ) await audit_crud.log( db, actor=actor, action="create", resource_type="Manufacturer", resource_id=created.id, description=audit_crud.describe_create("Manufacturer", created.id, created.name), ) return templates.TemplateResponse( request, "admin/_manufacturers_table.html", {"manufacturers": await manufacturer_crud.list_all(db)}, ) @router.get("/manufacturers/{manufacturer_id}/edit", response_class=HTMLResponse) async def edit_manufacturer_row( request: Request, manufacturer_id: int, db: AsyncSession = Depends(get_db), _: User = Depends(web_require("manufacturers:write")), ): m = await manufacturer_crud.get(db, manufacturer_id) if m is None: raise HTTPException(status_code=404, detail="Manufacturer not found") return templates.TemplateResponse( request, "admin/_manufacturer_row_edit.html", {"m": m} ) @router.get("/manufacturers/{manufacturer_id}/row", response_class=HTMLResponse) async def manufacturer_row( request: Request, manufacturer_id: int, db: AsyncSession = Depends(get_db), _: User = Depends(web_require("manufacturers:read")), ): m = await manufacturer_crud.get(db, manufacturer_id) if m is None: raise HTTPException(status_code=404, detail="Manufacturer not found") return templates.TemplateResponse( request, "admin/_manufacturer_row.html", {"m": m} ) @router.post("/manufacturers/{manufacturer_id}", response_class=HTMLResponse) async def update_manufacturer( request: Request, manufacturer_id: int, name: str = Form(...), url: str = Form(...), logo_url: str = Form(""), db: AsyncSession = Depends(get_db), actor: User = Depends(web_require("manufacturers:write")), ): m = await manufacturer_crud.get(db, manufacturer_id) if m is None: raise HTTPException(status_code=404, detail="Manufacturer not found") changes = {"name": name, "url": url, "logo_url": logo_url.strip() or None} await manufacturer_crud.update(db, m, changes) await audit_crud.log( db, actor=actor, action="update", resource_type="Manufacturer", resource_id=manufacturer_id, description=audit_crud.describe_update("Manufacturer", manufacturer_id, changes), ) return templates.TemplateResponse( request, "admin/_manufacturer_row.html", {"m": m} ) @router.delete("/manufacturers/{manufacturer_id}") async def delete_manufacturer( manufacturer_id: int, db: AsyncSession = Depends(get_db), actor: User = Depends(web_require("manufacturers:delete")), ): m = await manufacturer_crud.get(db, manufacturer_id) if m is not None: name = m.name await manufacturer_crud.delete(db, m) await audit_crud.log( db, actor=actor, action="delete", resource_type="Manufacturer", resource_id=manufacturer_id, description=audit_crud.describe_delete("Manufacturer", manufacturer_id, name), ) return Response(status_code=status.HTTP_200_OK) # --- Scope Models ------------------------------------------------------------ @router.get("/scope-models", response_class=HTMLResponse) async def scope_models_page( request: Request, db: AsyncSession = Depends(get_db), user: User = Depends(web_require("scope_models:read")), ): return templates.TemplateResponse( request, "admin/scope_models.html", { "user": user, "scope_models": await scope_model_crud.list_all(db), "manufacturers": await manufacturer_crud.list_all(db), }, ) @router.post("/scope-models", response_class=HTMLResponse) async def create_scope_model( request: Request, name: str = Form(...), manufacturer_id: int = Form(...), url: str = Form(""), db: AsyncSession = Depends(get_db), actor: User = Depends(web_require("scope_models:write")), ): created = await scope_model_crud.create( db, name=name, manufacturer_id=manufacturer_id, url=url.strip() or None, ) await audit_crud.log( db, actor=actor, action="create", resource_type="ScopeModel", resource_id=created.id, description=audit_crud.describe_create("ScopeModel", created.id, created.name), ) return templates.TemplateResponse( request, "admin/_scope_models_table.html", {"scope_models": await scope_model_crud.list_all(db)}, ) @router.get("/scope-models/{scope_model_id}/edit", response_class=HTMLResponse) async def edit_scope_model_row( request: Request, scope_model_id: int, db: AsyncSession = Depends(get_db), _: User = Depends(web_require("scope_models:write")), ): sm = await scope_model_crud.get(db, scope_model_id) if sm is None: raise HTTPException(status_code=404, detail="Scope model not found") return templates.TemplateResponse( request, "admin/_scope_model_row_edit.html", {"sm": sm, "manufacturers": await manufacturer_crud.list_all(db)}, ) @router.get("/scope-models/{scope_model_id}/row", response_class=HTMLResponse) async def scope_model_row( request: Request, scope_model_id: int, db: AsyncSession = Depends(get_db), _: User = Depends(web_require("scope_models:read")), ): sm = await scope_model_crud.get(db, scope_model_id) if sm is None: raise HTTPException(status_code=404, detail="Scope model not found") return templates.TemplateResponse( request, "admin/_scope_model_row.html", {"sm": sm} ) @router.post("/scope-models/{scope_model_id}", response_class=HTMLResponse) async def update_scope_model( request: Request, scope_model_id: int, name: str = Form(...), manufacturer_id: int = Form(...), url: str = Form(""), db: AsyncSession = Depends(get_db), actor: User = Depends(web_require("scope_models:write")), ): sm = await scope_model_crud.get(db, scope_model_id) if sm is None: raise HTTPException(status_code=404, detail="Scope model not found") changes = {"name": name, "manufacturer_id": manufacturer_id, "url": url.strip() or None} await scope_model_crud.update(db, sm, changes) await audit_crud.log( db, actor=actor, action="update", resource_type="ScopeModel", resource_id=scope_model_id, description=audit_crud.describe_update("ScopeModel", scope_model_id, changes), ) return templates.TemplateResponse( request, "admin/_scope_model_row.html", {"sm": sm} ) @router.delete("/scope-models/{scope_model_id}") async def delete_scope_model( scope_model_id: int, db: AsyncSession = Depends(get_db), actor: User = Depends(web_require("scope_models:delete")), ): sm = await scope_model_crud.get(db, scope_model_id) if sm is not None: name = sm.name await scope_model_crud.delete(db, sm) await audit_crud.log( db, actor=actor, action="delete", resource_type="ScopeModel", resource_id=scope_model_id, description=audit_crud.describe_delete("ScopeModel", scope_model_id, name), ) return Response(status_code=status.HTTP_200_OK) # --- Part Types -------------------------------------------------------------- @router.get("/part-types", response_class=HTMLResponse) async def part_types_page( request: Request, db: AsyncSession = Depends(get_db), user: User = Depends(web_require("part_types:read")), ): return templates.TemplateResponse( request, "admin/part_types.html", {"user": user, "part_types": await part_type_crud.list_all(db)}, ) @router.post("/part-types", response_class=HTMLResponse) async def create_part_type( request: Request, name: str = Form(...), db: AsyncSession = Depends(get_db), actor: User = Depends(web_require("part_types:write")), ): if not await part_type_crud.get_by_name(db, name): created = await part_type_crud.create(db, name=name) await audit_crud.log( db, actor=actor, action="create", resource_type="PartType", resource_id=created.id, description=audit_crud.describe_create("PartType", created.id, created.name), ) return templates.TemplateResponse( request, "admin/_part_types_table.html", {"part_types": await part_type_crud.list_all(db)}, ) @router.get("/part-types/{part_type_id}/edit", response_class=HTMLResponse) async def edit_part_type_row( request: Request, part_type_id: int, db: AsyncSession = Depends(get_db), _: User = Depends(web_require("part_types:write")), ): pt = await part_type_crud.get(db, part_type_id) if pt is None: raise HTTPException(status_code=404, detail="Part type not found") return templates.TemplateResponse( request, "admin/_part_type_row_edit.html", {"pt": pt} ) @router.get("/part-types/{part_type_id}/row", response_class=HTMLResponse) async def part_type_row( request: Request, part_type_id: int, db: AsyncSession = Depends(get_db), _: User = Depends(web_require("part_types:read")), ): pt = await part_type_crud.get(db, part_type_id) if pt is None: raise HTTPException(status_code=404, detail="Part type not found") return templates.TemplateResponse( request, "admin/_part_type_row.html", {"pt": pt} ) @router.post("/part-types/{part_type_id}", response_class=HTMLResponse) async def update_part_type( request: Request, part_type_id: int, name: str = Form(...), db: AsyncSession = Depends(get_db), actor: User = Depends(web_require("part_types:write")), ): pt = await part_type_crud.get(db, part_type_id) if pt is None: raise HTTPException(status_code=404, detail="Part type not found") changes = {"name": name} await part_type_crud.update(db, pt, changes) await audit_crud.log( db, actor=actor, action="update", resource_type="PartType", resource_id=part_type_id, description=audit_crud.describe_update("PartType", part_type_id, changes), ) return templates.TemplateResponse( request, "admin/_part_type_row.html", {"pt": pt} ) @router.delete("/part-types/{part_type_id}") async def delete_part_type( part_type_id: int, db: AsyncSession = Depends(get_db), actor: User = Depends(web_require("part_types:delete")), ): pt = await part_type_crud.get(db, part_type_id) if pt is not None: name = pt.name await part_type_crud.delete(db, pt) await audit_crud.log( db, actor=actor, action="delete", resource_type="PartType", resource_id=part_type_id, description=audit_crud.describe_delete("PartType", part_type_id, name), ) return Response(status_code=status.HTTP_200_OK) # --- Audit Log --------------------------------------------------------------- @router.get("/audit-log", response_class=HTMLResponse) async def audit_log_page( request: Request, db: AsyncSession = Depends(get_db), user: User = Depends(web_require("audit_log:read")), ): return templates.TemplateResponse( request, "admin/audit_log.html", {"user": user, "entries": await audit_crud.list_recent(db)}, )