- Added new endpoints for listing calendars, events, creating/updating/deleting events, and handling free/busy requests. - Implemented ETag/If-Match support for event updates to ensure data integrity. - Introduced functionality for responding to invitations and creating Meet links from events. - Enhanced validation for event creation and updates, including attendee email checks. - Updated README documentation to reflect the new Calendar API features and usage examples. - Revised project checklist to indicate completion of Calendar API enhancements.
154 lines
4.4 KiB
Go
154 lines
4.4 KiB
Go
package calendar
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/ultisuite/ulti-backend/internal/api/paginate"
|
|
"github.com/ultisuite/ulti-backend/internal/api/query"
|
|
meetpkg "github.com/ultisuite/ulti-backend/internal/meet"
|
|
"github.com/ultisuite/ulti-backend/internal/nextcloud"
|
|
)
|
|
|
|
type Service struct {
|
|
nc *nextcloud.Client
|
|
meetCfg *meetpkg.Config
|
|
}
|
|
|
|
var ErrMeetDisabled = errors.New("meet is disabled")
|
|
|
|
func NewService(nc *nextcloud.Client, meetCfg *meetpkg.Config) *Service {
|
|
return &Service{nc: nc, meetCfg: meetCfg}
|
|
}
|
|
|
|
func calendarPath(userID, calID string) string {
|
|
return "/remote.php/dav/calendars/" + userID + "/" + calID + "/"
|
|
}
|
|
|
|
func (s *Service) ListCalendars(ctx context.Context, userID string) ([]nextcloud.Calendar, error) {
|
|
return s.nc.ListCalendars(ctx, userID)
|
|
}
|
|
|
|
type EventsList struct {
|
|
Events []nextcloud.Event `json:"events"`
|
|
Pagination query.PaginationMeta `json:"pagination,omitempty"`
|
|
}
|
|
|
|
func (s *Service) ListEvents(ctx context.Context, userID, calID string, params query.ListParams) (EventsList, error) {
|
|
from := time.Now().AddDate(0, -1, 0)
|
|
to := time.Now().AddDate(0, 1, 0)
|
|
if params.From != nil {
|
|
from = *params.From
|
|
}
|
|
if params.To != nil {
|
|
to = *params.To
|
|
}
|
|
|
|
events, err := s.nc.ListEvents(ctx, userID, calendarPath(userID, calID), from, to)
|
|
if err != nil {
|
|
return EventsList{}, err
|
|
}
|
|
filtered := filterEvents(events, params.Q)
|
|
page, total := paginate.Slice(filtered, params.Offset(), params.Limit())
|
|
return EventsList{
|
|
Events: page,
|
|
Pagination: params.Meta(&total),
|
|
}, nil
|
|
}
|
|
|
|
func (s *Service) CreateEvent(ctx context.Context, userID, calID string, event *nextcloud.Event) error {
|
|
if strings.TrimSpace(event.Organizer) == "" {
|
|
event.Organizer = userID
|
|
}
|
|
return s.nc.CreateEvent(ctx, userID, calendarPath(userID, calID), event)
|
|
}
|
|
|
|
func (s *Service) UpdateEvent(ctx context.Context, userID, eventPath, ifMatch string, event *nextcloud.Event) (string, error) {
|
|
if strings.TrimSpace(event.Organizer) == "" {
|
|
event.Organizer = userID
|
|
}
|
|
return s.nc.UpdateEvent(ctx, userID, eventPath, ifMatch, event)
|
|
}
|
|
|
|
func (s *Service) DeleteEvent(ctx context.Context, userID, eventPath string) error {
|
|
return s.nc.DeleteEvent(ctx, userID, eventPath)
|
|
}
|
|
|
|
func (s *Service) FreeBusy(ctx context.Context, userID string, req *nextcloud.FreeBusyRequest) (*nextcloud.FreeBusyResponse, error) {
|
|
return s.nc.FreeBusy(ctx, userID, req)
|
|
}
|
|
|
|
func (s *Service) RespondToInvitation(ctx context.Context, userID, eventPath, attendeeEmail, status, ifMatch string) (string, error) {
|
|
event, err := s.nc.GetEvent(ctx, userID, eventPath)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
target := strings.ToLower(strings.TrimSpace(attendeeEmail))
|
|
for i := range event.Attendees {
|
|
if strings.EqualFold(strings.TrimSpace(event.Attendees[i].Email), target) {
|
|
event.Attendees[i].Status = strings.ToUpper(strings.TrimSpace(status))
|
|
match := strings.TrimSpace(ifMatch)
|
|
if match == "" {
|
|
match = strings.TrimSpace(event.ETag)
|
|
}
|
|
return s.nc.UpdateEvent(ctx, userID, eventPath, match, event)
|
|
}
|
|
}
|
|
return "", nextcloud.ErrAttendeeNotFound
|
|
}
|
|
|
|
func (s *Service) CreateMeetLink(ctx context.Context, userID, userName, userEmail, eventPath, ifMatch string) (string, string, error) {
|
|
if s.meetCfg == nil {
|
|
return "", "", ErrMeetDisabled
|
|
}
|
|
event, err := s.nc.GetEvent(ctx, userID, eventPath)
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
roomID := strings.TrimSpace(event.UID)
|
|
if roomID == "" {
|
|
roomID = fmt.Sprintf("event-%d", time.Now().Unix())
|
|
}
|
|
token, err := s.meetCfg.GenerateToken(roomID, &meetpkg.UserInfo{
|
|
ID: userID,
|
|
Name: userName,
|
|
Email: userEmail,
|
|
IsMod: true,
|
|
}, 24*time.Hour)
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
event.MeetURL = token.MeetURL
|
|
if strings.TrimSpace(event.Location) == "" {
|
|
event.Location = token.MeetURL
|
|
}
|
|
match := strings.TrimSpace(ifMatch)
|
|
if match == "" {
|
|
match = strings.TrimSpace(event.ETag)
|
|
}
|
|
etag, err := s.nc.UpdateEvent(ctx, userID, eventPath, match, event)
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
return token.MeetURL, etag, nil
|
|
}
|
|
|
|
func filterEvents(events []nextcloud.Event, q string) []nextcloud.Event {
|
|
q = strings.ToLower(strings.TrimSpace(q))
|
|
if q == "" {
|
|
return events
|
|
}
|
|
out := make([]nextcloud.Event, 0, len(events))
|
|
for _, e := range events {
|
|
if strings.Contains(strings.ToLower(e.Summary), q) ||
|
|
strings.Contains(strings.ToLower(e.Description), q) ||
|
|
strings.Contains(strings.ToLower(e.Location), q) {
|
|
out = append(out, e)
|
|
}
|
|
}
|
|
return out
|
|
}
|