diff --git a/nip29/group.go b/nip29/group.go index df92841..0287363 100644 --- a/nip29/group.go +++ b/nip29/group.go @@ -20,6 +20,18 @@ type Group struct { LastMembersUpdate nostr.Timestamp } +func NewGroup(id string) *Group { + now := nostr.Now() + return &Group{ + ID: id, + Name: id, + Members: make(map[string]*Role), + LastMetadataUpdate: now, + LastAdminsUpdate: now, + LastMembersUpdate: now, + } +} + func (group Group) ToMetadataEvent() *nostr.Event { evt := &nostr.Event{ Kind: nostr.KindSimpleGroupMetadata, @@ -65,7 +77,7 @@ func (group Group) ToAdminsEvent() *nostr.Event { for member, role := range group.Members { if role != nil { // is an admin - tag := make([]string, 0, 3+len(role.Permissions)) + tag := make([]string, 3, 3+len(role.Permissions)) tag[0] = "p" tag[1] = member tag[2] = role.Name @@ -99,13 +111,11 @@ func (group *Group) MergeInMetadataEvent(evt *nostr.Event) error { if evt.Kind != nostr.KindSimpleGroupMetadata { return fmt.Errorf("expected kind %d, got %d", nostr.KindSimpleGroupMetadata, evt.Kind) } - - if evt.CreatedAt <= group.LastMetadataUpdate { + if evt.CreatedAt < group.LastMetadataUpdate { return fmt.Errorf("event is older than our last update (%d vs %d)", evt.CreatedAt, group.LastMetadataUpdate) } group.LastMetadataUpdate = evt.CreatedAt - group.ID = evt.Tags.GetD() group.Name = group.ID if tag := evt.Tags.GetFirst([]string{"name", ""}); tag != nil { @@ -132,8 +142,7 @@ func (group *Group) MergeInAdminsEvent(evt *nostr.Event) error { if evt.Kind != nostr.KindSimpleGroupAdmins { return fmt.Errorf("expected kind %d, got %d", nostr.KindSimpleGroupAdmins, evt.Kind) } - - if evt.CreatedAt <= group.LastAdminsUpdate { + if evt.CreatedAt < group.LastAdminsUpdate { return fmt.Errorf("event is older than our last update (%d vs %d)", evt.CreatedAt, group.LastAdminsUpdate) } @@ -148,6 +157,18 @@ func (group *Group) MergeInAdminsEvent(evt *nostr.Event) error { if !nostr.IsValidPublicKeyHex(tag[1]) { continue } + + role := group.Members[tag[1]] + if role == nil { + role = &Role{Name: tag[2]} + group.Members[tag[1]] = role + } + if role.Permissions == nil { + role.Permissions = make(map[Permission]struct{}, len(tag)-3) + } + for _, perm := range tag[2:] { + role.Permissions[Permission(perm)] = struct{}{} + } } return nil @@ -157,8 +178,7 @@ func (group *Group) MergeInMembersEvent(evt *nostr.Event) error { if evt.Kind != nostr.KindSimpleGroupMembers { return fmt.Errorf("expected kind %d, got %d", nostr.KindSimpleGroupMembers, evt.Kind) } - - if evt.CreatedAt <= group.LastMembersUpdate { + if evt.CreatedAt < group.LastMembersUpdate { return fmt.Errorf("event is older than our last update (%d vs %d)", evt.CreatedAt, group.LastMembersUpdate) } @@ -173,6 +193,11 @@ func (group *Group) MergeInMembersEvent(evt *nostr.Event) error { if !nostr.IsValidPublicKeyHex(tag[1]) { continue } + + _, exists := group.Members[tag[1]] + if !exists { + group.Members[tag[1]] = EmptyRole + } } return nil diff --git a/nip29/nip29_test.go b/nip29/nip29_test.go new file mode 100644 index 0000000..aecbd9c --- /dev/null +++ b/nip29/nip29_test.go @@ -0,0 +1,61 @@ +package nip29 + +import ( + "testing" +) + +const ( + ALICE = "eadad094b75b4690e7ee7124522861b8d81d5ed92e81eb678e776d1164d1efe9" + BOB = "6ac475cdf30e2006ee5142559544e86f8f1b485a9c8c1f2da467996fb7fcdfe7" + CAROL = "f81982b8b6ba354a1e09acfda348512ef93e5778847fb5f4b30fe6b0042f4b36" + DEREK = "24a049c4e5c9cff1764c312b2e0fa59a02af235b37809180b3f2c7b2ec3dbdfd" +) + +func TestGroupEventBackAndForth(t *testing.T) { + group1 := NewGroup("xyz") + group1.Name = "banana" + group1.Private = true + meta1 := group1.ToMetadataEvent() + if meta1.Tags.GetD() != "xyz" || + meta1.Tags.GetFirst([]string{"name", "banana"}) == nil || + meta1.Tags.GetFirst([]string{"private"}) == nil { + t.Fatalf("translation of group1 to meta1data event failed") + } + + group2 := NewGroup("abc") + group2.Members[ALICE] = &Role{Name: "nada", Permissions: map[Permission]struct{}{PermAddUser: {}}} + group2.Members[BOB] = &Role{Name: "nada", Permissions: map[Permission]struct{}{PermEditMetadata: {}}} + group2.Members[CAROL] = EmptyRole + group2.Members[DEREK] = EmptyRole + admins2 := group2.ToAdminsEvent() + if admins2.Tags.GetD() != "abc" || + len(admins2.Tags) != 3 || + admins2.Tags.GetFirst([]string{"p", ALICE, "nada", "add-user"}) == nil || + admins2.Tags.GetFirst([]string{"p", BOB, "nada", "edit-metadata"}) == nil { + t.Fatalf("translation of group2 to admins event failed") + } + + members2 := group2.ToMembersEvent() + if members2.Tags.GetD() != "abc" || + len(members2.Tags) != 5 || + members2.Tags.GetFirst([]string{"p", ALICE}) == nil || + members2.Tags.GetFirst([]string{"p", BOB}) == nil || + members2.Tags.GetFirst([]string{"p", CAROL}) == nil || + members2.Tags.GetFirst([]string{"p", DEREK}) == nil { + t.Fatalf("translation of group2 to members2 event failed") + } + + group1.MergeInMembersEvent(members2) + if len(group1.Members) != 4 || group1.Members[ALICE] != EmptyRole || group1.Members[DEREK] != EmptyRole { + t.Fatalf("merge of members2 into group1 failed") + } + group1.MergeInAdminsEvent(admins2) + if len(group1.Members) != 4 || group1.Members[ALICE].Name != "nada" || group1.Members[DEREK] != EmptyRole { + t.Fatalf("merge of admins2 into group1 failed") + } + + group2.MergeInMetadataEvent(meta1) + if group2.Name != "banana" || group2.ID != "abc" { + t.Fatalf("merge of meta1 into group2 failed") + } +}