This repository has no description
1package db
2
3import (
4 "database/sql"
5 "fmt"
6 "maps"
7 "slices"
8 "strings"
9 "time"
10
11 "github.com/bluesky-social/indigo/atproto/syntax"
12 "tangled.org/core/appview/models"
13 "tangled.org/core/orm"
14)
15
16// no updating type for now
17func AddLabelDefinition(e Execer, l *models.LabelDefinition) (int64, error) {
18 result, err := e.Exec(
19 `insert into label_definitions (
20 did,
21 rkey,
22 name,
23 value_type,
24 value_format,
25 value_enum,
26 scope,
27 color,
28 multiple,
29 created
30 )
31 values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
32 on conflict(did, rkey) do update set
33 name = excluded.name,
34 scope = excluded.scope,
35 color = excluded.color,
36 multiple = excluded.multiple`,
37 l.Did,
38 l.Rkey,
39 l.Name,
40 l.ValueType.Type,
41 l.ValueType.Format,
42 strings.Join(l.ValueType.Enum, ","),
43 strings.Join(l.Scope, ","),
44 l.Color,
45 l.Multiple,
46 l.Created.Format(time.RFC3339),
47 time.Now().Format(time.RFC3339),
48 )
49 if err != nil {
50 return 0, err
51 }
52
53 id, err := result.LastInsertId()
54 if err != nil {
55 return 0, err
56 }
57
58 l.Id = id
59
60 return id, nil
61}
62
63func DeleteLabelDefinition(e Execer, filters ...orm.Filter) error {
64 var conditions []string
65 var args []any
66 for _, filter := range filters {
67 conditions = append(conditions, filter.Condition())
68 args = append(args, filter.Arg()...)
69 }
70 whereClause := ""
71 if conditions != nil {
72 whereClause = " where " + strings.Join(conditions, " and ")
73 }
74 query := fmt.Sprintf(`delete from label_definitions %s`, whereClause)
75 _, err := e.Exec(query, args...)
76 return err
77}
78
79func GetLabelDefinitions(e Execer, filters ...orm.Filter) ([]models.LabelDefinition, error) {
80 var labelDefinitions []models.LabelDefinition
81 var conditions []string
82 var args []any
83
84 for _, filter := range filters {
85 conditions = append(conditions, filter.Condition())
86 args = append(args, filter.Arg()...)
87 }
88
89 whereClause := ""
90 if conditions != nil {
91 whereClause = " where " + strings.Join(conditions, " and ")
92 }
93
94 query := fmt.Sprintf(
95 `
96 select
97 id,
98 did,
99 rkey,
100 name,
101 value_type,
102 value_format,
103 value_enum,
104 scope,
105 color,
106 multiple,
107 created
108 from label_definitions
109 %s
110 order by created
111 `,
112 whereClause,
113 )
114
115 rows, err := e.Query(query, args...)
116 if err != nil {
117 return nil, err
118 }
119 defer rows.Close()
120
121 for rows.Next() {
122 var labelDefinition models.LabelDefinition
123 var createdAt, enumVariants, scopes string
124 var color sql.Null[string]
125 var multiple int
126
127 if err := rows.Scan(
128 &labelDefinition.Id,
129 &labelDefinition.Did,
130 &labelDefinition.Rkey,
131 &labelDefinition.Name,
132 &labelDefinition.ValueType.Type,
133 &labelDefinition.ValueType.Format,
134 &enumVariants,
135 &scopes,
136 &color,
137 &multiple,
138 &createdAt,
139 ); err != nil {
140 return nil, err
141 }
142
143 labelDefinition.Created, err = time.Parse(time.RFC3339, createdAt)
144 if err != nil {
145 labelDefinition.Created = time.Now()
146 }
147
148 if color.Valid {
149 labelDefinition.Color = &color.V
150 }
151
152 if multiple != 0 {
153 labelDefinition.Multiple = true
154 }
155
156 if enumVariants != "" {
157 labelDefinition.ValueType.Enum = strings.Split(enumVariants, ",")
158 }
159
160 for s := range strings.SplitSeq(scopes, ",") {
161 labelDefinition.Scope = append(labelDefinition.Scope, s)
162 }
163
164 labelDefinitions = append(labelDefinitions, labelDefinition)
165 }
166
167 return labelDefinitions, nil
168}
169
170// helper to get exactly one label def
171func GetLabelDefinition(e Execer, filters ...orm.Filter) (*models.LabelDefinition, error) {
172 labels, err := GetLabelDefinitions(e, filters...)
173 if err != nil {
174 return nil, err
175 }
176
177 if labels == nil {
178 return nil, sql.ErrNoRows
179 }
180
181 if len(labels) != 1 {
182 return nil, fmt.Errorf("too many rows returned")
183 }
184
185 return &labels[0], nil
186}
187
188func AddLabelOp(e Execer, l *models.LabelOp) (int64, error) {
189 result, err := e.Exec(
190 `insert into label_ops (
191 did,
192 rkey,
193 subject,
194 operation,
195 operand_key,
196 operand_value,
197 performed
198 )
199 values (?, ?, ?, ?, ?, ?, ?)
200 on conflict(did, rkey, subject, operand_key, operand_value) do update set
201 operation = excluded.operation,
202 operand_value = excluded.operand_value,
203 performed = excluded.performed`,
204 l.Did,
205 l.Rkey,
206 l.Subject.String(),
207 string(l.Operation),
208 l.OperandKey,
209 l.OperandValue,
210 l.PerformedAt.Format(time.RFC3339),
211 )
212 if err != nil {
213 return 0, err
214 }
215
216 id, err := result.LastInsertId()
217 if err != nil {
218 return 0, err
219 }
220
221 l.Id = id
222
223 return id, nil
224}
225
226func DeleteLabelOps(e Execer, filters ...orm.Filter) error {
227 var conditions []string
228 var args []any
229 for _, filter := range filters {
230 conditions = append(conditions, filter.Condition())
231 args = append(args, filter.Arg()...)
232 }
233 whereClause := ""
234 if conditions != nil {
235 whereClause = " where " + strings.Join(conditions, " and ")
236 }
237 query := fmt.Sprintf(`delete from label_ops %s`, whereClause)
238 _, err := e.Exec(query, args...)
239 return err
240}
241
242func GetLabelOps(e Execer, filters ...orm.Filter) ([]models.LabelOp, error) {
243 var labelOps []models.LabelOp
244 var conditions []string
245 var args []any
246
247 for _, filter := range filters {
248 conditions = append(conditions, filter.Condition())
249 args = append(args, filter.Arg()...)
250 }
251
252 whereClause := ""
253 if conditions != nil {
254 whereClause = " where " + strings.Join(conditions, " and ")
255 }
256
257 query := fmt.Sprintf(
258 `
259 select
260 id,
261 did,
262 rkey,
263 subject,
264 operation,
265 operand_key,
266 operand_value,
267 performed
268 from label_ops
269 %s
270 order by id
271 `,
272 whereClause,
273 )
274
275 rows, err := e.Query(query, args...)
276 if err != nil {
277 return nil, err
278 }
279 defer rows.Close()
280
281 for rows.Next() {
282 var labelOp models.LabelOp
283 var performedAt string
284
285 if err := rows.Scan(
286 &labelOp.Id,
287 &labelOp.Did,
288 &labelOp.Rkey,
289 &labelOp.Subject,
290 &labelOp.Operation,
291 &labelOp.OperandKey,
292 &labelOp.OperandValue,
293 &performedAt,
294 ); err != nil {
295 return nil, err
296 }
297
298 labelOp.PerformedAt, err = time.Parse(time.RFC3339, performedAt)
299 if err != nil {
300 labelOp.PerformedAt = time.Time{}
301 }
302
303 labelOps = append(labelOps, labelOp)
304 }
305
306 return labelOps, nil
307}
308
309// get labels for a given list of subject URIs
310func GetLabels(e Execer, filters ...orm.Filter) (map[syntax.ATURI]models.LabelState, error) {
311 ops, err := GetLabelOps(e, filters...)
312 if err != nil {
313 return nil, err
314 }
315
316 // group ops by subject
317 opsBySubject := make(map[syntax.ATURI][]models.LabelOp)
318 for _, op := range ops {
319 subject := syntax.ATURI(op.Subject)
320 opsBySubject[subject] = append(opsBySubject[subject], op)
321 }
322
323 // get all unique labelats for creating the context
324 labelAtSet := make(map[string]bool)
325 for _, op := range ops {
326 labelAtSet[op.OperandKey] = true
327 }
328 labelAts := slices.Collect(maps.Keys(labelAtSet))
329
330 actx, err := NewLabelApplicationCtx(e, orm.FilterIn("at_uri", labelAts))
331 if err != nil {
332 return nil, err
333 }
334
335 // apply label ops for each subject and collect results
336 results := make(map[syntax.ATURI]models.LabelState)
337 for subject, subjectOps := range opsBySubject {
338 state := models.NewLabelState()
339 actx.ApplyLabelOps(state, subjectOps)
340 results[subject] = state
341 }
342
343 return results, nil
344}
345
346func NewLabelApplicationCtx(e Execer, filters ...orm.Filter) (*models.LabelApplicationCtx, error) {
347 labels, err := GetLabelDefinitions(e, filters...)
348 if err != nil {
349 return nil, err
350 }
351
352 defs := make(map[string]*models.LabelDefinition)
353 for _, l := range labels {
354 defs[l.AtUri().String()] = &l
355 }
356
357 return &models.LabelApplicationCtx{Defs: defs}, nil
358}