Go · 3042 bytes Raw Blame History
1 // SPDX-License-Identifier: AGPL-3.0-or-later
2
3 package jobs
4
5 import (
6 "context"
7 "encoding/json"
8 "log/slog"
9
10 "github.com/jackc/pgx/v5/pgxpool"
11
12 "github.com/tenseleyFlow/shithub/internal/auth/audit"
13 "github.com/tenseleyFlow/shithub/internal/infra/storage"
14 "github.com/tenseleyFlow/shithub/internal/orgs"
15 "github.com/tenseleyFlow/shithub/internal/repos/lifecycle"
16 reposdb "github.com/tenseleyFlow/shithub/internal/repos/sqlc"
17 "github.com/tenseleyFlow/shithub/internal/worker"
18 )
19
20 // LifecycleSweepDeps wires the periodic sweep handler.
21 type LifecycleSweepDeps struct {
22 Pool *pgxpool.Pool
23 RepoFS *storage.RepoFS
24 Audit *audit.Recorder
25 Logger *slog.Logger
26 }
27
28 // LifecycleSweep runs two related housekeeping passes in one job:
29 //
30 // 1. Hard-delete every repo whose deleted_at is past the grace window.
31 // Each repo gets the full lifecycle.HardDelete cascade — FS + DB
32 // + audit. We process inline rather than fanning out to one job
33 // per repo because hard-deletes are rare and the per-row cost is
34 // small.
35 // 2. Flip pending transfer requests past expires_at to "expired".
36 //
37 // Enqueue this kind from a cron timer (S37 ships the systemd cron
38 // service); for now the operator can `INSERT` a job manually or call
39 // it once at boot.
40 func LifecycleSweep(deps LifecycleSweepDeps) worker.Handler {
41 return func(ctx context.Context, _ json.RawMessage) error {
42 // 1. Hard-delete past-grace repos.
43 rq := reposdb.New()
44 ids, err := rq.ListRepoIDsPastSoftDeleteGrace(ctx, deps.Pool)
45 if err != nil {
46 return err
47 }
48 ldeps := lifecycle.Deps{
49 Pool: deps.Pool, RepoFS: deps.RepoFS,
50 Audit: deps.Audit, Logger: deps.Logger,
51 }
52 for _, id := range ids {
53 if err := ctx.Err(); err != nil {
54 return err
55 }
56 if err := lifecycle.HardDelete(ctx, ldeps, 0, id); err != nil {
57 deps.Logger.WarnContext(ctx, "lifecycle:sweep: hard delete failed",
58 "repo_id", id, "error", err)
59 // Keep going — one bad row shouldn't poison the sweep.
60 }
61 }
62
63 // 2. Expire pending transfers past their TTL.
64 n, err := lifecycle.ExpirePending(ctx, ldeps)
65 if err != nil {
66 return err
67 }
68 if n > 0 {
69 deps.Logger.InfoContext(ctx, "lifecycle:sweep: expired transfers", "count", n)
70 }
71
72 // 3. S30 — hard-delete orgs past their 14-day grace window.
73 // Cascade per-repo via lifecycle.HardDelete inside the org
74 // orchestrator. Same per-row failure-tolerant shape as the
75 // repo path above.
76 odeps := orgs.Deps{Pool: deps.Pool, Logger: deps.Logger, Audit: deps.Audit}
77 ohd := orgs.HardDeleteDeps{Deps: odeps, RepoFS: deps.RepoFS, Audit: deps.Audit}
78 orgIDs, err := orgs.ListPastGraceOrgIDs(ctx, odeps)
79 if err != nil {
80 deps.Logger.WarnContext(ctx, "lifecycle:sweep: list past-grace orgs", "error", err)
81 }
82 for _, oid := range orgIDs {
83 if err := ctx.Err(); err != nil {
84 return err
85 }
86 if err := orgs.HardDelete(ctx, ohd, oid); err != nil {
87 deps.Logger.WarnContext(ctx, "lifecycle:sweep: org hard delete failed",
88 "org_id", oid, "error", err)
89 }
90 }
91 return nil
92 }
93 }
94