Monorepo for Tangled forked from tangled.org/core
Something went wrong. Try again.
Go
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525package models
import ( "context" "crypto/sha1" "encoding/hex" "encoding/json" "errors" "fmt" "slices" "time"
"github.com/bluesky-social/indigo/api/atproto" "github.com/bluesky-social/indigo/atproto/syntax" "github.com/bluesky-social/indigo/xrpc" "tangled.org/core/api/tangled" "tangled.org/core/idresolver")
type ConcreteType string
const ( ConcreteTypeNull ConcreteType = "null" ConcreteTypeString ConcreteType = "string" ConcreteTypeInt ConcreteType = "integer" ConcreteTypeBool ConcreteType = "boolean")
type ValueTypeFormat string
const ( ValueTypeFormatAny ValueTypeFormat = "any" ValueTypeFormatDid ValueTypeFormat = "did")
// ValueType represents an atproto lexicon type definition with constraintstype ValueType struct { Type ConcreteType `json:"type"` Format ValueTypeFormat `json:"format,omitempty"` Enum []string `json:"enum,omitempty"`}
func (vt *ValueType) AsRecord() tangled.LabelDefinition_ValueType { return tangled.LabelDefinition_ValueType{ Type: string(vt.Type), Format: string(vt.Format), Enum: vt.Enum, }}
func ValueTypeFromRecord(record tangled.LabelDefinition_ValueType) ValueType { return ValueType{ Type: ConcreteType(record.Type), Format: ValueTypeFormat(record.Format), Enum: record.Enum, }}
func (vt ValueType) IsConcreteType() bool { return vt.Type == ConcreteTypeNull || vt.Type == ConcreteTypeString || vt.Type == ConcreteTypeInt || vt.Type == ConcreteTypeBool}
func (vt ValueType) IsNull() bool { return vt.Type == ConcreteTypeNull}
func (vt ValueType) IsString() bool { return vt.Type == ConcreteTypeString}
func (vt ValueType) IsInt() bool { return vt.Type == ConcreteTypeInt}
func (vt ValueType) IsBool() bool { return vt.Type == ConcreteTypeBool}
func (vt ValueType) IsEnum() bool { return len(vt.Enum) > 0}
func (vt ValueType) IsDidFormat() bool { return vt.Format == ValueTypeFormatDid}
func (vt ValueType) IsAnyFormat() bool { return vt.Format == ValueTypeFormatAny}
type LabelDefinition struct { Id int64 Did string Rkey string
Name string ValueType ValueType Scope []string Color *string Multiple bool Created time.Time}
func (l *LabelDefinition) AtUri() syntax.ATURI { return syntax.ATURI(fmt.Sprintf("at://%s/%s/%s", l.Did, tangled.LabelDefinitionNSID, l.Rkey))}
func (l *LabelDefinition) AsRecord() tangled.LabelDefinition { vt := l.ValueType.AsRecord() return tangled.LabelDefinition{ Name: l.Name, Color: l.Color, CreatedAt: l.Created.Format(time.RFC3339), Multiple: &l.Multiple, Scope: l.Scope, ValueType: &vt, }}
// random color for a given seedfunc randomColor(seed string) string { hash := sha1.Sum([]byte(seed)) hexStr := hex.EncodeToString(hash[:]) r := hexStr[0:2] g := hexStr[2:4] b := hexStr[4:6]
return fmt.Sprintf("#%s%s%s", r, g, b)}
func (ld LabelDefinition) GetColor() string { if ld.Color == nil { seed := fmt.Sprintf("%d:%s:%s", ld.Id, ld.Did, ld.Rkey) color := randomColor(seed) return color }
return *ld.Color}
func LabelDefinitionFromRecord(did, rkey string, record tangled.LabelDefinition) (*LabelDefinition, error) { created, err := time.Parse(time.RFC3339, record.CreatedAt) if err != nil { created = time.Now() }
multiple := false if record.Multiple != nil { multiple = *record.Multiple }
var vt ValueType if record.ValueType != nil { vt = ValueTypeFromRecord(*record.ValueType) }
return &LabelDefinition{ Did: did, Rkey: rkey,
Name: record.Name, ValueType: vt, Scope: record.Scope, Color: record.Color, Multiple: multiple, Created: created, }, nil}
type LabelOp struct { Id int64 Did string Rkey string Subject syntax.ATURI Operation LabelOperation OperandKey string OperandValue string PerformedAt time.Time IndexedAt time.Time}
func (l LabelOp) SortAt() time.Time { createdAt := l.PerformedAt indexedAt := l.IndexedAt
// if we don't have an indexedat, fall back to now if indexedAt.IsZero() { indexedAt = time.Now() }
// if createdat is invalid (before epoch), treat as null -> return zero time if createdAt.Before(time.UnixMicro(0)) { return time.Time{} }
// if createdat is <= indexedat, use createdat if createdAt.Before(indexedAt) || createdAt.Equal(indexedAt) { return createdAt }
// otherwise, createdat is in the future relative to indexedat -> use indexedat return indexedAt}
type LabelOperation string
const ( LabelOperationAdd LabelOperation = "add" LabelOperationDel LabelOperation = "del")
// a record can create multiple label opsfunc LabelOpsFromRecord(did, rkey string, record tangled.LabelOp) []LabelOp { performed, err := time.Parse(time.RFC3339, record.PerformedAt) if err != nil { performed = time.Now() }
mkOp := func(operand *tangled.LabelOp_Operand) LabelOp { return LabelOp{ Did: did, Rkey: rkey, Subject: syntax.ATURI(record.Subject), OperandKey: operand.Key, OperandValue: operand.Value, PerformedAt: performed, } }
var ops []LabelOp // deletes first, then additions for _, o := range record.Delete { if o != nil { op := mkOp(o) op.Operation = LabelOperationDel ops = append(ops, op) } } for _, o := range record.Add { if o != nil { op := mkOp(o) op.Operation = LabelOperationAdd ops = append(ops, op) } }
return ops}
func LabelOpsAsRecord(ops []LabelOp) tangled.LabelOp { if len(ops) == 0 { return tangled.LabelOp{} }
// use the first operation to establish common fields first := ops[0] record := tangled.LabelOp{ Subject: string(first.Subject), PerformedAt: first.PerformedAt.Format(time.RFC3339), }
var addOperands []*tangled.LabelOp_Operand var deleteOperands []*tangled.LabelOp_Operand
for _, op := range ops { operand := &tangled.LabelOp_Operand{ Key: op.OperandKey, Value: op.OperandValue, }
switch op.Operation { case LabelOperationAdd: addOperands = append(addOperands, operand) case LabelOperationDel: deleteOperands = append(deleteOperands, operand) default: return tangled.LabelOp{} } }
record.Add = addOperands record.Delete = deleteOperands
return record}
type set = map[string]struct{}
type LabelState struct { inner map[string]set}
func NewLabelState() LabelState { return LabelState{ inner: make(map[string]set), }}
func (s LabelState) Inner() map[string]set { return s.inner}
func (s LabelState) ContainsLabel(l string) bool { if valset, exists := s.inner[l]; exists { if valset != nil { return true } }
return false}
// go maps behavior in templates make this necessary,// indexing a map and getting `set` in return is apparently truthyfunc (s LabelState) ContainsLabelAndVal(l, v string) bool { if valset, exists := s.inner[l]; exists { if _, exists := valset[v]; exists { return true } }
return false}
func (s LabelState) GetValSet(l string) set { if valset, exists := s.inner[l]; exists { return valset } else { return make(set) }}
type LabelApplicationCtx struct { Defs map[string]*LabelDefinition // labelAt -> labelDef}
var ( LabelNoOpError = errors.New("no-op"))
func (c *LabelApplicationCtx) ApplyLabelOp(state LabelState, op LabelOp) error { def, ok := c.Defs[op.OperandKey] if !ok { // this def was deleted, but an op exists, so we just skip over the op return nil }
switch op.Operation { case LabelOperationAdd: // if valueset is empty, init it if state.inner[op.OperandKey] == nil { state.inner[op.OperandKey] = make(set) }
// if valueset is populated & this val alr exists, this labelop is a noop if valueSet, exists := state.inner[op.OperandKey]; exists { if _, exists = valueSet[op.OperandValue]; exists { return LabelNoOpError } }
if def.Multiple { // append to set state.inner[op.OperandKey][op.OperandValue] = struct{}{} } else { // reset to just this value state.inner[op.OperandKey] = set{op.OperandValue: struct{}{}} }
case LabelOperationDel: // if label DNE, then deletion is a no-op if valueSet, exists := state.inner[op.OperandKey]; !exists { return LabelNoOpError } else if _, exists = valueSet[op.OperandValue]; !exists { // if value DNE, then deletion is no-op return LabelNoOpError }
if def.Multiple { // remove from set delete(state.inner[op.OperandKey], op.OperandValue) } else { // reset the entire label delete(state.inner, op.OperandKey) }
// if the map becomes empty, then set it to nil, this is just the inverse of add if len(state.inner[op.OperandKey]) == 0 { state.inner[op.OperandKey] = nil }
}
return nil}
func (c *LabelApplicationCtx) ApplyLabelOps(state LabelState, ops []LabelOp) { // sort label ops in sort order first slices.SortFunc(ops, func(a, b LabelOp) int { return a.SortAt().Compare(b.SortAt()) })
// apply ops in sequence for _, o := range ops { _ = c.ApplyLabelOp(state, o) }}
// IsInverse checks if one label operation is the inverse of another// returns true if one is an add and the other is a delete with the same key and valuefunc (op1 LabelOp) IsInverse(op2 LabelOp) bool { if op1.OperandKey != op2.OperandKey || op1.OperandValue != op2.OperandValue { return false }
return (op1.Operation == LabelOperationAdd && op2.Operation == LabelOperationDel) || (op1.Operation == LabelOperationDel && op2.Operation == LabelOperationAdd)}
// removes pairs of label operations that are inverses of each other// from the given slice. the function preserves the order of remaining operations.func ReduceLabelOps(ops []LabelOp) []LabelOp { if len(ops) <= 1 { return ops }
keep := make([]bool, len(ops)) for i := range keep { keep[i] = true }
for i := range ops { if !keep[i] { continue }
for j := i + 1; j < len(ops); j++ { if !keep[j] { continue }
if ops[i].IsInverse(ops[j]) { keep[i] = false keep[j] = false break // move to next i since this one is now eliminated } } }
// build result slice with only kept operations var result []LabelOp for i, op := range ops { if keep[i] { result = append(result, op) } }
return result}
func FetchLabelDefs(r *idresolver.Resolver, aturis []string) ([]LabelDefinition, error) { var labelDefs []LabelDefinition ctx := context.Background()
for _, dl := range aturis { atUri, err := syntax.ParseATURI(dl) if err != nil { return nil, fmt.Errorf("failed to parse AT-URI %s: %v", dl, err) } if atUri.Collection() != tangled.LabelDefinitionNSID { return nil, fmt.Errorf("expected AT-URI pointing %s collection: %s", tangled.LabelDefinitionNSID, atUri) }
owner, err := r.ResolveIdent(ctx, atUri.Authority().String()) if err != nil { return nil, fmt.Errorf("failed to resolve default label owner DID %s: %v", atUri.Authority(), err) }
xrpcc := xrpc.Client{ Host: owner.PDSEndpoint(), }
record, err := atproto.RepoGetRecord( ctx, &xrpcc, "", atUri.Collection().String(), atUri.Authority().String(), atUri.RecordKey().String(), ) if err != nil { return nil, fmt.Errorf("failed to get record for %s: %v", atUri, err) }
if record != nil { bytes, err := record.Value.MarshalJSON() if err != nil { return nil, fmt.Errorf("failed to marshal record value for %s: %v", atUri, err) }
raw := json.RawMessage(bytes) labelRecord := tangled.LabelDefinition{} err = json.Unmarshal(raw, &labelRecord) if err != nil { return nil, fmt.Errorf("invalid record for %s: %w", atUri, err) }
labelDef, err := LabelDefinitionFromRecord( atUri.Authority().String(), atUri.RecordKey().String(), labelRecord, ) if err != nil { return nil, fmt.Errorf("failed to create label definition from record %s: %v", atUri, err) }
labelDefs = append(labelDefs, *labelDef) } }
return labelDefs, nil}