~james/core

core/spindle/collaborators.go -rw-r--r-- 2.5 KiB
452f17b7 — james chore(import): snapshot james.dad.town/core at f70c71c7696a 15 hours ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
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)
}