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): board renaming #3462

Merged
3 changes: 2 additions & 1 deletion examples/gno.land/r/demo/boards2/board.gno
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ func (id BoardID) Key() string {
type Board struct {
id BoardID // only set for public boards.
name string
aliases []string
creator std.Address
threads avl.Tree // Post.id -> *Post
postsCtr uint64 // increments Post.id
Expand Down Expand Up @@ -135,7 +136,7 @@ func createDefaultBoardPermissions(owner std.Address) *DefaultPermissions {
return NewDefaultPermissions(
admindao.New(admindao.WithMember(owner)),
WithSuperRole(RoleOwner),
WithRole(RoleAdmin, PermissionMemberInvite),
WithRole(RoleAdmin, PermissionMemberInvite, PermissionBoardRename),
// TODO: Finish assigning all roles and permissions
// WithRole(RoleModerator, permissions...),
WithUser(owner, RoleOwner),
Expand Down
9 changes: 9 additions & 0 deletions examples/gno.land/r/demo/boards2/boards.gno
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,15 @@ func getBoard(id BoardID) (_ *Board, found bool) {
return v.(*Board), true
}

// mustGetBoardByName returns a board or panics when it's not found.
func mustGetBoardByName(name string) *Board {
v, found := gBoardsByName.Get(name)
if !found {
panic("board does not exist with name: " + name)
}
return v.(*Board)
}

// mustGetBoard returns a board or panics when it's not found.
func mustGetBoard(id BoardID) *Board {
board, found := getBoard(id)
Expand Down
12 changes: 11 additions & 1 deletion examples/gno.land/r/demo/boards2/permission.gno
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
package boards2

import "std"
import (
"std"

"gno.land/p/demo/boards2/admindao"
)

const (
PermissionBoardCreate Permission = "board:create"
PermissionBoardRename = "board:rename"
PermissionThreadCreate = "thread:create"
PermissionThreadEdit = "thread:edit"
PermissionThreadDelete = "thread:delete"
Expand All @@ -19,6 +24,7 @@ const (
RoleOwner Role = "owner"
RoleAdmin = "admin"
RoleModerator = "moderator"
RoleGuest = ""
)

type (
Expand Down Expand Up @@ -48,5 +54,9 @@ type (

// RemoveUser removes a user from the permissioner.
RemoveUser(std.Address) (removed bool)

// GetDAO returns the underlying DAO.
// Returned value can be nil if the implementation doesn't have a DAO.
GetDAO() *admindao.AdminDAO // TODO: should return an interface
}
)
64 changes: 53 additions & 11 deletions examples/gno.land/r/demo/boards2/permission_default.gno
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ func NewDefaultPermissions(dao *admindao.AdminDAO, options ...DefaultPermissions
// Roles returns the list of roles.
func (dp DefaultPermissions) Roles() []Role {
var roles []Role
if dp.superRole != "" {
roles = append(roles, dp.superRole)
}

dp.roles.Iterate("", "", func(name string, _ interface{}) bool {
roles = append(roles, Role(name))
return false
Expand All @@ -44,6 +48,10 @@ func (dp DefaultPermissions) Roles() []Role {

// RoleExists checks if a role exists.
func (dp DefaultPermissions) RoleExists(r Role) bool {
if dp.superRole != "" && r == dp.superRole {
return true
}

return dp.roles.Iterate("", "", func(name string, _ interface{}) bool {
return Role(name) == r
})
Expand Down Expand Up @@ -117,6 +125,12 @@ func (dp *DefaultPermissions) RemoveUser(user std.Address) bool {
return removed
}

// GetDAO returns the underlying DAO.
// Returned value can be nil if the implementation doesn't have a DAO.
func (dp DefaultPermissions) GetDAO() *admindao.AdminDAO {
return dp.dao
}

// WithPermission calls a callback when a user has a specific permission.
// It panics on error or when a handler panics.
// Callbacks are by default called when there is no handle registered for the permission.
Expand All @@ -128,6 +142,8 @@ func (dp *DefaultPermissions) WithPermission(user std.Address, perm Permission,
switch perm {
case PermissionBoardCreate:
dp.handleBoardCreate(args, cb)
case PermissionBoardRename:
dp.handleBoardRename(args, cb)
case PermissionMemberInvite:
dp.handleMemberInvite(args, cb)
default:
Expand All @@ -136,26 +152,36 @@ func (dp *DefaultPermissions) WithPermission(user std.Address, perm Permission,
}

func (DefaultPermissions) handleBoardCreate(args Args, cb func(Args)) {
// TODO: This way of dealing with arguments is delicate, ideally types should be used
name := args[0].(string)
if std.Address(name).IsValid() {
panic("addresses are not allowed as board name")
name, ok := args[0].(string)
if !ok {
panic("expected board name to be a string")
}

// When the board name is the name of a registered user
// check that caller is the owner of the name.
caller := std.GetOrigCaller()
user := users.GetUserByName(name)
if user != nil && user.Address != caller {
panic("board name is a user name registered to a different user")
assertBoardNameIsNotAddress(name)
assertBoardNameBelongsToCaller(name)

cb(args)
}

func (DefaultPermissions) handleBoardRename(args Args, cb func(Args)) {
newName, ok := args[2].(string)
if !ok {
panic("expected new board name to be a string")
}

assertBoardNameIsNotAddress(newName)
assertBoardNameBelongsToCaller(newName)

cb(args)
}

func (dp DefaultPermissions) handleMemberInvite(args Args, cb func(Args)) {
// Make sure that only owners invite other owners
role := args[1].(Role)
role, ok := args[1].(Role)
if !ok {
panic("expected a valid new member role")
}

if role == RoleOwner {
caller := std.GetOrigCaller()
if !dp.HasRole(caller, RoleOwner) {
Expand All @@ -178,3 +204,19 @@ func createDefaultPermissions(owner std.Address) *DefaultPermissions {
WithUser(owner, RoleOwner),
)
}

func assertBoardNameIsNotAddress(s string) {
if std.Address(s).IsValid() {
panic("addresses are not allowed as board name")
}
}

func assertBoardNameBelongsToCaller(name string) {
// When the board name is the name of a registered user
// check that caller is the owner of the name.
caller := std.GetOrigCaller()
user := users.GetUserByName(name)
if user != nil && user.Address != caller {
panic("board name is a user name registered to a different user")
}
}
44 changes: 38 additions & 6 deletions examples/gno.land/r/demo/boards2/public.gno
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,14 @@ func CreateBoard(name string) BoardID {
assertIsUserCall()

name = strings.TrimSpace(name)
if name == "" {
panic("board name is empty")
}
assertBoardNameIsNotEmpty(name)
assertBoardNameNotExists(name)

caller := std.GetOrigCaller()
id := incGetBoardID()
args := Args{name, id}
gPerm.WithPermission(caller, PermissionBoardCreate, args, func(Args) {
if gBoardsByName.Has(name) {
panic("board already exists")
}
assertBoardNameNotExists(name)

board := newBoard(id, name, caller)
gBoardsByID.Set(id.Key(), board)
Expand All @@ -36,6 +33,29 @@ func CreateBoard(name string) BoardID {
return id
}

func RenameBoard(name, newName string) {
assertIsUserCall()

newName = strings.TrimSpace(newName)
assertBoardNameIsNotEmpty(newName)
assertBoardNameNotExists(newName)

board := mustGetBoardByName(name)
bid := board.GetID()
caller := std.GetOrigCaller()
args := Args{bid, name, newName}
board.perms.WithPermission(caller, PermissionBoardRename, args, func(Args) {
assertBoardNameNotExists(newName)

board := mustGetBoard(bid)
board.aliases = append(board.aliases, board.name)
board.name = newName

// Index board for the new name keeping previous indexes for older names
gBoardsByName.Set(newName, board)
})
}

func FlagThread(bid BoardID, postID PostID, reason string) {
caller := std.GetOrigCaller()
board := mustGetBoard(bid)
Expand Down Expand Up @@ -237,6 +257,18 @@ func assertBoardExists(id BoardID) {
}
}

func assertBoardNameIsNotEmpty(name string) {
if name == "" {
panic("board name is empty")
}
}

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

func assertThreadExists(b *Board, threadID PostID) {
if _, found := b.GetThread(threadID); !found {
panic("thread not found: " + threadID.String())
Expand Down
32 changes: 32 additions & 0 deletions examples/gno.land/r/demo/boards2/z_3_a_filetest.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package main

import (
"std"

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

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

func init() {
std.TestSetOrigCaller(owner)
boards2.CreateBoard(name)
}

func main() {
newName := "bar"
_, exists := boards2.GetBoardIDFromName(newName)
println("Exists =", exists)

boards2.RenameBoard(name, newName)

bid, _ := boards2.GetBoardIDFromName(newName)
println("ID =", bid)
}

// Output:
// Exists = false
// ID = 1
24 changes: 24 additions & 0 deletions examples/gno.land/r/demo/boards2/z_3_b_filetest.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package main

import (
"std"

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

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

func init() {
std.TestSetOrigCaller(owner)
boards2.CreateBoard(name)
}

func main() {
boards2.RenameBoard(name, "")
}

// Error:
// board name is empty
24 changes: 24 additions & 0 deletions examples/gno.land/r/demo/boards2/z_3_c_filetest.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package main

import (
"std"

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

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

func init() {
std.TestSetOrigCaller(owner)
boards2.CreateBoard(name)
}

func main() {
boards2.RenameBoard(name, name)
}

// Error:
// board already exists
20 changes: 20 additions & 0 deletions examples/gno.land/r/demo/boards2/z_3_d_filetest.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package main

import (
"std"

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

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

func init() {
std.TestSetOrigCaller(owner)
}

func main() {
boards2.RenameBoard("unexisting", "foo")
}

// Error:
// board does not exist with name: unexisting
24 changes: 24 additions & 0 deletions examples/gno.land/r/demo/boards2/z_3_e_filetest.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package main

import (
"std"

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

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

func init() {
std.TestSetOrigCaller(owner)
boards2.CreateBoard(name)
}

func main() {
boards2.RenameBoard(name, "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5")
}

// Error:
// addresses are not allowed as board name
Loading
Loading