Skip to content

Commit

Permalink
feat: simplify boards2 implementation (#3115)
Browse files Browse the repository at this point in the history
Refactors the code copied from `gno.land/r/demo/boards` to simplify it
and to have it ready before introducing the new features.
  • Loading branch information
jeronimoalbi authored Nov 13, 2024
1 parent 1510a6f commit 4c7d16b
Show file tree
Hide file tree
Showing 8 changed files with 470 additions and 448 deletions.
89 changes: 47 additions & 42 deletions examples/gno.land/r/demo/boards2/board.gno
Original file line number Diff line number Diff line change
@@ -1,26 +1,30 @@
package boards

import (
"regexp"
"std"
"strconv"
"strings"
"time"

"gno.land/p/demo/avl"
"gno.land/p/moul/txlink"
)

//----------------------------------------
// Board
var reBoardName = regexp.MustCompile(`^[a-z]{3}[_a-z0-9]{0,23}[0-9]{3}$`)

type BoardID uint64

func (bid BoardID) String() string {
return strconv.Itoa(int(bid))
func (id BoardID) String() string {
return strconv.Itoa(int(id))
}

func (id BoardID) Key() string {
return padZero(uint64(id), 10)
}

type Board struct {
id BoardID // only set for public boards.
url string
name string
creator std.Address
threads avl.Tree // Post.id -> *Post
Expand All @@ -29,17 +33,15 @@ type Board struct {
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 {
func newBoard(id BoardID, name string, creator std.Address) *Board {
assertIsBoardName(name)

if gBoardsByName.Has(name) {
panic("board already exists")
}

return &Board{
id: id,
url: url,
name: name,
creator: creator,
threads: avl.Tree{},
Expand All @@ -62,39 +64,42 @@ 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
// GetURL returns the relative URL of the board.
func (board *Board) GetURL() string {
return strings.TrimPrefix(std.CurrentRealm().PkgPath(), "gno.land") + ":" + board.name
}

func (board *Board) GetThread(threadID PostID) (_ *Post, found bool) {
v, found := board.threads.Get(threadID.Key())
if !found {
return nil, false
}
return postI.(*Post)
return v.(*Post), true
}

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)
board.threads.Set(pid.Key(), 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)
_, removed := board.threads.Remove(pid.Key())
if !removed {
panic("thread does not exist with id " + pid.String())
}
}

// TODO: Change HasPermission to use a new authorization interface's `CanDo()`
func (board *Board) HasPermission(addr std.Address, perm Permission) bool {
if board.creator == addr {
switch perm {
case EditPermission:
case PermissionEdit:
return true
case DeletePermission:
case PermissionDelete:
return true
default:
return false
Expand All @@ -103,37 +108,37 @@ func (board *Board) HasPermission(addr std.Address, perm Permission) bool {
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"
func (board *Board) Render() string {
s := "\\[" + newLink("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"
board.threads.Iterate("", "", func(_ string, v interface{}) bool {
s += "----------------------------------------\n"
s += v.(*Post).RenderSummary() + "\n"
return false
})
}
return str
return s
}

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) GetURLFromThreadID(threadID PostID) string {
return board.GetURL() + "/" + threadID.String()
}

func (board *Board) GetURLFromReplyID(threadID, replyID PostID) string {
return board.GetURL() + "/" + threadID.String() + "/" + replyID.String()
}

func (board *Board) GetPostFormURL() string {
return txlink.URL("CreateThread", "bid", board.id.String())
}

func assertIsBoardName(name string) {
if !reBoardName.MatchString(name) {
panic("invalid board name: " + name)
}
}
60 changes: 46 additions & 14 deletions examples/gno.land/r/demo/boards2/boards.gno
Original file line number Diff line number Diff line change
@@ -1,22 +1,54 @@
package boards

import (
"regexp"
import "gno.land/p/demo/avl"

"gno.land/p/demo/avl"
)

//----------------------------------------
// Realm (package) state
// Default minimum fee in ugnot required for anonymous users
const defaultAnonymousFee = 100_000_000

var (
gBoards avl.Tree // id -> *Board
gBoardsCtr int // increments Board.id
gBoardsByName avl.Tree // name -> *Board
gDefaultAnonFee = 100000000 // minimum fee required if anonymous
gLastBoardID BoardID
gBoardsByID avl.Tree // string(id) -> *Board
gBoardsByName avl.Tree // string(name) -> *Board
)

//----------------------------------------
// Constants
// incGetBoardID returns a new board ID.
func incGetBoardID() BoardID {
gLastBoardID++
return gLastBoardID
}

// getBoard returns a board for a specific ID.
func getBoard(id BoardID) (_ *Board, found bool) {
v, exists := gBoardsByID.Get(id.Key())
if !exists {
return nil, false
}
return v.(*Board), true
}

// mustGetBoard returns a board or panics when it's not found.
func mustGetBoard(id BoardID) *Board {
board, found := getBoard(id)
if !found {
panic("board does not exist with ID: " + id.String())
}
return board
}

// mustGetThread returns a thread or panics when it's not found.
func mustGetThread(board *Board, threadID PostID) *Post {
thread, found := board.GetThread(threadID)
if !found {
panic("thread does not exist with ID: " + threadID.String())
}
return thread
}

var reName = regexp.MustCompile(`^[a-z]+[_a-z0-9]{2,29}$`)
// mustGetReply returns a reply or panics when it's not found.
func mustGetReply(thread *Post, replyID PostID) *Post {
reply, found := thread.GetReply(replyID)
if !found {
panic("reply does not exist with ID: " + replyID.String())
}
return reply
}
65 changes: 65 additions & 0 deletions examples/gno.land/r/demo/boards2/format.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package boards

import (
"std"
"strconv"
"strings"

"gno.land/r/demo/users"
)

func padLeft(s string, length int) string {
if len(s) >= length {
return s
}
return strings.Repeat(" ", length-len(s)) + s
}

func padZero(u64 uint64, length int) string {
s := strconv.Itoa(int(u64))
if len(s) >= length {
return s
}
return strings.Repeat("0", length-len(s)) + s
}

func indentBody(indent string, body string) string {
var (
res string
lines = strings.Split(body, "\n")
)
for i, line := range lines {
if i > 0 {
res += "\n"
}
res += indent + line
}
return res
}

// NOTE: length must be greater than 3.
func summaryOf(text string, length int) string {
lines := strings.SplitN(text, "\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
}

// newLink returns a Markdown link.
func newLink(label, uri string) string {
return "[" + label + "](" + uri + ")"
}

// newUserLink returns a Markdown link for an account to the users realm.
func newUserLink(addr std.Address) string {
user := users.GetUserByAddress(addr)
if user == nil {
return newLink(addr.String(), "/r/demo/users:"+addr.String())
}
return newLink("@"+user.Name, "/r/demo/users:"+user.Name)
}
Loading

0 comments on commit 4c7d16b

Please sign in to comment.