File size: 2,975 Bytes
582520a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 | """Appointment booking API."""
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload
from app.db import get_db
from app.models import Appointment, Conversation, Message
from app.schemas import AppointmentResponse, BookAppointmentRequest
from app.services import calcom
router = APIRouter(prefix="/api/appointments", tags=["appointments"])
@router.get("/slots")
async def list_slots() -> dict:
"""List next available slots."""
slots = await calcom.get_available_slots(3)
return {"slots": slots}
@router.post("/book", response_model=AppointmentResponse)
async def book_appointment(
body: BookAppointmentRequest,
db: AsyncSession = Depends(get_db),
) -> AppointmentResponse:
"""Book a selected slot for a conversation."""
result = await db.execute(
select(Conversation)
.where(Conversation.id == body.conversation_id)
.options(
selectinload(Conversation.lead),
selectinload(Conversation.appointment),
)
)
conv = result.scalar_one_or_none()
if conv is None:
raise HTTPException(status_code=404, detail="Not found")
if conv.appointment is not None:
raise HTTPException(
status_code=400, detail="Already booked"
)
name = body.attendee_name
email = body.attendee_email
if conv.lead:
name = name or conv.lead.name
email = email or conv.lead.email
booked = await calcom.book_slot(
slot_start=body.slot_start,
slot_end=body.slot_end,
attendee_name=name,
attendee_email=email,
)
appt = Appointment(
conversation_id=conv.id,
slot_start=booked["slot_start"],
slot_end=booked["slot_end"],
attendee_name=name,
attendee_email=email,
external_id=booked.get("external_id"),
status=booked.get("status", "confirmed"),
)
conv.appointment_booked = True
db.add(appt)
confirm = {
"en": (
f"Your meeting is confirmed for "
f"{body.slot_start}. See you then!"
),
"fr": (
f"Votre rendez-vous est confirmé pour "
f"{body.slot_start}. À bientôt !"
),
"ru": (
f"Встреча подтверждена на {body.slot_start}. "
f"До связи!"
),
}
lang = conv.language if conv.language in confirm else "en"
message = confirm[lang]
db.add(
Message(
conversation_id=conv.id,
role="assistant",
content=message,
language=lang,
grounded=True,
)
)
await db.commit()
await db.refresh(appt)
return AppointmentResponse(
id=appt.id,
slot_start=appt.slot_start,
slot_end=appt.slot_end,
status=appt.status,
message=message,
)
|