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.

api.gno

5.26 Kb · 191 lines
  1package reputation
  2
  3import (
  4	"net/url"
  5	"strconv"
  6	"strings"
  7
  8	"gno.land/p/g1lnkytfqcjwllws63gvf0mv9yt04aswy4y9amhm/guildhall/jsonw/v0"
  9	"gno.land/p/g1lnkytfqcjwllws63gvf0mv9yt04aswy4y9amhm/guildhall/trust/v0"
 10	"gno.land/p/nt/avl/v0"
 11)
 12
 13const apiMaxLimit = 50
 14
 15// renderAPI serves JSON for the web app, under Render("api/..."):
 16//
 17//	api/stats                                   totals, parameters, admin
 18//	api/top?limit=                              leaderboard
 19//	api/profile/<address>                       one contributor
 20//	api/records?addr=&kind=work|reviews&page=&limit=   newest first; all records without addr
 21//	api/record/<id>                             one record
 22//	api/seeds                                   seed reviewers
 23func renderAPI(route string, q url.Values) string {
 24	parts := strings.Split(route, "/")
 25	switch {
 26	case route == "api/stats":
 27		return jsonw.Obj(
 28			"records", jsonw.Int(int64(records.Size())),
 29			"contributors", jsonw.Int(int64(leaderboard.Size())),
 30			"seeds", jsonw.Int(int64(seeds.Size())),
 31			"admin", jsonw.String(Admin().String()),
 32			"params", jsonw.Obj(
 33				"base", jsonw.Int(params.Base),
 34				"trustThreshold", jsonw.Int(params.TrustThreshold),
 35				"trustFloorBps", jsonw.Int(params.TrustFloorBps),
 36				"selfFundedFactorBps", jsonw.Int(params.SelfFundedFactorBps),
 37			),
 38			"writers", jsonw.Strings(keys(&writers)),
 39		)
 40	case route == "api/top":
 41		var items []string
 42		for i, s := range Top(apiLimit(q)) {
 43			items = append(items, jsonw.Obj(
 44				"rank", jsonw.Int(int64(i+1)),
 45				"address", jsonw.String(s.Addr.String()),
 46				"score", jsonw.Int(s.Score),
 47				"level", jsonw.String(trust.Level(s.Score)),
 48				"completed", jsonw.Int(int64(CompletedCount(s.Addr))),
 49				"reviews", jsonw.Int(int64(ReviewCount(s.Addr))),
 50				"seed", jsonw.Bool(IsSeed(s.Addr)),
 51			))
 52		}
 53		return jsonw.Obj("items", jsonw.Arr(items))
 54	case len(parts) == 3 && parts[1] == "profile":
 55		return apiProfile(address(parts[2]))
 56	case route == "api/records":
 57		return apiRecords(q)
 58	case len(parts) == 3 && parts[1] == "record":
 59		id, err := strconv.ParseUint(parts[2], 10, 64)
 60		if err != nil {
 61			return apiError("invalid id")
 62		}
 63		r := getRecord(id)
 64		if r == nil {
 65			return apiError("not found")
 66		}
 67		return recordJSON(r)
 68	case route == "api/seeds":
 69		return jsonw.Obj("items", jsonw.Strings(keys(&seeds)))
 70	}
 71	return apiError("unknown endpoint")
 72}
 73
 74func apiProfile(addr address) string {
 75	if !addr.IsValid() {
 76		return apiError("invalid address")
 77	}
 78	p := getProfile(addr)
 79	var earned []string
 80	score, first, last := int64(0), jsonw.Null, jsonw.Null
 81	if p != nil {
 82		score, first, last = p.Score, jsonw.Time(p.First), jsonw.Time(p.Last)
 83		if p.earned != nil {
 84			p.earned.Iterate("", "", func(k string, v any) bool {
 85				earned = append(earned, jsonw.Obj("denom", jsonw.String(k), "amount", jsonw.Int(v.(int64))))
 86				return false
 87			})
 88		}
 89	}
 90	return jsonw.Obj(
 91		"address", jsonw.String(addr.String()),
 92		"score", jsonw.Int(score),
 93		"level", jsonw.String(trust.Level(score)),
 94		"rank", jsonw.Int(int64(Rank(addr))),
 95		"seed", jsonw.Bool(IsSeed(addr)),
 96		"completed", jsonw.Int(int64(CompletedCount(addr))),
 97		"reviews", jsonw.Int(int64(ReviewCount(addr))),
 98		"earned", jsonw.Arr(earned),
 99		"firstAt", first,
100		"lastAt", last,
101	)
102}
103
104func apiRecords(q url.Values) string {
105	limit := apiLimit(q)
106	page := 1
107	if n, err := strconv.Atoi(q.Get("page")); err == nil && n > 0 {
108		page = n
109	}
110	offset := (page - 1) * limit
111
112	var rs []Record
113	total := 0
114	if a := q.Get("addr"); a != "" {
115		addr := address(a)
116		if !addr.IsValid() {
117			return apiError("invalid address")
118		}
119		if q.Get("kind") == "reviews" {
120			rs = ReviewsOf(addr, offset, limit)
121			total = ReviewCount(addr)
122		} else {
123			rs = RecordsOf(addr, offset, limit)
124			total = CompletedCount(addr)
125		}
126	} else {
127		rs = latest(offset, limit)
128		total = records.Size()
129	}
130	items := make([]string, len(rs))
131	for i := range rs {
132		items[i] = recordJSON(&rs[i])
133	}
134	return jsonw.Obj("items", jsonw.Arr(items), "more", jsonw.Bool(total > page*limit), "total", jsonw.Int(int64(total)))
135}
136
137func recordJSON(r *Record) string {
138	return jsonw.Obj(
139		"id", jsonw.Uint(r.ID),
140		"worker", jsonw.String(r.Worker.String()),
141		"poster", jsonw.String(r.Poster.String()),
142		"reviewers", jsonw.Addresses(r.Reviewers),
143		"reviewerTrustBps", int64s(r.ReviewerTrust),
144		"points", jsonw.Int(r.Points),
145		"shareBps", jsonw.Int(r.ShareBps),
146		"amount", jsonw.Int(r.Amount),
147		"denom", jsonw.String(r.Denom),
148		"title", jsonw.String(r.Title),
149		"link", jsonw.String(r.Link),
150		"ref", jsonw.String(r.Ref),
151		"source", jsonw.String(r.Source),
152		"signedBy", jsonw.String(r.SignedBy.String()),
153		"arbitrated", jsonw.Bool(r.Arbitrated),
154		"height", jsonw.Int(r.Height),
155		"at", jsonw.Time(r.At),
156		"voided", jsonw.Bool(r.Voided),
157		"voidReason", jsonw.String(r.VoidReason),
158	)
159}
160
161func int64s(ns []int64) string {
162	out := make([]string, len(ns))
163	for i, n := range ns {
164		out[i] = jsonw.Int(n)
165	}
166	return jsonw.Arr(out)
167}
168
169func keys(t *avl.Tree) []string {
170	var out []string
171	t.Iterate("", "", func(k string, _ any) bool {
172		out = append(out, k)
173		return false
174	})
175	return out
176}
177
178func apiLimit(q url.Values) int {
179	n, err := strconv.Atoi(q.Get("limit"))
180	if err != nil || n < 1 {
181		return 20
182	}
183	if n > apiMaxLimit {
184		return apiMaxLimit
185	}
186	return n
187}
188
189func apiError(msg string) string {
190	return jsonw.Obj("error", jsonw.String(msg))
191}