board.gno
3.25 Kb ยท 138 lines
1package boards
2
3import (
4 "std"
5 "strconv"
6 "time"
7
8 "gno.land/p/demo/avl"
9)
10
11//----------------------------------------
12// Board
13
14type BoardID uint64
15
16func (bid BoardID) String() string {
17 return strconv.Itoa(int(bid))
18}
19
20type 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
31func 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.
55func NewPrivateBoard(url string, name string, creator std.Address) *Board {
56 return newBoard(0, url, name, creator)
57}
58*/
59
60func (board *Board) IsPrivate() bool {
61 return board.id == 0
62}
63
64func (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
73func (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.
83func (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
91func (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.
108func (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 any) bool {
113 if str != "" {
114 str += "----------------------------------------\n"
115 }
116 str += value.(*Post).RenderSummary() + "\n"
117 return false
118 })
119 }
120 return str
121}
122
123func (board *Board) incGetPostID() PostID {
124 board.postsCtr++
125 return PostID(board.postsCtr)
126}
127
128func (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
136func (board *Board) GetPostFormURL() string {
137 return gRealmLink.Call("CreateThread", "bid", board.id.String())
138}