···
+
"github.com/bluesky-social/indigo/atproto/syntax"
+
"tangled.sh/tangled.sh/core/api/tangled"
+
type ConcreteType string
+
ConcreteTypeNull ConcreteType = "null"
+
ConcreteTypeString ConcreteType = "string"
+
ConcreteTypeInt ConcreteType = "integer"
+
ConcreteTypeBool ConcreteType = "boolean"
+
type ValueTypeFormat string
+
ValueTypeFormatAny ValueTypeFormat = "any"
+
ValueTypeFormatDid ValueTypeFormat = "did"
+
// ValueType represents an atproto lexicon type definition with constraints
+
type 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{
+
Format: string(vt.Format),
+
func ValueTypeFromRecord(record tangled.LabelDefinition_ValueType) ValueType {
+
Type: ConcreteType(record.Type),
+
Format: ValueTypeFormat(record.Format),
+
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) IsEnumType() bool {
+
return len(vt.Enum) > 0
+
type LabelDefinition struct {
+
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{
+
CreatedAt: l.Created.Format(time.RFC3339),
+
Scope: l.Scope.String(),
+
// random color for a given seed
+
func randomColor(seed string) string {
+
hash := sha1.Sum([]byte(seed))
+
hexStr := hex.EncodeToString(hash[:])
+
return fmt.Sprintf("#%s%s%s", r, g, b)
+
func (ld LabelDefinition) GetColor() string {
+
seed := fmt.Sprintf("%d:%s:%s", ld.Id, ld.Did, ld.Rkey)
+
color := randomColor(seed)
+
func LabelDefinitionFromRecord(did, rkey string, record tangled.LabelDefinition) LabelDefinition {
+
created, err := time.Parse(time.RFC3339, record.CreatedAt)
+
if record.Multiple != nil {
+
multiple = *record.Multiple
+
if record.ValueType != nil {
+
vt = ValueTypeFromRecord(*record.ValueType)
+
return LabelDefinition{
+
Scope: syntax.NSID(record.Scope),
+
func DeleteLabelDefinition(e Execer, filters ...filter) error {
+
var conditions []string
+
for _, filter := range filters {
+
conditions = append(conditions, filter.Condition())
+
args = append(args, filter.Arg()...)
+
whereClause = " where " + strings.Join(conditions, " and ")
+
query := fmt.Sprintf(`delete from label_definitions %s`, whereClause)
+
_, err := e.Exec(query, args...)
+
func AddLabelDefinition(e Execer, l *LabelDefinition) (int64, error) {
+
`insert into label_definitions (
+
values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+
on conflict(did, rkey) do update set
+
scope = excluded.scope,
+
color = excluded.color,
+
multiple = excluded.multiple`,
+
strings.Join(l.ValueType.Enum, ","),
+
l.Created.Format(time.RFC3339),
+
time.Now().Format(time.RFC3339),
+
id, err := result.LastInsertId()
+
func GetLabelDefinitions(e Execer, filters ...filter) ([]LabelDefinition, error) {
+
var labelDefinitions []LabelDefinition
+
var conditions []string
+
for _, filter := range filters {
+
conditions = append(conditions, filter.Condition())
+
args = append(args, filter.Arg()...)
+
whereClause = " where " + strings.Join(conditions, " and ")
+
rows, err := e.Query(query, args...)
+
var labelDefinition LabelDefinition
+
var createdAt, enumVariants string
+
var color sql.Null[string]
+
&labelDefinition.ValueType.Type,
+
&labelDefinition.ValueType.Format,
+
&labelDefinition.Scope,
+
labelDefinition.Created, err = time.Parse(time.RFC3339, createdAt)
+
labelDefinition.Created = time.Now()
+
labelDefinition.Color = &color.V
+
labelDefinition.Multiple = true
+
if enumVariants != "" {
+
labelDefinition.ValueType.Enum = strings.Split(enumVariants, ",")
+
labelDefinitions = append(labelDefinitions, labelDefinition)
+
return labelDefinitions, nil
+
// helper to get exactly one label def
+
func GetLabelDefinition(e Execer, filters ...filter) (*LabelDefinition, error) {
+
labels, err := GetLabelDefinitions(e, filters...)
+
return nil, sql.ErrNoRows
+
return nil, fmt.Errorf("too many rows returned")
+
Operation LabelOperation
+
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() {
+
// if createdat is invalid (before epoch), treat as null -> return zero time
+
if createdAt.Before(time.UnixMicro(0)) {
+
// if createdat is <= indexedat, use createdat
+
if createdAt.Before(indexedAt) || createdAt.Equal(indexedAt) {
+
// otherwise, createdat is in the future relative to indexedat -> use indexedat
+
type LabelOperation string
+
LabelOperationAdd LabelOperation = "add"
+
LabelOperationDel LabelOperation = "del"
+
// a record can create multiple label ops
+
func LabelOpsFromRecord(did, rkey string, record tangled.LabelOp) []LabelOp {
+
performed, err := time.Parse(time.RFC3339, record.PerformedAt)
+
mkOp := func(operand *tangled.LabelOp_Operand) LabelOp {
+
Subject: syntax.ATURI(record.Subject),
+
OperandKey: operand.Key,
+
OperandValue: operand.Value,
+
PerformedAt: performed,
+
for _, o := range record.Add {
+
op.Operation = LabelOperationAdd
+
for _, o := range record.Delete {
+
op.Operation = LabelOperationDel
+
func LabelOpsAsRecord(ops []LabelOp) tangled.LabelOp {
+
return tangled.LabelOp{}
+
// use the first operation to establish common fields
+
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{
+
Value: op.OperandValue,
+
case LabelOperationAdd:
+
addOperands = append(addOperands, operand)
+
case LabelOperationDel:
+
deleteOperands = append(deleteOperands, operand)
+
return tangled.LabelOp{}
+
record.Add = addOperands
+
record.Delete = deleteOperands
+
func AddLabelOp(e Execer, l *LabelOp) (int64, error) {
+
`insert into label_ops (
+
values (?, ?, ?, ?, ?, ?, ?, ?)
+
on conflict(did, rkey, subject, operand_key, operand_value) do update set
+
operation = excluded.operation,
+
operand_value = excluded.operand_value,
+
performed = excluded.performed,
+
indexed = excluded.indexed`,
+
l.PerformedAt.Format(time.RFC3339),
+
now.Format(time.RFC3339),
+
id, err := result.LastInsertId()
+
func GetLabelOps(e Execer, filters ...filter) ([]LabelOp, error) {
+
var conditions []string
+
for _, filter := range filters {
+
conditions = append(conditions, filter.Condition())
+
args = append(args, filter.Arg()...)
+
whereClause = " where " + strings.Join(conditions, " and ")
+
rows, err := e.Query(query, args...)
+
var performedAt, indexedAt string
+
labelOp.PerformedAt, err = time.Parse(time.RFC3339, performedAt)
+
labelOp.PerformedAt = time.Now()
+
labelOp.IndexedAt, err = time.Parse(time.RFC3339, indexedAt)
+
labelOp.IndexedAt = time.Now()
+
labelOps = append(labelOps, labelOp)
+
// get labels for a given list of subject URIs
+
func GetLabels(e Execer, filters ...filter) (map[syntax.ATURI]LabelState, error) {
+
ops, err := GetLabelOps(e, filters...)
+
// group ops by subject
+
opsBySubject := make(map[syntax.ATURI][]LabelOp)
+
for _, op := range ops {
+
subject := syntax.ATURI(op.Subject)
+
opsBySubject[subject] = append(opsBySubject[subject], op)
+
// get all unique labelats for creating the context
+
labelAtSet := make(map[string]bool)
+
for _, op := range ops {
+
labelAtSet[op.OperandKey] = true
+
labelAts := slices.Collect(maps.Keys(labelAtSet))
+
actx, err := NewLabelApplicationCtx(e, FilterIn("at_uri", labelAts))
+
// apply label ops for each subject and collect results
+
results := make(map[syntax.ATURI]LabelState)
+
for subject, subjectOps := range opsBySubject {
+
state := NewLabelState()
+
actx.ApplyLabelOps(state, subjectOps)
+
results[subject] = state
+
log.Println("results for get labels", "s", results)
+
type set = map[string]struct{}
+
type LabelState struct {
+
func NewLabelState() LabelState {
+
inner: make(map[string]set),
+
func (s LabelState) Inner() map[string]set {
+
func (s LabelState) ContainsLabel(l string) bool {
+
if valset, exists := s.inner[l]; exists {
+
func (s *LabelState) GetValSet(l string) set {
+
type LabelApplicationCtx struct {
+
defs map[string]*LabelDefinition // labelAt -> labelDef
+
LabelNoOpError = errors.New("no-op")
+
func NewLabelApplicationCtx(e Execer, filters ...filter) (*LabelApplicationCtx, error) {
+
labels, err := GetLabelDefinitions(e, filters...)
+
defs := make(map[string]*LabelDefinition)
+
for _, l := range labels {
+
defs[l.AtUri().String()] = &l
+
return &LabelApplicationCtx{defs}, nil
+
func (c *LabelApplicationCtx) ApplyLabelOp(state LabelState, op LabelOp) error {
+
def := c.defs[op.OperandKey]
+
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 {
+
state.inner[op.OperandKey][op.OperandValue] = struct{}{}
+
// 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 {
+
} else if _, exists = valueSet[op.OperandValue]; !exists { // if value DNE, then deletion is no-op
+
delete(state.inner[op.OperandKey], op.OperandValue)
+
// 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
+
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)