boards.gno

package boards2

import (
	"chain/runtime"
	"strings"

	"gno.land/p/gnoland/boards"
	"gno.land/p/gnoland/boards/exts/permissions"
	"gno.land/p/moul/realmpath"
	"gno.land/p/moul/txlink"
	"gno.land/p/nt/avl/v0"
)

// TODO: Refactor globals in favor of a cleaner pattern
var (
	gRealmLink        txlink.Realm
	gNotice           string
	gHelp             string
	gListedBoardsByID avl.Tree // string(id) -> *boards.Board
	gInviteRequests   avl.Tree // string(board id) -> *avl.Tree(address -> time.Time)
	gBannedUsers      avl.Tree // string(board id) -> *avl.Tree(address -> time.Time)
	gLocked           struct {
		realm        bool
		realmMembers bool
	}
)

var (
	gBoards         = boards.NewStorage()
	gBoardsSequence = boards.NewIdentifierGenerator()
	gRealmPath      = strings.TrimPrefix(runtime.CurrentRealm().PkgPath(), "gno.land")
	gPerms          = initRealmPermissions(
		"g16jpf0puufcpcjkph5nxueec8etpcldz7zwgydq",
		"g1rp7cmetn27eqlpjpc4vuusf8kaj746tysc0qgh", // govdao t1 multisig
	)

	// TODO: Allow updating open account amount though a proposal (GovDAO, CommonDAO?)
	gOpenAccountAmount = int64(3_000_000_000) // ugnot required for open board actions
)

func init() {
	// Save current realm path so it's available during render calls
	gRealmLink = txlink.Realm(runtime.CurrentRealm().PkgPath())
}

// initRealmPermissions returns the default realm permissions.
func initRealmPermissions(owners ...address) boards.Permissions {
	perms := permissions.New(
		permissions.UseSingleUserRole(),
		permissions.WithSuperRole(RoleOwner),
	)
	perms.AddRole(RoleAdmin, PermissionBoardCreate)
	for _, owner := range owners {
		perms.SetUserRoles(owner, RoleOwner)
	}

	perms.ValidateFunc(PermissionBoardCreate, validateBasicBoardCreate)
	perms.ValidateFunc(PermissionMemberInvite, validateBasicMemberInvite)
	perms.ValidateFunc(PermissionRoleChange, validateBasicRoleChange)
	return perms
}

// getInviteRequests returns invite requests for a board.
func getInviteRequests(boardID boards.ID) (_ *avl.Tree, found bool) {
	v, exists := gInviteRequests.Get(boardID.Key())
	if !exists {
		return nil, false
	}
	return v.(*avl.Tree), true
}

// getBannedUsers returns banned users within a board.
func getBannedUsers(boardID boards.ID) (_ *avl.Tree, found bool) {
	v, exists := gBannedUsers.Get(boardID.Key())
	if !exists {
		return nil, false
	}
	return v.(*avl.Tree), true
}

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

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

// getThread returns a board thread.
func getThread(board *boards.Board, threadID boards.ID) (*boards.Post, bool) {
	thread, found := board.Threads.Get(threadID)
	if !found {
		// When thread is not found search it within hidden threads
		meta := board.Meta.(*BoardMeta)
		thread, found = meta.HiddenThreads.Get(threadID)
	}
	return thread, found
}

// getReply returns a thread comment or reply.
func getReply(thread *boards.Post, replyID boards.ID) (*boards.Post, bool) {
	meta := thread.Meta.(*ThreadMeta)
	return meta.AllReplies.Get(replyID)
}

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

// mustGetReply returns a reply or panics when it's not found.
func mustGetReply(thread *boards.Post, replyID boards.ID) *boards.Post {
	reply, found := getReply(thread, replyID)
	if !found {
		panic("reply does not exist with ID: " + replyID.String())
	}
	return reply
}

func mustGetPermissions(bid boards.ID) boards.Permissions {
	if bid != 0 {
		board := mustGetBoard(bid)
		return board.Permissions
	}
	return gPerms
}

func parseRealmPath(path string) *realmpath.Request {
	// Make sure request is using current realm path so paths can be parsed during Render
	r := realmpath.Parse(path)
	r.Realm = string(gRealmLink)
	return r
}