first commit
Made-with: Cursor
This commit is contained in:
583
internal/service/event.go
Normal file
583
internal/service/event.go
Normal file
@@ -0,0 +1,583 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/calendarapi/internal/models"
|
||||
"github.com/calendarapi/internal/repository"
|
||||
"github.com/calendarapi/internal/utils"
|
||||
"github.com/google/uuid"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
"github.com/teambition/rrule-go"
|
||||
)
|
||||
|
||||
type EventService struct {
|
||||
pool *pgxpool.Pool
|
||||
queries *repository.Queries
|
||||
calendar *CalendarService
|
||||
audit *AuditService
|
||||
scheduler ReminderScheduler
|
||||
}
|
||||
|
||||
type ReminderScheduler interface {
|
||||
ScheduleReminder(ctx context.Context, eventID, reminderID, userID uuid.UUID, triggerAt time.Time) error
|
||||
}
|
||||
|
||||
func NewEventService(pool *pgxpool.Pool, queries *repository.Queries, calendar *CalendarService, audit *AuditService, scheduler ReminderScheduler) *EventService {
|
||||
return &EventService{pool: pool, queries: queries, calendar: calendar, audit: audit, scheduler: scheduler}
|
||||
}
|
||||
|
||||
func (s *EventService) Create(ctx context.Context, userID uuid.UUID, req CreateEventRequest) (*models.Event, error) {
|
||||
if err := utils.ValidateEventTitle(req.Title); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if req.Timezone == "" {
|
||||
return nil, models.NewValidationError("timezone is required")
|
||||
}
|
||||
if err := utils.ValidateTimezone(req.Timezone); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := utils.ValidateTimeRange(req.StartTime, req.EndTime); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
role, err := s.calendar.GetRole(ctx, req.CalendarID, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if role != "owner" && role != "editor" {
|
||||
return nil, models.ErrForbidden
|
||||
}
|
||||
|
||||
startUTC := req.StartTime.UTC()
|
||||
endUTC := req.EndTime.UTC()
|
||||
|
||||
if req.RecurrenceRule != nil && *req.RecurrenceRule != "" {
|
||||
if _, err := rrule.StrToRRule(*req.RecurrenceRule); err != nil {
|
||||
return nil, models.NewValidationError("invalid recurrence_rule: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
tx, err := s.pool.Begin(ctx)
|
||||
if err != nil {
|
||||
return nil, models.ErrInternal
|
||||
}
|
||||
defer tx.Rollback(ctx)
|
||||
qtx := s.queries.WithTx(tx)
|
||||
|
||||
eventID := uuid.New()
|
||||
tags := req.Tags
|
||||
if tags == nil {
|
||||
tags = []string{}
|
||||
}
|
||||
|
||||
dbEvent, err := qtx.CreateEvent(ctx, repository.CreateEventParams{
|
||||
ID: utils.ToPgUUID(eventID),
|
||||
CalendarID: utils.ToPgUUID(req.CalendarID),
|
||||
Title: req.Title,
|
||||
Description: utils.ToPgTextPtr(req.Description),
|
||||
Location: utils.ToPgTextPtr(req.Location),
|
||||
StartTime: utils.ToPgTimestamptz(startUTC),
|
||||
EndTime: utils.ToPgTimestamptz(endUTC),
|
||||
Timezone: req.Timezone,
|
||||
AllDay: req.AllDay,
|
||||
RecurrenceRule: utils.ToPgTextPtr(req.RecurrenceRule),
|
||||
Tags: tags,
|
||||
CreatedBy: utils.ToPgUUID(userID),
|
||||
UpdatedBy: utils.ToPgUUID(userID),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, models.ErrInternal
|
||||
}
|
||||
|
||||
var reminders []models.Reminder
|
||||
for _, mins := range req.Reminders {
|
||||
if err := utils.ValidateReminderMinutes(mins); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rID := uuid.New()
|
||||
r, err := qtx.CreateReminder(ctx, repository.CreateReminderParams{
|
||||
ID: utils.ToPgUUID(rID),
|
||||
EventID: utils.ToPgUUID(eventID),
|
||||
MinutesBefore: mins,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, models.ErrInternal
|
||||
}
|
||||
reminders = append(reminders, models.Reminder{
|
||||
ID: utils.FromPgUUID(r.ID),
|
||||
MinutesBefore: r.MinutesBefore,
|
||||
})
|
||||
}
|
||||
|
||||
if err := tx.Commit(ctx); err != nil {
|
||||
return nil, models.ErrInternal
|
||||
}
|
||||
|
||||
for _, rem := range reminders {
|
||||
triggerAt := startUTC.Add(-time.Duration(rem.MinutesBefore) * time.Minute)
|
||||
if triggerAt.After(time.Now()) && s.scheduler != nil {
|
||||
_ = s.scheduler.ScheduleReminder(ctx, eventID, rem.ID, userID, triggerAt)
|
||||
}
|
||||
}
|
||||
|
||||
s.audit.Log(ctx, "event", eventID, "CREATE_EVENT", userID)
|
||||
|
||||
if reminders == nil {
|
||||
reminders = []models.Reminder{}
|
||||
}
|
||||
return eventFromDB(dbEvent, reminders, []models.Attendee{}, []models.Attachment{}), nil
|
||||
}
|
||||
|
||||
func (s *EventService) Get(ctx context.Context, userID uuid.UUID, eventID uuid.UUID) (*models.Event, error) {
|
||||
ev, err := s.queries.GetEventByID(ctx, utils.ToPgUUID(eventID))
|
||||
if err != nil {
|
||||
if err == pgx.ErrNoRows {
|
||||
return nil, models.ErrNotFound
|
||||
}
|
||||
return nil, models.ErrInternal
|
||||
}
|
||||
|
||||
calID := utils.FromPgUUID(ev.CalendarID)
|
||||
if _, err := s.calendar.GetRole(ctx, calID, userID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reminders, err := s.loadReminders(ctx, eventID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
attendees, err := s.loadAttendees(ctx, eventID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
attachments, err := s.loadAttachments(ctx, eventID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return eventFromDB(ev, reminders, attendees, attachments), nil
|
||||
}
|
||||
|
||||
func (s *EventService) List(ctx context.Context, userID uuid.UUID, params ListEventParams) ([]models.Event, *string, error) {
|
||||
if err := utils.ValidateRecurrenceRangeLimit(params.RangeStart, params.RangeEnd); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
limit := utils.ClampLimit(params.Limit)
|
||||
|
||||
var cursorTime pgtype.Timestamptz
|
||||
var cursorID pgtype.UUID
|
||||
if params.Cursor != "" {
|
||||
ct, cid, err := utils.ParseCursor(params.Cursor)
|
||||
if err != nil {
|
||||
return nil, nil, models.NewValidationError("invalid cursor")
|
||||
}
|
||||
cursorTime = utils.ToPgTimestamptzPtr(ct)
|
||||
cursorID = utils.ToPgUUID(*cid)
|
||||
}
|
||||
|
||||
nonRecurring, err := s.queries.ListEventsInRange(ctx, repository.ListEventsInRangeParams{
|
||||
UserID: utils.ToPgUUID(userID),
|
||||
RangeEnd: utils.ToPgTimestamptz(params.RangeEnd),
|
||||
RangeStart: utils.ToPgTimestamptz(params.RangeStart),
|
||||
CalendarID: optionalPgUUID(params.CalendarID),
|
||||
Search: utils.ToPgTextPtr(params.Search),
|
||||
Tag: utils.ToPgTextPtr(params.Tag),
|
||||
CursorTime: cursorTime,
|
||||
CursorID: cursorID,
|
||||
Lim: limit + 1,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, models.ErrInternal
|
||||
}
|
||||
|
||||
recurring, err := s.queries.ListRecurringEventsInRange(ctx, repository.ListRecurringEventsInRangeParams{
|
||||
UserID: utils.ToPgUUID(userID),
|
||||
RangeEnd: utils.ToPgTimestamptz(params.RangeEnd),
|
||||
CalendarID: optionalPgUUID(params.CalendarID),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, models.ErrInternal
|
||||
}
|
||||
|
||||
var allEvents []models.Event
|
||||
recurringIDs := make(map[uuid.UUID]bool)
|
||||
|
||||
for _, ev := range recurring {
|
||||
recurringIDs[utils.FromPgUUID(ev.ID)] = true
|
||||
occurrences := s.expandRecurrence(ev, params.RangeStart, params.RangeEnd)
|
||||
allEvents = append(allEvents, occurrences...)
|
||||
}
|
||||
|
||||
for _, ev := range nonRecurring {
|
||||
eid := utils.FromPgUUID(ev.ID)
|
||||
if recurringIDs[eid] {
|
||||
continue
|
||||
}
|
||||
allEvents = append(allEvents, *eventFromDB(ev, nil, nil, nil))
|
||||
}
|
||||
|
||||
sort.Slice(allEvents, func(i, j int) bool {
|
||||
si := effectiveStart(allEvents[i])
|
||||
sj := effectiveStart(allEvents[j])
|
||||
if si.Equal(sj) {
|
||||
return allEvents[i].ID.String() < allEvents[j].ID.String()
|
||||
}
|
||||
return si.Before(sj)
|
||||
})
|
||||
|
||||
hasMore := int32(len(allEvents)) > limit
|
||||
if hasMore {
|
||||
allEvents = allEvents[:limit]
|
||||
}
|
||||
|
||||
relatedLoaded := make(map[uuid.UUID]bool)
|
||||
remindersMap := make(map[uuid.UUID][]models.Reminder)
|
||||
attendeesMap := make(map[uuid.UUID][]models.Attendee)
|
||||
attachmentsMap := make(map[uuid.UUID][]models.Attachment)
|
||||
for _, ev := range allEvents {
|
||||
if relatedLoaded[ev.ID] {
|
||||
continue
|
||||
}
|
||||
relatedLoaded[ev.ID] = true
|
||||
remindersMap[ev.ID], _ = s.loadReminders(ctx, ev.ID)
|
||||
attendeesMap[ev.ID], _ = s.loadAttendees(ctx, ev.ID)
|
||||
attachmentsMap[ev.ID], _ = s.loadAttachments(ctx, ev.ID)
|
||||
}
|
||||
for i := range allEvents {
|
||||
allEvents[i].Reminders = remindersMap[allEvents[i].ID]
|
||||
allEvents[i].Attendees = attendeesMap[allEvents[i].ID]
|
||||
allEvents[i].Attachments = attachmentsMap[allEvents[i].ID]
|
||||
}
|
||||
|
||||
if hasMore {
|
||||
last := allEvents[len(allEvents)-1]
|
||||
cursor := utils.EncodeCursor(effectiveStart(last), last.ID)
|
||||
return allEvents, &cursor, nil
|
||||
}
|
||||
|
||||
return allEvents, nil, nil
|
||||
}
|
||||
|
||||
func (s *EventService) Update(ctx context.Context, userID uuid.UUID, eventID uuid.UUID, req UpdateEventRequest) (*models.Event, error) {
|
||||
ev, err := s.queries.GetEventByID(ctx, utils.ToPgUUID(eventID))
|
||||
if err != nil {
|
||||
if err == pgx.ErrNoRows {
|
||||
return nil, models.ErrNotFound
|
||||
}
|
||||
return nil, models.ErrInternal
|
||||
}
|
||||
|
||||
calID := utils.FromPgUUID(ev.CalendarID)
|
||||
role, err := s.calendar.GetRole(ctx, calID, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if role != "owner" && role != "editor" {
|
||||
return nil, models.ErrForbidden
|
||||
}
|
||||
|
||||
if req.Title != nil {
|
||||
if err := utils.ValidateEventTitle(*req.Title); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if req.Timezone != nil {
|
||||
if err := utils.ValidateTimezone(*req.Timezone); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
startTime := utils.FromPgTimestamptz(ev.StartTime)
|
||||
endTime := utils.FromPgTimestamptz(ev.EndTime)
|
||||
if req.StartTime != nil {
|
||||
startTime = req.StartTime.UTC()
|
||||
}
|
||||
if req.EndTime != nil {
|
||||
endTime = req.EndTime.UTC()
|
||||
}
|
||||
if err := utils.ValidateTimeRange(startTime, endTime); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if req.RecurrenceRule != nil && *req.RecurrenceRule != "" {
|
||||
if _, err := rrule.StrToRRule(*req.RecurrenceRule); err != nil {
|
||||
return nil, models.NewValidationError("invalid recurrence_rule: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
var pgStart, pgEnd pgtype.Timestamptz
|
||||
if req.StartTime != nil {
|
||||
pgStart = utils.ToPgTimestamptz(startTime)
|
||||
}
|
||||
if req.EndTime != nil {
|
||||
pgEnd = utils.ToPgTimestamptz(endTime)
|
||||
}
|
||||
|
||||
updated, err := s.queries.UpdateEvent(ctx, repository.UpdateEventParams{
|
||||
ID: utils.ToPgUUID(eventID),
|
||||
Title: utils.ToPgTextPtr(req.Title),
|
||||
Description: utils.ToPgTextPtr(req.Description),
|
||||
Location: utils.ToPgTextPtr(req.Location),
|
||||
StartTime: pgStart,
|
||||
EndTime: pgEnd,
|
||||
Timezone: utils.ToPgTextPtr(req.Timezone),
|
||||
AllDay: optionalPgBool(req.AllDay),
|
||||
RecurrenceRule: utils.ToPgTextPtr(req.RecurrenceRule),
|
||||
Tags: req.Tags,
|
||||
UpdatedBy: utils.ToPgUUID(userID),
|
||||
})
|
||||
if err != nil {
|
||||
if err == pgx.ErrNoRows {
|
||||
return nil, models.ErrNotFound
|
||||
}
|
||||
return nil, models.ErrInternal
|
||||
}
|
||||
|
||||
s.audit.Log(ctx, "event", eventID, "UPDATE_EVENT", userID)
|
||||
|
||||
reminders, _ := s.loadReminders(ctx, eventID)
|
||||
attendees, _ := s.loadAttendees(ctx, eventID)
|
||||
attachments, _ := s.loadAttachments(ctx, eventID)
|
||||
return eventFromDB(updated, reminders, attendees, attachments), nil
|
||||
}
|
||||
|
||||
func (s *EventService) Delete(ctx context.Context, userID uuid.UUID, eventID uuid.UUID) error {
|
||||
ev, err := s.queries.GetEventByID(ctx, utils.ToPgUUID(eventID))
|
||||
if err != nil {
|
||||
if err == pgx.ErrNoRows {
|
||||
return models.ErrNotFound
|
||||
}
|
||||
return models.ErrInternal
|
||||
}
|
||||
|
||||
calID := utils.FromPgUUID(ev.CalendarID)
|
||||
role, err := s.calendar.GetRole(ctx, calID, userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if role != "owner" && role != "editor" {
|
||||
return models.ErrForbidden
|
||||
}
|
||||
|
||||
if err := s.queries.SoftDeleteEvent(ctx, utils.ToPgUUID(eventID)); err != nil {
|
||||
return models.ErrInternal
|
||||
}
|
||||
|
||||
s.audit.Log(ctx, "event", eventID, "DELETE_EVENT", userID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *EventService) expandRecurrence(ev repository.Event, rangeStart, rangeEnd time.Time) []models.Event {
|
||||
if !ev.RecurrenceRule.Valid {
|
||||
return nil
|
||||
}
|
||||
|
||||
ruleStr := ev.RecurrenceRule.String
|
||||
dtStart := utils.FromPgTimestamptz(ev.StartTime)
|
||||
duration := utils.FromPgTimestamptz(ev.EndTime).Sub(dtStart)
|
||||
|
||||
fullRule := fmt.Sprintf("DTSTART:%s\nRRULE:%s", dtStart.UTC().Format("20060102T150405Z"), ruleStr)
|
||||
rule, err := rrule.StrToRRuleSet(fullRule)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
exceptions, _ := s.queries.ListExceptionsByEvent(context.Background(), ev.ID)
|
||||
exceptionDates := make(map[string]bool)
|
||||
for _, ex := range exceptions {
|
||||
if ex.ExceptionDate.Valid {
|
||||
exceptionDates[ex.ExceptionDate.Time.Format("2006-01-02")] = true
|
||||
}
|
||||
}
|
||||
|
||||
occurrences := rule.Between(rangeStart.UTC(), rangeEnd.UTC(), true)
|
||||
var results []models.Event
|
||||
|
||||
for _, occ := range occurrences {
|
||||
dateKey := occ.Format("2006-01-02")
|
||||
if exceptionDates[dateKey] {
|
||||
continue
|
||||
}
|
||||
|
||||
occEnd := occ.Add(duration)
|
||||
occStart := occ
|
||||
results = append(results, models.Event{
|
||||
ID: utils.FromPgUUID(ev.ID),
|
||||
CalendarID: utils.FromPgUUID(ev.CalendarID),
|
||||
Title: ev.Title,
|
||||
Description: utils.FromPgText(ev.Description),
|
||||
Location: utils.FromPgText(ev.Location),
|
||||
StartTime: dtStart,
|
||||
EndTime: dtStart.Add(duration),
|
||||
Timezone: ev.Timezone,
|
||||
AllDay: ev.AllDay,
|
||||
RecurrenceRule: utils.FromPgText(ev.RecurrenceRule),
|
||||
IsOccurrence: true,
|
||||
OccurrenceStartTime: &occStart,
|
||||
OccurrenceEndTime: &occEnd,
|
||||
CreatedBy: utils.FromPgUUID(ev.CreatedBy),
|
||||
UpdatedBy: utils.FromPgUUID(ev.UpdatedBy),
|
||||
CreatedAt: utils.FromPgTimestamptz(ev.CreatedAt),
|
||||
UpdatedAt: utils.FromPgTimestamptz(ev.UpdatedAt),
|
||||
Tags: ev.Tags,
|
||||
Reminders: []models.Reminder{},
|
||||
Attendees: []models.Attendee{},
|
||||
Attachments: []models.Attachment{},
|
||||
})
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
func (s *EventService) loadReminders(ctx context.Context, eventID uuid.UUID) ([]models.Reminder, error) {
|
||||
rows, err := s.queries.ListRemindersByEvent(ctx, utils.ToPgUUID(eventID))
|
||||
if err != nil {
|
||||
return nil, models.ErrInternal
|
||||
}
|
||||
result := make([]models.Reminder, len(rows))
|
||||
for i, r := range rows {
|
||||
result[i] = models.Reminder{
|
||||
ID: utils.FromPgUUID(r.ID),
|
||||
MinutesBefore: r.MinutesBefore,
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *EventService) loadAttendees(ctx context.Context, eventID uuid.UUID) ([]models.Attendee, error) {
|
||||
rows, err := s.queries.ListAttendeesByEvent(ctx, utils.ToPgUUID(eventID))
|
||||
if err != nil {
|
||||
return nil, models.ErrInternal
|
||||
}
|
||||
result := make([]models.Attendee, len(rows))
|
||||
for i, a := range rows {
|
||||
var uid *uuid.UUID
|
||||
if a.UserID.Valid {
|
||||
u := utils.FromPgUUID(a.UserID)
|
||||
uid = &u
|
||||
}
|
||||
result[i] = models.Attendee{
|
||||
ID: utils.FromPgUUID(a.ID),
|
||||
UserID: uid,
|
||||
Email: utils.FromPgText(a.Email),
|
||||
Status: a.Status,
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *EventService) loadAttachments(ctx context.Context, eventID uuid.UUID) ([]models.Attachment, error) {
|
||||
rows, err := s.queries.ListAttachmentsByEvent(ctx, utils.ToPgUUID(eventID))
|
||||
if err != nil {
|
||||
return nil, models.ErrInternal
|
||||
}
|
||||
result := make([]models.Attachment, len(rows))
|
||||
for i, a := range rows {
|
||||
result[i] = models.Attachment{
|
||||
ID: utils.FromPgUUID(a.ID),
|
||||
FileURL: a.FileUrl,
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func eventFromDB(ev repository.Event, reminders []models.Reminder, attendees []models.Attendee, attachments []models.Attachment) *models.Event {
|
||||
if reminders == nil {
|
||||
reminders = []models.Reminder{}
|
||||
}
|
||||
if attendees == nil {
|
||||
attendees = []models.Attendee{}
|
||||
}
|
||||
if attachments == nil {
|
||||
attachments = []models.Attachment{}
|
||||
}
|
||||
tags := ev.Tags
|
||||
if tags == nil {
|
||||
tags = []string{}
|
||||
}
|
||||
|
||||
return &models.Event{
|
||||
ID: utils.FromPgUUID(ev.ID),
|
||||
CalendarID: utils.FromPgUUID(ev.CalendarID),
|
||||
Title: ev.Title,
|
||||
Description: utils.FromPgText(ev.Description),
|
||||
Location: utils.FromPgText(ev.Location),
|
||||
StartTime: utils.FromPgTimestamptz(ev.StartTime),
|
||||
EndTime: utils.FromPgTimestamptz(ev.EndTime),
|
||||
Timezone: ev.Timezone,
|
||||
AllDay: ev.AllDay,
|
||||
RecurrenceRule: utils.FromPgText(ev.RecurrenceRule),
|
||||
CreatedBy: utils.FromPgUUID(ev.CreatedBy),
|
||||
UpdatedBy: utils.FromPgUUID(ev.UpdatedBy),
|
||||
CreatedAt: utils.FromPgTimestamptz(ev.CreatedAt),
|
||||
UpdatedAt: utils.FromPgTimestamptz(ev.UpdatedAt),
|
||||
Reminders: reminders,
|
||||
Attendees: attendees,
|
||||
Attachments: attachments,
|
||||
Tags: tags,
|
||||
}
|
||||
}
|
||||
|
||||
type CreateEventRequest struct {
|
||||
CalendarID uuid.UUID
|
||||
Title string
|
||||
Description *string
|
||||
Location *string
|
||||
StartTime time.Time
|
||||
EndTime time.Time
|
||||
Timezone string
|
||||
AllDay bool
|
||||
RecurrenceRule *string
|
||||
Reminders []int32
|
||||
Tags []string
|
||||
}
|
||||
|
||||
type UpdateEventRequest struct {
|
||||
Title *string
|
||||
Description *string
|
||||
Location *string
|
||||
StartTime *time.Time
|
||||
EndTime *time.Time
|
||||
Timezone *string
|
||||
AllDay *bool
|
||||
RecurrenceRule *string
|
||||
Tags []string
|
||||
}
|
||||
|
||||
type ListEventParams struct {
|
||||
RangeStart time.Time
|
||||
RangeEnd time.Time
|
||||
CalendarID *uuid.UUID
|
||||
Search *string
|
||||
Tag *string
|
||||
Limit int
|
||||
Cursor string
|
||||
}
|
||||
|
||||
func effectiveStart(e models.Event) time.Time {
|
||||
if e.OccurrenceStartTime != nil {
|
||||
return *e.OccurrenceStartTime
|
||||
}
|
||||
return e.StartTime
|
||||
}
|
||||
|
||||
func optionalPgUUID(id *uuid.UUID) pgtype.UUID {
|
||||
if id == nil {
|
||||
return pgtype.UUID{Valid: false}
|
||||
}
|
||||
return utils.ToPgUUID(*id)
|
||||
}
|
||||
|
||||
func optionalPgBool(b *bool) pgtype.Bool {
|
||||
if b == nil {
|
||||
return pgtype.Bool{Valid: false}
|
||||
}
|
||||
return pgtype.Bool{Bool: *b, Valid: true}
|
||||
}
|
||||
Reference in New Issue
Block a user