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