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 GetLabelOps(e Execer, filters ...orm.Filter) ([]models.LabelOp, error) {
227 var labelOps []models.LabelOp
228 var conditions []string
229 var args []any
230
231 for _, filter := range filters {
232 conditions = append(conditions, filter.Condition())
233 args = append(args, filter.Arg()...)
234 }
235
236 whereClause := ""
237 if conditions != nil {
238 whereClause = " where " + strings.Join(conditions, " and ")
239 }
240
241 query := fmt.Sprintf(
242 `
243 select
244 id,
245 did,
246 rkey,
247 subject,
248 operation,
249 operand_key,
250 operand_value,
251 performed
252 from label_ops
253 %s
254 order by id
255 `,
256 whereClause,
257 )
258
259 rows, err := e.Query(query, args...)
260 if err != nil {
261 return nil, err
262 }
263 defer rows.Close()
264
265 for rows.Next() {
266 var labelOp models.LabelOp
267 var performedAt string
268
269 if err := rows.Scan(
270 &labelOp.Id,
271 &labelOp.Did,
272 &labelOp.Rkey,
273 &labelOp.Subject,
274 &labelOp.Operation,
275 &labelOp.OperandKey,
276 &labelOp.OperandValue,
277 &performedAt,
278 ); err != nil {
279 return nil, err
280 }
281
282 labelOp.PerformedAt, err = time.Parse(time.RFC3339, performedAt)
283 if err != nil {
284 labelOp.PerformedAt = time.Time{}
285 }
286
287 labelOps = append(labelOps, labelOp)
288 }
289
290 return labelOps, nil
291}
292
293// get labels for a given list of subject URIs
294func GetLabels(e Execer, filters ...orm.Filter) (map[syntax.ATURI]models.LabelState, error) {
295 ops, err := GetLabelOps(e, filters...)
296 if err != nil {
297 return nil, err
298 }
299
300 // group ops by subject
301 opsBySubject := make(map[syntax.ATURI][]models.LabelOp)
302 for _, op := range ops {
303 subject := syntax.ATURI(op.Subject)
304 opsBySubject[subject] = append(opsBySubject[subject], op)
305 }
306
307 // get all unique labelats for creating the context
308 labelAtSet := make(map[string]bool)
309 for _, op := range ops {
310 labelAtSet[op.OperandKey] = true
311 }
312 labelAts := slices.Collect(maps.Keys(labelAtSet))
313
314 actx, err := NewLabelApplicationCtx(e, orm.FilterIn("at_uri", labelAts))
315 if err != nil {
316 return nil, err
317 }
318
319 // apply label ops for each subject and collect results
320 results := make(map[syntax.ATURI]models.LabelState)
321 for subject, subjectOps := range opsBySubject {
322 state := models.NewLabelState()
323 actx.ApplyLabelOps(state, subjectOps)
324 results[subject] = state
325 }
326
327 return results, nil
328}
329
330func NewLabelApplicationCtx(e Execer, filters ...orm.Filter) (*models.LabelApplicationCtx, error) {
331 labels, err := GetLabelDefinitions(e, filters...)
332 if err != nil {
333 return nil, err
334 }
335
336 defs := make(map[string]*models.LabelDefinition)
337 for _, l := range labels {
338 defs[l.AtUri().String()] = &l
339 }
340
341 return &models.LabelApplicationCtx{Defs: defs}, nil
342}