diff --git a/CLAUDE.md b/CLAUDE.md index 8f4e98d..fe2abae 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,26 +1,4 @@ `README.md` contains high-level project information. `justfile` contains common commands. -## Codebase Overview - -- **zooid/config.go**: Defines `Config` struct with TOML tags for relay configuration (self, groups, management, blossom, roles, data). Contains `LoadConfig()` function and `IsMember()` method. - -- **zooid/http.go**: Simple HTTP handler that calls `GetInstance()` and delegates to khatru relay. - -- **zooid/instance.go**: Core instance management. `Instance` struct holds config and khatru relay. `MakeInstance()` creates configured relay instances with handlers. `GetInstance()` provides singleton access with lazy loading. - -- **zooid/blossom.go**: Blossom file storage integration with member-only access controls. - -- **zooid/util.go**: Environment variable utilities with `Env()` function. - -- **cmd/relay/main.go**: HTTP server entry point with graceful shutdown. - -## SQLite EventStore - -The `sqlite/` directory contains a complete SQLite-based khatru eventstore implementation. - -### nostrlib API Compatibility -- `Event.Sig` is `[64]byte`, not a separate Signature type -- `Event.CreatedAt` is `nostr.Timestamp` (int64), not `time.Time` -- Use `hex.EncodeToString(evt.Sig[:])` for signature serialization -- Use `hex.DecodeString()` and `copy()` for signature parsing +On startup, run `tree zooid` to get an understanding of the structure of the codebase. diff --git a/zooid/groups.go b/zooid/groups.go index 12525f5..192f9bb 100644 --- a/zooid/groups.go +++ b/zooid/groups.go @@ -98,7 +98,7 @@ func (g *GroupStore) GetAdmins(h string) []nostr.PubKey { return g.Management.GetAdmins() } -func (g *GroupStore) GenerateAdminsEvent(h string) nostr.Event { +func (g *GroupStore) UpdateAdminsList(h string) error { tags := nostr.Tags{ nostr.Tag{"-"}, nostr.Tag{"d", h}, @@ -114,9 +114,7 @@ func (g *GroupStore) GenerateAdminsEvent(h string) nostr.Event { Tags: tags, } - g.Config.Sign(&event) - - return event + return g.Events.SignAndStoreEvent(&event, true) } // Membership @@ -194,7 +192,7 @@ func (g *GroupStore) GetMembers(h string) []nostr.PubKey { return members } -func (g *GroupStore) GenerateMembersEvent(h string) nostr.Event { +func (g *GroupStore) UpdateMembersList(h string) error { tags := nostr.Tags{ nostr.Tag{"-"}, nostr.Tag{"d", h}, @@ -210,9 +208,7 @@ func (g *GroupStore) GenerateMembersEvent(h string) nostr.Event { Tags: tags, } - g.Config.Sign(&event) - - return event + return g.Events.SignAndStoreEvent(&event, true) } // Other stuff diff --git a/zooid/instance.go b/zooid/instance.go index 9f00937..5576621 100644 --- a/zooid/instance.go +++ b/zooid/instance.go @@ -294,30 +294,6 @@ func (instance *Instance) QueryStored(ctx context.Context, filter nostr.Filter) generated = append(generated, instance.GenerateInviteEvent(pubkey)) } - if slices.Contains(filter.Kinds, nostr.KindSimpleGroupAdmins) { - groupsFilter := nostr.Filter{ - Kinds: []nostr.Kind{nostr.KindSimpleGroupMetadata}, - } - - for event := range instance.Events.QueryEvents(groupsFilter, 0) { - if tag := event.Tags.Find("d"); tag != nil { - generated = append(generated, instance.Groups.GenerateAdminsEvent(tag[1])) - } - } - } - - if slices.Contains(filter.Kinds, nostr.KindSimpleGroupMembers) { - groupsFilter := nostr.Filter{ - Kinds: []nostr.Kind{nostr.KindSimpleGroupMetadata}, - } - - for event := range instance.Events.QueryEvents(groupsFilter, 0) { - if tag := event.Tags.Find("d"); tag != nil { - generated = append(generated, instance.Groups.GenerateMembersEvent(tag[1])) - } - } - } - for _, event := range generated { if !filter.Matches(event) { continue @@ -458,25 +434,42 @@ func (instance *Instance) OnEvent(ctx context.Context, event nostr.Event) (rejec } func (instance *Instance) OnEventSaved(ctx context.Context, event nostr.Event) { + h := GetGroupIDFromEvent(event) + if event.Kind == nostr.KindSimpleGroupJoinRequest && instance.Config.Groups.AutoJoin { - h := GetGroupIDFromEvent(event) meta := instance.Groups.GetMetadata(h) if !HasTag(meta.Tags, "closed") { instance.Groups.AddMember(h, event.PubKey) + instance.Groups.UpdateMembersList(h) } } if event.Kind == nostr.KindSimpleGroupLeaveRequest && instance.Config.Groups.AutoLeave { - instance.Groups.RemoveMember(GetGroupIDFromEvent(event), event.PubKey) + instance.Groups.RemoveMember(h, event.PubKey) + instance.Groups.UpdateMembersList(h) } - if event.Kind == nostr.KindSimpleGroupCreateGroup || event.Kind == nostr.KindSimpleGroupEditMetadata { + if event.Kind == nostr.KindSimpleGroupPutUser { + instance.Groups.UpdateMembersList(h) + } + + if event.Kind == nostr.KindSimpleGroupRemoveUser { + instance.Groups.UpdateMembersList(h) + } + + if event.Kind == nostr.KindSimpleGroupCreateGroup { instance.Groups.SetMetadataFromEvent(event) + instance.Groups.UpdateAdminsList(h) + } + + if event.Kind == nostr.KindSimpleGroupEditMetadata { + instance.Groups.SetMetadataFromEvent(event) + instance.Groups.UpdateAdminsList(h) } if event.Kind == nostr.KindSimpleGroupDeleteGroup { - instance.Groups.DeleteGroup(GetGroupIDFromEvent(event)) + instance.Groups.DeleteGroup(h) } }