forked from tangled.org/core
Monorepo for Tangled — https://tangled.org
at master 7.2 kB view raw
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}