package spindle
import (
"context"
"errors"
"github.com/bluesky-social/indigo/atproto/syntax"
"tangled.org/core/api/org_tangled"
"tangled.org/core/rbac"
"tangled.org/core/spindle/db"
"tangled.org/core/spindle/knotclient"
)
// event order doesn't matter when the knot decides if the record exists
func (s *Spindle) applyCollaboratorMark(ctx context.Context, m db.RecordMark) bool {
repo, err := s.servedRepo(m.RepoDid)
if err != nil {
s.l.Error("looking up a repo for a collaborator mark", "repo", m.RepoDid, "err", err)
return false
}
subject, parseErr := syntax.ParseDID(m.Rkey)
if repo == nil || parseErr != nil {
return s.clearRecordMark(m)
}
k, err := s.knots.KnotOf(ctx, repo.RepoDid)
if err == nil {
err = s.knots.GetRecord(ctx, k, repo.RepoDid, org_tangled.GraphCollaboratorNSID, m.Rkey, nil)
}
exists := err == nil
if err != nil && !errors.Is(err, knotclient.ErrNotFound) {
return s.failCollaboratorMark(ctx, m, err)
}
// keep the knot read out of the lock, it can hang
err = s.whileServed(repo, func() error {
if exists {
return s.trackCollaborator(repo.RepoDid, subject)
}
return s.untrackCollaborator(repo.RepoDid, subject)
})
if err != nil {
return s.failCollaboratorMark(ctx, m, err)
}
return s.clearRecordMark(m)
}
func (s *Spindle) failCollaboratorMark(ctx context.Context, m db.RecordMark, err error) bool {
if ctx.Err() == nil {
s.l.Warn("applying a collaborator record failed", "repo", m.RepoDid, "subject", m.Rkey, "err", err)
s.failRecordMark(m.RecordRetry)
}
return false
}
func (s *Spindle) clearRecordMark(m db.RecordMark) bool {
if err := s.db.ClearRecordRetryAt(m.RecordRetry, m.Epoch); err != nil {
s.l.Error("clearing a record mark", "repo", m.RepoDid, "rkey", m.Rkey, "err", err)
return false
}
return true
}
// db row first, otherwise a revoke can't find it, and a failed policy add takes it back out
func (s *Spindle) trackCollaborator(repoDid, subject syntax.DID) error {
if err := s.db.AddKnotCollaborator(repoDid, subject); err != nil {
return err
}
if err := s.e.AddCollaborator(subject.String(), rbac.ThisServer, repoDid.String()); err != nil {
return errors.Join(err, s.db.DeleteRepoCollaboratorBySubjectRepo(subject, repoDid))
}
return nil
}
func (s *Spindle) untrackCollaborator(repoDid, subject syntax.DID) error {
if err := s.e.RemoveCollaborator(subject.String(), rbac.ThisServer, repoDid.String()); err != nil {
return err
}
return s.db.DeleteRepoCollaboratorBySubjectRepo(subject, repoDid)
}