package pgmonitor import ( "context" "encoding/json" "time" "github.com/google/uuid" "github.com/jackc/pgx/v5/pgxpool" ) // CleanupRequest for deprecated POST /postgres/cleanup (use /v1/maintenance/run). type CleanupRequest struct { PolicyID string `json:"policy_id"` Policy string `json:"policy"` DryRun bool `json:"dry_run"` Limit int `json:"limit"` } // InsertMaintenanceAudit records an audit row at job start. func InsertMaintenanceAudit(ctx context.Context, pool *pgxpool.Pool, tenantID, actorPrefix, kind, table string, dryRun bool) (string, error) { return InsertMaintenanceAuditWithPolicy(ctx, pool, tenantID, actorPrefix, kind, table, "", dryRun) } // InsertMaintenanceAuditWithPolicy records an audit row linked to maintenance_policy. func InsertMaintenanceAuditWithPolicy(ctx context.Context, pool *pgxpool.Pool, tenantID, actorPrefix, kind, table, policyID string, dryRun bool) (string, error) { id := uuid.New().String() _, err := pool.Exec(ctx, ` INSERT INTO postgres_maintenance_audit (id, tenant_id, actor_prefix, kind, target_table, policy_id, dry_run, status, created_at) VALUES ($1, NULLIF($2,''), NULLIF($3,''), $4, NULLIF($5,''), NULLIF($6,''), $7, 'running', now())`, id, tenantID, actorPrefix, kind, table, policyID, dryRun) return id, err } // FinishMaintenanceAudit updates terminal state. func FinishMaintenanceAudit(ctx context.Context, pool *pgxpool.Pool, id, status string, detail map[string]any, errMsg *string) error { var detailJSON []byte if detail != nil { detailJSON, _ = json.Marshal(detail) } _, err := pool.Exec(ctx, ` UPDATE postgres_maintenance_audit SET status = $2, detail_json = $3::jsonb, error_message = $4, finished_at = now(), started_at = COALESCE(started_at, now()) WHERE id = $1`, id, status, string(detailJSON), errMsg) return err } // ListMaintenanceLogs returns paginated audit rows. func ListMaintenanceLogs(ctx context.Context, pool *pgxpool.Pool, cursor string, limit int) ([]MaintenanceLogRow, string, bool, error) { limit = clampLimit(limit, 20, 100) args := []any{limit + 1} q := ` SELECT id, COALESCE(tenant_id,''), COALESCE(actor_prefix,''), kind, COALESCE(target_table,''), dry_run, status, detail_json, COALESCE(error_message,''), created_at, started_at, finished_at FROM postgres_maintenance_audit` if cursor != "" { q += ` WHERE created_at < (SELECT created_at FROM postgres_maintenance_audit WHERE id = $2)` args = append(args, cursor) } q += ` ORDER BY created_at DESC LIMIT $1` rows, err := pool.Query(ctx, q, args...) if err != nil { return nil, "", false, err } defer rows.Close() var out []MaintenanceLogRow for rows.Next() { var r MaintenanceLogRow var detailRaw []byte var started, finished *time.Time if err := rows.Scan(&r.ID, &r.TenantID, &r.ActorPrefix, &r.Kind, &r.TargetTable, &r.DryRun, &r.Status, &detailRaw, &r.Error, &r.CreatedAt, &started, &finished); err != nil { return nil, "", false, err } r.StartedAt = started r.FinishedAt = finished if len(detailRaw) > 0 { _ = json.Unmarshal(detailRaw, &r.Detail) } out = append(out, r) } hasMore := len(out) > limit if hasMore { out = out[:limit] } next := "" if hasMore && len(out) > 0 { next = out[len(out)-1].ID } return out, next, hasMore, rows.Err() }