From 35cee37511b702699523f4a0e984f363acff3d51 Mon Sep 17 00:00:00 2001 From: jeronimoalbi Date: Fri, 4 Oct 2024 15:06:47 +0200 Subject: [PATCH 01/19] wip: defining a simple package API for creating posts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: İlker G. Öztürk --- examples/gno.land/p/demo/boardsv2/app.gno | 35 +++++++ examples/gno.land/p/demo/boardsv2/board.gno | 20 ++++ .../gno.land/p/demo/boardsv2/boardsv2.gno | 8 +- examples/gno.land/p/demo/boardsv2/option.gno | 11 +++ examples/gno.land/p/demo/boardsv2/post.gno | 30 ++++++ .../gno.land/p/demo/boardsv2/post/content.gno | 5 + .../boardsv2/post/plugins/content/content.gno | 30 ++++++ .../gno.land/p/demo/boardsv2/post/post.gno | 91 ++++++++++++++++++- .../gno.land/p/demo/boardsv2/post/storage.gno | 4 + .../gno.land/p/demo/boardsv2/post/view.gno | 2 +- .../gno.land/r/demo/boardsv2/boardsv2.gno | 45 ++++++++- 11 files changed, 277 insertions(+), 4 deletions(-) create mode 100644 examples/gno.land/p/demo/boardsv2/app.gno create mode 100644 examples/gno.land/p/demo/boardsv2/board.gno create mode 100644 examples/gno.land/p/demo/boardsv2/option.gno create mode 100644 examples/gno.land/p/demo/boardsv2/post.gno create mode 100644 examples/gno.land/p/demo/boardsv2/post/content.gno create mode 100644 examples/gno.land/p/demo/boardsv2/post/plugins/content/content.gno create mode 100644 examples/gno.land/p/demo/boardsv2/post/storage.gno diff --git a/examples/gno.land/p/demo/boardsv2/app.gno b/examples/gno.land/p/demo/boardsv2/app.gno new file mode 100644 index 00000000000..ab2f184e5e8 --- /dev/null +++ b/examples/gno.land/p/demo/boardsv2/app.gno @@ -0,0 +1,35 @@ +package boardsv2 + +import ( + "gno.land/demo/p/boardsv2/post" + commentplugin "gno.land/demo/p/boardsv2/post/plugins/content" +) + +type App struct { + s Storage + boards []Board +} + +func New(s Storage, o ...Option) App { + a := App{ + s: Storage, + } + return a +} + +func (a *App) AddBoard(name, title, description string) (*Board, error) { + p := post.New(commentplugin.TitleBasedContent{ + Title: title, + Description: description, + }) + post.Add(a.s, name, p) + return a.GetBoard(name) +} + +func (a *App) GetBoard(name string) (board *Board, found bool) { + +} + +func (a *App) ListBoards() ([]*Board, error) { + +} diff --git a/examples/gno.land/p/demo/boardsv2/board.gno b/examples/gno.land/p/demo/boardsv2/board.gno new file mode 100644 index 00000000000..cf5d89ce828 --- /dev/null +++ b/examples/gno.land/p/demo/boardsv2/board.gno @@ -0,0 +1,20 @@ +package boardsv2 + +type Board struct { +} + +func (b *Board) AddPost() error { + +} + +func (b *Board) GetPost(id string) (post *Post, found bool) { + +} + +func (b *Board) Fork() error { + +} + +func (b *Board) Lock() error { + +} diff --git a/examples/gno.land/p/demo/boardsv2/boardsv2.gno b/examples/gno.land/p/demo/boardsv2/boardsv2.gno index 4ad3d466272..91c308f9576 100644 --- a/examples/gno.land/p/demo/boardsv2/boardsv2.gno +++ b/examples/gno.land/p/demo/boardsv2/boardsv2.gno @@ -1 +1,7 @@ -package boardsv2 \ No newline at end of file +// boardsv2 is a reddit like abstraction around post/*. +// You might implement other abstractions around post/* to create +// different type of dApps. +// refer to the app.gno file to get started. +package boardsv2 + + diff --git a/examples/gno.land/p/demo/boardsv2/option.gno b/examples/gno.land/p/demo/boardsv2/option.gno new file mode 100644 index 00000000000..6d45c967dde --- /dev/null +++ b/examples/gno.land/p/demo/boardsv2/option.gno @@ -0,0 +1,11 @@ +package boardsv2 + +type Option struct{} + +// LinearReputationPolicy allows upvoting or downvoting a post by one +// for each account. +func LineerReputationPolicy() Option {} + +// TokenBasedReputationPolicy allows upvoting or downvoting a post propotional +// to the specified tokens that an account holds. +func TokenBasedReputationPolicy() Option {} diff --git a/examples/gno.land/p/demo/boardsv2/post.gno b/examples/gno.land/p/demo/boardsv2/post.gno new file mode 100644 index 00000000000..a15e1df5399 --- /dev/null +++ b/examples/gno.land/p/demo/boardsv2/post.gno @@ -0,0 +1,30 @@ +package boardsv2 + +import ( + "gno.land/demo/p/boardsv2/post" + replyplugin "gno.land/demo/p/boardsv2/post/plugins/content/reply" +) + +type Post struct { + post post.Post + st Store +} + +func (p *Post) Comment(creator std.Address, message string) (id string, err error) { + pp := p.New(replyplugin.MessageContent{ + Message: message, + }) + id := p.post.NextIncrementalKey(creator.String()) // Post.ID/address/1 = "comment ID" + if err := post.Add(p.st, id); err != nil { + return "", err + } + return id, nil +} + +func (p *Post) Upvote() error { + +} + +func (p *Post) Downvote() error { + +} diff --git a/examples/gno.land/p/demo/boardsv2/post/content.gno b/examples/gno.land/p/demo/boardsv2/post/content.gno new file mode 100644 index 00000000000..cbfd45c36fe --- /dev/null +++ b/examples/gno.land/p/demo/boardsv2/post/content.gno @@ -0,0 +1,5 @@ +package post + +type Content interface { + Render() string +} diff --git a/examples/gno.land/p/demo/boardsv2/post/plugins/content/content.gno b/examples/gno.land/p/demo/boardsv2/post/plugins/content/content.gno new file mode 100644 index 00000000000..05043e7e172 --- /dev/null +++ b/examples/gno.land/p/demo/boardsv2/post/plugins/content/content.gno @@ -0,0 +1,30 @@ +package commentplugin + +type CommentContent struct { + Body string +} + +func (c CommentContent) Render() string { + +} + +type TitleBasedContent struct{} + +type TextContent struct { + Title string + Body string + Tags []string +} + +func (c TextContent) Render() string { + +} + +type PollContent struct { + Question string + Options []string + Votes []struct { + Address std.Adress + Option string + } +} diff --git a/examples/gno.land/p/demo/boardsv2/post/post.gno b/examples/gno.land/p/demo/boardsv2/post/post.gno index 54be1f50a86..d103579d437 100644 --- a/examples/gno.land/p/demo/boardsv2/post/post.gno +++ b/examples/gno.land/p/demo/boardsv2/post/post.gno @@ -1 +1,90 @@ -package post \ No newline at end of file +package post + +import "time" + +/* +alicePost = &Post { content: "foo" } (0x001) +bobFork := &Post { Origial: alicePost (0x001) } + +//1. Check gc behavior in realm for forks + +--- +alicePost := &(*alicePost) (0x002) +alicePost.content = "new content" + +bobFork := &Post { Origial: uintptr(0x001) } +--- +type Post struct { + ID int + Level int +} + +package reddit + +// explore with plugins +// - boardsv2 +// - pkg/general +// - pkg/reddit +var ( + rating avl.Tree +) + +genericPost := Post{} +reddit.UpvotePost(genericPost.ID) +*/ + +// Blog example +// Home +// - post 1 (content: title, body, author, label, timestamp) +// - post 1.1 (body, author) (thread) +// - post 1.1.1 (comment to a thread but also a new thread) +// - post 1.1.1.1 +// - post 1.2 (thread) +// +// - post 2 +// - post 3 +// +// Reddit example +// Home +// - post 1 (title, body) (board) +// - post 1.1 (title, body) (sub-board) +// - post 1.1.1 (title, body, label) +// - post 1.1.1.1 (comment, thread) +type Post struct { + ID string + Content Content // title, body, label, author, other metadata... + Level int + Base *Post + Children []*Post + Forks []*Post + UpdatedAt time.Time + CreatedAt time.Time // the time when created by user or forked. + Creator std.Address +} + +// create plugins for Post type < +// upvoting < implement first plugin +// define public API for plugin, post packages and boardsv2 +// moderation +// +// plugin ideas: +// - visibility +// - upcoting +// - acess control > you shouldn't be able to answer to the boards yo're not invited +// - moedaration (ban certain posts -this could be through a dao in the future) + +func New(s Storage) Post { + +} + +func Create(c Content) *Post { + +} + +func (p *Post) NextIncrementalKey(base string) string { + +} + +// func (p *Post) Append() error { +// +// } diff --git a/examples/gno.land/p/demo/boardsv2/post/storage.gno b/examples/gno.land/p/demo/boardsv2/post/storage.gno new file mode 100644 index 00000000000..49a5f7eef32 --- /dev/null +++ b/examples/gno.land/p/demo/boardsv2/post/storage.gno @@ -0,0 +1,4 @@ +package post + +type Storage interface { +} diff --git a/examples/gno.land/p/demo/boardsv2/post/view.gno b/examples/gno.land/p/demo/boardsv2/post/view.gno index 54be1f50a86..235520f8734 100644 --- a/examples/gno.land/p/demo/boardsv2/post/view.gno +++ b/examples/gno.land/p/demo/boardsv2/post/view.gno @@ -1 +1 @@ -package post \ No newline at end of file +package post diff --git a/examples/gno.land/r/demo/boardsv2/boardsv2.gno b/examples/gno.land/r/demo/boardsv2/boardsv2.gno index 4ad3d466272..4205c210da5 100644 --- a/examples/gno.land/r/demo/boardsv2/boardsv2.gno +++ b/examples/gno.land/r/demo/boardsv2/boardsv2.gno @@ -1 +1,44 @@ -package boardsv2 \ No newline at end of file +package boardsv2 + +import "gno.land/p/demo/avl" + +// TODO: This goes in the realm +// type Boards struct { +// // TODO: Define how do we want to display and sort boards and posts (upvotes, pinned, ...) +// boards avl.Tree +// Title string +// Description string +// } + +func Render(path string) string { + // TODO: Implement render + return "" +} + +// TODO: Define public API + +func CreateBoard() {} // Maybe +func EditBoard() {} // Maybe +func ForkBoard() {} // Maybe + +func CreatePost() {} +func EditPost() {} +func ForkPost() {} +func DeletePost() {} +func Repost() {} +func Pin() {} +func Invite() {} // Maybe: Could also rely on an allow list +func UpVote() {} +func DownVote() {} + +func Comment() {} // Maybe +func EditComment() {} // Maybe +func DeleteComment() {} // Maybe + +func ToggleCommentsSupport() {} // Maybe +func ToggleThreadsSupport() {} // Maybe +func GetTags() {} // Maybe: List of allowed tags (moderated) + +func AddModerator() {} // Maybe +func RemoveModerator() {} // Maybe +func GetModerators() {} // Maybe From 2e86bbe03aa2f9a12429fa20204be8c9cf00c052 Mon Sep 17 00:00:00 2001 From: jeronimoalbi Date: Fri, 4 Oct 2024 17:23:15 +0200 Subject: [PATCH 02/19] wip: thread and views related brainstorming MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: İlker G. Öztürk Co-authored-by: Denys Sedchenko --- examples/gno.land/p/demo/boardsv2/app.gno | 41 ++++++++++++++++--- examples/gno.land/p/demo/boardsv2/board.gno | 6 ++- examples/gno.land/p/demo/boardsv2/option.gno | 2 +- .../gno.land/p/demo/boardsv2/post/view.gno | 9 ++++ .../p/demo/boardsv2/{post.gno => thread.gno} | 8 ++-- 5 files changed, 54 insertions(+), 12 deletions(-) rename examples/gno.land/p/demo/boardsv2/{post.gno => thread.gno} (69%) diff --git a/examples/gno.land/p/demo/boardsv2/app.gno b/examples/gno.land/p/demo/boardsv2/app.gno index ab2f184e5e8..28017f895c8 100644 --- a/examples/gno.land/p/demo/boardsv2/app.gno +++ b/examples/gno.land/p/demo/boardsv2/app.gno @@ -2,28 +2,57 @@ package boardsv2 import ( "gno.land/demo/p/boardsv2/post" - commentplugin "gno.land/demo/p/boardsv2/post/plugins/content" + contentplugin "gno.land/demo/p/boardsv2/post/plugins/content" ) +// type Rating struct{} +// +// var ratingIndex = avl.Tree{} +// app.AddBoardHook(func (changeType int, change ChangeSet) { +// if changeType == 0 { +// ratingIndex.Set("...", ) +// } +// }) + type App struct { - s Storage + st Storage boards []Board } func New(s Storage, o ...Option) App { a := App{ - s: Storage, + st: Storage, } + // Define the rule for a spesific view. + boardsView := view.New(view.Filter{ + Level: 0, // this will give me the list of the boards. + }) + return a } func (a *App) AddBoard(name, title, description string) (*Board, error) { - p := post.New(commentplugin.TitleBasedContent{ + p := post.New(contentplugin.TitleBasedContent{ Title: title, Description: description, }) - post.Add(a.s, name, p) - return a.GetBoard(name) + + // I want to create a query for listing threads under this new board. + threadView := view.New(view.Filter{ + Level: 1, + SlugPrefix: name, + }) + userActivityView := view.New(view.Filter{ + LevelGte: 2, + By: func(content Content) []View { + c.Author // by account address + } + }) + + if err := post.Add(a.st, name, p); err != nil { + nil, err + } + return a.GetBoard(name), nil } func (a *App) GetBoard(name string) (board *Board, found bool) { diff --git a/examples/gno.land/p/demo/boardsv2/board.gno b/examples/gno.land/p/demo/boardsv2/board.gno index cf5d89ce828..e56895a9b1d 100644 --- a/examples/gno.land/p/demo/boardsv2/board.gno +++ b/examples/gno.land/p/demo/boardsv2/board.gno @@ -7,10 +7,14 @@ func (b *Board) AddPost() error { } -func (b *Board) GetPost(id string) (post *Post, found bool) { +func (b *Board) GetThread(id string) (post *Post, found bool) { } +func (b *Board) ListThreads(id string) (post *Post, found bool) { + threadView.List() // there should be an iterator, pagination +} + func (b *Board) Fork() error { } diff --git a/examples/gno.land/p/demo/boardsv2/option.gno b/examples/gno.land/p/demo/boardsv2/option.gno index 6d45c967dde..1112093c775 100644 --- a/examples/gno.land/p/demo/boardsv2/option.gno +++ b/examples/gno.land/p/demo/boardsv2/option.gno @@ -4,7 +4,7 @@ type Option struct{} // LinearReputationPolicy allows upvoting or downvoting a post by one // for each account. -func LineerReputationPolicy() Option {} +func LinearReputationPolicy() Option {} // TokenBasedReputationPolicy allows upvoting or downvoting a post propotional // to the specified tokens that an account holds. diff --git a/examples/gno.land/p/demo/boardsv2/post/view.gno b/examples/gno.land/p/demo/boardsv2/post/view.gno index 235520f8734..3921e441039 100644 --- a/examples/gno.land/p/demo/boardsv2/post/view.gno +++ b/examples/gno.land/p/demo/boardsv2/post/view.gno @@ -1 +1,10 @@ package post + +// Two cases to solve +// - Give me a list of boards (board list page) +// - Give me a list of comments, created by a user accross all boards (user activity page, of a user) +type View interface { + Name() string + Size() int + Iterate(start, end string, fn func(key string, v interface{}) bool) bool +} diff --git a/examples/gno.land/p/demo/boardsv2/post.gno b/examples/gno.land/p/demo/boardsv2/thread.gno similarity index 69% rename from examples/gno.land/p/demo/boardsv2/post.gno rename to examples/gno.land/p/demo/boardsv2/thread.gno index a15e1df5399..0ecad4ae41d 100644 --- a/examples/gno.land/p/demo/boardsv2/post.gno +++ b/examples/gno.land/p/demo/boardsv2/thread.gno @@ -5,12 +5,12 @@ import ( replyplugin "gno.land/demo/p/boardsv2/post/plugins/content/reply" ) -type Post struct { +type Thread struct { post post.Post st Store } -func (p *Post) Comment(creator std.Address, message string) (id string, err error) { +func (p *Thread) Comment(creator std.Address, message string) (id string, err error) { pp := p.New(replyplugin.MessageContent{ Message: message, }) @@ -21,10 +21,10 @@ func (p *Post) Comment(creator std.Address, message string) (id string, err erro return id, nil } -func (p *Post) Upvote() error { +func (p *Thread) Upvote() error { } -func (p *Post) Downvote() error { +func (p *Thread) Downvote() error { } From ff037e4ad64f6c2611d60c65688cd94f51e147a6 Mon Sep 17 00:00:00 2001 From: jeronimoalbi Date: Sun, 6 Oct 2024 19:52:44 +0200 Subject: [PATCH 03/19] wip: drafting possible patterns and implementation This changes are just one idea in case we want to consider using a single type, like Post or Node for example, for all board types. The proposed ideas here are based on points discussed by the team up to this point of the definition. The idea is to see if we can define a good pattern to implement the boards (boards, posts, comments, ...) using a single type as mentioned by Jae at some point during early discussions. These changes are NOT finished, they are a "showcase" for further exploration and discussion at this point. --- .../p/demo/boardsv2/draft1/boards.gno | 26 ++++++++++++++ .../p/demo/boardsv2/draft1/content_board.gno | 27 ++++++++++++++ .../demo/boardsv2/draft1/content_comment.gno | 27 ++++++++++++++ .../p/demo/boardsv2/draft1/content_poll.gno | 32 +++++++++++++++++ .../p/demo/boardsv2/draft1/content_post.gno | 29 +++++++++++++++ .../gno.land/p/demo/boardsv2/draft1/post.gno | 35 +++++++++++++++++++ 6 files changed, 176 insertions(+) create mode 100644 examples/gno.land/p/demo/boardsv2/draft1/boards.gno create mode 100644 examples/gno.land/p/demo/boardsv2/draft1/content_board.gno create mode 100644 examples/gno.land/p/demo/boardsv2/draft1/content_comment.gno create mode 100644 examples/gno.land/p/demo/boardsv2/draft1/content_poll.gno create mode 100644 examples/gno.land/p/demo/boardsv2/draft1/content_post.gno create mode 100644 examples/gno.land/p/demo/boardsv2/draft1/post.gno diff --git a/examples/gno.land/p/demo/boardsv2/draft1/boards.gno b/examples/gno.land/p/demo/boardsv2/draft1/boards.gno new file mode 100644 index 00000000000..287144a210a --- /dev/null +++ b/examples/gno.land/p/demo/boardsv2/draft1/boards.gno @@ -0,0 +1,26 @@ +package boardsv2 + +// NOTE: Boards type should be a realm type +type Boards struct { + // NOTE: Most of all types could be saved within the same AVL tree, prefixing the level + // NOTE: We provably want different AVL trees (stores) + // NOTE: Again, we could consider using Node instead of Post, and tree instead of posts (semantics) + posts avl.Tree // string(Post.Level + slug) -> *Post (post, comment, poll) +} + +// NOTE: Iterating only Post type is confusing semantically +// NOTE: Potential need cast/switch/type-check when iterating. +func (b Boards) Iterate(slug string, fn func(*Post) bool) bool {} +func (b Boards) IteratePosts(slug string, fn func(*Post) bool) bool {} +func (b Boards) IterateComments(slug string, fn func(*Post) bool) bool {} + +// How to map render paths to actual post instances? +// +// AVL KEYS BY LEVEL PREFIX (start/end) +// Boards => 0_ ... 1_ +// Posts => 1_BOARD/ ... 2_ +// Comments => 2_BOARD/POST/ ... 3_ +// +// HOW TO GUESS PREFIX FROM SLUG +// User enters a SLUG => (one part => 1_BOARD)(more than one part => 1_BOARD/POST) +// How to recognize comments? Should be URL accesible? We could use ":" as separator (not optimal) diff --git a/examples/gno.land/p/demo/boardsv2/draft1/content_board.gno b/examples/gno.land/p/demo/boardsv2/draft1/content_board.gno new file mode 100644 index 00000000000..415d52c46b8 --- /dev/null +++ b/examples/gno.land/p/demo/boardsv2/draft1/content_board.gno @@ -0,0 +1,27 @@ +package boardsv2 + +const ContentTypeBoard = "boards:board" + +var _ Content = (*BoardContent)(nil) + +type BoardContent struct { + Name string +} + +func NewBoard() *Post { + return &Post{ + // ... + Level: LevelBoard, + Content: &BoardContent{ + // ... + }, + } +} + +func (c BoardContent) Type() string { + return ContentTypeBoard +} + +func (c BoardContent) Render() string { + return "" +} diff --git a/examples/gno.land/p/demo/boardsv2/draft1/content_comment.gno b/examples/gno.land/p/demo/boardsv2/draft1/content_comment.gno new file mode 100644 index 00000000000..fcaac13c3e8 --- /dev/null +++ b/examples/gno.land/p/demo/boardsv2/draft1/content_comment.gno @@ -0,0 +1,27 @@ +package boardsv2 + +const ContentTypeComment = "boards:comment" + +var _ Content = (*CommentContent)(nil) + +type CommentContent struct { + Body string +} + +func NewComment() *Post { + return &Post{ + // ... + Level: LevelComment, + Content: &CommentContent{ + // ... + }, + } +} + +func (c CommentContent) Type() string { + return ContentTypeComment +} + +func (c CommentContent) Render() string { + return "" +} diff --git a/examples/gno.land/p/demo/boardsv2/draft1/content_poll.gno b/examples/gno.land/p/demo/boardsv2/draft1/content_poll.gno new file mode 100644 index 00000000000..9bc10e97617 --- /dev/null +++ b/examples/gno.land/p/demo/boardsv2/draft1/content_poll.gno @@ -0,0 +1,32 @@ +package boardsv2 + +const ContentTypePoll = "boards:poll" + +var _ Content = (*PollContent)(nil) + +type PollContent struct { + Question string + Options []string + Votes []struct { + Address std.Adress + Option string + } +} + +func NewPoll() *Post { + return &Post{ + // ... + Level: LevelPost, + Content: &PollContent{ + // ... + }, + } +} + +func (c PollContent) Type() string { + return ContentTypePoll +} + +func (c PollContent) Render() string { + return "" +} diff --git a/examples/gno.land/p/demo/boardsv2/draft1/content_post.gno b/examples/gno.land/p/demo/boardsv2/draft1/content_post.gno new file mode 100644 index 00000000000..289b5ba0099 --- /dev/null +++ b/examples/gno.land/p/demo/boardsv2/draft1/content_post.gno @@ -0,0 +1,29 @@ +package boardsv2 + +const ContentTypePost = "boards:post" + +var _ Content = (*TextContent)(nil) + +type TextContent struct { + Title string + Body string + Tags []string +} + +func NewPost() *Post { + return &Post{ + // ... + Level: LevelPost, + Content: &TextContent{ + // ... + }, + } +} + +func (c TextContent) Type() string { + return ContentTypePost +} + +func (c TextContent) Render() string { + return "" +} diff --git a/examples/gno.land/p/demo/boardsv2/draft1/post.gno b/examples/gno.land/p/demo/boardsv2/draft1/post.gno new file mode 100644 index 00000000000..70e9ed8b71e --- /dev/null +++ b/examples/gno.land/p/demo/boardsv2/draft1/post.gno @@ -0,0 +1,35 @@ +package boardsv2 + +import ( + "strconv" +) + +const ( + LevelBoard = iota + LevelPost + LevelComment +) + +type ( + Content interface { + Type() string + Render() string + } + + // TODO: Still have to consider how to add custom features like up/down voting, reputation, fork, lock, ... + Post struct { + ID string + Content Content // NOTE: Maybe should be Type (board as content is odd) + Level int + Base *Post + Children []*Post + Forks []*Post + UpdatedAt time.Time + CreatedAt time.Time + Creator std.Address + } +) + +func (p Post) NextIncrementalKey(baseKey string) string { + return baseKey + "/" + strconv.Itoa(len(p.Children)) +} From ccf0816263ba5b587d7e0a2464451d70818b509b Mon Sep 17 00:00:00 2001 From: jeronimoalbi Date: Mon, 7 Oct 2024 11:37:41 +0200 Subject: [PATCH 04/19] wip: exploring how to deal with different post type features Maybe by using an storage abstraction and functions we could deal with specific features like up/down voting, creationg of different post types, and so on. This would keep the Post type simple and features would be handled by the different storage abstractions. --- .../p/demo/boardsv2/draft1/boards.gno | 39 +++++++++++++-- .../p/demo/boardsv2/draft1/features.gno | 50 +++++++++++++++++++ .../gno.land/p/demo/boardsv2/draft1/post.gno | 12 ++++- .../gno.land/p/demo/boardsv2/draft1/store.gno | 7 +++ 4 files changed, 102 insertions(+), 6 deletions(-) create mode 100644 examples/gno.land/p/demo/boardsv2/draft1/features.gno create mode 100644 examples/gno.land/p/demo/boardsv2/draft1/store.gno diff --git a/examples/gno.land/p/demo/boardsv2/draft1/boards.gno b/examples/gno.land/p/demo/boardsv2/draft1/boards.gno index 287144a210a..78f3c47155a 100644 --- a/examples/gno.land/p/demo/boardsv2/draft1/boards.gno +++ b/examples/gno.land/p/demo/boardsv2/draft1/boards.gno @@ -1,18 +1,24 @@ package boardsv2 +import ( + "errors" + "strconv" +) + // NOTE: Boards type should be a realm type type Boards struct { // NOTE: Most of all types could be saved within the same AVL tree, prefixing the level - // NOTE: We provably want different AVL trees (stores) + // NOTE: We might want different AVL trees to avoid using level prefixes + // NOTE: We could introduce the stores concept for the posts type instead // NOTE: Again, we could consider using Node instead of Post, and tree instead of posts (semantics) posts avl.Tree // string(Post.Level + slug) -> *Post (post, comment, poll) } // NOTE: Iterating only Post type is confusing semantically // NOTE: Potential need cast/switch/type-check when iterating. -func (b Boards) Iterate(slug string, fn func(*Post) bool) bool {} -func (b Boards) IteratePosts(slug string, fn func(*Post) bool) bool {} -func (b Boards) IterateComments(slug string, fn func(*Post) bool) bool {} +func (b Boards) Iterate(path string, fn func(*Post) bool) bool {} +func (b Boards) IteratePosts(path string, fn func(*Post) bool) bool {} +func (b Boards) IterateComments(path string, fn func(*Post) bool) bool {} // How to map render paths to actual post instances? // @@ -24,3 +30,28 @@ func (b Boards) IterateComments(slug string, fn func(*Post) bool) bool {} // HOW TO GUESS PREFIX FROM SLUG // User enters a SLUG => (one part => 1_BOARD)(more than one part => 1_BOARD/POST) // How to recognize comments? Should be URL accesible? We could use ":" as separator (not optimal) + +func (b *Boards) Set(p *Post) (updated bool) { + key := newKey(p.Level, p.Slug()) + return b.posts.Set(key, p) +} + +func (b *Boards) Remove(level int, path string) (_ *Post, removed bool) { + key := newKey(level, path) + if v, removed := b.posts.Remove(key); removed { + return v.(*Post), true + } + return nil, false +} + +func (b Boards) Get(level int, path string) (_ *Post, found bool) { + key := newKey(level, path) + if v, found := b.posts.Get(key); found { + return v.(*Post), true + } + return "", false +} + +func newKey(level int, path string) string { + return strconv.Itoa(level) + "_" + path +} diff --git a/examples/gno.land/p/demo/boardsv2/draft1/features.gno b/examples/gno.land/p/demo/boardsv2/draft1/features.gno new file mode 100644 index 00000000000..0ebcf632e3e --- /dev/null +++ b/examples/gno.land/p/demo/boardsv2/draft1/features.gno @@ -0,0 +1,50 @@ +package boardsv2 + +func AddBoard(s PostStore, slug string /* ... */) (path string, _ error) { + // TODO: Finish implementation + + return slug, nil +} + +// NOTE: Define a pattern to add functionality to posts by type (AddComment, AddThread, AddPoll, Repost, Upvote, ...) +// NOTE: Maybe though functions that assert the right arguments +func AddComment(s PostStore, parentPath string, creator std.Address, message string) (path string, _ error) { + // Try to get parent as a post or a comment, otherwise parent doesn't support comments + p, found := b.Get(LevelPost, parentPath) + if !found { + p, found = b.Get(LevelComment, parentPath) + if !found { + return "", errors.New("parent post or comment not found: " + parentPath) + } + } + + comment := NewComment(p /* ... */) + + // TODO: Finish implementation + + path = parentPath + "/" + comment.ID + return path, nil +} + +// NOTE: Arguments could potentially be many, consider variadic + sane defaults (?) +func AddThread(s PostStore, parentPath, slug string, creator std.Address /* ... */) (path string, _ error) { + p, found := b.Get(LevelPost, parentPath) + if !found { + return "", errors.New("parent post not found: " + parentPath) + } + + post := NewPost(p, slug /* ... */) + + // TODO: Finish implementation + + path = parentPath + "/" + post.ID + return path, nil +} + +// ----- Other features ----- +// type VotesStore interface { +// /*...*/ +// } +// +// func Upvote(s VotesStore /* ... */) {} +// func DownVote(s VotesStore /* ... */) {} diff --git a/examples/gno.land/p/demo/boardsv2/draft1/post.gno b/examples/gno.land/p/demo/boardsv2/draft1/post.gno index 70e9ed8b71e..38783c34a9c 100644 --- a/examples/gno.land/p/demo/boardsv2/draft1/post.gno +++ b/examples/gno.land/p/demo/boardsv2/draft1/post.gno @@ -18,8 +18,9 @@ type ( // TODO: Still have to consider how to add custom features like up/down voting, reputation, fork, lock, ... Post struct { - ID string - Content Content // NOTE: Maybe should be Type (board as content is odd) + ID string // NOTE: It would be nice to use a type alias for the ID field type + Content Content // NOTE: Maybe should be called Type (board as content is odd) + Parent *Post // NOTE: If all is a post we need to have a parent Level int Base *Post Children []*Post @@ -30,6 +31,13 @@ type ( } ) +func (p Post) Slug() string { // NOTE: Not optimal, calculate slug dynamically + if p.Parent == nil { + return p.ID + } + return p.Parent.Slug() + "/" + p.ID +} + func (p Post) NextIncrementalKey(baseKey string) string { return baseKey + "/" + strconv.Itoa(len(p.Children)) } diff --git a/examples/gno.land/p/demo/boardsv2/draft1/store.gno b/examples/gno.land/p/demo/boardsv2/draft1/store.gno new file mode 100644 index 00000000000..07000e3c59f --- /dev/null +++ b/examples/gno.land/p/demo/boardsv2/draft1/store.gno @@ -0,0 +1,7 @@ +package boardsv2 + +// NOTE: Maybe we could abstract the location where posts are stored +type PostStore interface { + Set(*Post) (updated bool) + Get(level int, path string) (_ *Post, found bool) // NOTE: Level could be a type alias for better semantics +} From b7b4ea0ea941d1e752fea3737bf40a2693e8d4ab Mon Sep 17 00:00:00 2001 From: jeronimoalbi Date: Mon, 7 Oct 2024 17:21:56 +0200 Subject: [PATCH 05/19] wip: defining pluggable architecture for custom features MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: İlker G. Öztürk --- .../p/demo/boardsv2/draft1/boards.gno | 32 ++++++++++------ .../p/demo/boardsv2/draft1/content_board.gno | 4 +- .../p/demo/boardsv2/draft1/content_poll.gno | 2 +- .../p/demo/boardsv2/draft1/features.gno | 23 ++++++++++- .../p/demo/boardsv2/draft1/plugin_locking.gno | 19 ++++++++++ .../boardsv2/draft1/plugin_reputation.gno | 38 +++++++++++++++++++ .../gno.land/p/demo/boardsv2/draft1/post.gno | 34 ++++++++--------- .../gno.land/p/demo/boardsv2/draft1/store.gno | 2 + examples/gno.land/p/demo/boardsv2/option.gno | 2 + 9 files changed, 122 insertions(+), 34 deletions(-) create mode 100644 examples/gno.land/p/demo/boardsv2/draft1/plugin_locking.gno create mode 100644 examples/gno.land/p/demo/boardsv2/draft1/plugin_reputation.gno diff --git a/examples/gno.land/p/demo/boardsv2/draft1/boards.gno b/examples/gno.land/p/demo/boardsv2/draft1/boards.gno index 78f3c47155a..87fbc26b01f 100644 --- a/examples/gno.land/p/demo/boardsv2/draft1/boards.gno +++ b/examples/gno.land/p/demo/boardsv2/draft1/boards.gno @@ -1,24 +1,28 @@ package boardsv2 import ( - "errors" "strconv" ) -// NOTE: Boards type should be a realm type +// TODO: Locking +// - Locking a board means, you can not create new threads, and you can not comment in existing ones +// - Locking a thread means, you can not comment in this thread anymore + +// TODO: Move boards (or App) to `boardsv2` type Boards struct { - // NOTE: Most of all types could be saved within the same AVL tree, prefixing the level // NOTE: We might want different AVL trees to avoid using level prefixes - // NOTE: We could introduce the stores concept for the posts type instead - // NOTE: Again, we could consider using Node instead of Post, and tree instead of posts (semantics) - posts avl.Tree // string(Post.Level + slug) -> *Post (post, comment, poll) + posts avl.Tree // string(Post.Level + Post.CreatedAt + slug) -> *Post (post, comment, poll) + lockingPlugin lockingplugin.Pugin } -// NOTE: Iterating only Post type is confusing semantically -// NOTE: Potential need cast/switch/type-check when iterating. -func (b Boards) Iterate(path string, fn func(*Post) bool) bool {} -func (b Boards) IteratePosts(path string, fn func(*Post) bool) bool {} -func (b Boards) IterateComments(path string, fn func(*Post) bool) bool {} +// TODO: Support pagination Start/End (see pager implementation) +func (b Boards) Iterate(level int, path string, fn func(*Post) bool) bool {} +func (b Boards) ReverseIterate(level int, path string, fn func(*Post) bool) bool {} + +func (b *Boards) Lock(path string) { + post := b.Get(LevelBoard, path) // Otherwise we try LevelPost + b.lockingPlugin.Lock(post) +} // How to map render paths to actual post instances? // @@ -30,6 +34,11 @@ func (b Boards) IterateComments(path string, fn func(*Post) bool) bool {} // HOW TO GUESS PREFIX FROM SLUG // User enters a SLUG => (one part => 1_BOARD)(more than one part => 1_BOARD/POST) // How to recognize comments? Should be URL accesible? We could use ":" as separator (not optimal) +// +// LEVEL_BOARD/POST/POST-2/COMMENT/COMMENT-2 (deprecated) +// LEVEL_TIMESTAMP_BOARD/POST/COMMENT +// +// :board/post/comment func (b *Boards) Set(p *Post) (updated bool) { key := newKey(p.Level, p.Slug()) @@ -53,5 +62,6 @@ func (b Boards) Get(level int, path string) (_ *Post, found bool) { } func newKey(level int, path string) string { + // TODO: Add timestamp to key return strconv.Itoa(level) + "_" + path } diff --git a/examples/gno.land/p/demo/boardsv2/draft1/content_board.gno b/examples/gno.land/p/demo/boardsv2/draft1/content_board.gno index 415d52c46b8..ec38c599578 100644 --- a/examples/gno.land/p/demo/boardsv2/draft1/content_board.gno +++ b/examples/gno.land/p/demo/boardsv2/draft1/content_board.gno @@ -1,5 +1,7 @@ package boardsv2 +// TODO: Move content types to `boardsv2` API + const ContentTypeBoard = "boards:board" var _ Content = (*BoardContent)(nil) @@ -9,7 +11,7 @@ type BoardContent struct { } func NewBoard() *Post { - return &Post{ + return &Post{ // TODO: Use a contructor to be able to use private fields (use options), NewPost // ... Level: LevelBoard, Content: &BoardContent{ diff --git a/examples/gno.land/p/demo/boardsv2/draft1/content_poll.gno b/examples/gno.land/p/demo/boardsv2/draft1/content_poll.gno index 9bc10e97617..3dc508e8e92 100644 --- a/examples/gno.land/p/demo/boardsv2/draft1/content_poll.gno +++ b/examples/gno.land/p/demo/boardsv2/draft1/content_poll.gno @@ -13,7 +13,7 @@ type PollContent struct { } } -func NewPoll() *Post { +func NewPoll( /* ... */ ) *Post { return &Post{ // ... Level: LevelPost, diff --git a/examples/gno.land/p/demo/boardsv2/draft1/features.gno b/examples/gno.land/p/demo/boardsv2/draft1/features.gno index 0ebcf632e3e..c5dff81eee4 100644 --- a/examples/gno.land/p/demo/boardsv2/draft1/features.gno +++ b/examples/gno.land/p/demo/boardsv2/draft1/features.gno @@ -1,5 +1,7 @@ package boardsv2 +import "errors" + func AddBoard(s PostStore, slug string /* ... */) (path string, _ error) { // TODO: Finish implementation @@ -10,17 +12,34 @@ func AddBoard(s PostStore, slug string /* ... */) (path string, _ error) { // NOTE: Maybe though functions that assert the right arguments func AddComment(s PostStore, parentPath string, creator std.Address, message string) (path string, _ error) { // Try to get parent as a post or a comment, otherwise parent doesn't support comments - p, found := b.Get(LevelPost, parentPath) + p, found := s.Get(LevelPost, parentPath) if !found { - p, found = b.Get(LevelComment, parentPath) + p, found = s.Get(LevelComment, parentPath) if !found { return "", errors.New("parent post or comment not found: " + parentPath) } } + // TODO: + // Call the IsLocked function from the plugin for both the board post and thread post + // of this new comment. And confirm that both of them are false + // if so, then proceed, otherwise can not add new comments because locked. + // level 0 - boards + // level 1 - thread + // level 2 - comment + // level 3 - comment under comment + // level 4 - comment under comment under comment + // ... + + // TODO: + // Consider using reverse iteration while checking IsLocked in parent levels. + // If the keys in the AVL tree has levels as the prefix it should be optimized. If + // timestamp is used it may not be. + comment := NewComment(p /* ... */) // TODO: Finish implementation + s.Set( /* ... */ ) path = parentPath + "/" + comment.ID return path, nil diff --git a/examples/gno.land/p/demo/boardsv2/draft1/plugin_locking.gno b/examples/gno.land/p/demo/boardsv2/draft1/plugin_locking.gno new file mode 100644 index 00000000000..b390284fbcf --- /dev/null +++ b/examples/gno.land/p/demo/boardsv2/draft1/plugin_locking.gno @@ -0,0 +1,19 @@ +package boardsv2 + +func NewLockingPlugin( /* options */ ) LockingPlugin { +} + +type LockingStorage struct { + IsLocked bool +} + +type LockingPlugin struct { +} + +func (p *LockingPlugin) Lock(p *Post) { + // TODO: Check local storare to see if it's lock otherwise change storage + p.GetPluginStore(p.Name()).(LockingStorage).IsLocked = true + // TODO: We need to update (Save) the post everytime there is a change. +} +func (p *LockingPlugin) Unlock(p *Post) {} +func (p LockingPlugin) IsLocked(p *Post) bool {} diff --git a/examples/gno.land/p/demo/boardsv2/draft1/plugin_reputation.gno b/examples/gno.land/p/demo/boardsv2/draft1/plugin_reputation.gno new file mode 100644 index 00000000000..aba2faeebfd --- /dev/null +++ b/examples/gno.land/p/demo/boardsv2/draft1/plugin_reputation.gno @@ -0,0 +1,38 @@ +package boardsv2 + +// TODO: Move to a plugins folder +// TODO: Plugins can also have global stores (plugins can be local to a post or global) + +func NewReputationPlugin( /* options */ ) ReputationPlugin { + +} + +type ReputationStorage struct { + Upvotes uint + Downbotes uint + ListOfWhoVotedWhat avl.Tree // string(std.Address) -> ?? (TODO: define) +} + +type ReputationPlugin struct { + AllowedLevels []int +} + +func (p ReputationPlugin) Name() string { + return "reputation_plugin" +} + +func (p *ReputationPlugin) Votes(p *Post) uint32 { +} + +func (p *ReputationPlugin) Upvote(p *Post) { + // TODO: Assert that post has the allowed level + st := p.GetPluginStore(p.Name()).(*Storage) + + // TODO: modify global state + // TODO: modify local state +} + +func (p *ReputationPlugin) Downvote(p *Post) { + // TODO: Assert that post has the allowed level + +} diff --git a/examples/gno.land/p/demo/boardsv2/draft1/post.gno b/examples/gno.land/p/demo/boardsv2/draft1/post.gno index 38783c34a9c..78b617a78f3 100644 --- a/examples/gno.land/p/demo/boardsv2/draft1/post.gno +++ b/examples/gno.land/p/demo/boardsv2/draft1/post.gno @@ -2,6 +2,7 @@ package boardsv2 import ( "strconv" + "time" ) const ( @@ -12,32 +13,27 @@ const ( type ( Content interface { - Type() string + Type() string // NOTE: The idea is to avoid casting Render() string } - // TODO: Still have to consider how to add custom features like up/down voting, reputation, fork, lock, ... + // TODO: ID is the full path to the post + Post struct { - ID string // NOTE: It would be nice to use a type alias for the ID field type - Content Content // NOTE: Maybe should be called Type (board as content is odd) - Parent *Post // NOTE: If all is a post we need to have a parent - Level int - Base *Post - Children []*Post - Forks []*Post - UpdatedAt time.Time - CreatedAt time.Time - Creator std.Address + ID string // NOTE: It would be nice to use a type alias for the ID field type + Content Content // NOTE: Maybe should be called Type (board as content is odd) + PluginContent avl.Tree // string(plugin name) -> interface{} + Parent *Post // NOTE: If all is a post we need to have a parent + Level int + Base *Post + Children []*Post + Forks []*Post + UpdatedAt time.Time + CreatedAt time.Time + Creator std.Address } ) -func (p Post) Slug() string { // NOTE: Not optimal, calculate slug dynamically - if p.Parent == nil { - return p.ID - } - return p.Parent.Slug() + "/" + p.ID -} - func (p Post) NextIncrementalKey(baseKey string) string { return baseKey + "/" + strconv.Itoa(len(p.Children)) } diff --git a/examples/gno.land/p/demo/boardsv2/draft1/store.gno b/examples/gno.land/p/demo/boardsv2/draft1/store.gno index 07000e3c59f..f8c98f3e724 100644 --- a/examples/gno.land/p/demo/boardsv2/draft1/store.gno +++ b/examples/gno.land/p/demo/boardsv2/draft1/store.gno @@ -4,4 +4,6 @@ package boardsv2 type PostStore interface { Set(*Post) (updated bool) Get(level int, path string) (_ *Post, found bool) // NOTE: Level could be a type alias for better semantics + + // TODO: Add iterator (or define PostIterator interface) } diff --git a/examples/gno.land/p/demo/boardsv2/option.gno b/examples/gno.land/p/demo/boardsv2/option.gno index 1112093c775..b8d7b65c643 100644 --- a/examples/gno.land/p/demo/boardsv2/option.gno +++ b/examples/gno.land/p/demo/boardsv2/option.gno @@ -9,3 +9,5 @@ func LinearReputationPolicy() Option {} // TokenBasedReputationPolicy allows upvoting or downvoting a post propotional // to the specified tokens that an account holds. func TokenBasedReputationPolicy() Option {} + +// TODO: make it configurable how many levels allowed From 27a9be5d5be0e41ca88d9340559555bbc8d7ae7d Mon Sep 17 00:00:00 2001 From: jeronimoalbi Date: Mon, 7 Oct 2024 17:26:29 +0200 Subject: [PATCH 06/19] chore: add missing tags fields MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: İlker G. Öztürk --- examples/gno.land/p/demo/boardsv2/draft1/content_board.gno | 1 + examples/gno.land/p/demo/boardsv2/draft1/content_poll.gno | 1 + 2 files changed, 2 insertions(+) diff --git a/examples/gno.land/p/demo/boardsv2/draft1/content_board.gno b/examples/gno.land/p/demo/boardsv2/draft1/content_board.gno index ec38c599578..e59af9bc4cf 100644 --- a/examples/gno.land/p/demo/boardsv2/draft1/content_board.gno +++ b/examples/gno.land/p/demo/boardsv2/draft1/content_board.gno @@ -8,6 +8,7 @@ var _ Content = (*BoardContent)(nil) type BoardContent struct { Name string + Tags []string } func NewBoard() *Post { diff --git a/examples/gno.land/p/demo/boardsv2/draft1/content_poll.gno b/examples/gno.land/p/demo/boardsv2/draft1/content_poll.gno index 3dc508e8e92..1f3a0bc9846 100644 --- a/examples/gno.land/p/demo/boardsv2/draft1/content_poll.gno +++ b/examples/gno.land/p/demo/boardsv2/draft1/content_poll.gno @@ -11,6 +11,7 @@ type PollContent struct { Address std.Adress Option string } + Tags []string } func NewPoll( /* ... */ ) *Post { From 0b81834e82c22d623923625230654da76eae0ca9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=B0lker=20G=2E=20=C3=96zt=C3=BCrk?= Date: Tue, 8 Oct 2024 01:01:40 +0300 Subject: [PATCH 07/19] wip: experiment draft2 --- .../gno.land/p/demo/boardsv2/draf2/boards.gno | 135 ++++++++++++++++++ .../p/demo/boardsv2/draf2/body_board.gno | 0 .../p/demo/boardsv2/draf2/body_comment.gno | 0 .../p/demo/boardsv2/draf2/body_thread.gno | 0 .../p/demo/boardsv2/draf2/comments.gno | 0 .../gno.land/p/demo/boardsv2/draf2/option.gno | 19 +++ .../p/demo/boardsv2/draf2/post/cursor.gno | 8 ++ .../draf2/post/plugin/comment/comment.gno | 33 +++++ .../boardsv2/draf2/post/plugin/plugin.gno | 0 .../boardsv2/draf2/post/plugin/text/text.gno | 3 + .../draf2/post/plugin/title/title.gno | 24 ++++ .../p/demo/boardsv2/draf2/post/post.gno | 31 ++++ .../demo/boardsv2/draf2/post/store/store.gno | 0 .../p/demo/boardsv2/draf2/threads.gno | 0 14 files changed, 253 insertions(+) create mode 100644 examples/gno.land/p/demo/boardsv2/draf2/boards.gno create mode 100644 examples/gno.land/p/demo/boardsv2/draf2/body_board.gno create mode 100644 examples/gno.land/p/demo/boardsv2/draf2/body_comment.gno create mode 100644 examples/gno.land/p/demo/boardsv2/draf2/body_thread.gno create mode 100644 examples/gno.land/p/demo/boardsv2/draf2/comments.gno create mode 100644 examples/gno.land/p/demo/boardsv2/draf2/option.gno create mode 100644 examples/gno.land/p/demo/boardsv2/draf2/post/cursor.gno create mode 100644 examples/gno.land/p/demo/boardsv2/draf2/post/plugin/comment/comment.gno create mode 100644 examples/gno.land/p/demo/boardsv2/draf2/post/plugin/plugin.gno create mode 100644 examples/gno.land/p/demo/boardsv2/draf2/post/plugin/text/text.gno create mode 100644 examples/gno.land/p/demo/boardsv2/draf2/post/plugin/title/title.gno create mode 100644 examples/gno.land/p/demo/boardsv2/draf2/post/post.gno create mode 100644 examples/gno.land/p/demo/boardsv2/draf2/post/store/store.gno create mode 100644 examples/gno.land/p/demo/boardsv2/draf2/threads.gno diff --git a/examples/gno.land/p/demo/boardsv2/draf2/boards.gno b/examples/gno.land/p/demo/boardsv2/draf2/boards.gno new file mode 100644 index 00000000000..ad8f0937ed6 --- /dev/null +++ b/examples/gno.land/p/demo/boardsv2/draf2/boards.gno @@ -0,0 +1,135 @@ +package boards + +const ( + LevelBoard = iota + LevelThread + LevelComment +) + +type App struct { + c Context +} + + +func New(st post.Storage) App { + a := App{ + st: st, + plugs: make(map[post.PluginName]plugin.Plugin), + } + + a.plugs[pluginbody.Name] = pluginbody.New(st) // load global state from st + + return a +} + +type Board struct { + post.Post + c Context +} + +func (b Board) Content() BoardContent { + return b.c.Plugin(pluginbasiccontent.Name).Content(b.Post) +} + +func (b Board) Render() string { + +} + +type Thread struct { + post.Post + c Context +} + +func (t Thread) TextContent() ThreadTextContent { + +} + +func (t Thread) PollContent() ThreadPollContent {} +func (t Thread) Type() ContentType {} + +// Comments returns a list of comments sent to the thread. +// The comment slice will be non-nil only when Thread is initiated +// through ThreadWithComments. +func (t Thread) Comments() []Comment {} + +type Comment struct { + post.Post + c Context +} + +func (c Comment) Content() CommentContent {} + +func (a App) Board(path string) ([]Board, error) { + a.c.Get(level, path func(){}) +} +func (a App) LockBoard(path string) (error) {} +func (a App) ForkBoard(path string) (error) {} + +func (a App) Boards(c post.Cursor) ([]Board, error) {} +func (a App) CreateBoard(c BoardContent) (Board, error) {} + +func (b App) Thread(path string) (Thread, error) { + return ThreadWithComments(path, nil) +} +func (a App) LockThread(path string) (error) {} +func (a App) ForkThread(path string) {} + +// ThreadWithComments returns a thread with its comments with the comment depth +// configured with commentDepth for direct and child comments. +// For ex. +// To get a thread with only 10 direct (parent level) comments use: +// - []int{10} +// To get a thread with 10 direct comments and 3 of their child comments use: +// - []int{10, 3} +// You can define configure this for more levels until you reach to value defined +// by MaxCommentDepth. +// By default the configuration is as follows: +// - []int{20, 3} +func (b App) ThreadWithComments(path string, commentDepth []int) (Thread, error) {} +func (b App) Threads(c post.Cursor) ([]Thread, error) {} +func (b App) CreateTextThread(c ThreadTextContent) (Thread, error) {} +func (b App) CreatePollThread(c ThreadPollContent) (Thread, error) {} + +// parentPath could be a path to thread (root), or path to any of the +// nested comments. +func (b App) Comments(parentPath string, c Cursor) ([]Comment, error) {} +func (b App) CreateComment(path string, c plugincomment.Content) (Comment, error) { + post, err := a.c.Plugin(plugincomment.Name).NewPost(c, LevelComment) + if err != nil { + return Comment{}, err + } + return Comment{Post: post, c: a.c} +} + +func (a App) Render(path string) string {} + +type Context struct { + opts []Option + st post.Storage + plugs map[post.PluginName]plugin.Plugin +} + +func (c Context) Plugin(n post.PluginName) post.Plugin { + +} + +func (c Context) Set(p *Post) (updated bool) { + key := newKey(p.Level, p.Slug()) + return b.posts.Set(key, p) +} + +func (c Context) Remove(level int, path string) (_ *Post, removed bool) { + key := newKey(level, path) + if v, removed := b.posts.Remove(key); removed { + return v.(*Post), true + } + return nil, false +} + +func (c Context) Get(level int, path string, iterator func()) (_ *Post, found bool) { + key := newKey(level, path) + if v, found := b.posts.Get(key); found { + return v.(*Post), true + } + return "", false +} diff --git a/examples/gno.land/p/demo/boardsv2/draf2/body_board.gno b/examples/gno.land/p/demo/boardsv2/draf2/body_board.gno new file mode 100644 index 00000000000..e69de29bb2d diff --git a/examples/gno.land/p/demo/boardsv2/draf2/body_comment.gno b/examples/gno.land/p/demo/boardsv2/draf2/body_comment.gno new file mode 100644 index 00000000000..e69de29bb2d diff --git a/examples/gno.land/p/demo/boardsv2/draf2/body_thread.gno b/examples/gno.land/p/demo/boardsv2/draf2/body_thread.gno new file mode 100644 index 00000000000..e69de29bb2d diff --git a/examples/gno.land/p/demo/boardsv2/draf2/comments.gno b/examples/gno.land/p/demo/boardsv2/draf2/comments.gno new file mode 100644 index 00000000000..e69de29bb2d diff --git a/examples/gno.land/p/demo/boardsv2/draf2/option.gno b/examples/gno.land/p/demo/boardsv2/draf2/option.gno new file mode 100644 index 00000000000..48ab0cc1bff --- /dev/null +++ b/examples/gno.land/p/demo/boardsv2/draf2/option.gno @@ -0,0 +1,19 @@ +package boards + +type Option struct{} + +// LinearReputationPolicy allows upvoting or downvoting a post by one +// for each account. +func LinearReputationPolicy() Option {} + +// TokenBasedReputationPolicy allows upvoting or downvoting a post propotional +// to the specified tokens that an account holds. +func TokenBasedReputationPolicy() Option {} + +// MaxPostDepth configures the max depth for nested comments. +// 0 -> boards +// 1 -> threads +// 2 -> comments-1 (direct comments to the threads) +// The above are already reserved. +// Setting it to zero will disable comments. +func MaxCommentDepth(d int) Option {} diff --git a/examples/gno.land/p/demo/boardsv2/draf2/post/cursor.gno b/examples/gno.land/p/demo/boardsv2/draf2/post/cursor.gno new file mode 100644 index 00000000000..44f8ebe85f6 --- /dev/null +++ b/examples/gno.land/p/demo/boardsv2/draf2/post/cursor.gno @@ -0,0 +1,8 @@ +package post + +type Cursor struct { + FromID string + Count int +} + +func NewCursor(fromID string, count int) Cursor {} diff --git a/examples/gno.land/p/demo/boardsv2/draf2/post/plugin/comment/comment.gno b/examples/gno.land/p/demo/boardsv2/draf2/post/plugin/comment/comment.gno new file mode 100644 index 00000000000..f657df880bc --- /dev/null +++ b/examples/gno.land/p/demo/boardsv2/draf2/post/plugin/comment/comment.gno @@ -0,0 +1,33 @@ +package plugincomment + +const Name = "post-comment" + +func New(st Storage) Plugin { +} + +type Plugin struct { + st Storage +} + +type Content struct { + Title string + Description string + Tags []string +} + +func NewPost(id string, c Content, level int) (*Post, error) { + post := &Post{ + ID: id, + Level: level, + } + return post, p.SetContent(post, c) +} + +func (p Plugin) Content(post *Post) Content { + return post.Body[Name].(Content) +} + +func (p Plugin) SetContent(post *Post, c Content) error { + post.Body[Name] = c + return p.st.Set(post.ID, post) +} diff --git a/examples/gno.land/p/demo/boardsv2/draf2/post/plugin/plugin.gno b/examples/gno.land/p/demo/boardsv2/draf2/post/plugin/plugin.gno new file mode 100644 index 00000000000..e69de29bb2d diff --git a/examples/gno.land/p/demo/boardsv2/draf2/post/plugin/text/text.gno b/examples/gno.land/p/demo/boardsv2/draf2/post/plugin/text/text.gno new file mode 100644 index 00000000000..f0e98e95ced --- /dev/null +++ b/examples/gno.land/p/demo/boardsv2/draf2/post/plugin/text/text.gno @@ -0,0 +1,3 @@ +package contenttext + +const Name = "post-text" diff --git a/examples/gno.land/p/demo/boardsv2/draf2/post/plugin/title/title.gno b/examples/gno.land/p/demo/boardsv2/draf2/post/plugin/title/title.gno new file mode 100644 index 00000000000..5a052076993 --- /dev/null +++ b/examples/gno.land/p/demo/boardsv2/draf2/post/plugin/title/title.gno @@ -0,0 +1,24 @@ +package contenttitle + +const Name = "post-title-only" + +func New(st Storage) Plugin { +} + +type Plugin struct { + st Storage +} + +type Content struct { + Title string + Description string + Tags []string +} + +func (p Plugin) Content(post *Post) Content { + return post.Body[Name].(Content) +} + +func (p Plugin) SetContent(post *Post, c Content) { + post.Body[Name] = c +} diff --git a/examples/gno.land/p/demo/boardsv2/draf2/post/post.gno b/examples/gno.land/p/demo/boardsv2/draf2/post/post.gno new file mode 100644 index 00000000000..a697c15ed27 --- /dev/null +++ b/examples/gno.land/p/demo/boardsv2/draf2/post/post.gno @@ -0,0 +1,31 @@ +package boardsv2 + +import ( + "strconv" + "time" +) + +type ( + Body interface { + Type() string // NOTE: The idea is to avoid casting + } + + // TODO: ID is the full path to the post + + Post struct { + ID string // NOTE: It would be nice to use a type alias for the ID field type + Body Body // NOTE: Maybe should be called Type (board as content is odd) + Parent *Post // NOTE: If all is a post we need to have a parent + Level int + Base *Post + Children []*Post + Forks []*Post + UpdatedAt time.Time + CreatedAt time.Time + Creator std.Address + } +) + +func (p Post) NextIncrementalKey(baseKey string) string { + return baseKey + "/" + strconv.Itoa(len(p.Children)) +} diff --git a/examples/gno.land/p/demo/boardsv2/draf2/post/store/store.gno b/examples/gno.land/p/demo/boardsv2/draf2/post/store/store.gno new file mode 100644 index 00000000000..e69de29bb2d diff --git a/examples/gno.land/p/demo/boardsv2/draf2/threads.gno b/examples/gno.land/p/demo/boardsv2/draf2/threads.gno new file mode 100644 index 00000000000..e69de29bb2d From cc509f938f345cf47a7a03fcafcae8de0bee40d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=B0lker=20G=2E=20=C3=96zt=C3=BCrk?= Date: Tue, 8 Oct 2024 12:21:47 +0300 Subject: [PATCH 08/19] wip: draft2 experiment cont --- examples/gno.land/p/demo/boardsv2/draf2/body_board.gno | 0 examples/gno.land/p/demo/boardsv2/draf2/body_comment.gno | 0 examples/gno.land/p/demo/boardsv2/draf2/body_thread.gno | 0 examples/gno.land/p/demo/boardsv2/draf2/comments.gno | 1 + .../gno.land/p/demo/boardsv2/draf2/post/plugin/lock/lock.gno | 1 + .../gno.land/p/demo/boardsv2/draf2/post/plugin/poll/poll.gno | 1 + .../p/demo/boardsv2/draf2/post/plugin/reputation/reputation.gno | 1 + .../gno.land/p/demo/boardsv2/draf2/post/plugin/text/text.gno | 2 +- .../gno.land/p/demo/boardsv2/draf2/post/plugin/title/title.gno | 2 +- examples/gno.land/p/demo/boardsv2/draf2/post/store/store.gno | 1 + 10 files changed, 7 insertions(+), 2 deletions(-) delete mode 100644 examples/gno.land/p/demo/boardsv2/draf2/body_board.gno delete mode 100644 examples/gno.land/p/demo/boardsv2/draf2/body_comment.gno delete mode 100644 examples/gno.land/p/demo/boardsv2/draf2/body_thread.gno create mode 100644 examples/gno.land/p/demo/boardsv2/draf2/post/plugin/lock/lock.gno create mode 100644 examples/gno.land/p/demo/boardsv2/draf2/post/plugin/poll/poll.gno create mode 100644 examples/gno.land/p/demo/boardsv2/draf2/post/plugin/reputation/reputation.gno diff --git a/examples/gno.land/p/demo/boardsv2/draf2/body_board.gno b/examples/gno.land/p/demo/boardsv2/draf2/body_board.gno deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/examples/gno.land/p/demo/boardsv2/draf2/body_comment.gno b/examples/gno.land/p/demo/boardsv2/draf2/body_comment.gno deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/examples/gno.land/p/demo/boardsv2/draf2/body_thread.gno b/examples/gno.land/p/demo/boardsv2/draf2/body_thread.gno deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/examples/gno.land/p/demo/boardsv2/draf2/comments.gno b/examples/gno.land/p/demo/boardsv2/draf2/comments.gno index e69de29bb2d..b818fc17eb6 100644 --- a/examples/gno.land/p/demo/boardsv2/draf2/comments.gno +++ b/examples/gno.land/p/demo/boardsv2/draf2/comments.gno @@ -0,0 +1 @@ +package boards diff --git a/examples/gno.land/p/demo/boardsv2/draf2/post/plugin/lock/lock.gno b/examples/gno.land/p/demo/boardsv2/draf2/post/plugin/lock/lock.gno new file mode 100644 index 00000000000..5cd488aaabe --- /dev/null +++ b/examples/gno.land/p/demo/boardsv2/draf2/post/plugin/lock/lock.gno @@ -0,0 +1 @@ +package pluginlock diff --git a/examples/gno.land/p/demo/boardsv2/draf2/post/plugin/poll/poll.gno b/examples/gno.land/p/demo/boardsv2/draf2/post/plugin/poll/poll.gno new file mode 100644 index 00000000000..db9c0bb40ce --- /dev/null +++ b/examples/gno.land/p/demo/boardsv2/draf2/post/plugin/poll/poll.gno @@ -0,0 +1 @@ +package pluginpoll diff --git a/examples/gno.land/p/demo/boardsv2/draf2/post/plugin/reputation/reputation.gno b/examples/gno.land/p/demo/boardsv2/draf2/post/plugin/reputation/reputation.gno new file mode 100644 index 00000000000..155fc53850f --- /dev/null +++ b/examples/gno.land/p/demo/boardsv2/draf2/post/plugin/reputation/reputation.gno @@ -0,0 +1 @@ +package pluginreputation diff --git a/examples/gno.land/p/demo/boardsv2/draf2/post/plugin/text/text.gno b/examples/gno.land/p/demo/boardsv2/draf2/post/plugin/text/text.gno index f0e98e95ced..a9f41c39947 100644 --- a/examples/gno.land/p/demo/boardsv2/draf2/post/plugin/text/text.gno +++ b/examples/gno.land/p/demo/boardsv2/draf2/post/plugin/text/text.gno @@ -1,3 +1,3 @@ -package contenttext +package plugintext const Name = "post-text" diff --git a/examples/gno.land/p/demo/boardsv2/draf2/post/plugin/title/title.gno b/examples/gno.land/p/demo/boardsv2/draf2/post/plugin/title/title.gno index 5a052076993..da2f8e905b5 100644 --- a/examples/gno.land/p/demo/boardsv2/draf2/post/plugin/title/title.gno +++ b/examples/gno.land/p/demo/boardsv2/draf2/post/plugin/title/title.gno @@ -1,4 +1,4 @@ -package contenttitle +package plugintitle const Name = "post-title-only" diff --git a/examples/gno.land/p/demo/boardsv2/draf2/post/store/store.gno b/examples/gno.land/p/demo/boardsv2/draf2/post/store/store.gno index e69de29bb2d..72440ea2a61 100644 --- a/examples/gno.land/p/demo/boardsv2/draf2/post/store/store.gno +++ b/examples/gno.land/p/demo/boardsv2/draf2/post/store/store.gno @@ -0,0 +1 @@ +package store From 362e13ed825ad9361e92a0703f4756cc67dd884e Mon Sep 17 00:00:00 2001 From: jeronimoalbi Date: Tue, 8 Oct 2024 11:31:42 +0200 Subject: [PATCH 09/19] wip: reorganize plugins --- .../p/demo/boardsv2/draft1/boards.gno | 8 +- .../draft1/plugin/locking/locking.gno | 48 ++++++++++++ .../draft1/plugin/reputation/options.gno | 15 ++++ .../draft1/plugin/reputation/reputation.gno | 73 +++++++++++++++++++ .../p/demo/boardsv2/draft1/plugin_locking.gno | 19 ----- .../boardsv2/draft1/plugin_reputation.gno | 38 ---------- .../gno.land/p/demo/boardsv2/draft1/post.gno | 20 +++-- 7 files changed, 154 insertions(+), 67 deletions(-) create mode 100644 examples/gno.land/p/demo/boardsv2/draft1/plugin/locking/locking.gno create mode 100644 examples/gno.land/p/demo/boardsv2/draft1/plugin/reputation/options.gno create mode 100644 examples/gno.land/p/demo/boardsv2/draft1/plugin/reputation/reputation.gno delete mode 100644 examples/gno.land/p/demo/boardsv2/draft1/plugin_locking.gno delete mode 100644 examples/gno.land/p/demo/boardsv2/draft1/plugin_reputation.gno diff --git a/examples/gno.land/p/demo/boardsv2/draft1/boards.gno b/examples/gno.land/p/demo/boardsv2/draft1/boards.gno index 87fbc26b01f..f05972c0879 100644 --- a/examples/gno.land/p/demo/boardsv2/draft1/boards.gno +++ b/examples/gno.land/p/demo/boardsv2/draft1/boards.gno @@ -11,8 +11,8 @@ import ( // TODO: Move boards (or App) to `boardsv2` type Boards struct { // NOTE: We might want different AVL trees to avoid using level prefixes - posts avl.Tree // string(Post.Level + Post.CreatedAt + slug) -> *Post (post, comment, poll) - lockingPlugin lockingplugin.Pugin + posts avl.Tree // string(Post.Level + Post.CreatedAt + slug) -> *Post (post, comment, poll) + locking lockingplugin.Plugin } // TODO: Support pagination Start/End (see pager implementation) @@ -21,7 +21,9 @@ func (b Boards) ReverseIterate(level int, path string, fn func(*Post) bool) bool func (b *Boards) Lock(path string) { post := b.Get(LevelBoard, path) // Otherwise we try LevelPost - b.lockingPlugin.Lock(post) + if err := b.locking.Lock(post); err != nil { + panic(err) + } } // How to map render paths to actual post instances? diff --git a/examples/gno.land/p/demo/boardsv2/draft1/plugin/locking/locking.gno b/examples/gno.land/p/demo/boardsv2/draft1/plugin/locking/locking.gno new file mode 100644 index 00000000000..2b6010893a8 --- /dev/null +++ b/examples/gno.land/p/demo/boardsv2/draft1/plugin/locking/locking.gno @@ -0,0 +1,48 @@ +package lockingplugin + +import "errors" + +var ErrInvalidPostType = errors.New("post type is not a board or thread") + +type ( + Plugin struct{} + Storage struct { + IsLocked bool + } +) + +func New() Plugin { + return Plugin{} +} + +func (p Plugin) Name() string { + return "boards:locking" +} + +func (p *Plugin) Lock(p *Post) error { + if !isBoardOrThread(p) { + return ErrInvalidPostType + } + + p.MustGetPluginStorage(p.Name()).(*Storage).IsLocked = true +} + +func (p *Plugin) Unlock(p *Post) error { + if !isBoardOrThread(p) { + return ErrInvalidPostType + } + + p.MustGetPluginStorage(p.Name()).(*Storage).IsLocked = false +} + +func (p Plugin) IsLocked(p *Post) bool { + if !isBoardOrThread(p) { + return ErrInvalidPostType + } + + return p.MustGetPluginStorage(p.Name()).(*Storage).IsLocked +} + +func isBoardOrThread(p *Post) bool { + return p.Level == LevelBoard || p.Level == LevelPost +} diff --git a/examples/gno.land/p/demo/boardsv2/draft1/plugin/reputation/options.gno b/examples/gno.land/p/demo/boardsv2/draft1/plugin/reputation/options.gno new file mode 100644 index 00000000000..83287a53580 --- /dev/null +++ b/examples/gno.land/p/demo/boardsv2/draft1/plugin/reputation/options.gno @@ -0,0 +1,15 @@ +package reputationplugin + +type Option func(*Plugin) + +func UseTokenBasePolicy() Option { + return func(p *Plugin) { + p.Policy = PolicyTokenBase + } +} + +func AllowedPostLevels(levels []int) Option { + return func(p *Plugin) { + p.AllowedPostLevels = levels + } +} diff --git a/examples/gno.land/p/demo/boardsv2/draft1/plugin/reputation/reputation.gno b/examples/gno.land/p/demo/boardsv2/draft1/plugin/reputation/reputation.gno new file mode 100644 index 00000000000..7d8c2351846 --- /dev/null +++ b/examples/gno.land/p/demo/boardsv2/draft1/plugin/reputation/reputation.gno @@ -0,0 +1,73 @@ +package reputationplugin + +const ( + PolicyLinear = iota + PolicyTokenBase +) + +var ErrNotSupported = errors.New("reputation not supported") + +type ( + Plugin struct { + Policy int + AllowedPostLevels []int + } + + Storage struct { + Upvotes uint + Downbotes uint + ListOfWhoVotedWhat avl.Tree // string(std.Address) -> ?? (TODO: define) + } +) + +func NewReputationPlugin(o ...Option) Plugin { + var p Plugin + for _, apply := range o { + apply(&p) + } + return p +} + +func (p Plugin) Name() string { + return "boards:reputation" +} + +func (p Plugin) HasReputationSupport(p *Post) bool { + if len(p.AllowedPostLevels) == 0 { + return true + } + + for _, lvl := range p.AllowedPostLevels { + if p.Level == lvl { + return true + } + } + return false +} + +func (p *Plugin) Votes(p *Post) uint32 { + if !p.HasReputationSupport(p) { + return ErrNotSupported + } + + // TODO: Implement +} + +func (p *Plugin) Upvote(p *Post) error { + if !p.HasReputationSupport(p) { + return ErrNotSupported + } + + // TODO: Modify global state + // TODO: Modify local state + // TODO: Implement + st := p.MustGetPluginStorage(p.Name()).(*Storage) +} + +func (p *Plugin) Downvote(p *Post) error { + if !p.HasReputationSupport(p) { + return ErrNotSupported + } + + // TODO: Implement +} diff --git a/examples/gno.land/p/demo/boardsv2/draft1/plugin_locking.gno b/examples/gno.land/p/demo/boardsv2/draft1/plugin_locking.gno deleted file mode 100644 index b390284fbcf..00000000000 --- a/examples/gno.land/p/demo/boardsv2/draft1/plugin_locking.gno +++ /dev/null @@ -1,19 +0,0 @@ -package boardsv2 - -func NewLockingPlugin( /* options */ ) LockingPlugin { -} - -type LockingStorage struct { - IsLocked bool -} - -type LockingPlugin struct { -} - -func (p *LockingPlugin) Lock(p *Post) { - // TODO: Check local storare to see if it's lock otherwise change storage - p.GetPluginStore(p.Name()).(LockingStorage).IsLocked = true - // TODO: We need to update (Save) the post everytime there is a change. -} -func (p *LockingPlugin) Unlock(p *Post) {} -func (p LockingPlugin) IsLocked(p *Post) bool {} diff --git a/examples/gno.land/p/demo/boardsv2/draft1/plugin_reputation.gno b/examples/gno.land/p/demo/boardsv2/draft1/plugin_reputation.gno deleted file mode 100644 index aba2faeebfd..00000000000 --- a/examples/gno.land/p/demo/boardsv2/draft1/plugin_reputation.gno +++ /dev/null @@ -1,38 +0,0 @@ -package boardsv2 - -// TODO: Move to a plugins folder -// TODO: Plugins can also have global stores (plugins can be local to a post or global) - -func NewReputationPlugin( /* options */ ) ReputationPlugin { - -} - -type ReputationStorage struct { - Upvotes uint - Downbotes uint - ListOfWhoVotedWhat avl.Tree // string(std.Address) -> ?? (TODO: define) -} - -type ReputationPlugin struct { - AllowedLevels []int -} - -func (p ReputationPlugin) Name() string { - return "reputation_plugin" -} - -func (p *ReputationPlugin) Votes(p *Post) uint32 { -} - -func (p *ReputationPlugin) Upvote(p *Post) { - // TODO: Assert that post has the allowed level - st := p.GetPluginStore(p.Name()).(*Storage) - - // TODO: modify global state - // TODO: modify local state -} - -func (p *ReputationPlugin) Downvote(p *Post) { - // TODO: Assert that post has the allowed level - -} diff --git a/examples/gno.land/p/demo/boardsv2/draft1/post.gno b/examples/gno.land/p/demo/boardsv2/draft1/post.gno index 78b617a78f3..1498cda066f 100644 --- a/examples/gno.land/p/demo/boardsv2/draft1/post.gno +++ b/examples/gno.land/p/demo/boardsv2/draft1/post.gno @@ -13,17 +13,15 @@ const ( type ( Content interface { - Type() string // NOTE: The idea is to avoid casting + Type() string Render() string } - // TODO: ID is the full path to the post - Post struct { - ID string // NOTE: It would be nice to use a type alias for the ID field type - Content Content // NOTE: Maybe should be called Type (board as content is odd) - PluginContent avl.Tree // string(plugin name) -> interface{} - Parent *Post // NOTE: If all is a post we need to have a parent + ID string + Content Content + PluginStorage avl.Tree // string(plugin name) -> interface{}(plugin storage) + Parent *Post Level int Base *Post Children []*Post @@ -34,6 +32,14 @@ type ( } ) +func (p Post) MustGetPluginStorage(name string) interface{} { + if v, found := p.pluginStorage.Get(name); found { + return v + } + + panic("plugin storage not found: " + name) +} + func (p Post) NextIncrementalKey(baseKey string) string { return baseKey + "/" + strconv.Itoa(len(p.Children)) } From 51373495a53cb2d7fd6dcc4be81872191422b57d Mon Sep 17 00:00:00 2001 From: jeronimoalbi Date: Tue, 8 Oct 2024 15:05:08 +0200 Subject: [PATCH 10/19] chore: updated draft1 plugins to habe a global name --- .../p/demo/boardsv2/draft1/plugin/locking/locking.gno | 4 +++- .../p/demo/boardsv2/draft1/plugin/reputation/reputation.gno | 6 +++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/examples/gno.land/p/demo/boardsv2/draft1/plugin/locking/locking.gno b/examples/gno.land/p/demo/boardsv2/draft1/plugin/locking/locking.gno index 2b6010893a8..ba23251a4ef 100644 --- a/examples/gno.land/p/demo/boardsv2/draft1/plugin/locking/locking.gno +++ b/examples/gno.land/p/demo/boardsv2/draft1/plugin/locking/locking.gno @@ -2,6 +2,8 @@ package lockingplugin import "errors" +const Name = "boards:locking" + var ErrInvalidPostType = errors.New("post type is not a board or thread") type ( @@ -16,7 +18,7 @@ func New() Plugin { } func (p Plugin) Name() string { - return "boards:locking" + return Name } func (p *Plugin) Lock(p *Post) error { diff --git a/examples/gno.land/p/demo/boardsv2/draft1/plugin/reputation/reputation.gno b/examples/gno.land/p/demo/boardsv2/draft1/plugin/reputation/reputation.gno index 7d8c2351846..13fcfa3facd 100644 --- a/examples/gno.land/p/demo/boardsv2/draft1/plugin/reputation/reputation.gno +++ b/examples/gno.land/p/demo/boardsv2/draft1/plugin/reputation/reputation.gno @@ -1,10 +1,14 @@ package reputationplugin +import "errors" + const ( PolicyLinear = iota PolicyTokenBase ) +const Name = "boards:reputation" + var ErrNotSupported = errors.New("reputation not supported") type ( @@ -29,7 +33,7 @@ func NewReputationPlugin(o ...Option) Plugin { } func (p Plugin) Name() string { - return "boards:reputation" + return Name } func (p Plugin) HasReputationSupport(p *Post) bool { From daac9bd8938a537da66a0fb4b0d85b6a41fc8815 Mon Sep 17 00:00:00 2001 From: jeronimoalbi Date: Tue, 8 Oct 2024 15:06:40 +0200 Subject: [PATCH 11/19] wip: exploring Ilker's plugin proposal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: İlker G. Öztürk --- .../gno.land/p/demo/boardsv2/draf2/app.gno | 91 ++++++++++++ .../gno.land/p/demo/boardsv2/draf2/board.gno | 14 ++ .../gno.land/p/demo/boardsv2/draf2/boards.gno | 135 ------------------ .../p/demo/boardsv2/draf2/comment.gno | 8 ++ .../p/demo/boardsv2/draf2/comments.gno | 1 - .../p/demo/boardsv2/draf2/context.gno | 38 +++++ .../draf2/post/plugin/comment/comment.gno | 27 ++-- .../boardsv2/draf2/post/plugin/plugin.gno | 7 + .../p/demo/boardsv2/draf2/post/post.gno | 27 ++-- .../gno.land/p/demo/boardsv2/draf2/thread.gno | 18 +++ .../p/demo/boardsv2/draf2/threads.gno | 0 11 files changed, 201 insertions(+), 165 deletions(-) create mode 100644 examples/gno.land/p/demo/boardsv2/draf2/app.gno create mode 100644 examples/gno.land/p/demo/boardsv2/draf2/board.gno delete mode 100644 examples/gno.land/p/demo/boardsv2/draf2/boards.gno create mode 100644 examples/gno.land/p/demo/boardsv2/draf2/comment.gno delete mode 100644 examples/gno.land/p/demo/boardsv2/draf2/comments.gno create mode 100644 examples/gno.land/p/demo/boardsv2/draf2/context.gno create mode 100644 examples/gno.land/p/demo/boardsv2/draf2/thread.gno delete mode 100644 examples/gno.land/p/demo/boardsv2/draf2/threads.gno diff --git a/examples/gno.land/p/demo/boardsv2/draf2/app.gno b/examples/gno.land/p/demo/boardsv2/draf2/app.gno new file mode 100644 index 00000000000..30125302302 --- /dev/null +++ b/examples/gno.land/p/demo/boardsv2/draf2/app.gno @@ -0,0 +1,91 @@ +package boards + +import ( + "plugin" + + "golang.org/x/mod/sumdb/storage" +) + +const ( + LevelBoard = iota + LevelThread + LevelComment +) + +type App struct { + cntx Context +} + +func New(st storage.PostStorage, o ...Option) App { + // TODO: avl.Tree feels wrong here but maybe get rid of the map anyway + p := map[plugin.Name]plugin.Plugin{ + plugintitle.Name: plugintitle.New(st), // content for boards + plugintext.Name: plugintext.New(st),// content for text based threads + pluginpoll.Name: pluginpoll.New(st),// content for poll based threads + plugincomment.Name: plugincomment.New(st),// content for comments to the threads + } + + c := Context{ + storage: st, + plugins: p, + } + + a := App{ + cntx: c, + } + + return a +} + +func (a App) Board(path string) (Board, error) { + a.c.Get(level, path func(){}) +} + +func (a App) Boards(c post.Cursor) ([]Board, error) { + +} + +func (a App) CreateBoard(c BoardContent) (Board, error) {} + +func (b App) Thread(path string) (Thread, error) { + return ThreadWithComments(path, nil) +} + +// Fork forks either a board or a thread by their path. +func (a App) Fork(path, newPath string) error {} + +// Lock locks either a board or a thread by their path. +// Once a board is locked new threads to the board and comments to the existing +// threads won't be allowed. +// Once a thread is locked new comments to the thread won't be allowed. +func (a App) Lock(path string) error {} + + +// ThreadWithComments returns a thread with its comments with the comment depth +// configured with commentDepth for direct and child comments. +// For ex. +// To get a thread with only 10 direct (parent level) comments use: +// - []int{10} +// To get a thread with 10 direct comments and 3 of their child comments use: +// - []int{10, 3} +// You can define configure this for more levels until you reach to value defined +// by MaxCommentDepth. +// By default the configuration is as follows: +// - []int{20, 3} +func (b App) ThreadWithComments(path string, commentDepth []int) (Thread, error) {} +func (b App) Threads(c post.Cursor) ([]Thread, error) {} +func (b App) CreateTextThread(c ThreadTextContent) (Thread, error) {} +func (b App) CreatePollThread(c ThreadPollContent) (Thread, error) {} + +// parentPath could be a path to thread (root), or path to any of the +// nested comments. +func (b App) Comments(parentPath string, c Cursor) ([]Comment, error) {} +func (b App) CreateComment(path string, c plugincomment.Content) (Comment, error) { + post, err := a.c.Plugin(plugincomment.Name).NewPost(c, LevelComment) + if err != nil { + return Comment{}, err + } + return Comment{Post: post, c: a.c} +} + +func (a App) Render(path string) string {} diff --git a/examples/gno.land/p/demo/boardsv2/draf2/board.gno b/examples/gno.land/p/demo/boardsv2/draf2/board.gno new file mode 100644 index 00000000000..ac9258fa629 --- /dev/null +++ b/examples/gno.land/p/demo/boardsv2/draf2/board.gno @@ -0,0 +1,14 @@ +package boards + +type Board struct { + post.Post + c Context +} + +func (b Board) Content() BoardContent { + return b.c.Plugin(pluginbasiccontent.Name).Content(b.Post) +} + +func (b Board) Render() string { + +} diff --git a/examples/gno.land/p/demo/boardsv2/draf2/boards.gno b/examples/gno.land/p/demo/boardsv2/draf2/boards.gno deleted file mode 100644 index ad8f0937ed6..00000000000 --- a/examples/gno.land/p/demo/boardsv2/draf2/boards.gno +++ /dev/null @@ -1,135 +0,0 @@ -package boards - -const ( - LevelBoard = iota - LevelThread - LevelComment -) - -type App struct { - c Context -} - - -func New(st post.Storage) App { - a := App{ - st: st, - plugs: make(map[post.PluginName]plugin.Plugin), - } - - a.plugs[pluginbody.Name] = pluginbody.New(st) // load global state from st - - return a -} - -type Board struct { - post.Post - c Context -} - -func (b Board) Content() BoardContent { - return b.c.Plugin(pluginbasiccontent.Name).Content(b.Post) -} - -func (b Board) Render() string { - -} - -type Thread struct { - post.Post - c Context -} - -func (t Thread) TextContent() ThreadTextContent { - -} - -func (t Thread) PollContent() ThreadPollContent {} -func (t Thread) Type() ContentType {} - -// Comments returns a list of comments sent to the thread. -// The comment slice will be non-nil only when Thread is initiated -// through ThreadWithComments. -func (t Thread) Comments() []Comment {} - -type Comment struct { - post.Post - c Context -} - -func (c Comment) Content() CommentContent {} - -func (a App) Board(path string) ([]Board, error) { - a.c.Get(level, path func(){}) -} -func (a App) LockBoard(path string) (error) {} -func (a App) ForkBoard(path string) (error) {} - -func (a App) Boards(c post.Cursor) ([]Board, error) {} -func (a App) CreateBoard(c BoardContent) (Board, error) {} - -func (b App) Thread(path string) (Thread, error) { - return ThreadWithComments(path, nil) -} -func (a App) LockThread(path string) (error) {} -func (a App) ForkThread(path string) {} - -// ThreadWithComments returns a thread with its comments with the comment depth -// configured with commentDepth for direct and child comments. -// For ex. -// To get a thread with only 10 direct (parent level) comments use: -// - []int{10} -// To get a thread with 10 direct comments and 3 of their child comments use: -// - []int{10, 3} -// You can define configure this for more levels until you reach to value defined -// by MaxCommentDepth. -// By default the configuration is as follows: -// - []int{20, 3} -func (b App) ThreadWithComments(path string, commentDepth []int) (Thread, error) {} -func (b App) Threads(c post.Cursor) ([]Thread, error) {} -func (b App) CreateTextThread(c ThreadTextContent) (Thread, error) {} -func (b App) CreatePollThread(c ThreadPollContent) (Thread, error) {} - -// parentPath could be a path to thread (root), or path to any of the -// nested comments. -func (b App) Comments(parentPath string, c Cursor) ([]Comment, error) {} -func (b App) CreateComment(path string, c plugincomment.Content) (Comment, error) { - post, err := a.c.Plugin(plugincomment.Name).NewPost(c, LevelComment) - if err != nil { - return Comment{}, err - } - return Comment{Post: post, c: a.c} -} - -func (a App) Render(path string) string {} - -type Context struct { - opts []Option - st post.Storage - plugs map[post.PluginName]plugin.Plugin -} - -func (c Context) Plugin(n post.PluginName) post.Plugin { - -} - -func (c Context) Set(p *Post) (updated bool) { - key := newKey(p.Level, p.Slug()) - return b.posts.Set(key, p) -} - -func (c Context) Remove(level int, path string) (_ *Post, removed bool) { - key := newKey(level, path) - if v, removed := b.posts.Remove(key); removed { - return v.(*Post), true - } - return nil, false -} - -func (c Context) Get(level int, path string, iterator func()) (_ *Post, found bool) { - key := newKey(level, path) - if v, found := b.posts.Get(key); found { - return v.(*Post), true - } - return "", false -} diff --git a/examples/gno.land/p/demo/boardsv2/draf2/comment.gno b/examples/gno.land/p/demo/boardsv2/draf2/comment.gno new file mode 100644 index 00000000000..d66a77d1a67 --- /dev/null +++ b/examples/gno.land/p/demo/boardsv2/draf2/comment.gno @@ -0,0 +1,8 @@ +package boards + +type Comment struct { + post.Post + c Context +} + +func (c Comment) Content() CommentContent {} diff --git a/examples/gno.land/p/demo/boardsv2/draf2/comments.gno b/examples/gno.land/p/demo/boardsv2/draf2/comments.gno deleted file mode 100644 index b818fc17eb6..00000000000 --- a/examples/gno.land/p/demo/boardsv2/draf2/comments.gno +++ /dev/null @@ -1 +0,0 @@ -package boards diff --git a/examples/gno.land/p/demo/boardsv2/draf2/context.gno b/examples/gno.land/p/demo/boardsv2/draf2/context.gno new file mode 100644 index 00000000000..15c31e1db1d --- /dev/null +++ b/examples/gno.land/p/demo/boardsv2/draf2/context.gno @@ -0,0 +1,38 @@ +package boards + +import "plugin" + +type Context struct { + opts []Option + st post.Storage + plugs map[post.PluginName]plugin.Plugin +} + +func newContext() Context { + +} + +func (c Context) Plugin(n post.PluginName) post.Plugin { + +} + +func (c Context) Set(p *Post) (updated bool) { + key := newKey(p.Level, p.Slug()) + return b.posts.Set(key, p) +} + +func (c Context) Remove(level int, path string) (_ *Post, removed bool) { + key := newKey(level, path) + if v, removed := b.posts.Remove(key); removed { + return v.(*Post), true + } + return nil, false +} + +func (c Context) Get(level int, path string, iterator func()) (_ *Post, found bool) { + key := newKey(level, path) + if v, found := b.posts.Get(key); found { + return v.(*Post), true + } + return "", false +} diff --git a/examples/gno.land/p/demo/boardsv2/draf2/post/plugin/comment/comment.gno b/examples/gno.land/p/demo/boardsv2/draf2/post/plugin/comment/comment.gno index f657df880bc..2c4d7a53994 100644 --- a/examples/gno.land/p/demo/boardsv2/draf2/post/plugin/comment/comment.gno +++ b/examples/gno.land/p/demo/boardsv2/draf2/post/plugin/comment/comment.gno @@ -6,28 +6,29 @@ func New(st Storage) Plugin { } type Plugin struct { - st Storage + postStorage Storage } -type Content struct { - Title string +type Content struct { // Content of the comment. + Title string Description string - Tags []string + Tags []string } -func NewPost(id string, c Content, level int) (*Post, error) { - post := &Post{ - ID: id, +func (p Plugin) CreateComment(id string, c Content, level int) *post.Post { + pp := &post.Post{ + ID: id, Level: level, } - return post, p.SetContent(post, c) + p.EditCommentContent(pp, c) + return pp } -func (p Plugin) Content(post *Post) Content { - return post.Body[Name].(Content) +func (p Plugin) Content(pst *post.Post) Content { + return pst.PluginStorage[Name].(Content) } -func (p Plugin) SetContent(post *Post, c Content) error { - post.Body[Name] = c - return p.st.Set(post.ID, post) +func (p Plugin) EditCommentContent(pp *Post, c Content) (updated bool) { + pp.PluginStorage[Name] = c + return p.postStorage.Set(post.ID, pp) } diff --git a/examples/gno.land/p/demo/boardsv2/draf2/post/plugin/plugin.gno b/examples/gno.land/p/demo/boardsv2/draf2/post/plugin/plugin.gno index e69de29bb2d..2f1cff7e627 100644 --- a/examples/gno.land/p/demo/boardsv2/draf2/post/plugin/plugin.gno +++ b/examples/gno.land/p/demo/boardsv2/draf2/post/plugin/plugin.gno @@ -0,0 +1,7 @@ +package plugin + +type Plugin interface { + Type() string +} + +type Name string diff --git a/examples/gno.land/p/demo/boardsv2/draf2/post/post.gno b/examples/gno.land/p/demo/boardsv2/draf2/post/post.gno index a697c15ed27..04b6b88f48c 100644 --- a/examples/gno.land/p/demo/boardsv2/draf2/post/post.gno +++ b/examples/gno.land/p/demo/boardsv2/draf2/post/post.gno @@ -1,28 +1,23 @@ package boardsv2 import ( + "plugin" "strconv" "time" ) type ( - Body interface { - Type() string // NOTE: The idea is to avoid casting - } - - // TODO: ID is the full path to the post - Post struct { - ID string // NOTE: It would be nice to use a type alias for the ID field type - Body Body // NOTE: Maybe should be called Type (board as content is odd) - Parent *Post // NOTE: If all is a post we need to have a parent - Level int - Base *Post - Children []*Post - Forks []*Post - UpdatedAt time.Time - CreatedAt time.Time - Creator std.Address + ID string + PluginStore plugin.Plugin + Parent *Post + Level int + Base *Post + Children []*Post + Forks []*Post + UpdatedAt time.Time + CreatedAt time.Time + Creator std.Address } ) diff --git a/examples/gno.land/p/demo/boardsv2/draf2/thread.gno b/examples/gno.land/p/demo/boardsv2/draf2/thread.gno new file mode 100644 index 00000000000..42e731d4fd2 --- /dev/null +++ b/examples/gno.land/p/demo/boardsv2/draf2/thread.gno @@ -0,0 +1,18 @@ +package boards + +type Thread struct { + post.Post + c Context +} + +func (t Thread) TextContent() ThreadTextContent { + +} + +func (t Thread) PollContent() ThreadPollContent {} +func (t Thread) Type() ContentType {} + +// Comments returns a list of comments sent to the thread. +// The comment slice will be non-nil only when Thread is initiated +// through ThreadWithComments. +func (t Thread) Comments() []Comment {} diff --git a/examples/gno.land/p/demo/boardsv2/draf2/threads.gno b/examples/gno.land/p/demo/boardsv2/draf2/threads.gno deleted file mode 100644 index e69de29bb2d..00000000000 From 17af0134d402403df8c8db589e6743e66fb451f5 Mon Sep 17 00:00:00 2001 From: jeronimoalbi Date: Tue, 8 Oct 2024 15:07:45 +0200 Subject: [PATCH 12/19] chore: correct `draft2` folder name --- examples/gno.land/p/demo/boardsv2/{draf2 => draft2}/app.gno | 0 examples/gno.land/p/demo/boardsv2/{draf2 => draft2}/board.gno | 0 examples/gno.land/p/demo/boardsv2/{draf2 => draft2}/comment.gno | 0 examples/gno.land/p/demo/boardsv2/{draf2 => draft2}/context.gno | 0 examples/gno.land/p/demo/boardsv2/{draf2 => draft2}/option.gno | 0 .../gno.land/p/demo/boardsv2/{draf2 => draft2}/post/cursor.gno | 0 .../boardsv2/{draf2 => draft2}/post/plugin/comment/comment.gno | 0 .../p/demo/boardsv2/{draf2 => draft2}/post/plugin/lock/lock.gno | 0 .../p/demo/boardsv2/{draf2 => draft2}/post/plugin/plugin.gno | 0 .../p/demo/boardsv2/{draf2 => draft2}/post/plugin/poll/poll.gno | 0 .../{draf2 => draft2}/post/plugin/reputation/reputation.gno | 0 .../p/demo/boardsv2/{draf2 => draft2}/post/plugin/text/text.gno | 0 .../p/demo/boardsv2/{draf2 => draft2}/post/plugin/title/title.gno | 0 examples/gno.land/p/demo/boardsv2/{draf2 => draft2}/post/post.gno | 0 .../p/demo/boardsv2/{draf2 => draft2}/post/store/store.gno | 0 examples/gno.land/p/demo/boardsv2/{draf2 => draft2}/thread.gno | 0 16 files changed, 0 insertions(+), 0 deletions(-) rename examples/gno.land/p/demo/boardsv2/{draf2 => draft2}/app.gno (100%) rename examples/gno.land/p/demo/boardsv2/{draf2 => draft2}/board.gno (100%) rename examples/gno.land/p/demo/boardsv2/{draf2 => draft2}/comment.gno (100%) rename examples/gno.land/p/demo/boardsv2/{draf2 => draft2}/context.gno (100%) rename examples/gno.land/p/demo/boardsv2/{draf2 => draft2}/option.gno (100%) rename examples/gno.land/p/demo/boardsv2/{draf2 => draft2}/post/cursor.gno (100%) rename examples/gno.land/p/demo/boardsv2/{draf2 => draft2}/post/plugin/comment/comment.gno (100%) rename examples/gno.land/p/demo/boardsv2/{draf2 => draft2}/post/plugin/lock/lock.gno (100%) rename examples/gno.land/p/demo/boardsv2/{draf2 => draft2}/post/plugin/plugin.gno (100%) rename examples/gno.land/p/demo/boardsv2/{draf2 => draft2}/post/plugin/poll/poll.gno (100%) rename examples/gno.land/p/demo/boardsv2/{draf2 => draft2}/post/plugin/reputation/reputation.gno (100%) rename examples/gno.land/p/demo/boardsv2/{draf2 => draft2}/post/plugin/text/text.gno (100%) rename examples/gno.land/p/demo/boardsv2/{draf2 => draft2}/post/plugin/title/title.gno (100%) rename examples/gno.land/p/demo/boardsv2/{draf2 => draft2}/post/post.gno (100%) rename examples/gno.land/p/demo/boardsv2/{draf2 => draft2}/post/store/store.gno (100%) rename examples/gno.land/p/demo/boardsv2/{draf2 => draft2}/thread.gno (100%) diff --git a/examples/gno.land/p/demo/boardsv2/draf2/app.gno b/examples/gno.land/p/demo/boardsv2/draft2/app.gno similarity index 100% rename from examples/gno.land/p/demo/boardsv2/draf2/app.gno rename to examples/gno.land/p/demo/boardsv2/draft2/app.gno diff --git a/examples/gno.land/p/demo/boardsv2/draf2/board.gno b/examples/gno.land/p/demo/boardsv2/draft2/board.gno similarity index 100% rename from examples/gno.land/p/demo/boardsv2/draf2/board.gno rename to examples/gno.land/p/demo/boardsv2/draft2/board.gno diff --git a/examples/gno.land/p/demo/boardsv2/draf2/comment.gno b/examples/gno.land/p/demo/boardsv2/draft2/comment.gno similarity index 100% rename from examples/gno.land/p/demo/boardsv2/draf2/comment.gno rename to examples/gno.land/p/demo/boardsv2/draft2/comment.gno diff --git a/examples/gno.land/p/demo/boardsv2/draf2/context.gno b/examples/gno.land/p/demo/boardsv2/draft2/context.gno similarity index 100% rename from examples/gno.land/p/demo/boardsv2/draf2/context.gno rename to examples/gno.land/p/demo/boardsv2/draft2/context.gno diff --git a/examples/gno.land/p/demo/boardsv2/draf2/option.gno b/examples/gno.land/p/demo/boardsv2/draft2/option.gno similarity index 100% rename from examples/gno.land/p/demo/boardsv2/draf2/option.gno rename to examples/gno.land/p/demo/boardsv2/draft2/option.gno diff --git a/examples/gno.land/p/demo/boardsv2/draf2/post/cursor.gno b/examples/gno.land/p/demo/boardsv2/draft2/post/cursor.gno similarity index 100% rename from examples/gno.land/p/demo/boardsv2/draf2/post/cursor.gno rename to examples/gno.land/p/demo/boardsv2/draft2/post/cursor.gno diff --git a/examples/gno.land/p/demo/boardsv2/draf2/post/plugin/comment/comment.gno b/examples/gno.land/p/demo/boardsv2/draft2/post/plugin/comment/comment.gno similarity index 100% rename from examples/gno.land/p/demo/boardsv2/draf2/post/plugin/comment/comment.gno rename to examples/gno.land/p/demo/boardsv2/draft2/post/plugin/comment/comment.gno diff --git a/examples/gno.land/p/demo/boardsv2/draf2/post/plugin/lock/lock.gno b/examples/gno.land/p/demo/boardsv2/draft2/post/plugin/lock/lock.gno similarity index 100% rename from examples/gno.land/p/demo/boardsv2/draf2/post/plugin/lock/lock.gno rename to examples/gno.land/p/demo/boardsv2/draft2/post/plugin/lock/lock.gno diff --git a/examples/gno.land/p/demo/boardsv2/draf2/post/plugin/plugin.gno b/examples/gno.land/p/demo/boardsv2/draft2/post/plugin/plugin.gno similarity index 100% rename from examples/gno.land/p/demo/boardsv2/draf2/post/plugin/plugin.gno rename to examples/gno.land/p/demo/boardsv2/draft2/post/plugin/plugin.gno diff --git a/examples/gno.land/p/demo/boardsv2/draf2/post/plugin/poll/poll.gno b/examples/gno.land/p/demo/boardsv2/draft2/post/plugin/poll/poll.gno similarity index 100% rename from examples/gno.land/p/demo/boardsv2/draf2/post/plugin/poll/poll.gno rename to examples/gno.land/p/demo/boardsv2/draft2/post/plugin/poll/poll.gno diff --git a/examples/gno.land/p/demo/boardsv2/draf2/post/plugin/reputation/reputation.gno b/examples/gno.land/p/demo/boardsv2/draft2/post/plugin/reputation/reputation.gno similarity index 100% rename from examples/gno.land/p/demo/boardsv2/draf2/post/plugin/reputation/reputation.gno rename to examples/gno.land/p/demo/boardsv2/draft2/post/plugin/reputation/reputation.gno diff --git a/examples/gno.land/p/demo/boardsv2/draf2/post/plugin/text/text.gno b/examples/gno.land/p/demo/boardsv2/draft2/post/plugin/text/text.gno similarity index 100% rename from examples/gno.land/p/demo/boardsv2/draf2/post/plugin/text/text.gno rename to examples/gno.land/p/demo/boardsv2/draft2/post/plugin/text/text.gno diff --git a/examples/gno.land/p/demo/boardsv2/draf2/post/plugin/title/title.gno b/examples/gno.land/p/demo/boardsv2/draft2/post/plugin/title/title.gno similarity index 100% rename from examples/gno.land/p/demo/boardsv2/draf2/post/plugin/title/title.gno rename to examples/gno.land/p/demo/boardsv2/draft2/post/plugin/title/title.gno diff --git a/examples/gno.land/p/demo/boardsv2/draf2/post/post.gno b/examples/gno.land/p/demo/boardsv2/draft2/post/post.gno similarity index 100% rename from examples/gno.land/p/demo/boardsv2/draf2/post/post.gno rename to examples/gno.land/p/demo/boardsv2/draft2/post/post.gno diff --git a/examples/gno.land/p/demo/boardsv2/draf2/post/store/store.gno b/examples/gno.land/p/demo/boardsv2/draft2/post/store/store.gno similarity index 100% rename from examples/gno.land/p/demo/boardsv2/draf2/post/store/store.gno rename to examples/gno.land/p/demo/boardsv2/draft2/post/store/store.gno diff --git a/examples/gno.land/p/demo/boardsv2/draf2/thread.gno b/examples/gno.land/p/demo/boardsv2/draft2/thread.gno similarity index 100% rename from examples/gno.land/p/demo/boardsv2/draf2/thread.gno rename to examples/gno.land/p/demo/boardsv2/draft2/thread.gno From f71343c1ae7f4eb3675e39539f593f98d8f4810b Mon Sep 17 00:00:00 2001 From: jeronimoalbi Date: Wed, 9 Oct 2024 14:43:05 +0200 Subject: [PATCH 13/19] wip: refined and organized draft1 and draft2 ideas into draft3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: İlker G. Öztürk --- .../gno.land/p/demo/boardsv2/draft3/app.gno | 91 +++++++++++++++++++ .../gno.land/p/demo/boardsv2/draft3/board.gno | 14 +++ .../p/demo/boardsv2/draft3/comment.gno | 8 ++ .../p/demo/boardsv2/draft3/context.gno | 38 ++++++++ .../p/demo/boardsv2/draft3/option.gno | 19 ++++ .../draft3/post/plugin/comment/comment.gno | 46 ++++++++++ .../boardsv2/draft3/post/plugin/lock/lock.gno | 51 +++++++++++ .../boardsv2/draft3/post/plugin/plugin.gno | 8 ++ .../boardsv2/draft3/post/plugin/poll/poll.gno | 39 ++++++++ .../draft3/post/plugin/reputation/options.gno | 15 +++ .../post/plugin/reputation/reputation.gno | 76 ++++++++++++++++ .../boardsv2/draft3/post/plugin/text/text.gno | 25 +++++ .../draft3/post/plugin/title/title.gno | 26 ++++++ .../p/demo/boardsv2/draft3/post/post.gno | 27 ++++++ .../boardsv2/draft3/post/store/cursor.gno | 8 ++ .../demo/boardsv2/draft3/post/store/store.gno | 6 ++ .../p/demo/boardsv2/draft3/thread.gno | 18 ++++ .../gno.land/r/demo/boardsv2/draft2/main.gno | 15 +++ .../r/demo/boardsv2/draft3/boards.gno | 11 +++ 19 files changed, 541 insertions(+) create mode 100644 examples/gno.land/p/demo/boardsv2/draft3/app.gno create mode 100644 examples/gno.land/p/demo/boardsv2/draft3/board.gno create mode 100644 examples/gno.land/p/demo/boardsv2/draft3/comment.gno create mode 100644 examples/gno.land/p/demo/boardsv2/draft3/context.gno create mode 100644 examples/gno.land/p/demo/boardsv2/draft3/option.gno create mode 100644 examples/gno.land/p/demo/boardsv2/draft3/post/plugin/comment/comment.gno create mode 100644 examples/gno.land/p/demo/boardsv2/draft3/post/plugin/lock/lock.gno create mode 100644 examples/gno.land/p/demo/boardsv2/draft3/post/plugin/plugin.gno create mode 100644 examples/gno.land/p/demo/boardsv2/draft3/post/plugin/poll/poll.gno create mode 100644 examples/gno.land/p/demo/boardsv2/draft3/post/plugin/reputation/options.gno create mode 100644 examples/gno.land/p/demo/boardsv2/draft3/post/plugin/reputation/reputation.gno create mode 100644 examples/gno.land/p/demo/boardsv2/draft3/post/plugin/text/text.gno create mode 100644 examples/gno.land/p/demo/boardsv2/draft3/post/plugin/title/title.gno create mode 100644 examples/gno.land/p/demo/boardsv2/draft3/post/post.gno create mode 100644 examples/gno.land/p/demo/boardsv2/draft3/post/store/cursor.gno create mode 100644 examples/gno.land/p/demo/boardsv2/draft3/post/store/store.gno create mode 100644 examples/gno.land/p/demo/boardsv2/draft3/thread.gno create mode 100644 examples/gno.land/r/demo/boardsv2/draft2/main.gno create mode 100644 examples/gno.land/r/demo/boardsv2/draft3/boards.gno diff --git a/examples/gno.land/p/demo/boardsv2/draft3/app.gno b/examples/gno.land/p/demo/boardsv2/draft3/app.gno new file mode 100644 index 00000000000..cd00c860ce9 --- /dev/null +++ b/examples/gno.land/p/demo/boardsv2/draft3/app.gno @@ -0,0 +1,91 @@ +package boards + +import ( + "plugin" + + "golang.org/x/mod/sumdb/storage" +) + +const ( + LevelBoard = iota + LevelThread + LevelComment +) + +type App struct { + cntx Context +} + +func New(st storage.PostStorage, o ...Option) App { + // TODO: avl.Tree feels wrong here but maybe get rid of the map anyway + p := map[string]plugin.Plugin{ + plugintitle.Name: plugintitle.New(st), // content for boards + plugintext.Name: plugintext.New(st),// content for text based threads + pluginpoll.Name: pluginpoll.New(st),// content for poll based threads + plugincomment.Name: plugincomment.New(st),// content for comments to the threads + pluginreputation.Name: pluginreputation.New( + pluginreputation.UseTokenBasePolicy(), + pluginreputation.AllowedPostLevels(post.LevelPost, post.LevelComment), + ), + } + + return App{ + cntx: Context{ + storage: st, + plugins: p, + }, + } +} + +func (a App) Board(path string) (Board, error) { + a.c.Get(level, path func(){}) +} + +func (a App) Boards(c post.Cursor) ([]Board, error) { + +} + +func (a App) CreateBoard(c BoardContent) (Board, error) {} + +func (b App) Thread(path string) (Thread, error) { + return ThreadWithComments(path, nil) +} + +// Fork forks either a board or a thread by their path. +func (a App) Fork(path, newPath string) error {} + +// Lock locks either a board or a thread by their path. +// Once a board is locked new threads to the board and comments to the existing +// threads won't be allowed. +// Once a thread is locked new comments to the thread won't be allowed. +func (a App) Lock(path string) error {} + + +// ThreadWithComments returns a thread with its comments with the comment depth +// configured with commentDepth for direct and child comments. +// For ex. +// To get a thread with only 10 direct (parent level) comments use: +// - []int{10} +// To get a thread with 10 direct comments and 3 of their child comments use: +// - []int{10, 3} +// You can define configure this for more levels until you reach to value defined +// by MaxCommentDepth. +// By default the configuration is as follows: +// - []int{20, 3} +func (b App) ThreadWithComments(path string, commentDepth []int) (Thread, error) {} +func (b App) Threads(c post.Cursor) ([]Thread, error) {} +func (b App) CreateTextThread(c ThreadTextContent) (Thread, error) {} +func (b App) CreatePollThread(c ThreadPollContent) (Thread, error) {} + +// parentPath could be a path to thread (root), or path to any of the +// nested comments. +func (b App) Comments(parentPath string, c Cursor) ([]Comment, error) {} +func (b App) CreateComment(path string, c plugincomment.Content) (Comment, error) { + post, err := a.c.Plugin(plugincomment.Name).NewPost(c, LevelComment) + if err != nil { + return Comment{}, err + } + return Comment{Post: post, c: a.c} +} + +func (a App) Render(path string) string {} diff --git a/examples/gno.land/p/demo/boardsv2/draft3/board.gno b/examples/gno.land/p/demo/boardsv2/draft3/board.gno new file mode 100644 index 00000000000..ac9258fa629 --- /dev/null +++ b/examples/gno.land/p/demo/boardsv2/draft3/board.gno @@ -0,0 +1,14 @@ +package boards + +type Board struct { + post.Post + c Context +} + +func (b Board) Content() BoardContent { + return b.c.Plugin(pluginbasiccontent.Name).Content(b.Post) +} + +func (b Board) Render() string { + +} diff --git a/examples/gno.land/p/demo/boardsv2/draft3/comment.gno b/examples/gno.land/p/demo/boardsv2/draft3/comment.gno new file mode 100644 index 00000000000..d66a77d1a67 --- /dev/null +++ b/examples/gno.land/p/demo/boardsv2/draft3/comment.gno @@ -0,0 +1,8 @@ +package boards + +type Comment struct { + post.Post + c Context +} + +func (c Comment) Content() CommentContent {} diff --git a/examples/gno.land/p/demo/boardsv2/draft3/context.gno b/examples/gno.land/p/demo/boardsv2/draft3/context.gno new file mode 100644 index 00000000000..15c31e1db1d --- /dev/null +++ b/examples/gno.land/p/demo/boardsv2/draft3/context.gno @@ -0,0 +1,38 @@ +package boards + +import "plugin" + +type Context struct { + opts []Option + st post.Storage + plugs map[post.PluginName]plugin.Plugin +} + +func newContext() Context { + +} + +func (c Context) Plugin(n post.PluginName) post.Plugin { + +} + +func (c Context) Set(p *Post) (updated bool) { + key := newKey(p.Level, p.Slug()) + return b.posts.Set(key, p) +} + +func (c Context) Remove(level int, path string) (_ *Post, removed bool) { + key := newKey(level, path) + if v, removed := b.posts.Remove(key); removed { + return v.(*Post), true + } + return nil, false +} + +func (c Context) Get(level int, path string, iterator func()) (_ *Post, found bool) { + key := newKey(level, path) + if v, found := b.posts.Get(key); found { + return v.(*Post), true + } + return "", false +} diff --git a/examples/gno.land/p/demo/boardsv2/draft3/option.gno b/examples/gno.land/p/demo/boardsv2/draft3/option.gno new file mode 100644 index 00000000000..48ab0cc1bff --- /dev/null +++ b/examples/gno.land/p/demo/boardsv2/draft3/option.gno @@ -0,0 +1,19 @@ +package boards + +type Option struct{} + +// LinearReputationPolicy allows upvoting or downvoting a post by one +// for each account. +func LinearReputationPolicy() Option {} + +// TokenBasedReputationPolicy allows upvoting or downvoting a post propotional +// to the specified tokens that an account holds. +func TokenBasedReputationPolicy() Option {} + +// MaxPostDepth configures the max depth for nested comments. +// 0 -> boards +// 1 -> threads +// 2 -> comments-1 (direct comments to the threads) +// The above are already reserved. +// Setting it to zero will disable comments. +func MaxCommentDepth(d int) Option {} diff --git a/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/comment/comment.gno b/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/comment/comment.gno new file mode 100644 index 00000000000..a14b21204e7 --- /dev/null +++ b/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/comment/comment.gno @@ -0,0 +1,46 @@ +package plugincomment + +const Name = "post-comment" + +type ( + Plugin struct { + posts Storage // TODO: Maybe it should be "post.Storage" or in that line + } + + // Content is the comment's content. + Content struct { + Title string + Description string + Tags []string + } +) + +func New(st Storage) Plugin { + return Plugin{ + posts: st, + } +} + +func (p Plugin) Render() string { // TODO: Add render method to other plugins + // TODO: Implement render support for comments + return "" +} + +func (p Plugin) CreateComment(id string, c Content, level int) *post.Post { + pst := &post.Post{ + ID: id, + Level: level, + } + p.SetContent(pst, c) + return pst +} + +func (p Plugin) Content(pst *post.Post) (_ *Content, ok bool) { + c, ok := pst.PluginStorage[Name].(*Content) + return c, ok +} + +func (p Plugin) SetContent(pst *Post, c Content) (updated bool) { + ps.PluginStorage[Name] = c + return p.posts.Set(pst) +} diff --git a/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/lock/lock.gno b/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/lock/lock.gno new file mode 100644 index 00000000000..a1e4b554f16 --- /dev/null +++ b/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/lock/lock.gno @@ -0,0 +1,51 @@ +package pluginlock + +import "errors" + +const Name = "lock" + +var ErrInvalidPostType = errors.New("post type is not a board or thread") + +type ( + Plugin struct{} + Lock struct { + IsLocked bool + } +) + +func New() Plugin { + return Plugin{} +} + +func (p Plugin) Name() string { + return Name +} + +func (p *Plugin) Lock(pst *post.Post) error { + if !isBoardOrThread(pst) { + return ErrInvalidPostType + } + + pst.PluginStore[Name].(*Lock).IsLocked = true +} + +func (p *Plugin) Unlock(pst *post.Post) error { + if !isBoardOrThread(pst) { + return ErrInvalidPostType + } + + pst.PluginStore[Name].(*Lock).IsLocked = false +} + +func (p Plugin) IsLocked(pst *post.Post) bool { + if !isBoardOrThread(pst) { + return ErrInvalidPostType + } + + // TODO: Check parents if current post is not locked + return pst.PluginStore[Name].(*Lock).IsLocked +} + +func isBoardOrThread(pst *post.Post) bool { + return pst.Level == post.LevelBoard || pst.Level == post.LevelPost +} diff --git a/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/plugin.gno b/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/plugin.gno new file mode 100644 index 00000000000..b350107dbcd --- /dev/null +++ b/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/plugin.gno @@ -0,0 +1,8 @@ +// TODO: Document how plugins work and best practices +package plugin + +// NOTE: Consider adding lifecycle methods like `Post` creation, deletion, ... +type Plugin interface { + Name() string + Render() string +} diff --git a/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/poll/poll.gno b/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/poll/poll.gno new file mode 100644 index 00000000000..3067397fdbd --- /dev/null +++ b/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/poll/poll.gno @@ -0,0 +1,39 @@ +package pluginpoll + +const Name = "post-poll" + +type ( + Plugin struct { + posts post.Storage // TODO: Implement posts storage + } + + Poll struct { + Question string + Options []string + Votes []struct { + Address std.Adress + Option string + } + Tags []string + } +) + +func New(st post.Storage) Plugin { + return Plugin{ + posts: st, + } +} + +func (p Plugin) CreatePoll(id string, v Poll) *post.Post { + pst := &post.Post{ + ID: id, + Level: LevelPost, + } + p.SetPoll(pst, v) + return pst +} + +func (p Plugin) SetPoll(pst *post.Post, v Poll) (updated bool) { + pst.PluginStorage[Name] = v + return p.posts.Set(pst.ID, pst) +} diff --git a/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/reputation/options.gno b/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/reputation/options.gno new file mode 100644 index 00000000000..522f02ee3e3 --- /dev/null +++ b/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/reputation/options.gno @@ -0,0 +1,15 @@ +package pluginreputation + +type Option func(*Plugin) + +func UseTokenBasePolicy() Option { + return func(p *Plugin) { + p.Policy = PolicyTokenBase + } +} + +func AllowedPostLevels(levels []int) Option { + return func(p *Plugin) { + p.AllowedPostLevels = levels + } +} diff --git a/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/reputation/reputation.gno b/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/reputation/reputation.gno new file mode 100644 index 00000000000..3d9d46b7450 --- /dev/null +++ b/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/reputation/reputation.gno @@ -0,0 +1,76 @@ +package pluginreputation + +import "errors" + +const ( + PolicyLinear = iota + PolicyTokenBase +) + +const Name = "reputation" + +var ErrNotSupported = errors.New("reputation not supported") + +type ( + // TODO: Implement reputation plugin + Plugin struct { + Policy int + AllowedPostLevels []int + } + + Reputation struct { + Upvotes uint + Downbotes uint + } +) + +func NewReputationPlugin(o ...Option) Plugin { + var p Plugin + for _, apply := range o { + apply(&p) + } + return p +} + +func (p Plugin) Name() string { + return Name +} + +func (p Plugin) HasReputationSupport(pst *post.Post) bool { + if len(p.AllowedPostLevels) == 0 { + return true + } + + for _, lvl := range p.AllowedPostLevels { + if pst.Level == lvl { + return true + } + } + return false +} + +func (p *Plugin) Votes(pst *post.Post) uint32 { + if !p.HasReputationSupport(pst) { + return ErrNotSupported + } + + // TODO: Implement +} + +func (p *Plugin) Upvote(pst *post.Post) error { + if !p.HasReputationSupport(pst) { + return ErrNotSupported + } + + // TODO: Modify global state + // TODO: Modify local state + r := p.MustGetPluginStorage(p.Name()).(*Reputation) +} + +func (p *Plugin) Downvote(pst *post.Post) error { + if !p.HasReputationSupport(pst) { + return ErrNotSupported + } + + // TODO: Implement +} diff --git a/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/text/text.gno b/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/text/text.gno new file mode 100644 index 00000000000..8b5e8ac08f3 --- /dev/null +++ b/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/text/text.gno @@ -0,0 +1,25 @@ +// plugintext is a content type for representing a tweet, blog post or a thread like Reddit. +package plugintext + +const Name = "post-text" + +type ( + Plugin struct{} + Text struct { + Title string + Body string + Tags []string + } +) + +func New() Plugin { + return Plugin{} +} + +func (p Plugin) Content(pst *post.Post) Content { + return pst.Body[Name].(*Text) +} + +func (p Plugin) SetContent(pst *post.Post, t Text) { + pst.Body[Name] = c +} diff --git a/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/title/title.gno b/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/title/title.gno new file mode 100644 index 00000000000..d4b6a51fd48 --- /dev/null +++ b/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/title/title.gno @@ -0,0 +1,26 @@ +// plugintext is a content type for representing organizations, categories or sections. +package plugintitle + +const Name = "post-title" + +type ( + Plugin struct{} + + Content struct { + Title string + Description string + Tags []string + } +) + +func New() Plugin { + return Plugin{} +} + +func (p Plugin) Content(pst *post.Post) Content { + return pst.Body[Name].(*Content) +} + +func (p Plugin) SetContent(pst *post.Post, c Content) { + pst.Body[Name] = c +} diff --git a/examples/gno.land/p/demo/boardsv2/draft3/post/post.gno b/examples/gno.land/p/demo/boardsv2/draft3/post/post.gno new file mode 100644 index 00000000000..d538a51619d --- /dev/null +++ b/examples/gno.land/p/demo/boardsv2/draft3/post/post.gno @@ -0,0 +1,27 @@ +package post + +import ( + "strconv" + "time" + + "gno.land/p/demo/boards/post/plugin" +) + +type ( + Post struct { + ID string + PluginStore plugin.Plugin + Parent *Post + Level int + Base *Post + Children []*Post + Forks []*Post + UpdatedAt time.Time + CreatedAt time.Time + Creator std.Address + } +) + +func (p Post) NextIncrementalKey(baseKey string) string { + return baseKey + "/" + strconv.Itoa(len(p.Children)) +} diff --git a/examples/gno.land/p/demo/boardsv2/draft3/post/store/cursor.gno b/examples/gno.land/p/demo/boardsv2/draft3/post/store/cursor.gno new file mode 100644 index 00000000000..84696ca42a7 --- /dev/null +++ b/examples/gno.land/p/demo/boardsv2/draft3/post/store/cursor.gno @@ -0,0 +1,8 @@ +package store + +type Cursor struct { + FromID string + Count int +} + +func NewCursor(fromID string, count int) Cursor {} diff --git a/examples/gno.land/p/demo/boardsv2/draft3/post/store/store.gno b/examples/gno.land/p/demo/boardsv2/draft3/post/store/store.gno new file mode 100644 index 00000000000..5c7fba3db4d --- /dev/null +++ b/examples/gno.land/p/demo/boardsv2/draft3/post/store/store.gno @@ -0,0 +1,6 @@ +package store + +// TODO: Define a storage interface and create and avl.Tree wrapper +type Store interface{} + +type AVLTreeStore struct{} // TODO: Use IAVL instead if there is a package implemented diff --git a/examples/gno.land/p/demo/boardsv2/draft3/thread.gno b/examples/gno.land/p/demo/boardsv2/draft3/thread.gno new file mode 100644 index 00000000000..42e731d4fd2 --- /dev/null +++ b/examples/gno.land/p/demo/boardsv2/draft3/thread.gno @@ -0,0 +1,18 @@ +package boards + +type Thread struct { + post.Post + c Context +} + +func (t Thread) TextContent() ThreadTextContent { + +} + +func (t Thread) PollContent() ThreadPollContent {} +func (t Thread) Type() ContentType {} + +// Comments returns a list of comments sent to the thread. +// The comment slice will be non-nil only when Thread is initiated +// through ThreadWithComments. +func (t Thread) Comments() []Comment {} diff --git a/examples/gno.land/r/demo/boardsv2/draft2/main.gno b/examples/gno.land/r/demo/boardsv2/draft2/main.gno new file mode 100644 index 00000000000..6f8d203d31a --- /dev/null +++ b/examples/gno.land/r/demo/boardsv2/draft2/main.gno @@ -0,0 +1,15 @@ +package boards + +var postStore = avl.Tree{} // string(level + timestamp + slug) -> *Post + +func newApp() boards.App { // stateless approach for App struct + return boards.New( + postStore, + boards.MaxCommentDepth(10), + boards.LinearReputationPolicy(), + ) +} + +func Boards(c post.Cursor) ([]boards.Board, error) { + return newApp().Boards(c) +} diff --git a/examples/gno.land/r/demo/boardsv2/draft3/boards.gno b/examples/gno.land/r/demo/boardsv2/draft3/boards.gno new file mode 100644 index 00000000000..d7b678c408e --- /dev/null +++ b/examples/gno.land/r/demo/boardsv2/draft3/boards.gno @@ -0,0 +1,11 @@ +package boards + +var app = boards.New( + store.NewPostStore(), // #TODO: avl.Tree[ string(level + timestamp + slug) -> *Post ] + boards.MaxCommentDepth(10), + boards.LinearReputationPolicy(), +) + +func Boards(c post.Cursor) []boards.Board { + return app.Boards(c) +} From 67360873b7785c097d6b3cfc0eaefbb160d8d6b9 Mon Sep 17 00:00:00 2001 From: jeronimoalbi Date: Wed, 9 Oct 2024 14:46:08 +0200 Subject: [PATCH 14/19] chore: move initial files into draft0 folder MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: İlker G. Öztürk --- examples/gno.land/p/demo/boardsv2/{ => draft0}/app.gno | 0 examples/gno.land/p/demo/boardsv2/{ => draft0}/board.gno | 0 examples/gno.land/p/demo/boardsv2/{ => draft0}/boardsv2.gno | 0 examples/gno.land/p/demo/boardsv2/{ => draft0}/option.gno | 0 .../p/demo/boardsv2/{ => draft0}/post/avlstorage/avlstorage.gno | 0 examples/gno.land/p/demo/boardsv2/{ => draft0}/post/content.gno | 0 examples/gno.land/p/demo/boardsv2/{ => draft0}/post/plugin.gno | 0 .../p/demo/boardsv2/{ => draft0}/post/plugins/content/content.gno | 0 examples/gno.land/p/demo/boardsv2/{ => draft0}/post/post.gno | 0 examples/gno.land/p/demo/boardsv2/{ => draft0}/post/storage.gno | 0 examples/gno.land/p/demo/boardsv2/{ => draft0}/post/view.gno | 0 examples/gno.land/p/demo/boardsv2/{ => draft0}/thread.gno | 0 12 files changed, 0 insertions(+), 0 deletions(-) rename examples/gno.land/p/demo/boardsv2/{ => draft0}/app.gno (100%) rename examples/gno.land/p/demo/boardsv2/{ => draft0}/board.gno (100%) rename examples/gno.land/p/demo/boardsv2/{ => draft0}/boardsv2.gno (100%) rename examples/gno.land/p/demo/boardsv2/{ => draft0}/option.gno (100%) rename examples/gno.land/p/demo/boardsv2/{ => draft0}/post/avlstorage/avlstorage.gno (100%) rename examples/gno.land/p/demo/boardsv2/{ => draft0}/post/content.gno (100%) rename examples/gno.land/p/demo/boardsv2/{ => draft0}/post/plugin.gno (100%) rename examples/gno.land/p/demo/boardsv2/{ => draft0}/post/plugins/content/content.gno (100%) rename examples/gno.land/p/demo/boardsv2/{ => draft0}/post/post.gno (100%) rename examples/gno.land/p/demo/boardsv2/{ => draft0}/post/storage.gno (100%) rename examples/gno.land/p/demo/boardsv2/{ => draft0}/post/view.gno (100%) rename examples/gno.land/p/demo/boardsv2/{ => draft0}/thread.gno (100%) diff --git a/examples/gno.land/p/demo/boardsv2/app.gno b/examples/gno.land/p/demo/boardsv2/draft0/app.gno similarity index 100% rename from examples/gno.land/p/demo/boardsv2/app.gno rename to examples/gno.land/p/demo/boardsv2/draft0/app.gno diff --git a/examples/gno.land/p/demo/boardsv2/board.gno b/examples/gno.land/p/demo/boardsv2/draft0/board.gno similarity index 100% rename from examples/gno.land/p/demo/boardsv2/board.gno rename to examples/gno.land/p/demo/boardsv2/draft0/board.gno diff --git a/examples/gno.land/p/demo/boardsv2/boardsv2.gno b/examples/gno.land/p/demo/boardsv2/draft0/boardsv2.gno similarity index 100% rename from examples/gno.land/p/demo/boardsv2/boardsv2.gno rename to examples/gno.land/p/demo/boardsv2/draft0/boardsv2.gno diff --git a/examples/gno.land/p/demo/boardsv2/option.gno b/examples/gno.land/p/demo/boardsv2/draft0/option.gno similarity index 100% rename from examples/gno.land/p/demo/boardsv2/option.gno rename to examples/gno.land/p/demo/boardsv2/draft0/option.gno diff --git a/examples/gno.land/p/demo/boardsv2/post/avlstorage/avlstorage.gno b/examples/gno.land/p/demo/boardsv2/draft0/post/avlstorage/avlstorage.gno similarity index 100% rename from examples/gno.land/p/demo/boardsv2/post/avlstorage/avlstorage.gno rename to examples/gno.land/p/demo/boardsv2/draft0/post/avlstorage/avlstorage.gno diff --git a/examples/gno.land/p/demo/boardsv2/post/content.gno b/examples/gno.land/p/demo/boardsv2/draft0/post/content.gno similarity index 100% rename from examples/gno.land/p/demo/boardsv2/post/content.gno rename to examples/gno.land/p/demo/boardsv2/draft0/post/content.gno diff --git a/examples/gno.land/p/demo/boardsv2/post/plugin.gno b/examples/gno.land/p/demo/boardsv2/draft0/post/plugin.gno similarity index 100% rename from examples/gno.land/p/demo/boardsv2/post/plugin.gno rename to examples/gno.land/p/demo/boardsv2/draft0/post/plugin.gno diff --git a/examples/gno.land/p/demo/boardsv2/post/plugins/content/content.gno b/examples/gno.land/p/demo/boardsv2/draft0/post/plugins/content/content.gno similarity index 100% rename from examples/gno.land/p/demo/boardsv2/post/plugins/content/content.gno rename to examples/gno.land/p/demo/boardsv2/draft0/post/plugins/content/content.gno diff --git a/examples/gno.land/p/demo/boardsv2/post/post.gno b/examples/gno.land/p/demo/boardsv2/draft0/post/post.gno similarity index 100% rename from examples/gno.land/p/demo/boardsv2/post/post.gno rename to examples/gno.land/p/demo/boardsv2/draft0/post/post.gno diff --git a/examples/gno.land/p/demo/boardsv2/post/storage.gno b/examples/gno.land/p/demo/boardsv2/draft0/post/storage.gno similarity index 100% rename from examples/gno.land/p/demo/boardsv2/post/storage.gno rename to examples/gno.land/p/demo/boardsv2/draft0/post/storage.gno diff --git a/examples/gno.land/p/demo/boardsv2/post/view.gno b/examples/gno.land/p/demo/boardsv2/draft0/post/view.gno similarity index 100% rename from examples/gno.land/p/demo/boardsv2/post/view.gno rename to examples/gno.land/p/demo/boardsv2/draft0/post/view.gno diff --git a/examples/gno.land/p/demo/boardsv2/thread.gno b/examples/gno.land/p/demo/boardsv2/draft0/thread.gno similarity index 100% rename from examples/gno.land/p/demo/boardsv2/thread.gno rename to examples/gno.land/p/demo/boardsv2/draft0/thread.gno From 492f6d21311921a636456ab724101acbfe401c65 Mon Sep 17 00:00:00 2001 From: jeronimoalbi Date: Thu, 10 Oct 2024 12:10:04 +0200 Subject: [PATCH 15/19] wip: code cleanup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: İlker G. Öztürk --- examples/gno.land/p/demo/boardsv2/draft3/app.gno | 2 +- .../gno.land/p/demo/boardsv2/draft3/context.gno | 4 +--- .../draft3/post/plugin/comment/comment.gno | 16 ++++++++++------ .../boardsv2/draft3/post/plugin/lock/lock.gno | 4 ++++ .../boardsv2/draft3/post/plugin/poll/poll.gno | 14 +++++++++++--- .../draft3/post/plugin/reputation/reputation.gno | 6 +++++- .../boardsv2/draft3/post/plugin/text/text.gno | 9 +++++++++ .../boardsv2/draft3/post/plugin/title/title.gno | 9 +++++++++ .../p/demo/boardsv2/draft3/post/store.gno | 5 +++++ .../boardsv2/draft3/{post => }/store/cursor.gno | 0 .../boardsv2/draft3/{post => }/store/store.gno | 0 11 files changed, 55 insertions(+), 14 deletions(-) create mode 100644 examples/gno.land/p/demo/boardsv2/draft3/post/store.gno rename examples/gno.land/p/demo/boardsv2/draft3/{post => }/store/cursor.gno (100%) rename examples/gno.land/p/demo/boardsv2/draft3/{post => }/store/store.gno (100%) diff --git a/examples/gno.land/p/demo/boardsv2/draft3/app.gno b/examples/gno.land/p/demo/boardsv2/draft3/app.gno index cd00c860ce9..32a4054977a 100644 --- a/examples/gno.land/p/demo/boardsv2/draft3/app.gno +++ b/examples/gno.land/p/demo/boardsv2/draft3/app.gno @@ -16,7 +16,7 @@ type App struct { cntx Context } -func New(st storage.PostStorage, o ...Option) App { +func New(st post.Store, o ...Option) App { // TODO: avl.Tree feels wrong here but maybe get rid of the map anyway p := map[string]plugin.Plugin{ plugintitle.Name: plugintitle.New(st), // content for boards diff --git a/examples/gno.land/p/demo/boardsv2/draft3/context.gno b/examples/gno.land/p/demo/boardsv2/draft3/context.gno index 15c31e1db1d..f3139abccb4 100644 --- a/examples/gno.land/p/demo/boardsv2/draft3/context.gno +++ b/examples/gno.land/p/demo/boardsv2/draft3/context.gno @@ -4,16 +4,14 @@ import "plugin" type Context struct { opts []Option - st post.Storage + st post.Store plugs map[post.PluginName]plugin.Plugin } func newContext() Context { - } func (c Context) Plugin(n post.PluginName) post.Plugin { - } func (c Context) Set(p *Post) (updated bool) { diff --git a/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/comment/comment.gno b/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/comment/comment.gno index a14b21204e7..f7b1eed2e5e 100644 --- a/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/comment/comment.gno +++ b/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/comment/comment.gno @@ -4,7 +4,7 @@ const Name = "post-comment" type ( Plugin struct { - posts Storage // TODO: Maybe it should be "post.Storage" or in that line + posts post.Store } // Content is the comment's content. @@ -15,13 +15,17 @@ type ( } ) -func New(st Storage) Plugin { +func New(st post.Store) Plugin { return Plugin{ posts: st, } } -func (p Plugin) Render() string { // TODO: Add render method to other plugins +func (p Plugin) Name() string { + return Name +} + +func (p Plugin) Render() string { // TODO: Implement render support for comments return "" } @@ -36,11 +40,11 @@ func (p Plugin) CreateComment(id string, c Content, level int) *post.Post { } func (p Plugin) Content(pst *post.Post) (_ *Content, ok bool) { - c, ok := pst.PluginStorage[Name].(*Content) + c, ok := pst.PluginStore[Name].(*Content) return c, ok } -func (p Plugin) SetContent(pst *Post, c Content) (updated bool) { - ps.PluginStorage[Name] = c +func (p Plugin) SetContent(pst *post.Post, c Content) (updated bool) { + ps.PluginStore[Name] = c return p.posts.Set(pst) } diff --git a/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/lock/lock.gno b/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/lock/lock.gno index a1e4b554f16..fe75dd364c5 100644 --- a/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/lock/lock.gno +++ b/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/lock/lock.gno @@ -21,6 +21,10 @@ func (p Plugin) Name() string { return Name } +func (p Plugin) Render() string { + return "" +} + func (p *Plugin) Lock(pst *post.Post) error { if !isBoardOrThread(pst) { return ErrInvalidPostType diff --git a/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/poll/poll.gno b/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/poll/poll.gno index 3067397fdbd..a20a4a22dd7 100644 --- a/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/poll/poll.gno +++ b/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/poll/poll.gno @@ -4,7 +4,7 @@ const Name = "post-poll" type ( Plugin struct { - posts post.Storage // TODO: Implement posts storage + posts post.Store } Poll struct { @@ -18,12 +18,20 @@ type ( } ) -func New(st post.Storage) Plugin { +func New(st post.Store) Plugin { return Plugin{ posts: st, } } +func (p Plugin) Name() string { + return Name +} + +func (p Plugin) Render() string { + return "" +} + func (p Plugin) CreatePoll(id string, v Poll) *post.Post { pst := &post.Post{ ID: id, @@ -34,6 +42,6 @@ func (p Plugin) CreatePoll(id string, v Poll) *post.Post { } func (p Plugin) SetPoll(pst *post.Post, v Poll) (updated bool) { - pst.PluginStorage[Name] = v + pst.PluginStore[Name] = v return p.posts.Set(pst.ID, pst) } diff --git a/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/reputation/reputation.gno b/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/reputation/reputation.gno index 3d9d46b7450..b7654b2fc40 100644 --- a/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/reputation/reputation.gno +++ b/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/reputation/reputation.gno @@ -36,6 +36,10 @@ func (p Plugin) Name() string { return Name } +func (p Plugin) Render() string { + return "" +} + func (p Plugin) HasReputationSupport(pst *post.Post) bool { if len(p.AllowedPostLevels) == 0 { return true @@ -64,7 +68,7 @@ func (p *Plugin) Upvote(pst *post.Post) error { // TODO: Modify global state // TODO: Modify local state - r := p.MustGetPluginStorage(p.Name()).(*Reputation) + r := pst.PluginStore[Name].(*Reputation) } func (p *Plugin) Downvote(pst *post.Post) error { diff --git a/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/text/text.gno b/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/text/text.gno index 8b5e8ac08f3..3cb17d7402f 100644 --- a/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/text/text.gno +++ b/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/text/text.gno @@ -16,6 +16,15 @@ func New() Plugin { return Plugin{} } +func (p Plugin) Name() string { + return Name +} + +func (p Plugin) Render() string { + // TODO: Implement render support for text + return "" +} + func (p Plugin) Content(pst *post.Post) Content { return pst.Body[Name].(*Text) } diff --git a/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/title/title.gno b/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/title/title.gno index d4b6a51fd48..506f32001a8 100644 --- a/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/title/title.gno +++ b/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/title/title.gno @@ -17,6 +17,15 @@ func New() Plugin { return Plugin{} } +func (p Plugin) Name() string { + return Name +} + +func (p Plugin) Render() string { + // TODO: Implement render support for title + return "" +} + func (p Plugin) Content(pst *post.Post) Content { return pst.Body[Name].(*Content) } diff --git a/examples/gno.land/p/demo/boardsv2/draft3/post/store.gno b/examples/gno.land/p/demo/boardsv2/draft3/post/store.gno new file mode 100644 index 00000000000..fae4d472dcb --- /dev/null +++ b/examples/gno.land/p/demo/boardsv2/draft3/post/store.gno @@ -0,0 +1,5 @@ +package post + +type Store struct { + // TODO: Implement posts store +} diff --git a/examples/gno.land/p/demo/boardsv2/draft3/post/store/cursor.gno b/examples/gno.land/p/demo/boardsv2/draft3/store/cursor.gno similarity index 100% rename from examples/gno.land/p/demo/boardsv2/draft3/post/store/cursor.gno rename to examples/gno.land/p/demo/boardsv2/draft3/store/cursor.gno diff --git a/examples/gno.land/p/demo/boardsv2/draft3/post/store/store.gno b/examples/gno.land/p/demo/boardsv2/draft3/store/store.gno similarity index 100% rename from examples/gno.land/p/demo/boardsv2/draft3/post/store/store.gno rename to examples/gno.land/p/demo/boardsv2/draft3/store/store.gno From 6975233590d40efd93efae8298a91504fa89250a Mon Sep 17 00:00:00 2001 From: jeronimoalbi Date: Thu, 10 Oct 2024 20:03:18 +0200 Subject: [PATCH 16/19] wip: continue exploring plugins and app ideas --- .../gno.land/p/demo/boardsv2/draft3/app.gno | 92 ++++++++++++------- .../gno.land/p/demo/boardsv2/draft3/board.gno | 39 +++++++- .../p/demo/boardsv2/draft3/option.gno | 19 ---- .../p/demo/boardsv2/draft3/options.gno | 38 ++++++++ .../boardsv2/draft3/post/plugin/plugin.gno | 48 +++++++++- .../draft3/post/plugin/reputation/options.gno | 4 +- .../post/plugin/reputation/reputation.gno | 10 +- .../p/demo/boardsv2/draft3/post/store.gno | 15 ++- .../p/demo/boardsv2/draft3/store/cursor.gno | 1 + .../r/demo/boardsv2/draft3/boards.gno | 60 +++++++++++- 10 files changed, 258 insertions(+), 68 deletions(-) delete mode 100644 examples/gno.land/p/demo/boardsv2/draft3/option.gno create mode 100644 examples/gno.land/p/demo/boardsv2/draft3/options.gno diff --git a/examples/gno.land/p/demo/boardsv2/draft3/app.gno b/examples/gno.land/p/demo/boardsv2/draft3/app.gno index 32a4054977a..ee5bb33d849 100644 --- a/examples/gno.land/p/demo/boardsv2/draft3/app.gno +++ b/examples/gno.land/p/demo/boardsv2/draft3/app.gno @@ -1,9 +1,10 @@ package boards import ( - "plugin" - - "golang.org/x/mod/sumdb/storage" + "gno.land/p/demo/boards/post" + "gno.land/p/demo/boards/post/plugin" + pluginreputation "gno.land/p/demo/boards/post/plugin/reputation" + plugintitle "gno.land/p/demo/boards/post/plugin/title" ) const ( @@ -12,45 +13,65 @@ const ( LevelComment ) +// App is the boards application. type App struct { - cntx Context + posts post.Store + plugins *plugin.Registry + maxCommentsDepth int + disableComments bool + reputationPolicy pluginreputation.Policy } -func New(st post.Store, o ...Option) App { - // TODO: avl.Tree feels wrong here but maybe get rid of the map anyway - p := map[string]plugin.Plugin{ - plugintitle.Name: plugintitle.New(st), // content for boards - plugintext.Name: plugintext.New(st),// content for text based threads - pluginpoll.Name: pluginpoll.New(st),// content for poll based threads - plugincomment.Name: plugincomment.New(st),// content for comments to the threads - pluginreputation.Name: pluginreputation.New( - pluginreputation.UseTokenBasePolicy(), - pluginreputation.AllowedPostLevels(post.LevelPost, post.LevelComment), - ), +// New creates a new boards application. +func New(st post.Store, options ...Option) App { + app := App{posts: st} + for _, apply := range options { + apply(&app) } - return App{ - cntx: Context{ - storage: st, - plugins: p, - }, - } + app.plugins := plugin.NewRegistry( + plugintitle.New(st), // Plugin for boards + plugintext.New(st), // Plugin for text based threads + pluginpoll.New(st), // Plugin for poll based threads + plugincomment.New(st), // Plugin for comments to the threads + pluginreputation.New( + pluginreputation.UsePolicy(app.reputationPolicy), + pluginreputation.AllowedPostLevels(post.LevelPost, post.LevelComment), + ), + ) + return app } -func (a App) Board(path string) (Board, error) { - a.c.Get(level, path func(){}) +func (a App) GetPost(path string) (_ *post.Post, found bool) { + return a.post.Get(path) // NOTE: Allowing this enables us to have generic functions like Lock } -func (a App) Boards(c post.Cursor) ([]Board, error) { - +func (a App) GetBoard(path string) (_ Board, found bool) { + p, found := a.posts.Get(path) + if !found || p.Level != LevelBoard { + return Board{}, false + } + return Board{p}, true } -func (a App) CreateBoard(c BoardContent) (Board, error) {} +func (a App) GetThread(path string) (_ Thread, found bool) { + p, found := a.posts.Get(path) + if !found || p.Level != LevelThread { + return Thread{}, false + } + return Thread{p}, true +} -func (b App) Thread(path string) (Thread, error) { - return ThreadWithComments(path, nil) +func (a App) GetComment(path string) (_ Comment, found bool) { + p, found := a.posts.Get(path) + if !found || p.Level != LevelComment { + return Comment{}, false + } + return Comment{p}, true } +func (a App) CreateBoard(slug, title, description string, tags []string) (Board, error) {} + // Fork forks either a board or a thread by their path. func (a App) Fork(path, newPath string) error {} @@ -60,6 +81,14 @@ func (a App) Fork(path, newPath string) error {} // Once a thread is locked new comments to the thread won't be allowed. func (a App) Lock(path string) error {} +// ---- TODO: Review the following list of app methods ----- // + +func (a App) Boards(c post.Cursor) ([]Board, error) { +} + +func (b App) Thread(path string) (Thread, error) { + return ThreadWithComments(path, nil) +} // ThreadWithComments returns a thread with its comments with the comment depth // configured with commentDepth for direct and child comments. @@ -73,13 +102,14 @@ func (a App) Lock(path string) error {} // By default the configuration is as follows: // - []int{20, 3} func (b App) ThreadWithComments(path string, commentDepth []int) (Thread, error) {} -func (b App) Threads(c post.Cursor) ([]Thread, error) {} -func (b App) CreateTextThread(c ThreadTextContent) (Thread, error) {} -func (b App) CreatePollThread(c ThreadPollContent) (Thread, error) {} +func (b App) Threads(c post.Cursor) ([]Thread, error) {} +func (b App) CreateTextThread(c ThreadTextContent) (Thread, error) {} +func (b App) CreatePollThread(c ThreadPollContent) (Thread, error) {} // parentPath could be a path to thread (root), or path to any of the // nested comments. func (b App) Comments(parentPath string, c Cursor) ([]Comment, error) {} + func (b App) CreateComment(path string, c plugincomment.Content) (Comment, error) { post, err := a.c.Plugin(plugincomment.Name).NewPost(c, LevelComment) if err != nil { diff --git a/examples/gno.land/p/demo/boardsv2/draft3/board.gno b/examples/gno.land/p/demo/boardsv2/draft3/board.gno index ac9258fa629..da90f152b89 100644 --- a/examples/gno.land/p/demo/boardsv2/draft3/board.gno +++ b/examples/gno.land/p/demo/boardsv2/draft3/board.gno @@ -1,14 +1,45 @@ package boards +import ( + pluginreputation "gno.land/p/demo/boards/post/plugin/reputation" + plugintitle "gno.land/p/demo/boards/post/plugin/title" +) + type Board struct { - post.Post - c Context + *post.Post +} + +func (b Board) Title() string { + return b.getContent().Title +} + +func (b Board) Description() string { + return b.getContent().Description +} + +func (b Board) Tags() string { + return b.getContent().Tags +} + +func (b Board) Upvote() error { + r := b.getReputation() + return r.Upvote(b.Post) } -func (b Board) Content() BoardContent { - return b.c.Plugin(pluginbasiccontent.Name).Content(b.Post) +func (b Board) Downvote() error { + r := b.getReputation() + return r.Downvote(b.Post) } func (b Board) Render() string { + c := b.getContent() + return c.Render() +} + +func (b Board) getContent() *plugintitle.Content { + return b.PluginStore[plugintitle.Name].(*plugintitle.Content) +} +func (b Board) getReputation() *pluginreputation.Reputation { + return b.PluginStore[pluginreputation.Name].(*pluginreputation.Reputation) } diff --git a/examples/gno.land/p/demo/boardsv2/draft3/option.gno b/examples/gno.land/p/demo/boardsv2/draft3/option.gno deleted file mode 100644 index 48ab0cc1bff..00000000000 --- a/examples/gno.land/p/demo/boardsv2/draft3/option.gno +++ /dev/null @@ -1,19 +0,0 @@ -package boards - -type Option struct{} - -// LinearReputationPolicy allows upvoting or downvoting a post by one -// for each account. -func LinearReputationPolicy() Option {} - -// TokenBasedReputationPolicy allows upvoting or downvoting a post propotional -// to the specified tokens that an account holds. -func TokenBasedReputationPolicy() Option {} - -// MaxPostDepth configures the max depth for nested comments. -// 0 -> boards -// 1 -> threads -// 2 -> comments-1 (direct comments to the threads) -// The above are already reserved. -// Setting it to zero will disable comments. -func MaxCommentDepth(d int) Option {} diff --git a/examples/gno.land/p/demo/boardsv2/draft3/options.gno b/examples/gno.land/p/demo/boardsv2/draft3/options.gno new file mode 100644 index 00000000000..cbc395e94e0 --- /dev/null +++ b/examples/gno.land/p/demo/boardsv2/draft3/options.gno @@ -0,0 +1,38 @@ +package boards + +import ( + pluginreputation "gno.land/p/demo/boards/post/plugin/reputation" +) + +// Option configures board applications. +type Options func(*App) + +// LinearReputationPolicy allows upvoting or downvoting a post by one for each account. +func LinearReputationPolicy() Option { + return func(a *App) { + a.reputationPolicy = pluginreputation.PolicyLinear + } +} + +// TokenBasedReputationPolicy allows upvoting or downvoting +// a post propotional to the specified tokens that an account holds. +func TokenBasedReputationPolicy() Option { + return func(a *App) { + a.reputationPolicy = pluginreputation.PolicyTokenBased + } +} + +// MaxCommentsDepth configures the max depth for nested comments. +// Setting it to zero allows infinite comments (default). +func MaxCommentsDepth(d int) Option { + return func(a *App) { + a.maxCommentsDepth = d + } +} + +// DisableComments disables comment support. +func DisableComments() Option { + return func(a *App) { + a.disableComments = true + } +} diff --git a/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/plugin.gno b/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/plugin.gno index b350107dbcd..5a42dc8bd10 100644 --- a/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/plugin.gno +++ b/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/plugin.gno @@ -1,8 +1,48 @@ // TODO: Document how plugins work and best practices package plugin -// NOTE: Consider adding lifecycle methods like `Post` creation, deletion, ... -type Plugin interface { - Name() string - Render() string +import ( + "gno.land/p/demo/avl" +) + +type ( + // NOTE: Consider adding lifecycle methods like `Post` creation, deletion, ... + Plugin interface { + Name() string + Render() string + } + + Registry struct { + plugins avl.Tree // string(name) -> Plugin + } +) + +func NewRegistry(plugins ...Plugin) *Registry { + r := &Registry{} + for _, p := range plugins { + r.plugins.Set(p.Name(), p) + } + return r +} + +func (r Registry) Has(name string) bool { + return r.posts.Has(name) +} + +func (r Registry) Get(name string) (_ Plugin, found bool) { + if v, found := r.plugins.Get(name); found { + return v.(Plugin), true + } + return nil, false +} + +func (r *Registry) Add(p Plugin) { + r.plugins.Set(p.Name(), p) +} + +func (r *Registry) Remove(name string) (_ Plugin, removed bool) { + if v, removed := r.plugins.Remove(name, p); removed { + return v.(Plugin), false + } + return nil, false } diff --git a/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/reputation/options.gno b/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/reputation/options.gno index 522f02ee3e3..a87b45bf1e0 100644 --- a/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/reputation/options.gno +++ b/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/reputation/options.gno @@ -2,9 +2,9 @@ package pluginreputation type Option func(*Plugin) -func UseTokenBasePolicy() Option { +func UsePolicy(v Policy) Option { return func(p *Plugin) { - p.Policy = PolicyTokenBase + p.Policy = v } } diff --git a/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/reputation/reputation.gno b/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/reputation/reputation.gno index b7654b2fc40..f62ca3816f6 100644 --- a/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/reputation/reputation.gno +++ b/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/reputation/reputation.gno @@ -3,8 +3,8 @@ package pluginreputation import "errors" const ( - PolicyLinear = iota - PolicyTokenBase + PolicyLinear Policy = iota + PolicyTokenBased ) const Name = "reputation" @@ -12,9 +12,11 @@ const Name = "reputation" var ErrNotSupported = errors.New("reputation not supported") type ( + Policy int + // TODO: Implement reputation plugin Plugin struct { - Policy int + Policy Policy AllowedPostLevels []int } @@ -24,7 +26,7 @@ type ( } ) -func NewReputationPlugin(o ...Option) Plugin { +func New(o ...Option) Plugin { var p Plugin for _, apply := range o { apply(&p) diff --git a/examples/gno.land/p/demo/boardsv2/draft3/post/store.gno b/examples/gno.land/p/demo/boardsv2/draft3/post/store.gno index fae4d472dcb..43762d87867 100644 --- a/examples/gno.land/p/demo/boardsv2/draft3/post/store.gno +++ b/examples/gno.land/p/demo/boardsv2/draft3/post/store.gno @@ -1,5 +1,18 @@ package post +func NewStore() Store { + return Store{} +} + +// TODO: Implement posts store type Store struct { - // TODO: Implement posts store + posts avl.Tree // string(level + creation timestamp + slug) -> *Post + slugs avl.Tree // string(slug) -> *Post +} + +func (s Store) Get(path string) (_ *Post, found bool) { + if v, found := s.slugs.Get(path); found { + return v.(*Post), true + } + return nil, false } diff --git a/examples/gno.land/p/demo/boardsv2/draft3/store/cursor.gno b/examples/gno.land/p/demo/boardsv2/draft3/store/cursor.gno index 84696ca42a7..c4fc379c28d 100644 --- a/examples/gno.land/p/demo/boardsv2/draft3/store/cursor.gno +++ b/examples/gno.land/p/demo/boardsv2/draft3/store/cursor.gno @@ -1,5 +1,6 @@ package store +// TODO: Define how cursors should be used alongside stores type Cursor struct { FromID string Count int diff --git a/examples/gno.land/r/demo/boardsv2/draft3/boards.gno b/examples/gno.land/r/demo/boardsv2/draft3/boards.gno index d7b678c408e..64be90a65a3 100644 --- a/examples/gno.land/r/demo/boardsv2/draft3/boards.gno +++ b/examples/gno.land/r/demo/boardsv2/draft3/boards.gno @@ -1,11 +1,65 @@ package boards +import ( + "std" + + "gno.land/p/demo/boards" + "gno.land/p/demo/boards/post" +) + var app = boards.New( - store.NewPostStore(), // #TODO: avl.Tree[ string(level + timestamp + slug) -> *Post ] + post.NewStore(), boards.MaxCommentDepth(10), boards.LinearReputationPolicy(), ) -func Boards(c post.Cursor) []boards.Board { - return app.Boards(c) +func Render(path string) string { + // TODO: Define how to render the tree of boards, posts and comments + return "" +} + +func CreateBoard(slug, title, description string, tags []string) (path string) { + creator := std.GetOrigCaller() + board := app.CreateBoard(slug, title, description, tags, creator) + return board.ID +} + +func Lock(path string) { + // NOTE: Explore if it's better to use Post or Board/Thread types + post := getBoardOrThread(path) + if post == nil { + panic("path doesn't exist or locking this path not supported") + } + + assertOrigCallerIsCreator(post) + + // NOTE: If app supports generic Post methods API is simpler, otherwise we need LockBoard & LockThread + if err := app.Lock(post); err != nil { + panic(err) + } +} + +func Fork(path, newPath string) { + post := getBoardOrThread(path) + if post == nil { + panic("path doesn't exist or forking this path not supported") + } + + if err := app.Fork(post, newPath); err != nil { + panic(err) + } +} + +func getBoardOrThread(path string) *post.Post { + post := app.GetBoard(path) + if post == nil { + post = app.GetThread(path) + } + return post +} + +func assertOrigCallerIsCreator(p *post.Post) { + if post.Creator != std.GetOrigCaller() { + panic("original caller is not allowed to perform this action") + } } From cecf7a807ced6bd16099ecafefd4a1fbf5421e60 Mon Sep 17 00:00:00 2001 From: jeronimoalbi Date: Thu, 10 Oct 2024 20:06:36 +0200 Subject: [PATCH 17/19] chore: remove comment --- examples/gno.land/r/demo/boardsv2/draft3/boards.gno | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/gno.land/r/demo/boardsv2/draft3/boards.gno b/examples/gno.land/r/demo/boardsv2/draft3/boards.gno index 64be90a65a3..ed4f2adbd42 100644 --- a/examples/gno.land/r/demo/boardsv2/draft3/boards.gno +++ b/examples/gno.land/r/demo/boardsv2/draft3/boards.gno @@ -33,7 +33,6 @@ func Lock(path string) { assertOrigCallerIsCreator(post) - // NOTE: If app supports generic Post methods API is simpler, otherwise we need LockBoard & LockThread if err := app.Lock(post); err != nil { panic(err) } From 0f48ffb63176ade7a91bf0d6fe4fc8a5b598904a Mon Sep 17 00:00:00 2001 From: jeronimoalbi Date: Tue, 29 Oct 2024 16:52:36 +0100 Subject: [PATCH 18/19] wip: improvements These changes were done before the change of direction for the boards implementation. Commiting them to avoid loosing the ideas and improvements present here. --- .../gno.land/p/demo/boardsv2/draft3/app.gno | 20 +++++-- .../gno.land/p/demo/boardsv2/draft3/board.gno | 25 +++++--- .../boardsv2/draft3/post/plugin/fork/fork.gno | 47 +++++++++++++++ .../draft3/post/plugin/fork/options.gno | 9 +++ .../post/plugin/reputation/reputation.gno | 33 +++++++--- .../draft3/post/plugin/reputation/store.gno | 58 ++++++++++++++++++ .../boardsv2/draft3/post/plugin/text/text.gno | 12 ++-- .../p/demo/boardsv2/draft3/post/post.gno | 26 ++++---- .../p/demo/boardsv2/draft3/thread.gno | 60 ++++++++++++++++--- .../r/demo/boardsv2/draft3/boards.gno | 14 +++-- 10 files changed, 252 insertions(+), 52 deletions(-) create mode 100644 examples/gno.land/p/demo/boardsv2/draft3/post/plugin/fork/fork.gno create mode 100644 examples/gno.land/p/demo/boardsv2/draft3/post/plugin/fork/options.gno create mode 100644 examples/gno.land/p/demo/boardsv2/draft3/post/plugin/reputation/store.gno diff --git a/examples/gno.land/p/demo/boardsv2/draft3/app.gno b/examples/gno.land/p/demo/boardsv2/draft3/app.gno index ee5bb33d849..991678ac608 100644 --- a/examples/gno.land/p/demo/boardsv2/draft3/app.gno +++ b/examples/gno.land/p/demo/boardsv2/draft3/app.gno @@ -3,6 +3,8 @@ package boards import ( "gno.land/p/demo/boards/post" "gno.land/p/demo/boards/post/plugin" + pluginfork "gno.land/p/demo/boards/post/plugin/fork" + pluginpoll "gno.land/p/demo/boards/post/plugin/poll" pluginreputation "gno.land/p/demo/boards/post/plugin/reputation" plugintitle "gno.land/p/demo/boards/post/plugin/title" ) @@ -38,14 +40,13 @@ func New(st post.Store, options ...Option) App { pluginreputation.UsePolicy(app.reputationPolicy), pluginreputation.AllowedPostLevels(post.LevelPost, post.LevelComment), ), + pluginfork.New( + pluginfork.AllowedPostLevels(post.LevelPost), + ), ) return app } -func (a App) GetPost(path string) (_ *post.Post, found bool) { - return a.post.Get(path) // NOTE: Allowing this enables us to have generic functions like Lock -} - func (a App) GetBoard(path string) (_ Board, found bool) { p, found := a.posts.Get(path) if !found || p.Level != LevelBoard { @@ -73,7 +74,16 @@ func (a App) GetComment(path string) (_ Comment, found bool) { func (a App) CreateBoard(slug, title, description string, tags []string) (Board, error) {} // Fork forks either a board or a thread by their path. -func (a App) Fork(path, newPath string) error {} +func (a App) ForkBoard(b Board, newPath string) error { + // NOTE: Instead of `app.ForkBoard()` we could use `b.Fork(newPath)` instead but that requires Board to have plugin access + // NOTE: This case gets the plugin from the plugin list to fork + p, _ := a.plugins.Get(pluginfork.Name) + return p.Fork(b.Post, newPath) +} + +func (a App) ForkThread(t Thread, newPath string) error { + // TODO: Implement thread fork app support +} // Lock locks either a board or a thread by their path. // Once a board is locked new threads to the board and comments to the existing diff --git a/examples/gno.land/p/demo/boardsv2/draft3/board.gno b/examples/gno.land/p/demo/boardsv2/draft3/board.gno index da90f152b89..b75db758a79 100644 --- a/examples/gno.land/p/demo/boardsv2/draft3/board.gno +++ b/examples/gno.land/p/demo/boardsv2/draft3/board.gno @@ -1,24 +1,31 @@ package boards import ( + "gno.land/p/demo/boards/post" + pluginfork "gno.land/p/demo/boards/post/plugin/fork" pluginreputation "gno.land/p/demo/boards/post/plugin/reputation" plugintitle "gno.land/p/demo/boards/post/plugin/title" ) -type Board struct { - *post.Post -} +type ( + BoardContent plugintitle.Content + + Board struct { + *post.Post + } +) -func (b Board) Title() string { - return b.getContent().Title +func NewBoard(pst *post.Post) Board { + // TODO: Local plugins must be initialized here (same for other plugins) + return Board{pst} } -func (b Board) Description() string { - return b.getContent().Description +func (b Board) Info() BoardContent { + return BoardContent(b.getContent()) } -func (b Board) Tags() string { - return b.getContent().Tags +func (b Board) Update(c BoardContent) { + b.PluginStore[plugintitle.Name] = plugintitle.Content(c) } func (b Board) Upvote() error { diff --git a/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/fork/fork.gno b/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/fork/fork.gno new file mode 100644 index 00000000000..543f18b6e8d --- /dev/null +++ b/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/fork/fork.gno @@ -0,0 +1,47 @@ +package pluginfork + +import ( + "gno.land/p/demo/boards/post" +) + +const Name = "fork" + +// TODO: Implement fork plugin to support thread forking +type Plugin struct { + AllowedPostLevels []int +} + +func New(o ...Option) Plugin { + var p Plugin + for _, apply := range o { + apply(&p) + } + return p +} + +func (p Plugin) Name() string { + return Name +} + +func (p Plugin) Render() string { + // TODO: Implement render support for text + return "" +} + +func (p Plugin) HasForkSupport(pst *post.Post) bool { + if len(p.AllowedPostLevels) == 0 { + return true + } + + for _, lvl := range p.AllowedPostLevels { + if pst.Level == lvl { + return true + } + } + return false +} + +func (p Plugin) Fork(pst *post.Post, newPath string) error { + // TODO: Implement fork support + return nil +} diff --git a/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/fork/options.gno b/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/fork/options.gno new file mode 100644 index 00000000000..85fe888bcb1 --- /dev/null +++ b/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/fork/options.gno @@ -0,0 +1,9 @@ +package pluginfork + +type Option func(*Plugin) + +func AllowedPostLevels(levels []int) Option { + return func(p *Plugin) { + p.AllowedPostLevels = levels + } +} diff --git a/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/reputation/reputation.gno b/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/reputation/reputation.gno index f62ca3816f6..0ec8e1406af 100644 --- a/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/reputation/reputation.gno +++ b/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/reputation/reputation.gno @@ -1,7 +1,11 @@ package pluginreputation -import "errors" +import ( + "errors" + "std" +) +// NOTE: Think about implementing a reputation based policy const ( PolicyLinear Policy = iota PolicyTokenBased @@ -14,15 +18,15 @@ var ErrNotSupported = errors.New("reputation not supported") type ( Policy int - // TODO: Implement reputation plugin Plugin struct { + Store Store Policy Policy AllowedPostLevels []int } Reputation struct { Upvotes uint - Downbotes uint + Downvotes uint } ) @@ -55,12 +59,21 @@ func (p Plugin) HasReputationSupport(pst *post.Post) bool { return false } -func (p *Plugin) Votes(pst *post.Post) uint32 { +func (p Plugin) Votes(pst *post.Post) (upvotes uint64, downvotes uint64) { if !p.HasReputationSupport(pst) { return ErrNotSupported } - // TODO: Implement + r := pst.PluginStore[Name].(*Reputation) + return r.Upvotes, r.Downvotes +} + +func (p Plugin) Voters(pst *post.Post) []std.Address { + if !p.HasReputationSupport(pst) { + return ErrNotSupported + } + + // TODO: Implement support for tracking voters } func (p *Plugin) Upvote(pst *post.Post) error { @@ -68,9 +81,10 @@ func (p *Plugin) Upvote(pst *post.Post) error { return ErrNotSupported } - // TODO: Modify global state - // TODO: Modify local state + // TODO: Handle accounts and change downvotes for existing accounts that downvoted r := pst.PluginStore[Name].(*Reputation) + r.Upvotes++ + p.store.inc(pst.ID) } func (p *Plugin) Downvote(pst *post.Post) error { @@ -78,5 +92,8 @@ func (p *Plugin) Downvote(pst *post.Post) error { return ErrNotSupported } - // TODO: Implement + // TODO: Handle accounts and change upvotes for existing accounts that upvoted + r := pst.PluginStore[Name].(*Reputation) + r.Downvotes++ + p.store.dec(pst.ID) } diff --git a/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/reputation/store.gno b/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/reputation/store.gno new file mode 100644 index 00000000000..9661210f830 --- /dev/null +++ b/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/reputation/store.gno @@ -0,0 +1,58 @@ +package pluginreputation + +import ( + "gno.land/p/demo/seqid" +) + +type ( + VotesIterFn = func(votes uint64, path string) bool + + Store struct { + votes avl.Tree // string(count) -> string(path) + } +) + +func (s Store) Iterate(fn VotesIterFn) bool { + // TODO: Support pagination of votes? + return s.votes.Iterate("", "", func(key string, v interface{}) bool { + count, _ := seqid.FromBinary(key) + return fn(uint64(count), v.(string)) + }) +} + +func (s *Store) inc(path string) uint64 { + var ( + current seqid.ID + v, found = s.votes.Get(path) + ) + if found { + current = v.(seqid.ID) + // TODO: Implement the right solution because this is not right, just showcase + s.votes.Remove(current.Binary()) + } + + current.Next() + s.votes.Set(current.Binary(), path) + return uint64(current) +} + +func (s *Store) dec(path string) uint64 { + var ( + current seqid.ID + v, found = s.votes.Get(path) + ) + if found { + current = v.(seqid.ID) + } + + if current == 0 { + return current + } + + s.votes.Remove(current.Binary()) + current-- + if current != 0 { + s.votes.Set(current.Binary(), current) + } + return uint64(current) +} diff --git a/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/text/text.gno b/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/text/text.gno index 3cb17d7402f..014956e367b 100644 --- a/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/text/text.gno +++ b/examples/gno.land/p/demo/boardsv2/draft3/post/plugin/text/text.gno @@ -1,11 +1,15 @@ // plugintext is a content type for representing a tweet, blog post or a thread like Reddit. package plugintext +import ( + "gno.land/p/demo/boards/post" // NOTE: Plugins should be at the same level of post package +) + const Name = "post-text" type ( - Plugin struct{} - Text struct { + Plugin struct{} + Content struct { Title string Body string Tags []string @@ -26,9 +30,9 @@ func (p Plugin) Render() string { } func (p Plugin) Content(pst *post.Post) Content { - return pst.Body[Name].(*Text) + return pst.Body[Name].(*Content) } -func (p Plugin) SetContent(pst *post.Post, t Text) { +func (p Plugin) SetContent(pst *post.Post, c Content) { pst.Body[Name] = c } diff --git a/examples/gno.land/p/demo/boardsv2/draft3/post/post.gno b/examples/gno.land/p/demo/boardsv2/draft3/post/post.gno index d538a51619d..229a904fd66 100644 --- a/examples/gno.land/p/demo/boardsv2/draft3/post/post.gno +++ b/examples/gno.land/p/demo/boardsv2/draft3/post/post.gno @@ -7,20 +7,18 @@ import ( "gno.land/p/demo/boards/post/plugin" ) -type ( - Post struct { - ID string - PluginStore plugin.Plugin - Parent *Post - Level int - Base *Post - Children []*Post - Forks []*Post - UpdatedAt time.Time - CreatedAt time.Time - Creator std.Address - } -) +type Post struct { + ID string + PluginStore plugin.Plugin + Parent *Post + Level int + Base *Post + Children []*Post + Forks []*Post + UpdatedAt time.Time + CreatedAt time.Time + Creator std.Address +} func (p Post) NextIncrementalKey(baseKey string) string { return baseKey + "/" + strconv.Itoa(len(p.Children)) diff --git a/examples/gno.land/p/demo/boardsv2/draft3/thread.gno b/examples/gno.land/p/demo/boardsv2/draft3/thread.gno index 42e731d4fd2..cce35799b6f 100644 --- a/examples/gno.land/p/demo/boardsv2/draft3/thread.gno +++ b/examples/gno.land/p/demo/boardsv2/draft3/thread.gno @@ -1,18 +1,64 @@ package boards -type Thread struct { - post.Post - c Context +import ( + "gno.land/p/demo/boards/post" + pluginfork "gno.land/p/demo/boards/post/plugin/fork" + pluginpoll "gno.land/p/demo/boards/post/plugin/poll" + pluginreputation "gno.land/p/demo/boards/post/plugin/reputation" + plugintext "gno.land/p/demo/boards/post/plugin/text" +) + +type ( + ThreadContent plugintext.Content + + // TODO: Should polls be handler within this type? + Thread struct { + *post.Post + } +) + +func (t Thread) Info() ThreadContent { + return ThreadContent(t.getContent()) +} + +func (t Thread) Update(c ThreadContent) { + t.PluginStore[plugintext.Name] = plugintext.Content(c) } -func (t Thread) TextContent() ThreadTextContent { +func (t Thread) Upvote() error { + r := t.getReputation() + return r.Upvote(t.Post) +} +func (t Thread) Downvote() error { + r := t.getReputation() + return r.Downvote(t.Post) } -func (t Thread) PollContent() ThreadPollContent {} -func (t Thread) Type() ContentType {} +func (t Thread) Fork(newPath string) error { + f := t.getFork() + return f.Fork(t.Post) +} + +func (t Thread) Render() string { + c := t.getContent() + return c.Render() +} // Comments returns a list of comments sent to the thread. // The comment slice will be non-nil only when Thread is initiated // through ThreadWithComments. -func (t Thread) Comments() []Comment {} +// TODO: Add support to get sub-threads (any type) and comments +// func (t Thread) Comments() []Comment {} + +func (t Thread) getContent() *plugintext.Content { + return t.PluginStore[plugintext.Name].(*plugintext.Content) +} + +func (t Thread) getReputation() *pluginreputation.Reputation { + return t.PluginStore[pluginreputation.Name].(*pluginreputation.Reputation) +} + +func (t Thread) getFork() *pluginfork.Fork { + return t.PluginStore[pluginfork.Name].(*pluginfork.Fork) +} diff --git a/examples/gno.land/r/demo/boardsv2/draft3/boards.gno b/examples/gno.land/r/demo/boardsv2/draft3/boards.gno index ed4f2adbd42..efc316fa458 100644 --- a/examples/gno.land/r/demo/boardsv2/draft3/boards.gno +++ b/examples/gno.land/r/demo/boardsv2/draft3/boards.gno @@ -25,7 +25,6 @@ func CreateBoard(slug, title, description string, tags []string) (path string) { } func Lock(path string) { - // NOTE: Explore if it's better to use Post or Board/Thread types post := getBoardOrThread(path) if post == nil { panic("path doesn't exist or locking this path not supported") @@ -33,6 +32,7 @@ func Lock(path string) { assertOrigCallerIsCreator(post) + // NOTE: Explore if it's better to use Post or Board/Thread types if err := app.Lock(post); err != nil { panic(err) } @@ -44,17 +44,21 @@ func Fork(path, newPath string) { panic("path doesn't exist or forking this path not supported") } + // TODO: Use this way + app.ForkBoard(board) + app.ForkThread(thread) + if err := app.Fork(post, newPath); err != nil { panic(err) } } func getBoardOrThread(path string) *post.Post { - post := app.GetBoard(path) - if post == nil { - post = app.GetThread(path) + p, found := app.GetPost(path) + if found && (p.Level == boards.LevelBoard || p.Level == boards.LevelThread) { + return p } - return post + return nil } func assertOrigCallerIsCreator(p *post.Post) { From 9a60ee5ffb452fca424d95daa48725ca992e5061 Mon Sep 17 00:00:00 2001 From: jeronimoalbi Date: Mon, 4 Nov 2024 17:48:19 +0100 Subject: [PATCH 19/19] chore: address review comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: İlker G. Öztürk --- .../gno.land/p/demo/boardsv2/draft3/app.gno | 18 ++++++++++-------- .../p/demo/boardsv2/draft3/options.gno | 9 +-------- .../p/demo/boardsv2/draft3/post/store.gno | 13 +++++++++++++ 3 files changed, 24 insertions(+), 16 deletions(-) diff --git a/examples/gno.land/p/demo/boardsv2/draft3/app.gno b/examples/gno.land/p/demo/boardsv2/draft3/app.gno index 991678ac608..3893e0866f7 100644 --- a/examples/gno.land/p/demo/boardsv2/draft3/app.gno +++ b/examples/gno.land/p/demo/boardsv2/draft3/app.gno @@ -20,13 +20,15 @@ type App struct { posts post.Store plugins *plugin.Registry maxCommentsDepth int - disableComments bool reputationPolicy pluginreputation.Policy } // New creates a new boards application. func New(st post.Store, options ...Option) App { - app := App{posts: st} + app := App{ + posts: st, + maxCommentsDepth: -1, // Infinite number of comments + } for _, apply := range options { apply(&app) } @@ -48,24 +50,24 @@ func New(st post.Store, options ...Option) App { } func (a App) GetBoard(path string) (_ Board, found bool) { - p, found := a.posts.Get(path) - if !found || p.Level != LevelBoard { + p, found := a.posts.GetByLevel(path, LevelBoard) + if !found { return Board{}, false } return Board{p}, true } func (a App) GetThread(path string) (_ Thread, found bool) { - p, found := a.posts.Get(path) - if !found || p.Level != LevelThread { + p, found := a.posts.GetByLevel(path, LevelThread) + if !found { return Thread{}, false } return Thread{p}, true } func (a App) GetComment(path string) (_ Comment, found bool) { - p, found := a.posts.Get(path) - if !found || p.Level != LevelComment { + p, found := a.posts.GetByLevel(path, LevelComment) + if !found { return Comment{}, false } return Comment{p}, true diff --git a/examples/gno.land/p/demo/boardsv2/draft3/options.gno b/examples/gno.land/p/demo/boardsv2/draft3/options.gno index cbc395e94e0..906b1130663 100644 --- a/examples/gno.land/p/demo/boardsv2/draft3/options.gno +++ b/examples/gno.land/p/demo/boardsv2/draft3/options.gno @@ -23,16 +23,9 @@ func TokenBasedReputationPolicy() Option { } // MaxCommentsDepth configures the max depth for nested comments. -// Setting it to zero allows infinite comments (default). +// Setting it to -1 allows an infinite number of nested comments (default). func MaxCommentsDepth(d int) Option { return func(a *App) { a.maxCommentsDepth = d } } - -// DisableComments disables comment support. -func DisableComments() Option { - return func(a *App) { - a.disableComments = true - } -} diff --git a/examples/gno.land/p/demo/boardsv2/draft3/post/store.gno b/examples/gno.land/p/demo/boardsv2/draft3/post/store.gno index 43762d87867..49513f250db 100644 --- a/examples/gno.land/p/demo/boardsv2/draft3/post/store.gno +++ b/examples/gno.land/p/demo/boardsv2/draft3/post/store.gno @@ -16,3 +16,16 @@ func (s Store) Get(path string) (_ *Post, found bool) { } return nil, false } + +func (s Store) GetByLevel(path string, level int) (_ *Post, found bool) { + v, found := s.slugs.Get(path) + if !found { + return nil, false + } + + p := v.(*Post) + if p.Level != level { + return nil, false + } + return p, true +}