···
···
"github.com/bluesky-social/indigo/atproto/syntax"
15
-
"tangled.sh/tangled.sh/core/api/tangled"
16
-
"tangled.sh/tangled.sh/core/consts"
19
-
type ConcreteType string
22
-
ConcreteTypeNull ConcreteType = "null"
23
-
ConcreteTypeString ConcreteType = "string"
24
-
ConcreteTypeInt ConcreteType = "integer"
25
-
ConcreteTypeBool ConcreteType = "boolean"
28
-
type ValueTypeFormat string
31
-
ValueTypeFormatAny ValueTypeFormat = "any"
32
-
ValueTypeFormatDid ValueTypeFormat = "did"
12
+
"tangled.org/core/appview/models"
35
-
// ValueType represents an atproto lexicon type definition with constraints
36
-
type ValueType struct {
37
-
Type ConcreteType `json:"type"`
38
-
Format ValueTypeFormat `json:"format,omitempty"`
39
-
Enum []string `json:"enum,omitempty"`
42
-
func (vt *ValueType) AsRecord() tangled.LabelDefinition_ValueType {
43
-
return tangled.LabelDefinition_ValueType{
44
-
Type: string(vt.Type),
45
-
Format: string(vt.Format),
50
-
func ValueTypeFromRecord(record tangled.LabelDefinition_ValueType) ValueType {
52
-
Type: ConcreteType(record.Type),
53
-
Format: ValueTypeFormat(record.Format),
58
-
func (vt ValueType) IsConcreteType() bool {
59
-
return vt.Type == ConcreteTypeNull ||
60
-
vt.Type == ConcreteTypeString ||
61
-
vt.Type == ConcreteTypeInt ||
62
-
vt.Type == ConcreteTypeBool
65
-
func (vt ValueType) IsNull() bool {
66
-
return vt.Type == ConcreteTypeNull
69
-
func (vt ValueType) IsString() bool {
70
-
return vt.Type == ConcreteTypeString
73
-
func (vt ValueType) IsInt() bool {
74
-
return vt.Type == ConcreteTypeInt
77
-
func (vt ValueType) IsBool() bool {
78
-
return vt.Type == ConcreteTypeBool
81
-
func (vt ValueType) IsEnum() bool {
82
-
return len(vt.Enum) > 0
85
-
func (vt ValueType) IsDidFormat() bool {
86
-
return vt.Format == ValueTypeFormatDid
89
-
func (vt ValueType) IsAnyFormat() bool {
90
-
return vt.Format == ValueTypeFormatAny
93
-
type LabelDefinition struct {
106
-
func (l *LabelDefinition) AtUri() syntax.ATURI {
107
-
return syntax.ATURI(fmt.Sprintf("at://%s/%s/%s", l.Did, tangled.LabelDefinitionNSID, l.Rkey))
110
-
func (l *LabelDefinition) AsRecord() tangled.LabelDefinition {
111
-
vt := l.ValueType.AsRecord()
112
-
return tangled.LabelDefinition{
115
-
CreatedAt: l.Created.Format(time.RFC3339),
116
-
Multiple: &l.Multiple,
122
-
// random color for a given seed
123
-
func randomColor(seed string) string {
124
-
hash := sha1.Sum([]byte(seed))
125
-
hexStr := hex.EncodeToString(hash[:])
130
-
return fmt.Sprintf("#%s%s%s", r, g, b)
133
-
func (ld LabelDefinition) GetColor() string {
134
-
if ld.Color == nil {
135
-
seed := fmt.Sprintf("%d:%s:%s", ld.Id, ld.Did, ld.Rkey)
136
-
color := randomColor(seed)
143
-
func LabelDefinitionFromRecord(did, rkey string, record tangled.LabelDefinition) (*LabelDefinition, error) {
144
-
created, err := time.Parse(time.RFC3339, record.CreatedAt)
146
-
created = time.Now()
150
-
if record.Multiple != nil {
151
-
multiple = *record.Multiple
155
-
if record.ValueType != nil {
156
-
vt = ValueTypeFromRecord(*record.ValueType)
159
-
return &LabelDefinition{
165
-
Scope: record.Scope,
166
-
Color: record.Color,
167
-
Multiple: multiple,
172
-
func DeleteLabelDefinition(e Execer, filters ...filter) error {
173
-
var conditions []string
175
-
for _, filter := range filters {
176
-
conditions = append(conditions, filter.Condition())
177
-
args = append(args, filter.Arg()...)
180
-
if conditions != nil {
181
-
whereClause = " where " + strings.Join(conditions, " and ")
183
-
query := fmt.Sprintf(`delete from label_definitions %s`, whereClause)
184
-
_, err := e.Exec(query, args...)
// no updating type for now
189
-
func AddLabelDefinition(e Execer, l *LabelDefinition) (int64, error) {
16
+
func AddLabelDefinition(e Execer, l *models.LabelDefinition) (int64, error) {
`insert into label_definitions (
···
235
-
func GetLabelDefinitions(e Execer, filters ...filter) ([]LabelDefinition, error) {
236
-
var labelDefinitions []LabelDefinition
62
+
func DeleteLabelDefinition(e Execer, filters ...filter) error {
63
+
var conditions []string
65
+
for _, filter := range filters {
66
+
conditions = append(conditions, filter.Condition())
67
+
args = append(args, filter.Arg()...)
70
+
if conditions != nil {
71
+
whereClause = " where " + strings.Join(conditions, " and ")
73
+
query := fmt.Sprintf(`delete from label_definitions %s`, whereClause)
74
+
_, err := e.Exec(query, args...)
78
+
func GetLabelDefinitions(e Execer, filters ...filter) ([]models.LabelDefinition, error) {
79
+
var labelDefinitions []models.LabelDefinition
···
278
-
var labelDefinition LabelDefinition
121
+
var labelDefinition models.LabelDefinition
var createdAt, enumVariants, scopes string
var color sql.Null[string]
···
// helper to get exactly one label def
327
-
func GetLabelDefinition(e Execer, filters ...filter) (*LabelDefinition, error) {
170
+
func GetLabelDefinition(e Execer, filters ...filter) (*models.LabelDefinition, error) {
labels, err := GetLabelDefinitions(e, filters...)
···
344
-
type LabelOp struct {
348
-
Subject syntax.ATURI
349
-
Operation LabelOperation
351
-
OperandValue string
352
-
PerformedAt time.Time
353
-
IndexedAt time.Time
356
-
func (l LabelOp) SortAt() time.Time {
357
-
createdAt := l.PerformedAt
358
-
indexedAt := l.IndexedAt
360
-
// if we don't have an indexedat, fall back to now
361
-
if indexedAt.IsZero() {
362
-
indexedAt = time.Now()
365
-
// if createdat is invalid (before epoch), treat as null -> return zero time
366
-
if createdAt.Before(time.UnixMicro(0)) {
370
-
// if createdat is <= indexedat, use createdat
371
-
if createdAt.Before(indexedAt) || createdAt.Equal(indexedAt) {
375
-
// otherwise, createdat is in the future relative to indexedat -> use indexedat
379
-
type LabelOperation string
382
-
LabelOperationAdd LabelOperation = "add"
383
-
LabelOperationDel LabelOperation = "del"
386
-
// a record can create multiple label ops
387
-
func LabelOpsFromRecord(did, rkey string, record tangled.LabelOp) []LabelOp {
388
-
performed, err := time.Parse(time.RFC3339, record.PerformedAt)
390
-
performed = time.Now()
393
-
mkOp := func(operand *tangled.LabelOp_Operand) LabelOp {
397
-
Subject: syntax.ATURI(record.Subject),
398
-
OperandKey: operand.Key,
399
-
OperandValue: operand.Value,
400
-
PerformedAt: performed,
405
-
for _, o := range record.Add {
408
-
op.Operation = LabelOperationAdd
409
-
ops = append(ops, op)
412
-
for _, o := range record.Delete {
415
-
op.Operation = LabelOperationDel
416
-
ops = append(ops, op)
423
-
func LabelOpsAsRecord(ops []LabelOp) tangled.LabelOp {
425
-
return tangled.LabelOp{}
428
-
// use the first operation to establish common fields
430
-
record := tangled.LabelOp{
431
-
Subject: string(first.Subject),
432
-
PerformedAt: first.PerformedAt.Format(time.RFC3339),
435
-
var addOperands []*tangled.LabelOp_Operand
436
-
var deleteOperands []*tangled.LabelOp_Operand
438
-
for _, op := range ops {
439
-
operand := &tangled.LabelOp_Operand{
440
-
Key: op.OperandKey,
441
-
Value: op.OperandValue,
444
-
switch op.Operation {
445
-
case LabelOperationAdd:
446
-
addOperands = append(addOperands, operand)
447
-
case LabelOperationDel:
448
-
deleteOperands = append(deleteOperands, operand)
450
-
return tangled.LabelOp{}
454
-
record.Add = addOperands
455
-
record.Delete = deleteOperands
460
-
func AddLabelOp(e Execer, l *LabelOp) (int64, error) {
187
+
func AddLabelOp(e Execer, l *models.LabelOp) (int64, error) {
···
503
-
func GetLabelOps(e Execer, filters ...filter) ([]LabelOp, error) {
504
-
var labelOps []LabelOp
230
+
func GetLabelOps(e Execer, filters ...filter) ([]models.LabelOp, error) {
231
+
var labelOps []models.LabelOp
···
544
-
var labelOp LabelOp
271
+
var labelOp models.LabelOp
var performedAt, indexedAt string
···
// get labels for a given list of subject URIs
578
-
func GetLabels(e Execer, filters ...filter) (map[syntax.ATURI]LabelState, error) {
305
+
func GetLabels(e Execer, filters ...filter) (map[syntax.ATURI]models.LabelState, error) {
ops, err := GetLabelOps(e, filters...)
585
-
opsBySubject := make(map[syntax.ATURI][]LabelOp)
312
+
opsBySubject := make(map[syntax.ATURI][]models.LabelOp)
subject := syntax.ATURI(op.Subject)
opsBySubject[subject] = append(opsBySubject[subject], op)
···
// apply label ops for each subject and collect results
604
-
results := make(map[syntax.ATURI]LabelState)
331
+
results := make(map[syntax.ATURI]models.LabelState)
for subject, subjectOps := range opsBySubject {
606
-
state := NewLabelState()
333
+
state := models.NewLabelState()
actx.ApplyLabelOps(state, subjectOps)
···
614
-
type set = map[string]struct{}
616
-
type LabelState struct {
617
-
inner map[string]set
620
-
func NewLabelState() LabelState {
622
-
inner: make(map[string]set),
626
-
func (s LabelState) Inner() map[string]set {
630
-
func (s LabelState) ContainsLabel(l string) bool {
631
-
if valset, exists := s.inner[l]; exists {
640
-
// go maps behavior in templates make this necessary,
641
-
// indexing a map and getting `set` in return is apparently truthy
642
-
func (s LabelState) ContainsLabelAndVal(l, v string) bool {
643
-
if valset, exists := s.inner[l]; exists {
644
-
if _, exists := valset[v]; exists {
652
-
func (s LabelState) GetValSet(l string) set {
653
-
if valset, exists := s.inner[l]; exists {
660
-
type LabelApplicationCtx struct {
661
-
Defs map[string]*LabelDefinition // labelAt -> labelDef
665
-
LabelNoOpError = errors.New("no-op")
668
-
func NewLabelApplicationCtx(e Execer, filters ...filter) (*LabelApplicationCtx, error) {
341
+
func NewLabelApplicationCtx(e Execer, filters ...filter) (*models.LabelApplicationCtx, error) {
labels, err := GetLabelDefinitions(e, filters...)
674
-
defs := make(map[string]*LabelDefinition)
347
+
defs := make(map[string]*models.LabelDefinition)
for _, l := range labels {
defs[l.AtUri().String()] = &l
679
-
return &LabelApplicationCtx{defs}, nil
682
-
func (c *LabelApplicationCtx) ApplyLabelOp(state LabelState, op LabelOp) error {
683
-
def, ok := c.Defs[op.OperandKey]
685
-
// this def was deleted, but an op exists, so we just skip over the op
689
-
switch op.Operation {
690
-
case LabelOperationAdd:
691
-
// if valueset is empty, init it
692
-
if state.inner[op.OperandKey] == nil {
693
-
state.inner[op.OperandKey] = make(set)
696
-
// if valueset is populated & this val alr exists, this labelop is a noop
697
-
if valueSet, exists := state.inner[op.OperandKey]; exists {
698
-
if _, exists = valueSet[op.OperandValue]; exists {
699
-
return LabelNoOpError
705
-
state.inner[op.OperandKey][op.OperandValue] = struct{}{}
707
-
// reset to just this value
708
-
state.inner[op.OperandKey] = set{op.OperandValue: struct{}{}}
711
-
case LabelOperationDel:
712
-
// if label DNE, then deletion is a no-op
713
-
if valueSet, exists := state.inner[op.OperandKey]; !exists {
714
-
return LabelNoOpError
715
-
} else if _, exists = valueSet[op.OperandValue]; !exists { // if value DNE, then deletion is no-op
716
-
return LabelNoOpError
721
-
delete(state.inner[op.OperandKey], op.OperandValue)
723
-
// reset the entire label
724
-
delete(state.inner, op.OperandKey)
727
-
// if the map becomes empty, then set it to nil, this is just the inverse of add
728
-
if len(state.inner[op.OperandKey]) == 0 {
729
-
state.inner[op.OperandKey] = nil
737
-
func (c *LabelApplicationCtx) ApplyLabelOps(state LabelState, ops []LabelOp) {
738
-
// sort label ops in sort order first
739
-
slices.SortFunc(ops, func(a, b LabelOp) int {
740
-
return a.SortAt().Compare(b.SortAt())
743
-
// apply ops in sequence
744
-
for _, o := range ops {
745
-
_ = c.ApplyLabelOp(state, o)
749
-
// IsInverse checks if one label operation is the inverse of another
750
-
// returns true if one is an add and the other is a delete with the same key and value
751
-
func (op1 LabelOp) IsInverse(op2 LabelOp) bool {
752
-
if op1.OperandKey != op2.OperandKey || op1.OperandValue != op2.OperandValue {
756
-
return (op1.Operation == LabelOperationAdd && op2.Operation == LabelOperationDel) ||
757
-
(op1.Operation == LabelOperationDel && op2.Operation == LabelOperationAdd)
760
-
// removes pairs of label operations that are inverses of each other
761
-
// from the given slice. the function preserves the order of remaining operations.
762
-
func ReduceLabelOps(ops []LabelOp) []LabelOp {
767
-
keep := make([]bool, len(ops))
768
-
for i := range keep {
772
-
for i := range ops {
777
-
for j := i + 1; j < len(ops); j++ {
782
-
if ops[i].IsInverse(ops[j]) {
785
-
break // move to next i since this one is now eliminated
790
-
// build result slice with only kept operations
791
-
var result []LabelOp
792
-
for i, op := range ops {
794
-
result = append(result, op)
801
-
func DefaultLabelDefs() []string {
806
-
"good-first-issue",
810
-
defs := make([]string, len(rkeys))
811
-
for i, r := range rkeys {
812
-
defs[i] = fmt.Sprintf("at://%s/%s/%s", consts.TangledDid, tangled.LabelDefinitionNSID, r)
352
+
return &models.LabelApplicationCtx{defs}, nil