from uuid import UUID from fastapi import APIRouter, Depends, HTTPException from sqlalchemy import delete, select from sqlalchemy.orm import Session from app.api import deps from app.api.SSEBroadcasterRoute import SSEBroadcasterRoute from app.api.utils import assert_project_exists_or_404, update_object_from_payload, verify_id_list from app.models import ( OrgRole, Slot, SlotTemplate, User, Volunteer, association_table_volunteer_slot, ) from app.schemas.requests import ( SlotCreateRequest, SlotUpdateRequest, ) from app.schemas.responses import SlotResponse router = APIRouter( route_class=SSEBroadcasterRoute, prefix="/project/{project_id}", tags=["project"] ) READ_ROLES = (OrgRole.ORG_ADMIN, OrgRole.RESPO_BENEVOLE, OrgRole.RESPO_COMMISSION) WRITE_ROLES = (OrgRole.ORG_ADMIN, OrgRole.RESPO_BENEVOLE, OrgRole.RESPO_COMMISSION) def assert_template_from_commission( session: Session, current_user: User, project_id: UUID, template_id: UUID ): template_row = session.execute( select(SlotTemplate.commission_id, SlotTemplate.project_id).where( SlotTemplate.id == template_id ) ).first() if template_row is None or template_row[1] != str(project_id): raise HTTPException(400, "Invalid template id") deps.assert_commission_ownership(session, current_user, project_id, template_row[0]) @router.get("/slots", response_model=list[SlotResponse]) async def list_project_slots( project_id: UUID, current_user: User = Depends(deps.require_org_role(*READ_ROLES)), session: Session = Depends(deps.get_session), ): """List slots from project""" assert_project_exists_or_404(session, project_id) results = session.execute(select(Slot).where(Slot.project_id == project_id)) return results.scalars().all() @router.post("/slot", response_model=SlotResponse, openapi_extra={"sse_event": "slot_created"}) async def create_slot( project_id: UUID, new_slot: SlotCreateRequest, current_user: User = Depends(deps.require_org_role(*WRITE_ROLES)), session: Session = Depends(deps.get_session), ): """Create a new slot to the project""" assert_project_exists_or_404(session, project_id) if new_slot.template_id: assert_template_from_commission(session, current_user, project_id, new_slot.template_id) input_dict = new_slot.model_dump() # Extract volunteer list from input dict volunteers: list[UUID] = [] if input_dict["volunteers"] is not None: volunteers = input_dict["volunteers"] await verify_id_list(session, volunteers, project_id, Volunteer, "Invalid volunteer list") del input_dict["volunteers"] slot = Slot(project_id=project_id, **input_dict) session.add(slot) session.commit() # Add the slot to the list of volunteer if len(volunteers) > 0: session.execute( association_table_volunteer_slot.insert().values( [(volunteer_id, slot.id) for volunteer_id in volunteers] ) ) session.commit() return slot @router.post( "/slot/{slot_id}", response_model=SlotResponse, openapi_extra={"sse_event": "slot_updated"} ) async def update_slot( project_id: UUID, slot_id: UUID, new_slot: SlotUpdateRequest, current_user: User = Depends(deps.require_org_role(*WRITE_ROLES)), session: Session = Depends(deps.get_session), ): """Update a slot from the project""" slot = session.get(Slot, slot_id) if (slot is None) or (slot.project_id != str(project_id)): raise HTTPException(status_code=404, detail="Slot not found : ") if new_slot.template_id: assert_template_from_commission(session, current_user, project_id, new_slot.template_id) if slot.template_id is not None: assert_template_from_commission(session, current_user, project_id, slot.template_id) input_dict = new_slot.model_dump(exclude_unset=True) if "volunteers" in input_dict: volunteers: list[UUID] = input_dict["volunteers"] await verify_id_list(session, volunteers, project_id, Volunteer, "Invalid volunteer list") session.execute( association_table_volunteer_slot.delete().where( association_table_volunteer_slot.c.slot_id == slot.id ) ) if len(volunteers) > 0: session.execute( association_table_volunteer_slot.insert().values( [(volunteer_id, slot.id) for volunteer_id in volunteers] ) ) del input_dict["volunteers"] if "template_id" in input_dict and input_dict["template_id"] == "": slot.template_id = None del input_dict["template_id"] update_object_from_payload(slot, input_dict) session.commit() session.refresh(slot) return slot @router.delete("/slot/{slot_id}", openapi_extra={"sse_event": "slot_deleted"}) async def delete_slot( project_id: UUID, slot_id: UUID, current_user: User = Depends(deps.require_org_role(*WRITE_ROLES)), session: Session = Depends(deps.get_session), ): """Delete a slot from the project""" result = session.execute( select(Slot.id, Slot.template_id).where(Slot.id == slot_id, Slot.project_id == project_id) ).first() if result is None: raise HTTPException(status_code=404, detail="Slot not found") if result[1] is not None: assert_template_from_commission(session, current_user, project_id, result[1]) session.execute(delete(Slot).where(Slot.id == slot_id)) session.commit()