events.gno

4.44 Kb ยท 196 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	"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}