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

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

home.gno

4.09 Kb · 154 lines
  1// Package home is zôÖma's profile page on gno.land.
  2//
  3// gnoweb renders a profile by calling Render("") on the realm at exactly
  4// /r/<namespace>/home, so this path carries no /vN suffix.
  5//
  6// The page is assembled from SLOTS: named markdown fragments, one Set
  7// transaction each. The slot named "layout" is the template; every :slug:
  8// placeholder in it is replaced by the slot of that name, in a single pass.
  9// init seeds every slot from content.gno, so the page is complete the block
 10// it goes live and later edits are data, not redeploys.
 11//
 12// One thing on this page is not written by its owner: visitors can send a
 13// heart (<3), one per address, and the page counts them.
 14package home
 15
 16import (
 17	"strconv"
 18	"strings"
 19
 20	"chain/runtime"
 21
 22	"gno.land/p/nt/avl/v0"
 23)
 24
 25// admin is the only address allowed to edit slots.
 26const admin = address("g1747t5m2f08plqjlrjk2q0qld7465hxz8gkx59c")
 27
 28const (
 29	layoutSlug = "layout"
 30	maxSlugLen = 64
 31	maxRecent  = 7
 32)
 33
 34type slot struct {
 35	body    string
 36	rev     int
 37	updated int64
 38}
 39
 40var (
 41	slots = avl.NewTree() // slug -> *slot
 42	rev   int             // total slot writes
 43
 44	hearts = avl.NewTree() // address string -> int64 height of the heart
 45	recent []address        // newest first, at most maxRecent
 46)
 47
 48// reservedSlugs are placeholders computed at render time. A slot may not take
 49// one of these names, so nothing a slot says can shadow chain state.
 50var reservedSlugs = []string{"chainid", "height", "hearts", "hero", "realm", "rev"}
 51
 52func init() {
 53	for _, s := range defaultSlots {
 54		rev++
 55		slots.Set(s.slug, &slot{body: s.body, rev: rev, updated: runtime.ChainHeight()})
 56	}
 57	for _, g := range defaultGalleries {
 58		galleries.Set(g.name, parseGallery(g.spec))
 59	}
 60}
 61
 62func assertAdmin(cur realm) {
 63	if cur.Previous().Address() != admin {
 64		panic("restricted to admin")
 65	}
 66}
 67
 68func validSlug(slug string) bool {
 69	if len(slug) == 0 || len(slug) > maxSlugLen {
 70		return false
 71	}
 72	for i := 0; i < len(slug); i++ {
 73		c := slug[i]
 74		if !(c >= 'a' && c <= 'z' || c >= '0' && c <= '9' || c == '-' || c == '_' || c == '.') {
 75			return false
 76		}
 77	}
 78	return true
 79}
 80
 81func assertWritableSlug(slug string) {
 82	if !validSlug(slug) {
 83		panic("invalid slug: want 1-" + strconv.Itoa(maxSlugLen) + " bytes of [a-z0-9._-], got " + strconv.Quote(slug))
 84	}
 85	for _, r := range reservedSlugs {
 86		if slug == r {
 87			panic("reserved slug: " + slug)
 88		}
 89	}
 90	if strings.HasPrefix(slug, galleryPrefix) {
 91		panic("reserved slug: " + galleryPrefix + "* renders a gallery, use SetGallery")
 92	}
 93}
 94
 95// Set creates or replaces one slot.
 96func Set(cur realm, slug, body string) {
 97	assertAdmin(cur)
 98	assertWritableSlug(slug)
 99	rev++
100	slots.Set(slug, &slot{body: body, rev: rev, updated: runtime.ChainHeight()})
101}
102
103// Append adds to the end of a slot, creating it when absent, for bodies too
104// large for one transaction.
105func Append(cur realm, slug, body string) {
106	assertAdmin(cur)
107	assertWritableSlug(slug)
108	prev := ""
109	if v := slots.Get(slug); v != nil {
110		prev = v.(*slot).body
111	}
112	rev++
113	slots.Set(slug, &slot{body: prev + body, rev: rev, updated: runtime.ChainHeight()})
114}
115
116// Delete removes a slot. Deleting "layout" falls back to the built-in layout.
117func Delete(cur realm, slug string) {
118	assertAdmin(cur)
119	if _, removed := slots.Remove(slug); !removed {
120		panic("no such slot: " + slug)
121	}
122	rev++
123}
124
125// Get returns a slot body, or "" when the slot does not exist.
126func Get(slug string) string {
127	if v := slots.Get(slug); v != nil {
128		return v.(*slot).body
129	}
130	return ""
131}
132
133// Revision is the total number of slot writes accepted.
134func Revision() int { return rev }
135
136// Heart records one <3 from the caller. Each address can send exactly one.
137func Heart(cur realm) {
138	from := cur.Previous().Address()
139	if hearts.Has(from.String()) {
140		panic("already sent: one <3 per address")
141	}
142	hearts.Set(from.String(), runtime.ChainHeight())
143
144	recent = append([]address{from}, recent...)
145	if len(recent) > maxRecent {
146		recent = recent[:maxRecent]
147	}
148}
149
150// Hearts returns how many distinct addresses sent a <3.
151func Hearts() int { return hearts.Size() }
152
153// HasHeart reports whether addr already sent a <3.
154func HasHeart(addr address) bool { return hearts.Has(addr.String()) }