diff --git a/appview/ingester.go b/appview/ingester.go --- a/appview/ingester.go +++ b/appview/ingester.go @@ -387,6 +387,8 @@ err = i.Enforcer.AddSpindleMember(record.Instance, memberId.DID.String()) if err != nil { return fmt.Errorf("failed to update ACLs: %w", err) } + + l.Info("added spindle member") case models.CommitOperationDelete: rkey := e.Commit.RKey @@ -433,6 +435,8 @@ if err = i.Enforcer.E.SavePolicy(); err != nil { return fmt.Errorf("failed to save ACLs: %w", err) } + + l.Info("removed spindle member") } return nil diff --git a/appview/spindles/spindles.go b/appview/spindles/spindles.go --- a/appview/spindles/spindles.go +++ b/appview/spindles/spindles.go @@ -619,14 +619,14 @@ } if string(spindles[0].Owner) != user.Did { l.Error("unauthorized", "user", user.Did, "owner", spindles[0].Owner) - s.Pages.Notice(w, noticeId, "Failed to add member, unauthorized attempt.") + s.Pages.Notice(w, noticeId, "Failed to remove member, unauthorized attempt.") return } member := r.FormValue("member") if member == "" { l.Error("empty member") - s.Pages.Notice(w, noticeId, "Failed to add member, empty form.") + s.Pages.Notice(w, noticeId, "Failed to remove member, empty form.") return } l = l.With("member", member) @@ -634,12 +634,12 @@ memberId, err := s.IdResolver.ResolveIdent(r.Context(), member) if err != nil { l.Error("failed to resolve member identity to handle", "err", err) - s.Pages.Notice(w, noticeId, "Failed to add member, identity resolution failed.") + s.Pages.Notice(w, noticeId, "Failed to remove member, identity resolution failed.") return } if memberId.Handle.IsInvalidHandle() { l.Error("failed to resolve member identity to handle") - s.Pages.Notice(w, noticeId, "Failed to add member, identity resolution failed.") + s.Pages.Notice(w, noticeId, "Failed to remove member, identity resolution failed.") return } diff --git a/spindle/db/db.go b/spindle/db/db.go --- a/spindle/db/db.go +++ b/spindle/db/db.go @@ -45,6 +45,21 @@ unique(owner, name) ); + create table if not exists spindle_members ( + -- identifiers for the record + id integer primary key autoincrement, + did text not null, + rkey text not null, + + -- data + instance text not null, + subject text not null, + created text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')), + + -- constraints + unique (did, instance, subject) + ); + -- status event for a single workflow create table if not exists events ( rkey text not null, diff --git a/spindle/db/member.go b/spindle/db/member.go new file mode 100644 --- /dev/null +++ b/spindle/db/member.go @@ -0,0 +1,59 @@ +package db + +import ( + "time" + + "github.com/bluesky-social/indigo/atproto/syntax" +) + +type SpindleMember struct { + Id int + Did syntax.DID // owner of the record + Rkey string // rkey of the record + Instance string + Subject syntax.DID // the member being added + Created time.Time +} + +func AddSpindleMember(db *DB, member SpindleMember) error { + _, err := db.Exec( + `insert or ignore into spindle_members (did, rkey, instance, subject) values (?, ?, ?, ?)`, + member.Did, + member.Rkey, + member.Instance, + member.Subject, + ) + return err +} + +func RemoveSpindleMember(db *DB, owner_did, rkey string) error { + _, err := db.Exec( + "delete from spindle_members where did = ? and rkey = ?", + owner_did, + rkey, + ) + return err +} + +func GetSpindleMember(db *DB, did, rkey string) (*SpindleMember, error) { + query := + `select id, did, rkey, instance, subject, created + from spindle_members + where did = ? and rkey = ?` + + var member SpindleMember + var createdAt string + err := db.QueryRow(query, did, rkey).Scan( + &member.Id, + &member.Did, + &member.Rkey, + &member.Instance, + &member.Subject, + &createdAt, + ) + if err != nil { + return nil, err + } + + return &member, nil +} diff --git a/spindle/ingester.go b/spindle/ingester.go --- a/spindle/ingester.go +++ b/spindle/ingester.go @@ -5,11 +5,13 @@ "context" "encoding/json" "errors" "fmt" + "time" "tangled.sh/tangled.sh/core/api/tangled" "tangled.sh/tangled.sh/core/eventconsumer" "tangled.sh/tangled.sh/core/idresolver" "tangled.sh/tangled.sh/core/rbac" + "tangled.sh/tangled.sh/core/spindle/db" comatproto "github.com/bluesky-social/indigo/api/atproto" "github.com/bluesky-social/indigo/atproto/identity" @@ -50,8 +52,9 @@ } } func (s *Spindle) ingestMember(_ context.Context, e *models.Event) error { + var err error did := e.Did - var err error + rkey := e.Commit.RKey l := s.l.With("component", "ingester", "record", tangled.SpindleMemberNSID) @@ -66,9 +69,6 @@ return err } domain := s.cfg.Server.Hostname - if s.cfg.Server.Dev { - domain = s.cfg.Server.ListenAddr - } recordInstance := record.Instance if recordInstance != domain { @@ -82,6 +82,17 @@ l.Error("failed to add member", "did", did, "error", err) return fmt.Errorf("failed to enforce permissions: %w", err) } + if err := db.AddSpindleMember(s.db, db.SpindleMember{ + Did: syntax.DID(did), + Rkey: rkey, + Instance: recordInstance, + Subject: syntax.DID(record.Subject), + Created: time.Now(), + }); err != nil { + l.Error("failed to add member", "error", err) + return fmt.Errorf("failed to add member: %w", err) + } + if err := s.e.AddSpindleMember(rbacDomain, record.Subject); err != nil { l.Error("failed to add member", "error", err) return fmt.Errorf("failed to add member: %w", err) @@ -95,6 +106,30 @@ } s.jc.AddDid(record.Subject) return nil + + case models.CommitOperationDelete: + record, err := db.GetSpindleMember(s.db, did, rkey) + if err != nil { + l.Error("failed to find member", "error", err) + return fmt.Errorf("failed to find member: %w", err) + } + + if err := db.RemoveSpindleMember(s.db, did, rkey); err != nil { + l.Error("failed to remove member", "error", err) + return fmt.Errorf("failed to remove member: %w", err) + } + + if err := s.e.RemoveSpindleMember(rbacDomain, record.Subject.String()); err != nil { + l.Error("failed to add member", "error", err) + return fmt.Errorf("failed to add member: %w", err) + } + l.Info("added member from firehose", "member", record.Subject) + + if err := s.db.RemoveDid(record.Subject.String()); err != nil { + l.Error("failed to add did", "error", err) + return fmt.Errorf("failed to add did: %w", err) + } + s.jc.RemoveDid(record.Subject.String()) } return nil