events.gno
4.67 Kb ยท 198 lines
1// Package events allows you to upload data about specific IRL/online events
2// It includes dynamic support for updating rendering events based on their
3// status, ie if they are upcoming, in progress, or in the past.
4package events
5
6import (
7 "chain"
8 "sort"
9 "strings"
10 "time"
11
12 "gno.land/p/nt/ownable"
13 "gno.land/p/nt/seqid"
14 "gno.land/p/nt/ufmt"
15)
16
17type (
18 Event struct {
19 id string
20 name string // name of event
21 description string // short description of event
22 link string // link to auth corresponding web2 page, ie eventbrite/luma or conference page
23 location string // location of the event
24 startTime time.Time // given in RFC3339
25 endTime time.Time // end time of the event, given in RFC3339
26 }
27
28 eventsSlice []*Event
29)
30
31var (
32 Ownable = ownable.NewWithAddress(address("g125em6arxsnj49vx35f0n0z34putv5ty3376fg5")) // @leohhhn
33 events = make(eventsSlice, 0) // sorted
34 idCounter seqid.ID
35)
36
37const (
38 maxDescLength = 100
39 EventAdded = "EventAdded"
40 EventDeleted = "EventDeleted"
41 EventEdited = "EventEdited"
42)
43
44// AddEvent adds auth new event
45// Start time & end time need to be specified in RFC3339, ie 2024-08-08T12:00:00+02:00
46func AddEvent(_ realm, name, description, link, location, startTime, endTime string) (string, error) {
47 Ownable.AssertOwnedByPrevious()
48
49 if strings.TrimSpace(name) == "" {
50 return "", ErrEmptyName
51 }
52
53 if len(description) > maxDescLength {
54 return "", ufmt.Errorf("%s: provided length is %d, maximum is %d", ErrDescriptionTooLong, len(description), maxDescLength)
55 }
56
57 // Parse times
58 st, et, err := parseTimes(startTime, endTime)
59 if err != nil {
60 return "", err
61 }
62
63 id := idCounter.Next().String()
64 e := &Event{
65 id: id,
66 name: name,
67 description: description,
68 link: link,
69 location: location,
70 startTime: st,
71 endTime: et,
72 }
73
74 events = append(events, e)
75 sort.Sort(events)
76
77 chain.Emit(EventAdded,
78 "id", e.id,
79 )
80
81 return id, nil
82}
83
84// DeleteEvent deletes an event with auth given ID
85func DeleteEvent(_ realm, id string) {
86 Ownable.AssertOwnedByPrevious()
87
88 e, idx, err := GetEventByID(id)
89 if err != nil {
90 panic(err)
91 }
92
93 events = append(events[:idx], events[idx+1:]...)
94
95 chain.Emit(EventDeleted,
96 "id", e.id,
97 )
98}
99
100// EditEvent edits an event with auth given ID
101// It only updates values corresponding to non-empty arguments sent with the call
102// Note: if you need to update the start time or end time, you need to provide both every time
103func EditEvent(_ realm, id string, name, description, link, location, startTime, endTime string) {
104 Ownable.AssertOwnedByPrevious()
105
106 e, _, err := GetEventByID(id)
107 if err != nil {
108 panic(err)
109 }
110
111 // Set only valid values
112 if strings.TrimSpace(name) != "" {
113 e.name = name
114 }
115
116 if strings.TrimSpace(description) != "" {
117 e.description = description
118 }
119
120 if strings.TrimSpace(link) != "" {
121 e.link = link
122 }
123
124 if strings.TrimSpace(location) != "" {
125 e.location = location
126 }
127
128 if strings.TrimSpace(startTime) != "" || strings.TrimSpace(endTime) != "" {
129 st, et, err := parseTimes(startTime, endTime)
130 if err != nil {
131 panic(err) // need to also revert other state changes
132 }
133
134 oldStartTime := e.startTime
135 e.startTime = st
136 e.endTime = et
137
138 // If sort order was disrupted, sort again
139 if oldStartTime != e.startTime {
140 sort.Sort(events)
141 }
142 }
143
144 chain.Emit(EventEdited,
145 "id", e.id,
146 )
147}
148
149func GetEventByID(id string) (*Event, int, error) {
150 for i, event := range events {
151 if event.id == id {
152 return event, i, nil
153 }
154 }
155
156 return nil, -1, ErrNoSuchID
157}
158
159// Len returns the length of the slice
160func (m eventsSlice) Len() int {
161 return len(m)
162}
163
164// Less compares the startTime fields of two elements
165// In this case, events will be sorted by largest startTime first (upcoming > past)
166func (m eventsSlice) Less(i, j int) bool {
167 return m[i].startTime.After(m[j].startTime)
168}
169
170// Swap swaps two elements in the slice
171func (m eventsSlice) Swap(i, j int) {
172 m[i], m[j] = m[j], m[i]
173}
174
175// parseTimes parses the start and end time for an event and checks for possible errors
176func parseTimes(startTime, endTime string) (time.Time, time.Time, error) {
177 st, err := time.Parse(time.RFC3339, startTime)
178 if err != nil {
179 return time.Time{}, time.Time{}, ufmt.Errorf("%s: %s", ErrInvalidStartTime, err.Error())
180 }
181
182 et, err := time.Parse(time.RFC3339, endTime)
183 if err != nil {
184 return time.Time{}, time.Time{}, ufmt.Errorf("%s: %s", ErrInvalidEndTime, err.Error())
185 }
186
187 if et.Before(st) {
188 return time.Time{}, time.Time{}, ErrEndBeforeStart
189 }
190
191 _, stOffset := st.Zone()
192 _, etOffset := et.Zone()
193 if stOffset != etOffset {
194 return time.Time{}, time.Time{}, ErrStartEndTimezonemMismatch
195 }
196
197 return st, et, nil
198}