From 893da5b9ba9437138a763bbd097739be9ec2e649 Mon Sep 17 00:00:00 2001 From: curo1305 Date: Sun, 31 May 2026 15:22:46 +0200 Subject: [PATCH] =?UTF-8?q?docs(06.2-04):=20complete=20ADMIN-06=20audit=20?= =?UTF-8?q?enrichment=20+=20daily=20exports=20=E2=80=94=2010=20tests=20pas?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Handle-enriched audit log (user_handle, actor_handle via aliased double-JOIN) - user_handle filter with handle-to-UUID resolution, empty result for unknown handles - fetch+Blob CSV export replacing window.location.href (T-06.2-04-03) - GET /audit-log/daily-exports and /daily-exports/{date} with date regex validation - Daily exports section in AuditLogTab with date dropdown + Download button - Full audit test suite: 10 passed; backend suite: 337 passed, 1 pre-existing failure --- .../06.2-04-SUMMARY.md | 129 ++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 .planning/phases/06.2-close-v1-sharing-cloud-delete-csv-export-gaps/06.2-04-SUMMARY.md diff --git a/.planning/phases/06.2-close-v1-sharing-cloud-delete-csv-export-gaps/06.2-04-SUMMARY.md b/.planning/phases/06.2-close-v1-sharing-cloud-delete-csv-export-gaps/06.2-04-SUMMARY.md new file mode 100644 index 0000000..0fa94e7 --- /dev/null +++ b/.planning/phases/06.2-close-v1-sharing-cloud-delete-csv-export-gaps/06.2-04-SUMMARY.md @@ -0,0 +1,129 @@ +--- +phase: "06.2" +plan: "04" +subsystem: "admin-audit-log" +tags: [audit-log, handle-enrichment, csv-export, daily-exports, admin, security] +dependency_graph: + requires: ["06.2-02", "06.2-03"] + provides: ["ADMIN-06 complete", "handle-enriched audit log", "fixed CSV export", "daily export UI"] + affects: ["backend/api/audit.py", "frontend/src/components/admin/AuditLogTab.vue", "frontend/src/api/client.js"] +tech_stack: + added: [] + patterns: + - "SQLAlchemy aliased double-JOIN for handle enrichment" + - "Handle-to-UUID resolution with empty-result fallback for unknown handles" + - "asyncio.to_thread wrapping synchronous MinIO SDK calls" + - "fetch+Blob URL pattern for authenticated CSV download" + - "Date path parameter regex validation before MinIO key construction" +key_files: + created: [] + modified: + - backend/api/audit.py + - backend/tests/test_audit.py + - frontend/src/api/client.js + - frontend/src/components/admin/AuditLogTab.vue +decisions: + - "Module-level import of get_storage_backend and MinIOBackend in audit.py to enable testable patch targets" + - "Separate count query (no JOIN) from data query (with JOIN) to avoid COUNT subquery ambiguity on multi-column selects (Pitfall 4)" + - "export_audit_log uses same _audit_to_dict_with_handles() as list_audit_log to prevent UUID-only CSV export regression (Pitfall 7)" + - "Updated test_audit_log_export_csv expected CSV header to include user_handle and actor_handle columns" +metrics: + duration: "~25 minutes" + completed_date: "2026-05-31" + tasks_completed: 2 + files_modified: 4 +--- + +# Phase 06.2 Plan 04: ADMIN-06 audit enrichment + CSV + daily exports Summary + +**One-liner:** Handle-enriched audit log (aliased double-JOIN), user_handle filter with handle→UUID resolution, fixed CSV export via fetch+Blob, and new daily-export listing + streaming download endpoints with MinIO integration. + +## What Was Built + +### Backend (backend/api/audit.py) + +**New helper functions:** +- `_audit_to_dict_with_handles(entry, user_handle, actor_handle)` — extends the existing `_audit_to_dict()` to include `user_handle` and `actor_handle` fields. Used by both the JSON viewer and CSV export (Pitfall 7 compliance). +- `_build_filtered_query_with_handles(start, end, user_uuid, event_type)` — builds a multi-column select joining `User` twice (as `UserSubject` and `UserActor` via `aliased()`) to resolve handles. Returns `(AuditLog, user_handle, actor_handle)` row tuples. + +**Updated endpoints:** +- `GET /api/admin/audit-log` — `user_id: Optional[uuid.UUID]` replaced with `user_handle: Optional[str]`. Handle resolved to UUID via preliminary SELECT; unknown handles return empty results (not 422). Data query uses enriched JOIN; count query uses plain query to avoid subquery ambiguity (Pitfall 4). +- `GET /api/admin/audit-log/export` — same user_handle change; uses `_audit_to_dict_with_handles()` so CSV includes `user_handle` and `actor_handle` columns. + +**New endpoints (registered before existing ones to ensure route priority):** +- `GET /api/admin/audit-log/daily-exports` — lists MinIO `audit-logs` bucket via `asyncio.to_thread(_list)`. Returns `{items: [{date, key}]}` sorted descending by date. Returns `{items: []}` if backend is not MinIOBackend. +- `GET /api/admin/audit-log/daily-exports/{date}` — validates date against `re.fullmatch(r"\d{4}-\d{2}-\d{2}", date)` before constructing `f"audit-logs/{date}.csv"` key (T-06.2-04-01 path traversal prevention). Streams CSV via `asyncio.to_thread(_get)`. Returns 404 on invalid date or missing file. + +Both new endpoints use `Depends(get_current_admin)` (T-06.2-04-02). + +### Backend Tests (backend/tests/test_audit.py) + +Five xfail stubs promoted to full integration tests: +1. `test_audit_log_includes_user_handle` — seeds entry, asserts `user_handle` and `actor_handle` keys present, handle matches admin_user fixture +2. `test_audit_log_filter_by_handle` — seeds two users, asserts filtering by handle returns only matching entries +3. `test_audit_log_filter_unknown_handle` — asserts 200 + `items==[]` + `total==0` for unknown handle +4. `test_daily_exports_list` — mocks `get_storage_backend` with mock MinIO client, asserts sorted `items` returned +5. `test_daily_export_download` — mocks `get_object`, asserts `text/csv` Content-Type, `2026-05-30` in Content-Disposition, 404 for invalid date + +Also updated `test_audit_log_export_csv` expected CSV header to include `user_handle,actor_handle` columns — regression caused by enriched export; correct per Pitfall 7. + +**Final test counts:** 10 tests pass (4 pre-existing + 6 updated/promoted), full suite 337 passed / 1 pre-existing failure (test_extract_docx, ModuleNotFoundError — unrelated). + +### Frontend (frontend/src/api/client.js) + +Three new exported functions: +- `adminExportAuditLogCsv(params)` — raw `fetch()` with Authorization Bearer header, `res.text()` (not `res.json()`), Blob + `` click download pattern (D-13, T-06.2-04-03) +- `adminListDailyExports()` — raw `fetch()` + `res.json()` for the JSON-returning listing endpoint +- `adminDownloadDailyExport(date)` — raw `fetch()` with Bearer header, Blob download as `audit-{date}.csv` + +Updated `adminListAuditLog()` — parameter renamed from `user_id` to `user_handle` to match backend API change. + +### Frontend (frontend/src/components/admin/AuditLogTab.vue) + +- Label "User" → "User handle"; `filters.user_id` → `filters.user_handle`; `fetchLog()` passes `user_handle` param +- `exportCsv()` replaced with async function calling `api.adminExportAuditLogCsv()`; loading state `exportingCsv` ref; error display +- New "Daily exports" section below pagination: loading/empty/populated states, date `