github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/examples/gno.land/r/demo/boards/board.gno (about) 1 package boards 2 3 import ( 4 "std" 5 "strconv" 6 "time" 7 8 "gno.land/p/demo/avl" 9 ) 10 11 //---------------------------------------- 12 // Board 13 14 type BoardID uint64 15 16 func (bid BoardID) String() string { 17 return strconv.Itoa(int(bid)) 18 } 19 20 type Board struct { 21 id BoardID // only set for public boards. 22 url string 23 name string 24 creator std.Address 25 threads avl.Tree // Post.id -> *Post 26 postsCtr uint64 // increments Post.id 27 createdAt time.Time 28 deleted avl.Tree // TODO reserved for fast-delete. 29 } 30 31 func newBoard(id BoardID, url string, name string, creator std.Address) *Board { 32 if !reName.MatchString(name) { 33 panic("invalid name: " + name) 34 } 35 exists := gBoardsByName.Has(name) 36 if exists { 37 panic("board already exists") 38 } 39 return &Board{ 40 id: id, 41 url: url, 42 name: name, 43 creator: creator, 44 threads: avl.Tree{}, 45 createdAt: time.Now(), 46 deleted: avl.Tree{}, 47 } 48 } 49 50 /* TODO support this once we figure out how to ensure URL correctness. 51 // A private board is not tracked by gBoards*, 52 // but must be persisted by the caller's realm. 53 // Private boards have 0 id and does not ping 54 // back the remote board on reposts. 55 func NewPrivateBoard(url string, name string, creator std.Address) *Board { 56 return newBoard(0, url, name, creator) 57 } 58 */ 59 60 func (board *Board) IsPrivate() bool { 61 return board.id == 0 62 } 63 64 func (board *Board) GetThread(pid PostID) *Post { 65 pidkey := postIDKey(pid) 66 postI, exists := board.threads.Get(pidkey) 67 if !exists { 68 return nil 69 } 70 return postI.(*Post) 71 } 72 73 func (board *Board) AddThread(creator std.Address, title string, body string) *Post { 74 pid := board.incGetPostID() 75 pidkey := postIDKey(pid) 76 thread := newPost(board, pid, creator, title, body, pid, 0, 0) 77 board.threads.Set(pidkey, thread) 78 return thread 79 } 80 81 // NOTE: this can be potentially very expensive for threads with many replies. 82 // TODO: implement optional fast-delete where thread is simply moved. 83 func (board *Board) DeleteThread(pid PostID) { 84 pidkey := postIDKey(pid) 85 _, removed := board.threads.Remove(pidkey) 86 if !removed { 87 panic("thread does not exist with id " + pid.String()) 88 } 89 } 90 91 func (board *Board) HasPermission(addr std.Address, perm Permission) bool { 92 if board.creator == addr { 93 switch perm { 94 case EditPermission: 95 return true 96 case DeletePermission: 97 return true 98 default: 99 return false 100 } 101 } 102 return false 103 } 104 105 // Renders the board for display suitable as plaintext in 106 // console. This is suitable for demonstration or tests, 107 // but not for prod. 108 func (board *Board) RenderBoard() string { 109 str := "" 110 str += "\\[[post](" + board.GetPostFormURL() + ")]\n\n" 111 if board.threads.Size() > 0 { 112 board.threads.Iterate("", "", func(key string, value interface{}) bool { 113 if str != "" { 114 str += "----------------------------------------\n" 115 } 116 str += value.(*Post).RenderSummary() + "\n" 117 return false 118 }) 119 } 120 return str 121 } 122 123 func (board *Board) incGetPostID() PostID { 124 board.postsCtr++ 125 return PostID(board.postsCtr) 126 } 127 128 func (board *Board) GetURLFromThreadAndReplyID(threadID, replyID PostID) string { 129 if replyID == 0 { 130 return board.url + "/" + threadID.String() 131 } else { 132 return board.url + "/" + threadID.String() + "/" + replyID.String() 133 } 134 } 135 136 func (board *Board) GetPostFormURL() string { 137 return "/r/demo/boards?help&__func=CreateThread" + 138 "&bid=" + board.id.String() + 139 "&body.type=textarea" 140 }