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