···19611961 return err
19621962 })
1963196319641964+ orm.RunMigration(conn, logger, "add-knot-members-table", func(tx *sql.Tx) error {
19651965+ _, err := tx.Exec(`
19661966+ create table if not exists knot_members (
19671967+ id integer primary key autoincrement,
19681968+ did text not null,
19691969+ rkey text not null,
19701970+ domain text not null,
19711971+ subject text not null,
19721972+ created text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
19731973+ unique (did, domain, subject)
19741974+ );
19751975+ create index if not exists idx_knot_members_did_rkey on knot_members(did, rkey);
19761976+ `)
19771977+ return err
19781978+ })
19791979+19641980 return &DB{
19651981 db,
19661982 logger,
+88
appview/db/registration.go
···9292 _, err := e.Exec(`
9393 insert into registrations (domain, did)
9494 values (?, ?)
9595+ on conflict (domain, did) do nothing
9596 `, domain, did)
9697 return err
9798}
···114115 _, err := e.Exec(query, args...)
115116 return err
116117}
118118+119119+func AddKnotMember(e Execer, member models.KnotMember) error {
120120+ _, err := e.Exec(
121121+ `insert into knot_members (did, rkey, domain, subject)
122122+ values (?, ?, ?, ?)
123123+ on conflict (did, domain, subject) do update set rkey = excluded.rkey`,
124124+ member.Did,
125125+ member.Rkey,
126126+ member.Domain,
127127+ member.Subject,
128128+ )
129129+ return err
130130+}
131131+132132+func RemoveKnotMember(e Execer, filters ...orm.Filter) error {
133133+ if len(filters) == 0 {
134134+ return fmt.Errorf("RemoveKnotMember requires at least one filter")
135135+ }
136136+137137+ var conditions []string
138138+ var args []any
139139+ for _, filter := range filters {
140140+ conditions = append(conditions, filter.Condition())
141141+ args = append(args, filter.Arg()...)
142142+ }
143143+144144+ query := fmt.Sprintf(`delete from knot_members where %s`, strings.Join(conditions, " and "))
145145+146146+ _, err := e.Exec(query, args...)
147147+ return err
148148+}
149149+150150+func GetKnotMembers(e Execer, filters ...orm.Filter) ([]models.KnotMember, error) {
151151+ var members []models.KnotMember
152152+153153+ var conditions []string
154154+ var args []any
155155+ for _, filter := range filters {
156156+ conditions = append(conditions, filter.Condition())
157157+ args = append(args, filter.Arg()...)
158158+ }
159159+160160+ whereClause := ""
161161+ if conditions != nil {
162162+ whereClause = " where " + strings.Join(conditions, " and ")
163163+ }
164164+165165+ query := fmt.Sprintf(
166166+ `select id, did, rkey, domain, subject, created
167167+ from knot_members
168168+ %s
169169+ order by created
170170+ `,
171171+ whereClause,
172172+ )
173173+174174+ rows, err := e.Query(query, args...)
175175+ if err != nil {
176176+ return nil, err
177177+ }
178178+ defer rows.Close()
179179+180180+ for rows.Next() {
181181+ var member models.KnotMember
182182+ var createdAt string
183183+184184+ if err := rows.Scan(
185185+ &member.Id,
186186+ &member.Did,
187187+ &member.Rkey,
188188+ &member.Domain,
189189+ &member.Subject,
190190+ &createdAt,
191191+ ); err != nil {
192192+ return nil, err
193193+ }
194194+195195+ member.Created, err = time.Parse(time.RFC3339, createdAt)
196196+ if err != nil {
197197+ member.Created = time.Now()
198198+ }
199199+200200+ members = append(members, member)
201201+ }
202202+203203+ return members, nil
204204+}
+7-8
appview/db/spindle.go
···8383 return spindles, nil
8484}
85858686-// if there is an existing spindle with the same instance, this returns an error
8786func AddSpindle(e Execer, spindle models.Spindle) error {
8887 _, err := e.Exec(
8989- `insert into spindles (owner, instance) values (?, ?)`,
8888+ `insert into spindles (owner, instance) values (?, ?)
8989+ on conflict (owner, instance) do nothing`,
9090 spindle.Owner,
9191 spindle.Instance,
9292 )
···147147}
148148149149func RemoveSpindleMember(e Execer, filters ...orm.Filter) error {
150150+ if len(filters) == 0 {
151151+ return fmt.Errorf("RemoveSpindleMember requires at least one filter")
152152+ }
153153+150154 var conditions []string
151155 var args []any
152156 for _, filter := range filters {
···154158 args = append(args, filter.Arg()...)
155159 }
156160157157- whereClause := ""
158158- if conditions != nil {
159159- whereClause = " where " + strings.Join(conditions, " and ")
160160- }
161161-162162- query := fmt.Sprintf(`delete from spindle_members %s`, whereClause)
161161+ query := fmt.Sprintf(`delete from spindle_members where %s`, strings.Join(conditions, " and "))
163162164163 _, err := e.Exec(query, args...)
165164 return err
+333-59
appview/ingester.go
···3838)
39394040type Ingester struct {
4141+ Ctx context.Context
4142 Db *db.DB
4243 Enforcer *rbac.Enforcer
4344 IdResolver *idresolver.Resolver
···8788 case tangled.SpindleNSID:
8889 err = i.ingestSpindle(ctx, e)
8990 case tangled.KnotMemberNSID:
9090- err = i.ingestKnotMember(e)
9191+ err = i.ingestKnotMember(ctx, e)
9192 case tangled.KnotNSID:
9292- err = i.ingestKnot(e)
9393+ err = i.ingestKnot(ctx, e)
9394 case tangled.StringNSID:
9495 err = i.ingestString(e)
9596 case tangled.RepoIssueNSID:
···640641 l = l.With("nsid", e.Commit.Collection)
641642642643 switch e.Commit.Operation {
643643- case jmodels.CommitOperationCreate:
644644+ case jmodels.CommitOperationCreate, jmodels.CommitOperationUpdate:
644645 raw := json.RawMessage(e.Commit.Record)
645646 record := tangled.SpindleMember{}
646647 err = json.Unmarshal(raw, &record)
···651652652653 // only spindle owner can invite to spindles
653654 ok, err := i.Enforcer.IsSpindleInviteAllowed(did, record.Instance)
654654- if err != nil || !ok {
655655- return fmt.Errorf("failed to enforce permissions: %w", err)
655655+ if err != nil {
656656+ return fmt.Errorf("failed to check invite permission: %w", err)
657657+ }
658658+ if !ok {
659659+ if verifyErr := i.verifySpindle(ctx, record.Instance, did); verifyErr != nil {
660660+ return fmt.Errorf("invite denied and verify failed: %w", verifyErr)
661661+ }
662662+ ok, err = i.Enforcer.IsSpindleInviteAllowed(did, record.Instance)
663663+ if err != nil {
664664+ return fmt.Errorf("failed to re-check invite permission: %w", err)
665665+ }
666666+ if !ok {
667667+ return fmt.Errorf("invite denied for did %s on spindle %s", did, record.Instance)
668668+ }
656669 }
657670658671 memberId, err := i.IdResolver.ResolveIdent(ctx, record.Subject)
···661674 }
662675663676 if memberId.Handle.IsInvalidHandle() {
664664- return err
677677+ return fmt.Errorf("invalid handle for member %s", record.Subject)
665678 }
666679667667- err = db.AddSpindleMember(i.Db, models.SpindleMember{
680680+ existing, err := db.GetSpindleMembers(i.Db,
681681+ orm.FilterEq("did", did),
682682+ orm.FilterEq("rkey", e.Commit.RKey),
683683+ )
684684+ if err != nil {
685685+ return fmt.Errorf("failed to look up existing member: %w", err)
686686+ }
687687+ if len(existing) > 1 {
688688+ return fmt.Errorf("multiple spindle members with rkey %s", e.Commit.RKey)
689689+ }
690690+691691+ tx, err := i.Db.Begin()
692692+ if err != nil {
693693+ return fmt.Errorf("failed to start txn: %w", err)
694694+ }
695695+ committed := false
696696+ defer func() {
697697+ if committed {
698698+ return
699699+ }
700700+ tx.Rollback()
701701+ i.Enforcer.E.LoadPolicy()
702702+ }()
703703+704704+ if len(existing) == 1 {
705705+ prev := existing[0]
706706+ if prev.Instance != record.Instance || prev.Subject != memberId.DID {
707707+ if err = db.RemoveSpindleMember(tx,
708708+ orm.FilterEq("did", did),
709709+ orm.FilterEq("rkey", e.Commit.RKey),
710710+ ); err != nil {
711711+ return fmt.Errorf("failed to remove stale row: %w", err)
712712+ }
713713+ if err = i.Enforcer.RemoveSpindleMember(prev.Instance, prev.Subject.String()); err != nil {
714714+ return fmt.Errorf("failed to remove stale ACL: %w", err)
715715+ }
716716+ }
717717+ }
718718+719719+ if err = db.AddSpindleMember(tx, models.SpindleMember{
668720 Did: syntax.DID(did),
669721 Rkey: e.Commit.RKey,
670722 Instance: record.Instance,
671723 Subject: memberId.DID,
672672- })
673673- if !ok {
724724+ }); err != nil {
674725 return fmt.Errorf("failed to add to db: %w", err)
675726 }
676727677677- err = i.Enforcer.AddSpindleMember(record.Instance, memberId.DID.String())
678678- if err != nil {
728728+ if err = i.Enforcer.AddSpindleMember(record.Instance, memberId.DID.String()); err != nil {
679729 return fmt.Errorf("failed to update ACLs: %w", err)
680730 }
681731682682- l.Info("added spindle member")
732732+ if err = tx.Commit(); err != nil {
733733+ return fmt.Errorf("failed to commit txn: %w", err)
734734+ }
735735+736736+ if err = i.Enforcer.E.SavePolicy(); err != nil {
737737+ return fmt.Errorf("failed to save ACLs: %w", err)
738738+ }
739739+ committed = true
740740+741741+ l.Info("upserted spindle member")
683742 case jmodels.CommitOperationDelete:
684743 rkey := e.Commit.RKey
685744···698757 if err != nil {
699758 return fmt.Errorf("failed to start txn: %w", err)
700759 }
760760+ committed := false
761761+ defer func() {
762762+ if committed {
763763+ return
764764+ }
765765+ tx.Rollback()
766766+ i.Enforcer.E.LoadPolicy()
767767+ }()
701768702769 // remove record by rkey && update enforcer
703770 if err = db.RemoveSpindleMember(
···721788 if err = i.Enforcer.E.SavePolicy(); err != nil {
722789 return fmt.Errorf("failed to save ACLs: %w", err)
723790 }
791791+ committed = true
724792725793 l.Info("removed spindle member")
726794 }
···736804 l = l.With("nsid", e.Commit.Collection)
737805738806 switch e.Commit.Operation {
739739- case jmodels.CommitOperationCreate:
807807+ case jmodels.CommitOperationCreate, jmodels.CommitOperationUpdate:
740808 raw := json.RawMessage(e.Commit.Record)
741809 record := tangled.Spindle{}
742810 err = json.Unmarshal(raw, &record)
···756824 return err
757825 }
758826759759- err = retry.Do(
760760- func() error { return serververify.RunVerification(ctx, instance, did, i.Config.Core.Dev) },
761761- retry.Attempts(5), retry.Delay(5*time.Second), retry.MaxDelay(80*time.Second),
762762- retry.DelayType(retry.BackOffDelay), retry.LastErrorOnly(true),
763763- )
764764- if err != nil {
765765- l.Error("failed to verify spindle after retries", "err", err, "instance", instance)
766766- return err
767767- }
768768-769769- _, err = serververify.MarkSpindleVerified(i.Db, i.Enforcer, instance, did)
770770- if err != nil {
771771- return fmt.Errorf("failed to mark verified: %w", err)
827827+ if err := i.verifySpindle(ctx, instance, did); err != nil {
828828+ l.Warn("failed to verify spindle", "instance", instance, "did", did, "err", err)
772829 }
773830774831 return nil
···886943 return nil
887944}
888945889889-func (i *Ingester) ingestKnotMember(e *jmodels.Event) error {
946946+func (i *Ingester) ingestKnotMember(ctx context.Context, e *jmodels.Event) error {
890947 did := e.Did
891948 var err error
892949···894951 l = l.With("nsid", e.Commit.Collection)
895952896953 switch e.Commit.Operation {
897897- case jmodels.CommitOperationCreate:
954954+ case jmodels.CommitOperationCreate, jmodels.CommitOperationUpdate:
898955 raw := json.RawMessage(e.Commit.Record)
899956 record := tangled.KnotMember{}
900957 err = json.Unmarshal(raw, &record)
···905962906963 // only knot owner can invite to knots
907964 ok, err := i.Enforcer.IsKnotInviteAllowed(did, record.Domain)
908908- if err != nil || !ok {
909909- return fmt.Errorf("failed to enforce permissions: %w", err)
965965+ if err != nil {
966966+ return fmt.Errorf("failed to check invite permission: %w", err)
967967+ }
968968+ if !ok {
969969+ if verifyErr := i.verifyKnot(ctx, record.Domain, did); verifyErr != nil {
970970+ return fmt.Errorf("invite denied and verify failed: %w", verifyErr)
971971+ }
972972+ ok, err = i.Enforcer.IsKnotInviteAllowed(did, record.Domain)
973973+ if err != nil {
974974+ return fmt.Errorf("failed to re-check invite permission: %w", err)
975975+ }
976976+ if !ok {
977977+ return fmt.Errorf("invite denied for did %s on knot %s", did, record.Domain)
978978+ }
910979 }
911980912912- memberId, err := i.IdResolver.ResolveIdent(context.Background(), record.Subject)
981981+ memberId, err := i.IdResolver.ResolveIdent(ctx, record.Subject)
913982 if err != nil {
914983 return err
915984 }
916985917986 if memberId.Handle.IsInvalidHandle() {
918918- return err
987987+ return fmt.Errorf("invalid handle for member %s", record.Subject)
919988 }
920989921921- err = i.Enforcer.AddKnotMember(record.Domain, memberId.DID.String())
990990+ existing, err := db.GetKnotMembers(i.Db,
991991+ orm.FilterEq("did", did),
992992+ orm.FilterEq("rkey", e.Commit.RKey),
993993+ )
922994 if err != nil {
995995+ return fmt.Errorf("failed to look up existing member: %w", err)
996996+ }
997997+ if len(existing) > 1 {
998998+ return fmt.Errorf("multiple knot members with rkey %s", e.Commit.RKey)
999999+ }
10001000+10011001+ tx, err := i.Db.Begin()
10021002+ if err != nil {
10031003+ return fmt.Errorf("failed to start txn: %w", err)
10041004+ }
10051005+ committed := false
10061006+ defer func() {
10071007+ if committed {
10081008+ return
10091009+ }
10101010+ tx.Rollback()
10111011+ i.Enforcer.E.LoadPolicy()
10121012+ }()
10131013+10141014+ if len(existing) == 1 {
10151015+ prev := existing[0]
10161016+ if prev.Domain != record.Domain || prev.Subject != memberId.DID {
10171017+ if err = db.RemoveKnotMember(tx,
10181018+ orm.FilterEq("did", did),
10191019+ orm.FilterEq("rkey", e.Commit.RKey),
10201020+ ); err != nil {
10211021+ return fmt.Errorf("failed to remove stale row: %w", err)
10221022+ }
10231023+ if err = i.Enforcer.RemoveKnotMember(prev.Domain, prev.Subject.String()); err != nil {
10241024+ return fmt.Errorf("failed to remove stale ACL: %w", err)
10251025+ }
10261026+ }
10271027+ }
10281028+10291029+ if err = db.AddKnotMember(tx, models.KnotMember{
10301030+ Did: syntax.DID(did),
10311031+ Rkey: e.Commit.RKey,
10321032+ Domain: record.Domain,
10331033+ Subject: memberId.DID,
10341034+ }); err != nil {
10351035+ return fmt.Errorf("failed to add to db: %w", err)
10361036+ }
10371037+10381038+ if err = i.Enforcer.AddKnotMember(record.Domain, memberId.DID.String()); err != nil {
9231039 return fmt.Errorf("failed to update ACLs: %w", err)
9241040 }
9251041926926- l.Info("added knot member")
10421042+ if err = tx.Commit(); err != nil {
10431043+ return fmt.Errorf("failed to commit txn: %w", err)
10441044+ }
10451045+10461046+ if err = i.Enforcer.E.SavePolicy(); err != nil {
10471047+ return fmt.Errorf("failed to save ACLs: %w", err)
10481048+ }
10491049+ committed = true
10501050+10511051+ l.Info("upserted knot member")
9271052 case jmodels.CommitOperationDelete:
928928- // we don't store knot members in a table (like we do for spindle)
929929- // and we can't remove this just yet. possibly fixed if we switch
930930- // to either:
931931- // 1. a knot_members table like with spindle and store the rkey
932932- // 2. use the knot host as the rkey
933933- //
934934- // TODO: implement member deletion
935935- l.Info("skipping knot member delete", "did", did, "rkey", e.Commit.RKey)
10531053+ rkey := e.Commit.RKey
10541054+10551055+ members, err := db.GetKnotMembers(
10561056+ i.Db,
10571057+ orm.FilterEq("did", did),
10581058+ orm.FilterEq("rkey", rkey),
10591059+ )
10601060+ if err != nil {
10611061+ return fmt.Errorf("failed to look up knot member with rkey %s: %w", rkey, err)
10621062+ }
10631063+ if len(members) == 0 {
10641064+ l.Info("knot member already removed", "rkey", rkey)
10651065+ return nil
10661066+ }
10671067+ if len(members) > 1 {
10681068+ return fmt.Errorf("multiple knot members with rkey %s", rkey)
10691069+ }
10701070+ member := members[0]
10711071+10721072+ tx, err := i.Db.Begin()
10731073+ if err != nil {
10741074+ return fmt.Errorf("failed to start txn: %w", err)
10751075+ }
10761076+ committed := false
10771077+ defer func() {
10781078+ if committed {
10791079+ return
10801080+ }
10811081+ tx.Rollback()
10821082+ i.Enforcer.E.LoadPolicy()
10831083+ }()
10841084+10851085+ if err = db.RemoveKnotMember(
10861086+ tx,
10871087+ orm.FilterEq("did", did),
10881088+ orm.FilterEq("rkey", rkey),
10891089+ ); err != nil {
10901090+ return fmt.Errorf("failed to remove from db: %w", err)
10911091+ }
10921092+10931093+ if err = i.Enforcer.RemoveKnotMember(member.Domain, member.Subject.String()); err != nil {
10941094+ return fmt.Errorf("failed to update ACLs: %w", err)
10951095+ }
10961096+10971097+ if err = tx.Commit(); err != nil {
10981098+ return fmt.Errorf("failed to commit txn: %w", err)
10991099+ }
11001100+11011101+ if err = i.Enforcer.E.SavePolicy(); err != nil {
11021102+ return fmt.Errorf("failed to save ACLs: %w", err)
11031103+ }
11041104+ committed = true
11051105+11061106+ l.Info("removed knot member")
9361107 }
93711089381109 return nil
9391110}
9401111941941-func (i *Ingester) ingestKnot(e *jmodels.Event) error {
11121112+func (i *Ingester) ingestKnot(ctx context.Context, e *jmodels.Event) error {
9421113 did := e.Did
9431114 var err error
9441115···9461117 l = l.With("nsid", e.Commit.Collection)
94711189481119 switch e.Commit.Operation {
949949- case jmodels.CommitOperationCreate:
11201120+ case jmodels.CommitOperationCreate, jmodels.CommitOperationUpdate:
9501121 raw := json.RawMessage(e.Commit.Record)
9511122 record := tangled.Knot{}
9521123 err = json.Unmarshal(raw, &record)
···9631134 return err
9641135 }
9651136966966- err = retry.Do(
967967- func() error {
968968- return serververify.RunVerification(context.Background(), domain, did, i.Config.Core.Dev)
969969- },
970970- retry.Attempts(5), retry.Delay(5*time.Second), retry.MaxDelay(80*time.Second),
971971- retry.DelayType(retry.BackOffDelay), retry.LastErrorOnly(true),
972972- )
973973- if err != nil {
974974- l.Error("failed to verify knot after retries", "err", err, "domain", domain)
975975- return err
976976- }
977977-978978- err = serververify.MarkKnotVerified(i.Db, i.Enforcer, domain, did)
979979- if err != nil {
980980- return fmt.Errorf("failed to mark verified: %w", err)
11371137+ if err := i.verifyKnot(ctx, domain, did); err != nil {
11381138+ l.Warn("failed to verify knot", "domain", domain, "did", did, "err", err)
9811139 }
98211409831141 return nil
···10081166 i.Enforcer.E.LoadPolicy()
10091167 }()
1010116811691169+ err = db.RemoveKnotMember(
11701170+ tx,
11711171+ orm.FilterEq("did", did),
11721172+ orm.FilterEq("domain", domain),
11731173+ )
11741174+ if err != nil {
11751175+ return err
11761176+ }
11771177+10111178 err = db.DeleteKnot(
10121179 tx,
10131180 orm.FilterEq("did", did),
···1037120410381205 return nil
10391206}
12071207+12081208+const (
12091209+ verifyAttempts = 4
12101210+ verifyMinDelay = 1 * time.Second
12111211+ verifyMaxDelay = 5 * time.Second
12121212+)
12131213+12141214+func (i *Ingester) verifyKnot(ctx context.Context, domain, did string) error {
12151215+ regs, err := db.GetRegistrations(i.Db,
12161216+ orm.FilterEq("domain", domain),
12171217+ orm.FilterEq("did", did),
12181218+ )
12191219+ if err != nil {
12201220+ return fmt.Errorf("look up registration: %w", err)
12211221+ }
12221222+ if len(regs) != 1 {
12231223+ return fmt.Errorf("no registration for %s by %s", domain, did)
12241224+ }
12251225+ if regs[0].Registered != nil {
12261226+ return nil
12271227+ }
12281228+12291229+ err = retry.Do(
12301230+ func() error { return serververify.RunVerification(ctx, domain, did, i.Config.Core.Dev) },
12311231+ retry.Context(ctx),
12321232+ retry.Attempts(verifyAttempts),
12331233+ retry.Delay(verifyMinDelay),
12341234+ retry.MaxDelay(verifyMaxDelay),
12351235+ retry.DelayType(retry.BackOffDelay),
12361236+ retry.LastErrorOnly(true),
12371237+ )
12381238+ if err != nil {
12391239+ return fmt.Errorf("verify: %w", err)
12401240+ }
12411241+ return serververify.MarkKnotVerified(i.Db, i.Enforcer, domain, did)
12421242+}
12431243+12441244+func (i *Ingester) verifySpindle(ctx context.Context, instance, did string) error {
12451245+ spindles, err := db.GetSpindles(ctx, i.Db,
12461246+ orm.FilterEq("instance", instance),
12471247+ orm.FilterEq("owner", did),
12481248+ )
12491249+ if err != nil {
12501250+ return fmt.Errorf("look up spindle: %w", err)
12511251+ }
12521252+ if len(spindles) != 1 {
12531253+ return fmt.Errorf("no spindle for %s by %s", instance, did)
12541254+ }
12551255+ if spindles[0].Verified != nil {
12561256+ return nil
12571257+ }
12581258+12591259+ err = retry.Do(
12601260+ func() error { return serververify.RunVerification(ctx, instance, did, i.Config.Core.Dev) },
12611261+ retry.Context(ctx),
12621262+ retry.Attempts(verifyAttempts),
12631263+ retry.Delay(verifyMinDelay),
12641264+ retry.MaxDelay(verifyMaxDelay),
12651265+ retry.DelayType(retry.BackOffDelay),
12661266+ retry.LastErrorOnly(true),
12671267+ )
12681268+ if err != nil {
12691269+ return fmt.Errorf("verify: %w", err)
12701270+ }
12711271+ _, err = serververify.MarkSpindleVerified(i.Db, i.Enforcer, instance, did)
12721272+ return err
12731273+}
12741274+12751275+const sweepConcurrency = 4
12761276+12771277+func (i *Ingester) SweepPendingVerifications() {
12781278+ l := i.Logger.With("handler", "SweepPendingVerifications")
12791279+12801280+ var g errgroup.Group
12811281+ g.SetLimit(sweepConcurrency)
12821282+12831283+ regs, err := db.GetRegistrations(i.Db, orm.FilterIs("registered", nil))
12841284+ if err != nil {
12851285+ l.Error("failed to list unverified knots", "err", err)
12861286+ } else {
12871287+ for _, reg := range regs {
12881288+ g.Go(func() error {
12891289+ if err := i.verifyKnot(i.Ctx, reg.Domain, reg.ByDid); err != nil {
12901290+ l.Warn("verify knot failed", "domain", reg.Domain, "did", reg.ByDid, "err", err)
12911291+ }
12921292+ return nil
12931293+ })
12941294+ }
12951295+ }
12961296+12971297+ spindles, err := db.GetSpindles(i.Ctx, i.Db, orm.FilterIs("verified", nil))
12981298+ if err != nil {
12991299+ l.Error("failed to list unverified spindles", "err", err)
13001300+ g.Wait()
13011301+ return
13021302+ }
13031303+ for _, s := range spindles {
13041304+ g.Go(func() error {
13051305+ if err := i.verifySpindle(i.Ctx, s.Instance, s.Owner.String()); err != nil {
13061306+ l.Warn("verify spindle failed", "instance", s.Instance, "owner", s.Owner, "err", err)
13071307+ }
13081308+ return nil
13091309+ })
13101310+ }
13111311+ g.Wait()
13121312+}
13131313+10401314func (i *Ingester) ingestIssue(ctx context.Context, e *jmodels.Event) error {
10411315 did := e.Did
10421316 rkey := e.Commit.RKey
+31-1
appview/ingester_repo.go
···262262 return fmt.Errorf("failed to fetch repo for delete: %w", err)
263263 }
264264265265- if err := db.RemoveRepo(i.Db, e.Did, e.Commit.RKey); err != nil {
265265+ if i.Enforcer == nil {
266266+ return fmt.Errorf("ingester has no RBAC enforcer configured")
267267+ }
268268+269269+ tx, err := i.Db.Begin()
270270+ if err != nil {
271271+ return fmt.Errorf("failed to start txn: %w", err)
272272+ }
273273+ committed := false
274274+ defer func() {
275275+ if committed {
276276+ return
277277+ }
278278+ tx.Rollback()
279279+ i.Enforcer.E.LoadPolicy()
280280+ }()
281281+282282+ if err := db.RemoveRepo(tx, e.Did, e.Commit.RKey); err != nil {
266283 return fmt.Errorf("failed to delete repo: %w", err)
267284 }
285285+286286+ if err := i.Enforcer.WipeRepoPolicies(repo.Knot, repo.RepoIdentifier()); err != nil {
287287+ return fmt.Errorf("failed to wipe repo permissions: %w", err)
288288+ }
289289+290290+ if err := tx.Commit(); err != nil {
291291+ return fmt.Errorf("failed to commit txn: %w", err)
292292+ }
293293+294294+ if err := i.Enforcer.E.SavePolicy(); err != nil {
295295+ return fmt.Errorf("failed to save ACLs: %w", err)
296296+ }
297297+ committed = true
268298269299 i.Notifier.DeleteRepo(ctx, repo)
270300 l.Info("deleted repo row")