Search Apps Documentation Source Content File Folder Download Copy Actions Download 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}