Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor!: split r/demo/boards #1989

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 137 additions & 0 deletions examples/gno.land/p/demo/boards/board.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package boards

import (
"std"
"strconv"
"time"

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

//----------------------------------------
// Board

type BoardID uint64

func BoardIDKey(bid BoardID) string {
return padZero(uint64(bid), 10)
}

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.
}

/* 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 NewBoard(id BoardID, url string, name string, creator std.Address) *Board {
return &Board{
id: id,
url: url,
name: name,
creator: creator,
threads: avl.Tree{},
createdAt: time.Now(),
deleted: avl.Tree{},
}
}

func (board *Board) Id() BoardID {
return board.id
}

func (board *Board) Url() string {
return board.url
}

func (board *Board) Name() string {
return board.name
}

func (board *Board) Creator() std.Address {
return board.creator
}

func (board *Board) Threads() avl.Tree {
return board.threads
}

func (board *Board) CreatedAt() time.Time {
return board.createdAt
}

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 {
harry-hov marked this conversation as resolved.
Show resolved Hide resolved
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
}

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()
}
}
3 changes: 3 additions & 0 deletions examples/gno.land/p/demo/boards/gno.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module gno.land/p/demo/boards

require gno.land/p/demo/avl v0.0.0-latest
32 changes: 32 additions & 0 deletions examples/gno.land/p/demo/boards/misc.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package boards

import (
"strconv"
"strings"
)

func postIDKey(pid PostID) string {
return padZero(uint64(pid), 10)
}

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
}
}

// 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
}
199 changes: 199 additions & 0 deletions examples/gno.land/p/demo/boards/post.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
package boards

import (
"std"
"strconv"
"time"

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

//----------------------------------------
// 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) Board() Board {
return *post.board
}

func (post *Post) Id() PostID {
return post.id
}

func (post *Post) Creator() std.Address {
return post.creator
}

func (post *Post) Title() string {
return post.title
}

func (post *Post) Body() string {
return post.body
}

func (post *Post) Replies() avl.Tree {
return post.replies
}

func (post *Post) RepliesAll() avl.Tree {
return post.repliesAll
}

func (post *Post) Reposts() avl.Tree {
return post.reposts
}

func (post *Post) ThreadID() PostID {
return post.threadID
}

func (post *Post) ParentID() PostID {
return post.parentID
}

func (post *Post) IsThread() bool {
return post.parentID == 0
}

func (post *Post) RepostBoard() BoardID {
return post.repostBoard
}

func (post *Post) CreatedAt() time.Time {
return post.createdAt
}

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)
}
}
2 changes: 1 addition & 1 deletion examples/gno.land/p/demo/groups/gno.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module gno.land/p/demo/groups

require (
gno.land/p/demo/boards v0.0.0-latest
gno.land/p/demo/rat v0.0.0-latest
gno.land/r/demo/boards v0.0.0-latest
)
2 changes: 1 addition & 1 deletion examples/gno.land/p/demo/groups/groups.gno
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package groups

import (
"gno.land/r/demo/boards"
"gno.land/p/demo/boards"
)

// TODO implement something and test.
Expand Down
Loading
Loading