tenseleyflow/shithub / ac27487

Browse files

S33: webhook sqlc package + queries

Authored by espadonne
SHA
ac274873ab7efa9e4c2ea9e6dcf7c29f78fbcf07
Parents
0699606
Tree
78d0b01

8 changed files

StatusFile+-
A internal/webhook/queries/cursor.sql 35 0
A internal/webhook/queries/webhooks.sql 170 0
A internal/webhook/sqlc/cursor.sql.go 116 0
A internal/webhook/sqlc/db.go 25 0
A internal/webhook/sqlc/models.go 1917 0
A internal/webhook/sqlc/querier.go 67 0
A internal/webhook/sqlc/webhooks.sql.go 684 0
M sqlc.yaml 16 0
internal/webhook/queries/cursor.sqladded
@@ -0,0 +1,35 @@
1
+-- SPDX-License-Identifier: AGPL-3.0-or-later
2
+--
3
+-- Webhook fanout reads the same domain_events / cursor surface that
4
+-- notifications do — but with its own consumer row so progress
5
+-- doesn't collide with notify_fanout.
6
+
7
+-- name: GetWebhookCursor :one
8
+SELECT consumer, last_event_id, updated_at
9
+FROM domain_events_processed
10
+WHERE consumer = $1;
11
+
12
+-- name: SetWebhookCursor :exec
13
+INSERT INTO domain_events_processed (consumer, last_event_id)
14
+VALUES ($1, $2)
15
+ON CONFLICT (consumer)
16
+DO UPDATE SET last_event_id = EXCLUDED.last_event_id,
17
+              updated_at    = now();
18
+
19
+-- name: ListUnprocessedDomainEvents :many
20
+SELECT id, actor_user_id, kind, repo_id, source_kind, source_id,
21
+       public, payload, created_at
22
+FROM domain_events
23
+WHERE id > $1
24
+ORDER BY id
25
+LIMIT $2;
26
+
27
+-- name: GetRepoOwnerKindForFanout :one
28
+-- Resolve a repo's owner so the fanout knows which webhooks to fire.
29
+-- Returns owner_kind 'user' or 'org' so the matcher knows what bucket
30
+-- to look in. (We use 'user' as a literal string here; the webhook
31
+-- machinery itself only stores 'repo'/'org' as owners — repo-owned
32
+-- webhooks attach to the repo regardless of who owns the repo.)
33
+SELECT owner_user_id, owner_org_id
34
+FROM repos
35
+WHERE id = $1;
internal/webhook/queries/webhooks.sqladded
@@ -0,0 +1,170 @@
1
+-- SPDX-License-Identifier: AGPL-3.0-or-later
2
+--
3
+-- Query surface for the webhook package. Naming mirrors the verb the
4
+-- caller uses; visibility / ownership filters are applied by the
5
+-- handler layer (the queries are deliberately narrow on key columns).
6
+
7
+-- name: CreateWebhook :one
8
+INSERT INTO webhooks (
9
+    owner_kind, owner_id, url, content_type, events,
10
+    secret_ciphertext, secret_nonce, active, ssl_verification,
11
+    auto_disable_threshold, created_by_user_id
12
+) VALUES (
13
+    sqlc.arg(owner_kind), sqlc.arg(owner_id), sqlc.arg(url),
14
+    sqlc.arg(content_type), sqlc.arg(events),
15
+    sqlc.arg(secret_ciphertext), sqlc.arg(secret_nonce),
16
+    sqlc.arg(active), sqlc.arg(ssl_verification),
17
+    sqlc.arg(auto_disable_threshold),
18
+    sqlc.narg(created_by_user_id)::bigint
19
+)
20
+RETURNING *;
21
+
22
+-- name: GetWebhookByID :one
23
+SELECT * FROM webhooks WHERE id = $1;
24
+
25
+-- name: ListWebhooksForOwner :many
26
+SELECT * FROM webhooks
27
+WHERE owner_kind = $1 AND owner_id = $2
28
+ORDER BY created_at DESC;
29
+
30
+-- name: ListActiveWebhooksForOwner :many
31
+-- Used by fanout to find subscribers for an event.
32
+SELECT * FROM webhooks
33
+WHERE owner_kind = $1 AND owner_id = $2
34
+  AND active = true AND disabled_at IS NULL;
35
+
36
+-- name: UpdateWebhook :exec
37
+UPDATE webhooks
38
+   SET url                    = $2,
39
+       content_type           = $3,
40
+       events                 = $4,
41
+       active                 = $5,
42
+       ssl_verification       = $6,
43
+       auto_disable_threshold = $7,
44
+       updated_at             = now()
45
+ WHERE id = $1;
46
+
47
+-- name: UpdateWebhookSecret :exec
48
+UPDATE webhooks
49
+   SET secret_ciphertext = $2,
50
+       secret_nonce      = $3,
51
+       updated_at        = now()
52
+ WHERE id = $1;
53
+
54
+-- name: DeleteWebhook :exec
55
+DELETE FROM webhooks WHERE id = $1;
56
+
57
+-- name: SetWebhookActive :exec
58
+-- Re-enables a previously auto-disabled webhook (UI affordance).
59
+-- Resets the failure counter and clears disabled_at/reason.
60
+UPDATE webhooks
61
+   SET active               = $2,
62
+       disabled_at          = NULL,
63
+       disabled_reason      = NULL,
64
+       consecutive_failures = 0,
65
+       updated_at           = now()
66
+ WHERE id = $1;
67
+
68
+-- name: RecordWebhookSuccess :exec
69
+UPDATE webhooks
70
+   SET consecutive_failures = 0,
71
+       last_success_at      = now(),
72
+       updated_at           = now()
73
+ WHERE id = $1;
74
+
75
+-- name: RecordWebhookFailure :one
76
+-- Increments the failure counter and reports the new value so the
77
+-- caller can decide whether to auto-disable.
78
+UPDATE webhooks
79
+   SET consecutive_failures = consecutive_failures + 1,
80
+       last_failure_at      = now(),
81
+       updated_at           = now()
82
+ WHERE id = $1
83
+RETURNING consecutive_failures, auto_disable_threshold;
84
+
85
+-- name: AutoDisableWebhook :exec
86
+UPDATE webhooks
87
+   SET disabled_at     = now(),
88
+       disabled_reason = $2,
89
+       updated_at      = now()
90
+ WHERE id = $1;
91
+
92
+-- name: CreateDelivery :one
93
+INSERT INTO webhook_deliveries (
94
+    webhook_id, event_kind, event_id, payload, request_headers,
95
+    request_body, attempt, max_attempts, next_retry_at, status,
96
+    idempotency_key, redeliver_of
97
+) VALUES (
98
+    sqlc.arg(webhook_id), sqlc.arg(event_kind), sqlc.narg(event_id)::bigint,
99
+    sqlc.arg(payload), sqlc.arg(request_headers), sqlc.arg(request_body),
100
+    sqlc.arg(attempt), sqlc.arg(max_attempts), sqlc.narg(next_retry_at)::timestamptz,
101
+    sqlc.arg(status), sqlc.arg(idempotency_key), sqlc.narg(redeliver_of)::bigint
102
+)
103
+RETURNING *;
104
+
105
+-- name: GetDeliveryByID :one
106
+SELECT * FROM webhook_deliveries WHERE id = $1;
107
+
108
+-- name: ListDeliveriesForWebhook :many
109
+SELECT id, webhook_id, event_kind, event_id, delivery_uuid, response_status,
110
+       response_truncated, started_at, completed_at, attempt, max_attempts,
111
+       next_retry_at, status, error_summary, redeliver_of
112
+FROM webhook_deliveries
113
+WHERE webhook_id = $1
114
+ORDER BY started_at DESC
115
+LIMIT $2;
116
+
117
+-- name: ClaimDueDeliveries :many
118
+-- Hot-path for the deliver worker: picks up to $1 rows that are
119
+-- pending or in retry-ready state, FOR UPDATE SKIP LOCKED so concurrent
120
+-- workers don't double-send. The deliverer marks the row 'pending'
121
+-- with a far-future next_retry_at while it works on it (defense
122
+-- against re-claim during a long HTTP timeout).
123
+SELECT id FROM webhook_deliveries
124
+WHERE status IN ('pending', 'failed_retry')
125
+  AND (next_retry_at IS NULL OR next_retry_at <= now())
126
+ORDER BY next_retry_at NULLS FIRST, id
127
+LIMIT $1
128
+FOR UPDATE SKIP LOCKED;
129
+
130
+-- name: MarkDeliverySucceeded :exec
131
+UPDATE webhook_deliveries
132
+   SET status              = 'succeeded',
133
+       response_status     = $2,
134
+       response_headers    = $3,
135
+       response_body       = $4,
136
+       response_truncated  = $5,
137
+       completed_at        = now(),
138
+       error_summary       = NULL
139
+ WHERE id = $1;
140
+
141
+-- name: MarkDeliveryRetry :exec
142
+UPDATE webhook_deliveries
143
+   SET status              = 'failed_retry',
144
+       attempt             = attempt + 1,
145
+       response_status     = sqlc.narg(response_status)::int,
146
+       response_headers    = sqlc.narg(response_headers)::jsonb,
147
+       response_body       = sqlc.narg(response_body)::bytea,
148
+       response_truncated  = sqlc.arg(response_truncated)::bool,
149
+       next_retry_at       = sqlc.arg(next_retry_at)::timestamptz,
150
+       error_summary       = sqlc.arg(error_summary)::text
151
+ WHERE id = sqlc.arg(id)::bigint;
152
+
153
+-- name: MarkDeliveryPermanentFailure :exec
154
+UPDATE webhook_deliveries
155
+   SET status              = 'failed_permanent',
156
+       response_status     = sqlc.narg(response_status)::int,
157
+       response_headers    = sqlc.narg(response_headers)::jsonb,
158
+       response_body       = sqlc.narg(response_body)::bytea,
159
+       response_truncated  = sqlc.arg(response_truncated)::bool,
160
+       completed_at        = now(),
161
+       error_summary       = sqlc.arg(error_summary)::text
162
+ WHERE id = sqlc.arg(id)::bigint;
163
+
164
+-- name: PurgeOldDeliveries :execrows
165
+-- Cron: drops terminal deliveries older than the retention window.
166
+-- pending/failed_retry rows are left alone so an in-flight retry isn't
167
+-- aborted out from under the worker.
168
+DELETE FROM webhook_deliveries
169
+WHERE status IN ('succeeded', 'failed_permanent')
170
+  AND completed_at < now() - sqlc.arg(retention)::interval;
internal/webhook/sqlc/cursor.sql.goadded
@@ -0,0 +1,116 @@
1
+// Code generated by sqlc. DO NOT EDIT.
2
+// versions:
3
+//   sqlc v1.31.1
4
+// source: cursor.sql
5
+
6
+package webhookdb
7
+
8
+import (
9
+	"context"
10
+
11
+	"github.com/jackc/pgx/v5/pgtype"
12
+)
13
+
14
+const getRepoOwnerKindForFanout = `-- name: GetRepoOwnerKindForFanout :one
15
+SELECT owner_user_id, owner_org_id
16
+FROM repos
17
+WHERE id = $1
18
+`
19
+
20
+type GetRepoOwnerKindForFanoutRow struct {
21
+	OwnerUserID pgtype.Int8
22
+	OwnerOrgID  pgtype.Int8
23
+}
24
+
25
+// Resolve a repo's owner so the fanout knows which webhooks to fire.
26
+// Returns owner_kind 'user' or 'org' so the matcher knows what bucket
27
+// to look in. (We use 'user' as a literal string here; the webhook
28
+// machinery itself only stores 'repo'/'org' as owners — repo-owned
29
+// webhooks attach to the repo regardless of who owns the repo.)
30
+func (q *Queries) GetRepoOwnerKindForFanout(ctx context.Context, db DBTX, id int64) (GetRepoOwnerKindForFanoutRow, error) {
31
+	row := db.QueryRow(ctx, getRepoOwnerKindForFanout, id)
32
+	var i GetRepoOwnerKindForFanoutRow
33
+	err := row.Scan(&i.OwnerUserID, &i.OwnerOrgID)
34
+	return i, err
35
+}
36
+
37
+const getWebhookCursor = `-- name: GetWebhookCursor :one
38
+
39
+SELECT consumer, last_event_id, updated_at
40
+FROM domain_events_processed
41
+WHERE consumer = $1
42
+`
43
+
44
+// SPDX-License-Identifier: AGPL-3.0-or-later
45
+//
46
+// Webhook fanout reads the same domain_events / cursor surface that
47
+// notifications do — but with its own consumer row so progress
48
+// doesn't collide with notify_fanout.
49
+func (q *Queries) GetWebhookCursor(ctx context.Context, db DBTX, consumer string) (DomainEventsProcessed, error) {
50
+	row := db.QueryRow(ctx, getWebhookCursor, consumer)
51
+	var i DomainEventsProcessed
52
+	err := row.Scan(&i.Consumer, &i.LastEventID, &i.UpdatedAt)
53
+	return i, err
54
+}
55
+
56
+const listUnprocessedDomainEvents = `-- name: ListUnprocessedDomainEvents :many
57
+SELECT id, actor_user_id, kind, repo_id, source_kind, source_id,
58
+       public, payload, created_at
59
+FROM domain_events
60
+WHERE id > $1
61
+ORDER BY id
62
+LIMIT $2
63
+`
64
+
65
+type ListUnprocessedDomainEventsParams struct {
66
+	ID    int64
67
+	Limit int32
68
+}
69
+
70
+func (q *Queries) ListUnprocessedDomainEvents(ctx context.Context, db DBTX, arg ListUnprocessedDomainEventsParams) ([]DomainEvent, error) {
71
+	rows, err := db.Query(ctx, listUnprocessedDomainEvents, arg.ID, arg.Limit)
72
+	if err != nil {
73
+		return nil, err
74
+	}
75
+	defer rows.Close()
76
+	items := []DomainEvent{}
77
+	for rows.Next() {
78
+		var i DomainEvent
79
+		if err := rows.Scan(
80
+			&i.ID,
81
+			&i.ActorUserID,
82
+			&i.Kind,
83
+			&i.RepoID,
84
+			&i.SourceKind,
85
+			&i.SourceID,
86
+			&i.Public,
87
+			&i.Payload,
88
+			&i.CreatedAt,
89
+		); err != nil {
90
+			return nil, err
91
+		}
92
+		items = append(items, i)
93
+	}
94
+	if err := rows.Err(); err != nil {
95
+		return nil, err
96
+	}
97
+	return items, nil
98
+}
99
+
100
+const setWebhookCursor = `-- name: SetWebhookCursor :exec
101
+INSERT INTO domain_events_processed (consumer, last_event_id)
102
+VALUES ($1, $2)
103
+ON CONFLICT (consumer)
104
+DO UPDATE SET last_event_id = EXCLUDED.last_event_id,
105
+              updated_at    = now()
106
+`
107
+
108
+type SetWebhookCursorParams struct {
109
+	Consumer    string
110
+	LastEventID int64
111
+}
112
+
113
+func (q *Queries) SetWebhookCursor(ctx context.Context, db DBTX, arg SetWebhookCursorParams) error {
114
+	_, err := db.Exec(ctx, setWebhookCursor, arg.Consumer, arg.LastEventID)
115
+	return err
116
+}
internal/webhook/sqlc/db.goadded
@@ -0,0 +1,25 @@
1
+// Code generated by sqlc. DO NOT EDIT.
2
+// versions:
3
+//   sqlc v1.31.1
4
+
5
+package webhookdb
6
+
7
+import (
8
+	"context"
9
+
10
+	"github.com/jackc/pgx/v5"
11
+	"github.com/jackc/pgx/v5/pgconn"
12
+)
13
+
14
+type DBTX interface {
15
+	Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error)
16
+	Query(context.Context, string, ...interface{}) (pgx.Rows, error)
17
+	QueryRow(context.Context, string, ...interface{}) pgx.Row
18
+}
19
+
20
+func New() *Queries {
21
+	return &Queries{}
22
+}
23
+
24
+type Queries struct {
25
+}
internal/webhook/sqlc/models.goadded
1917 lines changed — click to load
@@ -0,0 +1,1917 @@
1
+// Code generated by sqlc. DO NOT EDIT.
2
+// versions:
3
+//   sqlc v1.31.1
4
+
5
+package webhookdb
6
+
7
+import (
8
+	"database/sql/driver"
9
+	"fmt"
10
+	"net/netip"
11
+
12
+	"github.com/jackc/pgx/v5/pgtype"
13
+)
14
+
15
+type CheckConclusion string
16
+
17
+const (
18
+	CheckConclusionSuccess        CheckConclusion = "success"
19
+	CheckConclusionFailure        CheckConclusion = "failure"
20
+	CheckConclusionNeutral        CheckConclusion = "neutral"
21
+	CheckConclusionCancelled      CheckConclusion = "cancelled"
22
+	CheckConclusionSkipped        CheckConclusion = "skipped"
23
+	CheckConclusionTimedOut       CheckConclusion = "timed_out"
24
+	CheckConclusionActionRequired CheckConclusion = "action_required"
25
+	CheckConclusionStale          CheckConclusion = "stale"
26
+)
27
+
28
+func (e *CheckConclusion) Scan(src interface{}) error {
29
+	switch s := src.(type) {
30
+	case []byte:
31
+		*e = CheckConclusion(s)
32
+	case string:
33
+		*e = CheckConclusion(s)
34
+	default:
35
+		return fmt.Errorf("unsupported scan type for CheckConclusion: %T", src)
36
+	}
37
+	return nil
38
+}
39
+
40
+type NullCheckConclusion struct {
41
+	CheckConclusion CheckConclusion
42
+	Valid           bool // Valid is true if CheckConclusion is not NULL
43
+}
44
+
45
+// Scan implements the Scanner interface.
46
+func (ns *NullCheckConclusion) Scan(value interface{}) error {
47
+	if value == nil {
48
+		ns.CheckConclusion, ns.Valid = "", false
49
+		return nil
50
+	}
51
+	ns.Valid = true
52
+	return ns.CheckConclusion.Scan(value)
53
+}
54
+
55
+// Value implements the driver Valuer interface.
56
+func (ns NullCheckConclusion) Value() (driver.Value, error) {
57
+	if !ns.Valid {
58
+		return nil, nil
59
+	}
60
+	return string(ns.CheckConclusion), nil
61
+}
62
+
63
+type CheckStatus string
64
+
65
+const (
66
+	CheckStatusQueued     CheckStatus = "queued"
67
+	CheckStatusInProgress CheckStatus = "in_progress"
68
+	CheckStatusCompleted  CheckStatus = "completed"
69
+	CheckStatusPending    CheckStatus = "pending"
70
+)
71
+
72
+func (e *CheckStatus) Scan(src interface{}) error {
73
+	switch s := src.(type) {
74
+	case []byte:
75
+		*e = CheckStatus(s)
76
+	case string:
77
+		*e = CheckStatus(s)
78
+	default:
79
+		return fmt.Errorf("unsupported scan type for CheckStatus: %T", src)
80
+	}
81
+	return nil
82
+}
83
+
84
+type NullCheckStatus struct {
85
+	CheckStatus CheckStatus
86
+	Valid       bool // Valid is true if CheckStatus is not NULL
87
+}
88
+
89
+// Scan implements the Scanner interface.
90
+func (ns *NullCheckStatus) Scan(value interface{}) error {
91
+	if value == nil {
92
+		ns.CheckStatus, ns.Valid = "", false
93
+		return nil
94
+	}
95
+	ns.Valid = true
96
+	return ns.CheckStatus.Scan(value)
97
+}
98
+
99
+// Value implements the driver Valuer interface.
100
+func (ns NullCheckStatus) Value() (driver.Value, error) {
101
+	if !ns.Valid {
102
+		return nil, nil
103
+	}
104
+	return string(ns.CheckStatus), nil
105
+}
106
+
107
+type CollabRole string
108
+
109
+const (
110
+	CollabRoleRead     CollabRole = "read"
111
+	CollabRoleTriage   CollabRole = "triage"
112
+	CollabRoleWrite    CollabRole = "write"
113
+	CollabRoleMaintain CollabRole = "maintain"
114
+	CollabRoleAdmin    CollabRole = "admin"
115
+)
116
+
117
+func (e *CollabRole) Scan(src interface{}) error {
118
+	switch s := src.(type) {
119
+	case []byte:
120
+		*e = CollabRole(s)
121
+	case string:
122
+		*e = CollabRole(s)
123
+	default:
124
+		return fmt.Errorf("unsupported scan type for CollabRole: %T", src)
125
+	}
126
+	return nil
127
+}
128
+
129
+type NullCollabRole struct {
130
+	CollabRole CollabRole
131
+	Valid      bool // Valid is true if CollabRole is not NULL
132
+}
133
+
134
+// Scan implements the Scanner interface.
135
+func (ns *NullCollabRole) Scan(value interface{}) error {
136
+	if value == nil {
137
+		ns.CollabRole, ns.Valid = "", false
138
+		return nil
139
+	}
140
+	ns.Valid = true
141
+	return ns.CollabRole.Scan(value)
142
+}
143
+
144
+// Value implements the driver Valuer interface.
145
+func (ns NullCollabRole) Value() (driver.Value, error) {
146
+	if !ns.Valid {
147
+		return nil, nil
148
+	}
149
+	return string(ns.CollabRole), nil
150
+}
151
+
152
+type IssueKind string
153
+
154
+const (
155
+	IssueKindIssue IssueKind = "issue"
156
+	IssueKindPr    IssueKind = "pr"
157
+)
158
+
159
+func (e *IssueKind) Scan(src interface{}) error {
160
+	switch s := src.(type) {
161
+	case []byte:
162
+		*e = IssueKind(s)
163
+	case string:
164
+		*e = IssueKind(s)
165
+	default:
166
+		return fmt.Errorf("unsupported scan type for IssueKind: %T", src)
167
+	}
168
+	return nil
169
+}
170
+
171
+type NullIssueKind struct {
172
+	IssueKind IssueKind
173
+	Valid     bool // Valid is true if IssueKind is not NULL
174
+}
175
+
176
+// Scan implements the Scanner interface.
177
+func (ns *NullIssueKind) Scan(value interface{}) error {
178
+	if value == nil {
179
+		ns.IssueKind, ns.Valid = "", false
180
+		return nil
181
+	}
182
+	ns.Valid = true
183
+	return ns.IssueKind.Scan(value)
184
+}
185
+
186
+// Value implements the driver Valuer interface.
187
+func (ns NullIssueKind) Value() (driver.Value, error) {
188
+	if !ns.Valid {
189
+		return nil, nil
190
+	}
191
+	return string(ns.IssueKind), nil
192
+}
193
+
194
+type IssueRefSource string
195
+
196
+const (
197
+	IssueRefSourceCommentBody   IssueRefSource = "comment_body"
198
+	IssueRefSourceIssueBody     IssueRefSource = "issue_body"
199
+	IssueRefSourceCommitMessage IssueRefSource = "commit_message"
200
+)
201
+
202
+func (e *IssueRefSource) Scan(src interface{}) error {
203
+	switch s := src.(type) {
204
+	case []byte:
205
+		*e = IssueRefSource(s)
206
+	case string:
207
+		*e = IssueRefSource(s)
208
+	default:
209
+		return fmt.Errorf("unsupported scan type for IssueRefSource: %T", src)
210
+	}
211
+	return nil
212
+}
213
+
214
+type NullIssueRefSource struct {
215
+	IssueRefSource IssueRefSource
216
+	Valid          bool // Valid is true if IssueRefSource is not NULL
217
+}
218
+
219
+// Scan implements the Scanner interface.
220
+func (ns *NullIssueRefSource) Scan(value interface{}) error {
221
+	if value == nil {
222
+		ns.IssueRefSource, ns.Valid = "", false
223
+		return nil
224
+	}
225
+	ns.Valid = true
226
+	return ns.IssueRefSource.Scan(value)
227
+}
228
+
229
+// Value implements the driver Valuer interface.
230
+func (ns NullIssueRefSource) Value() (driver.Value, error) {
231
+	if !ns.Valid {
232
+		return nil, nil
233
+	}
234
+	return string(ns.IssueRefSource), nil
235
+}
236
+
237
+type IssueState string
238
+
239
+const (
240
+	IssueStateOpen   IssueState = "open"
241
+	IssueStateClosed IssueState = "closed"
242
+)
243
+
244
+func (e *IssueState) Scan(src interface{}) error {
245
+	switch s := src.(type) {
246
+	case []byte:
247
+		*e = IssueState(s)
248
+	case string:
249
+		*e = IssueState(s)
250
+	default:
251
+		return fmt.Errorf("unsupported scan type for IssueState: %T", src)
252
+	}
253
+	return nil
254
+}
255
+
256
+type NullIssueState struct {
257
+	IssueState IssueState
258
+	Valid      bool // Valid is true if IssueState is not NULL
259
+}
260
+
261
+// Scan implements the Scanner interface.
262
+func (ns *NullIssueState) Scan(value interface{}) error {
263
+	if value == nil {
264
+		ns.IssueState, ns.Valid = "", false
265
+		return nil
266
+	}
267
+	ns.Valid = true
268
+	return ns.IssueState.Scan(value)
269
+}
270
+
271
+// Value implements the driver Valuer interface.
272
+func (ns NullIssueState) Value() (driver.Value, error) {
273
+	if !ns.Valid {
274
+		return nil, nil
275
+	}
276
+	return string(ns.IssueState), nil
277
+}
278
+
279
+type IssueStateReason string
280
+
281
+const (
282
+	IssueStateReasonCompleted  IssueStateReason = "completed"
283
+	IssueStateReasonNotPlanned IssueStateReason = "not_planned"
284
+	IssueStateReasonReopened   IssueStateReason = "reopened"
285
+	IssueStateReasonDuplicate  IssueStateReason = "duplicate"
286
+)
287
+
288
+func (e *IssueStateReason) Scan(src interface{}) error {
289
+	switch s := src.(type) {
290
+	case []byte:
291
+		*e = IssueStateReason(s)
292
+	case string:
293
+		*e = IssueStateReason(s)
294
+	default:
295
+		return fmt.Errorf("unsupported scan type for IssueStateReason: %T", src)
296
+	}
297
+	return nil
298
+}
299
+
300
+type NullIssueStateReason struct {
301
+	IssueStateReason IssueStateReason
302
+	Valid            bool // Valid is true if IssueStateReason is not NULL
303
+}
304
+
305
+// Scan implements the Scanner interface.
306
+func (ns *NullIssueStateReason) Scan(value interface{}) error {
307
+	if value == nil {
308
+		ns.IssueStateReason, ns.Valid = "", false
309
+		return nil
310
+	}
311
+	ns.Valid = true
312
+	return ns.IssueStateReason.Scan(value)
313
+}
314
+
315
+// Value implements the driver Valuer interface.
316
+func (ns NullIssueStateReason) Value() (driver.Value, error) {
317
+	if !ns.Valid {
318
+		return nil, nil
319
+	}
320
+	return string(ns.IssueStateReason), nil
321
+}
322
+
323
+type MilestoneState string
324
+
325
+const (
326
+	MilestoneStateOpen   MilestoneState = "open"
327
+	MilestoneStateClosed MilestoneState = "closed"
328
+)
329
+
330
+func (e *MilestoneState) Scan(src interface{}) error {
331
+	switch s := src.(type) {
332
+	case []byte:
333
+		*e = MilestoneState(s)
334
+	case string:
335
+		*e = MilestoneState(s)
336
+	default:
337
+		return fmt.Errorf("unsupported scan type for MilestoneState: %T", src)
338
+	}
339
+	return nil
340
+}
341
+
342
+type NullMilestoneState struct {
343
+	MilestoneState MilestoneState
344
+	Valid          bool // Valid is true if MilestoneState is not NULL
345
+}
346
+
347
+// Scan implements the Scanner interface.
348
+func (ns *NullMilestoneState) Scan(value interface{}) error {
349
+	if value == nil {
350
+		ns.MilestoneState, ns.Valid = "", false
351
+		return nil
352
+	}
353
+	ns.Valid = true
354
+	return ns.MilestoneState.Scan(value)
355
+}
356
+
357
+// Value implements the driver Valuer interface.
358
+func (ns NullMilestoneState) Value() (driver.Value, error) {
359
+	if !ns.Valid {
360
+		return nil, nil
361
+	}
362
+	return string(ns.MilestoneState), nil
363
+}
364
+
365
+type NotificationThreadKind string
366
+
367
+const (
368
+	NotificationThreadKindIssue NotificationThreadKind = "issue"
369
+	NotificationThreadKindPr    NotificationThreadKind = "pr"
370
+)
371
+
372
+func (e *NotificationThreadKind) Scan(src interface{}) error {
373
+	switch s := src.(type) {
374
+	case []byte:
375
+		*e = NotificationThreadKind(s)
376
+	case string:
377
+		*e = NotificationThreadKind(s)
378
+	default:
379
+		return fmt.Errorf("unsupported scan type for NotificationThreadKind: %T", src)
380
+	}
381
+	return nil
382
+}
383
+
384
+type NullNotificationThreadKind struct {
385
+	NotificationThreadKind NotificationThreadKind
386
+	Valid                  bool // Valid is true if NotificationThreadKind is not NULL
387
+}
388
+
389
+// Scan implements the Scanner interface.
390
+func (ns *NullNotificationThreadKind) Scan(value interface{}) error {
391
+	if value == nil {
392
+		ns.NotificationThreadKind, ns.Valid = "", false
393
+		return nil
394
+	}
395
+	ns.Valid = true
396
+	return ns.NotificationThreadKind.Scan(value)
397
+}
398
+
399
+// Value implements the driver Valuer interface.
400
+func (ns NullNotificationThreadKind) Value() (driver.Value, error) {
401
+	if !ns.Valid {
402
+		return nil, nil
403
+	}
404
+	return string(ns.NotificationThreadKind), nil
405
+}
406
+
407
+type OrgPlan string
408
+
409
+const (
410
+	OrgPlanFree       OrgPlan = "free"
411
+	OrgPlanTeam       OrgPlan = "team"
412
+	OrgPlanEnterprise OrgPlan = "enterprise"
413
+)
414
+
415
+func (e *OrgPlan) Scan(src interface{}) error {
416
+	switch s := src.(type) {
417
+	case []byte:
418
+		*e = OrgPlan(s)
419
+	case string:
420
+		*e = OrgPlan(s)
421
+	default:
422
+		return fmt.Errorf("unsupported scan type for OrgPlan: %T", src)
423
+	}
424
+	return nil
425
+}
426
+
427
+type NullOrgPlan struct {
428
+	OrgPlan OrgPlan
429
+	Valid   bool // Valid is true if OrgPlan is not NULL
430
+}
431
+
432
+// Scan implements the Scanner interface.
433
+func (ns *NullOrgPlan) Scan(value interface{}) error {
434
+	if value == nil {
435
+		ns.OrgPlan, ns.Valid = "", false
436
+		return nil
437
+	}
438
+	ns.Valid = true
439
+	return ns.OrgPlan.Scan(value)
440
+}
441
+
442
+// Value implements the driver Valuer interface.
443
+func (ns NullOrgPlan) Value() (driver.Value, error) {
444
+	if !ns.Valid {
445
+		return nil, nil
446
+	}
447
+	return string(ns.OrgPlan), nil
448
+}
449
+
450
+type OrgRole string
451
+
452
+const (
453
+	OrgRoleOwner  OrgRole = "owner"
454
+	OrgRoleMember OrgRole = "member"
455
+)
456
+
457
+func (e *OrgRole) Scan(src interface{}) error {
458
+	switch s := src.(type) {
459
+	case []byte:
460
+		*e = OrgRole(s)
461
+	case string:
462
+		*e = OrgRole(s)
463
+	default:
464
+		return fmt.Errorf("unsupported scan type for OrgRole: %T", src)
465
+	}
466
+	return nil
467
+}
468
+
469
+type NullOrgRole struct {
470
+	OrgRole OrgRole
471
+	Valid   bool // Valid is true if OrgRole is not NULL
472
+}
473
+
474
+// Scan implements the Scanner interface.
475
+func (ns *NullOrgRole) Scan(value interface{}) error {
476
+	if value == nil {
477
+		ns.OrgRole, ns.Valid = "", false
478
+		return nil
479
+	}
480
+	ns.Valid = true
481
+	return ns.OrgRole.Scan(value)
482
+}
483
+
484
+// Value implements the driver Valuer interface.
485
+func (ns NullOrgRole) Value() (driver.Value, error) {
486
+	if !ns.Valid {
487
+		return nil, nil
488
+	}
489
+	return string(ns.OrgRole), nil
490
+}
491
+
492
+type PrFileStatus string
493
+
494
+const (
495
+	PrFileStatusAdded    PrFileStatus = "added"
496
+	PrFileStatusModified PrFileStatus = "modified"
497
+	PrFileStatusDeleted  PrFileStatus = "deleted"
498
+	PrFileStatusRenamed  PrFileStatus = "renamed"
499
+	PrFileStatusCopied   PrFileStatus = "copied"
500
+)
501
+
502
+func (e *PrFileStatus) Scan(src interface{}) error {
503
+	switch s := src.(type) {
504
+	case []byte:
505
+		*e = PrFileStatus(s)
506
+	case string:
507
+		*e = PrFileStatus(s)
508
+	default:
509
+		return fmt.Errorf("unsupported scan type for PrFileStatus: %T", src)
510
+	}
511
+	return nil
512
+}
513
+
514
+type NullPrFileStatus struct {
515
+	PrFileStatus PrFileStatus
516
+	Valid        bool // Valid is true if PrFileStatus is not NULL
517
+}
518
+
519
+// Scan implements the Scanner interface.
520
+func (ns *NullPrFileStatus) Scan(value interface{}) error {
521
+	if value == nil {
522
+		ns.PrFileStatus, ns.Valid = "", false
523
+		return nil
524
+	}
525
+	ns.Valid = true
526
+	return ns.PrFileStatus.Scan(value)
527
+}
528
+
529
+// Value implements the driver Valuer interface.
530
+func (ns NullPrFileStatus) Value() (driver.Value, error) {
531
+	if !ns.Valid {
532
+		return nil, nil
533
+	}
534
+	return string(ns.PrFileStatus), nil
535
+}
536
+
537
+type PrMergeMethod string
538
+
539
+const (
540
+	PrMergeMethodMerge  PrMergeMethod = "merge"
541
+	PrMergeMethodSquash PrMergeMethod = "squash"
542
+	PrMergeMethodRebase PrMergeMethod = "rebase"
543
+)
544
+
545
+func (e *PrMergeMethod) Scan(src interface{}) error {
546
+	switch s := src.(type) {
547
+	case []byte:
548
+		*e = PrMergeMethod(s)
549
+	case string:
550
+		*e = PrMergeMethod(s)
551
+	default:
552
+		return fmt.Errorf("unsupported scan type for PrMergeMethod: %T", src)
553
+	}
554
+	return nil
555
+}
556
+
557
+type NullPrMergeMethod struct {
558
+	PrMergeMethod PrMergeMethod
559
+	Valid         bool // Valid is true if PrMergeMethod is not NULL
560
+}
561
+
562
+// Scan implements the Scanner interface.
563
+func (ns *NullPrMergeMethod) Scan(value interface{}) error {
564
+	if value == nil {
565
+		ns.PrMergeMethod, ns.Valid = "", false
566
+		return nil
567
+	}
568
+	ns.Valid = true
569
+	return ns.PrMergeMethod.Scan(value)
570
+}
571
+
572
+// Value implements the driver Valuer interface.
573
+func (ns NullPrMergeMethod) Value() (driver.Value, error) {
574
+	if !ns.Valid {
575
+		return nil, nil
576
+	}
577
+	return string(ns.PrMergeMethod), nil
578
+}
579
+
580
+type PrMergeableState string
581
+
582
+const (
583
+	PrMergeableStateUnknown PrMergeableState = "unknown"
584
+	PrMergeableStateClean   PrMergeableState = "clean"
585
+	PrMergeableStateDirty   PrMergeableState = "dirty"
586
+	PrMergeableStateBlocked PrMergeableState = "blocked"
587
+	PrMergeableStateBehind  PrMergeableState = "behind"
588
+)
589
+
590
+func (e *PrMergeableState) Scan(src interface{}) error {
591
+	switch s := src.(type) {
592
+	case []byte:
593
+		*e = PrMergeableState(s)
594
+	case string:
595
+		*e = PrMergeableState(s)
596
+	default:
597
+		return fmt.Errorf("unsupported scan type for PrMergeableState: %T", src)
598
+	}
599
+	return nil
600
+}
601
+
602
+type NullPrMergeableState struct {
603
+	PrMergeableState PrMergeableState
604
+	Valid            bool // Valid is true if PrMergeableState is not NULL
605
+}
606
+
607
+// Scan implements the Scanner interface.
608
+func (ns *NullPrMergeableState) Scan(value interface{}) error {
609
+	if value == nil {
610
+		ns.PrMergeableState, ns.Valid = "", false
611
+		return nil
612
+	}
613
+	ns.Valid = true
614
+	return ns.PrMergeableState.Scan(value)
615
+}
616
+
617
+// Value implements the driver Valuer interface.
618
+func (ns NullPrMergeableState) Value() (driver.Value, error) {
619
+	if !ns.Valid {
620
+		return nil, nil
621
+	}
622
+	return string(ns.PrMergeableState), nil
623
+}
624
+
625
+type PrReviewSide string
626
+
627
+const (
628
+	PrReviewSideLeft  PrReviewSide = "left"
629
+	PrReviewSideRight PrReviewSide = "right"
630
+)
631
+
632
+func (e *PrReviewSide) Scan(src interface{}) error {
633
+	switch s := src.(type) {
634
+	case []byte:
635
+		*e = PrReviewSide(s)
636
+	case string:
637
+		*e = PrReviewSide(s)
638
+	default:
639
+		return fmt.Errorf("unsupported scan type for PrReviewSide: %T", src)
640
+	}
641
+	return nil
642
+}
643
+
644
+type NullPrReviewSide struct {
645
+	PrReviewSide PrReviewSide
646
+	Valid        bool // Valid is true if PrReviewSide is not NULL
647
+}
648
+
649
+// Scan implements the Scanner interface.
650
+func (ns *NullPrReviewSide) Scan(value interface{}) error {
651
+	if value == nil {
652
+		ns.PrReviewSide, ns.Valid = "", false
653
+		return nil
654
+	}
655
+	ns.Valid = true
656
+	return ns.PrReviewSide.Scan(value)
657
+}
658
+
659
+// Value implements the driver Valuer interface.
660
+func (ns NullPrReviewSide) Value() (driver.Value, error) {
661
+	if !ns.Valid {
662
+		return nil, nil
663
+	}
664
+	return string(ns.PrReviewSide), nil
665
+}
666
+
667
+type PrReviewState string
668
+
669
+const (
670
+	PrReviewStateComment        PrReviewState = "comment"
671
+	PrReviewStateApprove        PrReviewState = "approve"
672
+	PrReviewStateRequestChanges PrReviewState = "request_changes"
673
+)
674
+
675
+func (e *PrReviewState) Scan(src interface{}) error {
676
+	switch s := src.(type) {
677
+	case []byte:
678
+		*e = PrReviewState(s)
679
+	case string:
680
+		*e = PrReviewState(s)
681
+	default:
682
+		return fmt.Errorf("unsupported scan type for PrReviewState: %T", src)
683
+	}
684
+	return nil
685
+}
686
+
687
+type NullPrReviewState struct {
688
+	PrReviewState PrReviewState
689
+	Valid         bool // Valid is true if PrReviewState is not NULL
690
+}
691
+
692
+// Scan implements the Scanner interface.
693
+func (ns *NullPrReviewState) Scan(value interface{}) error {
694
+	if value == nil {
695
+		ns.PrReviewState, ns.Valid = "", false
696
+		return nil
697
+	}
698
+	ns.Valid = true
699
+	return ns.PrReviewState.Scan(value)
700
+}
701
+
702
+// Value implements the driver Valuer interface.
703
+func (ns NullPrReviewState) Value() (driver.Value, error) {
704
+	if !ns.Valid {
705
+		return nil, nil
706
+	}
707
+	return string(ns.PrReviewState), nil
708
+}
709
+
710
+type PrincipalKind string
711
+
712
+const (
713
+	PrincipalKindUser PrincipalKind = "user"
714
+	PrincipalKindOrg  PrincipalKind = "org"
715
+)
716
+
717
+func (e *PrincipalKind) Scan(src interface{}) error {
718
+	switch s := src.(type) {
719
+	case []byte:
720
+		*e = PrincipalKind(s)
721
+	case string:
722
+		*e = PrincipalKind(s)
723
+	default:
724
+		return fmt.Errorf("unsupported scan type for PrincipalKind: %T", src)
725
+	}
726
+	return nil
727
+}
728
+
729
+type NullPrincipalKind struct {
730
+	PrincipalKind PrincipalKind
731
+	Valid         bool // Valid is true if PrincipalKind is not NULL
732
+}
733
+
734
+// Scan implements the Scanner interface.
735
+func (ns *NullPrincipalKind) Scan(value interface{}) error {
736
+	if value == nil {
737
+		ns.PrincipalKind, ns.Valid = "", false
738
+		return nil
739
+	}
740
+	ns.Valid = true
741
+	return ns.PrincipalKind.Scan(value)
742
+}
743
+
744
+// Value implements the driver Valuer interface.
745
+func (ns NullPrincipalKind) Value() (driver.Value, error) {
746
+	if !ns.Valid {
747
+		return nil, nil
748
+	}
749
+	return string(ns.PrincipalKind), nil
750
+}
751
+
752
+type RepoInitStatus string
753
+
754
+const (
755
+	RepoInitStatusInitialized RepoInitStatus = "initialized"
756
+	RepoInitStatusInitPending RepoInitStatus = "init_pending"
757
+	RepoInitStatusInitFailed  RepoInitStatus = "init_failed"
758
+)
759
+
760
+func (e *RepoInitStatus) Scan(src interface{}) error {
761
+	switch s := src.(type) {
762
+	case []byte:
763
+		*e = RepoInitStatus(s)
764
+	case string:
765
+		*e = RepoInitStatus(s)
766
+	default:
767
+		return fmt.Errorf("unsupported scan type for RepoInitStatus: %T", src)
768
+	}
769
+	return nil
770
+}
771
+
772
+type NullRepoInitStatus struct {
773
+	RepoInitStatus RepoInitStatus
774
+	Valid          bool // Valid is true if RepoInitStatus is not NULL
775
+}
776
+
777
+// Scan implements the Scanner interface.
778
+func (ns *NullRepoInitStatus) Scan(value interface{}) error {
779
+	if value == nil {
780
+		ns.RepoInitStatus, ns.Valid = "", false
781
+		return nil
782
+	}
783
+	ns.Valid = true
784
+	return ns.RepoInitStatus.Scan(value)
785
+}
786
+
787
+// Value implements the driver Valuer interface.
788
+func (ns NullRepoInitStatus) Value() (driver.Value, error) {
789
+	if !ns.Valid {
790
+		return nil, nil
791
+	}
792
+	return string(ns.RepoInitStatus), nil
793
+}
794
+
795
+type RepoVisibility string
796
+
797
+const (
798
+	RepoVisibilityPublic  RepoVisibility = "public"
799
+	RepoVisibilityPrivate RepoVisibility = "private"
800
+)
801
+
802
+func (e *RepoVisibility) Scan(src interface{}) error {
803
+	switch s := src.(type) {
804
+	case []byte:
805
+		*e = RepoVisibility(s)
806
+	case string:
807
+		*e = RepoVisibility(s)
808
+	default:
809
+		return fmt.Errorf("unsupported scan type for RepoVisibility: %T", src)
810
+	}
811
+	return nil
812
+}
813
+
814
+type NullRepoVisibility struct {
815
+	RepoVisibility RepoVisibility
816
+	Valid          bool // Valid is true if RepoVisibility is not NULL
817
+}
818
+
819
+// Scan implements the Scanner interface.
820
+func (ns *NullRepoVisibility) Scan(value interface{}) error {
821
+	if value == nil {
822
+		ns.RepoVisibility, ns.Valid = "", false
823
+		return nil
824
+	}
825
+	ns.Valid = true
826
+	return ns.RepoVisibility.Scan(value)
827
+}
828
+
829
+// Value implements the driver Valuer interface.
830
+func (ns NullRepoVisibility) Value() (driver.Value, error) {
831
+	if !ns.Valid {
832
+		return nil, nil
833
+	}
834
+	return string(ns.RepoVisibility), nil
835
+}
836
+
837
+type TeamPrivacy string
838
+
839
+const (
840
+	TeamPrivacyVisible TeamPrivacy = "visible"
841
+	TeamPrivacySecret  TeamPrivacy = "secret"
842
+)
843
+
844
+func (e *TeamPrivacy) Scan(src interface{}) error {
845
+	switch s := src.(type) {
846
+	case []byte:
847
+		*e = TeamPrivacy(s)
848
+	case string:
849
+		*e = TeamPrivacy(s)
850
+	default:
851
+		return fmt.Errorf("unsupported scan type for TeamPrivacy: %T", src)
852
+	}
853
+	return nil
854
+}
855
+
856
+type NullTeamPrivacy struct {
857
+	TeamPrivacy TeamPrivacy
858
+	Valid       bool // Valid is true if TeamPrivacy is not NULL
859
+}
860
+
861
+// Scan implements the Scanner interface.
862
+func (ns *NullTeamPrivacy) Scan(value interface{}) error {
863
+	if value == nil {
864
+		ns.TeamPrivacy, ns.Valid = "", false
865
+		return nil
866
+	}
867
+	ns.Valid = true
868
+	return ns.TeamPrivacy.Scan(value)
869
+}
870
+
871
+// Value implements the driver Valuer interface.
872
+func (ns NullTeamPrivacy) Value() (driver.Value, error) {
873
+	if !ns.Valid {
874
+		return nil, nil
875
+	}
876
+	return string(ns.TeamPrivacy), nil
877
+}
878
+
879
+type TeamRepoRole string
880
+
881
+const (
882
+	TeamRepoRoleRead     TeamRepoRole = "read"
883
+	TeamRepoRoleTriage   TeamRepoRole = "triage"
884
+	TeamRepoRoleWrite    TeamRepoRole = "write"
885
+	TeamRepoRoleMaintain TeamRepoRole = "maintain"
886
+	TeamRepoRoleAdmin    TeamRepoRole = "admin"
887
+)
888
+
889
+func (e *TeamRepoRole) Scan(src interface{}) error {
890
+	switch s := src.(type) {
891
+	case []byte:
892
+		*e = TeamRepoRole(s)
893
+	case string:
894
+		*e = TeamRepoRole(s)
895
+	default:
896
+		return fmt.Errorf("unsupported scan type for TeamRepoRole: %T", src)
897
+	}
898
+	return nil
899
+}
900
+
901
+type NullTeamRepoRole struct {
902
+	TeamRepoRole TeamRepoRole
903
+	Valid        bool // Valid is true if TeamRepoRole is not NULL
904
+}
905
+
906
+// Scan implements the Scanner interface.
907
+func (ns *NullTeamRepoRole) Scan(value interface{}) error {
908
+	if value == nil {
909
+		ns.TeamRepoRole, ns.Valid = "", false
910
+		return nil
911
+	}
912
+	ns.Valid = true
913
+	return ns.TeamRepoRole.Scan(value)
914
+}
915
+
916
+// Value implements the driver Valuer interface.
917
+func (ns NullTeamRepoRole) Value() (driver.Value, error) {
918
+	if !ns.Valid {
919
+		return nil, nil
920
+	}
921
+	return string(ns.TeamRepoRole), nil
922
+}
923
+
924
+type TeamRole string
925
+
926
+const (
927
+	TeamRoleMember     TeamRole = "member"
928
+	TeamRoleMaintainer TeamRole = "maintainer"
929
+)
930
+
931
+func (e *TeamRole) Scan(src interface{}) error {
932
+	switch s := src.(type) {
933
+	case []byte:
934
+		*e = TeamRole(s)
935
+	case string:
936
+		*e = TeamRole(s)
937
+	default:
938
+		return fmt.Errorf("unsupported scan type for TeamRole: %T", src)
939
+	}
940
+	return nil
941
+}
942
+
943
+type NullTeamRole struct {
944
+	TeamRole TeamRole
945
+	Valid    bool // Valid is true if TeamRole is not NULL
946
+}
947
+
948
+// Scan implements the Scanner interface.
949
+func (ns *NullTeamRole) Scan(value interface{}) error {
950
+	if value == nil {
951
+		ns.TeamRole, ns.Valid = "", false
952
+		return nil
953
+	}
954
+	ns.Valid = true
955
+	return ns.TeamRole.Scan(value)
956
+}
957
+
958
+// Value implements the driver Valuer interface.
959
+func (ns NullTeamRole) Value() (driver.Value, error) {
960
+	if !ns.Valid {
961
+		return nil, nil
962
+	}
963
+	return string(ns.TeamRole), nil
964
+}
965
+
966
+type TransferPrincipalKind string
967
+
968
+const (
969
+	TransferPrincipalKindUser TransferPrincipalKind = "user"
970
+	TransferPrincipalKindOrg  TransferPrincipalKind = "org"
971
+)
972
+
973
+func (e *TransferPrincipalKind) Scan(src interface{}) error {
974
+	switch s := src.(type) {
975
+	case []byte:
976
+		*e = TransferPrincipalKind(s)
977
+	case string:
978
+		*e = TransferPrincipalKind(s)
979
+	default:
980
+		return fmt.Errorf("unsupported scan type for TransferPrincipalKind: %T", src)
981
+	}
982
+	return nil
983
+}
984
+
985
+type NullTransferPrincipalKind struct {
986
+	TransferPrincipalKind TransferPrincipalKind
987
+	Valid                 bool // Valid is true if TransferPrincipalKind is not NULL
988
+}
989
+
990
+// Scan implements the Scanner interface.
991
+func (ns *NullTransferPrincipalKind) Scan(value interface{}) error {
992
+	if value == nil {
993
+		ns.TransferPrincipalKind, ns.Valid = "", false
994
+		return nil
995
+	}
996
+	ns.Valid = true
997
+	return ns.TransferPrincipalKind.Scan(value)
998
+}
999
+
1000
+// Value implements the driver Valuer interface.
1001
+func (ns NullTransferPrincipalKind) Value() (driver.Value, error) {
1002
+	if !ns.Valid {
1003
+		return nil, nil
1004
+	}
1005
+	return string(ns.TransferPrincipalKind), nil
1006
+}
1007
+
1008
+type TransferStatus string
1009
+
1010
+const (
1011
+	TransferStatusPending  TransferStatus = "pending"
1012
+	TransferStatusAccepted TransferStatus = "accepted"
1013
+	TransferStatusDeclined TransferStatus = "declined"
1014
+	TransferStatusCanceled TransferStatus = "canceled"
1015
+	TransferStatusExpired  TransferStatus = "expired"
1016
+)
1017
+
1018
+func (e *TransferStatus) Scan(src interface{}) error {
1019
+	switch s := src.(type) {
1020
+	case []byte:
1021
+		*e = TransferStatus(s)
1022
+	case string:
1023
+		*e = TransferStatus(s)
1024
+	default:
1025
+		return fmt.Errorf("unsupported scan type for TransferStatus: %T", src)
1026
+	}
1027
+	return nil
1028
+}
1029
+
1030
+type NullTransferStatus struct {
1031
+	TransferStatus TransferStatus
1032
+	Valid          bool // Valid is true if TransferStatus is not NULL
1033
+}
1034
+
1035
+// Scan implements the Scanner interface.
1036
+func (ns *NullTransferStatus) Scan(value interface{}) error {
1037
+	if value == nil {
1038
+		ns.TransferStatus, ns.Valid = "", false
1039
+		return nil
1040
+	}
1041
+	ns.Valid = true
1042
+	return ns.TransferStatus.Scan(value)
1043
+}
1044
+
1045
+// Value implements the driver Valuer interface.
1046
+func (ns NullTransferStatus) Value() (driver.Value, error) {
1047
+	if !ns.Valid {
1048
+		return nil, nil
1049
+	}
1050
+	return string(ns.TransferStatus), nil
1051
+}
1052
+
1053
+type WatchLevel string
1054
+
1055
+const (
1056
+	WatchLevelAll           WatchLevel = "all"
1057
+	WatchLevelParticipating WatchLevel = "participating"
1058
+	WatchLevelIgnore        WatchLevel = "ignore"
1059
+)
1060
+
1061
+func (e *WatchLevel) Scan(src interface{}) error {
1062
+	switch s := src.(type) {
1063
+	case []byte:
1064
+		*e = WatchLevel(s)
1065
+	case string:
1066
+		*e = WatchLevel(s)
1067
+	default:
1068
+		return fmt.Errorf("unsupported scan type for WatchLevel: %T", src)
1069
+	}
1070
+	return nil
1071
+}
1072
+
1073
+type NullWatchLevel struct {
1074
+	WatchLevel WatchLevel
1075
+	Valid      bool // Valid is true if WatchLevel is not NULL
1076
+}
1077
+
1078
+// Scan implements the Scanner interface.
1079
+func (ns *NullWatchLevel) Scan(value interface{}) error {
1080
+	if value == nil {
1081
+		ns.WatchLevel, ns.Valid = "", false
1082
+		return nil
1083
+	}
1084
+	ns.Valid = true
1085
+	return ns.WatchLevel.Scan(value)
1086
+}
1087
+
1088
+// Value implements the driver Valuer interface.
1089
+func (ns NullWatchLevel) Value() (driver.Value, error) {
1090
+	if !ns.Valid {
1091
+		return nil, nil
1092
+	}
1093
+	return string(ns.WatchLevel), nil
1094
+}
1095
+
1096
+type WebhookContentType string
1097
+
1098
+const (
1099
+	WebhookContentTypeJson WebhookContentType = "json"
1100
+	WebhookContentTypeForm WebhookContentType = "form"
1101
+)
1102
+
1103
+func (e *WebhookContentType) Scan(src interface{}) error {
1104
+	switch s := src.(type) {
1105
+	case []byte:
1106
+		*e = WebhookContentType(s)
1107
+	case string:
1108
+		*e = WebhookContentType(s)
1109
+	default:
1110
+		return fmt.Errorf("unsupported scan type for WebhookContentType: %T", src)
1111
+	}
1112
+	return nil
1113
+}
1114
+
1115
+type NullWebhookContentType struct {
1116
+	WebhookContentType WebhookContentType
1117
+	Valid              bool // Valid is true if WebhookContentType is not NULL
1118
+}
1119
+
1120
+// Scan implements the Scanner interface.
1121
+func (ns *NullWebhookContentType) Scan(value interface{}) error {
1122
+	if value == nil {
1123
+		ns.WebhookContentType, ns.Valid = "", false
1124
+		return nil
1125
+	}
1126
+	ns.Valid = true
1127
+	return ns.WebhookContentType.Scan(value)
1128
+}
1129
+
1130
+// Value implements the driver Valuer interface.
1131
+func (ns NullWebhookContentType) Value() (driver.Value, error) {
1132
+	if !ns.Valid {
1133
+		return nil, nil
1134
+	}
1135
+	return string(ns.WebhookContentType), nil
1136
+}
1137
+
1138
+type WebhookDeliveryStatus string
1139
+
1140
+const (
1141
+	WebhookDeliveryStatusPending         WebhookDeliveryStatus = "pending"
1142
+	WebhookDeliveryStatusSucceeded       WebhookDeliveryStatus = "succeeded"
1143
+	WebhookDeliveryStatusFailedRetry     WebhookDeliveryStatus = "failed_retry"
1144
+	WebhookDeliveryStatusFailedPermanent WebhookDeliveryStatus = "failed_permanent"
1145
+)
1146
+
1147
+func (e *WebhookDeliveryStatus) Scan(src interface{}) error {
1148
+	switch s := src.(type) {
1149
+	case []byte:
1150
+		*e = WebhookDeliveryStatus(s)
1151
+	case string:
1152
+		*e = WebhookDeliveryStatus(s)
1153
+	default:
1154
+		return fmt.Errorf("unsupported scan type for WebhookDeliveryStatus: %T", src)
1155
+	}
1156
+	return nil
1157
+}
1158
+
1159
+type NullWebhookDeliveryStatus struct {
1160
+	WebhookDeliveryStatus WebhookDeliveryStatus
1161
+	Valid                 bool // Valid is true if WebhookDeliveryStatus is not NULL
1162
+}
1163
+
1164
+// Scan implements the Scanner interface.
1165
+func (ns *NullWebhookDeliveryStatus) Scan(value interface{}) error {
1166
+	if value == nil {
1167
+		ns.WebhookDeliveryStatus, ns.Valid = "", false
1168
+		return nil
1169
+	}
1170
+	ns.Valid = true
1171
+	return ns.WebhookDeliveryStatus.Scan(value)
1172
+}
1173
+
1174
+// Value implements the driver Valuer interface.
1175
+func (ns NullWebhookDeliveryStatus) Value() (driver.Value, error) {
1176
+	if !ns.Valid {
1177
+		return nil, nil
1178
+	}
1179
+	return string(ns.WebhookDeliveryStatus), nil
1180
+}
1181
+
1182
+type WebhookOwnerKind string
1183
+
1184
+const (
1185
+	WebhookOwnerKindRepo WebhookOwnerKind = "repo"
1186
+	WebhookOwnerKindOrg  WebhookOwnerKind = "org"
1187
+)
1188
+
1189
+func (e *WebhookOwnerKind) Scan(src interface{}) error {
1190
+	switch s := src.(type) {
1191
+	case []byte:
1192
+		*e = WebhookOwnerKind(s)
1193
+	case string:
1194
+		*e = WebhookOwnerKind(s)
1195
+	default:
1196
+		return fmt.Errorf("unsupported scan type for WebhookOwnerKind: %T", src)
1197
+	}
1198
+	return nil
1199
+}
1200
+
1201
+type NullWebhookOwnerKind struct {
1202
+	WebhookOwnerKind WebhookOwnerKind
1203
+	Valid            bool // Valid is true if WebhookOwnerKind is not NULL
1204
+}
1205
+
1206
+// Scan implements the Scanner interface.
1207
+func (ns *NullWebhookOwnerKind) Scan(value interface{}) error {
1208
+	if value == nil {
1209
+		ns.WebhookOwnerKind, ns.Valid = "", false
1210
+		return nil
1211
+	}
1212
+	ns.Valid = true
1213
+	return ns.WebhookOwnerKind.Scan(value)
1214
+}
1215
+
1216
+// Value implements the driver Valuer interface.
1217
+func (ns NullWebhookOwnerKind) Value() (driver.Value, error) {
1218
+	if !ns.Valid {
1219
+		return nil, nil
1220
+	}
1221
+	return string(ns.WebhookOwnerKind), nil
1222
+}
1223
+
1224
+type AuthAuditLog struct {
1225
+	ID         int64
1226
+	ActorID    pgtype.Int8
1227
+	Action     string
1228
+	TargetType string
1229
+	TargetID   pgtype.Int8
1230
+	Meta       []byte
1231
+	CreatedAt  pgtype.Timestamptz
1232
+}
1233
+
1234
+type AuthThrottle struct {
1235
+	ID              int64
1236
+	Scope           string
1237
+	Identifier      string
1238
+	Hits            int32
1239
+	WindowStartedAt pgtype.Timestamptz
1240
+}
1241
+
1242
+type BranchProtectionRule struct {
1243
+	ID                             int64
1244
+	RepoID                         int64
1245
+	Pattern                        string
1246
+	PreventForcePush               bool
1247
+	PreventDeletion                bool
1248
+	RequirePrForPush               bool
1249
+	AllowedPusherUserIds           []int64
1250
+	RequireSignedCommits           bool
1251
+	StatusChecksRequired           []string
1252
+	CreatedAt                      pgtype.Timestamptz
1253
+	UpdatedAt                      pgtype.Timestamptz
1254
+	CreatedByUserID                pgtype.Int8
1255
+	RequiredReviewCount            int32
1256
+	DismissStaleReviewsOnPush      bool
1257
+	RequireCodeOwnerReview         bool
1258
+	DismissStaleStatusChecksOnPush bool
1259
+}
1260
+
1261
+type CheckRun struct {
1262
+	ID          int64
1263
+	SuiteID     int64
1264
+	RepoID      int64
1265
+	HeadSha     string
1266
+	Name        string
1267
+	Status      CheckStatus
1268
+	Conclusion  NullCheckConclusion
1269
+	StartedAt   pgtype.Timestamptz
1270
+	CompletedAt pgtype.Timestamptz
1271
+	DetailsUrl  string
1272
+	Output      []byte
1273
+	ExternalID  pgtype.Text
1274
+	CreatedAt   pgtype.Timestamptz
1275
+	UpdatedAt   pgtype.Timestamptz
1276
+}
1277
+
1278
+type CheckSuite struct {
1279
+	ID         int64
1280
+	RepoID     int64
1281
+	HeadSha    string
1282
+	AppSlug    string
1283
+	Status     CheckStatus
1284
+	Conclusion NullCheckConclusion
1285
+	CreatedAt  pgtype.Timestamptz
1286
+	UpdatedAt  pgtype.Timestamptz
1287
+}
1288
+
1289
+type CodeSearchContent struct {
1290
+	RepoID      int64
1291
+	RefName     string
1292
+	Path        string
1293
+	ContentTsv  interface{}
1294
+	ContentTrgm string
1295
+}
1296
+
1297
+type CodeSearchPath struct {
1298
+	RepoID  int64
1299
+	RefName string
1300
+	Path    string
1301
+	Tsv     interface{}
1302
+}
1303
+
1304
+type DomainEvent struct {
1305
+	ID          int64
1306
+	ActorUserID pgtype.Int8
1307
+	Kind        string
1308
+	RepoID      pgtype.Int8
1309
+	SourceKind  string
1310
+	SourceID    int64
1311
+	Public      bool
1312
+	Payload     []byte
1313
+	CreatedAt   pgtype.Timestamptz
1314
+}
1315
+
1316
+type DomainEventsProcessed struct {
1317
+	Consumer    string
1318
+	LastEventID int64
1319
+	UpdatedAt   pgtype.Timestamptz
1320
+}
1321
+
1322
+type EmailVerification struct {
1323
+	ID          int64
1324
+	UserEmailID int64
1325
+	TokenHash   []byte
1326
+	ExpiresAt   pgtype.Timestamptz
1327
+	UsedAt      pgtype.Timestamptz
1328
+	CreatedAt   pgtype.Timestamptz
1329
+}
1330
+
1331
+type Issue struct {
1332
+	ID                int64
1333
+	RepoID            int64
1334
+	Number            int64
1335
+	Kind              IssueKind
1336
+	Title             string
1337
+	Body              string
1338
+	BodyHtmlCached    pgtype.Text
1339
+	MdPipelineVersion int32
1340
+	AuthorUserID      pgtype.Int8
1341
+	State             IssueState
1342
+	StateReason       NullIssueStateReason
1343
+	Locked            bool
1344
+	LockReason        pgtype.Text
1345
+	MilestoneID       pgtype.Int8
1346
+	CreatedAt         pgtype.Timestamptz
1347
+	UpdatedAt         pgtype.Timestamptz
1348
+	EditedAt          pgtype.Timestamptz
1349
+	ClosedAt          pgtype.Timestamptz
1350
+	ClosedByUserID    pgtype.Int8
1351
+}
1352
+
1353
+type IssueAssignee struct {
1354
+	IssueID          int64
1355
+	UserID           int64
1356
+	AssignedAt       pgtype.Timestamptz
1357
+	AssignedByUserID pgtype.Int8
1358
+}
1359
+
1360
+type IssueComment struct {
1361
+	ID                int64
1362
+	IssueID           int64
1363
+	AuthorUserID      pgtype.Int8
1364
+	Body              string
1365
+	BodyHtmlCached    pgtype.Text
1366
+	MdPipelineVersion int32
1367
+	CreatedAt         pgtype.Timestamptz
1368
+	UpdatedAt         pgtype.Timestamptz
1369
+	EditedAt          pgtype.Timestamptz
1370
+}
1371
+
1372
+type IssueEvent struct {
1373
+	ID          int64
1374
+	IssueID     int64
1375
+	ActorUserID pgtype.Int8
1376
+	Kind        string
1377
+	Meta        []byte
1378
+	RefTargetID pgtype.Int8
1379
+	CreatedAt   pgtype.Timestamptz
1380
+}
1381
+
1382
+type IssueLabel struct {
1383
+	IssueID         int64
1384
+	LabelID         int64
1385
+	AppliedAt       pgtype.Timestamptz
1386
+	AppliedByUserID pgtype.Int8
1387
+}
1388
+
1389
+type IssueReference struct {
1390
+	ID             int64
1391
+	SourceIssueID  pgtype.Int8
1392
+	TargetIssueID  int64
1393
+	SourceKind     IssueRefSource
1394
+	SourceObjectID pgtype.Int8
1395
+	CreatedAt      pgtype.Timestamptz
1396
+}
1397
+
1398
+type IssuesSearch struct {
1399
+	IssueID      int64
1400
+	RepoID       int64
1401
+	Kind         IssueKind
1402
+	State        IssueState
1403
+	AuthorUserID pgtype.Int8
1404
+	Tsv          interface{}
1405
+}
1406
+
1407
+type Job struct {
1408
+	ID          int64
1409
+	Kind        string
1410
+	Payload     []byte
1411
+	RunAt       pgtype.Timestamptz
1412
+	Attempts    int32
1413
+	MaxAttempts int32
1414
+	LastError   pgtype.Text
1415
+	LockedBy    pgtype.Text
1416
+	LockedAt    pgtype.Timestamptz
1417
+	CompletedAt pgtype.Timestamptz
1418
+	FailedAt    pgtype.Timestamptz
1419
+	CreatedAt   pgtype.Timestamptz
1420
+}
1421
+
1422
+type Label struct {
1423
+	ID          int64
1424
+	RepoID      int64
1425
+	Name        string
1426
+	Color       string
1427
+	Description string
1428
+	CreatedAt   pgtype.Timestamptz
1429
+}
1430
+
1431
+type Meta struct {
1432
+	Key       string
1433
+	Value     []byte
1434
+	UpdatedAt pgtype.Timestamptz
1435
+}
1436
+
1437
+type Milestone struct {
1438
+	ID          int64
1439
+	RepoID      int64
1440
+	Title       string
1441
+	Description string
1442
+	State       MilestoneState
1443
+	DueOn       pgtype.Timestamptz
1444
+	CreatedAt   pgtype.Timestamptz
1445
+	ClosedAt    pgtype.Timestamptz
1446
+}
1447
+
1448
+type Notification struct {
1449
+	ID              int64
1450
+	RecipientUserID int64
1451
+	Kind            string
1452
+	Reason          string
1453
+	RepoID          pgtype.Int8
1454
+	ThreadKind      NullNotificationThreadKind
1455
+	ThreadID        pgtype.Int8
1456
+	SourceEventID   pgtype.Int8
1457
+	Unread          bool
1458
+	LastEventAt     pgtype.Timestamptz
1459
+	LastActorUserID pgtype.Int8
1460
+	Summary         []byte
1461
+	CreatedAt       pgtype.Timestamptz
1462
+	UpdatedAt       pgtype.Timestamptz
1463
+}
1464
+
1465
+type NotificationEmailLog struct {
1466
+	ID              int64
1467
+	RecipientUserID int64
1468
+	NotificationID  pgtype.Int8
1469
+	ThreadKind      NullNotificationThreadKind
1470
+	ThreadID        pgtype.Int8
1471
+	SentAt          pgtype.Timestamptz
1472
+	MessageID       pgtype.Text
1473
+}
1474
+
1475
+type NotificationThread struct {
1476
+	RecipientUserID int64
1477
+	ThreadKind      NotificationThreadKind
1478
+	ThreadID        int64
1479
+	Subscribed      bool
1480
+	Reason          string
1481
+	UpdatedAt       pgtype.Timestamptz
1482
+}
1483
+
1484
+type Org struct {
1485
+	ID                    int64
1486
+	Slug                  string
1487
+	DisplayName           string
1488
+	Description           string
1489
+	AvatarObjectKey       pgtype.Text
1490
+	Location              string
1491
+	Website               string
1492
+	BillingEmail          string
1493
+	Plan                  OrgPlan
1494
+	AllowMemberRepoCreate bool
1495
+	CreatedByUserID       pgtype.Int8
1496
+	SuspendedAt           pgtype.Timestamptz
1497
+	SuspendedReason       pgtype.Text
1498
+	DeletedAt             pgtype.Timestamptz
1499
+	CreatedAt             pgtype.Timestamptz
1500
+	UpdatedAt             pgtype.Timestamptz
1501
+}
1502
+
1503
+type OrgInvitation struct {
1504
+	ID              int64
1505
+	OrgID           int64
1506
+	InvitedByUserID pgtype.Int8
1507
+	TargetUserID    pgtype.Int8
1508
+	TargetEmail     pgtype.Text
1509
+	Role            OrgRole
1510
+	TokenHash       []byte
1511
+	ExpiresAt       pgtype.Timestamptz
1512
+	AcceptedAt      pgtype.Timestamptz
1513
+	DeclinedAt      pgtype.Timestamptz
1514
+	CanceledAt      pgtype.Timestamptz
1515
+	CreatedAt       pgtype.Timestamptz
1516
+}
1517
+
1518
+type OrgMember struct {
1519
+	OrgID           int64
1520
+	UserID          int64
1521
+	Role            OrgRole
1522
+	InvitedByUserID pgtype.Int8
1523
+	JoinedAt        pgtype.Timestamptz
1524
+}
1525
+
1526
+type PasswordReset struct {
1527
+	ID        int64
1528
+	UserID    int64
1529
+	TokenHash []byte
1530
+	ExpiresAt pgtype.Timestamptz
1531
+	UsedAt    pgtype.Timestamptz
1532
+	CreatedAt pgtype.Timestamptz
1533
+}
1534
+
1535
+type PrReview struct {
1536
+	ID                int64
1537
+	PrIssueID         int64
1538
+	AuthorUserID      pgtype.Int8
1539
+	State             PrReviewState
1540
+	Body              string
1541
+	BodyHtmlCached    pgtype.Text
1542
+	SubmittedAt       pgtype.Timestamptz
1543
+	DismissedAt       pgtype.Timestamptz
1544
+	DismissedByUserID pgtype.Int8
1545
+	DismissalReason   string
1546
+}
1547
+
1548
+type PrReviewComment struct {
1549
+	ID                int64
1550
+	PrIssueID         int64
1551
+	ReviewID          pgtype.Int8
1552
+	AuthorUserID      pgtype.Int8
1553
+	FilePath          string
1554
+	Side              PrReviewSide
1555
+	OriginalCommitSha string
1556
+	OriginalLine      int32
1557
+	OriginalPosition  int32
1558
+	CurrentPosition   pgtype.Int4
1559
+	Body              string
1560
+	BodyHtmlCached    pgtype.Text
1561
+	InReplyToID       pgtype.Int8
1562
+	Pending           bool
1563
+	ResolvedAt        pgtype.Timestamptz
1564
+	ResolvedByUserID  pgtype.Int8
1565
+	CreatedAt         pgtype.Timestamptz
1566
+	UpdatedAt         pgtype.Timestamptz
1567
+	EditedAt          pgtype.Timestamptz
1568
+}
1569
+
1570
+type PrReviewRequest struct {
1571
+	ID                  int64
1572
+	PrIssueID           int64
1573
+	RequestedUserID     pgtype.Int8
1574
+	RequestedTeamID     pgtype.Int8
1575
+	RequestedByUserID   pgtype.Int8
1576
+	RequestedAt         pgtype.Timestamptz
1577
+	DismissedAt         pgtype.Timestamptz
1578
+	SatisfiedByReviewID pgtype.Int8
1579
+}
1580
+
1581
+type Principal struct {
1582
+	Slug string
1583
+	Kind PrincipalKind
1584
+	ID   int64
1585
+}
1586
+
1587
+type PullRequest struct {
1588
+	IssueID            int64
1589
+	BaseRef            string
1590
+	HeadRef            string
1591
+	HeadRepoID         int64
1592
+	BaseOid            string
1593
+	HeadOid            string
1594
+	Draft              bool
1595
+	Mergeable          pgtype.Bool
1596
+	MergeableState     PrMergeableState
1597
+	MergeCommitSha     pgtype.Text
1598
+	MergedAt           pgtype.Timestamptz
1599
+	MergedByUserID     pgtype.Int8
1600
+	MergeMethod        NullPrMergeMethod
1601
+	BaseOidAtMerge     pgtype.Text
1602
+	HeadOidAtMerge     pgtype.Text
1603
+	LastSynchronizedAt pgtype.Timestamptz
1604
+}
1605
+
1606
+type PullRequestCommit struct {
1607
+	PrID           int64
1608
+	Sha            string
1609
+	Position       int32
1610
+	AuthorName     string
1611
+	AuthorEmail    string
1612
+	CommitterName  string
1613
+	CommitterEmail string
1614
+	Subject        string
1615
+	Body           string
1616
+	AuthoredAt     pgtype.Timestamptz
1617
+	CommittedAt    pgtype.Timestamptz
1618
+}
1619
+
1620
+type PullRequestFile struct {
1621
+	PrID      int64
1622
+	Path      string
1623
+	Status    PrFileStatus
1624
+	OldPath   pgtype.Text
1625
+	Additions int32
1626
+	Deletions int32
1627
+	Changes   int32
1628
+}
1629
+
1630
+type PushEvent struct {
1631
+	ID           int64
1632
+	RepoID       int64
1633
+	PusherUserID pgtype.Int8
1634
+	BeforeSha    string
1635
+	AfterSha     string
1636
+	Ref          string
1637
+	Protocol     string
1638
+	RequestID    string
1639
+	ProcessedAt  pgtype.Timestamptz
1640
+	CreatedAt    pgtype.Timestamptz
1641
+}
1642
+
1643
+type Repo struct {
1644
+	ID                 int64
1645
+	OwnerUserID        pgtype.Int8
1646
+	OwnerOrgID         pgtype.Int8
1647
+	Name               string
1648
+	Description        string
1649
+	Visibility         RepoVisibility
1650
+	DefaultBranch      string
1651
+	IsArchived         bool
1652
+	ArchivedAt         pgtype.Timestamptz
1653
+	DeletedAt          pgtype.Timestamptz
1654
+	DiskUsedBytes      int64
1655
+	ForkOfRepoID       pgtype.Int8
1656
+	LicenseKey         pgtype.Text
1657
+	PrimaryLanguage    pgtype.Text
1658
+	HasIssues          bool
1659
+	HasPulls           bool
1660
+	CreatedAt          pgtype.Timestamptz
1661
+	UpdatedAt          pgtype.Timestamptz
1662
+	DefaultBranchOid   pgtype.Text
1663
+	AllowSquashMerge   bool
1664
+	AllowRebaseMerge   bool
1665
+	AllowMergeCommit   bool
1666
+	DefaultMergeMethod PrMergeMethod
1667
+	StarCount          int64
1668
+	WatcherCount       int64
1669
+	ForkCount          int64
1670
+	InitStatus         RepoInitStatus
1671
+	LastIndexedOid     pgtype.Text
1672
+}
1673
+
1674
+type RepoCollaborator struct {
1675
+	RepoID        int64
1676
+	UserID        int64
1677
+	Role          CollabRole
1678
+	AddedAt       pgtype.Timestamptz
1679
+	AddedByUserID pgtype.Int8
1680
+}
1681
+
1682
+type RepoIssueCounter struct {
1683
+	RepoID     int64
1684
+	NextNumber int64
1685
+}
1686
+
1687
+type RepoRedirect struct {
1688
+	OldOwnerUserID pgtype.Int8
1689
+	OldOwnerOrgID  pgtype.Int8
1690
+	OldName        string
1691
+	RepoID         int64
1692
+	RedirectedAt   pgtype.Timestamptz
1693
+}
1694
+
1695
+type RepoTopic struct {
1696
+	RepoID    int64
1697
+	Topic     string
1698
+	CreatedAt pgtype.Timestamptz
1699
+}
1700
+
1701
+type RepoTransferRequest struct {
1702
+	ID              int64
1703
+	RepoID          int64
1704
+	FromUserID      int64
1705
+	ToPrincipalKind TransferPrincipalKind
1706
+	ToPrincipalID   int64
1707
+	CreatedBy       int64
1708
+	CreatedAt       pgtype.Timestamptz
1709
+	ExpiresAt       pgtype.Timestamptz
1710
+	Status          TransferStatus
1711
+	AcceptedAt      pgtype.Timestamptz
1712
+	DeclinedAt      pgtype.Timestamptz
1713
+	CanceledAt      pgtype.Timestamptz
1714
+}
1715
+
1716
+type ReposSearch struct {
1717
+	RepoID int64
1718
+	Tsv    interface{}
1719
+}
1720
+
1721
+type Star struct {
1722
+	UserID    int64
1723
+	RepoID    int64
1724
+	StarredAt pgtype.Timestamptz
1725
+}
1726
+
1727
+type Team struct {
1728
+	ID              int64
1729
+	OrgID           int64
1730
+	Slug            string
1731
+	DisplayName     string
1732
+	Description     string
1733
+	ParentTeamID    pgtype.Int8
1734
+	Privacy         TeamPrivacy
1735
+	CreatedByUserID pgtype.Int8
1736
+	CreatedAt       pgtype.Timestamptz
1737
+	UpdatedAt       pgtype.Timestamptz
1738
+}
1739
+
1740
+type TeamMember struct {
1741
+	TeamID        int64
1742
+	UserID        int64
1743
+	Role          TeamRole
1744
+	AddedByUserID pgtype.Int8
1745
+	AddedAt       pgtype.Timestamptz
1746
+}
1747
+
1748
+type TeamRepoAccess struct {
1749
+	TeamID        int64
1750
+	RepoID        int64
1751
+	Role          TeamRepoRole
1752
+	AddedByUserID pgtype.Int8
1753
+	AddedAt       pgtype.Timestamptz
1754
+}
1755
+
1756
+type User struct {
1757
+	ID                int64
1758
+	Username          string
1759
+	DisplayName       string
1760
+	PrimaryEmailID    pgtype.Int8
1761
+	PasswordHash      string
1762
+	PasswordAlgo      string
1763
+	PasswordUpdatedAt pgtype.Timestamptz
1764
+	EmailVerified     bool
1765
+	LastLoginAt       pgtype.Timestamptz
1766
+	SuspendedAt       pgtype.Timestamptz
1767
+	SuspendedReason   pgtype.Text
1768
+	DeletedAt         pgtype.Timestamptz
1769
+	CreatedAt         pgtype.Timestamptz
1770
+	UpdatedAt         pgtype.Timestamptz
1771
+	Bio               string
1772
+	Location          string
1773
+	Website           string
1774
+	Company           string
1775
+	Pronouns          string
1776
+	AvatarObjectKey   pgtype.Text
1777
+	Theme             string
1778
+	SessionEpoch      int32
1779
+}
1780
+
1781
+type UserEmail struct {
1782
+	ID                    int64
1783
+	UserID                int64
1784
+	Email                 string
1785
+	IsPrimary             bool
1786
+	Verified              bool
1787
+	VerificationTokenHash []byte
1788
+	VerificationSentAt    pgtype.Timestamptz
1789
+	VerifiedAt            pgtype.Timestamptz
1790
+	CreatedAt             pgtype.Timestamptz
1791
+}
1792
+
1793
+type UserNotificationPref struct {
1794
+	UserID    int64
1795
+	Key       string
1796
+	Value     []byte
1797
+	UpdatedAt pgtype.Timestamptz
1798
+}
1799
+
1800
+type UserRecoveryCode struct {
1801
+	ID          int64
1802
+	UserID      int64
1803
+	CodeHash    []byte
1804
+	UsedAt      pgtype.Timestamptz
1805
+	GeneratedAt pgtype.Timestamptz
1806
+	CreatedAt   pgtype.Timestamptz
1807
+}
1808
+
1809
+type UserSshKey struct {
1810
+	ID                int64
1811
+	UserID            int64
1812
+	Title             string
1813
+	FingerprintSha256 string
1814
+	KeyType           string
1815
+	KeyBits           int32
1816
+	PublicKey         string
1817
+	LastUsedAt        pgtype.Timestamptz
1818
+	LastUsedIp        *netip.Addr
1819
+	CreatedAt         pgtype.Timestamptz
1820
+}
1821
+
1822
+type UserToken struct {
1823
+	ID          int64
1824
+	UserID      int64
1825
+	Name        string
1826
+	TokenHash   []byte
1827
+	TokenPrefix string
1828
+	Scopes      []string
1829
+	ExpiresAt   pgtype.Timestamptz
1830
+	LastUsedAt  pgtype.Timestamptz
1831
+	LastUsedIp  *netip.Addr
1832
+	RevokedAt   pgtype.Timestamptz
1833
+	CreatedAt   pgtype.Timestamptz
1834
+}
1835
+
1836
+type UserTotp struct {
1837
+	ID              int64
1838
+	UserID          int64
1839
+	SecretEncrypted []byte
1840
+	SecretNonce     []byte
1841
+	ConfirmedAt     pgtype.Timestamptz
1842
+	LastUsedCounter int64
1843
+	CreatedAt       pgtype.Timestamptz
1844
+	UpdatedAt       pgtype.Timestamptz
1845
+}
1846
+
1847
+type UsernameRedirect struct {
1848
+	OldUsername string
1849
+	UserID      int64
1850
+	ChangedAt   pgtype.Timestamptz
1851
+}
1852
+
1853
+type UsersSearch struct {
1854
+	UserID int64
1855
+	Tsv    interface{}
1856
+}
1857
+
1858
+type Watch struct {
1859
+	UserID    int64
1860
+	RepoID    int64
1861
+	Level     WatchLevel
1862
+	UpdatedAt pgtype.Timestamptz
1863
+}
1864
+
1865
+type Webhook struct {
1866
+	ID                   int64
1867
+	OwnerKind            WebhookOwnerKind
1868
+	OwnerID              int64
1869
+	Url                  string
1870
+	ContentType          WebhookContentType
1871
+	Events               []string
1872
+	SecretCiphertext     []byte
1873
+	SecretNonce          []byte
1874
+	Active               bool
1875
+	SslVerification      bool
1876
+	ConsecutiveFailures  int32
1877
+	AutoDisableThreshold int32
1878
+	DisabledAt           pgtype.Timestamptz
1879
+	DisabledReason       pgtype.Text
1880
+	LastSuccessAt        pgtype.Timestamptz
1881
+	LastFailureAt        pgtype.Timestamptz
1882
+	CreatedByUserID      pgtype.Int8
1883
+	CreatedAt            pgtype.Timestamptz
1884
+	UpdatedAt            pgtype.Timestamptz
1885
+}
1886
+
1887
+type WebhookDelivery struct {
1888
+	ID                int64
1889
+	WebhookID         int64
1890
+	EventKind         string
1891
+	EventID           pgtype.Int8
1892
+	DeliveryUuid      pgtype.UUID
1893
+	Payload           []byte
1894
+	RequestHeaders    []byte
1895
+	RequestBody       []byte
1896
+	ResponseStatus    pgtype.Int4
1897
+	ResponseHeaders   []byte
1898
+	ResponseBody      []byte
1899
+	ResponseTruncated bool
1900
+	StartedAt         pgtype.Timestamptz
1901
+	CompletedAt       pgtype.Timestamptz
1902
+	Attempt           int32
1903
+	MaxAttempts       int32
1904
+	NextRetryAt       pgtype.Timestamptz
1905
+	Status            WebhookDeliveryStatus
1906
+	IdempotencyKey    string
1907
+	ErrorSummary      pgtype.Text
1908
+	RedeliverOf       pgtype.Int8
1909
+}
1910
+
1911
+type WebhookEventsPending struct {
1912
+	ID        int64
1913
+	RepoID    int64
1914
+	EventKind string
1915
+	Payload   []byte
1916
+	CreatedAt pgtype.Timestamptz
1917
+}
internal/webhook/sqlc/querier.goadded
@@ -0,0 +1,67 @@
1
+// Code generated by sqlc. DO NOT EDIT.
2
+// versions:
3
+//   sqlc v1.31.1
4
+
5
+package webhookdb
6
+
7
+import (
8
+	"context"
9
+
10
+	"github.com/jackc/pgx/v5/pgtype"
11
+)
12
+
13
+type Querier interface {
14
+	AutoDisableWebhook(ctx context.Context, db DBTX, arg AutoDisableWebhookParams) error
15
+	// Hot-path for the deliver worker: picks up to $1 rows that are
16
+	// pending or in retry-ready state, FOR UPDATE SKIP LOCKED so concurrent
17
+	// workers don't double-send. The deliverer marks the row 'pending'
18
+	// with a far-future next_retry_at while it works on it (defense
19
+	// against re-claim during a long HTTP timeout).
20
+	ClaimDueDeliveries(ctx context.Context, db DBTX, limit int32) ([]int64, error)
21
+	CreateDelivery(ctx context.Context, db DBTX, arg CreateDeliveryParams) (WebhookDelivery, error)
22
+	// SPDX-License-Identifier: AGPL-3.0-or-later
23
+	//
24
+	// Query surface for the webhook package. Naming mirrors the verb the
25
+	// caller uses; visibility / ownership filters are applied by the
26
+	// handler layer (the queries are deliberately narrow on key columns).
27
+	CreateWebhook(ctx context.Context, db DBTX, arg CreateWebhookParams) (Webhook, error)
28
+	DeleteWebhook(ctx context.Context, db DBTX, id int64) error
29
+	GetDeliveryByID(ctx context.Context, db DBTX, id int64) (WebhookDelivery, error)
30
+	// Resolve a repo's owner so the fanout knows which webhooks to fire.
31
+	// Returns owner_kind 'user' or 'org' so the matcher knows what bucket
32
+	// to look in. (We use 'user' as a literal string here; the webhook
33
+	// machinery itself only stores 'repo'/'org' as owners — repo-owned
34
+	// webhooks attach to the repo regardless of who owns the repo.)
35
+	GetRepoOwnerKindForFanout(ctx context.Context, db DBTX, id int64) (GetRepoOwnerKindForFanoutRow, error)
36
+	GetWebhookByID(ctx context.Context, db DBTX, id int64) (Webhook, error)
37
+	// SPDX-License-Identifier: AGPL-3.0-or-later
38
+	//
39
+	// Webhook fanout reads the same domain_events / cursor surface that
40
+	// notifications do — but with its own consumer row so progress
41
+	// doesn't collide with notify_fanout.
42
+	GetWebhookCursor(ctx context.Context, db DBTX, consumer string) (DomainEventsProcessed, error)
43
+	// Used by fanout to find subscribers for an event.
44
+	ListActiveWebhooksForOwner(ctx context.Context, db DBTX, arg ListActiveWebhooksForOwnerParams) ([]Webhook, error)
45
+	ListDeliveriesForWebhook(ctx context.Context, db DBTX, arg ListDeliveriesForWebhookParams) ([]ListDeliveriesForWebhookRow, error)
46
+	ListUnprocessedDomainEvents(ctx context.Context, db DBTX, arg ListUnprocessedDomainEventsParams) ([]DomainEvent, error)
47
+	ListWebhooksForOwner(ctx context.Context, db DBTX, arg ListWebhooksForOwnerParams) ([]Webhook, error)
48
+	MarkDeliveryPermanentFailure(ctx context.Context, db DBTX, arg MarkDeliveryPermanentFailureParams) error
49
+	MarkDeliveryRetry(ctx context.Context, db DBTX, arg MarkDeliveryRetryParams) error
50
+	MarkDeliverySucceeded(ctx context.Context, db DBTX, arg MarkDeliverySucceededParams) error
51
+	// Cron: drops terminal deliveries older than the retention window.
52
+	// pending/failed_retry rows are left alone so an in-flight retry isn't
53
+	// aborted out from under the worker.
54
+	PurgeOldDeliveries(ctx context.Context, db DBTX, retention pgtype.Interval) (int64, error)
55
+	// Increments the failure counter and reports the new value so the
56
+	// caller can decide whether to auto-disable.
57
+	RecordWebhookFailure(ctx context.Context, db DBTX, id int64) (RecordWebhookFailureRow, error)
58
+	RecordWebhookSuccess(ctx context.Context, db DBTX, id int64) error
59
+	// Re-enables a previously auto-disabled webhook (UI affordance).
60
+	// Resets the failure counter and clears disabled_at/reason.
61
+	SetWebhookActive(ctx context.Context, db DBTX, arg SetWebhookActiveParams) error
62
+	SetWebhookCursor(ctx context.Context, db DBTX, arg SetWebhookCursorParams) error
63
+	UpdateWebhook(ctx context.Context, db DBTX, arg UpdateWebhookParams) error
64
+	UpdateWebhookSecret(ctx context.Context, db DBTX, arg UpdateWebhookSecretParams) error
65
+}
66
+
67
+var _ Querier = (*Queries)(nil)
internal/webhook/sqlc/webhooks.sql.goadded
@@ -0,0 +1,684 @@
1
+// Code generated by sqlc. DO NOT EDIT.
2
+// versions:
3
+//   sqlc v1.31.1
4
+// source: webhooks.sql
5
+
6
+package webhookdb
7
+
8
+import (
9
+	"context"
10
+
11
+	"github.com/jackc/pgx/v5/pgtype"
12
+)
13
+
14
+const autoDisableWebhook = `-- name: AutoDisableWebhook :exec
15
+UPDATE webhooks
16
+   SET disabled_at     = now(),
17
+       disabled_reason = $2,
18
+       updated_at      = now()
19
+ WHERE id = $1
20
+`
21
+
22
+type AutoDisableWebhookParams struct {
23
+	ID             int64
24
+	DisabledReason pgtype.Text
25
+}
26
+
27
+func (q *Queries) AutoDisableWebhook(ctx context.Context, db DBTX, arg AutoDisableWebhookParams) error {
28
+	_, err := db.Exec(ctx, autoDisableWebhook, arg.ID, arg.DisabledReason)
29
+	return err
30
+}
31
+
32
+const claimDueDeliveries = `-- name: ClaimDueDeliveries :many
33
+SELECT id FROM webhook_deliveries
34
+WHERE status IN ('pending', 'failed_retry')
35
+  AND (next_retry_at IS NULL OR next_retry_at <= now())
36
+ORDER BY next_retry_at NULLS FIRST, id
37
+LIMIT $1
38
+FOR UPDATE SKIP LOCKED
39
+`
40
+
41
+// Hot-path for the deliver worker: picks up to $1 rows that are
42
+// pending or in retry-ready state, FOR UPDATE SKIP LOCKED so concurrent
43
+// workers don't double-send. The deliverer marks the row 'pending'
44
+// with a far-future next_retry_at while it works on it (defense
45
+// against re-claim during a long HTTP timeout).
46
+func (q *Queries) ClaimDueDeliveries(ctx context.Context, db DBTX, limit int32) ([]int64, error) {
47
+	rows, err := db.Query(ctx, claimDueDeliveries, limit)
48
+	if err != nil {
49
+		return nil, err
50
+	}
51
+	defer rows.Close()
52
+	items := []int64{}
53
+	for rows.Next() {
54
+		var id int64
55
+		if err := rows.Scan(&id); err != nil {
56
+			return nil, err
57
+		}
58
+		items = append(items, id)
59
+	}
60
+	if err := rows.Err(); err != nil {
61
+		return nil, err
62
+	}
63
+	return items, nil
64
+}
65
+
66
+const createDelivery = `-- name: CreateDelivery :one
67
+INSERT INTO webhook_deliveries (
68
+    webhook_id, event_kind, event_id, payload, request_headers,
69
+    request_body, attempt, max_attempts, next_retry_at, status,
70
+    idempotency_key, redeliver_of
71
+) VALUES (
72
+    $1, $2, $3::bigint,
73
+    $4, $5, $6,
74
+    $7, $8, $9::timestamptz,
75
+    $10, $11, $12::bigint
76
+)
77
+RETURNING id, webhook_id, event_kind, event_id, delivery_uuid, payload, request_headers, request_body, response_status, response_headers, response_body, response_truncated, started_at, completed_at, attempt, max_attempts, next_retry_at, status, idempotency_key, error_summary, redeliver_of
78
+`
79
+
80
+type CreateDeliveryParams struct {
81
+	WebhookID      int64
82
+	EventKind      string
83
+	EventID        pgtype.Int8
84
+	Payload        []byte
85
+	RequestHeaders []byte
86
+	RequestBody    []byte
87
+	Attempt        int32
88
+	MaxAttempts    int32
89
+	NextRetryAt    pgtype.Timestamptz
90
+	Status         WebhookDeliveryStatus
91
+	IdempotencyKey string
92
+	RedeliverOf    pgtype.Int8
93
+}
94
+
95
+func (q *Queries) CreateDelivery(ctx context.Context, db DBTX, arg CreateDeliveryParams) (WebhookDelivery, error) {
96
+	row := db.QueryRow(ctx, createDelivery,
97
+		arg.WebhookID,
98
+		arg.EventKind,
99
+		arg.EventID,
100
+		arg.Payload,
101
+		arg.RequestHeaders,
102
+		arg.RequestBody,
103
+		arg.Attempt,
104
+		arg.MaxAttempts,
105
+		arg.NextRetryAt,
106
+		arg.Status,
107
+		arg.IdempotencyKey,
108
+		arg.RedeliverOf,
109
+	)
110
+	var i WebhookDelivery
111
+	err := row.Scan(
112
+		&i.ID,
113
+		&i.WebhookID,
114
+		&i.EventKind,
115
+		&i.EventID,
116
+		&i.DeliveryUuid,
117
+		&i.Payload,
118
+		&i.RequestHeaders,
119
+		&i.RequestBody,
120
+		&i.ResponseStatus,
121
+		&i.ResponseHeaders,
122
+		&i.ResponseBody,
123
+		&i.ResponseTruncated,
124
+		&i.StartedAt,
125
+		&i.CompletedAt,
126
+		&i.Attempt,
127
+		&i.MaxAttempts,
128
+		&i.NextRetryAt,
129
+		&i.Status,
130
+		&i.IdempotencyKey,
131
+		&i.ErrorSummary,
132
+		&i.RedeliverOf,
133
+	)
134
+	return i, err
135
+}
136
+
137
+const createWebhook = `-- name: CreateWebhook :one
138
+
139
+INSERT INTO webhooks (
140
+    owner_kind, owner_id, url, content_type, events,
141
+    secret_ciphertext, secret_nonce, active, ssl_verification,
142
+    auto_disable_threshold, created_by_user_id
143
+) VALUES (
144
+    $1, $2, $3,
145
+    $4, $5,
146
+    $6, $7,
147
+    $8, $9,
148
+    $10,
149
+    $11::bigint
150
+)
151
+RETURNING id, owner_kind, owner_id, url, content_type, events, secret_ciphertext, secret_nonce, active, ssl_verification, consecutive_failures, auto_disable_threshold, disabled_at, disabled_reason, last_success_at, last_failure_at, created_by_user_id, created_at, updated_at
152
+`
153
+
154
+type CreateWebhookParams struct {
155
+	OwnerKind            WebhookOwnerKind
156
+	OwnerID              int64
157
+	Url                  string
158
+	ContentType          WebhookContentType
159
+	Events               []string
160
+	SecretCiphertext     []byte
161
+	SecretNonce          []byte
162
+	Active               bool
163
+	SslVerification      bool
164
+	AutoDisableThreshold int32
165
+	CreatedByUserID      pgtype.Int8
166
+}
167
+
168
+// SPDX-License-Identifier: AGPL-3.0-or-later
169
+//
170
+// Query surface for the webhook package. Naming mirrors the verb the
171
+// caller uses; visibility / ownership filters are applied by the
172
+// handler layer (the queries are deliberately narrow on key columns).
173
+func (q *Queries) CreateWebhook(ctx context.Context, db DBTX, arg CreateWebhookParams) (Webhook, error) {
174
+	row := db.QueryRow(ctx, createWebhook,
175
+		arg.OwnerKind,
176
+		arg.OwnerID,
177
+		arg.Url,
178
+		arg.ContentType,
179
+		arg.Events,
180
+		arg.SecretCiphertext,
181
+		arg.SecretNonce,
182
+		arg.Active,
183
+		arg.SslVerification,
184
+		arg.AutoDisableThreshold,
185
+		arg.CreatedByUserID,
186
+	)
187
+	var i Webhook
188
+	err := row.Scan(
189
+		&i.ID,
190
+		&i.OwnerKind,
191
+		&i.OwnerID,
192
+		&i.Url,
193
+		&i.ContentType,
194
+		&i.Events,
195
+		&i.SecretCiphertext,
196
+		&i.SecretNonce,
197
+		&i.Active,
198
+		&i.SslVerification,
199
+		&i.ConsecutiveFailures,
200
+		&i.AutoDisableThreshold,
201
+		&i.DisabledAt,
202
+		&i.DisabledReason,
203
+		&i.LastSuccessAt,
204
+		&i.LastFailureAt,
205
+		&i.CreatedByUserID,
206
+		&i.CreatedAt,
207
+		&i.UpdatedAt,
208
+	)
209
+	return i, err
210
+}
211
+
212
+const deleteWebhook = `-- name: DeleteWebhook :exec
213
+DELETE FROM webhooks WHERE id = $1
214
+`
215
+
216
+func (q *Queries) DeleteWebhook(ctx context.Context, db DBTX, id int64) error {
217
+	_, err := db.Exec(ctx, deleteWebhook, id)
218
+	return err
219
+}
220
+
221
+const getDeliveryByID = `-- name: GetDeliveryByID :one
222
+SELECT id, webhook_id, event_kind, event_id, delivery_uuid, payload, request_headers, request_body, response_status, response_headers, response_body, response_truncated, started_at, completed_at, attempt, max_attempts, next_retry_at, status, idempotency_key, error_summary, redeliver_of FROM webhook_deliveries WHERE id = $1
223
+`
224
+
225
+func (q *Queries) GetDeliveryByID(ctx context.Context, db DBTX, id int64) (WebhookDelivery, error) {
226
+	row := db.QueryRow(ctx, getDeliveryByID, id)
227
+	var i WebhookDelivery
228
+	err := row.Scan(
229
+		&i.ID,
230
+		&i.WebhookID,
231
+		&i.EventKind,
232
+		&i.EventID,
233
+		&i.DeliveryUuid,
234
+		&i.Payload,
235
+		&i.RequestHeaders,
236
+		&i.RequestBody,
237
+		&i.ResponseStatus,
238
+		&i.ResponseHeaders,
239
+		&i.ResponseBody,
240
+		&i.ResponseTruncated,
241
+		&i.StartedAt,
242
+		&i.CompletedAt,
243
+		&i.Attempt,
244
+		&i.MaxAttempts,
245
+		&i.NextRetryAt,
246
+		&i.Status,
247
+		&i.IdempotencyKey,
248
+		&i.ErrorSummary,
249
+		&i.RedeliverOf,
250
+	)
251
+	return i, err
252
+}
253
+
254
+const getWebhookByID = `-- name: GetWebhookByID :one
255
+SELECT id, owner_kind, owner_id, url, content_type, events, secret_ciphertext, secret_nonce, active, ssl_verification, consecutive_failures, auto_disable_threshold, disabled_at, disabled_reason, last_success_at, last_failure_at, created_by_user_id, created_at, updated_at FROM webhooks WHERE id = $1
256
+`
257
+
258
+func (q *Queries) GetWebhookByID(ctx context.Context, db DBTX, id int64) (Webhook, error) {
259
+	row := db.QueryRow(ctx, getWebhookByID, id)
260
+	var i Webhook
261
+	err := row.Scan(
262
+		&i.ID,
263
+		&i.OwnerKind,
264
+		&i.OwnerID,
265
+		&i.Url,
266
+		&i.ContentType,
267
+		&i.Events,
268
+		&i.SecretCiphertext,
269
+		&i.SecretNonce,
270
+		&i.Active,
271
+		&i.SslVerification,
272
+		&i.ConsecutiveFailures,
273
+		&i.AutoDisableThreshold,
274
+		&i.DisabledAt,
275
+		&i.DisabledReason,
276
+		&i.LastSuccessAt,
277
+		&i.LastFailureAt,
278
+		&i.CreatedByUserID,
279
+		&i.CreatedAt,
280
+		&i.UpdatedAt,
281
+	)
282
+	return i, err
283
+}
284
+
285
+const listActiveWebhooksForOwner = `-- name: ListActiveWebhooksForOwner :many
286
+SELECT id, owner_kind, owner_id, url, content_type, events, secret_ciphertext, secret_nonce, active, ssl_verification, consecutive_failures, auto_disable_threshold, disabled_at, disabled_reason, last_success_at, last_failure_at, created_by_user_id, created_at, updated_at FROM webhooks
287
+WHERE owner_kind = $1 AND owner_id = $2
288
+  AND active = true AND disabled_at IS NULL
289
+`
290
+
291
+type ListActiveWebhooksForOwnerParams struct {
292
+	OwnerKind WebhookOwnerKind
293
+	OwnerID   int64
294
+}
295
+
296
+// Used by fanout to find subscribers for an event.
297
+func (q *Queries) ListActiveWebhooksForOwner(ctx context.Context, db DBTX, arg ListActiveWebhooksForOwnerParams) ([]Webhook, error) {
298
+	rows, err := db.Query(ctx, listActiveWebhooksForOwner, arg.OwnerKind, arg.OwnerID)
299
+	if err != nil {
300
+		return nil, err
301
+	}
302
+	defer rows.Close()
303
+	items := []Webhook{}
304
+	for rows.Next() {
305
+		var i Webhook
306
+		if err := rows.Scan(
307
+			&i.ID,
308
+			&i.OwnerKind,
309
+			&i.OwnerID,
310
+			&i.Url,
311
+			&i.ContentType,
312
+			&i.Events,
313
+			&i.SecretCiphertext,
314
+			&i.SecretNonce,
315
+			&i.Active,
316
+			&i.SslVerification,
317
+			&i.ConsecutiveFailures,
318
+			&i.AutoDisableThreshold,
319
+			&i.DisabledAt,
320
+			&i.DisabledReason,
321
+			&i.LastSuccessAt,
322
+			&i.LastFailureAt,
323
+			&i.CreatedByUserID,
324
+			&i.CreatedAt,
325
+			&i.UpdatedAt,
326
+		); err != nil {
327
+			return nil, err
328
+		}
329
+		items = append(items, i)
330
+	}
331
+	if err := rows.Err(); err != nil {
332
+		return nil, err
333
+	}
334
+	return items, nil
335
+}
336
+
337
+const listDeliveriesForWebhook = `-- name: ListDeliveriesForWebhook :many
338
+SELECT id, webhook_id, event_kind, event_id, delivery_uuid, response_status,
339
+       response_truncated, started_at, completed_at, attempt, max_attempts,
340
+       next_retry_at, status, error_summary, redeliver_of
341
+FROM webhook_deliveries
342
+WHERE webhook_id = $1
343
+ORDER BY started_at DESC
344
+LIMIT $2
345
+`
346
+
347
+type ListDeliveriesForWebhookParams struct {
348
+	WebhookID int64
349
+	Limit     int32
350
+}
351
+
352
+type ListDeliveriesForWebhookRow struct {
353
+	ID                int64
354
+	WebhookID         int64
355
+	EventKind         string
356
+	EventID           pgtype.Int8
357
+	DeliveryUuid      pgtype.UUID
358
+	ResponseStatus    pgtype.Int4
359
+	ResponseTruncated bool
360
+	StartedAt         pgtype.Timestamptz
361
+	CompletedAt       pgtype.Timestamptz
362
+	Attempt           int32
363
+	MaxAttempts       int32
364
+	NextRetryAt       pgtype.Timestamptz
365
+	Status            WebhookDeliveryStatus
366
+	ErrorSummary      pgtype.Text
367
+	RedeliverOf       pgtype.Int8
368
+}
369
+
370
+func (q *Queries) ListDeliveriesForWebhook(ctx context.Context, db DBTX, arg ListDeliveriesForWebhookParams) ([]ListDeliveriesForWebhookRow, error) {
371
+	rows, err := db.Query(ctx, listDeliveriesForWebhook, arg.WebhookID, arg.Limit)
372
+	if err != nil {
373
+		return nil, err
374
+	}
375
+	defer rows.Close()
376
+	items := []ListDeliveriesForWebhookRow{}
377
+	for rows.Next() {
378
+		var i ListDeliveriesForWebhookRow
379
+		if err := rows.Scan(
380
+			&i.ID,
381
+			&i.WebhookID,
382
+			&i.EventKind,
383
+			&i.EventID,
384
+			&i.DeliveryUuid,
385
+			&i.ResponseStatus,
386
+			&i.ResponseTruncated,
387
+			&i.StartedAt,
388
+			&i.CompletedAt,
389
+			&i.Attempt,
390
+			&i.MaxAttempts,
391
+			&i.NextRetryAt,
392
+			&i.Status,
393
+			&i.ErrorSummary,
394
+			&i.RedeliverOf,
395
+		); err != nil {
396
+			return nil, err
397
+		}
398
+		items = append(items, i)
399
+	}
400
+	if err := rows.Err(); err != nil {
401
+		return nil, err
402
+	}
403
+	return items, nil
404
+}
405
+
406
+const listWebhooksForOwner = `-- name: ListWebhooksForOwner :many
407
+SELECT id, owner_kind, owner_id, url, content_type, events, secret_ciphertext, secret_nonce, active, ssl_verification, consecutive_failures, auto_disable_threshold, disabled_at, disabled_reason, last_success_at, last_failure_at, created_by_user_id, created_at, updated_at FROM webhooks
408
+WHERE owner_kind = $1 AND owner_id = $2
409
+ORDER BY created_at DESC
410
+`
411
+
412
+type ListWebhooksForOwnerParams struct {
413
+	OwnerKind WebhookOwnerKind
414
+	OwnerID   int64
415
+}
416
+
417
+func (q *Queries) ListWebhooksForOwner(ctx context.Context, db DBTX, arg ListWebhooksForOwnerParams) ([]Webhook, error) {
418
+	rows, err := db.Query(ctx, listWebhooksForOwner, arg.OwnerKind, arg.OwnerID)
419
+	if err != nil {
420
+		return nil, err
421
+	}
422
+	defer rows.Close()
423
+	items := []Webhook{}
424
+	for rows.Next() {
425
+		var i Webhook
426
+		if err := rows.Scan(
427
+			&i.ID,
428
+			&i.OwnerKind,
429
+			&i.OwnerID,
430
+			&i.Url,
431
+			&i.ContentType,
432
+			&i.Events,
433
+			&i.SecretCiphertext,
434
+			&i.SecretNonce,
435
+			&i.Active,
436
+			&i.SslVerification,
437
+			&i.ConsecutiveFailures,
438
+			&i.AutoDisableThreshold,
439
+			&i.DisabledAt,
440
+			&i.DisabledReason,
441
+			&i.LastSuccessAt,
442
+			&i.LastFailureAt,
443
+			&i.CreatedByUserID,
444
+			&i.CreatedAt,
445
+			&i.UpdatedAt,
446
+		); err != nil {
447
+			return nil, err
448
+		}
449
+		items = append(items, i)
450
+	}
451
+	if err := rows.Err(); err != nil {
452
+		return nil, err
453
+	}
454
+	return items, nil
455
+}
456
+
457
+const markDeliveryPermanentFailure = `-- name: MarkDeliveryPermanentFailure :exec
458
+UPDATE webhook_deliveries
459
+   SET status              = 'failed_permanent',
460
+       response_status     = $1::int,
461
+       response_headers    = $2::jsonb,
462
+       response_body       = $3::bytea,
463
+       response_truncated  = $4::bool,
464
+       completed_at        = now(),
465
+       error_summary       = $5::text
466
+ WHERE id = $6::bigint
467
+`
468
+
469
+type MarkDeliveryPermanentFailureParams struct {
470
+	ResponseStatus    pgtype.Int4
471
+	ResponseHeaders   []byte
472
+	ResponseBody      []byte
473
+	ResponseTruncated bool
474
+	ErrorSummary      string
475
+	ID                int64
476
+}
477
+
478
+func (q *Queries) MarkDeliveryPermanentFailure(ctx context.Context, db DBTX, arg MarkDeliveryPermanentFailureParams) error {
479
+	_, err := db.Exec(ctx, markDeliveryPermanentFailure,
480
+		arg.ResponseStatus,
481
+		arg.ResponseHeaders,
482
+		arg.ResponseBody,
483
+		arg.ResponseTruncated,
484
+		arg.ErrorSummary,
485
+		arg.ID,
486
+	)
487
+	return err
488
+}
489
+
490
+const markDeliveryRetry = `-- name: MarkDeliveryRetry :exec
491
+UPDATE webhook_deliveries
492
+   SET status              = 'failed_retry',
493
+       attempt             = attempt + 1,
494
+       response_status     = $1::int,
495
+       response_headers    = $2::jsonb,
496
+       response_body       = $3::bytea,
497
+       response_truncated  = $4::bool,
498
+       next_retry_at       = $5::timestamptz,
499
+       error_summary       = $6::text
500
+ WHERE id = $7::bigint
501
+`
502
+
503
+type MarkDeliveryRetryParams struct {
504
+	ResponseStatus    pgtype.Int4
505
+	ResponseHeaders   []byte
506
+	ResponseBody      []byte
507
+	ResponseTruncated bool
508
+	NextRetryAt       pgtype.Timestamptz
509
+	ErrorSummary      string
510
+	ID                int64
511
+}
512
+
513
+func (q *Queries) MarkDeliveryRetry(ctx context.Context, db DBTX, arg MarkDeliveryRetryParams) error {
514
+	_, err := db.Exec(ctx, markDeliveryRetry,
515
+		arg.ResponseStatus,
516
+		arg.ResponseHeaders,
517
+		arg.ResponseBody,
518
+		arg.ResponseTruncated,
519
+		arg.NextRetryAt,
520
+		arg.ErrorSummary,
521
+		arg.ID,
522
+	)
523
+	return err
524
+}
525
+
526
+const markDeliverySucceeded = `-- name: MarkDeliverySucceeded :exec
527
+UPDATE webhook_deliveries
528
+   SET status              = 'succeeded',
529
+       response_status     = $2,
530
+       response_headers    = $3,
531
+       response_body       = $4,
532
+       response_truncated  = $5,
533
+       completed_at        = now(),
534
+       error_summary       = NULL
535
+ WHERE id = $1
536
+`
537
+
538
+type MarkDeliverySucceededParams struct {
539
+	ID                int64
540
+	ResponseStatus    pgtype.Int4
541
+	ResponseHeaders   []byte
542
+	ResponseBody      []byte
543
+	ResponseTruncated bool
544
+}
545
+
546
+func (q *Queries) MarkDeliverySucceeded(ctx context.Context, db DBTX, arg MarkDeliverySucceededParams) error {
547
+	_, err := db.Exec(ctx, markDeliverySucceeded,
548
+		arg.ID,
549
+		arg.ResponseStatus,
550
+		arg.ResponseHeaders,
551
+		arg.ResponseBody,
552
+		arg.ResponseTruncated,
553
+	)
554
+	return err
555
+}
556
+
557
+const purgeOldDeliveries = `-- name: PurgeOldDeliveries :execrows
558
+DELETE FROM webhook_deliveries
559
+WHERE status IN ('succeeded', 'failed_permanent')
560
+  AND completed_at < now() - $1::interval
561
+`
562
+
563
+// Cron: drops terminal deliveries older than the retention window.
564
+// pending/failed_retry rows are left alone so an in-flight retry isn't
565
+// aborted out from under the worker.
566
+func (q *Queries) PurgeOldDeliveries(ctx context.Context, db DBTX, retention pgtype.Interval) (int64, error) {
567
+	result, err := db.Exec(ctx, purgeOldDeliveries, retention)
568
+	if err != nil {
569
+		return 0, err
570
+	}
571
+	return result.RowsAffected(), nil
572
+}
573
+
574
+const recordWebhookFailure = `-- name: RecordWebhookFailure :one
575
+UPDATE webhooks
576
+   SET consecutive_failures = consecutive_failures + 1,
577
+       last_failure_at      = now(),
578
+       updated_at           = now()
579
+ WHERE id = $1
580
+RETURNING consecutive_failures, auto_disable_threshold
581
+`
582
+
583
+type RecordWebhookFailureRow struct {
584
+	ConsecutiveFailures  int32
585
+	AutoDisableThreshold int32
586
+}
587
+
588
+// Increments the failure counter and reports the new value so the
589
+// caller can decide whether to auto-disable.
590
+func (q *Queries) RecordWebhookFailure(ctx context.Context, db DBTX, id int64) (RecordWebhookFailureRow, error) {
591
+	row := db.QueryRow(ctx, recordWebhookFailure, id)
592
+	var i RecordWebhookFailureRow
593
+	err := row.Scan(&i.ConsecutiveFailures, &i.AutoDisableThreshold)
594
+	return i, err
595
+}
596
+
597
+const recordWebhookSuccess = `-- name: RecordWebhookSuccess :exec
598
+UPDATE webhooks
599
+   SET consecutive_failures = 0,
600
+       last_success_at      = now(),
601
+       updated_at           = now()
602
+ WHERE id = $1
603
+`
604
+
605
+func (q *Queries) RecordWebhookSuccess(ctx context.Context, db DBTX, id int64) error {
606
+	_, err := db.Exec(ctx, recordWebhookSuccess, id)
607
+	return err
608
+}
609
+
610
+const setWebhookActive = `-- name: SetWebhookActive :exec
611
+UPDATE webhooks
612
+   SET active               = $2,
613
+       disabled_at          = NULL,
614
+       disabled_reason      = NULL,
615
+       consecutive_failures = 0,
616
+       updated_at           = now()
617
+ WHERE id = $1
618
+`
619
+
620
+type SetWebhookActiveParams struct {
621
+	ID     int64
622
+	Active bool
623
+}
624
+
625
+// Re-enables a previously auto-disabled webhook (UI affordance).
626
+// Resets the failure counter and clears disabled_at/reason.
627
+func (q *Queries) SetWebhookActive(ctx context.Context, db DBTX, arg SetWebhookActiveParams) error {
628
+	_, err := db.Exec(ctx, setWebhookActive, arg.ID, arg.Active)
629
+	return err
630
+}
631
+
632
+const updateWebhook = `-- name: UpdateWebhook :exec
633
+UPDATE webhooks
634
+   SET url                    = $2,
635
+       content_type           = $3,
636
+       events                 = $4,
637
+       active                 = $5,
638
+       ssl_verification       = $6,
639
+       auto_disable_threshold = $7,
640
+       updated_at             = now()
641
+ WHERE id = $1
642
+`
643
+
644
+type UpdateWebhookParams struct {
645
+	ID                   int64
646
+	Url                  string
647
+	ContentType          WebhookContentType
648
+	Events               []string
649
+	Active               bool
650
+	SslVerification      bool
651
+	AutoDisableThreshold int32
652
+}
653
+
654
+func (q *Queries) UpdateWebhook(ctx context.Context, db DBTX, arg UpdateWebhookParams) error {
655
+	_, err := db.Exec(ctx, updateWebhook,
656
+		arg.ID,
657
+		arg.Url,
658
+		arg.ContentType,
659
+		arg.Events,
660
+		arg.Active,
661
+		arg.SslVerification,
662
+		arg.AutoDisableThreshold,
663
+	)
664
+	return err
665
+}
666
+
667
+const updateWebhookSecret = `-- name: UpdateWebhookSecret :exec
668
+UPDATE webhooks
669
+   SET secret_ciphertext = $2,
670
+       secret_nonce      = $3,
671
+       updated_at        = now()
672
+ WHERE id = $1
673
+`
674
+
675
+type UpdateWebhookSecretParams struct {
676
+	ID               int64
677
+	SecretCiphertext []byte
678
+	SecretNonce      []byte
679
+}
680
+
681
+func (q *Queries) UpdateWebhookSecret(ctx context.Context, db DBTX, arg UpdateWebhookSecretParams) error {
682
+	_, err := db.Exec(ctx, updateWebhookSecret, arg.ID, arg.SecretCiphertext, arg.SecretNonce)
683
+	return err
684
+}
sqlc.yamlmodified
@@ -177,3 +177,19 @@ sql:
177177
         emit_exact_table_names: false
178178
         emit_empty_slices: true
179179
         emit_methods_with_db_argument: true
180
+
181
+  - engine: postgresql
182
+    schema: internal/migrationsfs/migrations
183
+    queries: internal/webhook/queries
184
+    gen:
185
+      go:
186
+        package: webhookdb
187
+        out: internal/webhook/sqlc
188
+        sql_package: pgx/v5
189
+        emit_json_tags: false
190
+        emit_pointers_for_null_types: false
191
+        emit_prepared_queries: false
192
+        emit_interface: true
193
+        emit_exact_table_names: false
194
+        emit_empty_slices: true
195
+        emit_methods_with_db_argument: true