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