package controlplane import ( "context" "fmt" "strings" ) func (s *Store) WriteAudit(ctx context.Context, input AuditInput) error { actorType := "console_user" if input.Actor.Bootstrap { actorType = "bootstrap" } _, err := s.db.Exec(ctx, `INSERT INTO audit_logs (actor_id, actor_type, actor_role, tenant_id, request_id, method, path, action, status_code, remote_ip, user_agent) VALUES (NULLIF($1,'')::uuid,$2,$3,NULLIF($4,'')::uuid,$5,$6,$7,$8,$9,NULLIF($10,'')::inet,$11)`, input.Actor.ID, actorType, input.Actor.Role, input.Actor.TenantID, input.RequestID, input.Method, input.Path, input.Action, input.StatusCode, input.RemoteIP, input.UserAgent) if err != nil { return fmt.Errorf("write audit log: %w", err) } return nil } func (s *Store) ListAudit(ctx context.Context, tenantID string, limit int) ([]AuditLog, error) { if limit < 1 || limit > 1000 { limit = 200 } query := `SELECT id, COALESCE(actor_id::text,''), actor_type, actor_role, COALESCE(tenant_id::text,''), request_id, method, path, action, status_code, COALESCE(host(remote_ip),''), user_agent, created_at FROM audit_logs` args := []any{} if strings.TrimSpace(tenantID) != "" { query += ` WHERE tenant_id=$1` args = append(args, tenantID) } args = append(args, limit) query += fmt.Sprintf(` ORDER BY created_at DESC LIMIT $%d`, len(args)) rows, err := s.db.Query(ctx, query, args...) if err != nil { return nil, fmt.Errorf("query audit logs: %w", err) } defer rows.Close() result := make([]AuditLog, 0) for rows.Next() { var item AuditLog if err := rows.Scan(&item.ID, &item.ActorID, &item.ActorType, &item.ActorRole, &item.TenantID, &item.RequestID, &item.Method, &item.Path, &item.Action, &item.StatusCode, &item.RemoteIP, &item.UserAgent, &item.CreatedAt); err != nil { return nil, fmt.Errorf("scan audit log: %w", err) } result = append(result, item) } return result, rows.Err() }