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