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}