Initial commit. Working login, example UI with prototype and demo, connections to DB and DAV, local and prod setups.
This commit is contained in:
@@ -0,0 +1,276 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user