feat: add email, scheduler, and Zulip integration services
- Add email service for sending meeting invites with ICS attachments - Add scheduler for background calendar sync jobs - Add Zulip service for meeting notifications - Make ics_url optional for participants - Add /api/schedule endpoint with 2-hour lead time validation - Update frontend to support scheduling flow Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -16,12 +16,24 @@ from app.schemas import (
|
||||
ParticipantCreate,
|
||||
ParticipantResponse,
|
||||
SyncResponse,
|
||||
ScheduleRequest,
|
||||
)
|
||||
from app.scheduler import start_scheduler, stop_scheduler
|
||||
from app.email_service import send_meeting_invite
|
||||
from app.zulip_service import send_zulip_notification
|
||||
from contextlib import asynccontextmanager
|
||||
from datetime import datetime, timezone, timedelta
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
app = FastAPI(title="Common Availability API")
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
start_scheduler()
|
||||
yield
|
||||
stop_scheduler()
|
||||
|
||||
app = FastAPI(title="Common Availability API", lifespan=lifespan)
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
@@ -57,7 +69,8 @@ async def create_participant(
|
||||
await db.refresh(participant)
|
||||
|
||||
try:
|
||||
await sync_participant_calendar(db, participant)
|
||||
if participant.ics_url:
|
||||
await sync_participant_calendar(db, participant)
|
||||
except Exception as e:
|
||||
logger.warning(f"Initial sync failed for {participant.email}: {e}")
|
||||
|
||||
@@ -121,7 +134,57 @@ async def sync_participant(participant_id: UUID, db: AsyncSession = Depends(get_
|
||||
raise HTTPException(status_code=404, detail="Participant not found")
|
||||
|
||||
try:
|
||||
count = await sync_participant_calendar(db, participant)
|
||||
return {"status": "success", "blocks_synced": count}
|
||||
if participant.ics_url:
|
||||
count = await sync_participant_calendar(db, participant)
|
||||
return {"status": "success", "blocks_synced": count}
|
||||
return {"status": "skipped", "message": "No ICS URL provided"}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@app.post("/api/schedule")
|
||||
async def schedule_meeting(
|
||||
data: ScheduleRequest, db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
# 1. Validate Lead Time (2 hours)
|
||||
min_start_time = datetime.now(timezone.utc) + timedelta(hours=2)
|
||||
if data.start_time.replace(tzinfo=timezone.utc) < min_start_time:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="Meetings must be scheduled at least 2 hours in advance."
|
||||
)
|
||||
|
||||
# 2. Fetch Participants
|
||||
result = await db.execute(
|
||||
select(Participant).where(Participant.id.in_(data.participant_ids))
|
||||
)
|
||||
participants = result.scalars().all()
|
||||
|
||||
if len(participants) != len(data.participant_ids):
|
||||
raise HTTPException(status_code=400, detail="Some participants not found")
|
||||
|
||||
participant_dicts = [
|
||||
{"name": p.name, "email": p.email} for p in participants
|
||||
]
|
||||
participant_names = [p.name for p in participants]
|
||||
|
||||
# 3. Send Notifications
|
||||
email_success = await send_meeting_invite(
|
||||
participant_dicts,
|
||||
data.title,
|
||||
data.description,
|
||||
data.start_time,
|
||||
data.end_time
|
||||
)
|
||||
|
||||
zulip_success = send_zulip_notification(
|
||||
data.title,
|
||||
data.start_time,
|
||||
participant_names
|
||||
)
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"email_sent": email_success,
|
||||
"zulip_sent": zulip_success
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user