forked from
tangled.org/core
Monorepo for Tangled — https://tangled.org
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 now := time.Now()
190 result, err := e.Exec(
191 `insert into label_ops (
192 did,
193 rkey,
194 subject,
195 operation,
196 operand_key,
197 operand_value,
198 performed,
199 indexed
200 )
201 values (?, ?, ?, ?, ?, ?, ?, ?)
202 on conflict(did, rkey, subject, operand_key, operand_value) do update set
203 operation = excluded.operation,
204 operand_value = excluded.operand_value,
205 performed = excluded.performed,
206 indexed = excluded.indexed`,
207 l.Did,
208 l.Rkey,
209 l.Subject.String(),
210 string(l.Operation),
211 l.OperandKey,
212 l.OperandValue,
213 l.PerformedAt.Format(time.RFC3339),
214 now.Format(time.RFC3339),
215 )
216 if err != nil {
217 return 0, err
218 }
219
220 id, err := result.LastInsertId()
221 if err != nil {
222 return 0, err
223 }
224
225 l.Id = id
226 l.IndexedAt = now
227
228 return id, nil
229}
230
231func GetLabelOps(e Execer, filters ...orm.Filter) ([]models.LabelOp, error) {
232 var labelOps []models.LabelOp
233 var conditions []string
234 var args []any
235
236 for _, filter := range filters {
237 conditions = append(conditions, filter.Condition())
238 args = append(args, filter.Arg()...)
239 }
240
241 whereClause := ""
242 if conditions != nil {
243 whereClause = " where " + strings.Join(conditions, " and ")
244 }
245
246 query := fmt.Sprintf(
247 `
248 select
249 id,
250 did,
251 rkey,
252 subject,
253 operation,
254 operand_key,
255 operand_value,
256 performed,
257 indexed
258 from label_ops
259 %s
260 order by indexed
261 `,
262 whereClause,
263 )
264
265 rows, err := e.Query(query, args...)
266 if err != nil {
267 return nil, err
268 }
269 defer rows.Close()
270
271 for rows.Next() {
272 var labelOp models.LabelOp
273 var performedAt, indexedAt string
274
275 if err := rows.Scan(
276 &labelOp.Id,
277 &labelOp.Did,
278 &labelOp.Rkey,
279 &labelOp.Subject,
280 &labelOp.Operation,
281 &labelOp.OperandKey,
282 &labelOp.OperandValue,
283 &performedAt,
284 &indexedAt,
285 ); err != nil {
286 return nil, err
287 }
288
289 labelOp.PerformedAt, err = time.Parse(time.RFC3339, performedAt)
290 if err != nil {
291 labelOp.PerformedAt = time.Now()
292 }
293
294 labelOp.IndexedAt, err = time.Parse(time.RFC3339, indexedAt)
295 if err != nil {
296 labelOp.IndexedAt = time.Now()
297 }
298
299 labelOps = append(labelOps, labelOp)
300 }
301
302 return labelOps, nil
303}
304
305// get labels for a given list of subject URIs
306func GetLabels(e Execer, filters ...orm.Filter) (map[syntax.ATURI]models.LabelState, error) {
307 ops, err := GetLabelOps(e, filters...)
308 if err != nil {
309 return nil, err
310 }
311
312 // group ops by subject
313 opsBySubject := make(map[syntax.ATURI][]models.LabelOp)
314 for _, op := range ops {
315 subject := syntax.ATURI(op.Subject)
316 opsBySubject[subject] = append(opsBySubject[subject], op)
317 }
318
319 // get all unique labelats for creating the context
320 labelAtSet := make(map[string]bool)
321 for _, op := range ops {
322 labelAtSet[op.OperandKey] = true
323 }
324 labelAts := slices.Collect(maps.Keys(labelAtSet))
325
326 actx, err := NewLabelApplicationCtx(e, orm.FilterIn("at_uri", labelAts))
327 if err != nil {
328 return nil, err
329 }
330
331 // apply label ops for each subject and collect results
332 results := make(map[syntax.ATURI]models.LabelState)
333 for subject, subjectOps := range opsBySubject {
334 state := models.NewLabelState()
335 actx.ApplyLabelOps(state, subjectOps)
336 results[subject] = state
337 }
338
339 return results, nil
340}
341
342func NewLabelApplicationCtx(e Execer, filters ...orm.Filter) (*models.LabelApplicationCtx, error) {
343 labels, err := GetLabelDefinitions(e, filters...)
344 if err != nil {
345 return nil, err
346 }
347
348 defs := make(map[string]*models.LabelDefinition)
349 for _, l := range labels {
350 defs[l.AtUri().String()] = &l
351 }
352
353 return &models.LabelApplicationCtx{Defs: defs}, nil
354}