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