142 lines
5.5 KiB
Python
142 lines
5.5 KiB
Python
# modules/calendar/service.py
|
|
from sqlalchemy.orm import Session
|
|
from sqlalchemy import or_ # Import or_
|
|
from datetime import datetime
|
|
from modules.calendar.models import CalendarEvent
|
|
from core.exceptions import not_found_exception
|
|
from modules.calendar.schemas import (
|
|
CalendarEventCreate,
|
|
CalendarEventUpdate,
|
|
)
|
|
|
|
# Import the celery app instance instead of the task functions directly
|
|
from core.celery_app import celery_app
|
|
|
|
# Keep task imports if cancel_event_notifications is still called directly and synchronously
|
|
from modules.calendar.tasks import cancel_event_notifications
|
|
|
|
|
|
def create_calendar_event(db: Session, user_id: int, event_data: CalendarEventCreate):
|
|
# Ensure tags is None if not provided or empty list, matching model
|
|
tags_to_store = event_data.tags if event_data.tags else None
|
|
event = CalendarEvent(
|
|
**event_data.model_dump(
|
|
exclude={"tags"}
|
|
), # Use model_dump and exclude tags initially
|
|
tags=tags_to_store, # Set tags separately
|
|
user_id=user_id,
|
|
)
|
|
db.add(event)
|
|
db.commit()
|
|
db.refresh(event)
|
|
# Schedule notifications using send_task
|
|
celery_app.send_task(
|
|
"modules.calendar.tasks.schedule_event_notifications", # Task name as string
|
|
args=[event.id],
|
|
)
|
|
return event
|
|
|
|
|
|
def get_calendar_events(
|
|
db: Session, user_id: int, start: datetime | None, end: datetime | None
|
|
):
|
|
"""
|
|
Retrieves calendar events for a user, optionally filtered by a date range.
|
|
|
|
Args:
|
|
db: The database session.
|
|
user_id: The ID of the user whose events are to be retrieved.
|
|
start: The start datetime of the filter range (inclusive).
|
|
end: The end datetime of the filter range (exclusive).
|
|
|
|
Returns:
|
|
A list of CalendarEvent objects matching the criteria, ordered by start time.
|
|
"""
|
|
print(f"Getting calendar events for user {user_id} in range [{start}, {end})")
|
|
query = db.query(CalendarEvent).filter(CalendarEvent.user_id == user_id)
|
|
|
|
# If start and end dates are provided, filter for events overlapping the range [start, end).
|
|
if start and end:
|
|
# An event overlaps the range [start, end) if:
|
|
# 1. It has a duration (end is not None) AND its interval [event.start, event.end)
|
|
# intersects with [start, end). Intersection occurs if:
|
|
# event.start < end AND event.end > start
|
|
# 2. It's a point event (end is None) AND its start time falls within the range:
|
|
# start <= event.start < end
|
|
query = query.filter(
|
|
or_(
|
|
# Case 1: Event has duration and overlaps
|
|
(CalendarEvent.end != None) # Use SQLAlchemy comparison # noqa: E711
|
|
& (CalendarEvent.start < end)
|
|
& (CalendarEvent.end > start),
|
|
# Case 2: Event is a point event within the range
|
|
(CalendarEvent.end == None) # Use SQLAlchemy comparison # noqa: E711
|
|
& (CalendarEvent.start >= start)
|
|
& (CalendarEvent.start < end),
|
|
)
|
|
)
|
|
# If only start is provided, filter events starting on or after start
|
|
elif start:
|
|
# Includes events with duration starting >= start
|
|
# Includes point events occurring >= start
|
|
query = query.filter(CalendarEvent.start >= start)
|
|
# If only end is provided, filter events ending before end, or point events occurring before end
|
|
elif end:
|
|
# Includes events with duration ending <= end (or starting before end if end is None)
|
|
# Includes point events occurring < end
|
|
query = query.filter(
|
|
or_(
|
|
# Event ends before the specified end time
|
|
(CalendarEvent.end is not None) & (CalendarEvent.end <= end),
|
|
# Point event occurs before the specified end time
|
|
(CalendarEvent.end is None) & (CalendarEvent.start < end),
|
|
)
|
|
)
|
|
# Alternative interpretation for "ending before end": include events that *start* before end
|
|
# query = query.filter(CalendarEvent.start < end)
|
|
|
|
return query.order_by(CalendarEvent.start).all() # Order by start time
|
|
|
|
|
|
def get_calendar_event_by_id(db: Session, user_id: int, event_id: int):
|
|
event = (
|
|
db.query(CalendarEvent)
|
|
.filter(CalendarEvent.id == event_id, CalendarEvent.user_id == user_id)
|
|
.first()
|
|
)
|
|
if not event:
|
|
raise not_found_exception()
|
|
return event
|
|
|
|
|
|
def update_calendar_event(
|
|
db: Session, user_id: int, event_id: int, event_data: CalendarEventUpdate
|
|
):
|
|
event = get_calendar_event_by_id(db, user_id, event_id) # Reuse get_by_id for check
|
|
# Use model_dump with exclude_unset=True to only update provided fields
|
|
update_data = event_data.model_dump(exclude_unset=True)
|
|
|
|
for key, value in update_data.items():
|
|
# Ensure tags is handled correctly (set to None if empty list provided)
|
|
if key == "tags" and isinstance(value, list) and not value:
|
|
setattr(event, key, None)
|
|
else:
|
|
setattr(event, key, value)
|
|
|
|
db.commit()
|
|
db.refresh(event)
|
|
# Re-schedule notifications using send_task
|
|
celery_app.send_task(
|
|
"modules.calendar.tasks.schedule_event_notifications", args=[event.id]
|
|
)
|
|
return event
|
|
|
|
|
|
def delete_calendar_event(db: Session, user_id: int, event_id: int):
|
|
event = get_calendar_event_by_id(db, user_id, event_id) # Reuse get_by_id for check
|
|
# Cancel any scheduled notifications before deleting
|
|
# Run synchronously here or make cancel_event_notifications an async task
|
|
cancel_event_notifications(event_id)
|
|
db.delete(event)
|
|
db.commit()
|