Monorepo for Tangled
tangled.org
1package knotserver
2
3import (
4 "context"
5 "fmt"
6 "log/slog"
7 "maps"
8 "slices"
9
10 "github.com/bluesky-social/indigo/atproto/syntax"
11
12 "tangled.org/core/knotserver/db"
13 "tangled.org/core/rbac"
14)
15
16const (
17 collaboratorBackfillMigration = "backfill-collaborators-from-casbin-v1"
18 knotMemberBackfillMigration = "backfill-knot-members-from-casbin-v1"
19)
20
21func BackfillKnotMembers(
22 ctx context.Context,
23 d *db.DB,
24 e *rbac.Enforcer,
25 ownerDid string,
26 logger *slog.Logger,
27) error {
28 l := logger.With("migration", knotMemberBackfillMigration)
29
30 applied, err := d.IsMigrationApplied(knotMemberBackfillMigration)
31 if err != nil {
32 return fmt.Errorf("check migration applied: %w", err)
33 }
34 if applied {
35 return nil
36 }
37
38 owner, err := syntax.ParseDID(ownerDid)
39 if err != nil {
40 return fmt.Errorf("invalid knot owner DID %q: %w", ownerDid, err)
41 }
42
43 members, err := e.GetKnotUsersByRole("server:member", rbac.ThisServer)
44 if err != nil {
45 return fmt.Errorf("list members: %w", err)
46 }
47 owners, err := e.GetKnotUsersByRole("server:owner", rbac.ThisServer)
48 if err != nil {
49 return fmt.Errorf("list owners: %w", err)
50 }
51
52 var rows []db.KnotMember
53 for _, candidate := range members {
54 if slices.Contains(owners, candidate) {
55 continue
56 }
57 subject, err := syntax.ParseDID(candidate)
58 if err != nil {
59 l.Warn("skipping member with invalid DID", "candidate", candidate, "err", err)
60 continue
61 }
62 rows = append(rows, db.KnotMember{Did: owner, Subject: subject})
63 }
64
65 if err := d.ApplyKnotMemberBackfill(ctx, rows, knotMemberBackfillMigration); err != nil {
66 return fmt.Errorf("apply backfill: %w", err)
67 }
68
69 l.Info("backfilled knot members from casbin", "count", len(rows))
70 return nil
71}
72
73func BackfillCollaborators(
74 ctx context.Context,
75 d *db.DB,
76 e *rbac.Enforcer,
77 logger *slog.Logger,
78 markApplied bool,
79) error {
80 l := logger.With("migration", collaboratorBackfillMigration)
81
82 applied, err := d.IsMigrationApplied(collaboratorBackfillMigration)
83 if err != nil {
84 return fmt.Errorf("check migration applied: %w", err)
85 }
86 if applied {
87 return nil
88 }
89
90 byRepo, err := e.GetCollaboratorsByRepo(rbac.ThisServer)
91 if err != nil {
92 return fmt.Errorf("list collaborators: %w", err)
93 }
94
95 var rows []db.Collaborator
96 var skipped int
97 for _, repoDid := range slices.Sorted(maps.Keys(byRepo)) {
98 candidates := byRepo[repoDid]
99
100 ownerDid, _, err := d.GetRepoKeyOwner(repoDid)
101 if err != nil {
102 l.Warn("skipping collaborators for unresolvable repo", "repoDid", repoDid, "collaborators", len(candidates), "err", err)
103 skipped += len(candidates)
104 continue
105 }
106
107 repo, err := syntax.ParseDID(repoDid)
108 if err != nil {
109 l.Warn("skipping collaborators for repo with invalid DID", "repoDid", repoDid, "collaborators", len(candidates), "err", err)
110 skipped += len(candidates)
111 continue
112 }
113 owner, err := syntax.ParseDID(ownerDid)
114 if err != nil {
115 l.Warn("skipping collaborators for repo with invalid owner DID", "repoDid", repoDid, "owner", ownerDid, "collaborators", len(candidates), "err", err)
116 skipped += len(candidates)
117 continue
118 }
119
120 for _, candidate := range candidates {
121 subject, err := syntax.ParseDID(candidate)
122 if err != nil {
123 l.Warn("skipping collaborator with invalid DID", "repoDid", repoDid, "candidate", candidate, "err", err)
124 skipped++
125 continue
126 }
127 rows = append(rows, db.Collaborator{
128 RepoDid: repo,
129 Subject: subject,
130 AddedBy: owner,
131 })
132 }
133 }
134
135 if err := d.ApplyCollaboratorBackfill(ctx, rows, collaboratorBackfillMigration, markApplied); err != nil {
136 return fmt.Errorf("apply backfill: %w", err)
137 }
138
139 l.Info("backfilled collaborators from casbin", "count", len(rows), "repos", len(byRepo), "skipped", skipped, "marked", markApplied)
140 return nil
141}