Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

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}