Phase 5: User appointments view and cancellation with UI and e2e tests

This commit is contained in:
counterweight 2025-12-21 00:24:16 +01:00
parent 8ff03a8ec3
commit 5108a620e7
Signed by: counterweight
GPG key ID: 883EDBAA726BD96C
14 changed files with 1539 additions and 4 deletions

View file

@ -195,3 +195,86 @@ async def create_booking(
cancelled_at=appointment.cancelled_at,
)
# =============================================================================
# User's Appointments Endpoints
# =============================================================================
appointments_router = APIRouter(prefix="/api/appointments", tags=["appointments"])
@appointments_router.get("", response_model=list[AppointmentResponse])
async def get_my_appointments(
db: AsyncSession = Depends(get_db),
current_user: User = Depends(require_permission(Permission.VIEW_OWN_APPOINTMENTS)),
) -> list[AppointmentResponse]:
"""Get the current user's appointments, sorted by date (upcoming first)."""
result = await db.execute(
select(Appointment)
.where(Appointment.user_id == current_user.id)
.order_by(Appointment.slot_start.desc())
)
appointments = result.scalars().all()
return [
AppointmentResponse(
id=apt.id,
user_id=apt.user_id,
user_email=current_user.email,
slot_start=apt.slot_start,
slot_end=apt.slot_end,
note=apt.note,
status=apt.status.value,
created_at=apt.created_at,
cancelled_at=apt.cancelled_at,
)
for apt in appointments
]
@appointments_router.post("/{appointment_id}/cancel", response_model=AppointmentResponse)
async def cancel_my_appointment(
appointment_id: int,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(require_permission(Permission.CANCEL_OWN_APPOINTMENT)),
) -> AppointmentResponse:
"""Cancel one of the current user's appointments."""
# Get the appointment
result = await db.execute(
select(Appointment).where(Appointment.id == appointment_id)
)
appointment = result.scalar_one_or_none()
if not appointment:
raise HTTPException(status_code=404, detail="Appointment not found")
# Verify ownership
if appointment.user_id != current_user.id:
raise HTTPException(status_code=403, detail="Cannot cancel another user's appointment")
# Check if already cancelled
if appointment.status != AppointmentStatus.BOOKED:
raise HTTPException(
status_code=400,
detail=f"Cannot cancel appointment with status '{appointment.status.value}'"
)
# Cancel the appointment
appointment.status = AppointmentStatus.CANCELLED_BY_USER
appointment.cancelled_at = datetime.now(timezone.utc)
await db.commit()
await db.refresh(appointment)
return AppointmentResponse(
id=appointment.id,
user_id=appointment.user_id,
user_email=current_user.email,
slot_start=appointment.slot_start,
slot_end=appointment.slot_end,
note=appointment.note,
status=appointment.status.value,
created_at=appointment.created_at,
cancelled_at=appointment.cancelled_at,
)