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