package api_impl import ( "context" "database/sql" "log/slog" "net/http" "atlas9.dev/c/core/dbi" "atlas9.dev/c/core/iam" "atlas9.dev/c/demo/api" "atlas9.dev/c/demo/lib" "atlas9.dev/c/demo/lib/access" ) type AuditImpl struct { DB *sql.DB Guard access.Guard Audit dbi.Factory[iam.AuditStore] } func (s *AuditImpl) ServeMux(mux *http.ServeMux) { mux.HandleFunc(api.Path_Audit_List, s.List) mux.HandleFunc(api.Path_Audit_ListByUser, s.ListByUser) } func (s *AuditImpl) List(w http.ResponseWriter, r *http.Request) { // Read and validate the request body var req api.Audit_ListReq if read(w, r, &req) { return } // Check access if check(w, r, s.Guard, iam.CapAuditRead, req.Tenant, "") { return } // Load the data from the database ctx := r.Context() var res api.Audit_ListRes err := dbi.ReadOnly(ctx, s.DB, func(tx dbi.DBI) error { return s.Audit(tx).List(ctx, req.Tenant, req.Page, &res.Page) }) write(ctx, w, err, res) } func (s *AuditImpl) ListByUser(w http.ResponseWriter, r *http.Request) { // Read and validate the request body var req api.Audit_ListByUserReq if read(w, r, &req) { return } // Reading your own trail is always allowed; reading another user's // requires system access. The store enforces this. // Load the data from the database ctx := r.Context() var res api.Audit_ListByUserRes err := dbi.ReadOnly(ctx, s.DB, func(tx dbi.DBI) error { return s.Audit(tx).ListByUser(ctx, req.UserID, req.Page, &res.Page) }) write(ctx, w, err, res) } // audit appends an audit entry within the caller's transaction, filling // Actor and Request from the request context. Call it after the mutation // succeeds, inside the same dbi.ReadWrite closure, so the entry commits // and rolls back with the change it describes. // // A pre-set Actor is preserved, for flows where the acting user is known // but not yet an authenticated principal (login). func audit(ctx context.Context, s iam.AuditStore, e iam.AuditEntry) error { if e.Actor == "" { e.Actor = iam.GetPrincipal(ctx).Subject } e.Request = lib.GetRequestID(ctx) if err := s.Append(ctx, &e); err != nil { return err } slog.DebugContext(ctx, "audit recorded", "action", e.Action, "resource", e.Resource) return nil }