PathrockNetwork Gno Explorer
HomeBlocksTransactionsRealmsPackagesValidatorsAnalytics

PathrockNetwork Gno Explorer — an independent explorer for Gno.land Mainnet (gnoland-1), operated by PathrockNetwork. Not an official Gno.land service.

gnowebarchive RPC

gno.land/r/gnoland/boards2/v0

Realm
Open in gnoweb ↗

Overview

Kind
Realm (renderable)
Name
v0
Namespace
gnoland / boards2
Files
252 (README)(gnomod.toml)
Exported functions
51
Module
gno.land/r/gnoland/boards2/v0
gno
0.9

Files (252)

  • README.mdmarkdown
  • gnomod.tomltoml
  • boards.gnogno
  • doc.gnogno
  • flag.gnogno
  • format.gnogno
  • hub.gnogno
  • meta.gnogno
  • permissions_validators_basic.gnogno
  • permissions_validators_open.gnogno
  • permissions.gnogno
  • public_ban.gnogno
  • public_flag.gnogno
  • public_freeze.gnogno
  • public_invite.gnogno
  • public_lock.gnogno
  • public.gnogno
  • render_board.gnogno
  • render_post.gnogno
  • render_reply.gnogno
  • render_thread.gnogno
  • render.gnogno
  • uris_board.gnogno
  • uris_post.gnogno
  • z_accept_invite_00_filetest.gnogno
  • z_accept_invite_01_filetest.gnogno
  • z_accept_invite_02_filetest.gnogno
  • z_accept_invite_03_filetest.gnogno
  • z_ban_00_filetest.gnogno
  • z_ban_01_filetest.gnogno
  • z_ban_02_filetest.gnogno
  • z_change_member_role_00_filetest.gnogno
  • z_change_member_role_01_filetest.gnogno
  • z_change_member_role_02_filetest.gnogno
  • z_change_member_role_03_filetest.gnogno
  • z_change_member_role_04_filetest.gnogno
  • z_change_member_role_05_filetest.gnogno
  • z_change_member_role_06_filetest.gnogno
  • z_change_member_role_07_filetest.gnogno
  • z_create_board_00_filetest.gnogno
  • z_create_board_01_filetest.gnogno
  • z_create_board_02_filetest.gnogno
  • z_create_board_03_filetest.gnogno
  • z_create_board_04_filetest.gnogno
  • z_create_board_05_filetest.gnogno
  • z_create_board_06_filetest.gnogno
  • z_create_board_07_filetest.gnogno
  • z_create_board_08_filetest.gnogno
  • z_create_board_09_filetest.gnogno
  • z_create_board_10_filetest.gnogno
  • z_create_reply_00_filetest.gnogno
  • z_create_reply_01_filetest.gnogno
  • z_create_reply_02_filetest.gnogno
  • z_create_reply_03_filetest.gnogno
  • z_create_reply_04_filetest.gnogno
  • z_create_reply_05_filetest.gnogno
  • z_create_reply_06_filetest.gnogno
  • z_create_reply_07_filetest.gnogno
  • z_create_reply_08_filetest.gnogno
  • z_create_reply_09_filetest.gnogno
  • z_create_reply_10_filetest.gnogno
  • z_create_reply_11_filetest.gnogno
  • z_create_reply_12_filetest.gnogno
  • z_create_reply_13_filetest.gnogno
  • z_create_reply_14_filetest.gnogno
  • z_create_reply_15_filetest.gnogno
  • z_create_reply_16_filetest.gnogno
  • z_create_repost_00_filetest.gnogno
  • z_create_repost_01_filetest.gnogno
  • z_create_repost_02_filetest.gnogno
  • z_create_repost_03_filetest.gnogno
  • z_create_repost_04_filetest.gnogno
  • z_create_thread_00_filetest.gnogno
  • z_create_thread_01_filetest.gnogno
  • z_create_thread_02_filetest.gnogno
  • z_create_thread_03_filetest.gnogno
  • z_create_thread_04_filetest.gnogno
  • z_create_thread_05_filetest.gnogno
  • z_create_thread_06_filetest.gnogno
  • z_delete_reply_00_filetest.gnogno
  • z_delete_reply_01_filetest.gnogno
  • z_delete_reply_02_filetest.gnogno
  • z_delete_reply_03_filetest.gnogno
  • z_delete_reply_04_filetest.gnogno
  • z_delete_reply_05_filetest.gnogno
  • z_delete_reply_06_filetest.gnogno
  • z_delete_reply_07_filetest.gnogno
  • z_delete_reply_08_filetest.gnogno
  • z_delete_reply_09_filetest.gnogno
  • z_delete_thread_00_filetest.gnogno
  • z_delete_thread_01_filetest.gnogno
  • z_delete_thread_02_filetest.gnogno
  • z_delete_thread_03_filetest.gnogno
  • z_delete_thread_04_filetest.gnogno
  • z_delete_thread_05_filetest.gnogno
  • z_delete_thread_06_filetest.gnogno
  • z_edit_reply_00_filetest.gnogno
  • z_edit_reply_01_filetest.gnogno
  • z_edit_reply_02_filetest.gnogno
  • z_edit_reply_03_filetest.gnogno
  • z_edit_reply_04_filetest.gnogno
  • z_edit_reply_05_filetest.gnogno
  • z_edit_reply_06_filetest.gnogno
  • z_edit_reply_07_filetest.gnogno
  • z_edit_reply_08_filetest.gnogno
  • z_edit_thread_00_filetest.gnogno
  • z_edit_thread_01_filetest.gnogno
  • z_edit_thread_02_filetest.gnogno
  • z_edit_thread_03_filetest.gnogno
  • z_edit_thread_04_filetest.gnogno
  • z_edit_thread_05_filetest.gnogno
  • z_edit_thread_06_filetest.gnogno
  • z_edit_thread_07_filetest.gnogno
  • z_flag_reply_00_filetest.gnogno
  • z_flag_reply_01_filetest.gnogno
  • z_flag_reply_02_filetest.gnogno
  • z_flag_reply_03_filetest.gnogno
  • z_flag_reply_04_filetest.gnogno
  • z_flag_reply_05_filetest.gnogno
  • z_flag_reply_06_filetest.gnogno
  • z_flag_reply_07_filetest.gnogno
  • z_flag_reply_08_filetest.gnogno
  • z_flag_reply_09_filetest.gnogno
  • z_flag_thread_00_filetest.gnogno
  • z_flag_thread_01_filetest.gnogno
  • z_flag_thread_02_filetest.gnogno
  • z_flag_thread_03_filetest.gnogno
  • z_flag_thread_04_filetest.gnogno
  • z_flag_thread_05_filetest.gnogno
  • z_flag_thread_06_filetest.gnogno
  • z_flag_thread_07_filetest.gnogno
  • z_flag_thread_08_filetest.gnogno
  • z_freeze_board_00_filetest.gnogno
  • z_freeze_board_01_filetest.gnogno
  • z_freeze_thread_00_filetest.gnogno
  • z_freeze_thread_01_filetest.gnogno
  • z_freeze_thread_02_filetest.gnogno
  • z_get_board_id_from_name_00_filetest.gnogno
  • z_get_board_id_from_name_01_filetest.gnogno
  • z_hub_0_a_filetest.gnogno
  • z_hub_0_b_filetest.gnogno
  • z_hub_0_c_filetest.gnogno
  • z_hub_1_a_filetest.gnogno
  • z_hub_1_b_filetest.gnogno
  • z_hub_1_c_filetest.gnogno
  • z_hub_2_a_filetest.gnogno
  • z_hub_2_b_filetest.gnogno
  • z_hub_3_a_filetest.gnogno
  • z_hub_3_b_filetest.gnogno
  • z_hub_4_a_filetest.gnogno
  • z_hub_4_b_filetest.gnogno
  • z_hub_5_a_filetest.gnogno
  • z_hub_5_b_filetest.gnogno
  • z_hub_6_a_filetest.gnogno
  • z_hub_6_b_filetest.gnogno
  • z_hub_6_c_filetest.gnogno
  • z_hub_6_d_filetest.gnogno
  • z_hub_6_e_filetest.gnogno
  • z_hub_7_a_filetest.gnogno
  • z_hub_7_b_filetest.gnogno
  • z_hub_8_a_filetest.gnogno
  • z_hub_8_b_filetest.gnogno
  • z_hub_8_c_filetest.gnogno
  • z_hub_9_a_filetest.gnogno
  • z_hub_9_b_filetest.gnogno
  • z_hub_9_c_filetest.gnogno
  • z_invite_member_00_filetest.gnogno
  • z_invite_member_01_filetest.gnogno
  • z_invite_member_02_filetest.gnogno
  • z_invite_member_03_filetest.gnogno
  • z_invite_member_04_filetest.gnogno
  • z_invite_member_05_filetest.gnogno
  • z_invite_member_06_filetest.gnogno
  • z_is_banned_00_filetest.gnogno
  • z_is_banned_01_filetest.gnogno
  • z_is_member_00_filetest.gnogno
  • z_is_member_01_filetest.gnogno
  • z_lock_realm_00_filetest.gnogno
  • z_lock_realm_01_filetest.gnogno
  • z_lock_realm_02_filetest.gnogno
  • z_lock_realm_03_filetest.gnogno
  • z_lock_realm_04_filetest.gnogno
  • z_lock_realm_05_filetest.gnogno
  • z_remove_member_00_filetest.gnogno
  • z_remove_member_01_filetest.gnogno
  • z_remove_member_02_filetest.gnogno
  • z_remove_member_03_filetest.gnogno
  • z_rename_board_00_filetest.gnogno
  • z_rename_board_01_filetest.gnogno
  • z_rename_board_02_filetest.gnogno
  • z_rename_board_03_filetest.gnogno
  • z_rename_board_04_filetest.gnogno
  • z_rename_board_05_filetest.gnogno
  • z_rename_board_06_filetest.gnogno
  • z_rename_board_07_filetest.gnogno
  • z_rename_board_08_filetest.gnogno
  • z_rename_board_09_filetest.gnogno
  • z_request_invite_00_filetest.gnogno
  • z_request_invite_01_filetest.gnogno
  • z_request_invite_02_filetest.gnogno
  • z_request_invite_03_filetest.gnogno
  • z_request_invite_04_filetest.gnogno
  • z_revoke_invite_00_filetest.gnogno
  • z_revoke_invite_01_filetest.gnogno
  • z_revoke_invite_02_filetest.gnogno
  • z_set_flagging_threshold_00_filetest.gnogno
  • z_set_flagging_threshold_01_filetest.gnogno
  • z_set_flagging_threshold_02_filetest.gnogno
  • z_set_permissions_00_filetest.gnogno
  • z_set_permissions_01_filetest.gnogno
  • z_set_permissions_02_filetest.gnogno
  • z_set_realm_notice_00_filetest.gnogno
  • z_set_realm_notice_01_filetest.gnogno
  • z_set_realm_notice_02_filetest.gnogno
  • z_set_realm_notice_03_filetest.gnogno
  • z_set_required_account_amount_00_filetest.gnogno
  • z_set_required_account_amount_01_filetest.gnogno
  • z_set_required_account_amount_02_filetest.gnogno
  • z_ui_admin_users_00_filetest.gnogno
  • z_ui_board_00_filetest.gnogno
  • z_ui_board_01_filetest.gnogno
  • z_ui_board_02_filetest.gnogno
  • z_ui_board_03_filetest.gnogno
  • z_ui_board_04_filetest.gnogno
  • z_ui_board_05_filetest.gnogno
  • z_ui_board_members_00_filetest.gnogno
  • z_ui_home_00_filetest.gnogno
  • z_ui_home_01_filetest.gnogno
  • z_ui_home_02_filetest.gnogno
  • z_ui_reply_00_filetest.gnogno
  • z_ui_reply_01_filetest.gnogno
  • z_ui_reply_02_filetest.gnogno
  • z_ui_reply_03_filetest.gnogno
  • z_ui_reply_04_filetest.gnogno
  • z_ui_thread_00_filetest.gnogno
  • z_ui_thread_01_filetest.gnogno
  • z_ui_thread_02_filetest.gnogno
  • z_ui_thread_03_filetest.gnogno
  • z_ui_thread_04_filetest.gnogno
  • z_ui_thread_05_filetest.gnogno
  • z_ui_thread_06_filetest.gnogno
  • z_ui_thread_07_filetest.gnogno
  • z_ui_thread_08_filetest.gnogno
  • z_ui_thread_09_filetest.gnogno
  • z_ui_thread_10_filetest.gnogno
  • z_ui_thread_11_filetest.gnogno
  • z_ui_thread_12_filetest.gnogno
  • z_ui_thread_13_filetest.gnogno
  • z_ui_thread_14_filetest.gnogno
  • z_unban_00_filetest.gnogno
  • z_unban_01_filetest.gnogno
  • z_unban_02_filetest.gnogno
  • public.gnogno
    1package boards223import (4	"chain"5	"chain/runtime/unsafe"6	"regexp"7	"strconv"8

    Functions

    • AcceptInvite(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, boardID uint64, user string)

    • AreRealmMembersLocked() bool

    • Ban(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, boardID uint64, user string, hours uint, reason string)

    • BoardCount() int

    • ChangeMemberRole(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, boardID uint64, member string, role string)

    • CreateBoard(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, name string, listed bool, open bool) uint64

    • CreateReply(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, boardID uint64, threadID uint64, replyID uint64, body string) uint64

    • CreateRepost(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, boardID uint64, threadID uint64, destinationBoardID uint64, title string, body string) uint64

    • CreateThread(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, boardID uint64, title string, body string) uint64

    • DeleteReply(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, boardID uint64, threadID uint64, replyID uint64)

    • DeleteThread(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, boardID uint64, threadID uint64)

    • EditReply(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, boardID uint64, threadID uint64, replyID uint64, body string)

    • EditThread(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, boardID uint64, threadID uint64, title string, body string)

    • FlagReply(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, boardID uint64, threadID uint64, replyID uint64, reason string)

    • FlagThread(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, boardID uint64, threadID uint64, reason string)

    • FreezeBoard(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, boardID uint64)

    • FreezeThread(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, boardID uint64, threadID uint64)

    • GetBoard(id uint64) (struct{id uint64; name string; aliases []string; readonly bool; threadCount int; memberCount int; creator .uverse.address; createdAt int64; updatedAt int64}, bool)

    • GetBoardIDFromName(interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, name string) (uint64, found bool)

    • GetBoards(start int, count int) []gno.land/p/gnoland/boards/exts/hub/v0.Board

    • GetComment(boardID uint64, threadID uint64, commentID uint64) (struct{id uint64; boardID uint64; threadID uint64; parentID uint64; body string; hidden bool; replyCount int; flagCount int; creator .uverse.address; createdAt int64; updatedAt int64}, bool)

    • GetComments(boardID uint64, threadID uint64, start int, count int) []gno.land/p/gnoland/boards/exts/hub/v0.Comment

    • GetFlaggingThreshold(boardID uint64) int

    • GetFlags(boardID uint64, threadID uint64, commentID uint64, start int, count int) []gno.land/p/gnoland/boards/exts/hub/v0.Flag

    • GetMembers(boardID uint64, start int, count int) []gno.land/p/gnoland/boards/exts/hub/v0.Member

    • GetReplies(boardID uint64, threadID uint64, commentID uint64, start int, count int) []gno.land/p/gnoland/boards/exts/hub/v0.Comment

    • GetReposts(boardID uint64, threadID uint64, start int, count int) []gno.land/p/gnoland/boards/exts/hub/v0.Thread

    • GetThread(boardID uint64, threadID uint64) (struct{id uint64; originalBoardID uint64; originalThreadID uint64; boardID uint64; title string; body string; hidden bool; readonly bool; commentCount int; repostCount int; flagCount int; creator .uverse.address; createdAt int64; updatedAt int64}, bool)

    • GetThreads(boardID uint64, start int, count int) []gno.land/p/gnoland/boards/exts/hub/v0.Thread

    • HasMemberRole(boardID uint64, member string, role string) bool

    • InviteMember(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, boardID uint64, user string, role string)

    • InviteMembers(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, boardID uint64, invites ...gno.land/r/gnoland/boards2/v0.Invite)

    • IsBanned(boardID uint64, user string) bool

    • IsBoardFrozen(boardID uint64) bool

    • IsMember(boardID uint64, user string) bool

    • IsRealmLocked() bool

    • IsThreadFrozen(boardID uint64, threadID uint64) bool

    • LockRealm(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, lockRealmMembers bool)

    • RemoveMember(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, boardID uint64, member string)

    • RenameBoard(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, name string, newName string)

    • Render(path string) string

    • RequestInvite(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, boardID uint64)

    • RevokeInvite(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, boardID uint64, user string)

    • SetFlaggingThreshold(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, boardID uint64, threshold int)

    • SetHelp(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, content string)

    • SetPermissions(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, boardID uint64, p interface {HasPermission func(.uverse.address, gno.land/p/gnoland/boards/v0.Permission) bool; HasRole func(.uverse.address, gno.land/p/gnoland/boards/v0.Role) bool; HasUser func(.uverse.address) bool; IterateUsers func(int, int, gno.land/p/gnoland/boards/v0.UsersIterFn) bool; RemoveUser func(.uverse.address) bool; SetUserRoles func(.uverse.address, ...gno.land/p/gnoland/boards/v0.Role); UsersCount func() int; WithPermission func(.uverse.address, gno.land/p/gnoland/boards/v0.Permission, gno.land/p/gnoland/boards/v0.Args, func())})

    • SetRealmNotice(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, message string)

    • SetRequiredAccountAmount(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, amount int64)

    • Unban(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, boardID uint64, user string, reason string)

    • UnfreezeBoard(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, boardID uint64)

    • UnfreezeThread(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, boardID uint64, threadID uint64)

    Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.

    Rendered

    RenderedRawgnoweb ↗

    vm/qrender output, sanitized (docs/render-security.md) and displayed in an empty-sandbox iframe — scripts, forms and popups cannot run. Links stay inert in-preview; right-click to open.

    "strings"
    9 "time"
    10
    11 "gno.land/p/gnoland/boards/v0"
    12)
    13
    14const (
    15 // MaxBoardNameLength defines the maximum length allowed for board names.
    16 MaxBoardNameLength = 50
    17
    18 // MaxThreadTitleLength defines the maximum length allowed for thread titles.
    19 MaxThreadTitleLength = 100
    20
    21 // MaxThreadBodyLength defines the maximum length allowed for thread bodies.
    22 // 40,000 mirrors Reddit's self-post body cap.
    23 MaxThreadBodyLength = 40000
    24
    25 // MaxReplyLength defines the maximum length allowed for replies.
    26 // 10,000 mirrors Reddit's comment cap.
    27 MaxReplyLength = 10000
    28)
    29
    30var reBoardName = regexp.MustCompile(`(?i)^[a-z]+[a-z0-9_\-]{2,50}$`)
    31
    32// SetHelp sets or updates boards realm help content.
    33func SetHelp(cur realm, content string) {
    34 content = strings.TrimSpace(content)
    35 caller := cur.Previous().Address()
    36 args := boards.Args{content}
    37 gPerms.WithPermission(caller, PermissionRealmHelpChange, args, func() {
    38 Help = content
    39 })
    40}
    41
    42// SetRequiredAccountAmount sets the required account amount to interact as a non member with open boards.
    43// Amount must be given as ugnot.
    44// The amount requirement is not applied to members that were invited to an open board.
    45func SetRequiredAccountAmount(cur realm, amount int64) {
    46 if amount < 0 {
    47 panic("invalid amount")
    48 }
    49
    50 caller := cur.Previous().Address()
    51 args := boards.Args{amount}
    52 gPerms.WithPermission(caller, PermissionAccountRequiredAmountChange, args, func() {
    53 RequiredAccountAmount = amount
    54
    55 chain.Emit(
    56 "RequiredAccountAmountChanged",
    57 "caller", caller.String(),
    58 "amount", strconv.FormatInt(amount, 10),
    59 )
    60 })
    61}
    62
    63// SetPermissions sets a permissions implementation for boards2 realm or a board.
    64func SetPermissions(cur realm, boardID boards.ID, p boards.Permissions) {
    65 assertRealmIsNotLocked()
    66 assertBoardExists(boardID)
    67
    68 if p == nil {
    69 panic("permissions is required")
    70 }
    71
    72 caller := cur.Previous().Address()
    73 args := boards.Args{boardID}
    74 gPerms.WithPermission(caller, PermissionPermissionsUpdate, args, func() {
    75 assertRealmIsNotLocked()
    76
    77 // When board ID is zero it means that realm permissions are being updated
    78 if boardID == 0 {
    79 gPerms = p
    80
    81 chain.Emit(
    82 "RealmPermissionsChanged",
    83 "caller", caller.String(),
    84 )
    85 return
    86 }
    87
    88 // Otherwise update the permissions of a single board
    89 board := mustGetBoard(boardID)
    90 board.Permissions = p
    91
    92 chain.Emit(
    93 "BoardPermissionsChanged",
    94 "caller", caller.String(),
    95 "boardID", board.ID.String(),
    96 )
    97 })
    98}
    99
    100// SetRealmNotice sets a notice to be displayed globally within the realm.
    101// An empty message removes the realm notice.
    102func SetRealmNotice(cur realm, message string) {
    103 message = strings.TrimSpace(message)
    104 caller := cur.Previous().Address()
    105 args := boards.Args{message}
    106 gPerms.WithPermission(caller, PermissionRealmNotice, args, func() {
    107 Notice = message
    108
    109 chain.Emit(
    110 "RealmNoticeChanged",
    111 "caller", caller.String(),
    112 "message", message,
    113 )
    114 })
    115}
    116
    117// GetBoardIDFromName searches a board by name and returns its ID.
    118func GetBoardIDFromName(_ realm, name string) (_ boards.ID, found bool) {
    119 board, found := gBoards.GetByName(name)
    120 if !found {
    121 return 0, false
    122 }
    123 return board.ID, true
    124}
    125
    126// BoardCount returns the total number of boards.
    127func BoardCount() int {
    128 return gBoards.Size()
    129}
    130
    131// CreateBoard creates a new board.
    132//
    133// Listed boards are included in the realm's list of boards.
    134// Open boards allow anyone to create threads and comment.
    135func CreateBoard(cur realm, name string, listed, open bool) boards.ID {
    136 assertRealmIsNotLocked()
    137
    138 name = strings.TrimSpace(name)
    139 assertIsValidBoardName(name)
    140 assertBoardNameNotExists(name)
    141
    142 caller := cur.Previous().Address()
    143 id := gBoardsSequence.Next()
    144 board := boards.New(id)
    145 args := boards.Args{caller, name, board.ID, listed, open}
    146 gPerms.WithPermission(caller, PermissionBoardCreate, args, func() {
    147 assertRealmIsNotLocked()
    148 assertBoardNameNotExists(name)
    149
    150 board.Name = name
    151 board.Creator = caller
    152 board.Meta = &BoardMeta{
    153 HiddenThreads: boards.NewPostStorage(),
    154 }
    155
    156 if open {
    157 board.Permissions = createOpenBoardPermissions(caller)
    158 } else {
    159 board.Permissions = createBasicBoardPermissions(caller)
    160 }
    161
    162 if err := gBoards.Add(board); err != nil {
    163 panic(err)
    164 }
    165
    166 // Listed boards are also indexed separately for easier iteration and pagination
    167 if listed {
    168 gListedBoardsByID.Set(board.ID.Key(), board)
    169 }
    170
    171 chain.Emit(
    172 "BoardCreated",
    173 "caller", caller.String(),
    174 "boardID", board.ID.String(),
    175 "name", name,
    176 )
    177 })
    178 return board.ID
    179}
    180
    181// RenameBoard changes the name of an existing board.
    182//
    183// A history of previous board names is kept when boards are renamed.
    184// Because of that boards are also accessible using previous name(s).
    185func RenameBoard(cur realm, name, newName string) {
    186 assertRealmIsNotLocked()
    187
    188 newName = strings.TrimSpace(newName)
    189 assertIsValidBoardName(newName)
    190 assertBoardNameNotExists(newName)
    191
    192 board := mustGetBoardByName(name)
    193 assertBoardIsNotFrozen(board)
    194
    195 caller := cur.Previous().Address()
    196 args := boards.Args{caller, board.ID, name, newName}
    197 board.Permissions.WithPermission(caller, PermissionBoardRename, args, func() {
    198 assertRealmIsNotLocked()
    199 assertBoardNameNotExists(newName)
    200
    201 board.Aliases = append(board.Aliases, board.Name)
    202 board.Name = newName
    203 board.UpdatedAt = time.Now()
    204
    205 // Index board for the new name keeping previous indexes for older names
    206 gBoards.Add(board)
    207
    208 chain.Emit(
    209 "BoardRenamed",
    210 "caller", caller.String(),
    211 "boardID", board.ID.String(),
    212 "name", name,
    213 "newName", newName,
    214 )
    215 })
    216}
    217
    218// CreateThread creates a new thread within a board.
    219func CreateThread(cur realm, boardID boards.ID, title, body string) boards.ID {
    220 assertRealmIsNotLocked()
    221
    222 title = strings.TrimSpace(title)
    223 assertTitleIsValid(title)
    224 assertThreadBodyIsValid(body)
    225
    226 caller := cur.Previous().Address()
    227 assertUserIsNotBanned(boardID, caller)
    228
    229 board := mustGetBoard(boardID)
    230 assertBoardIsNotFrozen(board)
    231
    232 thread := boards.MustNewThread(board, caller, title, body)
    233 args := boards.Args{caller, board.ID, thread.ID, title, body}
    234 board.Permissions.WithPermission(caller, PermissionThreadCreate, args, func() {
    235 assertRealmIsNotLocked()
    236 assertUserIsNotBanned(board.ID, caller)
    237
    238 thread.Meta = &ThreadMeta{
    239 AllReplies: boards.NewPostStorage(),
    240 }
    241
    242 if err := board.Threads.Add(thread); err != nil {
    243 panic(err)
    244 }
    245
    246 chain.Emit(
    247 "ThreadCreated",
    248 "caller", caller.String(),
    249 "boardID", board.ID.String(),
    250 "threadID", thread.ID.String(),
    251 "title", title,
    252 )
    253 })
    254 return thread.ID
    255}
    256
    257// CreateReply creates a new comment or reply within a thread.
    258//
    259// The value of `replyID` is only required when creating a reply of another reply.
    260func CreateReply(cur realm, boardID, threadID, replyID boards.ID, body string) boards.ID {
    261 assertRealmIsNotLocked()
    262
    263 body = strings.TrimSpace(body)
    264 assertReplyBodyIsValid(body)
    265
    266 caller := cur.Previous().Address()
    267 assertUserIsNotBanned(boardID, caller)
    268
    269 board := mustGetBoard(boardID)
    270 assertBoardIsNotFrozen(board)
    271
    272 thread := mustGetThread(board, threadID)
    273 assertThreadIsVisible(thread)
    274 assertThreadIsNotFrozen(thread)
    275
    276 // By default consider that reply's parent is the thread.
    277 // Or when replyID is assigned use that reply as the parent.
    278 parent := thread
    279 if replyID > 0 {
    280 parent = mustGetReply(thread, replyID)
    281 if parent.Hidden || parent.Readonly {
    282 panic("replying to a hidden or frozen reply is not allowed")
    283 }
    284 }
    285
    286 reply := boards.MustNewReply(parent, caller, body)
    287 args := boards.Args{caller, board.ID, thread.ID, parent.ID, reply.ID, body}
    288 board.Permissions.WithPermission(caller, PermissionReplyCreate, args, func() {
    289 assertRealmIsNotLocked()
    290
    291 // Add reply to its parent
    292 if err := parent.Replies.Add(reply); err != nil {
    293 panic(err)
    294 }
    295
    296 // Always add reply to the thread so it contains all comments and replies.
    297 // Comment and reply only contains direct replies.
    298 meta := thread.Meta.(*ThreadMeta)
    299 if err := meta.AllReplies.Add(reply); err != nil {
    300 panic(err)
    301 }
    302
    303 chain.Emit(
    304 "ReplyCreate",
    305 "caller", caller.String(),
    306 "boardID", board.ID.String(),
    307 "threadID", thread.ID.String(),
    308 "replyID", reply.ID.String(),
    309 )
    310 })
    311 return reply.ID
    312}
    313
    314// CreateRepost reposts a thread into another board.
    315func CreateRepost(cur realm, boardID, threadID, destinationBoardID boards.ID, title, body string) boards.ID {
    316 assertRealmIsNotLocked()
    317
    318 title = strings.TrimSpace(title)
    319 assertTitleIsValid(title)
    320 assertThreadBodyIsValid(body)
    321
    322 caller := cur.Previous().Address()
    323 assertUserIsNotBanned(destinationBoardID, caller)
    324
    325 dst := mustGetBoard(destinationBoardID)
    326 assertBoardIsNotFrozen(dst)
    327
    328 board := mustGetBoard(boardID)
    329 thread := mustGetThread(board, threadID)
    330 assertThreadIsVisible(thread)
    331
    332 repost := boards.MustNewRepost(thread, dst, caller)
    333 args := boards.Args{caller, board.ID, thread.ID, dst.ID, repost.ID, title, body}
    334 dst.Permissions.WithPermission(caller, PermissionThreadRepost, args, func() {
    335 assertRealmIsNotLocked()
    336
    337 repost.Title = title
    338 repost.Body = strings.TrimSpace(body)
    339 repost.Meta = &ThreadMeta{
    340 AllReplies: boards.NewPostStorage(),
    341 }
    342
    343 if err := dst.Threads.Add(repost); err != nil {
    344 panic(err)
    345 }
    346
    347 if err := thread.Reposts.Add(repost); err != nil {
    348 panic(err)
    349 }
    350
    351 chain.Emit(
    352 "Repost",
    353 "caller", caller.String(),
    354 "boardID", board.ID.String(),
    355 "threadID", thread.ID.String(),
    356 "destinationBoardID", dst.ID.String(),
    357 "repostID", repost.ID.String(),
    358 "title", title,
    359 )
    360 })
    361 return repost.ID
    362}
    363
    364// DeleteThread deletes a thread from a board.
    365//
    366// Threads can be deleted by the users who created them or otherwise by users with special permissions.
    367func DeleteThread(cur realm, boardID, threadID boards.ID) {
    368 assertRealmIsNotLocked()
    369
    370 caller := cur.Previous().Address()
    371 board := mustGetBoard(boardID)
    372 assertUserIsNotBanned(boardID, caller)
    373
    374 isRealmOwner := gPerms.HasRole(caller, RoleOwner) // TODO: Add DeleteThread filetest cases for realm owners
    375 if !isRealmOwner {
    376 assertBoardIsNotFrozen(board)
    377 }
    378
    379 thread := mustGetThread(board, threadID)
    380 deleteThread := func() {
    381 board.Threads.Remove(thread.ID)
    382
    383 chain.Emit(
    384 "ThreadDeleted",
    385 "caller", caller.String(),
    386 "boardID", board.ID.String(),
    387 "threadID", thread.ID.String(),
    388 )
    389 }
    390
    391 // Thread can be directly deleted by user that created it.
    392 // It can also be deleted by realm owners, to be able to delete inappropriate content.
    393 // TODO: Discuss and decide if realm owners should be able to delete threads.
    394 if isRealmOwner || caller == thread.Creator {
    395 deleteThread()
    396 return
    397 }
    398
    399 args := boards.Args{caller, board.ID, thread.ID}
    400 board.Permissions.WithPermission(caller, PermissionThreadDelete, args, func() {
    401 assertRealmIsNotLocked()
    402 deleteThread()
    403 })
    404}
    405
    406// DeleteReply deletes a reply from a thread.
    407//
    408// Replies can be deleted by the users who created them or otherwise by users with special permissions.
    409// Soft deletion is used when the deleted reply contains sub replies, in which case the reply content
    410// is replaced by a text informing that reply has been deleted to avoid deleting sub-replies.
    411func DeleteReply(cur realm, boardID, threadID, replyID boards.ID) {
    412 assertRealmIsNotLocked()
    413
    414 caller := cur.Previous().Address()
    415 board := mustGetBoard(boardID)
    416 assertUserIsNotBanned(boardID, caller)
    417
    418 thread := mustGetThread(board, threadID)
    419 reply := mustGetReply(thread, replyID)
    420 isRealmOwner := gPerms.HasRole(caller, RoleOwner) // TODO: Add DeleteReply filetest cases for realm owners
    421 if !isRealmOwner {
    422 assertBoardIsNotFrozen(board)
    423 assertThreadIsNotFrozen(thread)
    424 assertReplyIsVisible(reply)
    425 }
    426
    427 deleteReply := func() {
    428 // Soft delete comment/reply by changing its body when
    429 // it contains replies, otherwise hard delete it.
    430 if reply.Replies.Size() > 0 {
    431 reply.Body = "⚠ This comment has been deleted"
    432 reply.UpdatedAt = time.Now()
    433 } else {
    434 // Remove reply from the flat thread index.
    435 meta := thread.Meta.(*ThreadMeta)
    436 reply, removed := meta.AllReplies.Remove(replyID)
    437 if !removed {
    438 panic("reply not found")
    439 }
    440
    441 // Remove reply from its parent's direct-children list too. A
    442 // direct thread reply's parent is the thread itself; a nested
    443 // reply's parent is another reply (resolved via the flat index).
    444 // Missing the thread-parent case left a ghost in thread.Replies
    445 // (rendered in the threaded view, with a stale reply count).
    446 if reply.ParentID == thread.ID {
    447 thread.Replies.Remove(replyID)
    448 } else if parent, found := meta.AllReplies.Get(reply.ParentID); found {
    449 parent.Replies.Remove(replyID)
    450 }
    451 }
    452
    453 chain.Emit(
    454 "ReplyDeleted",
    455 "caller", caller.String(),
    456 "boardID", board.ID.String(),
    457 "threadID", thread.ID.String(),
    458 "replyID", reply.ID.String(),
    459 )
    460 }
    461
    462 // Reply can be directly deleted by user that created it.
    463 // It can also be deleted by realm owners, to be able to delete inappropriate content.
    464 // TODO: Discuss and decide if realm owners should be able to delete replies.
    465 if isRealmOwner || caller == reply.Creator {
    466 deleteReply()
    467 return
    468 }
    469
    470 args := boards.Args{caller, board.ID, thread.ID, reply.ID}
    471 board.Permissions.WithPermission(caller, PermissionReplyDelete, args, func() {
    472 assertRealmIsNotLocked()
    473 deleteReply()
    474 })
    475}
    476
    477// EditThread updates the title and body of a thread.
    478//
    479// Threads can be updated by the users who created them or otherwise by users with special permissions.
    480func EditThread(cur realm, boardID, threadID boards.ID, title, body string) {
    481 assertRealmIsNotLocked()
    482
    483 title = strings.TrimSpace(title)
    484 assertTitleIsValid(title)
    485 assertThreadBodyIsValid(body)
    486
    487 board := mustGetBoard(boardID)
    488 assertBoardIsNotFrozen(board)
    489
    490 caller := cur.Previous().Address()
    491 assertUserIsNotBanned(boardID, caller)
    492
    493 thread := mustGetThread(board, threadID)
    494 assertThreadIsNotFrozen(thread)
    495
    496 body = strings.TrimSpace(body)
    497 if !boards.IsRepost(thread) {
    498 assertBodyIsNotEmpty(body)
    499 }
    500
    501 editThread := func() {
    502 thread.Title = title
    503 thread.Body = body
    504 thread.UpdatedAt = time.Now()
    505
    506 chain.Emit(
    507 "ThreadEdited",
    508 "caller", caller.String(),
    509 "boardID", board.ID.String(),
    510 "threadID", thread.ID.String(),
    511 "title", title,
    512 )
    513 }
    514
    515 if caller == thread.Creator {
    516 editThread()
    517 return
    518 }
    519
    520 args := boards.Args{caller, board.ID, thread.ID, title, body}
    521 board.Permissions.WithPermission(caller, PermissionThreadEdit, args, func() {
    522 assertRealmIsNotLocked()
    523 editThread()
    524 })
    525}
    526
    527// EditReply updates the body of a comment or reply.
    528//
    529// Replies can be updated only by the users who created them.
    530func EditReply(cur realm, boardID, threadID, replyID boards.ID, body string) {
    531 assertRealmIsNotLocked()
    532
    533 body = strings.TrimSpace(body)
    534 assertReplyBodyIsValid(body)
    535
    536 board := mustGetBoard(boardID)
    537 assertBoardIsNotFrozen(board)
    538
    539 caller := cur.Previous().Address()
    540 assertUserIsNotBanned(boardID, caller)
    541
    542 thread := mustGetThread(board, threadID)
    543 assertThreadIsNotFrozen(thread)
    544
    545 reply := mustGetReply(thread, replyID)
    546 assertReplyIsVisible(reply)
    547
    548 if caller != reply.Creator {
    549 panic("only the reply creator is allowed to edit it")
    550 }
    551
    552 reply.Body = body
    553 reply.UpdatedAt = time.Now()
    554
    555 chain.Emit(
    556 "ReplyEdited",
    557 "caller", caller.String(),
    558 "boardID", board.ID.String(),
    559 "threadID", thread.ID.String(),
    560 "replyID", reply.ID.String(),
    561 "body", body,
    562 )
    563}
    564
    565// RemoveMember removes a member from the realm or a board.
    566//
    567// Board ID is only required when removing a member from board.
    568func RemoveMember(cur realm, boardID boards.ID, member address) {
    569 assertMembersUpdateIsEnabled(boardID)
    570 assertMemberAddressIsValid(member)
    571
    572 perms := mustGetPermissions(boardID)
    573 origin := unsafe.OriginCaller()
    574 caller := cur.Previous().Address()
    575 removeMember := func() {
    576 if !perms.RemoveUser(member) {
    577 panic("member not found")
    578 }
    579
    580 chain.Emit(
    581 "MemberRemoved",
    582 "caller", caller.String(),
    583 "origin", origin.String(), // When origin and caller match it means self removal
    584 "boardID", boardID.String(),
    585 "member", member.String(),
    586 )
    587 }
    588
    589 // Members can remove themselves without permission
    590 if origin == member {
    591 removeMember()
    592 return
    593 }
    594
    595 args := boards.Args{boardID, member}
    596 perms.WithPermission(caller, PermissionMemberRemove, args, func() {
    597 assertMembersUpdateIsEnabled(boardID)
    598 removeMember()
    599 })
    600}
    601
    602// IsMember checks if a user is a member of the realm or a board.
    603//
    604// Board ID is only required when checking if a user is a member of a board.
    605func IsMember(boardID boards.ID, user address) bool {
    606 assertUserAddressIsValid(user)
    607
    608 if boardID != 0 {
    609 board := mustGetBoard(boardID)
    610 assertBoardIsNotFrozen(board)
    611 }
    612
    613 perms := mustGetPermissions(boardID)
    614 return perms.HasUser(user)
    615}
    616
    617// HasMemberRole checks if a realm or board member has a specific role assigned.
    618//
    619// Board ID is only required when checking a member of a board.
    620func HasMemberRole(boardID boards.ID, member address, role boards.Role) bool {
    621 assertMemberAddressIsValid(member)
    622
    623 if boardID != 0 {
    624 board := mustGetBoard(boardID)
    625 assertBoardIsNotFrozen(board)
    626 }
    627
    628 perms := mustGetPermissions(boardID)
    629 return perms.HasRole(member, role)
    630}
    631
    632// ChangeMemberRole changes the role of a realm or board member.
    633//
    634// Board ID is only required when changing the role for a member of a board.
    635func ChangeMemberRole(cur realm, boardID boards.ID, member address, role boards.Role) {
    636 assertMemberAddressIsValid(member)
    637 assertMembersUpdateIsEnabled(boardID)
    638
    639 if role == "" {
    640 role = RoleGuest
    641 }
    642
    643 perms := mustGetPermissions(boardID)
    644 caller := cur.Previous().Address()
    645 args := boards.Args{caller, boardID, member, role}
    646 perms.WithPermission(caller, PermissionRoleChange, args, func() {
    647 assertMembersUpdateIsEnabled(boardID)
    648
    649 perms.SetUserRoles(member, role)
    650
    651 chain.Emit(
    652 "RoleChanged",
    653 "caller", caller.String(),
    654 "boardID", boardID.String(),
    655 "member", member.String(),
    656 "newRole", string(role),
    657 )
    658 })
    659}
    660
    661func assertMemberAddressIsValid(member address) {
    662 if !member.IsValid() {
    663 panic("invalid member address: " + member.String())
    664 }
    665}
    666
    667func assertUserAddressIsValid(user address) {
    668 if !user.IsValid() {
    669 panic("invalid user address: " + user.String())
    670 }
    671}
    672
    673func assertBoardExists(id boards.ID) {
    674 if id == 0 { // ID zero is used to refer to the realm
    675 return
    676 }
    677
    678 if _, found := gBoards.Get(id); !found {
    679 panic("board not found: " + id.String())
    680 }
    681}
    682
    683func assertBoardIsNotFrozen(b *boards.Board) {
    684 if b.Readonly {
    685 panic("board is frozen")
    686 }
    687}
    688
    689func assertIsValidBoardName(name string) {
    690 size := len(name)
    691 if size == 0 {
    692 panic("board name is empty")
    693 }
    694
    695 if size < 3 {
    696 panic("board name is too short, minimum length is 3 characters")
    697 }
    698
    699 if size > MaxBoardNameLength {
    700 n := strconv.Itoa(MaxBoardNameLength)
    701 panic("board name is too long, maximum allowed is " + n + " characters")
    702 }
    703
    704 if !reBoardName.MatchString(name) {
    705 panic("board name must start with a letter and have letters, numbers, \"-\" and \"_\"")
    706 }
    707}
    708
    709func assertThreadIsNotFrozen(t *boards.Post) {
    710 if t.Readonly {
    711 panic("thread is frozen")
    712 }
    713}
    714
    715func assertNameIsNotEmpty(name string) {
    716 if name == "" {
    717 panic("name is empty")
    718 }
    719}
    720
    721func assertTitleIsValid(title string) {
    722 if title == "" {
    723 panic("title is empty")
    724 }
    725
    726 if len(title) > MaxThreadTitleLength {
    727 n := strconv.Itoa(MaxThreadTitleLength)
    728 panic("title is too long, maximum allowed is " + n + " characters")
    729 }
    730}
    731
    732func assertBodyIsNotEmpty(body string) {
    733 if body == "" {
    734 panic("body is empty")
    735 }
    736}
    737
    738func assertBoardNameNotExists(name string) {
    739 name = strings.ToLower(name)
    740 if _, found := gBoards.GetByName(name); found {
    741 panic("board already exists")
    742 }
    743}
    744
    745func assertThreadExists(b *boards.Board, threadID boards.ID) {
    746 if _, found := getThread(b, threadID); !found {
    747 panic("thread not found: " + threadID.String())
    748 }
    749}
    750
    751func assertReplyExists(thread *boards.Post, replyID boards.ID) {
    752 if _, found := getReply(thread, replyID); !found {
    753 panic("reply not found: " + replyID.String())
    754 }
    755}
    756
    757func assertThreadIsVisible(thread *boards.Post) {
    758 if thread.Hidden {
    759 panic("thread is hidden")
    760 }
    761}
    762
    763func assertReplyIsVisible(thread *boards.Post) {
    764 if thread.Hidden {
    765 panic("reply is hidden")
    766 }
    767}
    768
    769func assertThreadBodyIsValid(body string) {
    770 if len(body) > MaxThreadBodyLength {
    771 n := strconv.Itoa(MaxThreadBodyLength)
    772 panic("thread body is too long, maximum allowed is " + n + " characters")
    773 }
    774}
    775
    776func assertReplyBodyIsValid(body string) {
    777 assertBodyIsNotEmpty(body)
    778
    779 if len(body) > MaxReplyLength {
    780 n := strconv.Itoa(MaxReplyLength)
    781 panic("reply is too long, maximum allowed is " + n + " characters")
    782 }
    783
    784 // No markdown-structure or gno-form blacklist here: reply bodies
    785 // render inside a <gno-foreign> sandbox (see indentForeignBody),
    786 // which contains block structure and omits forms at render time.
    787}
    788
    789func assertMembersUpdateIsEnabled(boardID boards.ID) {
    790 if boardID != 0 {
    791 assertRealmIsNotLocked()
    792 } else {
    793 assertRealmMembersAreNotLocked()
    794 }
    795}
    796