Files
Crussell/backend/internal/dav/shared.go
T

277 lines
8.4 KiB
Go

package dav
import (
"context"
"fmt"
"strings"
"time"
"github.com/jackc/pgx/v5/pgxpool"
)
// BaseService holds shared DB connection
type BaseService struct {
db *pgxpool.Pool
}
// newBaseService returns a new BaseService instance
func newBaseService(db *pgxpool.Pool) *BaseService {
return &BaseService{db: db}
}
// ============================================================================
// Calendar Functions
// ============================================================================
func (s *BaseService) ListEventsForMonth(year int, month time.Month) ([]CalendarEvent, error) {
start := time.Date(year, month, 1, 0, 0, 0, 0, time.UTC)
end := start.AddDate(0, 1, 0).Add(-time.Second)
query := `
SELECT id, calendarid, uri, calendardata, lastmodified, etag, size, componenttype,
firstoccurence, lastoccurence, uid
FROM dav_calendarobjects
WHERE firstoccurence >= $1 AND firstoccurence <= $2
ORDER BY firstoccurence
`
return s.queryEventsWithContacts(query, start.Unix(), end.Unix())
}
func (s *BaseService) ListEventsBetween(start, end time.Time) ([]CalendarEvent, error) {
query := `
SELECT id, calendarid, uri, calendardata, lastmodified, etag, size, componenttype,
firstoccurence, lastoccurence, uid
FROM dav_calendarobjects
WHERE firstoccurence >= $1 AND firstoccurence <= $2
ORDER BY firstoccurence
`
return s.queryEventsWithContacts(query, start.Unix(), end.Unix())
}
func (s *BaseService) ListEventsTomorrow() ([]CalendarEvent, error) {
now := time.Now()
tomorrow := time.Date(now.Year(), now.Month(), now.Day()+1, 0, 0, 0, 0, now.Location())
dayAfter := tomorrow.Add(24 * time.Hour)
return s.ListEventsBetween(tomorrow, dayAfter)
}
func (s *BaseService) ListEventsThisWeek() ([]CalendarEvent, error) {
now := time.Now()
weekday := int(now.Weekday())
if weekday == 0 { // Sunday
weekday = 7
}
monday := now.AddDate(0, 0, -weekday+1)
monday = time.Date(monday.Year(), monday.Month(), monday.Day(), 0, 0, 0, 0, now.Location())
sunday := monday.AddDate(0, 0, 7)
return s.ListEventsBetween(monday, sunday)
}
// ============================================================================
// Contact Functions
// ============================================================================
func (s *BaseService) GetContactByURI(addressBookID int, uri string) (*Contact, error) {
query := `
SELECT id, addressbookid, uri, carddata, lastmodified, etag, size
FROM dav_cards
WHERE addressbookid = $1 AND uri = $2
`
var c Contact
err := s.db.QueryRow(context.Background(), query, addressBookID, uri).Scan(
&c.ID, &c.AddressBookID, &c.URI, &c.CardData, &c.LastModified, &c.Etag, &c.Size,
)
if err != nil {
return nil, fmt.Errorf("contact not found: %w", err)
}
return &c, nil
}
func (s *BaseService) ListAllContacts() ([]Contact, error) {
query := `SELECT id, addressbookid, uri, carddata, lastmodified, etag, size FROM dav_cards ORDER BY lastmodified DESC`
rows, err := s.db.Query(context.Background(), query)
if err != nil {
return nil, err
}
defer rows.Close()
var contacts []Contact
for rows.Next() {
var c Contact
if err := rows.Scan(&c.ID, &c.AddressBookID, &c.URI, &c.CardData, &c.LastModified, &c.Etag, &c.Size); err != nil {
return nil, err
}
contacts = append(contacts, c)
}
return contacts, nil
}
func (s *BaseService) ListRecentContacts(days int) ([]Contact, error) {
cutoff := time.Now().AddDate(0, 0, -days).Unix()
query := `SELECT id, addressbookid, uri, carddata, lastmodified, etag, size FROM dav_cards WHERE lastmodified >= $1 ORDER BY lastmodified DESC`
rows, err := s.db.Query(context.Background(), query, cutoff)
if err != nil {
return nil, err
}
defer rows.Close()
var contacts []Contact
for rows.Next() {
var c Contact
if err := rows.Scan(&c.ID, &c.AddressBookID, &c.URI, &c.CardData, &c.LastModified, &c.Etag, &c.Size); err != nil {
return nil, err
}
contacts = append(contacts, c)
}
return contacts, nil
}
// ============================================================================
// Helper Methods
// ============================================================================
func (s *BaseService) queryEventsWithContacts(query string, args ...interface{}) ([]CalendarEvent, error) {
rows, err := s.db.Query(context.Background(), query, args...)
if err != nil {
return nil, err
}
defer rows.Close()
var events []CalendarEvent
for rows.Next() {
var e CalendarEvent
if err := rows.Scan(
&e.ID, &e.CalendarID, &e.URI, &e.CalendarData, &e.LastModified, &e.Etag, &e.Size,
&e.ComponentType, &e.FirstOccurence, &e.LastOccurence, &e.UID,
); err != nil {
return nil, err
}
e.ContactURIs = extractContactURIsFromICalendar(e.CalendarData)
events = append(events, e)
}
return events, nil
}
func extractContactURIsFromICalendar(icalData string) []string {
var uris []string
for _, line := range strings.Split(icalData, "\n") {
line = strings.TrimSpace(line)
if strings.HasPrefix(line, "ATTENDEE") {
parts := strings.Split(line, ":")
if len(parts) >= 2 {
uris = append(uris, strings.TrimSpace(parts[len(parts)-1]))
}
}
}
return uris
}
// CreateContact adds a new contact to an address book
func (s *BaseService) CreateContact(addressBookID int, userID string, input ContactInput) error {
now := time.Now().Unix()
uri := fmt.Sprintf("%s.vcf", userID)
cardData := GenerateVCard(input)
query := `
INSERT INTO dav_cards (addressbookid, uri, carddata, lastmodified, etag, size)
VALUES ($1, $2, $3, $4, $5, $6)
`
_, err := s.db.Exec(context.Background(), query,
addressBookID,
uri,
cardData,
now,
fmt.Sprintf("%d", now),
len(cardData),
)
return err
}
// UpdateContact updates an existing contact by URI
func (s *BaseService) UpdateContact(addressBookID int, uri string, input ContactInput) error {
now := time.Now().Unix()
cardData := GenerateVCard(input)
query := `
UPDATE dav_cards
SET carddata = $1, lastmodified = $2, etag = $3, size = $4
WHERE addressbookid = $5 AND uri = $6
`
_, err := s.db.Exec(context.Background(), query, cardData, now, fmt.Sprintf("%d", now), len(cardData), addressBookID, uri)
return err
}
// DeleteContact deletes a contact by URI
func (s *BaseService) DeleteContact(addressBookID int, uri string) error {
query := `DELETE FROM dav_cards WHERE addressbookid = $1 AND uri = $2`
_, err := s.db.Exec(context.Background(), query, addressBookID, uri)
return err
}
// CreateEvent adds a new event to a calendar
func (s *BaseService) CreateEvent(calendarID int, input EventInput) error {
uid := fmt.Sprintf("%d@example.com", time.Now().UnixNano())
now := time.Now().Unix()
calendarData := GenerateICalEvent(input)
query := `
INSERT INTO dav_calendarobjects
(calendarid, uri, calendardata, lastmodified, etag, size, componenttype,
firstoccurence, lastoccurence, uid)
VALUES ($1, $2, $3, $4, $5, $6, 'VEVENT', $7, $8, $9)
`
_, err := s.db.Exec(context.Background(), query,
calendarID,
uid+".ics",
calendarData,
now,
fmt.Sprintf("%d", now),
len(calendarData),
input.Start.Unix(),
input.End.Unix(),
uid,
)
return err
}
// UpdateEvent updates an existing calendar event by UID
func (s *BaseService) UpdateEvent(calendarID int, uid string, input EventInput) error {
now := time.Now().Unix()
calendarData := GenerateICalEvent(input)
query := `
UPDATE dav_calendarobjects
SET calendardata = $1, lastmodified = $2, etag = $3, size = $4,
firstoccurence = $5, lastoccurence = $6
WHERE calendarid = $7 AND uid = $8
`
_, err := s.db.Exec(context.Background(), query,
calendarData, now, fmt.Sprintf("%d", now), len(calendarData),
input.Start.Unix(), input.End.Unix(),
calendarID, uid,
)
return err
}
// DeleteEvent deletes an event by UID
func (s *BaseService) DeleteEvent(calendarID int, uid string) error {
query := `DELETE FROM dav_calendarobjects WHERE calendarid = $1 AND uid = $2`
_, err := s.db.Exec(context.Background(), query, calendarID, uid)
return err
}
// ListEventsForContactSQL returns all calendar events where the given contact URI is an attendee (SQL optimized)
func (s *BaseService) ListEventsForContact(contactURI string) ([]CalendarEvent, error) {
// Use pattern matching to find events containing the contact URI in ATTENDEE lines
likePattern := "%" + contactURI + "%"
query := `
SELECT id, calendarid, uri, calendardata, lastmodified, etag, size, componenttype,
firstoccurence, lastoccurence, uid
FROM dav_calendarobjects
WHERE calendardata LIKE $1
ORDER BY firstoccurence
`
return s.queryEventsWithContacts(query, likePattern)
}