Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat(boards2): implement freeze and unfreeze for thread and reply #3829

Open
wants to merge 2 commits into
base: devx/feature/boardsv2
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions examples/gno.land/r/nt/boards2/v1/freeze.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package boards2

import "std"

type freezeArgs struct {
boardID BoardID
threadID PostID
replyID PostID
}

func (args freezeArgs) getThread() (*Post, *Board) {
board := mustGetBoard(args.boardID)
assertBoardIsNotFrozen(board)

thread := mustGetThread(board, args.threadID)
return thread, board
}

func (args freezeArgs) getReply() (*Post, *Board) {
board := mustGetBoard(args.boardID)
assertBoardIsNotFrozen(board)

thread := mustGetThread(board, args.threadID)
assertThreadVisible(thread)
assertThreadIsNotFrozen(thread)

reply := mustGetReply(thread, args.replyID)
assertReplyVisible(reply)
return reply, board
}

func setReplyReadOnly(args freezeArgs, isReadOnly bool) {
reply, board := args.getReply()

if isReadOnly {
// disallow freezing already frozen reply
assertReplyIsNotFrozen(reply)
}

caller := std.OriginCaller()
permArgs := Args{args.boardID, args.threadID, args.replyID}
board.perms.WithPermission(caller, PermissionReplyFreeze, permArgs, func(Args) {
reply, _ := args.getReply()
reply.SetReadOnly(isReadOnly)
})
}

func setThreadReadOnly(args freezeArgs, isReadOnly bool) {
thread, board := args.getThread()

if isReadOnly {
// disallow freezing of already frozen thread
assertThreadIsNotFrozen(thread)
}

caller := std.OriginCaller()
permArgs := Args{args.boardID, args.threadID}
board.perms.WithPermission(caller, PermissionThreadFreeze, permArgs, func(Args) {
thread, _ := args.getThread()
thread.SetReadOnly(isReadOnly)
})
}

func setBoardReadOnly(boardID BoardID, isReadOnly bool) {
board := mustGetBoard(boardID)
if isReadOnly {
assertBoardIsNotFrozen(board)
}

caller := std.OriginCaller()
args := Args{boardID}
board.perms.WithPermission(caller, PermissionBoardFreeze, args, func(Args) {
board := mustGetBoard(boardID)
board.SetReadOnly(isReadOnly)
})
}
2 changes: 2 additions & 0 deletions examples/gno.land/r/nt/boards2/v1/permissions.gno
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ const (
PermissionThreadCreate = "thread:create"
PermissionThreadEdit = "thread:edit"
PermissionThreadDelete = "thread:delete"
PermissionThreadFreeze = "thread:freeze"
PermissionThreadFlag = "thread:flag"
PermissionThreadRepost = "thread:repost"
PermissionReplyCreate = "reply:create"
PermissionReplyDelete = "reply:delete"
PermissionReplyFlag = "reply:flag"
PermissionReplyFreeze = "reply:freeze"
PermissionMemberInvite = "member:invite"
PermissionMemberRemove = "member:remove"
PermissionRoleChange = "role:change"
Expand Down
9 changes: 9 additions & 0 deletions examples/gno.land/r/nt/boards2/v1/post.gno
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ type Post struct {
title string // optional
body string
isHidden bool
isReadOnly bool
replies avl.Tree // Post.id -> *Post
repliesAll avl.Tree // Post.id -> *Post (all replies, for top-level posts)
reposts avl.Tree // Board.id -> Post.id
Expand Down Expand Up @@ -126,6 +127,14 @@ func (post *Post) IsHidden() bool {
return post.isHidden
}

func (post *Post) SetReadOnly(isReadOnly bool) {
post.isReadOnly = isReadOnly
}

func (post *Post) IsReadOnly() bool {
return post.isReadOnly
}

func (post *Post) AddReply(creator std.Address, body string) *Post {
board := post.board
pid := board.incGetPostID()
Expand Down
118 changes: 108 additions & 10 deletions examples/gno.land/r/nt/boards2/v1/public.gno
Original file line number Diff line number Diff line change
Expand Up @@ -88,17 +88,12 @@ func RenameBoard(name, newName string) {

// FreezeBoard freezes a board so no more threads and comments can be created or modified.
func FreezeBoard(boardID BoardID) {
board := mustGetBoard(boardID)
assertBoardIsNotFrozen(board)

caller := std.OriginCaller()
args := Args{boardID}
board.perms.WithPermission(caller, PermissionBoardFreeze, args, func(Args) {
board := mustGetBoard(boardID)
assertBoardIsNotFrozen(board)
setBoardReadOnly(boardID, true)
}

board.SetReadOnly(true)
})
// UnfreezeBoard removes frozen status from a board.
func UnfreezeBoard(boardID BoardID) {
setBoardReadOnly(boardID, false)
}

// IsBoardFrozen checks if a board has been frozen.
Expand All @@ -107,6 +102,81 @@ func IsBoardFrozen(boardID BoardID) bool {
return board.IsReadOnly()
}

// FreezeThread freezes a thread so thread cannot be replied, modified or deleted.
//
// Fails if board is frozen.
func FreezeThread(boardID BoardID, threadID PostID) {
args := freezeArgs{
boardID: boardID,
threadID: threadID,
}

setThreadReadOnly(args, true)
}

// UnfreezeThread removes frozen status from a thread.
//
// Fails if board is frozen.
func UnfreezeThread(boardID BoardID, threadID PostID) {
args := freezeArgs{
boardID: boardID,
threadID: threadID,
}

setThreadReadOnly(args, false)
}

// IsThreadFrozen checks if a thread has been frozen.
//
// Returns true if board is frozen.
func IsThreadFrozen(boardID BoardID, threadID PostID) bool {
board := mustGetBoard(boardID)
thread := mustGetThread(board, threadID)
assertThreadVisible(thread)

return board.IsReadOnly() || thread.IsReadOnly()
}

// IsReplyFrozen checks if a thread reply has been frozen.
//
// Returns true when board or a parent thread is frozen.
func IsReplyFrozen(boardID BoardID, threadID, replyID PostID) bool {
board := mustGetBoard(boardID)
thread := mustGetThread(board, threadID)
assertThreadVisible(thread)

reply := mustGetReply(thread, replyID)
assertReplyVisible(reply)

return board.IsReadOnly() || thread.IsReadOnly() || reply.IsReadOnly()
}

// UnfreezeReply removes frozen status from a reply.
//
// Fails when parent thread or board are frozen.
func UnfreezeReply(boardID BoardID, threadID, replyID PostID) {
args := freezeArgs{
boardID: boardID,
threadID: threadID,
replyID: replyID,
}

setReplyReadOnly(args, false)
}

// FreezeReply freezes a thread reply so it cannot be modified or deleted.
//
// Fails when parent thread or board are frozen.
func FreezeReply(boardID BoardID, threadID, replyID PostID) {
args := freezeArgs{
boardID: boardID,
threadID: threadID,
replyID: replyID,
}

setReplyReadOnly(args, true)
}

// SetFlaggingThreshold sets the number of flags required to hide a thread or comment.
//
// Threshold is only applicable within the board where it's setted.
Expand Down Expand Up @@ -148,6 +218,7 @@ func FlagThread(boardID BoardID, threadID PostID, reason string) {
if !ok {
panic("post doesn't exist")
}
assertThreadIsNotFrozen(t)

f := Flag{
User: caller,
Expand Down Expand Up @@ -192,6 +263,7 @@ func CreateReply(boardID BoardID, threadID, replyID PostID, body string) PostID

thread := mustGetThread(board, threadID)
assertThreadVisible(thread)
assertThreadIsNotFrozen(thread)

var reply *Post
if replyID == 0 {
Expand All @@ -201,6 +273,7 @@ func CreateReply(boardID BoardID, threadID, replyID PostID, body string) PostID
// Try to get parent reply and add a new child reply
post := mustGetReply(thread, replyID)
assertReplyVisible(post)
assertReplyIsNotFrozen(post)

reply = post.AddReply(caller, body)
}
Expand All @@ -219,7 +292,10 @@ func FlagReply(boardID BoardID, threadID, replyID PostID, reason string) {
assertHasBoardPermission(board, caller, PermissionThreadFlag)

thread := mustGetThread(board, threadID)
assertThreadIsNotFrozen(thread)

reply := mustGetReply(thread, replyID)
assertReplyIsNotFrozen(thread)

f := Flag{
User: caller,
Expand Down Expand Up @@ -255,6 +331,8 @@ func DeleteThread(boardID BoardID, threadID PostID) {

caller := std.OriginCaller()
thread := mustGetThread(board, threadID)
assertThreadIsNotFrozen(thread)

if caller != thread.GetCreator() {
assertHasBoardPermission(board, caller, PermissionThreadDelete)
}
Expand All @@ -273,8 +351,11 @@ func DeleteReply(boardID BoardID, threadID, replyID PostID) {
assertBoardIsNotFrozen(board)

thread := mustGetThread(board, threadID)
assertThreadIsNotFrozen(thread)

reply := mustGetReply(thread, replyID)
assertReplyVisible(reply)
assertReplyIsNotFrozen(reply)

caller := std.OriginCaller()
if caller != reply.GetCreator() {
Expand Down Expand Up @@ -304,6 +385,8 @@ func EditThread(boardID BoardID, threadID PostID, title, body string) {
assertBoardIsNotFrozen(board)

thread := mustGetThread(board, threadID)
assertThreadIsNotFrozen(thread)

caller := std.OriginCaller()
if caller != thread.GetCreator() {
assertHasBoardPermission(board, caller, PermissionThreadEdit)
Expand All @@ -323,8 +406,11 @@ func EditReply(boardID BoardID, threadID, replyID PostID, body string) {
assertBoardIsNotFrozen(board)

thread := mustGetThread(board, threadID)
assertThreadIsNotFrozen(thread)

reply := mustGetReply(thread, replyID)
assertReplyVisible(reply)
assertReplyIsNotFrozen(reply)

if std.OriginCaller() != reply.GetCreator() {
panic("only the reply creator is allowed to edit it")
Expand Down Expand Up @@ -434,6 +520,18 @@ func assertBoardIsNotFrozen(b *Board) {
}
}

func assertThreadIsNotFrozen(t *Post) {
if t.IsReadOnly() {
panic("thread is frozen")
}
}

func assertReplyIsNotFrozen(r *Post) {
if r.IsReadOnly() {
panic("reply is frozen")
}
}

func assertNameIsNotEmpty(name string) {
if name == "" {
panic("name is empty")
Expand Down
30 changes: 30 additions & 0 deletions examples/gno.land/r/nt/boards2/v1/z_19_a_filetest.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package main

import (
"std"

boards2 "gno.land/r/nt/boards2/v1"
)

const owner = std.Address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") // @test1

var (
bid boards2.BoardID
tid boards2.PostID
)

func init() {
std.TestSetOriginCaller(owner)
bid = boards2.CreateBoard("test123")
tid = boards2.CreateThread(bid, "foo", "bar")

boards2.FreezeBoard(bid)
}

func main() {
// Attempt to freeze a thread on frozen board
boards2.FreezeThread(bid, tid)
}

// Error:
// board is frozen
30 changes: 30 additions & 0 deletions examples/gno.land/r/nt/boards2/v1/z_19_b_filetest.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package main

import (
"std"

boards2 "gno.land/r/nt/boards2/v1"
)

const owner = std.Address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") // @test1

var (
bid boards2.BoardID
tid boards2.PostID
)

func init() {
std.TestSetOriginCaller(owner)
bid = boards2.CreateBoard("test123")
tid = boards2.CreateThread(bid, "foo", "bar")

boards2.FreezeThread(bid, tid)
}

func main() {
// Attempt to freeze a frozen thread
boards2.FreezeThread(bid, tid)
}

// Error:
// thread is frozen
Loading
Loading