views.gno
7.00 Kb · 217 lines
1package curation
2
3import (
4 "chain/runtime"
5 "strconv"
6 "strings"
7
8 "gno.land/p/nt/avl/v0"
9 "gno.land/p/samcrew/launchpad/meta/v1"
10 "gno.land/r/samcrew/launchpad/nft/v1"
11)
12
13// int64 values are decimal strings so a JSON client cannot round them.
14func jsonInt(n int64) string { return meta.JSONString(strconv.FormatInt(n, 10)) }
15
16func StateJSON() string {
17 return "{" +
18 `"admin":` + meta.JSONString(admin.String()) +
19 `,"pendingAdmin":` + meta.JSONString(pendingAdmin.String()) +
20 `,"activeManagers":` + strconv.Itoa(ActiveManagerCount()) +
21 `,"maxSeats":` + strconv.Itoa(maxSeats) + "}"
22}
23
24// ManagersJSON lists the active seats, ordered by account.
25func ManagersJSON() string {
26 var b strings.Builder
27 b.WriteByte('[')
28 at := now()
29 seats.Iterate("", "", func(account string, v any) bool {
30 s := v.(seat)
31 if at < s.until {
32 if b.Len() > 1 {
33 b.WriteByte(',')
34 }
35 b.WriteString(`{"account":` + meta.JSONString(account) +
36 `,"lead":` + strconv.FormatBool(s.lead) +
37 `,"until":` + jsonInt(s.until) + "}")
38 }
39 return false
40 })
41 b.WriteByte(']')
42 return b.String()
43}
44
45// ApplicationJSON returns null for a collection nobody applied for.
46func ApplicationJSON(collection string) string {
47 a := applicationOf(collection)
48 if a == nil {
49 return "null"
50 }
51 return "{" +
52 `"collection":` + meta.JSONString(collection) +
53 `,"founder":` + meta.JSONString(a.Founder.String()) +
54 `,"statementHash":` + meta.JSONString(a.StatementHash) +
55 `,"statementCID":` + meta.JSONString(a.StatementCID) +
56 `,"revision":` + jsonInt(a.Revision) +
57 `,"status":` + meta.JSONString(a.Status) +
58 `,"reviewer":` + meta.JSONString(a.Reviewer.String()) +
59 `,"reasonHash":` + meta.JSONString(a.ReasonHash) +
60 `,"reasonCID":` + meta.JSONString(a.ReasonCID) +
61 `,"updatedAt":` + jsonInt(a.UpdatedAt) + "}"
62}
63
64// pageJSON renders a zero-based page of a tree in key order; the page size is
65// 1..50.
66func pageJSON(tree *avl.Tree, page, size int64, item func(v any) string) string {
67 if page < 0 || size < 1 || size > 50 {
68 panic("launchpad/curation: invalid page")
69 }
70 var b strings.Builder
71 b.WriteByte('[')
72 // page < total comes first so that page*size cannot overflow.
73 if total := int64(tree.Size()); page < total && page*size < total {
74 tree.IterateByOffset(int(page*size), int(size), func(_ string, v any) bool {
75 if b.Len() > 1 {
76 b.WriteByte(',')
77 }
78 b.WriteString(item(v))
79 return false
80 })
81 }
82 b.WriteByte(']')
83 return b.String()
84}
85
86// ApplicationsJSON pages through the applications in order of first filing.
87func ApplicationsJSON(page, size int64) string {
88 return pageJSON(&filings, page, size, func(v any) string {
89 return ApplicationJSON(v.(string))
90 })
91}
92
93// AccessJSON tells a private inbox what an account is to a collection right
94// now: whether it holds the creator role ("founder"), an active manager, and
95// whether Conflicted bars it from acting as one. It is public data to be read
96// on every request, not a token.
97//
98// The answer also says where and when it was evaluated: the chain ID, the
99// block height and the block time. The answer is not signed: checking them
100// exposes an honest node on the wrong chain or behind it, and proves nothing
101// against a node that lies. Its eight fields are a contract with that reader.
102func AccessJSON(collection, who string) string {
103 account := address(who)
104 checkAccount(account)
105 info := nft.GetInfo(collection)
106 return "{" +
107 `"chainId":` + meta.JSONString(runtime.ChainID()) +
108 `,"height":` + jsonInt(runtime.ChainHeight()) +
109 `,"time":` + jsonInt(now()) +
110 `,"collection":` + meta.JSONString(collection) +
111 `,"account":` + meta.JSONString(who) +
112 `,"founder":` + strconv.FormatBool(info.Creator == account) +
113 `,"manager":` + strconv.FormatBool(active(account)) +
114 `,"conflicted":` + strconv.FormatBool(conflicted(info, account)) + "}"
115}
116
117func featureJSON(collection string) string {
118 f := featureOf(collection)
119 if f == nil {
120 return "null"
121 }
122 return "{" +
123 `"proposer":` + meta.JSONString(f.proposer.String()) +
124 `,"approver":` + meta.JSONString(f.approver.String()) +
125 `,"reasonHash":` + meta.JSONString(f.reasonHash) +
126 `,"reasonCID":` + meta.JSONString(f.reasonCID) +
127 `,"until":` + jsonInt(f.until) + "}"
128}
129
130func holdJSON(collection string) string {
131 h := holdOf(collection)
132 if h == nil {
133 return "null"
134 }
135 return "{" +
136 `"actor":` + meta.JSONString(h.actor.String()) +
137 `,"confirmer":` + meta.JSONString(h.confirmer.String()) +
138 `,"extended":` + strconv.FormatBool(h.extended) +
139 `,"reasonHash":` + meta.JSONString(h.reasonHash) +
140 `,"reasonCID":` + meta.JSONString(h.reasonCID) +
141 `,"until":` + jsonInt(h.until) + "}"
142}
143
144func verificationJSON(collection string) string {
145 v := verifications.Get(collection)
146 if v == nil {
147 return "null"
148 }
149 r := v.(verification)
150 return "{" +
151 `"verified":` + strconv.FormatBool(r.verified) +
152 `,"reasonHash":` + meta.JSONString(r.reasonHash) +
153 `,"reasonCID":` + meta.JSONString(r.reasonCID) +
154 `,"updatedAt":` + jsonInt(r.updatedAt) + "}"
155}
156
157func appealJSON(key string) string {
158 a := appealOf(key)
159 if a == nil {
160 return "null"
161 }
162 return "{" +
163 `"collection":` + meta.JSONString(a.collection) +
164 `,"subject":` + meta.JSONString(a.subject) +
165 `,"statementHash":` + meta.JSONString(a.statementHash) +
166 `,"statementCID":` + meta.JSONString(a.statementCID) +
167 `,"filedAt":` + jsonInt(a.filedAt) +
168 `,"open":` + strconv.FormatBool(a.openKey != "") +
169 `,"upheld":` + strconv.FormatBool(a.upheld) +
170 `,"reasonHash":` + meta.JSONString(a.reasonHash) +
171 `,"reasonCID":` + meta.JSONString(a.reasonCID) +
172 `,"resolvedAt":` + jsonInt(a.resolvedAt) + "}"
173}
174
175// CollectionJSON is a collection's editorial record: what a client may show
176// now, then the records behind it and the latest appeal on each subject. A
177// part with no record is null.
178func CollectionJSON(collection string) string {
179 nft.GetInfo(collection)
180 var b strings.Builder
181 b.WriteString("{" +
182 `"collection":` + meta.JSONString(collection) +
183 `,"verified":` + strconv.FormatBool(IsVerified(collection)) +
184 `,"featured":` + strconv.FormatBool(IsFeatured(collection)) +
185 `,"hidden":` + strconv.FormatBool(IsHidden(collection)) +
186 `,"verification":` + verificationJSON(collection) +
187 `,"feature":` + featureJSON(collection) +
188 `,"hold":` + holdJSON(collection) +
189 `,"appeals":{`)
190 for i, subject := range subjects {
191 if i > 0 {
192 b.WriteByte(',')
193 }
194 b.WriteString(meta.JSONString(subject) + ":" + appealJSON(collection+"/"+subject))
195 }
196 b.WriteString("}}")
197 return b.String()
198}
199
200// AppealsJSON pages through the open appeals in filing order.
201func AppealsJSON(page, size int64) string {
202 return pageJSON(&openAppeals, page, size, func(v any) string {
203 return appealJSON(v.(string))
204 })
205}
206
207// FeaturesJSON pages through every collection that has a feature slot on
208// record, in ID text order, saying whether it is featured right now.
209func FeaturesJSON(page, size int64) string {
210 return pageJSON(&features, page, size, func(v any) string {
211 f := v.(*feature)
212 return "{" +
213 `"collection":` + meta.JSONString(f.collection) +
214 `,"featured":` + strconv.FormatBool(IsFeatured(f.collection)) +
215 `,"until":` + jsonInt(f.until) + "}"
216 })
217}