A vibe coded tangled fork which supports pijul.
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
16func UpsertLabelDefinition(e Execer, l *models.LabelDefinition) (int64, error) {
17 panic("unimplemented")
18}
19
20// no updating type for now
21func AddLabelDefinition(e Execer, l *models.LabelDefinition) (int64, error) {
22 result, err := e.Exec(
23 `insert into label_definitions (
24 did,
25 rkey,
26 name,
27 value_type,
28 value_format,
29 value_enum,
30 scope,
31 color,
32 multiple,
33 created
34 )
35 values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
36 on conflict(did, rkey) do update set
37 name = excluded.name,
38 scope = excluded.scope,
39 color = excluded.color,
40 multiple = excluded.multiple`,
41 l.Did,
42 l.Rkey,
43 l.Name,
44 l.ValueType.Type,
45 l.ValueType.Format,
46 strings.Join(l.ValueType.Enum, ","),
47 strings.Join(l.Scope, ","),
48 l.Color,
49 l.Multiple,
50 l.Created.Format(time.RFC3339),
51 time.Now().Format(time.RFC3339),
52 )
53 if err != nil {
54 return 0, err
55 }
56
57 id, err := result.LastInsertId()
58 if err != nil {
59 return 0, err
60 }
61
62 l.Id = id
63
64 return id, nil
65}
66
67func DeleteLabelDefinition(e Execer, filters ...orm.Filter) error {
68 var conditions []string
69 var args []any
70 for _, filter := range filters {
71 conditions = append(conditions, filter.Condition())
72 args = append(args, filter.Arg()...)
73 }
74 whereClause := ""
75 if conditions != nil {
76 whereClause = " where " + strings.Join(conditions, " and ")
77 }
78 query := fmt.Sprintf(`delete from label_definitions %s`, whereClause)
79 _, err := e.Exec(query, args...)
80 return err
81}
82
83func GetLabelDefinitions(e Execer, filters ...orm.Filter) ([]models.LabelDefinition, error) {
84 var labelDefinitions []models.LabelDefinition
85 var conditions []string
86 var args []any
87
88 for _, filter := range filters {
89 conditions = append(conditions, filter.Condition())
90 args = append(args, filter.Arg()...)
91 }
92
93 whereClause := ""
94 if conditions != nil {
95 whereClause = " where " + strings.Join(conditions, " and ")
96 }
97
98 query := fmt.Sprintf(
99 `
100 select
101 id,
102 did,
103 rkey,
104 name,
105 value_type,
106 value_format,
107 value_enum,
108 scope,
109 color,
110 multiple,
111 created
112 from label_definitions
113 %s
114 order by created
115 `,
116 whereClause,
117 )
118
119 rows, err := e.Query(query, args...)
120 if err != nil {
121 return nil, err
122 }
123 defer rows.Close()
124
125 for rows.Next() {
126 var labelDefinition models.LabelDefinition
127 var createdAt, enumVariants, scopes string
128 var color sql.Null[string]
129 var multiple int
130
131 if err := rows.Scan(
132 &labelDefinition.Id,
133 &labelDefinition.Did,
134 &labelDefinition.Rkey,
135 &labelDefinition.Name,
136 &labelDefinition.ValueType.Type,
137 &labelDefinition.ValueType.Format,
138 &enumVariants,
139 &scopes,
140 &color,
141 &multiple,
142 &createdAt,
143 ); err != nil {
144 return nil, err
145 }
146
147 labelDefinition.Created, err = time.Parse(time.RFC3339, createdAt)
148 if err != nil {
149 labelDefinition.Created = time.Now()
150 }
151
152 if color.Valid {
153 labelDefinition.Color = &color.V
154 }
155
156 if multiple != 0 {
157 labelDefinition.Multiple = true
158 }
159
160 if enumVariants != "" {
161 labelDefinition.ValueType.Enum = strings.Split(enumVariants, ",")
162 }
163
164 for s := range strings.SplitSeq(scopes, ",") {
165 labelDefinition.Scope = append(labelDefinition.Scope, s)
166 }
167
168 labelDefinitions = append(labelDefinitions, labelDefinition)
169 }
170
171 return labelDefinitions, nil
172}
173
174// helper to get exactly one label def
175func GetLabelDefinition(e Execer, filters ...orm.Filter) (*models.LabelDefinition, error) {
176 labels, err := GetLabelDefinitions(e, filters...)
177 if err != nil {
178 return nil, err
179 }
180
181 if labels == nil {
182 return nil, sql.ErrNoRows
183 }
184
185 if len(labels) != 1 {
186 return nil, fmt.Errorf("too many rows returned")
187 }
188
189 return &labels[0], nil
190}
191
192func AddLabelOp(e Execer, l *models.LabelOp) (int64, error) {
193 now := time.Now()
194 result, err := e.Exec(
195 `insert into label_ops (
196 did,
197 rkey,
198 subject,
199 operation,
200 operand_key,
201 operand_value,
202 performed,
203 indexed
204 )
205 values (?, ?, ?, ?, ?, ?, ?, ?)
206 on conflict(did, rkey, subject, operand_key, operand_value) do update set
207 operation = excluded.operation,
208 operand_value = excluded.operand_value,
209 performed = excluded.performed,
210 indexed = excluded.indexed`,
211 l.Did,
212 l.Rkey,
213 l.Subject.String(),
214 string(l.Operation),
215 l.OperandKey,
216 l.OperandValue,
217 l.PerformedAt.Format(time.RFC3339),
218 now.Format(time.RFC3339),
219 )
220 if err != nil {
221 return 0, err
222 }
223
224 id, err := result.LastInsertId()
225 if err != nil {
226 return 0, err
227 }
228
229 l.Id = id
230 l.IndexedAt = now
231
232 return id, nil
233}
234
235func GetLabelOps(e Execer, filters ...orm.Filter) ([]models.LabelOp, error) {
236 var labelOps []models.LabelOp
237 var conditions []string
238 var args []any
239
240 for _, filter := range filters {
241 conditions = append(conditions, filter.Condition())
242 args = append(args, filter.Arg()...)
243 }
244
245 whereClause := ""
246 if conditions != nil {
247 whereClause = " where " + strings.Join(conditions, " and ")
248 }
249
250 query := fmt.Sprintf(
251 `
252 select
253 id,
254 did,
255 rkey,
256 subject,
257 operation,
258 operand_key,
259 operand_value,
260 performed,
261 indexed
262 from label_ops
263 %s
264 order by indexed
265 `,
266 whereClause,
267 )
268
269 rows, err := e.Query(query, args...)
270 if err != nil {
271 return nil, err
272 }
273 defer rows.Close()
274
275 for rows.Next() {
276 var labelOp models.LabelOp
277 var performedAt, indexedAt string
278
279 if err := rows.Scan(
280 &labelOp.Id,
281 &labelOp.Did,
282 &labelOp.Rkey,
283 &labelOp.Subject,
284 &labelOp.Operation,
285 &labelOp.OperandKey,
286 &labelOp.OperandValue,
287 &performedAt,
288 &indexedAt,
289 ); err != nil {
290 return nil, err
291 }
292
293 labelOp.PerformedAt, err = time.Parse(time.RFC3339, performedAt)
294 if err != nil {
295 labelOp.PerformedAt = time.Now()
296 }
297
298 labelOp.IndexedAt, err = time.Parse(time.RFC3339, indexedAt)
299 if err != nil {
300 labelOp.IndexedAt = time.Now()
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}