Search Apps Documentation Source Content File Folder Download Copy Actions Download

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, ok := counts.Get(key); ok {
 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}