forked from
tangled.org/core
fork
Configure Feed
Select the types of activity you want to include in your feed.
Monorepo for Tangled
fork
Configure Feed
Select the types of activity you want to include in your feed.
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}