read.gno
7.70 Kb · 251 lines
1package tickets
2
3import (
4 "strconv"
5 "strings"
6
7 "gno.land/p/nym-alexiscolin000/gnoradio/store/v0"
8 "gno.land/p/nym-alexiscolin000/gnoradio/text/v0"
9 catalog "gno.land/r/nym-alexiscolin000/gnoradio/catalog/v1"
10 "gno.land/r/nym-alexiscolin000/gnoradio/data"
11 "gno.land/r/nym-alexiscolin000/gnoradio/tickets/nft"
12)
13
14// Read API for the home realm and the GnoRadio app (values only).
15
16// TicketOwner returns the current holder of a ticket.
17func TicketOwner(ticketID int) address {
18 mustTicket(ticketID)
19 return nft.OwnerOf(text.Itoa(ticketID))
20}
21
22// EventInfo returns a copy of a visible concert; ok is false otherwise.
23func EventInfo(id int) (Event, bool) {
24 if e := getEvent(id); visible(e) {
25 return *e, true
26 }
27 return Event{}, false
28}
29
30// HiddenNote says why a concert is hidden: "Removed by moderation: <reason>", or "".
31func HiddenNote(eventID int) string {
32 if e := getEvent(eventID); e != nil && e.Hidden {
33 return "Removed by moderation: " + e.Removed
34 }
35 return ""
36}
37
38// TicketInfo returns a copy of a ticket.
39func TicketInfo(id int) (Ticket, bool) {
40 if tk := getTicket(id); tk != nil {
41 return *tk, true
42 }
43 return Ticket{}, false
44}
45
46func EventCount() int { return metaCount(meta(), mEvents) }
47func TicketCount() int { return metaCount(meta(), mTickets) }
48
49// Attended returns how many concerts a holder checked in at.
50func Attended(holder address) int { return text.Atoi(get(cAttended, holder.String())) }
51
52// PresentedAt is when the ticket's current holder last presented it at the
53// door (Present) with this door code, unix seconds; 0 when they did not, or
54// it changed hands since, or it was checked in. CheckIn takes it for 10
55// minutes.
56func PresentedAt(ticketID int, code string) int64 {
57 rec := get(cPresent, store.Pad(ticketID))
58 if rec == "" || code == "" || address(store.Field(rec, 0)) != nft.Holder(ticketID) || store.Field(rec, 2) != code {
59 return 0
60 }
61 return text.Atoi64(store.Field(rec, 1))
62}
63
64// ServiceFee returns the per-ticket service fee in ugnot (0 while no treasury is set).
65func ServiceFee() int64 { return serviceFee() }
66
67// Admin is the moderator ("" once renounced).
68func Admin() address { return adminAddr() }
69
70// PendingAdmin is the address the moderator role is offered to ("" if none).
71func PendingAdmin() address { return admin.Pending() }
72
73// TicketsOf returns up to limit ticket ids held by owner, newest first.
74func TicketsOf(owner address, offset, limit int) []int {
75 limit = store.Limit(limit, 50)
76 if offset < 0 {
77 offset = 0
78 }
79 return store.Page(get(cOwned, owner.String()), offset, limit, true)
80}
81
82// ArtistEvents returns up to limit visible concert ids of an artist, newest
83// first.
84func ArtistEvents(artistID, limit int) []int {
85 limit = store.Limit(limit, 50)
86 if !catalog.ArtistVisible(artistID) {
87 return nil
88 }
89 var ids []int
90 p := store.Pad(artistID)
91 for end := p + "0"; ; {
92 keys, _ := store.Rows(data.Page(cByArtist, p+"/", end, 100, true))
93 for _, k := range keys {
94 if e := getEvent(idOf(k)); !e.Hidden {
95 if ids = append(ids, e.ID); len(ids) >= limit {
96 return ids
97 }
98 }
99 }
100 if len(keys) < 100 {
101 return ids
102 }
103 end = keys[len(keys)-1]
104 }
105}
106
107// Upcoming returns up to n visible, not cancelled future concerts, soonest
108// first.
109func Upcoming(n int) []Event {
110 n = store.Limit(n, 50)
111 var out []Event
112 eachUpcoming(n, func(e *Event) bool {
113 out = append(out, *e)
114 return len(out) >= n
115 })
116 return out
117}
118
119// PastEvents returns up to limit (1-100) ids of the concerts that have
120// started, were not cancelled and are not hidden, latest first from offset,
121// and how many there are. The start-time index pages them: cancelled or
122// hidden concerts take no room, however many there are.
123func PastEvents(offset, limit int) (ids []int, total int) {
124 limit = store.Limit(limit, 100)
125 total = data.Index(cUpcoming, store.TimeKey(now()+1))
126 if offset < 0 || offset >= total {
127 return nil, total
128 }
129 if offset+limit > total {
130 limit = total - offset
131 }
132 keys, _ := store.Rows(data.PageAt(cUpcoming, data.Size(cUpcoming)-total+offset, limit, true))
133 for _, k := range keys {
134 ids = append(ids, idOf(k))
135 }
136 return ids, total
137}
138
139// eachUpcoming walks the visible future concerts, soonest first, until fn
140// returns true or maxScan index entries were read; want sizes the first
141// page. Past entries are skipped by starting the walk at now.
142func eachUpcoming(want int, fn func(e *Event) bool) {
143 start, scanned, size := store.TimeKey(now()+1), 0, want+5
144 for {
145 if size > 100 {
146 size = 100
147 }
148 keys, _ := store.Rows(data.Page(cUpcoming, start, "", size, false))
149 for _, k := range keys {
150 scanned++
151 if e := getEvent(idOf(k)); visible(e) && !e.Cancelled && fn(e) || scanned >= maxScan {
152 return
153 }
154 }
155 if len(keys) < size {
156 return
157 }
158 start, size = keys[len(keys)-1]+"\x00", 100
159 }
160}
161
162// ---- JSON and art ----
163
164// EventSVG returns the specimen ticket of a visible concert as a markdown
165// image, or "" when the concert is not visible.
166func EventSVG(id int) string { return nft.EventSVG(id) }
167
168// TicketSVG returns a ticket as a markdown image, or "" if unknown.
169func TicketSVG(id int) string { return nft.TicketSVG(id) }
170
171func eventJSON(e *Event, fee string) string {
172 return `{"id":` + text.Itoa(e.ID) + `,"artist":` + text.Itoa(e.ArtistID) + `,"artistName":` + text.Str(nft.ArtistName(e.ArtistID)) +
173 `,"title":` + text.Str(e.Title) + `,"venue":` + text.Str(e.Venue) + `,"link":` + text.Str(e.Link) +
174 `,"start":` + text.Itoa64(e.Start) + `,"price":` + text.Itoa64(e.Price) + `,"capacity":` + text.Itoa(e.Capacity) +
175 `,"sold":` + text.Itoa(e.Sold) + `,"cancelled":` + strconv.FormatBool(e.Cancelled) + `,"fee":` + fee + `}`
176}
177
178// EventsJSON pages through concerts, newest first (limit <= 50): offset and
179// limit count stored concerts, hidden ones are left out of the page, and
180// "next" gives the offset of the following page (0 at the end).
181// upcoming=true lists future, not cancelled, visible concerts instead,
182// soonest first, with offset and limit counting listed concerts ("next" is
183// then a listed-concert offset, 0 when the scan ended).
184func EventsJSON(offset, limit int, upcoming bool) string {
185 limit = store.Limit(limit, 50)
186 if offset < 0 {
187 offset = 0
188 }
189 fee := text.Itoa64(serviceFee())
190 var b strings.Builder
191 b.WriteString(`{"total":` + text.Itoa(EventCount()) + `,"events":[`)
192 n, next := 0, 0
193 add := func(e *Event) {
194 if n > 0 {
195 b.WriteString(",")
196 }
197 b.WriteString(eventJSON(e, fee))
198 n++
199 }
200 if upcoming {
201 // One concert past a full page proves there is a next page.
202 i := 0
203 eachUpcoming(offset+limit+1, func(e *Event) bool {
204 if n >= limit {
205 next = i
206 return true
207 }
208 if i >= offset {
209 add(e)
210 }
211 i++
212 return false
213 })
214 } else {
215 // offset and limit count stored concerts, hidden ones included, so
216 // the page is found in O(log n); hidden ones are left out.
217 keys, recs := store.Rows(data.PageAt(cEvents, offset, limit, true))
218 for i, k := range keys {
219 if e := decode(text.Atoi(k), recs[i]); visible(e) {
220 add(e)
221 }
222 }
223 if offset+len(keys) < data.Size(cEvents) {
224 next = offset + len(keys)
225 }
226 }
227 b.WriteString(`],"next":` + text.Itoa(next) + `}`)
228 return b.String()
229}
230
231// TicketsOfJSON lists the tickets held by owner, newest first (<= 50).
232func TicketsOfJSON(owner address) string {
233 var b strings.Builder
234 b.WriteString(`[`)
235 cs := chunks{}
236 for i, id := range TicketsOf(owner, 0, 50) {
237 tk := cs.ticket(id)
238 if i > 0 {
239 b.WriteString(",")
240 }
241 b.WriteString(`{"id":` + text.Itoa(tk.ID) + `,"event":` + text.Itoa(tk.Event) + `,"serial":` + text.Itoa(tk.Serial) +
242 `,"attended":` + strconv.FormatBool(tk.Attended) + `}`)
243 }
244 b.WriteString(`]`)
245 return b.String()
246}
247
248// FeesJSON returns the ticket service fee and where it goes.
249func FeesJSON() string {
250 return `{"serviceFee":` + text.Itoa64(serviceFee()) + `,"treasury":` + text.Str(catalog.Treasury().String()) + `}`
251}