diff --git a/examples/gno.land/p/nt/commondao/exts/storage/gnomod.toml b/examples/gno.land/p/nt/commondao/exts/storage/gnomod.toml new file mode 100644 index 00000000000..e8dc60e239b --- /dev/null +++ b/examples/gno.land/p/nt/commondao/exts/storage/gnomod.toml @@ -0,0 +1,2 @@ +module = "gno.land/p/nt/commondao/exts/storage" +gno = "0.9" diff --git a/examples/gno.land/p/nt/commondao/exts/storage/member_storage.gno b/examples/gno.land/p/nt/commondao/exts/storage/member_storage.gno new file mode 100644 index 00000000000..1c274b6345b --- /dev/null +++ b/examples/gno.land/p/nt/commondao/exts/storage/member_storage.gno @@ -0,0 +1,164 @@ +package storage + +import ( + "gno.land/p/jeronimoalbi/message" + "gno.land/p/nt/avl" + "gno.land/p/nt/commondao" +) + +const ( + msgMemberAdd message.Topic = "MemberAdd" + msgMemberRemove = "MemberRemove" +) + +// GetMemberGroups returns the groups that a member belongs to. +// It returns no groups if member storage was not created using this package. +func GetMemberGroups(s commondao.MemberStorage, member address) []string { + storage, ok := s.(*memberStorage) + if !ok { + return nil + } + + v, found := storage.memberGroups.Get(member.String()) + if !found { + return nil + } + + tree := v.(*avl.Tree) + groups := make([]string, 0, tree.Size()) + tree.Iterate("", "", func(group string, _ any) bool { + groups = append(groups, group) + return false + }) + return groups +} + +// NewMemberStorage creates a new CommonDAO member storage with grouping support. +// +// This is a custom storage that automatically adds or removes members that are added +// or removed from any of the member groups. This allows for quick and inexpensive +// checks for the number of total unique storage users, including users added to groups, +// and also to iterate all of them without needing to iterate individual groups. +func NewMemberStorage() commondao.MemberStorage { + // Create a new broker to allow storages to publish and subscribe to messages + // It is used to add/remove users from the storage each time one or more groups change. + broker := message.NewBroker() + + // Define a factory for creating custom member storages when groups are created. + // Custom storage publishes when a member is added or removed from a group. + innerFactory := func(group string) commondao.MemberStorage { + return &groupMemberStorage{ + MemberStorage: commondao.NewMemberStorage(), + messages: broker, + group: group, + } + } + + // Create a member storage that automatically adds or removes members each time + // a member group changes. This allows the storage to keep all members within + // the same "root" storage for easier iteration. + storage := &memberStorage{ + MemberStorage: commondao.NewMemberStorageWithGrouping( + commondao.UseStorageFactory(innerFactory), + ), + messages: broker, + } + + // Subscribe to messages published by member groups + storage.messages.Subscribe(msgMemberAdd, storage.handleMemberAddMsg) + storage.messages.Subscribe(msgMemberRemove, storage.handleMemberRemoveMsg) + return storage +} + +type memberStorage struct { + commondao.MemberStorage + + memberGroups avl.Tree // string(address) -> *avl.Tree(group -> struct{}) + messages message.Subscriber +} + +func (s *memberStorage) handleMemberAddMsg(msg message.Message) { + data := msg.Data.(groupMemberUpdateData) + key := data.Member.String() + v, _ := s.memberGroups.Get(key) + groups, ok := v.(*avl.Tree) + if !ok { + // Create a new tree to track member's groups + groups = avl.NewTree() + s.memberGroups.Set(key, groups) + + // Add the new member to the storage + s.MemberStorage.Add(data.Member) + } + + // Keep track of the new member group + groups.Set(data.Group, struct{}{}) +} + +func (s *memberStorage) handleMemberRemoveMsg(msg message.Message) { + data := msg.Data.(groupMemberUpdateData) + key := data.Member.String() + v, found := s.memberGroups.Get(key) + if !found { + // Member should always be found + return + } + + // Remove the group from the list of groups member belongs + groups := v.(*avl.Tree) + groups.Remove(data.Group) + + // Remove the member from the storage when it doesn't belong to any group + if groups.Size() == 0 { + s.memberGroups.Remove(key) + s.MemberStorage.Remove(data.Member) + } +} + +// Size returns the number of members in the storage. +// It also includes unique members that belong to any number of member groups. +func (s memberStorage) Size() int { + return s.MemberStorage.Size() +} + +// IterateByOffset iterates members starting at the given offset. +// The callback can return true to stop iteration. +// It also iterates unique members that belong to any of the member groups. +func (s memberStorage) IterateByOffset(offset, count int, fn commondao.MemberIterFn) bool { + return s.MemberStorage.IterateByOffset(offset, count, fn) +} + +// groupMemberUpdateData defines a data type for group member updates. +type groupMemberUpdateData struct { + Group string + Member address +} + +// groupMemberStorage defines a member storage for member groups. +// This type of storage publishes messages when a member is added or removed from a group. +type groupMemberStorage struct { + commondao.MemberStorage + + group string + messages message.Publisher +} + +// Add adds a member to the storage. +// Returns true if the member is added, or false if it already existed. +func (s *groupMemberStorage) Add(member address) bool { + s.messages.Publish(msgMemberAdd, groupMemberUpdateData{ + Group: s.group, + Member: member, + }) + return s.MemberStorage.Add(member) +} + +// Remove removes a member from the storage. +// Returns true if member was removed, or false if it was not found. +func (s *groupMemberStorage) Remove(member address) bool { + s.messages.Publish(msgMemberRemove, groupMemberUpdateData{ + Group: s.group, + Member: member, + }) + return s.MemberStorage.Remove(member) +} diff --git a/examples/gno.land/p/nt/commondao/exts/storage/member_storage_test.gno b/examples/gno.land/p/nt/commondao/exts/storage/member_storage_test.gno new file mode 100644 index 00000000000..88aa3332720 --- /dev/null +++ b/examples/gno.land/p/nt/commondao/exts/storage/member_storage_test.gno @@ -0,0 +1,292 @@ +package storage_test + +import ( + "testing" + + "gno.land/p/nt/commondao" + "gno.land/p/nt/urequire" + + "gno.land/p/nt/commondao/exts/storage" +) + +func TestMemberStorageGroupingMemberAdd(t *testing.T) { + tests := []struct { + name string + setup func() commondao.MemberStorage + group string + member address + }{ + { + name: "empty group", + setup: func() commondao.MemberStorage { + s := storage.NewMemberStorage() + s.Grouping().Add("foo") + return s + }, + group: "foo", + member: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + }, + { + name: "group with members", + setup: func() commondao.MemberStorage { + s := storage.NewMemberStorage() + group, _ := s.Grouping().Add("foo") + group.Members().Add("g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq") + group.Members().Add("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") + return s + }, + group: "foo", + member: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + }, + { + name: "multiple groups with members", + setup: func() commondao.MemberStorage { + s := storage.NewMemberStorage() + group, _ := s.Grouping().Add("foo") + group.Members().Add("g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq") + group, _ = s.Grouping().Add("bar") + group.Members().Add("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") + return s + }, + group: "bar", + member: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Arrange + s := tt.setup() + group, _ := s.Grouping().Get(tt.group) + + // Act + group.Members().Add(tt.member) + + // Assert + urequire.True(t, s.Has(tt.member), "expect member to also be added to parent storage") + }) + } +} + +func TestMemberStorageGroupingMemberRemove(t *testing.T) { + tests := []struct { + name string + setup func() commondao.MemberStorage + group string + member address + }{ + { + name: "one group one member", + setup: func() commondao.MemberStorage { + s := storage.NewMemberStorage() + group, _ := s.Grouping().Add("foo") + group.Members().Add("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") + return s + }, + group: "foo", + member: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + }, + { + name: "one group multiple members", + setup: func() commondao.MemberStorage { + s := storage.NewMemberStorage() + group, _ := s.Grouping().Add("foo") + group.Members().Add("g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq") + group.Members().Add("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") + group.Members().Add("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") + return s + }, + group: "foo", + member: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + }, + { + name: "multiple groups multiple members", + setup: func() commondao.MemberStorage { + s := storage.NewMemberStorage() + group, _ := s.Grouping().Add("foo") + group.Members().Add("g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq") + group.Members().Add("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") + group, _ = s.Grouping().Add("bar") + group.Members().Add("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") + return s + }, + group: "foo", + member: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Arrange + s := tt.setup() + group, _ := s.Grouping().Get(tt.group) + + // Act + group.Members().Remove(tt.member) + + // Assert + urequire.False(t, s.Has(tt.member), "expect member to also be removed from parent storage") + }) + } +} + +func TestMemberStorageSize(t *testing.T) { + tests := []struct { + name string + setup func() commondao.MemberStorage + size int + }{ + { + name: "empty", + setup: func() commondao.MemberStorage { + return storage.NewMemberStorage() + }, + size: 0, + }, + { + name: "member without group", + setup: func() commondao.MemberStorage { + s := storage.NewMemberStorage() + s.Add("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") + return s + }, + size: 1, + }, + { + name: "multiple members without group", + setup: func() commondao.MemberStorage { + s := storage.NewMemberStorage() + s.Add("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") + s.Add("g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq") + return s + }, + size: 2, + }, + { + name: "member in group", + setup: func() commondao.MemberStorage { + s := storage.NewMemberStorage() + group, _ := s.Grouping().Add("foo") + group.Members().Add("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") + return s + }, + size: 1, + }, + { + name: "multiple members in group", + setup: func() commondao.MemberStorage { + s := storage.NewMemberStorage() + group, _ := s.Grouping().Add("foo") + group.Members().Add("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") + group.Members().Add("g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq") + return s + }, + size: 2, + }, + { + name: "multiple members in different groups", + setup: func() commondao.MemberStorage { + s := storage.NewMemberStorage() + group, _ := s.Grouping().Add("foo") + group.Members().Add("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") + group, _ = s.Grouping().Add("bar") + group.Members().Add("g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq") + return s + }, + size: 2, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Arrange + s := tt.setup() + + // Act + size := s.Size() + + // Assert + urequire.Equal(t, tt.size, size) + }) + } +} + +func TestMemberStorageIterateByOffset(t *testing.T) { + tests := []struct { + name string + setup func() commondao.MemberStorage + members []address + }{ + { + name: "empty", + setup: func() commondao.MemberStorage { + return storage.NewMemberStorage() + }, + }, + { + name: "without group", + setup: func() commondao.MemberStorage { + s := storage.NewMemberStorage() + s.Add("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") + s.Add("g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq") + return s + }, + members: []address{ + "g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq", + "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + }, + }, + { + name: "single group", + setup: func() commondao.MemberStorage { + s := storage.NewMemberStorage() + group, _ := s.Grouping().Add("foo") + group.Members().Add("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") + group.Members().Add("g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq") + return s + }, + members: []address{ + "g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq", + "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + }, + }, + { + name: "multiple groups", + setup: func() commondao.MemberStorage { + s := storage.NewMemberStorage() + group, _ := s.Grouping().Add("foo") + group.Members().Add("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") + group, _ = s.Grouping().Add("bar") + group.Members().Add("g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq") + return s + }, + members: []address{ + "g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq", + "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Arrange + var i int + s := tt.setup() + members := make([]address, s.Size()) + + // Act + s.IterateByOffset(0, s.Size(), func(addr address) bool { + members[i] = addr + i++ + return false + }) + + // Assert + urequire.Equal(t, len(tt.members), len(members), "expect iterated members to match") + + for i, member := range members { + urequire.Equal(t, tt.members[i], member, "expect member to match") + } + }) + } +} diff --git a/examples/gno.land/r/gnoland/boards2/v1/board.gno b/examples/gno.land/r/gnoland/boards2/v1/board.gno deleted file mode 100644 index 41df34c3b16..00000000000 --- a/examples/gno.land/r/gnoland/boards2/v1/board.gno +++ /dev/null @@ -1,241 +0,0 @@ -package boards2 - -import ( - "strconv" - "strings" - "time" - - "gno.land/p/jeronimoalbi/pager" - "gno.land/p/moul/md" - "gno.land/p/nt/avl" - "gno.land/p/nt/commondao" - "gno.land/p/nt/seqid" -) - -type ( - // PostIterFn defines a function type to iterate posts. - PostIterFn func(*Post) bool - - // BoardID defines a type for board identifiers. - BoardID uint64 -) - -// String returns the ID as a string. -func (id BoardID) String() string { - return strconv.Itoa(int(id)) -} - -// Key returns the ID as a string which can be used to index by ID. -func (id BoardID) Key() string { - return seqid.ID(id).String() -} - -// Board defines a type for boards. -type Board struct { - ID BoardID - Name string - Aliases []string - Creator address - Readonly bool - - perms Permissions - postsCtr uint64 // Increments Post.ID - threads avl.Tree // Post.ID -> *Post - createdAt time.Time -} - -func newBoard(id BoardID, name string, creator address, p Permissions) *Board { - return &Board{ - ID: id, - Name: name, - Creator: creator, - perms: p, - threads: avl.Tree{}, - createdAt: time.Now(), - } -} - -// CreatedAt returns the time when board was created. -func (board *Board) CreatedAt() time.Time { - return board.createdAt -} - -// MembersCount returns the total number of board members. -func (board *Board) MembersCount() int { - return board.perms.UsersCount() -} - -// IterateMembers iterates board members. -func (board *Board) IterateMembers(start, count int, fn func(address, []Role)) { - board.perms.IterateUsers(start, count, func(u User) bool { - fn(u.Address, u.Roles) - return false - }) -} - -// ThreadsCount returns the total number of board threads. -func (board *Board) ThreadsCount() int { - return board.threads.Size() -} - -// IterateThreads iterates board threads. -func (board *Board) IterateThreads(start, count int, fn PostIterFn) bool { - return board.threads.IterateByOffset(start, count, func(_ string, v any) bool { - p := v.(*Post) - return fn(p) - }) -} - -// ReverseIterateThreads iterates board threads in reverse order. -func (board *Board) ReverseIterateThreads(start, count int, fn PostIterFn) bool { - return board.threads.ReverseIterateByOffset(start, count, func(_ string, v any) bool { - p := v.(*Post) - return fn(p) - }) -} - -// GetThread returns board thread. -func (board *Board) GetThread(threadID PostID) (_ *Post, found bool) { - v, found := board.threads.Get(threadID.Key()) - if !found { - return nil, false - } - return v.(*Post), true -} - -// AddThread adds a new thread to the board. -func (board *Board) AddThread(creator address, title, body string) *Post { - pid := board.generateNextPostID() - thread := newPost(board, pid, pid, creator, title, body) - board.threads.Set(pid.Key(), thread) - return thread -} - -// DeleteThread deletes a thread from the board. -// NOTE: this can be potentially very expensive for threads with many replies. -// TODO: implement optional fast-delete where thread is simply moved. -func (board *Board) DeleteThread(pid PostID) { - _, removed := board.threads.Remove(pid.Key()) - if !removed { - panic("thread does not exist with ID " + pid.String()) - } -} - -// Render renders a board into Markdown. -func (board *Board) Render(path, menu string) string { - var ( - sb strings.Builder - creatorLink = userLink(board.Creator) - date = board.CreatedAt().Format(dateFormat) - ) - - sb.WriteString(md.H1(board.Name)) - sb.WriteString("Board created by " + creatorLink + " on " + date + ", #" + board.ID.String()) - if board.Readonly { - sb.WriteString(" \n_" + md.Bold("Starting new threads and commenting is disabled") + "_") - } - - sb.WriteString("\n") - - // XXX: Menu is rendered by the caller to deal with links and sub-menus - // TODO: We should have the render logic separated from boards so avoid sending menu as argument - if menu != "" { - sb.WriteString("\n" + menu + "\n") - } - - sb.WriteString(md.HorizontalRule()) - - if board.ThreadsCount() == 0 { - sb.WriteString(md.H3("This board doesn't have any threads")) - if !board.Readonly { - startConversationLink := md.Link("start a new conversation", makeCreateThreadURI(board)) - sb.WriteString("Do you want to " + startConversationLink + " in this board ?") - } - return sb.String() - } - - p, err := pager.New(path, board.ThreadsCount(), pager.WithPageSize(pageSizeDefault)) - if err != nil { - panic(err) - } - - render := func(thread *Post) bool { - if !thread.Hidden { - sb.WriteString(thread.RenderSummary() + "\n") - } - return false - } - - sb.WriteString("Sort by: ") - r := parseRealmPath(path) - if r.Query.Get("order") == "desc" { - r.Query.Set("order", "asc") - sb.WriteString(md.Link("newest first", r.String()) + "\n\n") - board.ReverseIterateThreads(p.Offset(), p.PageSize(), render) - } else { - r.Query.Set("order", "desc") - sb.WriteString(md.Link("oldest first", r.String()) + "\n\n") - board.IterateThreads(p.Offset(), p.PageSize(), render) - } - - if p.HasPages() { - sb.WriteString(md.HorizontalRule()) - sb.WriteString(pager.Picker(p)) - } - - return sb.String() -} - -func (board *Board) generateNextPostID() PostID { - board.postsCtr++ - return PostID(board.postsCtr) -} - -func createBasicBoardPermissions(owner address) *BasicPermissions { - dao := commondao.New(commondao.WithMember(owner)) - perms := NewBasicPermissions(dao) - perms.SetSuperRole(RoleOwner) - perms.AddRole( - RoleAdmin, - PermissionBoardRename, - PermissionBoardFlaggingUpdate, - PermissionMemberInvite, - PermissionMemberInviteRevoke, - PermissionMemberRemove, - PermissionThreadCreate, - PermissionThreadEdit, - PermissionThreadDelete, - PermissionThreadRepost, - PermissionThreadFlag, - PermissionThreadFreeze, - PermissionReplyCreate, - PermissionReplyDelete, - PermissionReplyFlag, - PermissionReplyFreeze, - PermissionRoleChange, - PermissionUserBan, - PermissionUserUnban, - ) - perms.AddRole( - RoleModerator, - PermissionThreadCreate, - PermissionThreadEdit, - PermissionThreadRepost, - PermissionThreadFlag, - PermissionReplyCreate, - PermissionReplyFlag, - PermissionUserBan, - PermissionUserUnban, - ) - perms.AddRole( - RoleGuest, - PermissionThreadCreate, - PermissionThreadRepost, - PermissionReplyCreate, - ) - perms.SetUserRoles(cross, owner, RoleOwner) - perms.ValidateFunc(PermissionBoardRename, validateBoardRename) - perms.ValidateFunc(PermissionMemberInvite, validateMemberInvite) - perms.ValidateFunc(PermissionRoleChange, validateRoleChange) - return perms -} diff --git a/examples/gno.land/r/gnoland/boards2/v1/board_test.gno b/examples/gno.land/r/gnoland/boards2/v1/board_test.gno deleted file mode 100644 index 4e91ec92b65..00000000000 --- a/examples/gno.land/r/gnoland/boards2/v1/board_test.gno +++ /dev/null @@ -1,57 +0,0 @@ -package boards2 - -import ( - "strings" - "testing" - - "gno.land/p/nt/testutils" - "gno.land/p/nt/uassert" -) - -func TestBoardURL(t *testing.T) { - pkgPath := strings.TrimPrefix(string(gRealmLink), "gno.land") - name := "foobar_test_get_url123" - want := pkgPath + ":" + name - - addr := testutils.TestAddress("creator") - perms := createBasicBoardPermissions(addr) - board := newBoard(1, name, addr, perms) - got := makeBoardURI(board) - uassert.Equal(t, want, got) -} - -func TestBoardGetThread(t *testing.T) { - addr := testutils.TestAddress("creator") - perms := createBasicBoardPermissions(addr) - b := newBoard(1, "test123", addr, perms) - - _, ok := b.GetThread(12345) - uassert.False(t, ok) - - post := b.AddThread(addr, "foo", "bar") - _, ok = b.GetThread(post.ID) - uassert.True(t, ok) -} - -func TestBoardDeleteThread(t *testing.T) { - addr := testutils.TestAddress("creator") - perms := createBasicBoardPermissions(addr) - b := newBoard(1, "test123", addr, perms) - - post := b.AddThread(addr, "foo", "bar") - b.DeleteThread(post.ID) - - _, ok := b.GetThread(post.ID) - uassert.False(t, ok) -} - -func TestBoardGetPostFormURL(t *testing.T) { - bid := BoardID(386) - addr := testutils.TestAddress("creator") - perms := createBasicBoardPermissions(addr) - b := newBoard(bid, "foo1234", addr, perms) - expect := gRealmLink.Call("CreateThread", "boardID", bid.String(), "title", "", "body", "") - - got := makeCreateThreadURI(b) - uassert.Equal(t, expect, got) -} diff --git a/examples/gno.land/r/gnoland/boards2/v1/boards.gno b/examples/gno.land/r/gnoland/boards2/v1/boards.gno index 74f3a5d63b4..00d03330bfc 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/boards.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/boards.gno @@ -2,8 +2,8 @@ package boards2 import ( "chain/runtime" - "strings" + "gno.land/p/gnoland/boards" "gno.land/p/moul/realmpath" "gno.land/p/moul/txlink" "gno.land/p/nt/avl" @@ -11,14 +11,10 @@ import ( // TODO: Refactor globals in favor of a cleaner pattern var ( - gPerms Permissions gRealmLink txlink.Realm gNotice string gHelp string - gLastReservedID BoardID - gBoardsByID avl.Tree // string(id) -> *Board - gListedBoardsByID avl.Tree // string(id) -> *Board - gBoardsByName avl.Tree // string(lowercase name) -> *Board + gListedBoardsByID avl.Tree // string(id) -> *boards.Board gInviteRequests avl.Tree // string(board id) -> *avl.Tree(address -> time.Time) gBannedUsers avl.Tree // string(board id) -> *avl.Tree(address -> time.Time) gLocked struct { @@ -27,44 +23,37 @@ var ( } ) -func init() { - // Save current realm path so it's available during render calls - gRealmLink = txlink.Realm(runtime.CurrentRealm().PkgPath()) - - // Initialize default realm permissions - gPerms = initRealmPermissions( +var ( + gBoards = boards.NewStorage() + gBoardsSequence = boards.NewIdentifierGenerator() + gPerms = initRealmPermissions( "g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq", // @devx "g1manfred47kzduec920z88wfr64ylksmdcedlf5", // @moul ) +) + +func init() { + // Save current realm path so it's available during render calls + gRealmLink = txlink.Realm(runtime.CurrentRealm().PkgPath()) } // initRealmPermissions returns the default realm permissions. -func initRealmPermissions(owners ...address) *BasicPermissions { - perms := createBasicPermissions(owners...) +func initRealmPermissions(owners ...address) boards.Permissions { + perms := NewBasicPermissions() + perms.SetSuperRole(RoleOwner) + perms.AddRole(RoleAdmin, PermissionBoardCreate) + for _, owner := range owners { + perms.SetUserRoles(cross, owner, RoleOwner) + } + perms.ValidateFunc(PermissionBoardCreate, validateBoardCreate) perms.ValidateFunc(PermissionMemberInvite, validateMemberInvite) perms.ValidateFunc(PermissionRoleChange, validateRoleChange) return perms } -// reserveBoardID returns a new board ID which won't be used by another board. -// Reserving IDs is necessary because boards can be created asynchronically. -func reserveBoardID() BoardID { - gLastReservedID++ - return gLastReservedID -} - -// getBoard returns a board for a specific ID. -func getBoard(id BoardID) (_ *Board, found bool) { - v, exists := gBoardsByID.Get(id.Key()) - if !exists { - return nil, false - } - return v.(*Board), true -} - // getInvitesRequests returns invite requests for a board. -func getInviteRequests(boardID BoardID) (_ *avl.Tree, found bool) { +func getInviteRequests(boardID boards.ID) (_ *avl.Tree, found bool) { v, exists := gInviteRequests.Get(boardID.Key()) if !exists { return nil, false @@ -73,7 +62,7 @@ func getInviteRequests(boardID BoardID) (_ *avl.Tree, found bool) { } // getBannedUsers returns banned users within a board. -func getBannedUsers(boardID BoardID) (_ *avl.Tree, found bool) { +func getBannedUsers(boardID boards.ID) (_ *avl.Tree, found bool) { v, exists := gBannedUsers.Get(boardID.Key()) if !exists { return nil, false @@ -81,18 +70,9 @@ func getBannedUsers(boardID BoardID) (_ *avl.Tree, found bool) { return v.(*avl.Tree), true } -// getBoardByName returns a board that matches a name. -func getBoardByName(name string) (_ *Board, found bool) { - name = strings.ToLower(name) - if v, found := gBoardsByName.Get(name); found { - return v.(*Board), true - } - return nil, false -} - // mustGetBoardByName returns a board or panics when it's not found. -func mustGetBoardByName(name string) *Board { - board, found := getBoardByName(name) +func mustGetBoardByName(name string) *boards.Board { + board, found := gBoards.GetByName(name) if !found { panic("board does not exist with name: " + name) } @@ -100,8 +80,8 @@ func mustGetBoardByName(name string) *Board { } // mustGetBoard returns a board or panics when it's not found. -func mustGetBoard(id BoardID) *Board { - board, found := getBoard(id) +func mustGetBoard(id boards.ID) *boards.Board { + board, found := gBoards.Get(id) if !found { panic("board does not exist with ID: " + id.String()) } @@ -109,8 +89,8 @@ func mustGetBoard(id BoardID) *Board { } // mustGetThread returns a thread or panics when it's not found. -func mustGetThread(board *Board, threadID PostID) *Post { - thread, found := board.GetThread(threadID) +func mustGetThread(board *boards.Board, threadID boards.ID) *boards.Post { + thread, found := board.Threads.Get(threadID) if !found { panic("thread does not exist with ID: " + threadID.String()) } @@ -118,18 +98,18 @@ func mustGetThread(board *Board, threadID PostID) *Post { } // mustGetReply returns a reply or panics when it's not found. -func mustGetReply(thread *Post, replyID PostID) *Post { - reply, found := thread.GetReply(replyID) +func mustGetReply(thread *boards.Post, replyID boards.ID) *boards.Post { + reply, found := thread.Replies.Get(replyID) if !found { panic("reply does not exist with ID: " + replyID.String()) } return reply } -func mustGetPermissions(bid BoardID) Permissions { +func mustGetPermissions(bid boards.ID) boards.Permissions { if bid != 0 { board := mustGetBoard(bid) - return board.perms + return board.Permissions } return gPerms } diff --git a/examples/gno.land/r/gnoland/boards2/v1/flag.gno b/examples/gno.land/r/gnoland/boards2/v1/flag.gno index 7815a968e6c..f739c81b115 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/flag.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/flag.gno @@ -3,6 +3,7 @@ package boards2 import ( "strconv" + "gno.land/p/gnoland/boards" "gno.land/p/nt/avl" ) @@ -11,31 +12,27 @@ const DefaultFlaggingThreshold = 1 var gFlaggingThresholds avl.Tree // string(board ID) -> int -type Flaggable interface { - // Flag adds a new flag to an item. - // It returns false when the user flagging already flagged the item. - Flag(user address, reason string) bool - - // FlagsCount returns number of times item was flagged. - FlagsCount() int -} - -// flagItem adds a flag to a flaggable item. -// Returns whether flag count threshold is reached and item can be hidden. +// flagItem adds a flag to a post. +// Returns whether flag count threshold is reached and post can be hidden. // Panics if flag count threshold was already reached. -func flagItem(item Flaggable, user address, reason string, threshold int) bool { - if item.FlagsCount() >= threshold { - panic("item flag count threshold exceeded: " + strconv.Itoa(threshold)) +func flagItem(post *boards.Post, user address, reason string, threshold int) bool { + if post.Flags.Size() >= threshold { + panic("flag count threshold exceeded: " + strconv.Itoa(threshold)) } - if !item.Flag(user, reason) { - panic("item has been already flagged by " + user.String()) + if post.Flags.Exists(user) { + panic("post has been already flagged by " + user.String()) } - return item.FlagsCount() == threshold + post.Flags.Add(boards.Flag{ + User: user, + Reason: reason, + }) + + return post.Flags.Size() == threshold } -func getFlaggingThreshold(bid BoardID) int { +func getFlaggingThreshold(bid boards.ID) int { if v, ok := gFlaggingThresholds.Get(bid.String()); ok { return v.(int) } diff --git a/examples/gno.land/r/gnoland/boards2/v1/format.gno b/examples/gno.land/r/gnoland/boards2/v1/format.gno index 75ea632e54a..fe1565c4b89 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/format.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/format.gno @@ -9,6 +9,8 @@ import ( "gno.land/r/sys/users" ) +const dateFormat = "2006-01-02 3:04pm MST" + func padLeft(s string, length int) string { if len(s) >= length { return s diff --git a/examples/gno.land/r/gnoland/boards2/v1/permissions.gno b/examples/gno.land/r/gnoland/boards2/v1/permissions.gno index 29f4a3c8eab..78bad254ca7 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/permissions.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/permissions.gno @@ -1,87 +1,85 @@ package boards2 -const ( - PermissionBoardCreate Permission = "board:create" - PermissionBoardFlaggingUpdate = "board:flagging-update" - PermissionBoardFreeze = "board:freeze" - PermissionBoardRename = "board:rename" - PermissionMemberInvite = "member:invite" - PermissionMemberInviteRevoke = "member:invite-remove" - PermissionMemberRemove = "member:remove" - PermissionPermissionsUpdate = "permissions:update" - PermissionRealmHelp = "realm:help" - PermissionRealmLock = "realm:lock" - PermissionRealmNotice = "realm:notice" - PermissionReplyCreate = "reply:create" - PermissionReplyDelete = "reply:delete" - PermissionReplyFlag = "reply:flag" - PermissionReplyFreeze = "reply:freeze" - PermissionRoleChange = "role:change" - PermissionThreadCreate = "thread:create" - PermissionThreadDelete = "thread:delete" - PermissionThreadEdit = "thread:edit" - PermissionThreadFlag = "thread:flag" - PermissionThreadFreeze = "thread:freeze" - PermissionThreadRepost = "thread:repost" - PermissionUserBan = "user:ban" - PermissionUserUnban = "user:unban" +import ( + "gno.land/p/gnoland/boards" ) const ( - RoleGuest Role = "" - RoleOwner = "owner" - RoleAdmin = "admin" - RoleModerator = "moderator" + RoleOwner boards.Role = "owner" + RoleAdmin = "admin" + RoleModerator = "moderator" + RoleGuest = "guest" ) -type ( - // Permission defines the type for permissions. - Permission string - - // Role defines the type for user roles. - Role string - - // Args is a list of generic arguments. - Args []interface{} - - // User contains user info. - User struct { - Address address - Roles []Role - } - - // UsersIterFn defines a function type to iterate users. - UsersIterFn func(User) bool - - // Permissions define an interface to for permissioned execution. - // TODO: Add crossing support to Permissions - Permissions interface { - // HasRole checks if a user has a specific role assigned. - HasRole(address, Role) bool - - // HasPermission checks if a user has a specific permission. - HasPermission(address, Permission) bool - - // WithPermission calls a callback when a user has a specific permission. - // It panics on error. - WithPermission(realm, address, Permission, Args, func(realm)) - - // SetUserRoles adds a new user when it doesn't exist and sets its roles. - // Method can also be called to change the roles of an existing user. - // It panics on error. - SetUserRoles(realm, address, ...Role) - - // RemoveUser removes a user from the permissioner. - // It panics on error. - RemoveUser(realm, address) (removed bool) - - // HasUser checks if a user exists. - HasUser(address) bool - - // UsersCount returns the total number of users the permissioner contains. - UsersCount() int - - // IterateUsers iterates permissions' users. - IterateUsers(start, count int, fn UsersIterFn) bool - } +const ( + PermissionBoardCreate boards.Permission = "board:create" + PermissionBoardFlaggingUpdate = "board:flagging-update" + PermissionBoardFreeze = "board:freeze" + PermissionBoardRename = "board:rename" + PermissionMemberInvite = "member:invite" + PermissionMemberInviteRevoke = "member:invite-remove" + PermissionMemberRemove = "member:remove" + PermissionPermissionsUpdate = "permissions:update" + PermissionRealmHelp = "realm:help" + PermissionRealmLock = "realm:lock" + PermissionRealmNotice = "realm:notice" + PermissionReplyCreate = "reply:create" + PermissionReplyDelete = "reply:delete" + PermissionReplyFlag = "reply:flag" + PermissionRoleChange = "role:change" + PermissionThreadCreate = "thread:create" + PermissionThreadDelete = "thread:delete" + PermissionThreadEdit = "thread:edit" + PermissionThreadFlag = "thread:flag" + PermissionThreadFreeze = "thread:freeze" + PermissionThreadRepost = "thread:repost" + PermissionUserBan = "user:ban" + PermissionUserUnban = "user:unban" ) + +func createBasicBoardPermissions(owner address) *BasicPermissions { + perms := NewBasicPermissions() + perms.SetSuperRole(RoleOwner) + perms.AddRole( + RoleAdmin, + PermissionBoardRename, + PermissionBoardFlaggingUpdate, + PermissionMemberInvite, + PermissionMemberInviteRevoke, + PermissionMemberRemove, + PermissionThreadCreate, + PermissionThreadEdit, + PermissionThreadDelete, + PermissionThreadRepost, + PermissionThreadFlag, + PermissionThreadFreeze, + PermissionReplyCreate, + PermissionReplyDelete, + PermissionReplyFlag, + PermissionRoleChange, + PermissionUserBan, + PermissionUserUnban, + ) + perms.AddRole( + RoleModerator, + PermissionThreadCreate, + PermissionThreadEdit, + PermissionThreadRepost, + PermissionThreadFlag, + PermissionReplyCreate, + PermissionReplyFlag, + PermissionUserBan, + PermissionUserUnban, + ) + perms.AddRole( + RoleGuest, + PermissionThreadCreate, + PermissionThreadRepost, + PermissionReplyCreate, + ) + perms.SetUserRoles(cross, owner, RoleOwner) + perms.ValidateFunc(PermissionBoardRename, validateBoardRename) + perms.ValidateFunc(PermissionMemberInvite, validateMemberInvite) + perms.ValidateFunc(PermissionRoleChange, validateRoleChange) + return perms +} diff --git a/examples/gno.land/r/gnoland/boards2/v1/permissions_basic.gno b/examples/gno.land/r/gnoland/boards2/v1/permissions_basic.gno index 6f57f644975..0708ad5a655 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/permissions_basic.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/permissions_basic.gno @@ -1,91 +1,144 @@ package boards2 import ( + "gno.land/p/gnoland/boards" "gno.land/p/nt/avl" "gno.land/p/nt/commondao" + "gno.land/p/nt/commondao/exts/storage" ) // ValidatorFunc defines a function type for permissions validators. -type ValidatorFunc func(Permissions, Args) error +type ValidatorFunc func(boards.Permissions, boards.Args) error // BasicPermissions manages users, roles and permissions. type BasicPermissions struct { - superRole Role + superRole boards.Role dao *commondao.CommonDAO - users *avl.Tree // string(address) -> []Role - roles *avl.Tree // string(role) -> []Permission - validators *avl.Tree // string(Permission) -> BasicPermissionValidator + validators *avl.Tree // string(boards.Permission) -> BasicPermissionValidator + public *avl.Tree // string(boards.Permission) -> struct{}{} } // NewBasicPermissions creates a new permissions type. // This type is a default implementation to handle users, roles and permissions. -func NewBasicPermissions(dao *commondao.CommonDAO) *BasicPermissions { - if dao == nil { - panic("basic permissions require a DAO") - } - +// It uses an underlying DAO to manage users and roles. +func NewBasicPermissions() *BasicPermissions { + s := storage.NewMemberStorage() return &BasicPermissions{ - dao: dao, - roles: avl.NewTree(), - users: avl.NewTree(), validators: avl.NewTree(), + public: avl.NewTree(), + dao: commondao.New( + // Use a custom boards member storage + commondao.WithMemberStorage(s), + ), } } +// DAO returns the underlying permissions DAO. +func (bp BasicPermissions) DAO() *commondao.CommonDAO { + return bp.dao +} + // ValidateFunc add a validator function for a permission. -func (bp *BasicPermissions) ValidateFunc(p Permission, fn ValidatorFunc) { +func (bp *BasicPermissions) ValidateFunc(p boards.Permission, fn ValidatorFunc) { bp.validators.Set(string(p), fn) } +// SetPublicPermissions assigns permissions that are available to anyone. +// It removes previous public permissions and assigns the new ones. +// By default there are no public permissions. +func (bp *BasicPermissions) SetPublicPermissions(permissions ...boards.Permission) { + bp.public = avl.NewTree() + for _, p := range permissions { + bp.public.Set(string(p), struct{}{}) + } +} + // SetSuperRole assigns a super role. // A super role is one that have all permissions. // These type of role doesn't need to be mapped to any permission. -func (bp *BasicPermissions) SetSuperRole(r Role) { +func (bp *BasicPermissions) SetSuperRole(r boards.Role) { + if bp.superRole == r { + return + } + + name := string(r) + bp.dao.Members().Grouping().Add(name) bp.superRole = r } // AddRole add a role with one or more assigned permissions. -func (bp *BasicPermissions) AddRole(r Role, p Permission, extra ...Permission) { - bp.roles.Set(string(r), append([]Permission{p}, extra...)) +// If role exists its permissions are overwritten with the new ones. +func (bp *BasicPermissions) AddRole(r boards.Role, p boards.Permission, extra ...boards.Permission) { + // Get member group for the role if it exists or otherwise create a new group + grouping := bp.dao.Members().Grouping() + name := string(r) + group, found := grouping.Get(name) + if !found { + var err error + group, err = grouping.Add(name) + if err != nil { + panic(err) + } + } + + // Save permissions within the member group overwritting any existing permissions + group.SetMeta(append([]boards.Permission{p}, extra...)) } // RoleExists checks if a role exists. -func (bp BasicPermissions) RoleExists(r Role) bool { - return (bp.superRole != "" && r == bp.superRole) || bp.roles.Has(string(r)) +func (bp BasicPermissions) RoleExists(r boards.Role) bool { + return (bp.superRole != "" && r == bp.superRole) || bp.dao.Members().Grouping().Has(string(r)) } // GetUserRoles returns the list of roles assigned to a user. -func (bp BasicPermissions) GetUserRoles(user address) []Role { - v, found := bp.users.Get(user.String()) - if !found { +func (bp BasicPermissions) GetUserRoles(user address) []boards.Role { + groups := storage.GetMemberGroups(bp.dao.Members(), user) + if groups == nil { return nil } - return v.([]Role) + + roles := make([]boards.Role, len(groups)) + for i, name := range groups { + roles[i] = boards.Role(name) + } + return roles } // HasRole checks if a user has a specific role assigned. -func (bp BasicPermissions) HasRole(user address, r Role) bool { - for _, role := range bp.GetUserRoles(user) { - if role == r { - return true - } +func (bp BasicPermissions) HasRole(user address, r boards.Role) bool { + name := string(r) + group, found := bp.dao.Members().Grouping().Get(name) + if !found { + return false } - return false + return group.Members().Has(user) } // HasPermission checks if a user has a specific permission. -func (bp BasicPermissions) HasPermission(user address, perm Permission) bool { - for _, r := range bp.GetUserRoles(user) { - if bp.superRole == r { +func (bp BasicPermissions) HasPermission(user address, perm boards.Permission) bool { + if bp.public.Has(string(perm)) { + return true + } + + groups := storage.GetMemberGroups(bp.dao.Members(), user) + if groups == nil { + return false + } + + grouping := bp.dao.Members().Grouping() + for _, name := range groups { + role := boards.Role(name) + if bp.superRole == role { return true } - v, found := bp.roles.Get(string(r)) + group, found := grouping.Get(name) if !found { continue } - for _, p := range v.([]Permission) { + meta := group.GetMeta() + for _, p := range meta.([]boards.Permission) { if p == perm { return true } @@ -97,25 +150,58 @@ func (bp BasicPermissions) HasPermission(user address, perm Permission) bool { // SetUserRoles adds a new user when it doesn't exist and sets its roles. // Method can also be called to change the roles of an existing user. // All user's roles can be removed by calling this method without roles. -func (bp *BasicPermissions) SetUserRoles(_ realm, user address, roles ...Role) { - if !bp.HasUser(user) { +func (bp *BasicPermissions) SetUserRoles(_ realm, user address, roles ...boards.Role) { + groups := storage.GetMemberGroups(bp.dao.Members(), user) + isGuest := len(roles) == 0 + + // If user has roles remove it from the groups its currently assigned + grouping := bp.dao.Members().Grouping() + if isGuest && groups != nil { + for _, name := range groups { + group, found := grouping.Get(name) + if !found { + continue + } + + group.Members().Remove(user) + } + } + + // Add user to the storage as guest when no roles are assigned + if isGuest { bp.dao.Members().Add(user) + return } + // Add user to role groups for _, r := range roles { - if !bp.RoleExists(r) { - panic("invalid role: " + string(r)) + name := string(r) + group, found := grouping.Get(name) + if !found { + panic("invalid role: " + name) } - } - bp.users.Set(user.String(), append([]Role(nil), roles...)) + group.Members().Add(user) + } } // RemoveUser removes a user from permissions. func (bp *BasicPermissions) RemoveUser(_ realm, user address) bool { - _, removed := bp.users.Remove(user.String()) - bp.dao.Members().Remove(user) - return removed + groups := storage.GetMemberGroups(bp.dao.Members(), user) + if groups == nil { + return bp.dao.Members().Remove(user) + } + + grouping := bp.dao.Members().Grouping() + for _, name := range groups { + group, found := grouping.Get(name) + if !found { + continue + } + + group.Members().Remove(user) + } + return true } // HasUser checks if a user exists. @@ -129,14 +215,18 @@ func (bp BasicPermissions) UsersCount() int { } // IterateUsers iterates permissions' users. -func (bp BasicPermissions) IterateUsers(start, count int, fn UsersIterFn) (stopped bool) { +func (bp BasicPermissions) IterateUsers(start, count int, fn boards.UsersIterFn) (stopped bool) { bp.dao.Members().IterateByOffset(start, count, func(addr address) bool { - roles := bp.GetUserRoles(addr) - stopped = fn(User{ - Address: addr, - Roles: roles, - }) - return stopped + user := boards.User{Address: addr} + groups := storage.GetMemberGroups(bp.dao.Members(), addr) + if groups != nil { + user.Roles = make([]boards.Role, len(groups)) + for i, name := range groups { + user.Roles[i] = boards.Role(name) + } + } + + return fn(user) }) return } @@ -144,8 +234,8 @@ func (bp BasicPermissions) IterateUsers(start, count int, fn UsersIterFn) (stopp // WithPermission calls a callback when a user has a specific permission. // It panics on error or when a handler panics. // Callbacks are by default called when there is no handle registered for the permission. -func (bp *BasicPermissions) WithPermission(_ realm, user address, p Permission, args Args, cb func(realm)) { - if !bp.HasPermission(user, p) || !bp.dao.Members().Has(user) { +func (bp *BasicPermissions) WithPermission(_ realm, user address, p boards.Permission, args boards.Args, cb func(realm)) { + if !bp.HasPermission(user, p) { panic("unauthorized") } @@ -160,13 +250,3 @@ func (bp *BasicPermissions) WithPermission(_ realm, user address, p Permission, cb(cross) } - -func createBasicPermissions(owners ...address) *BasicPermissions { - perms := NewBasicPermissions(commondao.New()) - perms.SetSuperRole(RoleOwner) - perms.AddRole(RoleAdmin, PermissionBoardCreate) - for _, owner := range owners { - perms.SetUserRoles(cross, owner, RoleOwner) - } - return perms -} diff --git a/examples/gno.land/r/gnoland/boards2/v1/permissions_basic_test.gno b/examples/gno.land/r/gnoland/boards2/v1/permissions_basic_test.gno index d2d16d452af..56bc189d5a6 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/permissions_basic_test.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/permissions_basic_test.gno @@ -3,19 +3,19 @@ package boards2 import ( "testing" - "gno.land/p/nt/commondao" + "gno.land/p/gnoland/boards" "gno.land/p/nt/uassert" "gno.land/p/nt/urequire" ) -var _ Permissions = (*BasicPermissions)(nil) +var _ boards.Permissions = (*BasicPermissions)(nil) func TestBasicPermissionsWithPermission(t *testing.T) { cases := []struct { name string user address - permission Permission - args Args + permission boards.Permission + args boards.Args setup func() *BasicPermissions err string called bool @@ -25,7 +25,7 @@ func TestBasicPermissionsWithPermission(t *testing.T) { user: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", permission: "bar", setup: func() *BasicPermissions { - perms := NewBasicPermissions(commondao.New()) + perms := NewBasicPermissions() perms.AddRole("foo", "bar") perms.SetUserRoles(cross, "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", "foo") return perms @@ -36,9 +36,9 @@ func TestBasicPermissionsWithPermission(t *testing.T) { name: "ok with arguments", user: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", permission: "bar", - args: Args{"a", "b"}, + args: boards.Args{"a", "b"}, setup: func() *BasicPermissions { - perms := NewBasicPermissions(commondao.New()) + perms := NewBasicPermissions() perms.AddRole("foo", "bar") perms.SetUserRoles(cross, "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", "foo") return perms @@ -50,7 +50,7 @@ func TestBasicPermissionsWithPermission(t *testing.T) { user: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", permission: "bar", setup: func() *BasicPermissions { - perms := NewBasicPermissions(commondao.New()) + perms := NewBasicPermissions() perms.AddRole("foo", "bar") perms.SetUserRoles(cross, "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") return perms @@ -62,7 +62,7 @@ func TestBasicPermissionsWithPermission(t *testing.T) { user: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", permission: "bar", setup: func() *BasicPermissions { - return NewBasicPermissions(commondao.New()) + return NewBasicPermissions() }, err: "unauthorized", }, @@ -91,6 +91,29 @@ func TestBasicPermissionsWithPermission(t *testing.T) { } } +func TestBasicPermissionsSetPublicPermissions(t *testing.T) { + user := address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") + perms := NewBasicPermissions() + + // Add a new role with permissions + perms.AddRole("adminRole", "fooPerm", "barPerm", "bazPerm") + urequire.False(t, perms.HasPermission(user, "fooPerm")) + urequire.False(t, perms.HasPermission(user, "barPerm")) + urequire.False(t, perms.HasPermission(user, "bazPerm")) + + // Assign a couple of public permissions + perms.SetPublicPermissions("fooPerm", "bazPerm") + urequire.True(t, perms.HasPermission(user, "fooPerm")) + urequire.False(t, perms.HasPermission(user, "barPerm")) + urequire.True(t, perms.HasPermission(user, "bazPerm")) + + // Clear all public permissions + perms.SetPublicPermissions() + urequire.False(t, perms.HasPermission(user, "fooPerm")) + urequire.False(t, perms.HasPermission(user, "barPerm")) + urequire.False(t, perms.HasPermission(user, "bazPerm")) +} + func TestBasicPermissionsGetUserRoles(t *testing.T) { cases := []struct { name string @@ -103,7 +126,7 @@ func TestBasicPermissionsGetUserRoles(t *testing.T) { user: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", roles: []string{"admin"}, setup: func() *BasicPermissions { - perms := NewBasicPermissions(commondao.New()) + perms := NewBasicPermissions() perms.AddRole("admin", "x") perms.SetUserRoles(cross, "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", "admin") return perms @@ -112,9 +135,9 @@ func TestBasicPermissionsGetUserRoles(t *testing.T) { { name: "multiple roles", user: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", - roles: []string{"admin", "foo", "bar"}, + roles: []string{"admin", "bar", "foo"}, setup: func() *BasicPermissions { - perms := NewBasicPermissions(commondao.New()) + perms := NewBasicPermissions() perms.AddRole("admin", "x") perms.AddRole("foo", "x") perms.AddRole("bar", "x") @@ -126,7 +149,7 @@ func TestBasicPermissionsGetUserRoles(t *testing.T) { name: "without roles", user: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", setup: func() *BasicPermissions { - perms := NewBasicPermissions(commondao.New()) + perms := NewBasicPermissions() perms.SetUserRoles(cross, "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") return perms }, @@ -135,7 +158,7 @@ func TestBasicPermissionsGetUserRoles(t *testing.T) { name: "not a user", user: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", setup: func() *BasicPermissions { - return NewBasicPermissions(commondao.New()) + return NewBasicPermissions() }, }, { @@ -143,7 +166,7 @@ func TestBasicPermissionsGetUserRoles(t *testing.T) { user: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", roles: []string{"admin"}, setup: func() *BasicPermissions { - perms := NewBasicPermissions(commondao.New()) + perms := NewBasicPermissions() perms.AddRole("admin", "x") perms.AddRole("bar", "x") perms.SetUserRoles(cross, "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", "admin") @@ -171,7 +194,7 @@ func TestBasicPermissionsHasRole(t *testing.T) { cases := []struct { name string user address - role Role + role boards.Role setup func() *BasicPermissions want bool }{ @@ -180,7 +203,7 @@ func TestBasicPermissionsHasRole(t *testing.T) { user: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", role: "admin", setup: func() *BasicPermissions { - perms := NewBasicPermissions(commondao.New()) + perms := NewBasicPermissions() perms.AddRole("admin", "x") perms.SetUserRoles(cross, "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", "admin") return perms @@ -192,7 +215,7 @@ func TestBasicPermissionsHasRole(t *testing.T) { user: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", role: "foo", setup: func() *BasicPermissions { - perms := NewBasicPermissions(commondao.New()) + perms := NewBasicPermissions() perms.AddRole("admin", "x") perms.AddRole("foo", "x") perms.SetUserRoles(cross, "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", "admin", "foo") @@ -204,7 +227,7 @@ func TestBasicPermissionsHasRole(t *testing.T) { name: "user without roles", user: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", setup: func() *BasicPermissions { - perms := NewBasicPermissions(commondao.New()) + perms := NewBasicPermissions() perms.SetUserRoles(cross, "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") return perms }, @@ -214,7 +237,7 @@ func TestBasicPermissionsHasRole(t *testing.T) { user: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", role: "bar", setup: func() *BasicPermissions { - perms := NewBasicPermissions(commondao.New()) + perms := NewBasicPermissions() perms.AddRole("foo", "x") perms.SetUserRoles(cross, "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", "foo") return perms @@ -235,7 +258,7 @@ func TestBasicPermissionsHasPermission(t *testing.T) { cases := []struct { name string user address - permission Permission + permission boards.Permission setup func() *BasicPermissions want bool }{ @@ -244,7 +267,7 @@ func TestBasicPermissionsHasPermission(t *testing.T) { user: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", permission: "bar", setup: func() *BasicPermissions { - perms := NewBasicPermissions(commondao.New()) + perms := NewBasicPermissions() perms.AddRole("foo", "bar") perms.SetUserRoles(cross, "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", "foo") return perms @@ -256,7 +279,7 @@ func TestBasicPermissionsHasPermission(t *testing.T) { user: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", permission: "bar", setup: func() *BasicPermissions { - perms := NewBasicPermissions(commondao.New()) + perms := NewBasicPermissions() perms.AddRole("foo", "bar") perms.SetUserRoles(cross, "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", "foo") perms.SetUserRoles(cross, "g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn", "foo") @@ -269,7 +292,7 @@ func TestBasicPermissionsHasPermission(t *testing.T) { user: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", permission: "other", setup: func() *BasicPermissions { - perms := NewBasicPermissions(commondao.New()) + perms := NewBasicPermissions() perms.AddRole("foo", "bar") perms.AddRole("baz", "other") perms.SetUserRoles(cross, "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", "foo", "baz") @@ -282,7 +305,7 @@ func TestBasicPermissionsHasPermission(t *testing.T) { user: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", permission: "other", setup: func() *BasicPermissions { - perms := NewBasicPermissions(commondao.New()) + perms := NewBasicPermissions() perms.AddRole("foo", "bar") perms.SetUserRoles(cross, "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", "foo") return perms @@ -294,7 +317,7 @@ func TestBasicPermissionsHasPermission(t *testing.T) { t.Run(tc.name, func(t *testing.T) { perms := tc.setup() got := perms.HasPermission(tc.user, tc.permission) - uassert.Equal(t, got, tc.want) + uassert.Equal(t, tc.want, got) }) } } @@ -303,16 +326,16 @@ func TestBasicPermissionsSetUserRoles(t *testing.T) { cases := []struct { name string user address - roles []Role + roles []boards.Role setup func() *BasicPermissions err string }{ { name: "add user", user: address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5"), - roles: []Role{"a"}, + roles: []boards.Role{"a"}, setup: func() *BasicPermissions { - perms := NewBasicPermissions(commondao.New()) + perms := NewBasicPermissions() perms.AddRole("a", "permission1") return perms }, @@ -320,9 +343,9 @@ func TestBasicPermissionsSetUserRoles(t *testing.T) { { name: "add user with multiple roles", user: address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5"), - roles: []Role{"a", "b"}, + roles: []boards.Role{"a", "b"}, setup: func() *BasicPermissions { - perms := NewBasicPermissions(commondao.New()) + perms := NewBasicPermissions() perms.AddRole("a", "permission1") perms.AddRole("b", "permission2") return perms @@ -331,9 +354,9 @@ func TestBasicPermissionsSetUserRoles(t *testing.T) { { name: "add when other users exists", user: address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5"), - roles: []Role{"a"}, + roles: []boards.Role{"a"}, setup: func() *BasicPermissions { - perms := NewBasicPermissions(commondao.New()) + perms := NewBasicPermissions() perms.AddRole("a", "permission1") perms.SetUserRoles(cross, "g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn", "a") perms.SetUserRoles(cross, "g1w4ek2u3jta047h6lta047h6lta047h6l9huexc") @@ -343,9 +366,9 @@ func TestBasicPermissionsSetUserRoles(t *testing.T) { { name: "update user roles", user: address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5"), - roles: []Role{"a", "b"}, + roles: []boards.Role{"a", "b"}, setup: func() *BasicPermissions { - perms := NewBasicPermissions(commondao.New()) + perms := NewBasicPermissions() perms.AddRole("a", "permission1") perms.AddRole("b", "permission2") perms.SetUserRoles(cross, "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", "a") @@ -355,9 +378,9 @@ func TestBasicPermissionsSetUserRoles(t *testing.T) { { name: "clear user roles", user: address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5"), - roles: []Role{}, + roles: []boards.Role{}, setup: func() *BasicPermissions { - perms := NewBasicPermissions(commondao.New()) + perms := NewBasicPermissions() perms.AddRole("a", "permission1") perms.AddRole("b", "permission2") perms.SetUserRoles(cross, "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", "a", "b") @@ -367,9 +390,9 @@ func TestBasicPermissionsSetUserRoles(t *testing.T) { { name: "set invalid role", user: address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5"), - roles: []Role{"a", "foo"}, + roles: []boards.Role{"a", "foo"}, setup: func() *BasicPermissions { - perms := NewBasicPermissions(commondao.New()) + perms := NewBasicPermissions() perms.AddRole("a", "permission1") return perms }, @@ -412,7 +435,7 @@ func TestBasicPermissionsRemoveUser(t *testing.T) { name: "ok", user: address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5"), setup: func() *BasicPermissions { - perms := NewBasicPermissions(commondao.New()) + perms := NewBasicPermissions() perms.SetUserRoles(cross, "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") return perms }, @@ -422,7 +445,7 @@ func TestBasicPermissionsRemoveUser(t *testing.T) { name: "user not found", user: address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5"), setup: func() *BasicPermissions { - return NewBasicPermissions(commondao.New()) + return NewBasicPermissions() }, }, } @@ -437,22 +460,22 @@ func TestBasicPermissionsRemoveUser(t *testing.T) { } func TestBasicPermissionsIterateUsers(t *testing.T) { - users := []User{ + users := []boards.User{ { Address: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", - Roles: []Role{"foo"}, + Roles: []boards.Role{"foo"}, }, { Address: "g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj", - Roles: []Role{"foo", "bar"}, + Roles: []boards.Role{"bar", "foo"}, }, { Address: "g1vh7krmmzfua5xjmkatvmx09z37w34lsvd2mxa5", - Roles: []Role{"bar"}, + Roles: []boards.Role{"bar"}, }, } - perms := NewBasicPermissions(commondao.New()) + perms := NewBasicPermissions() perms.AddRole("foo", "perm1") perms.AddRole("bar", "perm2") for _, u := range users { @@ -494,7 +517,7 @@ func TestBasicPermissionsIterateUsers(t *testing.T) { for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { var i int - perms.IterateUsers(0, len(users), func(u User) bool { + perms.IterateUsers(0, len(users), func(u boards.User) bool { urequire.True(t, i < len(users), "expect iterator to respect number of users") uassert.Equal(t, users[i].Address, u.Address) diff --git a/examples/gno.land/r/gnoland/boards2/v1/permissions_validators.gno b/examples/gno.land/r/gnoland/boards2/v1/permissions_validators.gno index 4a7b583517e..8dafc31646b 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/permissions_validators.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/permissions_validators.gno @@ -3,6 +3,8 @@ package boards2 import ( "errors" + "gno.land/p/gnoland/boards" + "gno.land/r/sys/users" ) @@ -13,7 +15,7 @@ import ( // 2. Board name // 3. Board ID // 4. Is board listed -func validateBoardCreate(_ Permissions, args Args) error { +func validateBoardCreate(_ boards.Permissions, args boards.Args) error { caller, ok := args[0].(address) if !ok { return errors.New("expected a valid caller address") @@ -41,7 +43,7 @@ func validateBoardCreate(_ Permissions, args Args) error { // 2. Board ID // 3. Current board name // 4. New board name -func validateBoardRename(_ Permissions, args Args) error { +func validateBoardRename(_ boards.Permissions, args boards.Args) error { caller, ok := args[0].(address) if !ok { return errors.New("expected a valid caller address") @@ -68,7 +70,7 @@ func validateBoardRename(_ Permissions, args Args) error { // 1. Caller address // 2. Board ID // 3. Invites -func validateMemberInvite(perms Permissions, args Args) error { +func validateMemberInvite(perms boards.Permissions, args boards.Args) error { caller, ok := args[0].(address) if !ok { return errors.New("expected a valid caller address") @@ -96,7 +98,7 @@ func validateMemberInvite(perms Permissions, args Args) error { // 2. Board ID // 3. Member address // 4. Role -func validateRoleChange(perms Permissions, args Args) error { +func validateRoleChange(perms boards.Permissions, args boards.Args) error { caller, ok := args[0].(address) if !ok { return errors.New("expected a valid caller address") @@ -105,7 +107,7 @@ func validateRoleChange(perms Permissions, args Args) error { // Owners and Admins can change roles. // Admins should not be able to assign or remove the Owner role from members. if perms.HasRole(caller, RoleAdmin) { - role, ok := args[3].(Role) + role, ok := args[3].(boards.Role) if !ok { return errors.New("expected a valid member role") } diff --git a/examples/gno.land/r/gnoland/boards2/v1/post.gno b/examples/gno.land/r/gnoland/boards2/v1/post.gno deleted file mode 100644 index 33a0aaa4220..00000000000 --- a/examples/gno.land/r/gnoland/boards2/v1/post.gno +++ /dev/null @@ -1,430 +0,0 @@ -package boards2 - -import ( - "errors" - "strconv" - "strings" - "time" - - "gno.land/p/jeronimoalbi/pager" - "gno.land/p/moul/md" - "gno.land/p/nt/avl" -) - -const dateFormat = "2006-01-02 3:04pm MST" - -// PostID defines a type for Post (Threads/Replies) identifiers. -type PostID uint64 - -// String returns the ID as a string. -func (id PostID) String() string { - return strconv.Itoa(int(id)) -} - -// Key returns the ID as a string with 10 characters padded with zeroes. -// This value can be used for indexing by ID. -func (id PostID) Key() string { - return padZero(uint64(id), 10) -} - -// A Post is a "thread" or a "reply" depending on context. -// A thread is a Post of a Board that holds other replies. -type Post struct { - ID PostID - Board *Board - Creator address - Title string - Body string - Hidden bool - Readonly bool - ThreadID PostID // Original Post.ID - ParentID PostID // Parent Post.ID (if reply or repost) - RepostBoardID BoardID // Original Board.ID (if repost) - UpdatedAt time.Time - - flags avl.Tree // string(address) -> string(reason) - replies avl.Tree // Post.ID -> *Post - repliesAll avl.Tree // Post.ID -> *Post (all replies, for top-level posts) - reposts avl.Tree // Board.ID -> Post.ID - createdAt time.Time -} - -func newPost(board *Board, threadID, id PostID, creator address, title, body string) *Post { - return &Post{ - Board: board, - ThreadID: threadID, - ID: id, - Creator: creator, - Title: title, - Body: body, - createdAt: time.Now(), - } -} - -// CreatedAt returns the time when post was created. -func (post *Post) CreatedAt() time.Time { - return post.createdAt -} - -// IsRepost checks if current post is repost. -func (post *Post) IsRepost() bool { - return post.RepostBoardID != 0 -} - -// IsThread checks if current post is a thread. -func (post *Post) IsThread() bool { - // repost threads also have parent ID - return post.ParentID == 0 || post.IsRepost() -} - -// Flag add a flag to the post. -// It returns false when the user flagging the post already flagged it. -func (post *Post) Flag(user address, reason string) bool { - if post.flags.Has(user.String()) { - return false - } - - post.flags.Set(user.String(), reason) - return true -} - -// FlagsCount returns the number of time post was flagged. -func (post *Post) FlagsCount() int { - return post.flags.Size() -} - -// AddReply adds a new reply to the post. -// Replies can be added to threads and also to other replies. -func (post *Post) AddReply(creator address, body string) *Post { - board := post.Board - pid := board.generateNextPostID() - pKey := pid.Key() - reply := newPost(board, post.ThreadID, pid, creator, "", body) - reply.ParentID = post.ID - // TODO: Figure out how to remove this redundancy of data "replies==repliesAll" in threads - post.replies.Set(pKey, reply) - if post.ThreadID == post.ID { - post.repliesAll.Set(pKey, reply) - } else { - thread, _ := board.GetThread(post.ThreadID) - thread.repliesAll.Set(pKey, reply) - } - return reply -} - -// HasReplies checks if post has replies. -func (post *Post) HasReplies() bool { - return post.replies.Size() > 0 -} - -// Get returns a post reply. -func (thread *Post) GetReply(pid PostID) (_ *Post, found bool) { - v, found := thread.repliesAll.Get(pid.Key()) - if !found { - return nil, false - } - return v.(*Post), true -} - -// Repost reposts a thread into another boards. -func (post *Post) Repost(creator address, dst *Board, title, body string) *Post { - if !post.IsThread() { - panic("post must be a thread to be reposted to another board") - } - - repost := dst.AddThread(creator, title, body) - repost.ParentID = post.ID - repost.RepostBoardID = post.Board.ID - - dst.threads.Set(repost.ID.Key(), repost) - post.reposts.Set(dst.ID.Key(), repost.ID) - return repost -} - -// DeleteReply deletes a reply from a thread. -func (post *Post) DeleteReply(replyID PostID) error { - if !post.IsThread() { - // TODO: Allow removing replies from parent replies too - panic("cannot delete reply from a non-thread post") - } - - if post.ID == replyID { - return errors.New("expected an ID of an inner reply") - } - - key := replyID.Key() - v, removed := post.repliesAll.Remove(key) - if !removed { - return errors.New("reply not found in thread") - } - - // TODO: Shouldn't reply be hidden instead of deleted? Maybe replace reply by a deleted message. - reply := v.(*Post) - if reply.ParentID != post.ID { - parent, _ := post.GetReply(reply.ParentID) - parent.replies.Remove(key) - } else { - post.replies.Remove(key) - } - return nil -} - -// Summary return a summary of the post's body. -// It returns the body making sure that the length is limited to 80 characters. -func (post *Post) Summary() string { - return summaryOf(post.Body, 80) -} - -func (post *Post) RenderSummary() string { - var ( - b strings.Builder - postURI = makeThreadURI(post) - threadSummary = summaryOf(post.Title, 80) - creatorLink = userLink(post.Creator) - date = post.CreatedAt().Format(dateFormat) - ) - - b.WriteString(md.Bold("≡ "+md.Link(threadSummary, postURI)) + " \n") - b.WriteString("Created by " + creatorLink + " on " + date + " \n") - - status := []string{ - strconv.Itoa(post.repliesAll.Size()) + " replies", - strconv.Itoa(post.reposts.Size()) + " reposts", - } - b.WriteString(md.Bold(strings.Join(status, " • ")) + "\n") - return b.String() -} - -func (post *Post) renderSourcePost(indent string) (string, *Post) { - if !post.IsRepost() { - return "", nil - } - - indent += "> " - - // TODO: figure out a way to decouple posts from a global storage. - board, ok := getBoard(post.RepostBoardID) - if !ok { - // TODO: Boards can't be deleted so this might be redundant - return indentBody(indent, md.Italic("⚠ Source board has been deleted")+"\n"), nil - } - - srcPost, ok := board.GetThread(post.ParentID) - if !ok { - return indentBody(indent, md.Italic("⚠ Source post has been deleted")+"\n"), nil - } - - if srcPost.Hidden { - return indentBody(indent, md.Italic("⚠ Source post has been flagged as inappropriate")+"\n"), nil - } - - return indentBody(indent, srcPost.Summary()) + "\n\n", srcPost -} - -// renderPostContent renders post text content (including repost body). -// Function will dump a predefined message instead of a body if post is hidden. -func (post *Post) renderPostContent(sb *strings.Builder, indent string, levels int) { - if post.Hidden { - // Flagged comment should be hidden, but replies still visible (see: #3480) - // Flagged threads will be hidden by render function caller. - sb.WriteString(indentBody(indent, md.Italic("⚠ Reply is hidden as it has been flagged as inappropriate")) + "\n") - return - } - - srcContent, srcPost := post.renderSourcePost(indent) - if post.IsRepost() && srcPost != nil { - originLink := md.Link("another thread", makeThreadURI(srcPost)) - sb.WriteString(" \nThis thread is a repost of " + originLink + ": \n") - } - - sb.WriteString(srcContent) - - if post.IsRepost() && srcPost == nil && len(post.Body) > 0 { - // Add a newline to separate source deleted message from repost body content - sb.WriteString("\n") - } - - sb.WriteString(indentBody(indent, post.Body)) - sb.WriteString("\n") - - if post.IsThread() { - // Split content and controls for threads. - sb.WriteString("\n") - } - - // Buttons & counters - sb.WriteString(indent) - if !post.IsThread() { - sb.WriteString(" \n") - sb.WriteString(indent) - } - - creatorLink := userLink(post.Creator) - date := post.CreatedAt().Format(dateFormat) - sb.WriteString("Created by " + creatorLink + " on " + date) - - // Add a reply view link to each top level reply - if !post.IsThread() { - sb.WriteString(", " + md.Link("#"+post.ID.String(), makeReplyURI(post))) - } - - if post.reposts.Size() > 0 { - sb.WriteString(", " + strconv.Itoa(post.reposts.Size()) + " repost(s)") - } - - sb.WriteString(" \n") - - actions := []string{ - md.Link("Flag", makeFlagURI(post)), - } - - if post.IsThread() { - actions = append(actions, md.Link("Repost", makeCreateRepostURI(post))) - } - - isReadonly := post.Readonly || post.Board.Readonly - if !isReadonly { - actions = append( - actions, - md.Link("Reply", makeCreateReplyURI(post)), - md.Link("Edit", makeEditPostURI(post)), - md.Link("Delete", makeDeletePostURI(post)), - ) - } - - if levels == 0 { - if post.IsThread() { - actions = append(actions, md.Link("Show all Replies", makeThreadURI(post))) - } else { - actions = append(actions, md.Link("View Thread", makeThreadURI(post))) - } - } - - sb.WriteString(strings.Join(actions, " • ") + " \n") -} - -func (post *Post) Render(path string, indent string, levels int) string { - if post == nil { - return "" - } - - var sb strings.Builder - - // Thread reposts might not have a title, if so get title from source thread - title := post.Title - if post.IsRepost() && title == "" { - if board, ok := getBoard(post.RepostBoardID); ok { - if src, ok := board.GetThread(post.ParentID); ok { - title = src.Title - } - } - } - - if title != "" { // Replies don't have a title - sb.WriteString(md.H1(title)) - } - sb.WriteString(indent + "\n") - - post.renderPostContent(&sb, indent, levels) - - if post.replies.Size() == 0 { - return sb.String() - } - - // XXX: This triggers for reply views - if levels == 0 { - sb.WriteString(indent + "\n") - return sb.String() - } - - if path != "" { - sb.WriteString(post.renderTopLevelReplies(path, indent, levels-1)) - } else { - sb.WriteString(post.renderSubReplies(indent, levels-1)) - } - return sb.String() -} - -func (post *Post) renderTopLevelReplies(path, indent string, levels int) string { - p, err := pager.New(path, post.replies.Size(), pager.WithPageSize(pageSizeReplies)) - if err != nil { - panic(err) - } - - var ( - b strings.Builder - commentsIndent = indent + "> " - ) - - render := func(_ string, v any) bool { - reply := v.(*Post) - b.WriteString(indent + "\n" + reply.Render("", commentsIndent, levels-1)) - return false - } - - b.WriteString("\n" + md.HorizontalRule() + "Sort by: ") - r := parseRealmPath(path) - if r.Query.Get("order") == "desc" { - r.Query.Set("order", "asc") - b.WriteString(md.Link("newest first", r.String()) + "\n") - post.replies.ReverseIterateByOffset(p.Offset(), p.PageSize(), render) - - } else { - r.Query.Set("order", "desc") - b.WriteString(md.Link("oldest first", r.String()) + "\n") - post.replies.IterateByOffset(p.Offset(), p.PageSize(), render) - } - - if p.HasPages() { - b.WriteString(md.HorizontalRule()) - b.WriteString(pager.Picker(p)) - } - - return b.String() -} - -func (post *Post) renderSubReplies(indent string, levels int) string { - var ( - b strings.Builder - commentsIndent = indent + "> " - ) - - post.replies.Iterate("", "", func(_ string, v any) bool { - reply := v.(*Post) - b.WriteString(indent + "\n" + reply.Render("", commentsIndent, levels-1)) - return false - }) - - return b.String() -} - -func (post *Post) RenderInner() string { - if post.IsThread() { - panic("unexpected thread") - } - - var ( - s string - threadID = post.ThreadID - thread, _ = post.Board.GetThread(threadID) // TODO: This seems redundant (post == thread) - ) - - // Fully render parent if it's not a repost. - if !post.IsRepost() { - var ( - parent *Post - parentID = post.ParentID - ) - - if thread.ID == parentID { - parent = thread - } else { - parent, _ = thread.GetReply(parentID) - } - - s += parent.Render("", "", 0) + "\n" - } - - s += post.Render("", "> ", 5) - return s -} diff --git a/examples/gno.land/r/gnoland/boards2/v1/post_test.gno b/examples/gno.land/r/gnoland/boards2/v1/post_test.gno deleted file mode 100644 index 8192b9f40bc..00000000000 --- a/examples/gno.land/r/gnoland/boards2/v1/post_test.gno +++ /dev/null @@ -1,384 +0,0 @@ -package boards2 - -import ( - "strings" - "testing" - - "gno.land/p/nt/testutils" - "gno.land/p/nt/uassert" - "gno.land/p/nt/ufmt" -) - -func TestPostFlag(t *testing.T) { - addr := testutils.TestAddress("creator") - post := createTestThread(t) - - uassert.True(t, post.Flag(addr, "foo")) - uassert.False(t, post.Flag(addr, "foo"), "should reject flag from duplicate user") - uassert.Equal(t, post.FlagsCount(), 1) -} - -func TestPostRepost(t *testing.T) { - // TODO: Improve this unit test - addr := testutils.TestAddress("creatorDstBoard") - perms := createBasicBoardPermissions(addr) - cases := []struct { - name, title, body string - dstBoard *Board - thread *Post - setup func() *Post - err string - }{ - { - name: "repost thread", - title: "Repost Title", - body: "Repost body", - dstBoard: newBoard(42, "dst123", addr, perms), - setup: func() *Post { return createTestThread(t) }, - }, - { - name: "invalid repost from reply", - setup: func() *Post { return createTestReply(t) }, - err: "post must be a thread to be reposted to another board", - }, - } - - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - var ( - repost *Post - creator = testutils.TestAddress("repostCreator") - thread = tc.setup() - ) - - createRepost := func() { - repost = thread.Repost(creator, tc.dstBoard, tc.title, tc.body) - } - - if tc.err != "" { - uassert.PanicsWithMessage(t, tc.err, createRepost) - return - } else { - uassert.NotPanics(t, createRepost) - } - - r, found := tc.dstBoard.GetThread(repost.ID) - uassert.True(t, found) - uassert.True(t, repost == r) - uassert.Equal(t, tc.title, repost.Title) - uassert.Equal(t, tc.body, repost.Body) - uassert.Equal(t, uint(thread.Board.ID), uint(repost.RepostBoardID)) - }) - } -} - -func TestNewThread(t *testing.T) { - testing.SetRealm(testing.NewCodeRealm("gno.land/r/gnoland/boards2/v1")) - - creator := testutils.TestAddress("creator") - title := "Test Title" - body := strings.Repeat("A", 82) - boardID := BoardID(1) - threadID := PostID(42) - boardName := "test123" - perms := createBasicBoardPermissions(creator) - board := newBoard(boardID, boardName, creator, perms) - url := ufmt.Sprintf( - "/r/gnoland/boards2/v1:%s/%d", - boardName, - uint(threadID), - ) - replyURL := ufmt.Sprintf( - "/r/gnoland/boards2/v1$help&func=CreateReply&boardID=%d&body=&replyID=0&threadID=%d", - uint(boardID), - uint(threadID), - ) - editURL := ufmt.Sprintf( - "/r/gnoland/boards2/v1$help&func=EditThread&boardID=%d&body=%s&threadID=%d&title=%s", - uint(boardID), - body, - uint(threadID), - strings.ReplaceAll(title, " ", "+"), - ) - repostURL := ufmt.Sprintf( - "/r/gnoland/boards2/v1$help&func=CreateRepost&boardID=%d&body=&destinationBoardID=&threadID=%d&title=", - uint(boardID), - uint(threadID), - ) - deleteURL := ufmt.Sprintf( - "/r/gnoland/boards2/v1$help&func=DeleteThread&boardID=%d&threadID=%d", - uint(boardID), - uint(threadID), - ) - flagURL := ufmt.Sprintf( - "/r/gnoland/boards2/v1$help&func=FlagThread&boardID=%d&reason=&threadID=%d", - uint(boardID), - uint(threadID), - ) - - thread := newPost(board, threadID, threadID, creator, title, body) - - uassert.True(t, thread.IsThread()) - uassert.Equal(t, uint(threadID), uint(thread.ID)) - uassert.False(t, thread.CreatedAt().IsZero()) - uassert.True(t, thread.UpdatedAt.IsZero()) - uassert.Equal(t, title, thread.Title) - uassert.Equal(t, body[:77]+"...", thread.Summary()) - uassert.False(t, thread.HasReplies()) - uassert.Equal(t, url, makeThreadURI(thread)) - uassert.Equal(t, replyURL, makeCreateReplyURI(thread)) - uassert.Equal(t, editURL, makeEditPostURI(thread)) - uassert.Equal(t, repostURL, makeCreateRepostURI(thread)) - uassert.Equal(t, deleteURL, makeDeletePostURI(thread)) - uassert.Equal(t, flagURL, makeFlagURI(thread)) -} - -func TestThreadAddReply(t *testing.T) { - replier := testutils.TestAddress("replier") - thread := createTestThread(t) - threadID := uint(thread.ID) - body := "A reply" - - reply := thread.AddReply(replier, body) - - r, found := thread.GetReply(reply.ID) - uassert.True(t, found) - uassert.True(t, reply == r) - uassert.Equal(t, threadID+1, uint(reply.ID)) - uassert.Equal(t, reply.Creator, replier) - uassert.Equal(t, reply.Body, body) - uassert.True(t, thread.HasReplies()) -} - -func TestThreadGetReply(t *testing.T) { - cases := []struct { - name string - thread *Post - setup func(thread *Post) (replyID PostID) - found bool - }{ - { - name: "found", - thread: createTestThread(t), - setup: func(thread *Post) PostID { - reply := thread.AddReply(testutils.TestAddress("replier"), "") - return reply.ID - }, - found: true, - }, - { - name: "not found", - thread: createTestThread(t), - setup: func(*Post) PostID { return 42 }, - }, - } - - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - replyID := tc.setup(tc.thread) - - reply, found := tc.thread.GetReply(replyID) - - uassert.Equal(t, tc.found, found) - if reply != nil { - uassert.Equal(t, uint(replyID), uint(reply.ID)) - } - }) - } -} - -func TestThreadDeleteReply(t *testing.T) { - thread := createTestThread(t) - cases := []struct { - name string - setup func() PostID - err string - }{ - { - name: "ok", - setup: func() PostID { - reply := thread.AddReply(testutils.TestAddress("replier"), "") - return reply.ID - }, - }, - { - name: "ok nested", - setup: func() PostID { - reply := thread.AddReply(testutils.TestAddress("replier"), "") - return reply.AddReply(testutils.TestAddress("replier2"), "").ID - }, - }, - { - name: "invalid", - setup: func() PostID { return thread.ID }, - err: "expected an ID of an inner reply", - }, - { - name: "not found", - setup: func() PostID { return 42 }, - err: "reply not found in thread", - }, - } - - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - replyID := tc.setup() - - err := thread.DeleteReply(replyID) - - if tc.err != "" { - uassert.ErrorContains(t, err, tc.err) - return - } - - uassert.NoError(t, err) - _, found := thread.GetReply(replyID) - uassert.False(t, found) - }) - } -} - -func TestThreadRenderSummary(t *testing.T) { - t.Skip("TODO: implement") -} - -func TestThreadRender(t *testing.T) { - t.Skip("TODO: implement") -} - -func TestThreadRenderInner(t *testing.T) { - t.Skip("TODO: implement") -} - -func TestNewReply(t *testing.T) { - testing.SetRealm(testing.NewCodeRealm("gno.land/r/gnoland/boards2/v1")) - - creator := testutils.TestAddress("creator") - body := strings.Repeat("A", 82) - boardID := BoardID(1) - threadID := PostID(42) - parentID := PostID(1) - replyID := PostID(2) - boardName := "test123" - perms := createBasicBoardPermissions(creator) - board := newBoard(boardID, boardName, creator, perms) - url := ufmt.Sprintf( - "/r/gnoland/boards2/v1:%s/%d/%d", - boardName, - uint(threadID), - uint(replyID), - ) - replyURL := ufmt.Sprintf( - "/r/gnoland/boards2/v1$help&func=CreateReply&boardID=%d&body=&replyID=%d&threadID=%d", - uint(boardID), - uint(replyID), - uint(threadID), - ) - deleteURL := ufmt.Sprintf( - "/r/gnoland/boards2/v1$help&func=DeleteReply&boardID=%d&replyID=%d&threadID=%d", - uint(boardID), - uint(replyID), - uint(threadID), - ) - - reply := newPost(board, threadID, replyID, creator, "", body) - reply.ParentID = parentID - - uassert.False(t, reply.IsThread()) - uassert.Equal(t, uint(replyID), uint(reply.ID)) - uassert.False(t, reply.CreatedAt().IsZero()) - uassert.True(t, reply.UpdatedAt.IsZero()) - uassert.False(t, reply.HasReplies()) - uassert.Equal(t, body[:77]+"...", reply.Summary()) - uassert.Equal(t, url, makeReplyURI(reply)) - uassert.Equal(t, replyURL, makeCreateReplyURI(reply)) - uassert.Equal(t, deleteURL, makeDeletePostURI(reply)) -} - -func TestReplyAddReply(t *testing.T) { - replier := testutils.TestAddress("replier") - thread := createTestThread(t) - parentReply := thread.AddReply(testutils.TestAddress("parentReplier"), "") - parentReplyID := uint(parentReply.ID) - body := "A child reply" - - reply := parentReply.AddReply(replier, body) - - r, found := thread.GetReply(reply.ID) - uassert.True(t, found) - uassert.True(t, reply == r) - uassert.Equal(t, parentReplyID, uint(reply.ParentID)) - uassert.Equal(t, parentReplyID+1, uint(reply.ID)) - uassert.Equal(t, reply.Creator, replier) - uassert.Equal(t, reply.Body, body) - uassert.False(t, reply.HasReplies()) - uassert.True(t, parentReply.HasReplies()) -} - -func TestReplyGetReply(t *testing.T) { - thread := createTestThread(t) - parentReply := thread.AddReply(testutils.TestAddress("parentReplier"), "") - cases := []struct { - name string - setup func() PostID - found bool - }{ - { - name: "found", - setup: func() PostID { - reply := parentReply.AddReply(testutils.TestAddress("replier"), "") - return reply.ID - }, - found: true, - }, - { - name: "not found", - setup: func() PostID { return 42 }, - }, - } - - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - replyID := tc.setup() - - reply, found := thread.GetReply(replyID) - - uassert.Equal(t, tc.found, found) - if reply != nil { - uassert.Equal(t, uint(replyID), uint(reply.ID)) - } - }) - } -} - -func TestReplyDeleteReply(t *testing.T) { - thread := createTestThread(t) - parentReply := thread.AddReply(testutils.TestAddress("replier"), "") - reply := parentReply.AddReply(testutils.TestAddress("replier2"), "") - - // NOTE: Deleting a reply from a parent reply should eventually be suported - uassert.PanicsWithMessage(t, "cannot delete reply from a non-thread post", func() { - parentReply.DeleteReply(reply.ID) - }) -} - -func TestReplyRender(t *testing.T) { - t.Skip("TODO: implement") -} - -func createTestThread(t *testing.T) *Post { - t.Helper() - - creator := testutils.TestAddress("creator") - perms := createBasicBoardPermissions(creator) - board := newBoard(1, "test_board_123", creator, perms) - return board.AddThread(creator, "Title", "Body") -} - -func createTestReply(t *testing.T) *Post { - t.Helper() - - creator := testutils.TestAddress("replier") - thread := createTestThread(t) - return thread.AddReply(creator, "Test message") -} diff --git a/examples/gno.land/r/gnoland/boards2/v1/public.gno b/examples/gno.land/r/gnoland/boards2/v1/public.gno index 9a495e69e7c..76670052ffc 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/public.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/public.gno @@ -7,6 +7,8 @@ import ( "strconv" "strings" "time" + + "gno.land/p/gnoland/boards" ) const ( @@ -33,28 +35,28 @@ var ( func SetHelp(_ realm, content string) { content = strings.TrimSpace(content) caller := runtime.PreviousRealm().Address() - args := Args{content} + args := boards.Args{content} gPerms.WithPermission(cross, caller, PermissionRealmHelp, args, func(realm) { gHelp = content }) } // SetPermissions sets a permissions implementation for boards2 realm or a board. -func SetPermissions(_ realm, bid BoardID, p Permissions) { +func SetPermissions(_ realm, boardID boards.ID, p boards.Permissions) { assertRealmIsNotLocked() - assertBoardExists(bid) + assertBoardExists(boardID) if p == nil { panic("permissions is required") } caller := runtime.PreviousRealm().Address() - args := Args{bid} + args := boards.Args{boardID} gPerms.WithPermission(cross, caller, PermissionPermissionsUpdate, args, func(realm) { assertRealmIsNotLocked() // When board ID is zero it means that realm permissions are being updated - if bid == 0 { + if boardID == 0 { gPerms = p chain.Emit( @@ -65,13 +67,13 @@ func SetPermissions(_ realm, bid BoardID, p Permissions) { } // Otherwise update the permissions of a single board - board := mustGetBoard(bid) - board.perms = p + board := mustGetBoard(boardID) + board.Permissions = p chain.Emit( "BoardPermissionsUpdated", "caller", caller.String(), - "boardID", bid.String(), + "boardID", board.ID.String(), ) }) } @@ -79,21 +81,23 @@ func SetPermissions(_ realm, bid BoardID, p Permissions) { // SetRealmNotice sets a notice to be displayed globally by the realm. // An empty message removes the realm notice. func SetRealmNotice(_ realm, message string) { + message = strings.TrimSpace(message) caller := runtime.PreviousRealm().Address() - assertHasPermission(gPerms, caller, PermissionThreadCreate) - - gNotice = strings.TrimSpace(message) + args := boards.Args{message} + gPerms.WithPermission(cross, caller, PermissionRealmNotice, args, func(realm) { + gNotice = message - chain.Emit( - "RealmNoticeChanged", - "caller", caller.String(), - "message", gNotice, - ) + chain.Emit( + "RealmNoticeChanged", + "caller", caller.String(), + "message", message, + ) + }) } // GetBoardIDFromName searches a board by name and returns it's ID. -func GetBoardIDFromName(_ realm, name string) (_ BoardID, found bool) { - board, found := getBoardByName(name) +func GetBoardIDFromName(_ realm, name string) (_ boards.ID, found bool) { + board, found := gBoards.GetByName(name) if !found { return 0, false } @@ -103,7 +107,7 @@ func GetBoardIDFromName(_ realm, name string) (_ BoardID, found bool) { // CreateBoard creates a new board. // // Listed boards are included in the list of boards. -func CreateBoard(_ realm, name string, listed bool) BoardID { +func CreateBoard(_ realm, name string, listed bool) boards.ID { assertRealmIsNotLocked() name = strings.TrimSpace(name) @@ -111,31 +115,34 @@ func CreateBoard(_ realm, name string, listed bool) BoardID { assertBoardNameNotExists(name) caller := runtime.PreviousRealm().Address() - id := reserveBoardID() - args := Args{caller, name, id, listed} + id := gBoardsSequence.Next() + board := boards.New(id) + args := boards.Args{caller, name, board.ID, listed} gPerms.WithPermission(cross, caller, PermissionBoardCreate, args, func(realm) { assertRealmIsNotLocked() assertBoardNameNotExists(name) - perms := createBasicBoardPermissions(caller) - board := newBoard(id, name, caller, perms) - key := id.Key() - gBoardsByID.Set(key, board) - gBoardsByName.Set(strings.ToLower(name), board) + board.Name = name + board.Permissions = createBasicBoardPermissions(caller) + board.Creator = caller + + if err := gBoards.Add(board); err != nil { + panic(err) + } // Listed boards are also indexed separately for easier iteration and pagination if listed { - gListedBoardsByID.Set(key, board) + gListedBoardsByID.Set(board.ID.Key(), board) } chain.Emit( "BoardCreated", "caller", caller.String(), - "boardID", id.String(), + "boardID", board.ID.String(), "name", name, ) }) - return id + return board.ID } // RenameBoard changes the name of an existing board. @@ -152,26 +159,22 @@ func RenameBoard(_ realm, name, newName string) { board := mustGetBoardByName(name) assertBoardIsNotFrozen(board) - bid := board.ID caller := runtime.PreviousRealm().Address() - args := Args{caller, bid, name, newName} - board.perms.WithPermission(cross, caller, PermissionBoardRename, args, func(realm) { + args := boards.Args{caller, board.ID, name, newName} + board.Permissions.WithPermission(cross, caller, PermissionBoardRename, args, func(realm) { assertRealmIsNotLocked() assertBoardNameNotExists(newName) - board := mustGetBoard(bid) - assertBoardIsNotFrozen(board) - board.Aliases = append(board.Aliases, board.Name) board.Name = newName // Index board for the new name keeping previous indexes for older names - gBoardsByName.Set(strings.ToLower(newName), board) + gBoards.Add(board) chain.Emit( "BoardRenamed", "caller", caller.String(), - "boardID", bid.String(), + "boardID", board.ID.String(), "name", name, "newName", newName, ) @@ -179,82 +182,102 @@ func RenameBoard(_ realm, name, newName string) { } // CreateThread creates a new thread within a board. -func CreateThread(_ realm, boardID BoardID, title, body string) PostID { +func CreateThread(_ realm, boardID boards.ID, title, body string) boards.ID { assertRealmIsNotLocked() title = strings.TrimSpace(title) assertTitleIsValid(title) - body = strings.TrimSpace(body) - assertBodyIsNotEmpty(body) + caller := runtime.PreviousRealm().Address() + assertUserIsNotBanned(boardID, caller) board := mustGetBoard(boardID) assertBoardIsNotFrozen(board) - caller := runtime.PreviousRealm().Address() - assertUserIsNotBanned(board.ID, caller) - assertHasPermission(board.perms, caller, PermissionThreadCreate) + thread := boards.MustNewThread(board, caller, title, body) + args := boards.Args{caller, board.ID, thread.ID, title, body} + board.Permissions.WithPermission(cross, caller, PermissionThreadCreate, args, func(realm) { + assertRealmIsNotLocked() + assertUserIsNotBanned(board.ID, caller) - thread := board.AddThread(caller, title, body) + thread.Replies = NewReplyStorage() - chain.Emit( - "ThreadCreated", - "caller", caller.String(), - "boardID", boardID.String(), - "threadID", thread.ID.String(), - "title", title, - ) + if err := board.Threads.Add(thread); err != nil { + panic(err) + } + chain.Emit( + "ThreadCreated", + "caller", caller.String(), + "boardID", board.ID.String(), + "threadID", thread.ID.String(), + "title", title, + ) + }) return thread.ID } // CreateReply creates a new comment or reply within a thread. // // The value of `replyID` is only required when creating a reply of another reply. -func CreateReply(_ realm, boardID BoardID, threadID, replyID PostID, body string) PostID { +func CreateReply(_ realm, boardID, threadID, replyID boards.ID, body string) boards.ID { assertRealmIsNotLocked() body = strings.TrimSpace(body) assertReplyBodyIsValid(body) - board := mustGetBoard(boardID) - assertBoardIsNotFrozen(board) - caller := runtime.PreviousRealm().Address() - assertHasPermission(board.perms, caller, PermissionReplyCreate) assertUserIsNotBanned(boardID, caller) + board := mustGetBoard(boardID) + assertBoardIsNotFrozen(board) + thread := mustGetThread(board, threadID) assertThreadIsVisible(thread) assertThreadIsNotFrozen(thread) - var reply *Post - if replyID == 0 { - // When the parent reply is the thread just add reply to thread - reply = thread.AddReply(caller, body) - } else { - // Try to get parent reply and add a new child reply - parent := mustGetReply(thread, replyID) + // By default consider that reply's parent is the thread. + // Or when replyID is assigned use that reply as the parent. + parent := thread + if replyID > 0 { + parent = mustGetReply(thread, replyID) if parent.Hidden || parent.Readonly { panic("replying to a hidden or frozen reply is not allowed") } - - reply = parent.AddReply(caller, body) } - chain.Emit( - "ReplyCreate", - "caller", caller.String(), - "boardID", boardID.String(), - "threadID", threadID.String(), - "replyID", reply.ID.String(), - ) + reply := boards.MustNewReply(parent, caller, body) + args := boards.Args{caller, board.ID, thread.ID, parent.ID, reply.ID, body} + board.Permissions.WithPermission(cross, caller, PermissionReplyCreate, args, func(realm) { + assertRealmIsNotLocked() + // Add reply to its parent + if err := parent.Replies.Add(reply); err != nil { + panic(err) + } + + // When parent is not a thread also add reply to the thread. + // The thread contains all replies and sub-replies, while each + // reply only contains direct sub-replies. + if parent.ID != thread.ID { + if err := thread.Replies.Add(reply); err != nil { + panic(err) + } + } + + chain.Emit( + "ReplyCreate", + "caller", caller.String(), + "boardID", board.ID.String(), + "threadID", thread.ID.String(), + "replyID", reply.ID.String(), + ) + }) return reply.ID } // CreateRepost reposts a thread into another board. -func CreateRepost(_ realm, boardID BoardID, threadID PostID, title, body string, destinationBoardID BoardID) PostID { +func CreateRepost(_ realm, boardID, threadID, destinationBoardID boards.ID, title, body string) boards.ID { assertRealmIsNotLocked() title = strings.TrimSpace(title) @@ -265,66 +288,80 @@ func CreateRepost(_ realm, boardID BoardID, threadID PostID, title, body string, dst := mustGetBoard(destinationBoardID) assertBoardIsNotFrozen(dst) - assertHasPermission(dst.perms, caller, PermissionThreadRepost) board := mustGetBoard(boardID) thread := mustGetThread(board, threadID) assertThreadIsVisible(thread) - if thread.IsRepost() { - panic("reposting a thread that is a repost is not allowed") - } + repost := boards.MustNewRepost(thread, dst, caller) + args := boards.Args{caller, board.ID, thread.ID, dst.ID, repost.ID, title, body} + dst.Permissions.WithPermission(cross, caller, PermissionThreadRepost, args, func(realm) { + assertRealmIsNotLocked() - body = strings.TrimSpace(body) - repost := thread.Repost(caller, dst, title, body) + repost.Title = title + repost.Body = strings.TrimSpace(body) - chain.Emit( - "Repost", - "caller", caller.String(), - "boardID", boardID.String(), - "threadID", threadID.String(), - "destinationBoardID", destinationBoardID.String(), - "repostID", repost.ID.String(), - "title", title, - ) + if err := dst.Threads.Add(repost); err != nil { + panic(err) + } + + if err := thread.Reposts.Add(repost); err != nil { + panic(err) + } + chain.Emit( + "Repost", + "caller", caller.String(), + "boardID", board.ID.String(), + "threadID", thread.ID.String(), + "destinationBoardID", dst.ID.String(), + "repostID", repost.ID.String(), + "title", title, + ) + }) return repost.ID } // DeleteThread deletes a thread from a board. // // Threads can be deleted by the users who created them or otherwise by users with special permissions. -func DeleteThread(_ realm, boardID BoardID, threadID PostID) { - // Council members should always be able to delete - caller := runtime.PreviousRealm().Address() - isRealmOwner := gPerms.HasRole(caller, RoleOwner) // TODO: Add DeleteThread filetest cases for realm owners - if !isRealmOwner { - assertRealmIsNotLocked() - } +func DeleteThread(_ realm, boardID, threadID boards.ID) { + assertRealmIsNotLocked() + caller := runtime.PreviousRealm().Address() board := mustGetBoard(boardID) assertUserIsNotBanned(boardID, caller) - thread := mustGetThread(board, threadID) - + isRealmOwner := gPerms.HasRole(caller, RoleOwner) // TODO: Add DeleteThread filetest cases for realm owners if !isRealmOwner { assertBoardIsNotFrozen(board) - assertThreadIsNotFrozen(thread) + } - if caller != thread.Creator { - assertHasPermission(board.perms, caller, PermissionThreadDelete) - } + thread := mustGetThread(board, threadID) + deleteThread := func() { + board.Threads.Remove(thread.ID) + + chain.Emit( + "ThreadDeleted", + "caller", caller.String(), + "boardID", board.ID.String(), + "threadID", thread.ID.String(), + ) } - // Hard delete thread and all its replies - board.DeleteThread(threadID) + // Thread can be directly deleted by user that created it. + // It can also be deleted by realm owners, to be able to delete inappropriate content. + // TODO: Discuss and decide if realm owners should be able to delete threads. + if isRealmOwner || caller == thread.Creator { + deleteThread() + return + } - chain.Emit( - "ThreadDeleted", - "caller", caller.String(), - "boardID", boardID.String(), - "threadID", threadID.String(), - ) + args := boards.Args{caller, board.ID, thread.ID} + board.Permissions.WithPermission(cross, caller, PermissionThreadDelete, args, func(realm) { + assertRealmIsNotLocked() + deleteThread() + }) } // DeleteReply deletes a reply from a thread. @@ -332,53 +369,72 @@ func DeleteThread(_ realm, boardID BoardID, threadID PostID) { // Replies can be deleted by the users who created them or otherwise by users with special permissions. // Soft deletion is used when the deleted reply contains sub replies, in which case the reply content // is replaced by a text informing that reply has been deleted to avoid deleting sub-replies. -func DeleteReply(_ realm, boardID BoardID, threadID, replyID PostID) { - // Council members should always be able to delete - caller := runtime.PreviousRealm().Address() - isRealmOwner := gPerms.HasRole(caller, RoleOwner) // TODO: Add DeleteReply filetest cases for realm owners - if !isRealmOwner { - assertRealmIsNotLocked() - } +func DeleteReply(_ realm, boardID, threadID, replyID boards.ID) { + assertRealmIsNotLocked() + caller := runtime.PreviousRealm().Address() board := mustGetBoard(boardID) assertUserIsNotBanned(boardID, caller) thread := mustGetThread(board, threadID) reply := mustGetReply(thread, replyID) - + isRealmOwner := gPerms.HasRole(caller, RoleOwner) // TODO: Add DeleteReply filetest cases for realm owners if !isRealmOwner { assertBoardIsNotFrozen(board) assertThreadIsNotFrozen(thread) assertReplyIsVisible(reply) - assertReplyIsNotFrozen(reply) + } - if caller != reply.Creator { - assertHasPermission(board.perms, caller, PermissionReplyDelete) + deleteReply := func() { + // Soft delete reply by changing its body when it contains + // sub-replies, otherwise hard delete it. + if reply.Replies.Size() > 0 { + reply.Body = "This reply has been deleted" + reply.UpdatedAt = time.Now() + } else { + // Remove reply from the thread + reply, removed := thread.Replies.Remove(replyID) + if !removed { + panic("reply not found") + } + + // Remove reply from reply's parent + if reply.ParentID != thread.ID { + parent, found := thread.Replies.Get(reply.ParentID) + if found { + parent.Replies.Remove(replyID) + } + } } + + chain.Emit( + "ReplyDeleted", + "caller", caller.String(), + "boardID", board.ID.String(), + "threadID", thread.ID.String(), + "replyID", reply.ID.String(), + ) } - // Soft delete reply by changing its body when it contains - // sub-replies, otherwise hard delete it. - if reply.HasReplies() { - reply.Body = "This reply has been deleted" - reply.UpdatedAt = time.Now() - } else { - thread.DeleteReply(replyID) + // Reply can be directly deleted by user that created it. + // It can also be deleted by realm owners, to be able to delete inappropriate content. + // TODO: Discuss and decide if realm owners should be able to delete replies. + if isRealmOwner || caller == reply.Creator { + deleteReply() + return } - chain.Emit( - "ReplyDeleted", - "caller", caller.String(), - "boardID", boardID.String(), - "threadID", threadID.String(), - "replyID", replyID.String(), - ) + args := boards.Args{caller, board.ID, thread.ID, reply.ID} + board.Permissions.WithPermission(cross, caller, PermissionReplyDelete, args, func(realm) { + assertRealmIsNotLocked() + deleteReply() + }) } // EditThread updates the title and body of thread. // // Threads can be updated by the users who created them or otherwise by users with special permissions. -func EditThread(_ realm, boardID BoardID, threadID PostID, title, body string) { +func EditThread(_ realm, boardID, threadID boards.ID, title, body string) { assertRealmIsNotLocked() title = strings.TrimSpace(title) @@ -394,31 +450,40 @@ func EditThread(_ realm, boardID BoardID, threadID PostID, title, body string) { assertThreadIsNotFrozen(thread) body = strings.TrimSpace(body) - if !thread.IsRepost() { + if !boards.IsRepost(thread) { assertBodyIsNotEmpty(body) } - if caller != thread.Creator { - assertHasPermission(board.perms, caller, PermissionThreadEdit) + editThread := func() { + thread.Title = title + thread.Body = body + thread.UpdatedAt = time.Now() + + chain.Emit( + "ThreadEdited", + "caller", caller.String(), + "boardID", board.ID.String(), + "threadID", thread.ID.String(), + "title", title, + ) } - thread.Title = title - thread.Body = body - thread.UpdatedAt = time.Now() + if caller == thread.Creator { + editThread() + return + } - chain.Emit( - "ThreadEdited", - "caller", caller.String(), - "boardID", boardID.String(), - "threadID", threadID.String(), - "title", title, - ) + args := boards.Args{caller, board.ID, thread.ID, title, body} + board.Permissions.WithPermission(cross, caller, PermissionThreadEdit, args, func(realm) { + assertRealmIsNotLocked() + editThread() + }) } // EditReply updates the body of comment or reply. // // Replies can be updated only by the users who created them. -func EditReply(_ realm, boardID BoardID, threadID, replyID PostID, body string) { +func EditReply(_ realm, boardID, threadID, replyID boards.ID, body string) { assertRealmIsNotLocked() body = strings.TrimSpace(body) @@ -435,7 +500,6 @@ func EditReply(_ realm, boardID BoardID, threadID, replyID PostID, body string) reply := mustGetReply(thread, replyID) assertReplyIsVisible(reply) - assertReplyIsNotFrozen(reply) if caller != reply.Creator { panic("only the reply creator is allowed to edit it") @@ -447,9 +511,9 @@ func EditReply(_ realm, boardID BoardID, threadID, replyID PostID, body string) chain.Emit( "ReplyEdited", "caller", caller.String(), - "boardID", boardID.String(), - "threadID", threadID.String(), - "replyID", replyID.String(), + "boardID", board.ID.String(), + "threadID", thread.ID.String(), + "replyID", reply.ID.String(), "body", body, ) } @@ -457,15 +521,14 @@ func EditReply(_ realm, boardID BoardID, threadID, replyID PostID, body string) // RemoveMember removes a member from the realm or a boards. // // Board ID is only required when removing a member from board. -func RemoveMember(_ realm, boardID BoardID, member address) { +func RemoveMember(_ realm, boardID boards.ID, member address) { assertMembersUpdateIsEnabled(boardID) assertMemberAddressIsValid(member) perms := mustGetPermissions(boardID) + origin := runtime.OriginCaller() caller := runtime.PreviousRealm().Address() - perms.WithPermission(cross, caller, PermissionMemberRemove, Args{member}, func(realm) { - assertMembersUpdateIsEnabled(boardID) - + removeMember := func() { if !perms.RemoveUser(cross, member) { panic("member not found") } @@ -473,16 +536,29 @@ func RemoveMember(_ realm, boardID BoardID, member address) { chain.Emit( "MemberRemoved", "caller", caller.String(), + "origin", origin.String(), // When origin and caller match it means self removal "boardID", boardID.String(), "member", member.String(), ) + } + + // Members can remove themselves without permission + if origin == member { + removeMember() + return + } + + args := boards.Args{boardID, member} + perms.WithPermission(cross, caller, PermissionMemberRemove, args, func(realm) { + assertMembersUpdateIsEnabled(boardID) + removeMember() }) } // IsMember checks if an user is a member of the realm or a board. // // Board ID is only required when checking if a user is a member of a board. -func IsMember(boardID BoardID, user address) bool { +func IsMember(boardID boards.ID, user address) bool { assertUserAddressIsValid(user) if boardID != 0 { @@ -497,7 +573,7 @@ func IsMember(boardID BoardID, user address) bool { // HasMemberRole checks if a realm or board member has a specific role assigned. // // Board ID is only required when checking a member of a board. -func HasMemberRole(boardID BoardID, member address, role Role) bool { +func HasMemberRole(boardID boards.ID, member address, role boards.Role) bool { assertMemberAddressIsValid(member) if boardID != 0 { @@ -512,13 +588,17 @@ func HasMemberRole(boardID BoardID, member address, role Role) bool { // ChangeMemberRole changes the role of a realm or board member. // // Board ID is only required when changing the role for a member of a board. -func ChangeMemberRole(_ realm, boardID BoardID, member address, role Role) { +func ChangeMemberRole(_ realm, boardID boards.ID, member address, role boards.Role) { assertMemberAddressIsValid(member) assertMembersUpdateIsEnabled(boardID) + if role == "" { + role = RoleGuest + } + perms := mustGetPermissions(boardID) caller := runtime.PreviousRealm().Address() - args := Args{caller, boardID, member, role} + args := boards.Args{caller, boardID, member, role} perms.WithPermission(cross, caller, PermissionRoleChange, args, func(realm) { assertMembersUpdateIsEnabled(boardID) @@ -536,15 +616,15 @@ func ChangeMemberRole(_ realm, boardID BoardID, member address, role Role) { // IterateRealmMembers iterates boards realm members. // The iteration is done only for realm members, board members are not iterated. -func IterateRealmMembers(offset int, fn UsersIterFn) (halted bool) { +func IterateRealmMembers(offset int, fn boards.UsersIterFn) (halted bool) { count := gPerms.UsersCount() - offset return gPerms.IterateUsers(offset, count, fn) } // GetBoard returns a single board. -func GetBoard(boardID BoardID) *Board { +func GetBoard(boardID boards.ID) *boards.Board { board := mustGetBoard(boardID) - if !board.perms.HasRole(runtime.OriginCaller(), RoleOwner) { + if !board.Permissions.HasRole(runtime.OriginCaller(), RoleOwner) { panic("forbidden") } return board @@ -562,23 +642,23 @@ func assertUserAddressIsValid(user address) { } } -func assertHasPermission(perms Permissions, user address, p Permission) { +func assertHasPermission(perms boards.Permissions, user address, p boards.Permission) { if !perms.HasPermission(user, p) { panic("unauthorized") } } -func assertBoardExists(id BoardID) { +func assertBoardExists(id boards.ID) { if id == 0 { // ID zero is used to refer to the realm return } - if _, found := getBoard(id); !found { + if _, found := gBoards.Get(id); !found { panic("board not found: " + id.String()) } } -func assertBoardIsNotFrozen(b *Board) { +func assertBoardIsNotFrozen(b *boards.Board) { if b.Readonly { panic("board is frozen") } @@ -604,18 +684,12 @@ func assertIsValidBoardName(name string) { } } -func assertThreadIsNotFrozen(t *Post) { +func assertThreadIsNotFrozen(t *boards.Post) { if t.Readonly { panic("thread is frozen") } } -func assertReplyIsNotFrozen(r *Post) { - if r.Readonly { - panic("reply is frozen") - } -} - func assertNameIsNotEmpty(name string) { if name == "" { panic("name is empty") @@ -629,7 +703,7 @@ func assertTitleIsValid(title string) { if len(title) > MaxThreadTitleLength { n := strconv.Itoa(MaxThreadTitleLength) - panic("thread title is too long, maximum allowed is " + n + " characters") + panic("title is too long, maximum allowed is " + n + " characters") } } @@ -641,30 +715,30 @@ func assertBodyIsNotEmpty(body string) { func assertBoardNameNotExists(name string) { name = strings.ToLower(name) - if gBoardsByName.Has(name) { + if _, found := gBoards.GetByName(name); found { panic("board already exists") } } -func assertThreadExists(b *Board, threadID PostID) { - if _, found := b.GetThread(threadID); !found { +func assertThreadExists(b *boards.Board, threadID boards.ID) { + if _, found := b.Threads.Get(threadID); !found { panic("thread not found: " + threadID.String()) } } -func assertReplyExists(thread *Post, replyID PostID) { - if _, found := thread.GetReply(replyID); !found { +func assertReplyExists(thread *boards.Post, replyID boards.ID) { + if _, found := thread.Replies.Get(replyID); !found { panic("reply not found: " + replyID.String()) } } -func assertThreadIsVisible(thread *Post) { +func assertThreadIsVisible(thread *boards.Post) { if thread.Hidden { panic("thread is hidden") } } -func assertReplyIsVisible(thread *Post) { +func assertReplyIsVisible(thread *boards.Post) { if thread.Hidden { panic("reply is hidden") } @@ -683,7 +757,7 @@ func assertReplyBodyIsValid(body string) { } } -func assertMembersUpdateIsEnabled(boardID BoardID) { +func assertMembersUpdateIsEnabled(boardID boards.ID) { if boardID != 0 { assertRealmIsNotLocked() } else { diff --git a/examples/gno.land/r/gnoland/boards2/v1/public_ban.gno b/examples/gno.land/r/gnoland/boards2/v1/public_ban.gno index 924e69f4c25..5ccdea82c7e 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/public_ban.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/public_ban.gno @@ -6,6 +6,7 @@ import ( "strings" "time" + "gno.land/p/gnoland/boards" "gno.land/p/nt/avl" ) @@ -19,7 +20,7 @@ const ( // Ban bans a user from a board for a period of time. // Only invited guest members and external users can be banned. // Banning board owners, admins and moderators is not allowed. -func Ban(_ realm, boardID BoardID, user address, hours uint, reason string) { +func Ban(_ realm, boardID boards.ID, user address, hours uint, reason string) { assertAddressIsValid(user) if hours == 0 { @@ -34,12 +35,11 @@ func Ban(_ realm, boardID BoardID, user address, hours uint, reason string) { board := mustGetBoard(boardID) caller := runtime.PreviousRealm().Address() until := time.Now().Add(time.Minute * 60 * time.Duration(hours)) - args := Args{boardID, user, until, reason} - board.perms.WithPermission(cross, caller, PermissionUserBan, args, func(realm) { + args := boards.Args{boardID, user, until, reason} + board.Permissions.WithPermission(cross, caller, PermissionUserBan, args, func(realm) { // When banning invited members make sure they are guests, otherwise // disallow banning. Only guest or external users can be banned. - board := mustGetBoard(boardID) - if board.perms.HasUser(user) && !board.perms.HasRole(user, RoleGuest) { + if board.Permissions.HasUser(user) && !board.Permissions.HasRole(user, RoleGuest) { panic("owner, admin and moderator banning is not allowed") } @@ -54,7 +54,7 @@ func Ban(_ realm, boardID BoardID, user address, hours uint, reason string) { chain.Emit( "UserBanned", "bannedBy", caller.String(), - "boardID", boardID.String(), + "boardID", board.ID.String(), "user", user.String(), "until", until.Format(time.RFC3339), "reason", reason, @@ -63,13 +63,13 @@ func Ban(_ realm, boardID BoardID, user address, hours uint, reason string) { } // Unban unbans a user from a board. -func Unban(_ realm, boardID BoardID, user address, reason string) { +func Unban(_ realm, boardID boards.ID, user address, reason string) { assertAddressIsValid(user) board := mustGetBoard(boardID) caller := runtime.PreviousRealm().Address() - args := Args{boardID, user, reason} - board.perms.WithPermission(cross, caller, PermissionUserUnban, args, func(realm) { + args := boards.Args{boardID, user, reason} + board.Permissions.WithPermission(cross, caller, PermissionUserUnban, args, func(realm) { banned, found := getBannedUsers(boardID) if !found || !banned.Has(user.String()) { panic("user is not banned") @@ -80,7 +80,7 @@ func Unban(_ realm, boardID BoardID, user address, reason string) { chain.Emit( "UserUnbanned", "bannedBy", caller.String(), - "boardID", boardID.String(), + "boardID", board.ID.String(), "user", user.String(), "reason", reason, ) @@ -88,7 +88,7 @@ func Unban(_ realm, boardID BoardID, user address, reason string) { } // IsBanned checks if a user is banned from a board. -func IsBanned(boardID BoardID, user address) bool { +func IsBanned(boardID boards.ID, user address) bool { banned, found := getBannedUsers(boardID) return found && banned.Has(user.String()) } @@ -99,7 +99,7 @@ func assertAddressIsValid(addr address) { } } -func assertUserIsNotBanned(boardID BoardID, user address) { +func assertUserIsNotBanned(boardID boards.ID, user address) { banned, found := getBannedUsers(boardID) if !found { return diff --git a/examples/gno.land/r/gnoland/boards2/v1/public_flag.gno b/examples/gno.land/r/gnoland/boards2/v1/public_flag.gno index 50d7b97daf7..1bc8bcc9940 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/public_flag.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/public_flag.gno @@ -4,12 +4,14 @@ import ( "chain" "chain/runtime" "strconv" + + "gno.land/p/gnoland/boards" ) // SetFlaggingThreshold sets the number of flags required to hide a thread or comment. // // Threshold is only applicable within the board where it's setted. -func SetFlaggingThreshold(_ realm, boardID BoardID, threshold int) { +func SetFlaggingThreshold(_ realm, boardID boards.ID, threshold int) { if threshold < 1 { panic("invalid flagging threshold") } @@ -20,25 +22,23 @@ func SetFlaggingThreshold(_ realm, boardID BoardID, threshold int) { assertBoardIsNotFrozen(board) caller := runtime.PreviousRealm().Address() - args := Args{boardID, threshold} - board.perms.WithPermission(cross, caller, PermissionBoardFlaggingUpdate, args, func(realm) { + args := boards.Args{board.ID, threshold} + board.Permissions.WithPermission(cross, caller, PermissionBoardFlaggingUpdate, args, func(realm) { assertRealmIsNotLocked() - board := mustGetBoard(boardID) - assertBoardIsNotFrozen(board) - gFlaggingThresholds.Set(boardID.String(), threshold) + chain.Emit( "FlaggingThresholdUpdated", "caller", caller.String(), - "boardID", boardID.String(), + "boardID", board.ID.String(), "threshold", strconv.Itoa(threshold), ) }) } // GetFlaggingThreshold returns the number of flags required to hide a thread or comment within a board. -func GetFlaggingThreshold(boardID BoardID) int { +func GetFlaggingThreshold(boardID boards.ID) int { assertBoardExists(boardID) return getFlaggingThreshold(boardID) } @@ -47,73 +47,92 @@ func GetFlaggingThreshold(boardID BoardID) int { // // Flagging requires special permissions and hides the thread when // the number of flags reaches a pre-defined flagging threshold. -func FlagThread(_ realm, boardID BoardID, threadID PostID, reason string) { - board := mustGetBoard(boardID) - - // Realm owners should be able to flag without permissions even when board is frozen +func FlagThread(_ realm, boardID, threadID boards.ID, reason string) { caller := runtime.PreviousRealm().Address() + board := mustGetBoard(boardID) isRealmOwner := gPerms.HasRole(caller, RoleOwner) if !isRealmOwner { assertRealmIsNotLocked() assertBoardIsNotFrozen(board) - assertHasPermission(board.perms, caller, PermissionThreadFlag) } - t, ok := board.GetThread(threadID) - if !ok { - panic("post doesn't exist") + thread, found := board.Threads.Get(threadID) + if !found { + panic("thread not found") } - assertThreadIsNotFrozen(t) - // Realm owners can hide with a single flag - hide := flagItem(t, caller, reason, getFlaggingThreshold(boardID)) - if hide || isRealmOwner { - t.Hidden = true + assertThreadIsNotFrozen(thread) + + flagThread := func() { + hide := flagItem(thread, caller, reason, getFlaggingThreshold(board.ID)) + if hide || isRealmOwner { + // Realm owners can hide with a single flag + thread.Hidden = true + } + + chain.Emit( + "ThreadFlagged", + "caller", caller.String(), + "boardID", board.ID.String(), + "threadID", thread.ID.String(), + "reason", reason, + ) } - chain.Emit( - "ThreadFlagged", - "caller", caller.String(), - "boardID", boardID.String(), - "threadID", threadID.String(), - "reason", reason, - ) + // Realm owners should be able to flag without permissions even when board is frozen + if isRealmOwner { + flagThread() + return + } + + args := boards.Args{caller, board.ID, thread.ID, reason} + board.Permissions.WithPermission(cross, caller, PermissionThreadFlag, args, func(realm) { + flagThread() + }) } // FlagReply adds a new flag to a comment or reply. // // Flagging requires special permissions and hides the comment or reply // when the number of flags reaches a pre-defined flagging threshold. -func FlagReply(_ realm, boardID BoardID, threadID, replyID PostID, reason string) { - board := mustGetBoard(boardID) - - // Realm owners should be able to flag without permissions even when board is frozen +func FlagReply(_ realm, boardID, threadID, replyID boards.ID, reason string) { caller := runtime.PreviousRealm().Address() + board := mustGetBoard(boardID) isRealmOwner := gPerms.HasRole(caller, RoleOwner) if !isRealmOwner { assertRealmIsNotLocked() assertBoardIsNotFrozen(board) - assertHasPermission(board.perms, caller, PermissionReplyFlag) } thread := mustGetThread(board, threadID) assertThreadIsNotFrozen(thread) reply := mustGetReply(thread, replyID) - assertReplyIsNotFrozen(thread) + flagReply := func() { + hide := flagItem(reply, caller, reason, getFlaggingThreshold(board.ID)) + if hide || isRealmOwner { + // Realm owners can hide with a single flag + reply.Hidden = true + } - // Realm owners can hide with a single flag - hide := flagItem(reply, caller, reason, getFlaggingThreshold(boardID)) - if hide || isRealmOwner { - reply.Hidden = true + chain.Emit( + "ReplyFlagged", + "caller", caller.String(), + "boardID", board.ID.String(), + "threadID", thread.ID.String(), + "replyID", reply.ID.String(), + "reason", reason, + ) } - chain.Emit( - "ReplyFlagged", - "caller", caller.String(), - "boardID", boardID.String(), - "threadID", threadID.String(), - "replyID", replyID.String(), - "reason", reason, - ) + // Realm owners should be able to flag without permissions even when board is frozen + if isRealmOwner { + flagReply() + return + } + + args := boards.Args{caller, board.ID, thread.ID, reply.ID, reason} + board.Permissions.WithPermission(cross, caller, PermissionReplyFlag, args, func(realm) { + flagReply() + }) } diff --git a/examples/gno.land/r/gnoland/boards2/v1/public_freeze.gno b/examples/gno.land/r/gnoland/boards2/v1/public_freeze.gno index d7dc268a3e7..00546c96baf 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/public_freeze.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/public_freeze.gno @@ -4,20 +4,22 @@ import ( "chain" "chain/runtime" "strconv" + + "gno.land/p/gnoland/boards" ) // FreezeBoard freezes a board so no more threads and comments can be created or modified. -func FreezeBoard(_ realm, boardID BoardID) { +func FreezeBoard(_ realm, boardID boards.ID) { setBoardReadonly(boardID, true) } // UnfreezeBoard removes frozen status from a board. -func UnfreezeBoard(_ realm, boardID BoardID) { +func UnfreezeBoard(_ realm, boardID boards.ID) { setBoardReadonly(boardID, false) } // IsBoardFrozen checks if a board has been frozen. -func IsBoardFrozen(boardID BoardID) bool { +func IsBoardFrozen(boardID boards.ID) bool { board := mustGetBoard(boardID) return board.Readonly } @@ -25,131 +27,74 @@ func IsBoardFrozen(boardID BoardID) bool { // FreezeThread freezes a thread so thread cannot be replied, modified or deleted. // // Fails if board is frozen. -func FreezeThread(_ realm, boardID BoardID, threadID PostID) { +func FreezeThread(_ realm, boardID, threadID boards.ID) { setThreadReadonly(boardID, threadID, true) } // UnfreezeThread removes frozen status from a thread. // // Fails if board is frozen. -func UnfreezeThread(_ realm, boardID BoardID, threadID PostID) { +func UnfreezeThread(_ realm, boardID, threadID boards.ID) { setThreadReadonly(boardID, threadID, false) } // IsThreadFrozen checks if a thread has been frozen. // // Returns true if board is frozen. -func IsThreadFrozen(boardID BoardID, threadID PostID) bool { +func IsThreadFrozen(boardID, threadID boards.ID) bool { board := mustGetBoard(boardID) thread := mustGetThread(board, threadID) - assertThreadIsVisible(thread) - return board.Readonly || thread.Readonly } -// UnfreezeReply removes frozen status from a reply. -// -// Fails when parent thread or board are frozen. -func UnfreezeReply(_ realm, boardID BoardID, threadID, replyID PostID) { - // XXX: Is there a use case for also freezing replies? - setReplyReadonly(boardID, threadID, replyID, false) -} - -// FreezeReply freezes a thread reply so it cannot be modified or deleted. -// -// Fails when parent thread or board are frozen. -func FreezeReply(_ realm, boardID BoardID, threadID, replyID PostID) { - setReplyReadonly(boardID, threadID, replyID, true) -} - -// IsReplyFrozen checks if a thread reply has been frozen. -// -// Returns true when board or a parent thread is frozen. -func IsReplyFrozen(boardID BoardID, threadID, replyID PostID) bool { - board := mustGetBoard(boardID) - thread := mustGetThread(board, threadID) - assertThreadIsVisible(thread) - - reply := mustGetReply(thread, replyID) - assertReplyIsVisible(reply) - - return board.Readonly || thread.Readonly || reply.Readonly -} - -func setReplyReadonly(boardID BoardID, threadID, replyID PostID, isReadonly bool) { +func setBoardReadonly(boardID boards.ID, readonly bool) { assertRealmIsNotLocked() board := mustGetBoard(boardID) - assertBoardIsNotFrozen(board) - - caller := runtime.PreviousRealm().Address() - assertHasPermission(board.perms, caller, PermissionReplyFreeze) - - thread := mustGetThread(board, threadID) - assertThreadIsVisible(thread) - assertThreadIsNotFrozen(thread) - - reply := mustGetReply(thread, replyID) - assertReplyIsVisible(reply) - - if isReadonly { - assertReplyIsNotFrozen(reply) + if readonly { + assertBoardIsNotFrozen(board) } - reply.Readonly = isReadonly - - chain.Emit( - "ReplyFreeze", - "caller", caller.String(), - "boardID", boardID.String(), - "threadID", threadID.String(), - "replyID", replyID.String(), - "frozen", strconv.FormatBool(isReadonly), - ) + caller := runtime.PreviousRealm().Address() + args := boards.Args{caller, board.ID, readonly} + board.Permissions.WithPermission(cross, caller, PermissionBoardFreeze, args, func(realm) { + assertRealmIsNotLocked() + + board.Readonly = readonly + + chain.Emit( + "BoardFreeze", + "caller", caller.String(), + "boardID", board.ID.String(), + "frozen", strconv.FormatBool(readonly), + ) + }) } -func setThreadReadonly(boardID BoardID, threadID PostID, isReadonly bool) { +func setThreadReadonly(boardID, threadID boards.ID, readonly bool) { assertRealmIsNotLocked() board := mustGetBoard(boardID) assertBoardIsNotFrozen(board) - caller := runtime.PreviousRealm().Address() - assertHasPermission(board.perms, caller, PermissionThreadFreeze) - thread := mustGetThread(board, threadID) - if isReadonly { + if readonly { assertThreadIsNotFrozen(thread) } - thread.Readonly = isReadonly - - chain.Emit( - "ThreadFreeze", - "caller", caller.String(), - "boardID", boardID.String(), - "threadID", threadID.String(), - "frozen", strconv.FormatBool(isReadonly), - ) -} - -func setBoardReadonly(boardID BoardID, isReadonly bool) { - assertRealmIsNotLocked() - - board := mustGetBoard(boardID) - if isReadonly { - assertBoardIsNotFrozen(board) - } - caller := runtime.PreviousRealm().Address() - assertHasPermission(board.perms, caller, PermissionBoardFreeze) - - board.Readonly = isReadonly - - chain.Emit( - "BoardFreeze", - "caller", caller.String(), - "boardID", boardID.String(), - "frozen", strconv.FormatBool(isReadonly), - ) + args := boards.Args{caller, board.ID, thread.ID, readonly} + board.Permissions.WithPermission(cross, caller, PermissionThreadFreeze, args, func(realm) { + assertRealmIsNotLocked() + + thread.Readonly = readonly + + chain.Emit( + "ThreadFreeze", + "caller", caller.String(), + "boardID", board.ID.String(), + "threadID", thread.ID.String(), + "frozen", strconv.FormatBool(readonly), + ) + }) } diff --git a/examples/gno.land/r/gnoland/boards2/v1/public_invite.gno b/examples/gno.land/r/gnoland/boards2/v1/public_invite.gno index 88dbcc1898d..850b756adbf 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/public_invite.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/public_invite.gno @@ -6,6 +6,7 @@ import ( "strings" "time" + "gno.land/p/gnoland/boards" "gno.land/p/nt/avl" ) @@ -15,13 +16,13 @@ type Invite struct { User address // Role is the optional role to assign to the user. - Role Role + Role boards.Role } // InviteMember adds a member to the realm or to a board. // // A role can optionally be specified to be assigned to the new member. -func InviteMember(_ realm, boardID BoardID, user address, role Role) { +func InviteMember(_ realm, boardID boards.ID, user address, role boards.Role) { inviteMembers(boardID, Invite{ User: user, Role: role, @@ -31,12 +32,12 @@ func InviteMember(_ realm, boardID BoardID, user address, role Role) { // InviteMembers adds one or more members to the realm or to a board. // // Board ID is only required when inviting a member to a specific board. -func InviteMembers(_ realm, boardID BoardID, invites ...Invite) { +func InviteMembers(_ realm, boardID boards.ID, invites ...Invite) { inviteMembers(boardID, invites...) } // RequestInvite request to be invited to a board. -func RequestInvite(_ realm, boardID BoardID) { +func RequestInvite(_ realm, boardID boards.ID) { assertMembersUpdateIsEnabled(boardID) if !runtime.PreviousRealm().IsUser() { @@ -48,7 +49,7 @@ func RequestInvite(_ realm, boardID BoardID) { board := mustGetBoard(boardID) user := runtime.PreviousRealm().Address() - if board.perms.HasUser(user) { + if board.Permissions.HasUser(user) { panic("caller is already a member") } @@ -69,12 +70,12 @@ func RequestInvite(_ realm, boardID BoardID) { } // AcceptInvite accepts a board invite request. -func AcceptInvite(_ realm, boardID BoardID, user address) { +func AcceptInvite(_ realm, boardID boards.ID, user address) { assertMembersUpdateIsEnabled(boardID) assertInviteRequestExists(boardID, user) board := mustGetBoard(boardID) - if board.perms.HasUser(user) { + if board.Permissions.HasUser(user) { panic("user is already a member") } @@ -83,8 +84,8 @@ func AcceptInvite(_ realm, boardID BoardID, user address) { User: user, Role: RoleGuest, } - args := Args{caller, boardID, []Invite{invite}} - board.perms.WithPermission(cross, caller, PermissionMemberInvite, args, func(realm) { + args := boards.Args{caller, boardID, []Invite{invite}} + board.Permissions.WithPermission(cross, caller, PermissionMemberInvite, args, func(realm) { assertMembersUpdateIsEnabled(boardID) invitee := user.String() @@ -93,31 +94,30 @@ func AcceptInvite(_ realm, boardID BoardID, user address) { panic("invite request not found") } - board := mustGetBoard(boardID) - if board.perms.HasUser(user) { + if board.Permissions.HasUser(user) { panic("user is already a member") } - board.perms.SetUserRoles(cross, user) + board.Permissions.SetUserRoles(cross, user) requests.Remove(invitee) chain.Emit( "MembersInvited", "invitedBy", caller.String(), - "boardID", boardID.String(), + "boardID", board.ID.String(), "members", user.String()+":"+string(RoleGuest), // TODO: Support optional role assign ) }) } // RevokeInvite revokes a board invite request. -func RevokeInvite(_ realm, boardID BoardID, user address) { +func RevokeInvite(_ realm, boardID boards.ID, user address) { assertInviteRequestExists(boardID, user) board := mustGetBoard(boardID) caller := runtime.PreviousRealm().Address() - args := Args{boardID, user, RoleGuest} - board.perms.WithPermission(cross, caller, PermissionMemberInviteRevoke, args, func(realm) { + args := boards.Args{boardID, user, RoleGuest} + board.Permissions.WithPermission(cross, caller, PermissionMemberInviteRevoke, args, func(realm) { invitee := user.String() requests, found := getInviteRequests(boardID) if !found || !requests.Has(invitee) { @@ -129,13 +129,13 @@ func RevokeInvite(_ realm, boardID BoardID, user address) { chain.Emit( "InviteRevoked", "revokedBy", caller.String(), - "boardID", boardID.String(), + "boardID", board.ID.String(), "user", user.String(), ) }) } -func inviteMembers(boardID BoardID, invites ...Invite) { +func inviteMembers(boardID boards.ID, invites ...Invite) { assertMembersUpdateIsEnabled(boardID) if len(invites) == 0 { @@ -144,11 +144,10 @@ func inviteMembers(boardID BoardID, invites ...Invite) { perms := mustGetPermissions(boardID) caller := runtime.PreviousRealm().Address() - args := Args{caller, boardID, invites} + args := boards.Args{caller, boardID, invites} perms.WithPermission(cross, caller, PermissionMemberInvite, args, func(realm) { assertMembersUpdateIsEnabled(boardID) - perms := mustGetPermissions(boardID) users := make([]string, len(invites)) for _, v := range invites { assertMemberAddressIsValid(v.User) @@ -171,7 +170,7 @@ func inviteMembers(boardID BoardID, invites ...Invite) { }) } -func assertInviteRequestExists(boardID BoardID, user address) { +func assertInviteRequestExists(boardID boards.ID, user address) { invitee := user.String() requests, found := getInviteRequests(boardID) if !found || !requests.Has(invitee) { diff --git a/examples/gno.land/r/gnoland/boards2/v1/public_lock.gno b/examples/gno.land/r/gnoland/boards2/v1/public_lock.gno index 4c1345395cd..7de84993aa2 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/public_lock.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/public_lock.gno @@ -4,6 +4,8 @@ import ( "chain" "chain/runtime" "strconv" + + "gno.land/p/gnoland/boards" ) // LockRealm locks the realm making it readonly. @@ -24,7 +26,7 @@ func LockRealm(_ realm, lockRealmMembers bool) { } caller := runtime.PreviousRealm().Address() - gPerms.WithPermission(cross, caller, PermissionRealmLock, Args{}, func(realm) { + gPerms.WithPermission(cross, caller, PermissionRealmLock, boards.Args{}, func(realm) { gLocked.realm = true gLocked.realmMembers = lockRealmMembers diff --git a/examples/gno.land/r/gnoland/boards2/v1/render.gno b/examples/gno.land/r/gnoland/boards2/v1/render.gno index 30e12692997..692ff5c6c5a 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/render.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/render.gno @@ -6,6 +6,7 @@ import ( "strings" "time" + "gno.land/p/gnoland/boards" "gno.land/p/jeronimoalbi/pager" "gno.land/p/moul/md" "gno.land/p/moul/mdtable" @@ -67,28 +68,27 @@ func renderBoardsList(res *mux.ResponseWriter, req *mux.Request) { renderBoardListMenu(res, req) res.Write(md.HorizontalRule()) - boards := gListedBoardsByID - if boards.Size() == 0 { + if gListedBoardsByID.Size() == 0 { link := gRealmLink.Call("CreateBoard", "name", "", "listed", "true") res.Write(md.H3("Currently there are no boards")) res.Write("Be the first to " + md.Link("create a new board", link) + " !") return } - p, err := pager.New(req.RawPath, boards.Size(), pager.WithPageSize(pageSizeDefault)) + p, err := pager.New(req.RawPath, gListedBoardsByID.Size(), pager.WithPageSize(pageSizeDefault)) if err != nil { panic(err) } render := func(_ string, v any) bool { - board := v.(*Board) + board := v.(*boards.Board) userLink := userLink(board.Creator) - date := board.CreatedAt().Format(dateFormat) + date := board.CreatedAt.Format(dateFormat) res.Write(md.Bold(md.Link(board.Name, makeBoardURI(board))) + " \n") res.Write("Created by " + userLink + " on " + date + ", #" + board.ID.String() + " \n") - status := strconv.Itoa(board.ThreadsCount()) + " threads" + status := strconv.Itoa(board.Threads.Size()) + " threads" if board.Readonly { status += ", read-only" } @@ -102,11 +102,11 @@ func renderBoardsList(res *mux.ResponseWriter, req *mux.Request) { if r.Query.Get("order") == "desc" { r.Query.Set("order", "asc") res.Write(md.Link("newest first", r.String()) + "\n\n") - boards.ReverseIterateByOffset(p.Offset(), p.PageSize(), render) + gListedBoardsByID.ReverseIterateByOffset(p.Offset(), p.PageSize(), render) } else { r.Query.Set("order", "desc") res.Write(md.Link("oldest first", r.String()) + "\n\n") - boards.IterateByOffset(p.Offset(), p.PageSize(), render) + gListedBoardsByID.IterateByOffset(p.Offset(), p.PageSize(), render) } if p.HasPages() { @@ -126,26 +126,13 @@ func renderBoardListMenu(res *mux.ResponseWriter, req *mux.Request) { res.Write("\n\n") } -func renderBoard(res *mux.ResponseWriter, req *mux.Request) { - name := req.GetVar("board") - board, found := getBoardByName(name) - if !found { - link := md.Link("create a new board", gRealmLink.Call("CreateBoard", "name", name, "listed", "true")) - res.Write(md.H3("The board you are looking for does not exist")) - res.Write("Do you want to " + link + " ?") - return - } - - menu := renderBoardMenu(board, req) - res.Write(board.Render(req.RawPath, menu)) -} - -func renderBoardMenu(board *Board, req *mux.Request) string { +func renderBoardMenu(board *boards.Board, req *mux.Request) string { var ( b strings.Builder boardMembersURL = makeBoardURI(board) + "/members" ) + b.WriteString("\n") if board.Readonly { b.WriteString(md.Link("List Members", boardMembersURL)) b.WriteString(" • ") @@ -181,87 +168,23 @@ func renderBoardMenu(board *Board, req *mux.Request) string { } } + b.WriteString("\n") return b.String() } -func renderThread(res *mux.ResponseWriter, req *mux.Request) { - name := req.GetVar("board") - board, found := getBoardByName(name) - if !found { - res.Write("Board does not exist: " + name) - return - } - - rawID := req.GetVar("thread") - tID, err := strconv.Atoi(rawID) - if err != nil { - res.Write("Invalid thread ID: " + rawID) - return - } - - thread, found := board.GetThread(PostID(tID)) - if !found { - res.Write("Thread does not exist with ID: " + rawID) - } else if thread.Hidden { - res.Write("Thread with ID: " + rawID + " has been flagged as inappropriate") - } else { - res.Write(thread.Render(req.RawPath, "", 5)) - } -} - -func renderReply(res *mux.ResponseWriter, req *mux.Request) { - name := req.GetVar("board") - board, found := getBoardByName(name) - if !found { - res.Write("Board does not exist: " + name) - return - } - - rawID := req.GetVar("thread") - tID, err := strconv.Atoi(rawID) - if err != nil { - res.Write("Invalid thread ID: " + rawID) - return - } - - rawID = req.GetVar("reply") - rID, err := strconv.Atoi(rawID) - if err != nil { - res.Write("Invalid reply ID: " + rawID) - return - } - - thread, found := board.GetThread(PostID(tID)) - if !found { - res.Write("Thread does not exist with ID: " + req.GetVar("thread")) - return - } - - reply, found := thread.GetReply(PostID(rID)) - if !found { - res.Write("Reply does not exist with ID: " + rawID) - return - } - - // Call render even for hidden replies to display children. - // Original comment content will be hidden under the hood. - // See: #3480 - res.Write(reply.RenderInner()) -} - func renderMembers(res *mux.ResponseWriter, req *mux.Request) { - boardID := BoardID(0) + boardID := boards.ID(0) perms := gPerms name := req.GetVar("board") if name != "" { - board, found := getBoardByName(name) + board, found := gBoards.GetByName(name) if !found { res.Write(md.H3("Board not found")) return } boardID = board.ID - perms = board.perms + perms = board.Permissions res.Write(md.H1(board.Name + " Members")) res.Write(md.H3("These are the board members")) @@ -282,7 +205,7 @@ func renderMembers(res *mux.ResponseWriter, req *mux.Request) { Headers: []string{"Member", "Role", "Actions"}, } - perms.IterateUsers(p.Offset(), p.PageSize(), func(u User) bool { + perms.IterateUsers(p.Offset(), p.PageSize(), func(u boards.User) bool { actions := []string{ md.Link("remove", gRealmLink.Call( "RemoveMember", @@ -313,7 +236,7 @@ func renderMembers(res *mux.ResponseWriter, req *mux.Request) { func renderInvites(res *mux.ResponseWriter, req *mux.Request) { name := req.GetVar("board") - board, found := getBoardByName(name) + board, found := gBoards.GetByName(name) if !found { res.Write(md.H3("Board not found")) return @@ -369,7 +292,7 @@ func renderInvites(res *mux.ResponseWriter, req *mux.Request) { func renderBannedUsers(res *mux.ResponseWriter, req *mux.Request) { name := req.GetVar("board") - board, found := getBoardByName(name) + board, found := gBoards.GetByName(name) if !found { res.Write(md.H3("Board not found")) return @@ -420,7 +343,7 @@ func infoAlert(title, msg string) string { return md.Blockquote(header + "\n" + msg) } -func rolesToString(roles []Role) string { +func rolesToString(roles []boards.Role) string { if len(roles) == 0 { return "" } diff --git a/examples/gno.land/r/gnoland/boards2/v1/render_board.gno b/examples/gno.land/r/gnoland/boards2/v1/render_board.gno new file mode 100644 index 00000000000..b6048dad320 --- /dev/null +++ b/examples/gno.land/r/gnoland/boards2/v1/render_board.gno @@ -0,0 +1,76 @@ +package boards2 + +import ( + "gno.land/p/gnoland/boards" + "gno.land/p/jeronimoalbi/pager" + "gno.land/p/moul/md" + "gno.land/p/nt/mux" +) + +func renderBoard(res *mux.ResponseWriter, req *mux.Request) { + name := req.GetVar("board") + board, found := gBoards.GetByName(name) + if !found { + link := md.Link("create a new board", gRealmLink.Call("CreateBoard", "name", name, "listed", "true")) + res.Write(md.H3("The board you are looking for does not exist")) + res.Write("Do you want to " + link + " ?") + return + } + + creatorLink := userLink(board.Creator) + date := board.CreatedAt.Format(dateFormat) + + res.Write(md.H1(board.Name)) + res.Write("Board created by " + creatorLink + " on " + date + ", #" + board.ID.String()) + if board.Readonly { + res.Write(" \n_" + md.Bold("Starting new threads and commenting is disabled") + "_") + } + + res.Write("\n" + renderBoardMenu(board, req)) + res.Write(md.HorizontalRule()) + + if board.Threads.Size() == 0 { + res.Write(md.H3("This board doesn't have any threads")) + if !board.Readonly { + startConversationLink := md.Link("start a new conversation", makeCreateThreadURI(board)) + res.Write("Do you want to " + startConversationLink + " in this board ?") + } + return + } + + p, err := pager.New(req.RawPath, board.Threads.Size(), pager.WithPageSize(pageSizeDefault)) + if err != nil { + panic(err) + } + + render := func(thread *boards.Post) bool { + if !thread.Hidden { + res.Write(renderThreadSummary(thread) + "\n") + } + return false + } + + res.Write("Sort by: ") + + r := parseRealmPath(req.RawPath) + sortOrder := r.Query.Get("order") + if sortOrder == "desc" { + r.Query.Set("order", "asc") + res.Write(md.Link("newest first", r.String()) + "\n\n") + } else { + r.Query.Set("order", "desc") + res.Write(md.Link("oldest first", r.String()) + "\n\n") + } + + count := p.PageSize() + if sortOrder == "desc" { + count = -count // Reverse iterate + } + + board.Threads.Iterate(p.Offset(), count, render) + + if p.HasPages() { + res.Write(md.HorizontalRule()) + res.Write(pager.Picker(p)) + } +} diff --git a/examples/gno.land/r/gnoland/boards2/v1/render_post.gno b/examples/gno.land/r/gnoland/boards2/v1/render_post.gno new file mode 100644 index 00000000000..dd1fd53c9a3 --- /dev/null +++ b/examples/gno.land/r/gnoland/boards2/v1/render_post.gno @@ -0,0 +1,180 @@ +package boards2 + +import ( + "strconv" + "strings" + + "gno.land/p/gnoland/boards" + "gno.land/p/moul/md" +) + +func renderPost(post *boards.Post, path, indent string, levels int) string { + var b strings.Builder + + // Thread reposts might not have a title, if so get title from source thread + title := post.Title + if boards.IsRepost(post) && title == "" { + if board, ok := gBoards.Get(post.OriginalBoardID); ok { + if src, ok := board.Threads.Get(post.ParentID); ok { + title = src.Title + } + } + } + + if title != "" { // Replies don't have a title + b.WriteString(md.H1(title)) + } + + b.WriteString(indent + "\n") + b.WriteString(renderPostContent(post, indent, levels)) + + if post.Replies.Size() == 0 { + return b.String() + } + + // XXX: This triggers for reply views + if levels == 0 { + b.WriteString(indent + "\n") + return b.String() + } + + if path != "" { + b.WriteString(renderTopLevelReplies(post, path, indent, levels-1)) + } else { + b.WriteString(renderSubReplies(post, indent, levels-1)) + } + return b.String() +} + +func renderPostContent(post *boards.Post, indent string, levels int) string { + var b strings.Builder + + if post.Hidden { + // Flagged comment should be hidden, but replies still visible (see: #3480) + // Flagged threads will be hidden by render function caller. + return indentBody(indent, md.Italic("⚠ Reply is hidden as it has been flagged as inappropriate")) + "\n" + } + + srcContent, srcPost := renderSourcePost(post, indent) + if boards.IsRepost(post) && srcPost != nil { + originLink := md.Link("another thread", makeThreadURI(srcPost)) + b.WriteString(" \nThis thread is a repost of " + originLink + ": \n") + } + + b.WriteString(srcContent) + + if boards.IsRepost(post) && srcPost == nil && len(post.Body) > 0 { + // Add a newline to separate source deleted message from repost body content + b.WriteString("\n") + } + + b.WriteString(indentBody(indent, post.Body)) + b.WriteString("\n") + + if boards.IsThread(post) { + // Split content and controls for threads. + b.WriteString("\n") + } + + // Buttons & counters + b.WriteString(indent) + if !boards.IsThread(post) { + b.WriteString(" \n") + b.WriteString(indent) + } + + creatorLink := userLink(post.Creator) + date := post.CreatedAt.Format(dateFormat) + b.WriteString("Created by " + creatorLink + " on " + date) + + // Add a reply view link to each top level reply + if !boards.IsThread(post) { + b.WriteString(", " + md.Link("#"+post.ID.String(), makeReplyURI(post))) + } else if post.Reposts.Size() > 0 { // Post is a thread + b.WriteString(", " + strconv.Itoa(post.Reposts.Size()) + " repost(s)") + } + + b.WriteString(" \n") + + actions := []string{ + md.Link("Flag", makeFlagURI(post)), + } + + if boards.IsThread(post) { + actions = append(actions, md.Link("Repost", makeCreateRepostURI(post))) + } + + isReadonly := post.Readonly || post.Board.Readonly + if !isReadonly { + actions = append( + actions, + md.Link("Reply", makeCreateReplyURI(post)), + md.Link("Edit", makeEditPostURI(post)), + md.Link("Delete", makeDeletePostURI(post)), + ) + } + + if levels == 0 { + if boards.IsThread(post) { + actions = append(actions, md.Link("Show all Replies", makeThreadURI(post))) + } else { + actions = append(actions, md.Link("View Thread", makeThreadURI(post))) + } + } + + b.WriteString(strings.Join(actions, " • ") + " \n") + return b.String() +} + +func renderPostInner(post *boards.Post) string { + if boards.IsThread(post) { + return "" + } + + var ( + s string + threadID = post.ThreadID + thread, _ = post.Board.Threads.Get(threadID) + ) + + // Fully render parent if it's not a repost. + if !boards.IsRepost(post) { + parentID := post.ParentID + parent := thread + + if thread.ID != parentID { + parent, _ = thread.Replies.Get(parentID) + } + + s += renderPost(parent, "", "", 0) + "\n" + } + + s += renderPost(post, "", "> ", 5) + return s +} + +func renderSourcePost(post *boards.Post, indent string) (string, *boards.Post) { + if !boards.IsRepost(post) { + return "", nil + } + + indent += "> " + + // TODO: figure out a way to decouple posts from a global storage. + board, ok := gBoards.Get(post.OriginalBoardID) + if !ok { + // TODO: Boards can't be deleted so this might be redundant + return indentBody(indent, md.Italic("⚠ Source board has been deleted")+"\n"), nil + } + + srcPost, ok := board.Threads.Get(post.ParentID) + if !ok { + return indentBody(indent, md.Italic("⚠ Source post has been deleted")+"\n"), nil + } + + if srcPost.Hidden { + return indentBody(indent, md.Italic("⚠ Source post has been flagged as inappropriate")+"\n"), nil + } + + return indentBody(indent, srcPost.Summary()) + "\n\n", srcPost +} diff --git a/examples/gno.land/r/gnoland/boards2/v1/render_reply.gno b/examples/gno.land/r/gnoland/boards2/v1/render_reply.gno new file mode 100644 index 00000000000..fb65d252acb --- /dev/null +++ b/examples/gno.land/r/gnoland/boards2/v1/render_reply.gno @@ -0,0 +1,107 @@ +package boards2 + +import ( + "strconv" + "strings" + + "gno.land/p/gnoland/boards" + "gno.land/p/jeronimoalbi/pager" + "gno.land/p/moul/md" + "gno.land/p/nt/mux" +) + +func renderReply(res *mux.ResponseWriter, req *mux.Request) { + name := req.GetVar("board") + board, found := gBoards.GetByName(name) + if !found { + res.Write("Board does not exist: " + name) + return + } + + rawID := req.GetVar("thread") + tID, err := strconv.Atoi(rawID) + if err != nil { + res.Write("Invalid thread ID: " + rawID) + return + } + + rawID = req.GetVar("reply") + rID, err := strconv.Atoi(rawID) + if err != nil { + res.Write("Invalid reply ID: " + rawID) + return + } + + thread, found := board.Threads.Get(boards.ID(tID)) + if !found { + res.Write("Thread does not exist with ID: " + req.GetVar("thread")) + return + } + + reply, found := thread.Replies.Get(boards.ID(rID)) + if !found { + res.Write("Reply does not exist with ID: " + rawID) + return + } + + // Call render even for hidden replies to display children. + // Original comment content will be hidden under the hood. + // See: #3480 + res.Write(renderPostInner(reply)) +} + +func renderTopLevelReplies(post *boards.Post, path, indent string, levels int) string { + p, err := pager.New(path, post.Replies.Size(), pager.WithPageSize(pageSizeReplies)) + if err != nil { + panic(err) + } + + var ( + b strings.Builder + commentsIndent = indent + "> " + ) + + render := func(reply *boards.Post) bool { + b.WriteString(indent + "\n" + renderPost(reply, "", commentsIndent, levels-1)) + return false + } + + b.WriteString("\n" + md.HorizontalRule() + "Sort by: ") + + r := parseRealmPath(path) + sortOrder := r.Query.Get("order") + if sortOrder == "desc" { + r.Query.Set("order", "asc") + b.WriteString(md.Link("newest first", r.String()) + "\n") + + } else { + r.Query.Set("order", "desc") + b.WriteString(md.Link("oldest first", r.String()) + "\n") + } + + count := p.PageSize() + if sortOrder == "desc" { + count = -count // Reverse iterate + } + + post.Replies.Iterate(p.Offset(), count, render) + + if p.HasPages() { + b.WriteString(md.HorizontalRule()) + b.WriteString(pager.Picker(p)) + } + return b.String() +} + +func renderSubReplies(post *boards.Post, indent string, levels int) string { + var ( + b strings.Builder + commentsIndent = indent + "> " + ) + + post.Replies.Iterate(0, post.Replies.Size(), func(reply *boards.Post) bool { + b.WriteString(indent + "\n" + renderPost(reply, "", commentsIndent, levels-1)) + return false + }) + return b.String() +} diff --git a/examples/gno.land/r/gnoland/boards2/v1/render_thread.gno b/examples/gno.land/r/gnoland/boards2/v1/render_thread.gno new file mode 100644 index 00000000000..d648ff1c233 --- /dev/null +++ b/examples/gno.land/r/gnoland/boards2/v1/render_thread.gno @@ -0,0 +1,59 @@ +package boards2 + +import ( + "strconv" + "strings" + + "gno.land/p/gnoland/boards" + "gno.land/p/moul/md" + "gno.land/p/nt/mux" +) + +func renderThread(res *mux.ResponseWriter, req *mux.Request) { + name := req.GetVar("board") + board, found := gBoards.GetByName(name) + if !found { + res.Write("Board does not exist: " + name) + return + } + + rawID := req.GetVar("thread") + tID, err := strconv.Atoi(rawID) + if err != nil { + res.Write("Invalid thread ID: " + rawID) + return + } + + thread, found := board.Threads.Get(boards.ID(tID)) + if !found { + res.Write("Thread does not exist with ID: " + rawID) + return + } + + if thread.Hidden { + res.Write("Thread with ID: " + rawID + " has been flagged as inappropriate") + return + } + + res.Write(renderPost(thread, req.RawPath, "", 5)) +} + +func renderThreadSummary(thread *boards.Post) string { + var ( + b strings.Builder + postURI = makeThreadURI(thread) + summary = summaryOf(thread.Title, 80) + creatorLink = userLink(thread.Creator) + date = thread.CreatedAt.Format(dateFormat) + ) + + b.WriteString(md.Bold("≡ "+md.Link(summary, postURI)) + " \n") + b.WriteString("Created by " + creatorLink + " on " + date + " \n") + + status := []string{ + strconv.Itoa(thread.Replies.Size()) + " replies", + strconv.Itoa(thread.Reposts.Size()) + " reposts", + } + b.WriteString(md.Bold(strings.Join(status, " • ")) + "\n") + return b.String() +} diff --git a/examples/gno.land/r/gnoland/boards2/v1/replies_storage.gno b/examples/gno.land/r/gnoland/boards2/v1/replies_storage.gno new file mode 100644 index 00000000000..b6c12bb026c --- /dev/null +++ b/examples/gno.land/r/gnoland/boards2/v1/replies_storage.gno @@ -0,0 +1,93 @@ +package boards2 + +import ( + "errors" + + "gno.land/p/gnoland/boards" + "gno.land/p/nt/avl" +) + +// NewReplyStorage creates a new storage for thread replies. +// This is a customized post storage that also keeps a flat index with all replies +// that exists within a thread, which allows to get any reply from their thread. +func NewReplyStorage() boards.PostStorage { + return &replyStorage{ + PostStorage: boards.NewPostStorage(), + all: avl.NewTree(), + } +} + +type replyStorage struct { + boards.PostStorage + + // Flat index to store all replies that exists within a thread + all *avl.Tree // string(Post.ID) -> *Post +} + +// Get retruns a reply that matches an ID. +// Reply can be a direct thread reply or a sub-reply. +func (s replyStorage) Get(id boards.ID) (*boards.Post, bool) { + post, found := s.PostStorage.Get(id) + if found { + return post, true + } + + k := makeReplyKey(id) + v, found := s.all.Get(k) + if !found { + return nil, false + } + return v.(*boards.Post), true +} + +// Remove removes a post from the storage. +func (s *replyStorage) Remove(id boards.ID) (*boards.Post, bool) { + post, removed := s.PostStorage.Remove(id) + if removed { + return post, true + } + + // When reply is not a direct thread reply try to remove it from the flat index + k := makeReplyKey(id) + v, removed := s.all.Remove(k) + if !removed { + return nil, false + } + return v.(*boards.Post), true +} + +// Add adds a post in the storage. +// It updates existing posts when storage contains one with the same ID. +func (s *replyStorage) Add(p *boards.Post) error { + if p == nil { + return errors.New("saving nil replies is not allowed") + } + + // If post is a direct thread child add it to the post storage + if p.ParentID == p.ThreadID { + return s.PostStorage.Add(p) + } + + // Otherwise when post is a sub-reply add it to the flat index + k := makeReplyKey(p.ID) + s.all.Set(k, p) + return nil +} + +// Size returns the number of direct replies in the storage. +// It doesn't includes sub-replies. +func (s replyStorage) Size() int { + return s.PostStorage.Size() +} + +// Iterate iterates direct replies. +// To reverse iterate posts use a negative count. +// If the callback returns true, the iteration is stopped. +// Sub-replies are NOT iterated. +func (s replyStorage) Iterate(start, count int, fn boards.PostIterFn) bool { + return s.PostStorage.Iterate(start, count, fn) +} + +func makeReplyKey(id boards.ID) string { + return id.Key() +} diff --git a/examples/gno.land/r/gnoland/boards2/v1/replies_storage_test.gno b/examples/gno.land/r/gnoland/boards2/v1/replies_storage_test.gno new file mode 100644 index 00000000000..3bdf1764b0b --- /dev/null +++ b/examples/gno.land/r/gnoland/boards2/v1/replies_storage_test.gno @@ -0,0 +1,319 @@ +package boards2_test + +import ( + "testing" + + "gno.land/p/gnoland/boards" + "gno.land/p/nt/urequire" + + boards2 "gno.land/r/gnoland/boards2/v1" +) + +func TestReplyStorageGet(t *testing.T) { + creator := address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") + tests := []struct { + name string + setup func(thread *boards.Post) boards.PostStorage + postID boards.ID + found bool + }{ + { + name: "single reply", + setup: func(t *boards.Post) boards.PostStorage { + s := boards2.NewReplyStorage() + s.Add(boards.MustNewReply(t, creator, "A")) // ID=2 + return s + }, + postID: 2, + found: true, + }, + { + name: "multiple replies", + setup: func(t *boards.Post) boards.PostStorage { + s := boards2.NewReplyStorage() + s.Add(boards.MustNewReply(t, creator, "A")) + s.Add(boards.MustNewReply(t, creator, "B")) // ID=3 + s.Add(boards.MustNewReply(t, creator, "C")) + return s + }, + postID: 3, + found: true, + }, + { + name: "single sub reply", + setup: func(t *boards.Post) boards.PostStorage { + parent := boards.MustNewReply(t, creator, "A") + s := boards2.NewReplyStorage() + s.Add(parent) + s.Add(boards.MustNewReply(parent, creator, "B")) // ID=3 + return s + }, + postID: 3, + found: true, + }, + { + name: "multiple sub replies", + setup: func(t *boards.Post) boards.PostStorage { + parent := boards.MustNewReply(t, creator, "A") + s := boards2.NewReplyStorage() + s.Add(parent) + s.Add(boards.MustNewReply(parent, creator, "C")) + s.Add(boards.MustNewReply(parent, creator, "D")) // ID=4 + s.Add(boards.MustNewReply(parent, creator, "E")) + return s + }, + postID: 4, + found: true, + }, + { + name: "not found", + setup: func(*boards.Post) boards.PostStorage { + return boards2.NewReplyStorage() + }, + postID: 404, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + board := boards.New(1) + thread := boards.MustNewThread(board, creator, "Title", "Body") + s := tt.setup(thread) + + reply, found := s.Get(tt.postID) + + if !tt.found { + urequire.False(t, found, "expect reply not to be found") + urequire.True(t, reply == nil, "expect reply to be nil") + return + } + + urequire.True(t, found, "expect reply to be found") + urequire.False(t, reply == nil, "expect reply not to be nil") + urequire.Equal(t, tt.postID.String(), reply.ID.String(), "expect reply ID to match") + }) + } +} + +func TestReplyStorageRemove(t *testing.T) { + creator := address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") + tests := []struct { + name string + setup func(thread *boards.Post) boards.PostStorage + postID boards.ID + removed bool + }{ + { + name: "reply", + setup: func(t *boards.Post) boards.PostStorage { + s := boards2.NewReplyStorage() + s.Add(boards.MustNewReply(t, creator, "A")) + s.Add(boards.MustNewReply(t, creator, "B")) // ID=3 + return s + }, + postID: 3, + removed: true, + }, + { + name: "sub reply", + setup: func(t *boards.Post) boards.PostStorage { + parent := boards.MustNewReply(t, creator, "A") + s := boards2.NewReplyStorage() + s.Add(parent) + s.Add(boards.MustNewReply(parent, creator, "A")) + s.Add(boards.MustNewReply(parent, creator, "B")) // ID=4 + return s + }, + postID: 4, + removed: true, + }, + { + name: "not found", + setup: func(*boards.Post) boards.PostStorage { + return boards2.NewReplyStorage() + }, + postID: 404, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + board := boards.New(1) + thread := boards.MustNewThread(board, creator, "Title", "Body") + s := tt.setup(thread) + + reply, removed := s.Remove(tt.postID) + + if !tt.removed { + urequire.False(t, removed, "expect reply not to be removed") + urequire.True(t, reply == nil, "expect reply to be nil") + return + } + + urequire.True(t, removed, "expect reply to be removed") + urequire.False(t, reply == nil, "expect reply not to be nil") + urequire.Equal(t, tt.postID.String(), reply.ID.String(), "expect reply ID to match") + + _, found := s.Get(tt.postID) + urequire.False(t, found, "expect reply not to be found") + }) + } +} + +func TestReplyStorageAdd(t *testing.T) { + tests := []struct { + name string + post *boards.Post + errMsg string + }{ + { + name: "reply", + post: &boards.Post{ID: 2, ParentID: 1, ThreadID: 1}, + }, + { + name: "sub reply", + post: &boards.Post{ID: 3, ParentID: 2, ThreadID: 1}, + }, + { + name: "nil reply", + post: nil, + errMsg: "saving nil replies is not allowed", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := boards2.NewReplyStorage() + + err := s.Add(tt.post) + + if tt.errMsg != "" { + urequire.Error(t, err, "expect an error") + urequire.ErrorContains(t, err, tt.errMsg, "expect error to match") + return + } + + urequire.NoError(t, err, "expect no error") + + _, found := s.Get(tt.post.ID) + urequire.True(t, found, "expect reply to be found") + }) + } +} + +func TestReplyStorageSize(t *testing.T) { + creator := address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") + tests := []struct { + name string + setup func(thread *boards.Post) boards.PostStorage + size int + }{ + { + name: "empty", + setup: func(*boards.Post) boards.PostStorage { + return boards2.NewReplyStorage() + }, + size: 0, + }, + { + name: "one reply", + setup: func(t *boards.Post) boards.PostStorage { + s := boards2.NewReplyStorage() + s.Add(boards.MustNewReply(t, creator, "A")) + return s + }, + size: 1, + }, + { + name: "multiple replies", + setup: func(t *boards.Post) boards.PostStorage { + s := boards2.NewReplyStorage() + s.Add(boards.MustNewReply(t, creator, "A")) + s.Add(boards.MustNewReply(t, creator, "B")) + return s + }, + size: 2, + }, + { + name: "multiple replies and sub reply", + setup: func(t *boards.Post) boards.PostStorage { + parent := boards.MustNewReply(t, creator, "A") + s := boards2.NewReplyStorage() + s.Add(parent) + s.Add(boards.MustNewReply(t, creator, "B")) + s.Add(boards.MustNewReply(parent, creator, "A2")) + return s + }, + size: 2, // Sub-replies are not counted + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + board := boards.New(1) + thread := boards.MustNewThread(board, creator, "Title", "Body") + s := tt.setup(thread) + + urequire.Equal(t, tt.size, s.Size()) + }) + } +} + +func TestReplyStorageIterate(t *testing.T) { + creator := address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") + tests := []struct { + name string + setup func(thread *boards.Post) boards.PostStorage + reverse bool + ids []boards.ID + }{ + { + name: "default", + setup: func(t *boards.Post) boards.PostStorage { + parent := boards.MustNewReply(t, creator, "A") + s := boards2.NewReplyStorage() + s.Add(parent) + s.Add(boards.MustNewReply(t, creator, "B")) + s.Add(boards.MustNewReply(t, creator, "C")) + s.Add(boards.MustNewReply(parent, creator, "A2")) // Sub-replies are ignored + return s + }, + ids: []boards.ID{2, 3, 4}, + }, + { + name: "reverse", + setup: func(t *boards.Post) boards.PostStorage { + parent := boards.MustNewReply(t, creator, "A") + s := boards2.NewReplyStorage() + s.Add(parent) + s.Add(boards.MustNewReply(t, creator, "B")) + s.Add(boards.MustNewReply(t, creator, "C")) + s.Add(boards.MustNewReply(parent, creator, "A2")) // Sub-replies are ignored + return s + }, + reverse: true, + ids: []boards.ID{4, 3, 2}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + board := boards.New(1) + thread := boards.MustNewThread(board, creator, "Title", "Body") + s := tt.setup(thread) + + count := s.Size() + if tt.reverse { + count = -count + } + + var i int + s.Iterate(0, count, func(p *boards.Post) bool { + urequire.True(t, tt.ids[i] == p.ID, "expect post ID to match") + + i++ + return false + }) + }) + } +} diff --git a/examples/gno.land/r/gnoland/boards2/v1/uris_board.gno b/examples/gno.land/r/gnoland/boards2/v1/uris_board.gno index b2dfee2ba6f..65da29874da 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/uris_board.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/uris_board.gno @@ -3,21 +3,23 @@ package boards2 import ( "net/url" "strings" + + "gno.land/p/gnoland/boards" ) -func makeBoardURI(b *Board) string { +func makeBoardURI(b *boards.Board) string { path := strings.TrimPrefix(string(gRealmLink), "gno.land") return path + ":" + url.PathEscape(b.Name) } -func makeFreezeBoardURI(b *Board) string { +func makeFreezeBoardURI(b *boards.Board) string { return gRealmLink.Call( "FreezeBoard", "boardID", b.ID.String(), ) } -func makeUnfreezeBoardURI(b *Board) string { +func makeUnfreezeBoardURI(b *boards.Board) string { return gRealmLink.Call( "UnfreezeBoard", "boardID", b.ID.String(), @@ -26,7 +28,7 @@ func makeUnfreezeBoardURI(b *Board) string { ) } -func makeInviteMemberURI(b *Board) string { +func makeInviteMemberURI(b *boards.Board) string { return gRealmLink.Call( "InviteMember", "boardID", b.ID.String(), @@ -35,7 +37,7 @@ func makeInviteMemberURI(b *Board) string { ) } -func makeCreateThreadURI(b *Board) string { +func makeCreateThreadURI(b *boards.Board) string { return gRealmLink.Call( "CreateThread", "boardID", b.ID.String(), @@ -44,7 +46,7 @@ func makeCreateThreadURI(b *Board) string { ) } -func makeRequestInviteURI(b *Board) string { +func makeRequestInviteURI(b *boards.Board) string { return gRealmLink.Call( "RequestInvite", "boardID", b.ID.String(), diff --git a/examples/gno.land/r/gnoland/boards2/v1/uris_post.gno b/examples/gno.land/r/gnoland/boards2/v1/uris_post.gno index 4bab29153bd..baff1a84dbf 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/uris_post.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/uris_post.gno @@ -1,7 +1,11 @@ package boards2 -func makeThreadURI(p *Post) string { - if p.IsThread() { +import ( + "gno.land/p/gnoland/boards" +) + +func makeThreadURI(p *boards.Post) string { + if boards.IsThread(p) { return makeBoardURI(p.Board) + "/" + p.ID.String() } @@ -9,12 +13,12 @@ func makeThreadURI(p *Post) string { return makeBoardURI(p.Board) + "/" + p.ThreadID.String() } -func makeReplyURI(p *Post) string { +func makeReplyURI(p *boards.Post) string { return makeBoardURI(p.Board) + "/" + p.ThreadID.String() + "/" + p.ID.String() } -func makeCreateReplyURI(p *Post) string { - if p.IsThread() { +func makeCreateReplyURI(p *boards.Post) string { + if boards.IsThread(p) { return gRealmLink.Call( "CreateReply", "boardID", p.Board.ID.String(), @@ -32,19 +36,19 @@ func makeCreateReplyURI(p *Post) string { ) } -func makeCreateRepostURI(p *Post) string { +func makeCreateRepostURI(p *boards.Post) string { return gRealmLink.Call( "CreateRepost", "boardID", p.Board.ID.String(), "threadID", p.ID.String(), + "destinationBoardID", "", "title", "", "body", "", - "destinationBoardID", "", ) } -func makeDeletePostURI(p *Post) string { - if p.IsThread() { +func makeDeletePostURI(p *boards.Post) string { + if boards.IsThread(p) { return gRealmLink.Call( "DeleteThread", "boardID", p.Board.ID.String(), @@ -59,8 +63,8 @@ func makeDeletePostURI(p *Post) string { ) } -func makeEditPostURI(p *Post) string { - if p.IsThread() { +func makeEditPostURI(p *boards.Post) string { + if boards.IsThread(p) { return gRealmLink.Call( "EditThread", "boardID", p.Board.ID.String(), @@ -79,8 +83,8 @@ func makeEditPostURI(p *Post) string { ) } -func makeFlagURI(p *Post) string { - if p.IsThread() { +func makeFlagURI(p *boards.Post) string { + if boards.IsThread(p) { return gRealmLink.Call( "FlagThread", "boardID", p.Board.ID.String(), diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_10_a_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_10_a_filetest.gno index 57dd21b8196..c56d2125c66 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_10_a_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_10_a_filetest.gno @@ -3,6 +3,8 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) @@ -12,8 +14,8 @@ const ( ) var ( - bid boards2.BoardID - pid boards2.PostID + bid boards.ID + pid boards.ID ) func init() { diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_10_c_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_10_c_filetest.gno index 535f436cb91..41adb2813f3 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_10_c_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_10_c_filetest.gno @@ -3,6 +3,8 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) @@ -12,8 +14,8 @@ const ( ) var ( - bid boards2.BoardID - pid boards2.PostID + bid boards.ID + pid boards.ID ) func init() { diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_10_d_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_10_d_filetest.gno index 3249e870872..1d99b943db3 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_10_d_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_10_d_filetest.gno @@ -3,12 +3,14 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) const owner address = "g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq" // @devx -var bid boards2.BoardID +var bid boards.ID func init() { testing.SetRealm(testing.NewUserRealm(owner)) @@ -22,4 +24,4 @@ func main() { } // Error: -// post doesn't exist +// thread not found diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_10_e_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_10_e_filetest.gno index 72ad0cf4966..fb59ee04748 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_10_e_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_10_e_filetest.gno @@ -3,14 +3,16 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) const owner address = "g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq" // @devx var ( - bid boards2.BoardID - pid boards2.PostID + bid boards.ID + pid boards.ID ) func init() { @@ -27,4 +29,4 @@ func main() { } // Error: -// item flag count threshold exceeded: 1 +// flag count threshold exceeded: 1 diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_10_f_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_10_f_filetest.gno index ab7a6575a21..92eb8ce01dd 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_10_f_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_10_f_filetest.gno @@ -3,6 +3,8 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) @@ -12,8 +14,8 @@ const ( ) var ( - bid boards2.BoardID - pid boards2.PostID + bid boards.ID + pid boards.ID ) func init() { diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_10_g_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_10_g_filetest.gno index 7a0c533a5a6..81a449b5665 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_10_g_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_10_g_filetest.gno @@ -3,6 +3,8 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) @@ -12,8 +14,8 @@ const ( ) var ( - bid boards2.BoardID - pid boards2.PostID + bid boards.ID + pid boards.ID ) func init() { @@ -38,4 +40,4 @@ func main() { } // Error: -// item has been already flagged by g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj +// post has been already flagged by g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_10_h_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_10_h_filetest.gno index 291b155c643..c9ee374d9d2 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_10_h_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_10_h_filetest.gno @@ -3,14 +3,16 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) const owner address = "g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq" // @devx var ( - bid boards2.BoardID - pid boards2.PostID + bid boards.ID + pid boards.ID ) func init() { diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_11_a_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_11_a_filetest.gno index b2c2b1fad3c..0d71871b1fd 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_11_a_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_11_a_filetest.gno @@ -4,6 +4,8 @@ import ( "strings" "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) @@ -15,8 +17,8 @@ const ( ) var ( - bid boards2.BoardID - pid boards2.PostID + bid boards.ID + pid boards.ID ) func init() { diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_11_b_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_11_b_filetest.gno index c18b9a54f1c..075cf36bf8c 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_11_b_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_11_b_filetest.gno @@ -3,14 +3,16 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) const owner address = "g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq" // @devx var ( - bid boards2.BoardID - pid boards2.PostID + bid boards.ID + pid boards.ID ) func init() { diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_11_c_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_11_c_filetest.gno index 3bfd1eb9f9c..5c9a145a6f4 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_11_c_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_11_c_filetest.gno @@ -3,14 +3,16 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) const owner address = "g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq" // @devx var ( - bid boards2.BoardID - pid boards2.PostID + bid boards.ID + pid boards.ID ) func init() { diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_11_e_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_11_e_filetest.gno index 3bfd1eb9f9c..5c9a145a6f4 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_11_e_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_11_e_filetest.gno @@ -3,14 +3,16 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) const owner address = "g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq" // @devx var ( - bid boards2.BoardID - pid boards2.PostID + bid boards.ID + pid boards.ID ) func init() { diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_11_f_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_11_f_filetest.gno index 40f30380adb..c31707c55c7 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_11_f_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_11_f_filetest.gno @@ -3,6 +3,8 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) @@ -12,8 +14,8 @@ const ( ) var ( - bid boards2.BoardID - pid boards2.PostID + bid boards.ID + pid boards.ID ) func init() { diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_11_g_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_11_g_filetest.gno index c239bad9698..d6f0f40794b 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_11_g_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_11_g_filetest.gno @@ -4,6 +4,8 @@ import ( "strings" "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) @@ -16,8 +18,8 @@ const ( ) var ( - bid boards2.BoardID - pid boards2.PostID + bid boards.ID + pid boards.ID ) func init() { diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_11_h_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_11_h_filetest.gno index 0ecf16a7d48..a2a0ddfdc75 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_11_h_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_11_h_filetest.gno @@ -4,14 +4,16 @@ import ( "strings" "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) const owner address = "g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq" // @devx var ( - bid boards2.BoardID - pid boards2.PostID + bid boards.ID + pid boards.ID title = strings.Repeat("X", boards2.MaxThreadTitleLength+1) ) @@ -28,4 +30,4 @@ func main() { } // Error: -// thread title is too long, maximum allowed is 100 characters +// title is too long, maximum allowed is 100 characters diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_12_a_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_12_a_filetest.gno index c86609e1771..a6cadbbadec 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_12_a_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_12_a_filetest.gno @@ -4,6 +4,8 @@ import ( "strings" "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) @@ -14,8 +16,8 @@ const ( ) var ( - bid boards2.BoardID - tid, rid boards2.PostID + bid boards.ID + tid, rid boards.ID ) func init() { diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_12_b_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_12_b_filetest.gno index 4db0566f051..36f7f5c8ffe 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_12_b_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_12_b_filetest.gno @@ -4,6 +4,8 @@ import ( "strings" "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) @@ -14,8 +16,8 @@ const ( ) var ( - bid boards2.BoardID - tid, rid boards2.PostID + bid boards.ID + tid, rid boards.ID ) func init() { diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_12_d_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_12_d_filetest.gno index 94cf7a39947..3e1d35cb1a2 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_12_d_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_12_d_filetest.gno @@ -3,12 +3,14 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) const owner address = "g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq" // @devx -var bid boards2.BoardID +var bid boards.ID func init() { testing.SetRealm(testing.NewUserRealm(owner)) diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_12_e_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_12_e_filetest.gno index def3cb3b95f..f33a07d3284 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_12_e_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_12_e_filetest.gno @@ -3,14 +3,16 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) const owner address = "g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq" // @devx var ( - bid boards2.BoardID - tid boards2.PostID + bid boards.ID + tid boards.ID ) func init() { diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_12_f_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_12_f_filetest.gno index e161d1d1460..827cdfd6f29 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_12_f_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_12_f_filetest.gno @@ -3,6 +3,8 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) @@ -12,8 +14,8 @@ const ( ) var ( - bid boards2.BoardID - tid, rid boards2.PostID + bid boards.ID + tid, rid boards.ID ) func init() { diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_12_g_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_12_g_filetest.gno index 99747a0b674..66782dea64b 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_12_g_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_12_g_filetest.gno @@ -3,6 +3,8 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) @@ -12,8 +14,8 @@ const ( ) var ( - bid boards2.BoardID - tid, rid boards2.PostID + bid boards.ID + tid, rid boards.ID ) func init() { diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_12_h_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_12_h_filetest.gno index dc02cac13dc..51c40210ccc 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_12_h_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_12_h_filetest.gno @@ -3,14 +3,16 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) const owner address = "g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq" // @devx var ( - bid boards2.BoardID - tid, rid boards2.PostID + bid boards.ID + tid, rid boards.ID ) func init() { diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_13_a_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_13_a_filetest.gno index 488068b92ad..71348a7e464 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_13_a_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_13_a_filetest.gno @@ -4,14 +4,16 @@ import ( "strings" "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) const owner address = "g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq" // @devx var ( - bid boards2.BoardID - rid, tid boards2.PostID + bid boards.ID + rid, tid boards.ID ) func init() { diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_13_c_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_13_c_filetest.gno index 3312b77361e..d478de41a72 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_13_c_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_13_c_filetest.gno @@ -3,12 +3,14 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) const owner address = "g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq" // @devx -var bid boards2.BoardID +var bid boards.ID func init() { testing.SetRealm(testing.NewUserRealm(owner)) diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_13_d_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_13_d_filetest.gno index a97be937356..88a366193fd 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_13_d_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_13_d_filetest.gno @@ -3,14 +3,16 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) const owner address = "g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq" // @devx var ( - bid boards2.BoardID - tid boards2.PostID + bid boards.ID + tid boards.ID ) func init() { diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_13_e_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_13_e_filetest.gno index 25ea6ba4821..c8de37138b0 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_13_e_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_13_e_filetest.gno @@ -3,14 +3,16 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) const owner address = "g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq" // @devx var ( - bid boards2.BoardID - rid, tid boards2.PostID + bid boards.ID + rid, tid boards.ID ) func init() { @@ -28,4 +30,4 @@ func main() { } // Error: -// item flag count threshold exceeded: 1 +// flag count threshold exceeded: 1 diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_13_f_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_13_f_filetest.gno index 775af8b8597..7ee39eeca4a 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_13_f_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_13_f_filetest.gno @@ -3,6 +3,8 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) @@ -12,8 +14,8 @@ const ( ) var ( - bid boards2.BoardID - rid, tid boards2.PostID + bid boards.ID + rid, tid boards.ID ) func init() { diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_13_g_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_13_g_filetest.gno index 36326a6a2e6..7e0b4eefea7 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_13_g_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_13_g_filetest.gno @@ -4,6 +4,8 @@ import ( "strings" "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) @@ -13,8 +15,8 @@ const ( ) var ( - bid boards2.BoardID - rid, tid boards2.PostID + bid boards.ID + rid, tid boards.ID ) func init() { diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_13_h_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_13_h_filetest.gno index 7db9929bada..c9269e285f5 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_13_h_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_13_h_filetest.gno @@ -4,6 +4,8 @@ import ( "strings" "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) @@ -13,8 +15,8 @@ const ( ) var ( - bid boards2.BoardID - rid, tid boards2.PostID + bid boards.ID + rid, tid boards.ID ) func init() { diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_14_a_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_14_a_filetest.gno index 1f57338ecbf..ea1206cbe3a 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_14_a_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_14_a_filetest.gno @@ -3,14 +3,16 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) const owner address = "g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq" // @devx var ( - bid boards2.BoardID - tid, rid boards2.PostID + bid boards.ID + tid, rid boards.ID ) func init() { diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_14_c_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_14_c_filetest.gno index ae159f34c9f..e02b3bbb530 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_14_c_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_14_c_filetest.gno @@ -3,12 +3,14 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) const owner address = "g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq" // @devx -var bid boards2.BoardID +var bid boards.ID func init() { testing.SetRealm(testing.NewUserRealm(owner)) diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_14_d_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_14_d_filetest.gno index 5fefd669bc0..c6fef5f7f1d 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_14_d_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_14_d_filetest.gno @@ -3,14 +3,16 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) const owner address = "g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq" // @devx var ( - bid boards2.BoardID - tid boards2.PostID + bid boards.ID + tid boards.ID ) func init() { diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_14_e_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_14_e_filetest.gno index 164529f62dd..2baa6a60c89 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_14_e_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_14_e_filetest.gno @@ -4,14 +4,16 @@ import ( "strings" "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) const owner address = "g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq" // @devx var ( - bid boards2.BoardID - tid, rid boards2.PostID + bid boards.ID + tid, rid boards.ID ) func init() { diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_14_f_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_14_f_filetest.gno index 016f8f16c58..cd266f97e27 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_14_f_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_14_f_filetest.gno @@ -3,6 +3,8 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) @@ -12,8 +14,8 @@ const ( ) var ( - bid boards2.BoardID - tid, rid boards2.PostID + bid boards.ID + tid, rid boards.ID ) func init() { diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_14_g_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_14_g_filetest.gno index 27e39f2c3a5..c8e8d478ef4 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_14_g_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_14_g_filetest.gno @@ -3,6 +3,8 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) @@ -12,8 +14,8 @@ const ( ) var ( - bid boards2.BoardID - tid, rid boards2.PostID + bid boards.ID + tid, rid boards.ID ) func init() { diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_15_a_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_15_a_filetest.gno index 921afdc0286..cef10d2415d 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_15_a_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_15_a_filetest.gno @@ -3,15 +3,17 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) const owner address = "g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq" // @devx var ( - srcBID boards2.BoardID - dstBID boards2.BoardID - srcTID boards2.PostID + srcBID boards.ID + dstBID boards.ID + srcTID boards.ID ) func init() { @@ -27,7 +29,7 @@ func main() { testing.SetRealm(testing.NewUserRealm(owner)) // Repost should fail if source thread is flagged - boards2.CreateRepost(cross, srcBID, srcTID, "foo", "bar", dstBID) + boards2.CreateRepost(cross, srcBID, srcTID, dstBID, "foo", "bar") } // Error: diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_15_b_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_15_b_filetest.gno index 7ecb1c6421b..d38faa418f7 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_15_b_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_15_b_filetest.gno @@ -3,15 +3,17 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) const owner address = "g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq" // @devx var ( - srcBID boards2.BoardID - dstBID boards2.BoardID - srcTID boards2.PostID = 1024 + srcBID boards.ID + dstBID boards.ID + srcTID boards.ID = 1024 ) func init() { @@ -24,7 +26,7 @@ func main() { testing.SetRealm(testing.NewUserRealm(owner)) // Repost should fail if source thread doesn't exist - boards2.CreateRepost(cross, srcBID, srcTID, "foo", "bar", dstBID) + boards2.CreateRepost(cross, srcBID, srcTID, dstBID, "foo", "bar") } // Error: diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_15_c_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_15_c_filetest.gno index e380b30ef13..f5d973ba849 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_15_c_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_15_c_filetest.gno @@ -3,6 +3,8 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) @@ -12,9 +14,9 @@ const ( ) var ( - srcBID boards2.BoardID - dstBID boards2.BoardID - srcTID boards2.PostID + srcBID boards.ID + dstBID boards.ID + srcTID boards.ID ) func init() { @@ -28,7 +30,7 @@ func init() { func main() { testing.SetRealm(testing.NewUserRealm(user)) - boards2.CreateRepost(cross, srcBID, srcTID, "foo", "bar", dstBID) + boards2.CreateRepost(cross, srcBID, srcTID, dstBID, "foo", "bar") } // Error: diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_15_d_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_15_d_filetest.gno index 36463a1a0e5..e3436507886 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_15_d_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_15_d_filetest.gno @@ -4,6 +4,8 @@ import ( "strings" "testing" + "gno.land/p/gnoland/boards" + "gno.land/p/nt/ufmt" boards2 "gno.land/r/gnoland/boards2/v1" ) @@ -11,9 +13,9 @@ import ( const owner address = "g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq" // @devx var ( - srcBID boards2.BoardID - dstBID boards2.BoardID - srcTID boards2.PostID + srcBID boards.ID + dstBID boards.ID + srcTID boards.ID ) func init() { @@ -28,7 +30,7 @@ func main() { testing.SetRealm(testing.NewUserRealm(owner)) // Success case - tID := boards2.CreateRepost(cross, srcBID, srcTID, "repost title", "repost text", dstBID) + tID := boards2.CreateRepost(cross, srcBID, srcTID, dstBID, "repost title", "repost text") p := ufmt.Sprintf("dst-board/%s", tID) out := boards2.Render(p) diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_15_e_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_15_e_filetest.gno index 8dade835548..7fc0f3fbd73 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_15_e_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_15_e_filetest.gno @@ -3,15 +3,17 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) const owner address = "g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq" // @devx var ( - srcBID boards2.BoardID - dstBID boards2.BoardID - srcTID boards2.PostID + srcBID boards.ID + dstBID boards.ID + srcTID boards.ID ) func init() { @@ -23,7 +25,7 @@ func init() { // Create a second board and repost a thread using an empty title srcBID = boards2.CreateBoard(cross, "source-board", false) - srcTID = boards2.CreateRepost(cross, origBID, origTID, "original title", "original text", srcBID) + srcTID = boards2.CreateRepost(cross, origBID, origTID, srcBID, "original title", "original text") // Create a third board to try reposting the repost dstBID = boards2.CreateBoard(cross, "destination-board", false) @@ -32,7 +34,7 @@ func init() { func main() { testing.SetRealm(testing.NewUserRealm(owner)) - boards2.CreateRepost(cross, srcBID, srcTID, "repost title", "repost text", dstBID) + boards2.CreateRepost(cross, srcBID, srcTID, dstBID, "repost title", "repost text") } // Error: diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_16_a_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_16_a_filetest.gno index eb70ca85a20..8891ef0239c 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_16_a_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_16_a_filetest.gno @@ -3,12 +3,14 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) const owner address = "g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq" // @devx -var bid boards2.BoardID +var bid boards.ID func init() { testing.SetRealm(testing.NewUserRealm(owner)) diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_16_b_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_16_b_filetest.gno index 3ae66a41e29..323ca2cf0b1 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_16_b_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_16_b_filetest.gno @@ -3,12 +3,14 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) const ( - owner address = "g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq" // @devx - bid boards2.BoardID = 404 + owner address = "g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq" // @devx + bid boards.ID = 404 ) func main() { diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_17_a_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_17_a_filetest.gno index 0345c66d1f0..55e4ebcfc72 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_17_a_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_17_a_filetest.gno @@ -3,12 +3,14 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) const owner address = "g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq" // @devx -var bid boards2.BoardID +var bid boards.ID func init() { testing.SetRealm(testing.NewUserRealm(owner)) diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_17_b_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_17_b_filetest.gno index 8a5cb435dce..76a50a3b1be 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_17_b_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_17_b_filetest.gno @@ -3,12 +3,14 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) const owner address = "g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq" // @devx -var bid boards2.BoardID +var bid boards.ID func init() { testing.SetRealm(testing.NewUserRealm(owner)) diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_18_a_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_18_a_filetest.gno index b6ea28166fe..63e20e2fcdf 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_18_a_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_18_a_filetest.gno @@ -3,21 +3,21 @@ package main import ( "testing" - "gno.land/p/nt/commondao" + "gno.land/p/gnoland/boards" boards2 "gno.land/r/gnoland/boards2/v1" ) const ( owner address = "g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq" // @devx - bid = boards2.BoardID(0) // Operate on realm instead of individual boards + bid = boards.ID(0) // Operate on realm instead of individual boards ) -var perms boards2.Permissions +var perms boards.Permissions func init() { // Create a new permissions instance without users - perms = boards2.NewBasicPermissions(commondao.New()) + perms = boards2.NewBasicPermissions() } func main() { diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_18_b_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_18_b_filetest.gno index 072d82b944b..6ea295a54bb 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_18_b_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_18_b_filetest.gno @@ -3,21 +3,21 @@ package main import ( "testing" - "gno.land/p/nt/commondao" + "gno.land/p/gnoland/boards" boards2 "gno.land/r/gnoland/boards2/v1" ) const ( user address = "g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj" // @test2 - bid = boards2.BoardID(0) // Operate on realm instead of individual boards + bid = boards.ID(0) // Operate on realm instead of individual boards ) -var perms boards2.Permissions +var perms boards.Permissions func init() { // Create a new permissions instance - perms = boards2.NewBasicPermissions(commondao.New()) + perms = boards2.NewBasicPermissions() } func main() { diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_18_c_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_18_c_filetest.gno index 48116fb56dc..8a065878684 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_18_c_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_18_c_filetest.gno @@ -3,7 +3,7 @@ package main import ( "testing" - "gno.land/p/nt/commondao" + "gno.land/p/gnoland/boards" boards2 "gno.land/r/gnoland/boards2/v1" ) @@ -11,13 +11,13 @@ import ( const owner address = "g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq" // @devx var ( - perms boards2.Permissions - bid boards2.BoardID + perms boards.Permissions + bid boards.ID ) func init() { // Create a new permissions instance without users - perms = boards2.NewBasicPermissions(commondao.New()) + perms = boards2.NewBasicPermissions() testing.SetRealm(testing.NewUserRealm(owner)) bid = boards2.CreateBoard(cross, "foobar", false) diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_19_a_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_19_a_filetest.gno index 77709ba0bf6..adf1e675b9f 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_19_a_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_19_a_filetest.gno @@ -3,14 +3,16 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) const owner address = "g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq" // @devx var ( - bid boards2.BoardID - tid boards2.PostID + bid boards.ID + tid boards.ID ) func init() { diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_19_b_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_19_b_filetest.gno index f4233eff018..bf6a9eed55e 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_19_b_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_19_b_filetest.gno @@ -3,14 +3,16 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) const owner address = "g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq" // @devx var ( - bid boards2.BoardID - tid boards2.PostID + bid boards.ID + tid boards.ID ) func init() { diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_19_c_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_19_c_filetest.gno index 519e2dbdf0c..e217a9dff77 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_19_c_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_19_c_filetest.gno @@ -3,14 +3,16 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) const owner address = "g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq" // @devx var ( - bid boards2.BoardID - tid boards2.PostID + bid boards.ID + tid boards.ID ) func init() { diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_1_a_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_1_a_filetest.gno index 3e8a427a6c0..9e9fea6c904 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_1_a_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_1_a_filetest.gno @@ -3,13 +3,15 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) const ( owner address = "g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq" // @devx user address = "g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj" // @test2 - bid = boards2.BoardID(0) // Operate on realm DAO instead of individual boards + bid = boards.ID(0) // Operate on realm DAO instead of individual boards role = boards2.RoleOwner ) diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_1_b_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_1_b_filetest.gno index 97f2c1c92fb..1455d2c1f9b 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_1_b_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_1_b_filetest.gno @@ -3,6 +3,8 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) @@ -12,7 +14,7 @@ const ( user address = "g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn" ) -var bid boards2.BoardID // Operate on board DAO +var bid boards.ID // Operate on board DAO func init() { testing.SetRealm(testing.NewUserRealm(owner)) diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_1_c_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_1_c_filetest.gno index e1f7a2b90f6..f55b37826e2 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_1_c_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_1_c_filetest.gno @@ -3,6 +3,8 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) @@ -13,7 +15,7 @@ const ( role = boards2.RoleAdmin ) -var bid boards2.BoardID // Operate on board DAO +var bid boards.ID // Operate on board DAO func init() { testing.SetRealm(testing.NewUserRealm(owner)) diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_1_d_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_1_d_filetest.gno index 6fe61517859..e50ab7308de 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_1_d_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_1_d_filetest.gno @@ -3,6 +3,8 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) @@ -14,7 +16,7 @@ const ( func main() { testing.SetRealm(testing.NewUserRealm(owner)) - boards2.InviteMember(cross, 0, user, boards2.Role("foobar")) // Operate on realm DAO instead of individual boards + boards2.InviteMember(cross, 0, user, boards.Role("foobar")) // Operate on realm DAO instead of individual boards } // Error: diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_1_e_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_1_e_filetest.gno index 9fc75f603c5..d951b7008d6 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_1_e_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_1_e_filetest.gno @@ -3,6 +3,8 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) @@ -12,7 +14,7 @@ const ( role = boards2.RoleOwner ) -var bid boards2.BoardID +var bid boards.ID func init() { testing.SetRealm(testing.NewUserRealm(owner)) diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_1_f_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_1_f_filetest.gno index 7579626375a..c38c9165b25 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_1_f_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_1_f_filetest.gno @@ -3,13 +3,15 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) const ( owner address = "g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq" // @devx user address = "g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj" // @test2 - bid = boards2.BoardID(0) // Operate on realm DAO instead of individual boards + bid = boards.ID(0) // Operate on realm DAO instead of individual boards role = boards2.RoleOwner ) diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_20_a_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_20_a_filetest.gno deleted file mode 100644 index 02a69848d32..00000000000 --- a/examples/gno.land/r/gnoland/boards2/v1/z_20_a_filetest.gno +++ /dev/null @@ -1,34 +0,0 @@ -package main - -import ( - "testing" - - boards2 "gno.land/r/gnoland/boards2/v1" -) - -const owner address = "g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq" // @devx - -var ( - bid boards2.BoardID - tid boards2.PostID - rid boards2.PostID -) - -func init() { - testing.SetRealm(testing.NewUserRealm(owner)) - bid = boards2.CreateBoard(cross, "test123", false) - tid = boards2.CreateThread(cross, bid, "foo", "bar") - rid = boards2.CreateReply(cross, bid, tid, 0, "reply") - - boards2.FreezeThread(cross, bid, tid) -} - -func main() { - testing.SetRealm(testing.NewUserRealm(owner)) - - // Attempt to freeze a reply on frozen thread - boards2.FreezeReply(cross, bid, tid, rid) -} - -// Error: -// thread is frozen diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_20_b_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_20_b_filetest.gno deleted file mode 100644 index 8bf87a246f4..00000000000 --- a/examples/gno.land/r/gnoland/boards2/v1/z_20_b_filetest.gno +++ /dev/null @@ -1,34 +0,0 @@ -package main - -import ( - "testing" - - boards2 "gno.land/r/gnoland/boards2/v1" -) - -const owner address = "g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq" // @devx - -var ( - bid boards2.BoardID - tid boards2.PostID - rid boards2.PostID -) - -func init() { - testing.SetRealm(testing.NewUserRealm(owner)) - bid = boards2.CreateBoard(cross, "test123", false) - tid = boards2.CreateThread(cross, bid, "foo", "bar") - rid = boards2.CreateReply(cross, bid, tid, 0, "reply") - - boards2.FreezeReply(cross, bid, tid, rid) -} - -func main() { - testing.SetRealm(testing.NewUserRealm(owner)) - - // Attempt to freeze already frozen reply - boards2.FreezeReply(cross, bid, tid, rid) -} - -// Error: -// reply is frozen diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_20_c_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_20_c_filetest.gno deleted file mode 100644 index 649357e70ef..00000000000 --- a/examples/gno.land/r/gnoland/boards2/v1/z_20_c_filetest.gno +++ /dev/null @@ -1,54 +0,0 @@ -package main - -import ( - "testing" - - boards2 "gno.land/r/gnoland/boards2/v1" -) - -const owner address = "g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq" // @devx - -var ( - bid boards2.BoardID - tid boards2.PostID - rid boards2.PostID -) - -func init() { - testing.SetRealm(testing.NewUserRealm(owner)) - bid = boards2.CreateBoard(cross, "test123", false) - tid = boards2.CreateThread(cross, bid, "foo", "bar") - rid = boards2.CreateReply(cross, bid, tid, 0, "reply") -} - -func main() { - testing.SetRealm(testing.NewUserRealm(owner)) - - // reply frozen - println(boards2.IsReplyFrozen(bid, tid, rid)) - boards2.FreezeReply(cross, bid, tid, rid) - println(boards2.IsReplyFrozen(bid, tid, rid)) - boards2.UnfreezeReply(cross, bid, tid, rid) - println(boards2.IsReplyFrozen(bid, tid, rid)) - - // thread frozen - boards2.FreezeThread(cross, bid, tid) - println(boards2.IsReplyFrozen(bid, tid, rid)) - boards2.UnfreezeThread(cross, bid, tid) - println(boards2.IsReplyFrozen(bid, tid, rid)) - - // board frozen - boards2.FreezeBoard(cross, bid) - println(boards2.IsReplyFrozen(bid, tid, rid)) - boards2.UnfreezeBoard(cross, bid) - println(boards2.IsReplyFrozen(bid, tid, rid)) -} - -// Output: -// false -// true -// false -// true -// false -// true -// false diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_20_d_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_20_d_filetest.gno deleted file mode 100644 index 327dd4f9944..00000000000 --- a/examples/gno.land/r/gnoland/boards2/v1/z_20_d_filetest.gno +++ /dev/null @@ -1,36 +0,0 @@ -package main - -import ( - "testing" - - boards2 "gno.land/r/gnoland/boards2/v1" -) - -const owner address = "g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq" // @devx - -var ( - bid boards2.BoardID - tid boards2.PostID - rid boards2.PostID -) - -func init() { - testing.SetRealm(testing.NewUserRealm(owner)) - bid = boards2.CreateBoard(cross, "test123", false) - tid = boards2.CreateThread(cross, bid, "foo", "bar") - rid = boards2.CreateReply(cross, bid, tid, 0, "reply") -} - -func main() { - testing.SetRealm(testing.NewUserRealm(owner)) - - // unfrozen thread - should work - boards2.CreateReply(cross, bid, tid, rid, "child 1") - - // frozen thread - can't reply - boards2.FreezeReply(cross, bid, tid, rid) - boards2.CreateReply(cross, bid, tid, rid, "child 2") -} - -// Error: -// replying to a hidden or frozen reply is not allowed diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_23_a_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_23_a_filetest.gno index af1e826ca6d..6faef37c55d 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_23_a_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_23_a_filetest.gno @@ -3,12 +3,14 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) const ( owner address = "g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq" // @devx - bid = boards2.BoardID(0) // Operate on realm DAO instead of individual boards + bid = boards.ID(0) // Operate on realm DAO instead of individual boards ) func init() { @@ -20,7 +22,7 @@ func init() { func main() { testing.SetRealm(testing.NewUserRealm(owner)) - boards2.IterateRealmMembers(0, func(u boards2.User) bool { + boards2.IterateRealmMembers(0, func(u boards.User) bool { println(u.Address, string(u.Roles[0])) return false }) diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_24_a_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_24_a_filetest.gno index 48499284d57..c1b5a7144b7 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_24_a_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_24_a_filetest.gno @@ -3,12 +3,14 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) const owner address = "g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq" // @devx -var bid boards2.BoardID +var bid boards.ID func init() { testing.SetRealm(testing.NewUserRealm(owner)) diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_24_b_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_24_b_filetest.gno index ef8b3fc70b3..aa8d242204e 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_24_b_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_24_b_filetest.gno @@ -3,6 +3,8 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) @@ -11,7 +13,7 @@ const ( admin address = "g1vh7krmmzfua5xjmkatvmx09z37w34lsvd2mxa5" ) -var bid boards2.BoardID +var bid boards.ID func init() { testing.SetRealm(testing.NewUserRealm(owner)) diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_24_c_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_24_c_filetest.gno index f413c951965..21b84cbcb75 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_24_c_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_24_c_filetest.gno @@ -3,12 +3,14 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) const owner address = "g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq" // @devx -var bid boards2.BoardID +var bid boards.ID func init() { testing.SetRealm(testing.NewUserRealm(owner)) diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_25_a_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_25_a_filetest.gno index c3555728ca6..740d8355cb2 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_25_a_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_25_a_filetest.gno @@ -3,12 +3,14 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) const owner address = "g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq" // @devx -var bid boards2.BoardID +var bid boards.ID func init() { testing.SetRealm(testing.NewUserRealm(owner)) diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_25_b_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_25_b_filetest.gno index ad6cdc30e0f..dbcc447e872 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_25_b_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_25_b_filetest.gno @@ -3,12 +3,14 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) const owner address = "g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq" // @devx -var bid boards2.BoardID +var bid boards.ID func init() { testing.SetRealm(testing.NewUserRealm(owner)) diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_25_c_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_25_c_filetest.gno index b02ad514adc..4312951aac3 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_25_c_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_25_c_filetest.gno @@ -3,12 +3,14 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) const owner address = "g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq" // @devx -var bid boards2.BoardID +var bid boards.ID func init() { testing.SetRealm(testing.NewUserRealm(owner)) diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_25_d_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_25_d_filetest.gno index 72b10910bbb..5340a4ce39d 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_25_d_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_25_d_filetest.gno @@ -3,6 +3,8 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) @@ -11,7 +13,7 @@ const ( user address = "g1vh7krmmzfua5xjmkatvmx09z37w34lsvd2mxa5" ) -var bid boards2.BoardID +var bid boards.ID func init() { testing.SetRealm(testing.NewUserRealm(owner)) diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_26_a_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_26_a_filetest.gno index a0ed7b49302..bcb98a565d6 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_26_a_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_26_a_filetest.gno @@ -3,6 +3,8 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) @@ -11,7 +13,7 @@ const ( user address = "g1vh7krmmzfua5xjmkatvmx09z37w34lsvd2mxa5" ) -var bid boards2.BoardID +var bid boards.ID func init() { testing.SetRealm(testing.NewUserRealm(owner)) diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_26_b_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_26_b_filetest.gno index 0a6ddd809d6..28132edc030 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_26_b_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_26_b_filetest.gno @@ -3,6 +3,8 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) @@ -11,7 +13,7 @@ const ( user address = "g1vh7krmmzfua5xjmkatvmx09z37w34lsvd2mxa5" ) -var bid boards2.BoardID +var bid boards.ID func init() { testing.SetRealm(testing.NewUserRealm(owner)) diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_26_c_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_26_c_filetest.gno index e0f6d9a03bd..4e6b5ea5b6e 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_26_c_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_26_c_filetest.gno @@ -3,6 +3,8 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) @@ -11,7 +13,7 @@ const ( user address = "g1vh7krmmzfua5xjmkatvmx09z37w34lsvd2mxa5" ) -var bid boards2.BoardID +var bid boards.ID func init() { testing.SetRealm(testing.NewUserRealm(owner)) diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_26_d_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_26_d_filetest.gno index 9d4f59c7b22..1e363f6058b 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_26_d_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_26_d_filetest.gno @@ -3,6 +3,8 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) @@ -11,7 +13,7 @@ const ( user address = "g1vh7krmmzfua5xjmkatvmx09z37w34lsvd2mxa5" ) -var bid boards2.BoardID +var bid boards.ID func init() { testing.SetRealm(testing.NewUserRealm(owner)) diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_27_a_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_27_a_filetest.gno index 99c54a3277d..0f02b29ae1b 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_27_a_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_27_a_filetest.gno @@ -3,6 +3,8 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) @@ -11,7 +13,7 @@ const ( user address = "g1vh7krmmzfua5xjmkatvmx09z37w34lsvd2mxa5" ) -var bid boards2.BoardID +var bid boards.ID func init() { testing.SetRealm(testing.NewUserRealm(owner)) diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_27_b_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_27_b_filetest.gno index 55fb6d203ee..869b84ab3d5 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_27_b_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_27_b_filetest.gno @@ -3,6 +3,8 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) @@ -11,7 +13,7 @@ const ( user address = "g1vh7krmmzfua5xjmkatvmx09z37w34lsvd2mxa5" ) -var bid boards2.BoardID +var bid boards.ID func init() { testing.SetRealm(testing.NewUserRealm(owner)) diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_27_c_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_27_c_filetest.gno index 725fc147b5f..8140ff24b12 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_27_c_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_27_c_filetest.gno @@ -3,6 +3,8 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) @@ -11,7 +13,7 @@ const ( user address = "g1vh7krmmzfua5xjmkatvmx09z37w34lsvd2mxa5" ) -var bid boards2.BoardID +var bid boards.ID func init() { testing.SetRealm(testing.NewUserRealm(owner)) diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_28_a_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_28_a_filetest.gno index 5b51de6cc8d..86d298db554 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_28_a_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_28_a_filetest.gno @@ -3,6 +3,8 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) @@ -11,7 +13,7 @@ const ( user address = "g1vh7krmmzfua5xjmkatvmx09z37w34lsvd2mxa5" ) -var bid boards2.BoardID +var bid boards.ID func init() { testing.SetRealm(testing.NewUserRealm(owner)) diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_28_b_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_28_b_filetest.gno index 99a587ae50f..630b5d137ba 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_28_b_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_28_b_filetest.gno @@ -3,6 +3,8 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) @@ -11,7 +13,7 @@ const ( user address = "g1vh7krmmzfua5xjmkatvmx09z37w34lsvd2mxa5" ) -var bid boards2.BoardID +var bid boards.ID func init() { testing.SetRealm(testing.NewUserRealm(owner)) diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_28_c_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_28_c_filetest.gno index 553c3e48c35..ceb09aa6b52 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_28_c_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_28_c_filetest.gno @@ -3,6 +3,8 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) @@ -11,7 +13,7 @@ const ( user address = "g1vh7krmmzfua5xjmkatvmx09z37w34lsvd2mxa5" ) -var bid boards2.BoardID +var bid boards.ID func init() { testing.SetRealm(testing.NewUserRealm(owner)) diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_29_a_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_29_a_filetest.gno index 33a8ea9580f..ae6fc55438a 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_29_a_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_29_a_filetest.gno @@ -3,12 +3,14 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) const owner address = "g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq" // @devx -var bid boards2.BoardID +var bid boards.ID func init() { testing.SetRealm(testing.NewUserRealm(owner)) diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_29_b_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_29_b_filetest.gno index 12336e1d690..0147ddc8c86 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_29_b_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_29_b_filetest.gno @@ -3,6 +3,8 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) @@ -11,7 +13,7 @@ const ( user address = "g1vh7krmmzfua5xjmkatvmx09z37w34lsvd2mxa5" ) -var bid boards2.BoardID +var bid boards.ID func init() { testing.SetRealm(testing.NewUserRealm(owner)) diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_2_a_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_2_a_filetest.gno index 63297b8fb08..6b68806e08b 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_2_a_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_2_a_filetest.gno @@ -4,6 +4,8 @@ import ( "strings" "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) @@ -14,8 +16,8 @@ const ( ) var ( - bid boards2.BoardID - tid boards2.PostID + bid boards.ID + tid boards.ID ) func init() { diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_2_c_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_2_c_filetest.gno index b6ed2da4d44..d79233a8740 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_2_c_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_2_c_filetest.gno @@ -3,12 +3,14 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) const owner address = "g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq" // @devx -var bid boards2.BoardID +var bid boards.ID func init() { testing.SetRealm(testing.NewUserRealm(owner)) diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_2_d_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_2_d_filetest.gno index a63510e3063..2abcc7f5559 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_2_d_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_2_d_filetest.gno @@ -3,14 +3,16 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) const owner address = "g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq" // @devx var ( - bid boards2.BoardID - tid boards2.PostID + bid boards.ID + tid boards.ID ) func init() { diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_2_e_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_2_e_filetest.gno index 400b74e2f55..e44a041b904 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_2_e_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_2_e_filetest.gno @@ -3,14 +3,16 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) const owner address = "g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq" // @devx var ( - bid boards2.BoardID - tid boards2.PostID + bid boards.ID + tid boards.ID ) func init() { diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_2_f_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_2_f_filetest.gno index c660d58e3dd..de9d8b76caa 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_2_f_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_2_f_filetest.gno @@ -3,14 +3,16 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) const owner address = "g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq" // @devx var ( - bid boards2.BoardID - tid, rid boards2.PostID + bid boards.ID + tid, rid boards.ID ) func init() { diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_2_g_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_2_g_filetest.gno index bd27b06028d..39b7adf8a1b 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_2_g_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_2_g_filetest.gno @@ -3,14 +3,16 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) const owner address = "g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq" // @devx var ( - bid boards2.BoardID - tid, rid boards2.PostID + bid boards.ID + tid, rid boards.ID ) func init() { diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_2_h_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_2_h_filetest.gno index 7833c1b1a37..decd22abf7e 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_2_h_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_2_h_filetest.gno @@ -3,6 +3,8 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) @@ -12,8 +14,8 @@ const ( ) var ( - bid boards2.BoardID - tid boards2.PostID + bid boards.ID + tid boards.ID ) func init() { diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_2_i_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_2_i_filetest.gno index 4b82a951845..6a7faa1455b 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_2_i_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_2_i_filetest.gno @@ -3,14 +3,16 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) const owner address = "g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq" // @devx var ( - bid boards2.BoardID - tid boards2.PostID + bid boards.ID + tid boards.ID ) func init() { diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_2_j_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_2_j_filetest.gno index 7f4e8e464cd..c029e72f606 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_2_j_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_2_j_filetest.gno @@ -4,6 +4,8 @@ import ( "strings" "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) @@ -14,8 +16,8 @@ const ( ) var ( - bid boards2.BoardID - tid, rid boards2.PostID + bid boards.ID + tid, rid boards.ID ) func init() { diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_2_k_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_2_k_filetest.gno index b432fdcd9ac..b5069118ebd 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_2_k_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_2_k_filetest.gno @@ -3,6 +3,8 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) @@ -12,8 +14,8 @@ const ( ) var ( - bid boards2.BoardID - tid, rid boards2.PostID + bid boards.ID + tid, rid boards.ID ) func init() { diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_2_l_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_2_l_filetest.gno index b15b7dd4708..39d3fb81cf7 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_2_l_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_2_l_filetest.gno @@ -3,14 +3,16 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) const owner address = "g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq" // @devx var ( - bid boards2.BoardID - tid boards2.PostID + bid boards.ID + tid boards.ID ) func init() { diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_2_m_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_2_m_filetest.gno index 1a90617b785..8e0a83a4821 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_2_m_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_2_m_filetest.gno @@ -4,14 +4,16 @@ import ( "strings" "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) const owner address = "g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq" // @devx var ( - bid boards2.BoardID - tid boards2.PostID + bid boards.ID + tid boards.ID ) func init() { diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_2_n_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_2_n_filetest.gno index f017effadb6..622e0313ac3 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_2_n_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_2_n_filetest.gno @@ -3,14 +3,16 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) const owner address = "g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq" // @devx var ( - bid boards2.BoardID - tid boards2.PostID + bid boards.ID + tid boards.ID ) func init() { diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_30_a_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_30_a_filetest.gno index 27893fe03da..f2cee2fc6d3 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_30_a_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_30_a_filetest.gno @@ -3,6 +3,8 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) @@ -11,7 +13,7 @@ const ( user address = "g1vh7krmmzfua5xjmkatvmx09z37w34lsvd2mxa5" ) -var bid boards2.BoardID +var bid boards.ID func init() { testing.SetRealm(testing.NewUserRealm(owner)) diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_30_b_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_30_b_filetest.gno index d9a0fbcaa80..83c2a12ab24 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_30_b_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_30_b_filetest.gno @@ -3,12 +3,14 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) const owner address = "g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq" // @devx -var bid boards2.BoardID +var bid boards.ID func init() { testing.SetRealm(testing.NewUserRealm(owner)) diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_30_c_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_30_c_filetest.gno index 846e9a7a4f0..82b2946a9bb 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_30_c_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_30_c_filetest.gno @@ -3,6 +3,8 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) @@ -11,7 +13,7 @@ const ( user address = "g1vh7krmmzfua5xjmkatvmx09z37w34lsvd2mxa5" ) -var bid boards2.BoardID +var bid boards.ID func init() { testing.SetRealm(testing.NewUserRealm(owner)) diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_3_a_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_3_a_filetest.gno index 15abfe5777f..6c6f3c0a946 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_3_a_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_3_a_filetest.gno @@ -3,6 +3,8 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) @@ -12,7 +14,7 @@ const ( newName = "bar123" ) -var bid boards2.BoardID // Operate on board DAO +var bid boards.ID // Operate on board DAO func init() { testing.SetRealm(testing.NewUserRealm(owner)) diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_3_f_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_3_f_filetest.gno index 62b9de25dee..c84c80a30ff 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_3_f_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_3_f_filetest.gno @@ -5,6 +5,8 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" users "gno.land/r/gnoland/users/v1" ) @@ -16,7 +18,7 @@ const ( newName = "barbaz123" ) -var bid boards2.BoardID // Operate on board DAO +var bid boards.ID // Operate on board DAO func init() { testing.SetRealm(testing.NewUserRealm(owner)) diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_3_g_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_3_g_filetest.gno index 054d09b49e0..7533c1737fa 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_3_g_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_3_g_filetest.gno @@ -5,6 +5,8 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" users "gno.land/r/gnoland/users/v1" ) @@ -17,7 +19,7 @@ const ( newName = "barbaz123" ) -var bid boards2.BoardID // Operate on board DAO +var bid boards.ID // Operate on board DAO func init() { testing.SetRealm(testing.NewUserRealm(owner)) diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_3_i_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_3_i_filetest.gno index 054d09b49e0..7533c1737fa 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_3_i_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_3_i_filetest.gno @@ -5,6 +5,8 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" users "gno.land/r/gnoland/users/v1" ) @@ -17,7 +19,7 @@ const ( newName = "barbaz123" ) -var bid boards2.BoardID // Operate on board DAO +var bid boards.ID // Operate on board DAO func init() { testing.SetRealm(testing.NewUserRealm(owner)) diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_4_a_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_4_a_filetest.gno index 760a863de08..f84a446e458 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_4_a_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_4_a_filetest.gno @@ -3,6 +3,8 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) @@ -10,7 +12,7 @@ const ( owner address = "g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq" // @devx member address = "g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj" // @test2 newRole = boards2.RoleOwner - bid = boards2.BoardID(0) // Operate on realm DAO instead of individual boards + bid = boards.ID(0) // Operate on realm DAO instead of individual boards ) func init() { diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_4_b_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_4_b_filetest.gno index 88186a08f51..cad9bfc97b1 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_4_b_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_4_b_filetest.gno @@ -3,6 +3,8 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) @@ -12,7 +14,7 @@ const ( newRole = boards2.RoleAdmin ) -var bid boards2.BoardID // Operate on board DAO +var bid boards.ID // Operate on board DAO func init() { testing.SetRealm(testing.NewUserRealm(owner)) diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_4_c_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_4_c_filetest.gno index 5b4fd0d5702..60ee8e9419c 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_4_c_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_4_c_filetest.gno @@ -3,6 +3,8 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) @@ -12,7 +14,7 @@ const ( admin address = "g1vh7krmmzfua5xjmkatvmx09z37w34lsvd2mxa5" ) -var bid boards2.BoardID // Operate on board DAO +var bid boards.ID // Operate on board DAO func init() { testing.SetRealm(testing.NewUserRealm(owner)) diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_4_d_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_4_d_filetest.gno index 849d0643f31..b8cd90863be 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_4_d_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_4_d_filetest.gno @@ -3,6 +3,8 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) @@ -12,7 +14,7 @@ const ( admin2 address = "g1vh7krmmzfua5xjmkatvmx09z37w34lsvd2mxa5" ) -var bid boards2.BoardID // Operate on board DAO +var bid boards.ID // Operate on board DAO func init() { testing.SetRealm(testing.NewUserRealm(owner)) diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_4_e_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_4_e_filetest.gno index 0d4101a1b09..05ee2ca8183 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_4_e_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_4_e_filetest.gno @@ -3,13 +3,15 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) const ( owner address = "g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq" // @devx member address = "g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj" // @test2 - bid = boards2.BoardID(0) // Operate on realm DAO members instead of individual boards + bid = boards.ID(0) // Operate on realm DAO members instead of individual boards newRole = boards2.RoleOwner ) diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_4_f_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_4_f_filetest.gno index bea8ce72ccc..f6a7a111096 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_4_f_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_4_f_filetest.gno @@ -3,6 +3,8 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) @@ -11,7 +13,7 @@ const ( admin address = "g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj" // @test2 ) -var bid boards2.BoardID // Operate on board DAO +var bid boards.ID // Operate on board DAO func init() { testing.SetRealm(testing.NewUserRealm(owner)) @@ -23,7 +25,7 @@ func init() { func main() { testing.SetRealm(testing.NewUserRealm(owner)) - boards2.ChangeMemberRole(cross, bid, admin, boards2.Role("foo")) + boards2.ChangeMemberRole(cross, bid, admin, boards.Role("foo")) } // Error: diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_4_g_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_4_g_filetest.gno index 4d198b670f0..91faebea6a0 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_4_g_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_4_g_filetest.gno @@ -3,6 +3,8 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) @@ -11,7 +13,7 @@ const ( admin address = "g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj" // @test2 ) -var bid boards2.BoardID // Operate on board DAO +var bid boards.ID // Operate on board DAO func init() { testing.SetRealm(testing.NewUserRealm(owner)) diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_5_a_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_5_a_filetest.gno index 8122bfb2c3c..1d4d51aa966 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_5_a_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_5_a_filetest.gno @@ -3,6 +3,8 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) @@ -11,7 +13,7 @@ const ( user address = "g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj" // @test2 ) -var bid boards2.BoardID // Operate on board DAO +var bid boards.ID // Operate on board DAO func init() { testing.SetRealm(testing.NewUserRealm(owner)) diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_5_d_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_5_d_filetest.gno new file mode 100644 index 00000000000..9c4008c5954 --- /dev/null +++ b/examples/gno.land/r/gnoland/boards2/v1/z_5_d_filetest.gno @@ -0,0 +1,36 @@ +package main + +import ( + "testing" + + "gno.land/p/gnoland/boards" + + boards2 "gno.land/r/gnoland/boards2/v1" +) + +const ( + owner address = "g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq" // @devx + user address = "g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj" // @test2 +) + +var bid boards.ID // Operate on board DAO + +func init() { + testing.SetRealm(testing.NewUserRealm(owner)) + bid = boards2.CreateBoard(cross, "test123", false) + + boards2.InviteMember(cross, bid, user, boards2.RoleGuest) +} + +func main() { + testing.SetRealm(testing.NewUserRealm(user)) + + // Users must be able to remove themselves without permissions + boards2.RemoveMember(cross, bid, user) + + // Check that user is not a member + println(boards2.IsMember(bid, user)) +} + +// Output: +// false diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_6_a_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_6_a_filetest.gno index 12524120306..9aa48c16c3d 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_6_a_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_6_a_filetest.gno @@ -3,6 +3,8 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) @@ -12,7 +14,7 @@ const ( role = boards2.RoleGuest ) -var bid boards2.BoardID // Operate on board DAO +var bid boards.ID // Operate on board DAO func init() { testing.SetRealm(testing.NewUserRealm(owner)) diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_6_b_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_6_b_filetest.gno index b45673cc674..707f0c6b5ea 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_6_b_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_6_b_filetest.gno @@ -3,13 +3,15 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) const ( owner address = "g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq" // @devx user address = "g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj" // @test2 - bid = boards2.BoardID(0) // Operate on realm DAO instead of individual boards + bid = boards.ID(0) // Operate on realm DAO instead of individual boards role = boards2.RoleGuest ) diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_7_a_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_7_a_filetest.gno index 312bf11ae91..c95c5bd79f9 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_7_a_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_7_a_filetest.gno @@ -3,6 +3,8 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) @@ -11,7 +13,7 @@ const ( name = "test123" ) -var bid boards2.BoardID +var bid boards.ID func init() { testing.SetRealm(testing.NewUserRealm(owner)) diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_8_a_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_8_a_filetest.gno index 335eecdc1ab..8f652f7f9c6 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_8_a_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_8_a_filetest.gno @@ -4,6 +4,8 @@ import ( "strings" "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) @@ -14,7 +16,7 @@ const ( path = "test-board/1" ) -var bid boards2.BoardID +var bid boards.ID func init() { testing.SetRealm(testing.NewUserRealm(owner)) diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_8_c_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_8_c_filetest.gno index ccdedbaf74c..bc1705641fb 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_8_c_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_8_c_filetest.gno @@ -3,6 +3,8 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) @@ -11,7 +13,7 @@ const ( user address = "g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj" // @test2 ) -var bid boards2.BoardID +var bid boards.ID func init() { testing.SetRealm(testing.NewUserRealm(owner)) diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_8_d_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_8_d_filetest.gno index 139225adfab..70d451b9af0 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_8_d_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_8_d_filetest.gno @@ -3,12 +3,14 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) const owner address = "g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq" // @devx -var bid boards2.BoardID +var bid boards.ID func init() { testing.SetRealm(testing.NewUserRealm(owner)) diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_8_e_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_8_e_filetest.gno index 7eeaeace48d..cdf490bd5e6 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_8_e_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_8_e_filetest.gno @@ -3,12 +3,14 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) const owner address = "g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq" // @devx -var bid boards2.BoardID +var bid boards.ID func init() { testing.SetRealm(testing.NewUserRealm(owner)) @@ -22,4 +24,4 @@ func main() { } // Error: -// body is empty +// thread body is required diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_9_a_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_9_a_filetest.gno index 2905d0c13f2..4de483f478f 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_9_a_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_9_a_filetest.gno @@ -3,6 +3,8 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) @@ -13,8 +15,8 @@ const ( ) var ( - bid boards2.BoardID - pid boards2.PostID + bid boards.ID + pid boards.ID ) func init() { diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_9_c_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_9_c_filetest.gno index 24226ad5cfa..54353a28080 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_9_c_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_9_c_filetest.gno @@ -3,12 +3,14 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) const owner address = "g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq" // @devx -var bid boards2.BoardID +var bid boards.ID func init() { testing.SetRealm(testing.NewUserRealm(owner)) diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_9_d_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_9_d_filetest.gno index 958b1a9f988..a8b9e87810a 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_9_d_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_9_d_filetest.gno @@ -3,6 +3,8 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) @@ -12,8 +14,8 @@ const ( ) var ( - bid boards2.BoardID - pid boards2.PostID + bid boards.ID + pid boards.ID ) func init() { diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_9_e_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_9_e_filetest.gno index 7c752e0c840..972f3c25d4c 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_9_e_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_9_e_filetest.gno @@ -3,6 +3,8 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) @@ -12,8 +14,8 @@ const ( ) var ( - bid boards2.BoardID - pid boards2.PostID + bid boards.ID + pid boards.ID ) func init() { diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_ui_2_a_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_ui_2_a_filetest.gno index 1ed723d3d97..a14870d8b0e 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_ui_2_a_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_ui_2_a_filetest.gno @@ -28,7 +28,7 @@ func init() { // Repost thread "C" into a different board dstBoardID := boards2.CreateBoard(cross, "Bar", false) - boards2.CreateRepost(cross, boardID, threadID, "Title", "Body", dstBoardID) + boards2.CreateRepost(cross, boardID, threadID, dstBoardID, "Title", "Body") } func main() { diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_ui_4_a_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_ui_4_a_filetest.gno index 06fe6546763..9df7d8aa23d 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_ui_4_a_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_ui_4_a_filetest.gno @@ -4,6 +4,8 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) @@ -12,7 +14,7 @@ const ( boardName = "BoardName" ) -var threadID boards2.PostID +var threadID boards.ID func init() { testing.SetRealm(testing.NewUserRealm(owner)) diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_ui_4_b_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_ui_4_b_filetest.gno index 935762e775c..390f125c936 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_ui_4_b_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_ui_4_b_filetest.gno @@ -4,6 +4,8 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) @@ -12,7 +14,7 @@ const ( boardName = "BoardName" ) -var threadID boards2.PostID +var threadID boards.ID func init() { testing.SetRealm(testing.NewUserRealm(owner)) diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_ui_4_c_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_ui_4_c_filetest.gno index cbb59830316..2b6eac659aa 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_ui_4_c_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_ui_4_c_filetest.gno @@ -4,6 +4,8 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) @@ -14,8 +16,8 @@ const ( ) var ( - threadID boards2.PostID - repostThreadID boards2.PostID + threadID boards.ID + repostThreadID boards.ID ) func init() { @@ -27,7 +29,7 @@ func init() { // Repost thread into a different board dstBoardID := boards2.CreateBoard(cross, dstBoardName, false) - repostThreadID = boards2.CreateRepost(cross, boardID, threadID, "Bar", "Body2", dstBoardID) + repostThreadID = boards2.CreateRepost(cross, boardID, threadID, dstBoardID, "Bar", "Body2") } func main() { diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_ui_4_d_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_ui_4_d_filetest.gno index 3bae8eb1af3..651f835c490 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_ui_4_d_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_ui_4_d_filetest.gno @@ -5,6 +5,8 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) @@ -13,7 +15,7 @@ const ( boardName = "BoardName" ) -var threadID boards2.PostID +var threadID boards.ID func init() { testing.SetRealm(testing.NewUserRealm(owner)) diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_ui_4_e_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_ui_4_e_filetest.gno index c80212309bb..da4e79680f7 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_ui_4_e_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_ui_4_e_filetest.gno @@ -4,6 +4,8 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) @@ -13,7 +15,7 @@ const ( dstBoardName = "BoardName2" ) -var repostThreadID boards2.PostID +var repostThreadID boards.ID func init() { testing.SetRealm(testing.NewUserRealm(owner)) @@ -24,7 +26,7 @@ func init() { // Repost thread into a different board dstBoardID := boards2.CreateBoard(cross, dstBoardName, false) - repostThreadID = boards2.CreateRepost(cross, boardID, threadID, "Bar", "Body2", dstBoardID) + repostThreadID = boards2.CreateRepost(cross, boardID, threadID, dstBoardID, "Bar", "Body2") // Remove the original thread boards2.DeleteThread(cross, boardID, threadID) diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_ui_4_f_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_ui_4_f_filetest.gno index 253273e7b30..ebbd8a84513 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_ui_4_f_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_ui_4_f_filetest.gno @@ -4,6 +4,8 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) @@ -12,7 +14,7 @@ const ( boardName = "BoardName" ) -var threadID boards2.PostID +var threadID boards.ID func init() { testing.SetRealm(testing.NewUserRealm(owner)) diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_ui_4_g_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_ui_4_g_filetest.gno index aafebcf0304..54da4328db9 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_ui_4_g_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_ui_4_g_filetest.gno @@ -4,6 +4,8 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) @@ -13,7 +15,7 @@ const ( dstBoardName = "BoardName2" ) -var repostThreadID boards2.PostID +var repostThreadID boards.ID func init() { testing.SetRealm(testing.NewUserRealm(owner)) @@ -24,7 +26,7 @@ func init() { // Repost thread into a different board dstBoardID := boards2.CreateBoard(cross, dstBoardName, false) - repostThreadID = boards2.CreateRepost(cross, boardID, threadID, "Bar", "Body2", dstBoardID) + repostThreadID = boards2.CreateRepost(cross, boardID, threadID, dstBoardID, "Bar", "Body2") // Flag original thread boards2.SetFlaggingThreshold(cross, boardID, 1) diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_ui_5_a_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_ui_5_a_filetest.gno index a80cd2159a2..5e9be15e075 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_ui_5_a_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_ui_5_a_filetest.gno @@ -4,6 +4,8 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) @@ -13,8 +15,8 @@ const ( ) var ( - threadID boards2.PostID - replyID boards2.PostID + threadID boards.ID + replyID boards.ID ) func init() { diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_ui_5_b_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_ui_5_b_filetest.gno index 7b3bdfcc7a5..4307759ae5e 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_ui_5_b_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_ui_5_b_filetest.gno @@ -4,6 +4,8 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) @@ -13,8 +15,8 @@ const ( ) var ( - threadID boards2.PostID - replyID boards2.PostID + threadID boards.ID + replyID boards.ID ) func init() { diff --git a/examples/gno.land/r/gnoland/boards2/v1/z_ui_5_c_filetest.gno b/examples/gno.land/r/gnoland/boards2/v1/z_ui_5_c_filetest.gno index 5310aff82c0..d5f118b95a9 100644 --- a/examples/gno.land/r/gnoland/boards2/v1/z_ui_5_c_filetest.gno +++ b/examples/gno.land/r/gnoland/boards2/v1/z_ui_5_c_filetest.gno @@ -4,6 +4,8 @@ package main import ( "testing" + "gno.land/p/gnoland/boards" + boards2 "gno.land/r/gnoland/boards2/v1" ) @@ -13,8 +15,8 @@ const ( ) var ( - threadID boards2.PostID - replyID boards2.PostID + threadID boards.ID + replyID boards.ID ) func init() {