···11+/*
22+Experimental extensions to github.com/bluesky-social/indigo/atproto/labeling
33+44+Has not been reviewed and not super confident in some of the naming and API shapes.
55+*/
66+package labeling
···11+package labeling
22+33+import (
44+ "context"
55+66+ "github.com/bluesky-social/indigo/atproto/labeling"
77+ "github.com/bluesky-social/indigo/atproto/syntax"
88+)
99+1010+// Subset of a full label, including only fields relevant to a subject at this moment (eg, not negated, not expired).
1111+//
1212+// Includes CID to disambiguate if a specific record is labeled, and includes 'cts' because it is required by `com.atproto.label.defs#defs`.
1313+type ShortLabel struct {
1414+ SourceDID string `json:"src" cborgen:"src"`
1515+ URI string `json:"uri" cborgen:"uri"`
1616+ CID *string `json:"cid,omitempty" cborgen:"cid,omitempty"`
1717+ Val string `json:"val" cborgen:"val"`
1818+ CreatedAt string `json:"cts" cborgen:"cts"`
1919+}
2020+2121+type LabelStore interface {
2222+ // TODO: should this automatically parse URIs and extract DIDs?
2323+ // fetches complete signed label objects from database. Must not return expired, negated, or future labels ('cts' in the future beyond a fuzzy window).
2424+ GetLabels(ctx context.Context, subjects []string, labelers []syntax.DID) ([]labeling.Label, error)
2525+ // Returns the same labels as [GetLabels], but only a subset of fields.
2626+ GetShortLabels(ctx context.Context, subjects []string, labelers []syntax.DID) ([]ShortLabel, error)
2727+}
+105
labeling/memstore.go
···11+package labeling
22+33+import (
44+ "context"
55+ "fmt"
66+ "time"
77+88+ "github.com/bluesky-social/indigo/atproto/labeling"
99+ "github.com/bluesky-social/indigo/atproto/syntax"
1010+)
1111+1212+// Ephemeral in-memory implementation of [LabelStore].
1313+//
1414+// Stores only a single label per source per URI. Eg, does not store separate labels for different record versions (CIDs).
1515+type MemStore struct {
1616+ Labels map[string]map[syntax.DID]labeling.Label
1717+}
1818+1919+func NewMemStore() *MemStore {
2020+ ms := MemStore{
2121+ Labels: map[string]map[syntax.DID]labeling.Label{},
2222+ }
2323+ return &ms
2424+}
2525+2626+func (s *MemStore) GetLabels(ctx context.Context, uris []string, srcs []syntax.DID) ([]labeling.Label, error) {
2727+ if len(uris) == 0 || len(srcs) == 0 {
2828+ return []labeling.Label{}, nil
2929+ }
3030+3131+ now := time.Now()
3232+3333+ out := []labeling.Label{}
3434+ for _, uri := range uris {
3535+ m, ok := s.Labels[uri]
3636+ if !ok {
3737+ continue
3838+ }
3939+ for _, src := range srcs {
4040+ l, ok := m[syntax.DID(src)]
4141+ if !ok {
4242+ continue
4343+ }
4444+ if l.Negated != nil && *l.Negated == true {
4545+ continue
4646+ }
4747+ cts, err := syntax.ParseDatetime(l.CreatedAt)
4848+ if err != nil {
4949+ // ignore bad timestamp
5050+ continue
5151+ }
5252+ // one minute of fuzzy time
5353+ if cts.Time().After(now.Add(time.Minute)) {
5454+ // ignore future labels
5555+ continue
5656+ }
5757+ if l.ExpiresAt != nil {
5858+ exp, err := syntax.ParseDatetime(*l.ExpiresAt)
5959+ if err != nil {
6060+ continue
6161+ }
6262+ if now.After(exp.Time()) {
6363+ continue
6464+ }
6565+ }
6666+ out = append(out, l)
6767+ }
6868+ }
6969+ return out, nil
7070+}
7171+7272+func (s *MemStore) GetShortLabels(ctx context.Context, uris []string, srcs []syntax.DID) ([]ShortLabel, error) {
7373+ labels, err := s.GetLabels(ctx, uris, srcs)
7474+ if err != nil {
7575+ return nil, err
7676+ }
7777+ out := make([]ShortLabel, len(labels))
7878+ for i, l := range labels {
7979+ out[i] = ShortLabel{
8080+ SourceDID: l.SourceDID,
8181+ URI: l.URI,
8282+ CID: l.CID,
8383+ Val: l.Val,
8484+ CreatedAt: l.CreatedAt,
8585+ }
8686+ }
8787+ return out, nil
8888+}
8989+9090+func (s *MemStore) PutLabels(ctx context.Context, labels []labeling.Label) error {
9191+ for _, l := range labels {
9292+ src, err := syntax.ParseDID(l.SourceDID)
9393+ if err != nil {
9494+ return fmt.Errorf("invalid DID in label (%s): %w", l.SourceDID, err)
9595+ }
9696+9797+ _, ok := s.Labels[l.URI]
9898+ if !ok {
9999+ s.Labels[l.URI] = map[syntax.DID]labeling.Label{}
100100+ }
101101+102102+ s.Labels[l.URI][src] = l
103103+ }
104104+ return nil
105105+}