| 1 | // SPDX-License-Identifier: AGPL-3.0-or-later |
| 2 | |
| 3 | package jobs |
| 4 | |
| 5 | import ( |
| 6 | "context" |
| 7 | "encoding/json" |
| 8 | "fmt" |
| 9 | "log/slog" |
| 10 | "time" |
| 11 | |
| 12 | "github.com/jackc/pgx/v5/pgtype" |
| 13 | "github.com/jackc/pgx/v5/pgxpool" |
| 14 | |
| 15 | "github.com/tenseleyFlow/shithub/internal/worker" |
| 16 | workerdb "github.com/tenseleyFlow/shithub/internal/worker/sqlc" |
| 17 | ) |
| 18 | |
| 19 | // JobsPurgeDeps wires the purge handler. |
| 20 | type JobsPurgeDeps struct { |
| 21 | Pool *pgxpool.Pool |
| 22 | Logger *slog.Logger |
| 23 | } |
| 24 | |
| 25 | // JobsPurgePayload — empty object is fine; defaults below. |
| 26 | type JobsPurgePayload struct { |
| 27 | CompletedOlderThanDays int `json:"completed_older_than_days,omitempty"` |
| 28 | FailedOlderThanDays int `json:"failed_older_than_days,omitempty"` |
| 29 | } |
| 30 | |
| 31 | // JobsPurge deletes completed/failed jobs older than the configured |
| 32 | // retention. Retention defaults: 14 days completed, 30 days failed. |
| 33 | // Designed to run as a cron job (S26 ships scheduling); for now it can |
| 34 | // be enqueued ad-hoc by the operator. |
| 35 | func JobsPurge(deps JobsPurgeDeps) worker.Handler { |
| 36 | return func(ctx context.Context, raw json.RawMessage) error { |
| 37 | p := JobsPurgePayload{} |
| 38 | if len(raw) > 0 { |
| 39 | _ = json.Unmarshal(raw, &p) // tolerant of empty/malformed; we have defaults |
| 40 | } |
| 41 | if p.CompletedOlderThanDays <= 0 { |
| 42 | p.CompletedOlderThanDays = 14 |
| 43 | } |
| 44 | if p.FailedOlderThanDays <= 0 { |
| 45 | p.FailedOlderThanDays = 30 |
| 46 | } |
| 47 | now := time.Now() |
| 48 | q := workerdb.New() |
| 49 | completedCutoff := pgtype.Timestamptz{Time: now.Add(-time.Duration(p.CompletedOlderThanDays) * 24 * time.Hour), Valid: true} |
| 50 | failedCutoff := pgtype.Timestamptz{Time: now.Add(-time.Duration(p.FailedOlderThanDays) * 24 * time.Hour), Valid: true} |
| 51 | |
| 52 | nC, err := q.PurgeCompletedJobs(ctx, deps.Pool, completedCutoff) |
| 53 | if err != nil { |
| 54 | return fmt.Errorf("purge completed: %w", err) |
| 55 | } |
| 56 | nF, err := q.PurgeFailedJobs(ctx, deps.Pool, failedCutoff) |
| 57 | if err != nil { |
| 58 | return fmt.Errorf("purge failed: %w", err) |
| 59 | } |
| 60 | deps.Logger.InfoContext(ctx, "jobs:purge", |
| 61 | "completed_deleted", nC, "failed_deleted", nF) |
| 62 | return nil |
| 63 | } |
| 64 | } |
| 65 |