diff --git a/examples/gno.land/r/demo/boards2/board.gno b/examples/gno.land/r/demo/boards2/board.gno deleted file mode 100644 index 79b27da84b2..00000000000 --- a/examples/gno.land/r/demo/boards2/board.gno +++ /dev/null @@ -1,139 +0,0 @@ -package boards - -import ( - "std" - "strconv" - "time" - - "gno.land/p/demo/avl" - "gno.land/p/moul/txlink" -) - -//---------------------------------------- -// Board - -type BoardID uint64 - -func (bid BoardID) String() string { - return strconv.Itoa(int(bid)) -} - -type Board struct { - id BoardID // only set for public boards. - url string - name string - creator std.Address - threads avl.Tree // Post.id -> *Post - postsCtr uint64 // increments Post.id - createdAt time.Time - deleted avl.Tree // TODO reserved for fast-delete. -} - -func newBoard(id BoardID, url string, name string, creator std.Address) *Board { - if !reName.MatchString(name) { - panic("invalid name: " + name) - } - exists := gBoardsByName.Has(name) - if exists { - panic("board already exists") - } - return &Board{ - id: id, - url: url, - name: name, - creator: creator, - threads: avl.Tree{}, - createdAt: time.Now(), - deleted: avl.Tree{}, - } -} - -/* TODO support this once we figure out how to ensure URL correctness. -// A private board is not tracked by gBoards*, -// but must be persisted by the caller's realm. -// Private boards have 0 id and does not ping -// back the remote board on reposts. -func NewPrivateBoard(url string, name string, creator std.Address) *Board { - return newBoard(0, url, name, creator) -} -*/ - -func (board *Board) IsPrivate() bool { - return board.id == 0 -} - -func (board *Board) GetThread(pid PostID) *Post { - pidkey := postIDKey(pid) - postI, exists := board.threads.Get(pidkey) - if !exists { - return nil - } - return postI.(*Post) -} - -func (board *Board) AddThread(creator std.Address, title string, body string) *Post { - pid := board.incGetPostID() - pidkey := postIDKey(pid) - thread := newPost(board, pid, creator, title, body, pid, 0, 0) - board.threads.Set(pidkey, thread) - return thread -} - -// NOTE: this can be potentially very expensive for threads with many replies. -// TODO: implement optional fast-delete where thread is simply moved. -func (board *Board) DeleteThread(pid PostID) { - pidkey := postIDKey(pid) - _, removed := board.threads.Remove(pidkey) - if !removed { - panic("thread does not exist with id " + pid.String()) - } -} - -func (board *Board) HasPermission(addr std.Address, perm Permission) bool { - if board.creator == addr { - switch perm { - case EditPermission: - return true - case DeletePermission: - return true - default: - return false - } - } - return false -} - -// Renders the board for display suitable as plaintext in -// console. This is suitable for demonstration or tests, -// but not for prod. -func (board *Board) RenderBoard() string { - str := "" - str += "\\[[post](" + board.GetPostFormURL() + ")]\n\n" - if board.threads.Size() > 0 { - board.threads.Iterate("", "", func(key string, value interface{}) bool { - if str != "" { - str += "----------------------------------------\n" - } - str += value.(*Post).RenderSummary() + "\n" - return false - }) - } - return str -} - -func (board *Board) incGetPostID() PostID { - board.postsCtr++ - return PostID(board.postsCtr) -} - -func (board *Board) GetURLFromThreadAndReplyID(threadID, replyID PostID) string { - if replyID == 0 { - return board.url + "/" + threadID.String() - } else { - return board.url + "/" + threadID.String() + "/" + replyID.String() - } -} - -func (board *Board) GetPostFormURL() string { - return txlink.URL("CreateThread", "bid", board.id.String()) -} diff --git a/examples/gno.land/r/demo/boards2/boards.gno b/examples/gno.land/r/demo/boards2/boards.gno deleted file mode 100644 index 5de0555a2f9..00000000000 --- a/examples/gno.land/r/demo/boards2/boards.gno +++ /dev/null @@ -1,22 +0,0 @@ -package boards - -import ( - "regexp" - - "gno.land/p/demo/avl" -) - -//---------------------------------------- -// Realm (package) state - -var ( - gBoards avl.Tree // id -> *Board - gBoardsCtr int // increments Board.id - gBoardsByName avl.Tree // name -> *Board - gDefaultAnonFee = 100000000 // minimum fee required if anonymous -) - -//---------------------------------------- -// Constants - -var reName = regexp.MustCompile(`^[a-z]+[_a-z0-9]{2,29}$`) diff --git a/examples/gno.land/r/demo/boards2/gno.mod b/examples/gno.land/r/demo/boards2/gno.mod deleted file mode 100644 index 1738959bf31..00000000000 --- a/examples/gno.land/r/demo/boards2/gno.mod +++ /dev/null @@ -1,7 +0,0 @@ -module gno.land/r/demo/boards2 - -require ( - gno.land/p/demo/avl v0.0.0-latest - gno.land/p/moul/txlink v0.0.0-latest - gno.land/r/demo/users v0.0.0-latest -) diff --git a/examples/gno.land/r/demo/boards2/misc.gno b/examples/gno.land/r/demo/boards2/misc.gno deleted file mode 100644 index bc561ca7d22..00000000000 --- a/examples/gno.land/r/demo/boards2/misc.gno +++ /dev/null @@ -1,95 +0,0 @@ -package boards - -import ( - "std" - "strconv" - "strings" - - "gno.land/r/demo/users" -) - -//---------------------------------------- -// private utility methods -// XXX ensure these cannot be called from public. - -func getBoard(bid BoardID) *Board { - bidkey := boardIDKey(bid) - board_, exists := gBoards.Get(bidkey) - if !exists { - return nil - } - board := board_.(*Board) - return board -} - -func incGetBoardID() BoardID { - gBoardsCtr++ - return BoardID(gBoardsCtr) -} - -func padLeft(str string, length int) string { - if len(str) >= length { - return str - } else { - return strings.Repeat(" ", length-len(str)) + str - } -} - -func padZero(u64 uint64, length int) string { - str := strconv.Itoa(int(u64)) - if len(str) >= length { - return str - } else { - return strings.Repeat("0", length-len(str)) + str - } -} - -func boardIDKey(bid BoardID) string { - return padZero(uint64(bid), 10) -} - -func postIDKey(pid PostID) string { - return padZero(uint64(pid), 10) -} - -func indentBody(indent string, body string) string { - lines := strings.Split(body, "\n") - res := "" - for i, line := range lines { - if i > 0 { - res += "\n" - } - res += indent + line - } - return res -} - -// NOTE: length must be greater than 3. -func summaryOf(str string, length int) string { - lines := strings.SplitN(str, "\n", 2) - line := lines[0] - if len(line) > length { - line = line[:(length-3)] + "..." - } else if len(lines) > 1 { - // len(line) <= 80 - line = line + "..." - } - return line -} - -func displayAddressMD(addr std.Address) string { - user := users.GetUserByAddress(addr) - if user == nil { - return "[" + addr.String() + "](/r/demo/users:" + addr.String() + ")" - } else { - return "[@" + user.Name + "](/r/demo/users:" + user.Name + ")" - } -} - -func usernameOf(addr std.Address) string { - user := users.GetUserByAddress(addr) - if user == nil { - return "" - } - return user.Name -} diff --git a/examples/gno.land/r/demo/boards2/post.gno b/examples/gno.land/r/demo/boards2/post.gno deleted file mode 100644 index 95d4b2977ba..00000000000 --- a/examples/gno.land/r/demo/boards2/post.gno +++ /dev/null @@ -1,263 +0,0 @@ -package boards - -import ( - "std" - "strconv" - "time" - - "gno.land/p/demo/avl" - "gno.land/p/moul/txlink" -) - -//---------------------------------------- -// Post - -// NOTE: a PostID is relative to the board. -type PostID uint64 - -func (pid PostID) String() string { - return strconv.Itoa(int(pid)) -} - -// A Post is a "thread" or a "reply" depending on context. -// A thread is a Post of a Board that holds other replies. -type Post struct { - board *Board - id PostID - creator std.Address - title string // optional - body string - replies avl.Tree // Post.id -> *Post - repliesAll avl.Tree // Post.id -> *Post (all replies, for top-level posts) - reposts avl.Tree // Board.id -> Post.id - threadID PostID // original Post.id - parentID PostID // parent Post.id (if reply or repost) - repostBoard BoardID // original Board.id (if repost) - createdAt time.Time - updatedAt time.Time -} - -func newPost(board *Board, id PostID, creator std.Address, title, body string, threadID, parentID PostID, repostBoard BoardID) *Post { - return &Post{ - board: board, - id: id, - creator: creator, - title: title, - body: body, - replies: avl.Tree{}, - repliesAll: avl.Tree{}, - reposts: avl.Tree{}, - threadID: threadID, - parentID: parentID, - repostBoard: repostBoard, - createdAt: time.Now(), - } -} - -func (post *Post) IsThread() bool { - return post.parentID == 0 -} - -func (post *Post) GetPostID() PostID { - return post.id -} - -func (post *Post) AddReply(creator std.Address, body string) *Post { - board := post.board - pid := board.incGetPostID() - pidkey := postIDKey(pid) - reply := newPost(board, pid, creator, "", body, post.threadID, post.id, 0) - post.replies.Set(pidkey, reply) - if post.threadID == post.id { - post.repliesAll.Set(pidkey, reply) - } else { - thread := board.GetThread(post.threadID) - thread.repliesAll.Set(pidkey, reply) - } - return reply -} - -func (post *Post) Update(title string, body string) { - post.title = title - post.body = body - post.updatedAt = time.Now() -} - -func (thread *Post) GetReply(pid PostID) *Post { - pidkey := postIDKey(pid) - replyI, ok := thread.repliesAll.Get(pidkey) - if !ok { - return nil - } else { - return replyI.(*Post) - } -} - -func (post *Post) AddRepostTo(creator std.Address, title, body string, dst *Board) *Post { - if !post.IsThread() { - panic("cannot repost non-thread post") - } - pid := dst.incGetPostID() - pidkey := postIDKey(pid) - repost := newPost(dst, pid, creator, title, body, pid, post.id, post.board.id) - dst.threads.Set(pidkey, repost) - if !dst.IsPrivate() { - bidkey := boardIDKey(dst.id) - post.reposts.Set(bidkey, pid) - } - return repost -} - -func (thread *Post) DeletePost(pid PostID) { - if thread.id == pid { - panic("should not happen") - } - pidkey := postIDKey(pid) - postI, removed := thread.repliesAll.Remove(pidkey) - if !removed { - panic("post not found in thread") - } - post := postI.(*Post) - if post.parentID != thread.id { - parent := thread.GetReply(post.parentID) - parent.replies.Remove(pidkey) - } else { - thread.replies.Remove(pidkey) - } -} - -func (post *Post) HasPermission(addr std.Address, perm Permission) bool { - if post.creator == addr { - switch perm { - case EditPermission: - return true - case DeletePermission: - return true - default: - return false - } - } - // post notes inherit permissions of the board. - return post.board.HasPermission(addr, perm) -} - -func (post *Post) GetSummary() string { - return summaryOf(post.body, 80) -} - -func (post *Post) GetURL() string { - if post.IsThread() { - return post.board.GetURLFromThreadAndReplyID( - post.id, 0) - } else { - return post.board.GetURLFromThreadAndReplyID( - post.threadID, post.id) - } -} - -func (post *Post) GetReplyFormURL() string { - return txlink.URL("CreateReply", - "bid", post.board.id.String(), - "threadid", post.threadID.String(), - "postid", post.id.String(), - ) -} - -func (post *Post) GetRepostFormURL() string { - return txlink.URL("CreateRepost", - "bid", post.board.id.String(), - "postid", post.id.String(), - ) -} - -func (post *Post) GetDeleteFormURL() string { - return txlink.URL("DeletePost", - "bid", post.board.id.String(), - "threadid", post.threadID.String(), - "postid", post.id.String(), - ) -} - -func (post *Post) RenderSummary() string { - if post.repostBoard != 0 { - dstBoard := getBoard(post.repostBoard) - if dstBoard == nil { - panic("repostBoard does not exist") - } - thread := dstBoard.GetThread(PostID(post.parentID)) - if thread == nil { - return "reposted post does not exist" - } - return "Repost: " + post.GetSummary() + "\n" + thread.RenderSummary() - } - str := "" - if post.title != "" { - str += "## [" + summaryOf(post.title, 80) + "](" + post.GetURL() + ")\n" - str += "\n" - } - str += post.GetSummary() + "\n" - str += "\\- " + displayAddressMD(post.creator) + "," - str += " [" + post.createdAt.Format("2006-01-02 3:04pm MST") + "](" + post.GetURL() + ")" - str += " \\[[x](" + post.GetDeleteFormURL() + ")]" - str += " (" + strconv.Itoa(post.replies.Size()) + " replies)" - str += " (" + strconv.Itoa(post.reposts.Size()) + " reposts)" + "\n" - return str -} - -func (post *Post) RenderPost(indent string, levels int) string { - if post == nil { - return "nil post" - } - str := "" - if post.title != "" { - str += indent + "# " + post.title + "\n" - str += indent + "\n" - } - str += indentBody(indent, post.body) + "\n" // TODO: indent body lines. - str += indent + "\\- " + displayAddressMD(post.creator) + ", " - str += "[" + post.createdAt.Format("2006-01-02 3:04pm (MST)") + "](" + post.GetURL() + ")" - str += " \\[[reply](" + post.GetReplyFormURL() + ")]" - if post.IsThread() { - str += " \\[[repost](" + post.GetRepostFormURL() + ")]" - } - str += " \\[[x](" + post.GetDeleteFormURL() + ")]\n" - if levels > 0 { - if post.replies.Size() > 0 { - post.replies.Iterate("", "", func(key string, value interface{}) bool { - str += indent + "\n" - str += value.(*Post).RenderPost(indent+"> ", levels-1) - return false - }) - } - } else { - if post.replies.Size() > 0 { - str += indent + "\n" - str += indent + "_[see all " + strconv.Itoa(post.replies.Size()) + " replies](" + post.GetURL() + ")_\n" - } - } - return str -} - -// render reply and link to context thread -func (post *Post) RenderInner() string { - if post.IsThread() { - panic("unexpected thread") - } - threadID := post.threadID - // replyID := post.id - parentID := post.parentID - str := "" - str += "_[see thread](" + post.board.GetURLFromThreadAndReplyID( - threadID, 0) + ")_\n\n" - thread := post.board.GetThread(post.threadID) - var parent *Post - if thread.id == parentID { - parent = thread - } else { - parent = thread.GetReply(parentID) - } - str += parent.RenderPost("", 0) - str += "\n" - str += post.RenderPost("> ", 5) - return str -} diff --git a/examples/gno.land/r/demo/boards2/public.gno b/examples/gno.land/r/demo/boards2/public.gno deleted file mode 100644 index 1d26126fcb2..00000000000 --- a/examples/gno.land/r/demo/boards2/public.gno +++ /dev/null @@ -1,185 +0,0 @@ -package boards - -import ( - "std" - "strconv" -) - -//---------------------------------------- -// Public facing functions - -func GetBoardIDFromName(name string) (BoardID, bool) { - boardI, exists := gBoardsByName.Get(name) - if !exists { - return 0, false - } - return boardI.(*Board).id, true -} - -func CreateBoard(name string) BoardID { - if !(std.IsOriginCall() || std.PrevRealm().IsUser()) { - panic("invalid non-user call") - } - bid := incGetBoardID() - caller := std.GetOrigCaller() - if usernameOf(caller) == "" { - panic("unauthorized") - } - url := "/r/demo/boards:" + name - board := newBoard(bid, url, name, caller) - bidkey := boardIDKey(bid) - gBoards.Set(bidkey, board) - gBoardsByName.Set(name, board) - return board.id -} - -func checkAnonFee() bool { - sent := std.GetOrigSend() - anonFeeCoin := std.NewCoin("ugnot", int64(gDefaultAnonFee)) - if len(sent) == 1 && sent[0].IsGTE(anonFeeCoin) { - return true - } - return false -} - -func CreateThread(bid BoardID, title string, body string) PostID { - if !(std.IsOriginCall() || std.PrevRealm().IsUser()) { - panic("invalid non-user call") - } - caller := std.GetOrigCaller() - if usernameOf(caller) == "" { - if !checkAnonFee() { - panic("please register, otherwise minimum fee " + strconv.Itoa(gDefaultAnonFee) + " is required if anonymous") - } - } - board := getBoard(bid) - if board == nil { - panic("board not exist") - } - thread := board.AddThread(caller, title, body) - return thread.id -} - -func CreateReply(bid BoardID, threadid, postid PostID, body string) PostID { - if !(std.IsOriginCall() || std.PrevRealm().IsUser()) { - panic("invalid non-user call") - } - caller := std.GetOrigCaller() - if usernameOf(caller) == "" { - if !checkAnonFee() { - panic("please register, otherwise minimum fee " + strconv.Itoa(gDefaultAnonFee) + " is required if anonymous") - } - } - board := getBoard(bid) - if board == nil { - panic("board not exist") - } - thread := board.GetThread(threadid) - if thread == nil { - panic("thread not exist") - } - if postid == threadid { - reply := thread.AddReply(caller, body) - return reply.id - } else { - post := thread.GetReply(postid) - reply := post.AddReply(caller, body) - return reply.id - } -} - -// If dstBoard is private, does not ping back. -// If board specified by bid is private, panics. -func CreateRepost(bid BoardID, postid PostID, title string, body string, dstBoardID BoardID) PostID { - if !(std.IsOriginCall() || std.PrevRealm().IsUser()) { - panic("invalid non-user call") - } - caller := std.GetOrigCaller() - if usernameOf(caller) == "" { - // TODO: allow with gDefaultAnonFee payment. - if !checkAnonFee() { - panic("please register, otherwise minimum fee " + strconv.Itoa(gDefaultAnonFee) + " is required if anonymous") - } - } - board := getBoard(bid) - if board == nil { - panic("src board not exist") - } - if board.IsPrivate() { - panic("cannot repost from a private board") - } - dst := getBoard(dstBoardID) - if dst == nil { - panic("dst board not exist") - } - thread := board.GetThread(postid) - if thread == nil { - panic("thread not exist") - } - repost := thread.AddRepostTo(caller, title, body, dst) - return repost.id -} - -func DeletePost(bid BoardID, threadid, postid PostID, reason string) { - if !(std.IsOriginCall() || std.PrevRealm().IsUser()) { - panic("invalid non-user call") - } - caller := std.GetOrigCaller() - board := getBoard(bid) - if board == nil { - panic("board not exist") - } - thread := board.GetThread(threadid) - if thread == nil { - panic("thread not exist") - } - if postid == threadid { - // delete thread - if !thread.HasPermission(caller, DeletePermission) { - panic("unauthorized") - } - board.DeleteThread(threadid) - } else { - // delete thread's post - post := thread.GetReply(postid) - if post == nil { - panic("post not exist") - } - if !post.HasPermission(caller, DeletePermission) { - panic("unauthorized") - } - thread.DeletePost(postid) - } -} - -func EditPost(bid BoardID, threadid, postid PostID, title, body string) { - if !(std.IsOriginCall() || std.PrevRealm().IsUser()) { - panic("invalid non-user call") - } - caller := std.GetOrigCaller() - board := getBoard(bid) - if board == nil { - panic("board not exist") - } - thread := board.GetThread(threadid) - if thread == nil { - panic("thread not exist") - } - if postid == threadid { - // edit thread - if !thread.HasPermission(caller, EditPermission) { - panic("unauthorized") - } - thread.Update(title, body) - } else { - // edit thread's post - post := thread.GetReply(postid) - if post == nil { - panic("post not exist") - } - if !post.HasPermission(caller, EditPermission) { - panic("unauthorized") - } - post.Update(title, body) - } -} diff --git a/examples/gno.land/r/demo/boards2/render.gno b/examples/gno.land/r/demo/boards2/render.gno deleted file mode 100644 index 3709ad02e5d..00000000000 --- a/examples/gno.land/r/demo/boards2/render.gno +++ /dev/null @@ -1,83 +0,0 @@ -package boards - -import ( - "strconv" - "strings" -) - -//---------------------------------------- -// Render functions - -func RenderBoard(bid BoardID) string { - board := getBoard(bid) - if board == nil { - return "missing board" - } - return board.RenderBoard() -} - -func Render(path string) string { - if path == "" { - str := "These are all the boards of this realm:\n\n" - gBoards.Iterate("", "", func(key string, value interface{}) bool { - board := value.(*Board) - str += " * [" + board.url + "](" + board.url + ")\n" - return false - }) - return str - } - parts := strings.Split(path, "/") - if len(parts) == 1 { - // /r/demo/boards:BOARD_NAME - name := parts[0] - boardI, exists := gBoardsByName.Get(name) - if !exists { - return "board does not exist: " + name - } - return boardI.(*Board).RenderBoard() - } else if len(parts) == 2 { - // /r/demo/boards:BOARD_NAME/THREAD_ID - name := parts[0] - boardI, exists := gBoardsByName.Get(name) - if !exists { - return "board does not exist: " + name - } - pid, err := strconv.Atoi(parts[1]) - if err != nil { - return "invalid thread id: " + parts[1] - } - board := boardI.(*Board) - thread := board.GetThread(PostID(pid)) - if thread == nil { - return "thread does not exist with id: " + parts[1] - } - return thread.RenderPost("", 5) - } else if len(parts) == 3 { - // /r/demo/boards:BOARD_NAME/THREAD_ID/REPLY_ID - name := parts[0] - boardI, exists := gBoardsByName.Get(name) - if !exists { - return "board does not exist: " + name - } - pid, err := strconv.Atoi(parts[1]) - if err != nil { - return "invalid thread id: " + parts[1] - } - board := boardI.(*Board) - thread := board.GetThread(PostID(pid)) - if thread == nil { - return "thread does not exist with id: " + parts[1] - } - rid, err := strconv.Atoi(parts[2]) - if err != nil { - return "invalid reply id: " + parts[2] - } - reply := thread.GetReply(PostID(rid)) - if reply == nil { - return "reply does not exist with id: " + parts[2] - } - return reply.RenderInner() - } else { - return "unrecognized path " + path - } -} diff --git a/examples/gno.land/r/demo/boards2/role.gno b/examples/gno.land/r/demo/boards2/role.gno deleted file mode 100644 index 64073d64f34..00000000000 --- a/examples/gno.land/r/demo/boards2/role.gno +++ /dev/null @@ -1,8 +0,0 @@ -package boards - -type Permission string - -const ( - DeletePermission Permission = "role:delete" - EditPermission Permission = "role:edit" -)