coverage~bigbes/sr-ht-spec3cb1c03dgraph/model/webhooks.go

Coverage
0.0% 0/65 statements
Δ
Blob
7b9d432
1 package model
2
3 import (
4 "context"
5 "database/sql"
6 "fmt"
7 "strconv"
8 "time"
9
10 sq "github.com/Masterminds/squirrel"
11 "github.com/lib/pq"
12
13 "sourcecraft.dev/bigbes/sr-ht-core/database"
14 coremodel "sourcecraft.dev/bigbes/sr-ht-core/model"
15 )
16
17 // WebhookDelivery is a record of a single webhook delivery attempt. It is
18 // hand-written (rather than generated) so it can satisfy database.Model:
19 // gqlgen autobinds it by name, and the field maps below are what the core-go
20 // query builder selects and scans through.
21 //
22 // Name is the table prefix — "user" here — so Table() resolves to
23 // gql_user_wh_delivery. It is not a GraphQL field; it is carried so a delivery
24 // loaded in a listing knows which subscription table to load its subscription
25 // from.
26 type WebhookDelivery struct {
27 UUID string `json:"uuid"`
28 Date time.Time `json:"date"`
29 Event WebhookEvent `json:"event"`
30 RequestBody string `json:"requestBody"`
31 ResponseBody *string `json:"responseBody"`
32 ResponseHeaders *string `json:"responseHeaders"`
33 ResponseStatus *int `json:"responseStatus"`
34
35 ID int
36 SubscriptionID int
37 Name string
38
39 alias string
40 fields *database.ModelFields
41 }
42
43 0 func (whd *WebhookDelivery) WithName(name string) *WebhookDelivery {
44 0 whd.Name = name
45 0 return whd
46 0 }
47
48 0 func (whd *WebhookDelivery) As(alias string) *WebhookDelivery {
49 0 whd.alias = alias
50 0 return whd
51 0 }
52
53 0 func (whd *WebhookDelivery) Alias() string {
54 0 return whd.alias
55 0 }
56
57 0 func (whd *WebhookDelivery) Table() string {
58 0 return "gql_" + whd.Name + "_wh_delivery"
59 0 }
60
61 0 func (whd *WebhookDelivery) Fields() *database.ModelFields {
62 0 if whd.fields != nil {
63 0 return whd.fields
64 0 }
65 0 whd.fields = &database.ModelFields{
66 0 Fields: []*database.FieldMap{
67 0 {SQL: "uuid", GQL: "uuid", Ptr: &whd.UUID},
68 0 {SQL: "date", GQL: "date", Ptr: &whd.Date},
69 0 {SQL: "event", GQL: "event", Ptr: &whd.Event},
70 0 {SQL: "request_body", GQL: "requestBody", Ptr: &whd.RequestBody},
71 0 {SQL: "response_body", GQL: "responseBody", Ptr: &whd.ResponseBody},
72 0 {SQL: "response_headers", GQL: "responseHeaders", Ptr: &whd.ResponseHeaders},
73 0 {SQL: "response_status", GQL: "responseStatus", Ptr: &whd.ResponseStatus},
74 0
75 0 // Always fetch:
76 0 {SQL: "id", GQL: "", Ptr: &whd.ID},
77 0 {SQL: "subscription_id", GQL: "", Ptr: &whd.SubscriptionID},
78 0 },
79 0 }
80 0 return whd.fields
81 }
82
83 func (whd *WebhookDelivery) QueryWithCursor(ctx context.Context,
84 runner sq.BaseRunner, q sq.SelectBuilder,
85 0 cur *coremodel.Cursor) ([]*WebhookDelivery, *coremodel.Cursor) {
86 0 var (
87 0 err error
88 0 rows *sql.Rows
89 0 )
90 0
91 0 if cur.Next != "" {
92 0 next, _ := strconv.ParseInt(cur.Next, 10, 64)
93 0 q = q.Where(database.WithAlias(whd.alias, "id")+"<= ?", next)
94 0 }
95 0 q = q.
96 0 OrderBy(database.WithAlias(whd.alias, "id") + " DESC").
97 0 Limit(uint64(cur.Count + 1))
98 0
99 0 if rows, err = q.RunWith(runner).QueryContext(ctx); err != nil {
100 0 panic(err)
101 }
102 0 defer rows.Close()
103 0
104 0 var deliveries []*WebhookDelivery
105 0 for rows.Next() {
106 0 var delivery WebhookDelivery
107 0 if err := rows.Scan(database.Scan(ctx, &delivery)...); err != nil {
108 0 panic(err)
109 }
110 0 delivery.Name = whd.Name
111 0 deliveries = append(deliveries, &delivery)
112 }
113
114 0 if len(deliveries) > cur.Count {
115 0 cur = &coremodel.Cursor{
116 0 Count: cur.Count,
117 0 Next: strconv.Itoa(deliveries[len(deliveries)-1].ID),
118 0 Search: cur.Search,
119 0 }
120 0 deliveries = deliveries[:cur.Count]
121 0 } else {
122 0 cur = nil
123 0 }
124
125 0 return deliveries, cur
126 }
127
128 // UserWebhookSubscription is a user-scoped webhook subscription. spec.sr.ht is
129 // single-owner, so the OAuth client field pages.sr.ht carries is dropped from
130 // the schema; the client_id column still exists in the DB and is loaded here as
131 // an always-fetch, non-GraphQL field so FilterWebhooks and the auth-config
132 // round-trip keep working unchanged.
133 type UserWebhookSubscription struct {
134 ID int `json:"id"`
135 Events []WebhookEvent `json:"events"`
136 Query string `json:"query"`
137 URL string `json:"url"`
138
139 UserID int
140 AuthMethod string
141 ClientID *string
142 TokenHash *string
143 Expires *time.Time
144 Grants *string
145 NodeID *string
146
147 alias string
148 fields *database.ModelFields
149 }
150
151 // Scan lets a WebhookEvent be read straight out of a pq.Array column.
152 0 func (we *WebhookEvent) Scan(src any) error {
153 0 bytes, ok := src.([]uint8)
154 0 if !ok {
155 0 return fmt.Errorf("unable to scan from %T into WebhookEvent", src)
156 0 }
157 0 *we = WebhookEvent(string(bytes))
158 0 if !we.IsValid() {
159 0 return fmt.Errorf("%s is not a valid WebhookEvent", string(bytes))
160 0 }
161 0 return nil
162 }
163
164 func (UserWebhookSubscription) IsWebhookSubscription() {}
165
166 0 func (sub *UserWebhookSubscription) As(alias string) *UserWebhookSubscription {
167 0 sub.alias = alias
168 0 return sub
169 0 }
170
171 0 func (sub *UserWebhookSubscription) Alias() string {
172 0 return sub.alias
173 0 }
174
175 0 func (sub *UserWebhookSubscription) Table() string {
176 0 return "gql_user_wh_sub"
177 0 }
178
179 0 func (sub *UserWebhookSubscription) Fields() *database.ModelFields {
180 0 if sub.fields != nil {
181 0 return sub.fields
182 0 }
183 0 sub.fields = &database.ModelFields{
184 0 Fields: []*database.FieldMap{
185 0 {SQL: "events", GQL: "events", Ptr: pq.Array(&sub.Events)},
186 0 {SQL: "url", GQL: "url", Ptr: &sub.URL},
187 0
188 0 // Always fetch:
189 0 {SQL: "id", GQL: "", Ptr: &sub.ID},
190 0 {SQL: "query", GQL: "", Ptr: &sub.Query},
191 0 {SQL: "user_id", GQL: "", Ptr: &sub.UserID},
192 0 {SQL: "auth_method", GQL: "", Ptr: &sub.AuthMethod},
193 0 {SQL: "token_hash", GQL: "", Ptr: &sub.TokenHash},
194 0 {SQL: "client_id", GQL: "", Ptr: &sub.ClientID},
195 0 {SQL: "grants", GQL: "", Ptr: &sub.Grants},
196 0 {SQL: "expires", GQL: "", Ptr: &sub.Expires},
197 0 {SQL: "node_id", GQL: "", Ptr: &sub.NodeID},
198 0 },
199 0 }
200 0 return sub.fields
201 }
202
203 func (sub *UserWebhookSubscription) QueryWithCursor(ctx context.Context,
204 runner sq.BaseRunner, q sq.SelectBuilder,
205 0 cur *coremodel.Cursor) ([]WebhookSubscription, *coremodel.Cursor) {
206 0 var (
207 0 err error
208 0 rows *sql.Rows
209 0 )
210 0
211 0 if cur.Next != "" {
212 0 next, _ := strconv.ParseInt(cur.Next, 10, 64)
213 0 q = q.Where(database.WithAlias(sub.alias, "id")+"<= ?", next)
214 0 }
215 0 q = q.
216 0 OrderBy(database.WithAlias(sub.alias, "id")).
217 0 Limit(uint64(cur.Count + 1))
218 0
219 0 if rows, err = q.RunWith(runner).QueryContext(ctx); err != nil {
220 0 panic(err)
221 }
222 0 defer rows.Close()
223 0
224 0 var (
225 0 subs []WebhookSubscription
226 0 lastID int
227 0 )
228 0 for rows.Next() {
229 0 var sub UserWebhookSubscription
230 0 if err := rows.Scan(database.Scan(ctx, &sub)...); err != nil {
231 0 panic(err)
232 }
233 0 subs = append(subs, &sub)
234 0 lastID = sub.ID
235 }
236
237 0 if len(subs) > cur.Count {
238 0 cur = &coremodel.Cursor{
239 0 Count: cur.Count,
240 0 Next: strconv.Itoa(lastID),
241 0 Search: cur.Search,
242 0 }
243 0 subs = subs[:cur.Count]
244 0 } else {
245 0 cur = nil
246 0 }
247
248 0 return subs, cur
249 }