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

kudos.gno

2.67 Kb · 115 lines
  1package kudos
  2
  3import (
  4	"chain"
  5	"chain/runtime/unsafe"
  6	"sort"
  7	"strconv"
  8	"strings"
  9
 10	"gno.land/p/nt/avl/v0"
 11)
 12
 13// counts maps an address (as string) -> number of kudos received.
 14var counts avl.Tree
 15
 16// feed holds recent kudos, newest first, capped at feedCap entries.
 17var feed []kudo
 18
 19const feedCap = 50
 20
 21type kudo struct {
 22	from   address
 23	to     address
 24	reason string
 25}
 26
 27// Give records a kudos from the caller to `to` with a reason.
 28// A caller cannot give kudos to themselves.
 29func Give(cur realm, to address, reason string) {
 30	from := unsafe.PreviousRealm().Address()
 31	if !to.IsValid() {
 32		panic("invalid recipient address")
 33	}
 34	if from == to {
 35		panic("cannot give kudos to yourself")
 36	}
 37	reason = strings.TrimSpace(reason)
 38	if reason == "" {
 39		panic("reason must not be empty")
 40	}
 41
 42	key := to.String()
 43	n := int64(0)
 44	if v := counts.Get(key); v != nil {
 45		n = v.(int64)
 46	}
 47	counts.Set(key, n+1)
 48
 49	// prepend newest, cap the feed
 50	feed = append([]kudo{{from: from, to: to, reason: reason}}, feed...)
 51	if len(feed) > feedCap {
 52		feed = feed[:feedCap]
 53	}
 54
 55	chain.Emit("KudosGiven", "from", from.String(), "to", to.String(), "reason", reason)
 56}
 57
 58// entry is a leaderboard row used for deterministic sorting.
 59type entry struct {
 60	addr  string
 61	count int64
 62}
 63
 64// byScore sorts by count desc, then address asc for stable ties.
 65type byScore []entry
 66
 67func (s byScore) Len() int      { return len(s) }
 68func (s byScore) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
 69func (s byScore) Less(i, j int) bool {
 70	if s[i].count != s[j].count {
 71		return s[i].count > s[j].count
 72	}
 73	return s[i].addr < s[j].addr
 74}
 75
 76func leaderboard() []entry {
 77	out := make([]entry, 0, counts.Size())
 78	counts.Iterate("", "", func(key string, value any) bool {
 79		out = append(out, entry{addr: key, count: value.(int64)})
 80		return false
 81	})
 82	sort.Sort(byScore(out))
 83	return out
 84}
 85
 86// Render shows a leaderboard of most-received addresses and a recent feed.
 87func Render(path string) string {
 88	var b strings.Builder
 89	b.WriteString("# Kudos\n\n")
 90	b.WriteString("Give props to addresses. `Give(to, reason)` records a kudos.\n\n")
 91
 92	b.WriteString("## Leaderboard\n\n")
 93	lb := leaderboard()
 94	if len(lb) == 0 {
 95		b.WriteString("_No kudos yet._\n\n")
 96	} else {
 97		b.WriteString("| # | Address | Kudos |\n")
 98		b.WriteString("| --- | --- | --- |\n")
 99		for i, e := range lb {
100			b.WriteString("| " + strconv.Itoa(i+1) + " | " + e.addr + " | " + strconv.FormatInt(e.count, 10) + " |\n")
101		}
102		b.WriteString("\n")
103	}
104
105	b.WriteString("## Recent kudos\n\n")
106	if len(feed) == 0 {
107		b.WriteString("_Nothing here yet._\n")
108	} else {
109		for _, k := range feed {
110			b.WriteString("- " + k.from.String() + " → " + k.to.String() + ": " + k.reason + "\n")
111		}
112	}
113
114	return b.String()
115}