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

db.gno

5.66 Kb · 187 lines
  1package tickets
  2
  3import (
  4	"strings"
  5
  6	"gno.land/p/nym-alexiscolin000/gnoradio/store/v0"
  7	"gno.land/p/nym-alexiscolin000/gnoradio/text/v0"
  8	catalog "gno.land/r/nym-alexiscolin000/gnoradio/catalog/v1"
  9	"gno.land/r/nym-alexiscolin000/gnoradio/data"
 10	"gno.land/r/nym-alexiscolin000/gnoradio/tickets/nft"
 11)
 12
 13// The tickets' collections in the data realm (docs/ARCHITECTURE-v1.md,
 14// section 3). P(n) is store.Pad, T(t) a 12-digit unix time. The event and
 15// ticket records are laid out as the permanent NFT realm reads them
 16// (nft.E*, nft.T*).
 17const (
 18	cEvents   = nft.CEvents        // P(id) -> event record
 19	cTickets  = nft.CTickets       // P((id-1)/16) -> 16 ticket records
 20	cByArtist = "tickets/byartist" // P(artist)/P(event) -> ""
 21	cUpcoming = "tickets/upcoming" // T(start)/P(event) -> "" (not cancelled, not hidden)
 22	cAhead    = "tickets/ahead"    // P(artist)/T(start)/P(event) -> "" (not cancelled)
 23	cOwned    = "tickets/owned"    // address -> packed ticket ids held
 24	cBuyers   = "tickets/buyers"   // P(event)/address -> tickets bought
 25	cAttended = "tickets/attended" // address -> concerts checked in at
 26	cMeta     = "tickets/meta"     // "n" -> Rec(events, tickets)
 27	cConfig   = "tickets/config"   // admin, fee
 28	cPresent  = "tickets/present"  // P(ticket) -> Rec(holder, unix time, door code) of its last Present
 29)
 30
 31var collections = []string{cEvents, cTickets, cByArtist, cUpcoming, cAhead, cOwned, cBuyers, cAttended, cMeta, cConfig, cPresent}
 32
 33// setup makes the collections and the first settings, once: a later
 34// release finds them made (it is not the writer in its init).
 35func setup(cur realm, holder address) {
 36	for _, c := range collections {
 37		data.Make(cross(cur), c)
 38	}
 39	if !data.Has(cMeta, "n") {
 40		save(cur, store.Ops{}.Set(cMeta, "n", store.Rec("0", "0")).Set(cConfig, "admin", holder.String()))
 41	}
 42}
 43
 44// save writes ops in one call: data.Set or data.Remove for one op, else one
 45// data.Batch.
 46func save(cur realm, ops store.Ops) {
 47	switch {
 48	case len(ops) == 0:
 49	case len(ops) > 4:
 50		data.Batch(cross(cur), ops)
 51	case ops[0] == store.OpSet:
 52		data.Set(cross(cur), ops[1], ops[2], ops[3])
 53	default:
 54		data.Remove(cross(cur), ops[1], ops[2])
 55	}
 56}
 57
 58func get(c, k string) string {
 59	v, _ := data.Get(c, k)
 60	return v
 61}
 62
 63func upKey(e *Event) string { return store.TimeKey(e.Start) + "/" + store.Pad(e.ID) }
 64
 65func aheadKey(e *Event) string { return store.Pad(e.ArtistID) + "/" + upKey(e) }
 66
 67// idOf reads the event id at the end of an index key.
 68func idOf(k string) int { return text.Atoi(k[strings.LastIndex(k, "/")+1:]) }
 69
 70// count is the number of keys of c with start <= key < end.
 71func count(c, start, end string) int { return data.Index(c, end) - data.Index(c, start) }
 72
 73// ---- counts: tickets/meta "n" ----
 74
 75const (
 76	mEvents = iota
 77	mTickets
 78)
 79
 80func meta() string { return get(cMeta, "n") }
 81
 82func metaCount(m string, i int) int {
 83	if m == "" {
 84		return 0
 85	}
 86	return text.Atoi(store.Field(m, i))
 87}
 88
 89// ---- events ----
 90
 91// decode reads an event record ("" gives nil).
 92func decode(id int, rec string) *Event {
 93	if rec == "" {
 94		return nil
 95	}
 96	f := store.Fields(rec)
 97	return &Event{ID: id, ArtistID: text.Atoi(f[nft.EArtist]), Title: f[nft.ETitle], Venue: f[nft.EVenue], Link: f[nft.ELink],
 98		Start: text.Atoi64(f[nft.EStart]), Price: text.Atoi64(f[nft.EPrice]), Capacity: text.Atoi(f[nft.ECapacity]), Sold: text.Atoi(f[nft.ESold]),
 99		Cancelled: f[nft.ECancelled] != "", Hidden: f[nft.EHidden] != "", Removed: f[nft.ERemoved]}
100}
101
102// record encodes an event in the order of nft.E*.
103func record(e *Event) string {
104	f := make([]string, nft.EFields)
105	f[nft.ESold], f[nft.EArtist], f[nft.EStart], f[nft.EPrice], f[nft.ECapacity] =
106		text.Itoa(e.Sold), text.Itoa(e.ArtistID), text.Itoa64(e.Start), text.Itoa64(e.Price), text.Itoa(e.Capacity)
107	f[nft.ECancelled], f[nft.EHidden], f[nft.ERemoved] = text.Flag(e.Cancelled), text.Flag(e.Hidden), e.Removed
108	f[nft.ETitle], f[nft.EVenue], f[nft.ELink] = e.Title, e.Venue, e.Link
109	return store.Rec(f...)
110}
111
112func getEvent(id int) *Event {
113	if id < 1 {
114		return nil
115	}
116	return decode(id, get(cEvents, store.Pad(id)))
117}
118
119// visible: not hidden by moderation, and its artist profile is visible.
120func visible(e *Event) bool { return e != nil && !e.Hidden && catalog.ArtistVisible(e.ArtistID) }
121
122func mustEvent(id int) *Event {
123	e := getEvent(id)
124	if !visible(e) {
125		panic("tickets: unknown concert")
126	}
127	return e
128}
129
130// ---- tickets: chunks of nft.Chunk records ----
131
132// chunks reads ticket records, each chunk once per call.
133type chunks map[string]string
134
135func (cs chunks) ticket(id int) *Ticket {
136	if id < 1 {
137		return nil
138	}
139	k, i := store.Slot(id, nft.Chunk)
140	ch, ok := cs[k]
141	if !ok {
142		ch = get(cTickets, k)
143		cs[k] = ch
144	}
145	if ch == "" {
146		return nil
147	}
148	rec := store.Field(ch, i)
149	if rec == "" {
150		return nil
151	}
152	return &Ticket{ID: id, Event: text.Atoi(store.Field(rec, nft.TEvent)), Serial: text.Atoi(store.Field(rec, nft.TSerial)),
153		Attended: store.Field(rec, nft.TAttended) != ""}
154}
155
156func getTicket(id int) *Ticket { return chunks{}.ticket(id) }
157
158func mustTicket(id int) *Ticket {
159	tk := getTicket(id)
160	if tk == nil {
161		panic("tickets: unknown ticket")
162	}
163	return tk
164}
165
166func ticketRecord(tk *Ticket) string {
167	return store.Rec(text.Itoa(tk.Event), text.Itoa(tk.Serial), text.Flag(tk.Attended))
168}
169
170// ---- holders' lists ----
171
172// own adds id to addr's list (remove: takes it out) as an op.
173func own(ops store.Ops, addr address, id int, add bool) store.Ops {
174	list := get(cOwned, addr.String())
175	if add {
176		if store.Count(list) >= maxOwned {
177			panic("tickets: this wallet holds " + text.Itoa(maxOwned) + " tickets, the most it can")
178		}
179		list, _ = store.Add(list, id)
180	} else {
181		list, _ = store.Remove(list, id)
182	}
183	if list == "" {
184		return ops.Del(cOwned, addr.String())
185	}
186	return ops.Set(cOwned, addr.String(), list)
187}