tenseleyflow/shithub / 332cef0

Browse files

repos/sigverify: shared View projection for render + REST callers

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
332cef0895584d340816a348286df237c7e67c05
Parents
387246f
Tree
a1ebab3

1 changed file

StatusFile+-
A internal/repos/sigverify/view.go 123 0
internal/repos/sigverify/view.goadded
@@ -0,0 +1,123 @@
1
+// SPDX-License-Identifier: AGPL-3.0-or-later
2
+
3
+package sigverify
4
+
5
+import (
6
+	"context"
7
+	"time"
8
+
9
+	reposdb "github.com/tenseleyFlow/shithub/internal/repos/sqlc"
10
+)
11
+
12
+// View is a render-friendly projection of a commit_verification_cache
13
+// row. It carries everything the HTML badge partial and the REST
14
+// `verification` object need; both consumers translate to their own
15
+// surface shapes from here.
16
+//
17
+// "No cache row" maps to View{Verified: false, Reason: ReasonUnsigned}
18
+// — gh's documented behavior for commits we haven't verified yet.
19
+//
20
+// "Cache row exists but invalidated_at != null" also maps to the
21
+// unsigned-shaped default. Rationale: a stale row means the signing
22
+// key was revoked between verification and now; pretending it's
23
+// verified would be wrong. Treating it as "needs re-verify, render
24
+// as unsigned for now" lets the backfill worker catch up
25
+// asynchronously without the render path doing crypto work inline.
26
+type View struct {
27
+	Verified       bool
28
+	Reason         Reason
29
+	Signature      string
30
+	Payload        []byte
31
+	VerifiedAt     *time.Time
32
+	SignerUserID   int64
33
+	SignerSubkeyID int64
34
+}
35
+
36
+// UnsignedView is the canonical "no cache row" / "row invalidated"
37
+// shape. Mirrors gh's "unsigned" rendering.
38
+func UnsignedView() View {
39
+	return View{Verified: false, Reason: ReasonUnsigned}
40
+}
41
+
42
+// ViewFromRow translates a sqlc cache row into the View shape.
43
+// Honors the invalidated_at flag by returning UnsignedView for
44
+// stale rows.
45
+func ViewFromRow(row reposdb.CommitVerificationCache) View {
46
+	if row.InvalidatedAt.Valid {
47
+		return UnsignedView()
48
+	}
49
+	view := View{
50
+		Verified: row.Verified,
51
+		Reason:   Reason(row.Reason),
52
+		Payload:  row.Payload,
53
+	}
54
+	if row.SignatureArmored.Valid {
55
+		view.Signature = row.SignatureArmored.String
56
+	}
57
+	if row.VerifiedAt.Valid {
58
+		t := row.VerifiedAt.Time
59
+		view.VerifiedAt = &t
60
+	}
61
+	if row.SignerUserID.Valid {
62
+		view.SignerUserID = row.SignerUserID.Int64
63
+	}
64
+	if row.SignerSubkeyID.Valid {
65
+		view.SignerSubkeyID = row.SignerSubkeyID.Int64
66
+	}
67
+	return view
68
+}
69
+
70
+// LoadViewsForOIDs batch-reads verification rows for a set of commit
71
+// OIDs and returns a map keyed by OID. OIDs without a cache row are
72
+// absent from the map — the renderer treats absence as
73
+// UnsignedView() via the LookupView helper.
74
+//
75
+// Used by the commit-list render path (both HTML and REST).
76
+func LoadViewsForOIDs(ctx context.Context, db reposdb.DBTX, repoID int64, oids []string) (map[string]View, error) {
77
+	if len(oids) == 0 {
78
+		return map[string]View{}, nil
79
+	}
80
+	q := reposdb.New()
81
+	rows, err := q.GetCommitVerificationsForOIDs(ctx, db, reposdb.GetCommitVerificationsForOIDsParams{
82
+		RepoID: repoID,
83
+		Oids:   oids,
84
+	})
85
+	if err != nil {
86
+		return nil, err
87
+	}
88
+	out := make(map[string]View, len(rows))
89
+	for _, row := range rows {
90
+		out[row.CommitOid] = ViewFromRow(row)
91
+	}
92
+	return out, nil
93
+}
94
+
95
+// LoadView reads a single commit's verification row. Returns
96
+// UnsignedView() when no row exists or the row is invalidated.
97
+func LoadView(ctx context.Context, db reposdb.DBTX, repoID int64, oid string) (View, error) {
98
+	q := reposdb.New()
99
+	row, err := q.GetCommitVerification(ctx, db, reposdb.GetCommitVerificationParams{
100
+		RepoID:    repoID,
101
+		CommitOid: oid,
102
+	})
103
+	if err != nil {
104
+		// Distinguishing "not found" from "DB error" matters: not-
105
+		// found is the unsigned-default fast path; DB error should
106
+		// propagate so the caller can decide between fail-open
107
+		// (render anyway) and fail-closed (5xx). Caller handles via
108
+		// errors.Is(err, pgx.ErrNoRows).
109
+		return UnsignedView(), err
110
+	}
111
+	return ViewFromRow(row), nil
112
+}
113
+
114
+// LookupView returns the View for the given OID from a map, falling
115
+// back to UnsignedView() on miss. Template-friendly helper so the
116
+// partial doesn't have to do nil-checks inline.
117
+func LookupView(m map[string]View, oid string) View {
118
+	v, ok := m[oid]
119
+	if !ok {
120
+		return UnsignedView()
121
+	}
122
+	return v
123
+}