Search Apps Documentation Source Content File Folder Download Copy Actions Download

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}