-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmodel.go
More file actions
493 lines (429 loc) · 14 KB
/
model.go
File metadata and controls
493 lines (429 loc) · 14 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
package cali
import (
"fmt"
"sort"
"strings"
"time"
)
// Event is a single record of an event and also contains links to other events through
// the parent id as well as a ref to the repeat logic used to create the event if it is
// a repeating event.
type Event struct {
// Id is the unique id for this event
Id int64 `json:"id"`
// CalendarId represents the calendar group this event is a part of
CalendarId int64 `json:"calendarId"`
// SourceId represents an id for an external source object that this event is directly tied to
SourceId *int64 `json:"sourceId"`
// ParentId is the id of another event that this event is related to via repeating events
// and can be used to update other related repeating events when this one changes
ParentId *int64 `json:"parentId"`
// OwnerId is the id of the user that created this event
OwnerId int64 `json:"ownerId"`
// EventType represents the overall type of the event. This is just an int, so you can set this
// to what ever you would like
EventType EventType `json:"eventType"`
// Title is the value that will be shown for this event when displayed on a calendar interface
Title string `json:"title"`
// Description is a longer field description of what the event is
Description *string `json:"description"`
// Url is a quick way to set the destination on an event that is clicked on in an interface
Url *string `json:"url"`
// Status represents the current status of the event, defaults to active, but events can also
// be canceled or removed
Status Status `json:"status"`
// IsAllDay is true if the event is an all day event which will set the time values to 00:00
IsAllDay bool `json:"isAllDay"`
// IsRepeating is true if this event is a part of a repeating series
IsRepeating bool `json:"isRepeating"`
// Repeat is the pattern to repeat the event
Repeat *Repeat `json:"repeat"`
// Zone must be a valid time.Location name like "UTC" or "America/New_York"
Zone string `json:"zone"`
// StartDay is the YYYY-MM-DD value representing the start day of this event
StartDay string `json:"startDay"`
// StartTime is the HH:MM value representing the start time of this event
StartTime string `json:"startTime"`
// EndDay is the YYYY-MM-DD value representing the end day of this event
EndDay string `json:"endDay"`
// EndTime is the HH:MM value representing the end time of this event
EndTime string `json:"endTime"`
// Created is a UTC timestamp for when the event was created
Created time.Time `json:"created"`
// Updated is a UTC timestamp for when the event was modified last
Updated time.Time `json:"updated"`
// UserData is a custom and optional blob of JSON saved to the event
UserData map[string]interface{} `json:"userData"`
}
// Start gets the time.Time value using the StartDay and StartTime fields
func (e Event) Start() (time.Time, error) {
return parseDayTime(e.StartDay, e.StartTime)
}
// End gets the time.Time value using the EndDay and EndTime fields
func (e Event) End() (time.Time, error) {
return parseDayTime(e.EndDay, e.EndTime)
}
const iCalDateTimeFormat = "20060102T150400Z"
// MarshallToICal marshalls this event to an ical format
func (e Event) MarshallToICal() string {
start, _ := e.Start()
end, _ := e.Start()
s := []string{
"BEGIN:VEVENT",
fmt.Sprintf("UID:%v", e.Id),
fmt.Sprintf("DTSTAMP:%v", start.Format(iCalDateTimeFormat)),
fmt.Sprintf("DTSTART:%v", start.Format(iCalDateTimeFormat)),
fmt.Sprintf("DTEND:%v", end.Format(iCalDateTimeFormat)),
fmt.Sprintf("SUMMARY:%v", strings.ReplaceAll(e.Title, "\n", " ")),
"CLASS:PRIVATE",
}
if e.Description != nil && len(*e.Description) > 0 {
s = append(s, fmt.Sprintf("DESCRIPTION:%v", *e.Description))
}
s = append(s, "END:VEVENT")
return strings.Join(s, "\n")
}
// parseDayTime takes a day of YYYY-MM-DD and an hourMin as HH-mm (or "")
// and converts it into a time.Time object
func parseDayTime(day, hourMin string) (time.Time, error) {
if day == "" {
return time.Time{}, fmt.Errorf("invalid day value")
}
if hourMin == "" {
return time.Parse(time.DateOnly, day)
}
return time.Parse(DayTimeFormat, fmt.Sprintf("%s %s", day, hourMin))
}
// DayTimeFormat is the time package format style for YYYY-MM-DD HH:mm
const DayTimeFormat = time.DateOnly + " 15:04"
// TimeFormat is the time package format style for HH:mm
const TimeFormat = "15:04"
type Details struct {
// Id is the unique id for this event
Id int64
// Title is the value that will be shown for this event when displayed on a calendar interface
Title string
// Description is a longer field description of what the event is
Description *string
// Url is a quick way to set the destination on an event that is clicked on in an interface
Url *string
// Status represents the current status of the event, defaults to active, but events can also
// be canceled or removed
Status Status
// IsAllDay is true if the event is an all day event which will set the time values to 00:00
IsAllDay bool
// Zone must be a valid time.Location name like "UTC" or "America/New_York"
Zone string
// StartDay is the YYYY-MM-DD value representing the start day of this event
StartDay string
// StartTime is the HH:MM value representing the start time of this event
StartTime string
// EndDay is the YYYY-MM-DD value representing the end day of this event
EndDay string
// EndTime is the HH:MM value representing the end time of this event
EndTime string
}
type Status int64
const (
// StatusActive is the default for events and it means it will show up on the calendar as a standard event
StatusActive Status = 0
// StatusCanceled is for when the owner of the event cancels the event but it still shows up on the calendar
// as a faded out event with a line through the text
StatusCanceled Status = 1
// StatusAbandoned is when all of the invitees of the event (including the owner of the event) declines the event
// which then causes the event to disappear from the calendar
StatusAbandoned Status = -2
// StatusRemoved is when the event was deleted by the owner of the event and it disappears from the calendar
StatusRemoved Status = -1
)
// EventType must be defined by the user of this library
type EventType = int64
// Invite is a record of an invitation to a specific user for a specific event
type Invite struct {
// EventId is a reference to the unique identifier for a specific event
EventId int64
// UserId is the reference for the user who's invite is in question
UserId int64
// Status refers to the response of the user to the invite of an event
// and defaults to pending which is kind of like a soft confirm
Status InviteStatus
// Permission is a bitmask for the allowed permissions for this user on this event
Permission Permission
// Created is a timestamp for when the invite invitation was created
Created time.Time
// Updated is a timestamp for when the invite invitation was modified last
Updated time.Time
}
func (i Invite) String() string {
return fmt.Sprintf("{Event:%v, User:%v, Status:%v, Perm:%v}", i.EventId, i.UserId, i.Status, i.Permission)
}
// InviteStatus conveys the invitation status of this invitation. Statuses that are equal or
// greater to zero will be considered positive statuses for the purpose of showing the event
// on that user's calendar. Anything less than 0 will be hidden on the user's calendar.
type InviteStatus int64
const (
// InviteStatusPending is the default and refers to a non-answer, pending invite will still be treated
// as a soft confirm and the event will remain on the user's calendar but be outlined
InviteStatusPending InviteStatus = 0
// InviteStatusConfirmed is an acknowledgment that the user is going to attend the event
InviteStatusConfirmed InviteStatus = 1
// InviteStatusDeclined is when the user decides tho not attend the event, if all users decline an event
// it becomes abandoned
InviteStatusDeclined InviteStatus = -1
// InviteStatusRevoked is when a user with the correct permission forcibly removes a user's invitation
InviteStatusRevoked InviteStatus = -2
)
type Bitmask uint32
func (f Bitmask) HasFlag(flag Bitmask) bool {
return f&flag != 0
}
func (f *Bitmask) AddFlag(flag Bitmask) {
*f |= flag
}
func (f *Bitmask) ClearFlag(flag Bitmask) {
*f &= ^flag
}
func (f *Bitmask) ToggleFlag(flag Bitmask) {
*f ^= flag
}
type Permission = Bitmask
const (
PermissionRead = 1 << iota
PermissionModify
PermissionInvite
PermissionCancel
PermissionDelete
)
const (
PermissionOwner = PermissionDelete | PermissionCancel | PermissionModify | PermissionInvite | PermissionRead
PermissionInvitee = PermissionRead
)
// MaxRepeatOccurrence is set to 30 events
const MaxRepeatOccurrence int64 = 30
// MaxRepeatDuration is set to 2 years
const MaxRepeatDuration = time.Duration(24*365*2) * time.Hour
// Repeat contains all of the values required to be able to repeat an event
// over a period of time or for a number of occurrences
type Repeat struct {
// RepeatType is a enumeration of the valid types of repeat events (daily,
// weekly, monthly, or yearly)
RepeatType RepeatType `json:"repeatType"`
// DayOfWeek is a bitmask of the days of the week (SMTWTFS)
DayOfWeek DayOfWeek `json:"dayOfWeek"`
// RepeatOccurrences is a number of times the event should repeat.
// It should be 0 if RepeatStopDate is not nil.
// It can't be more than MaxRepeatOccurrence.
RepeatOccurrences int64 `json:"repeatOccrrences"`
// RepeatStopDate is a timestamp for when the repeating event should stop.
// It should be nil if RepeatOccurrences > 1.
// It can't be more than MaxRepeatDuration.
RepeatStopDate *time.Time `json:"repeatStopDate"`
}
type RepeatType int64
const (
RepeatTypeDaily RepeatType = 0
RepeatTypeWeekly RepeatType = 1
RepeatTypeMonthly RepeatType = 2
RepeatTypeYearly RepeatType = 3
)
type DayOfWeek = Bitmask
const (
DayOfWeekSunday = 1 << iota
DayOfWeekMonday
DayOfWeekTuesday
DayOfWeekWednesday
DayOfWeekThursday
DayOfWeekFriday
DayOfWeekSaturday
)
func dayOfWeekFromWeekday(w time.Weekday) DayOfWeek {
switch w {
case time.Sunday:
return DayOfWeekSunday
case time.Monday:
return DayOfWeekMonday
case time.Tuesday:
return DayOfWeekTuesday
case time.Wednesday:
return DayOfWeekWednesday
case time.Thursday:
return DayOfWeekThursday
case time.Friday:
return DayOfWeekFriday
case time.Saturday:
return DayOfWeekSaturday
}
return DayOfWeekSunday
}
func _t(t time.Time) *time.Time {
return &t
}
// Query is the object that the data store uses to try and find the list of matching events
type Query struct {
// Start is an inclusive timestamp and should be compared against the end timestamp of other events (overlap)
Start *time.Time
// End is an inclusive timestamp and should be compared against the start timestamp of other events (overlap)
End *time.Time
// EventIds is a list of specific events that you want to query
EventIds []int64
// CalendarIds is a list of specific calendars that you want to query
CalendarIds []int64
// ParentIds is a list of parent ids that should be searched for and will find all events that have a match to the parent id
ParentIds []int64
// UserIds is a check if the user has an invite record for the event that is not
// declined or revoked
UserIds []int64
// EventTypes is a check if the event has a specific event type
EventTypes []EventType
// SourceIds is an OR check on the source ids
SourceIds []int64
// Statuses is an OR search for specific statuses
Statuses []Status
// Text is an OR search for specific words
Text []string
}
// Matches does a local check if the given event matches the query
func (q Query) Matches(event *Event) bool {
if event == nil {
return false
}
if q.Start != nil {
startDay := q.Start.Format(time.DateOnly)
startTime := q.Start.Format(TimeFormat)
if startDay > event.EndDay {
return false
}
if event.EndTime != "" && startDay+startTime > event.EndDay+event.EndTime {
return false
}
}
if q.End != nil {
endDay := q.End.Format(time.DateOnly)
endTime := q.End.Format(TimeFormat)
if endDay < event.StartDay {
return false
}
if event.StartTime != "" && endDay+endTime < event.StartDay+event.StartTime {
return false
}
}
found := false
if len(q.EventIds) > 0 {
for _, id := range q.EventIds {
if event.Id == id {
found = true
break
}
}
if !found {
return false
}
}
if len(q.CalendarIds) > 0 {
found = false
for _, id := range q.CalendarIds {
if event.CalendarId == id {
found = true
break
}
}
if !found {
return false
}
}
if len(q.ParentIds) > 0 {
found = false
for _, id := range q.ParentIds {
if event.ParentId != nil && *event.ParentId == id {
found = true
break
}
}
if !found {
return false
}
}
if len(q.EventTypes) > 0 {
found = false
for _, eventType := range q.EventTypes {
if event.EventType == eventType {
found = true
break
}
}
if !found {
return false
}
}
if len(q.SourceIds) > 0 {
found = false
for _, id := range q.SourceIds {
if event.SourceId != nil && *event.SourceId == id {
found = true
break
}
}
if !found {
return false
}
}
if len(q.Statuses) > 0 {
found = false
for _, status := range q.Statuses {
if event.Status == status {
found = true
break
}
}
if !found {
return false
}
}
if len(q.Text) > 0 {
found = false
for _, text := range q.Text {
if strings.Contains(event.Title, text) {
found = true
break
}
if event.Description != nil && strings.Contains(*event.Description, text) {
found = true
break
}
}
if !found {
return false
}
}
return true
}
type RepeatEditType int64
const (
RepeatEditTypeThis RepeatEditType = 0
RepeatEditTypeAll RepeatEditType = 1
RepeatEditTypeThisAndAfter RepeatEditType = 2
)
// Sort events by their start day and time where earlier events
// are first and later events are last
func Sort(e []*Event) []*Event {
sort.SliceStable(e, func(a int, b int) bool {
A := e[a]
B := e[b]
if A == nil {
return true
}
if B == nil {
return false
}
if A.StartDay < B.StartDay {
return true
} else if A.StartDay > B.StartDay {
return false
}
if A.StartTime <= B.StartTime {
return true
}
return false
})
return e
}