Search Apps Documentation Source Content File Folder Download Copy

events.gno

4.61 Kb ยท 200 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/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}