Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

home.gno

3.51 Kb · 145 lines
  1// Package home is samcrew's page on gno.land, served at /u/samcrew.
  2//
  3// The page is assembled from SLOTS (named markdown fragments, one Set each);
  4// the slot named "layout" is the template and every :slug: in it is filled in
  5// a single pass. The crew roster is not a slot: see crew.gno.
  6//
  7// The admin starts as the samcrew namespace multisig and moves with a
  8// two-step ProposeAdmin / AcceptAdmin, so it can later be handed to the DAO.
  9package home
 10
 11import (
 12	"strconv"
 13	"strings"
 14
 15	"chain/runtime"
 16
 17	"gno.land/p/nt/avl/v0"
 18)
 19
 20const (
 21	layoutSlug = "layout"
 22	maxSlugLen = 64
 23)
 24
 25type slot struct {
 26	body    string
 27	rev     int
 28	updated int64
 29}
 30
 31var (
 32	admin        = address("g136j0m08pkm2lwwde9dmlx8uee26llent9s5cpf") // samcrew namespace, 2-of-3
 33	pendingAdmin address
 34
 35	slots = avl.NewTree() // slug -> *slot
 36	rev   int
 37)
 38
 39var reservedSlugs = []string{"chainid", "crew", "height", "hero", "realm", "rev"}
 40
 41func init() {
 42	h := runtime.ChainHeight()
 43	for _, s := range defaultSlots() {
 44		rev++
 45		slots.Set(s.slug, &slot{body: s.body, rev: rev, updated: h})
 46	}
 47	for _, g := range defaultGalleries() {
 48		galleries.Set(g.name, parseGallery(g.spec))
 49	}
 50	for _, m := range defaultCrew() {
 51		addMember(m.id, m.handle, m.role, m.team, m.url)
 52	}
 53	for _, p := range defaultProfiles() {
 54		m := mustMember(p.id)
 55		m.addr, m.home, m.line = p.addr, p.home, p.line
 56		byAddr.Set(p.addr.String(), p.id)
 57	}
 58}
 59
 60// ---------------------------------------------------------------------------
 61// Admin
 62
 63func assertAdmin(cur realm) {
 64	if cur.Previous().Address() != admin {
 65		panic("restricted to admin")
 66	}
 67}
 68
 69// Admin returns the current admin address.
 70func Admin() address { return admin }
 71
 72// ProposeAdmin starts a handover. Nothing changes until the new address calls
 73// AcceptAdmin, so a typo cannot lock the page.
 74func ProposeAdmin(cur realm, next address) {
 75	assertAdmin(cur)
 76	if !next.IsValid() {
 77		panic("invalid address")
 78	}
 79	pendingAdmin = next
 80}
 81
 82// AcceptAdmin completes a handover started by ProposeAdmin.
 83func AcceptAdmin(cur realm) {
 84	caller := cur.Previous().Address()
 85	if pendingAdmin == "" || caller != pendingAdmin {
 86		panic("restricted to the proposed admin")
 87	}
 88	admin = caller
 89	pendingAdmin = ""
 90}
 91
 92// ---------------------------------------------------------------------------
 93// Slots
 94
 95func validSlug(slug string) bool {
 96	if len(slug) == 0 || len(slug) > maxSlugLen {
 97		return false
 98	}
 99	for i := 0; i < len(slug); i++ {
100		c := slug[i]
101		if !(c >= 'a' && c <= 'z' || c >= '0' && c <= '9' || c == '-' || c == '_' || c == '.') {
102			return false
103		}
104	}
105	return true
106}
107
108func assertWritableSlug(slug string) {
109	if !validSlug(slug) {
110		panic("invalid slug: want 1-" + strconv.Itoa(maxSlugLen) + " bytes of [a-z0-9._-], got " + strconv.Quote(slug))
111	}
112	for _, r := range reservedSlugs {
113		if slug == r {
114			panic("reserved slug: " + slug)
115		}
116	}
117	if strings.HasPrefix(slug, galleryPrefix) {
118		panic("reserved slug: " + galleryPrefix + "* renders a gallery, use SetGallery")
119	}
120}
121
122// Set creates or replaces one slot.
123func Set(cur realm, slug, body string) {
124	assertAdmin(cur)
125	assertWritableSlug(slug)
126	rev++
127	slots.Set(slug, &slot{body: body, rev: rev, updated: runtime.ChainHeight()})
128}
129
130// Delete removes a slot. Deleting "layout" falls back to the built-in layout.
131func Delete(cur realm, slug string) {
132	assertAdmin(cur)
133	if _, removed := slots.Remove(slug); !removed {
134		panic("no such slot: " + slug)
135	}
136	rev++
137}
138
139// Get returns a slot body, or "" when the slot does not exist.
140func Get(slug string) string {
141	if v := slots.Get(slug); v != nil {
142		return v.(*slot).body
143	}
144	return ""
145}