Search Apps Documentation Source Content File Folder Download Copy

board.gno

3.28 Kb ยท 139 lines
  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}