Update New Features
This commit is contained in:
127
calendar_service.py
Normal file
127
calendar_service.py
Normal file
@@ -0,0 +1,127 @@
|
||||
"""
|
||||
Calendar Service for generating iCalendar (.ics) data
|
||||
Implements RFC 5545 iCalendar format for universal calendar compatibility
|
||||
"""
|
||||
|
||||
from icalendar import Calendar, Event as iCalEvent, Alarm
|
||||
from datetime import datetime, timedelta
|
||||
from zoneinfo import ZoneInfo
|
||||
import uuid
|
||||
import os
|
||||
|
||||
|
||||
class CalendarService:
|
||||
"""Service for generating iCalendar (.ics) data compatible with all calendar apps"""
|
||||
|
||||
def __init__(self):
|
||||
self.domain = os.getenv('CALENDAR_DOMAIN', 'loaf.community')
|
||||
self.timezone = ZoneInfo(os.getenv('CALENDAR_TIMEZONE', 'America/New_York'))
|
||||
|
||||
def generate_event_uid(self) -> str:
|
||||
"""
|
||||
Generate unique event identifier (UUID4 hex-encoded per RFC 7986)
|
||||
|
||||
Returns:
|
||||
str: Unique identifier in format {uuid}@{domain}
|
||||
"""
|
||||
return f"{uuid.uuid4().hex}@{self.domain}"
|
||||
|
||||
def event_to_ical_event(self, event, include_reminder: bool = True):
|
||||
"""
|
||||
Convert database Event model to iCalendar Event component
|
||||
|
||||
Args:
|
||||
event: Event model instance from database
|
||||
include_reminder: Whether to add 1-hour reminder alarm
|
||||
|
||||
Returns:
|
||||
icalendar.Event: iCalendar event component
|
||||
"""
|
||||
ical_event = iCalEvent()
|
||||
|
||||
# Required properties
|
||||
ical_event.add('uid', event.calendar_uid or self.generate_event_uid())
|
||||
ical_event.add('dtstamp', datetime.now(self.timezone))
|
||||
ical_event.add('dtstart', event.start_at)
|
||||
ical_event.add('dtend', event.end_at)
|
||||
ical_event.add('summary', event.title)
|
||||
|
||||
# Optional properties
|
||||
if event.description:
|
||||
ical_event.add('description', event.description)
|
||||
if event.location:
|
||||
ical_event.add('location', event.location)
|
||||
|
||||
# Metadata
|
||||
ical_event.add('url', f"https://{self.domain}/events/{event.id}")
|
||||
ical_event.add('status', 'CONFIRMED')
|
||||
ical_event.add('sequence', 0)
|
||||
|
||||
# Add 1-hour reminder (VALARM component)
|
||||
if include_reminder:
|
||||
alarm = Alarm()
|
||||
alarm.add('action', 'DISPLAY')
|
||||
alarm.add('description', f"Reminder: {event.title}")
|
||||
alarm.add('trigger', timedelta(hours=-1))
|
||||
ical_event.add_component(alarm)
|
||||
|
||||
return ical_event
|
||||
|
||||
def create_calendar(self, name: str, description: str = None):
|
||||
"""
|
||||
Create base calendar with metadata
|
||||
|
||||
Args:
|
||||
name: Calendar name (X-WR-CALNAME)
|
||||
description: Optional calendar description
|
||||
|
||||
Returns:
|
||||
icalendar.Calendar: Base calendar object
|
||||
"""
|
||||
cal = Calendar()
|
||||
cal.add('prodid', '-//LOAF Membership Platform//EN')
|
||||
cal.add('version', '2.0')
|
||||
cal.add('x-wr-calname', name)
|
||||
cal.add('x-wr-timezone', str(self.timezone))
|
||||
if description:
|
||||
cal.add('x-wr-caldesc', description)
|
||||
cal.add('method', 'PUBLISH')
|
||||
cal.add('calscale', 'GREGORIAN')
|
||||
return cal
|
||||
|
||||
def create_single_event_calendar(self, event) -> bytes:
|
||||
"""
|
||||
Create calendar with single event for download
|
||||
|
||||
Args:
|
||||
event: Event model instance
|
||||
|
||||
Returns:
|
||||
bytes: iCalendar data as bytes
|
||||
"""
|
||||
cal = self.create_calendar(event.title)
|
||||
ical_event = self.event_to_ical_event(event)
|
||||
cal.add_component(ical_event)
|
||||
return cal.to_ics()
|
||||
|
||||
def create_subscription_feed(self, events: list, feed_name: str) -> bytes:
|
||||
"""
|
||||
Create calendar subscription feed with multiple events
|
||||
|
||||
Args:
|
||||
events: List of Event model instances
|
||||
feed_name: Name for the calendar feed
|
||||
|
||||
Returns:
|
||||
bytes: iCalendar data as bytes
|
||||
"""
|
||||
cal = self.create_calendar(
|
||||
feed_name,
|
||||
description="LOAF Community Events - Auto-syncing calendar feed"
|
||||
)
|
||||
|
||||
for event in events:
|
||||
ical_event = self.event_to_ical_event(event)
|
||||
cal.add_component(ical_event)
|
||||
|
||||
return cal.to_ics()
|
||||
Reference in New Issue
Block a user