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}