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}