slots.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. from uuid import UUID
  2. from fastapi import APIRouter, Depends, HTTPException
  3. from sqlalchemy import delete, select
  4. from sqlalchemy.orm import Session
  5. from app.api import deps
  6. from app.api.SSEBroadcasterRoute import SSEBroadcasterRoute
  7. from app.api.utils import assert_project_exists_or_404, update_object_from_payload, verify_id_list
  8. from app.models import (
  9. OrgRole,
  10. Slot,
  11. SlotTemplate,
  12. User,
  13. Volunteer,
  14. association_table_volunteer_slot,
  15. )
  16. from app.schemas.requests import (
  17. SlotCreateRequest,
  18. SlotUpdateRequest,
  19. )
  20. from app.schemas.responses import SlotResponse
  21. router = APIRouter(
  22. route_class=SSEBroadcasterRoute, prefix="/project/{project_id}", tags=["project"]
  23. )
  24. READ_ROLES = (OrgRole.ORG_ADMIN, OrgRole.RESPO_BENEVOLE, OrgRole.RESPO_COMMISSION)
  25. WRITE_ROLES = (OrgRole.ORG_ADMIN, OrgRole.RESPO_BENEVOLE, OrgRole.RESPO_COMMISSION)
  26. def assert_template_from_commission(
  27. session: Session, current_user: User, project_id: UUID, template_id: UUID
  28. ):
  29. template_row = session.execute(
  30. select(SlotTemplate.commission_id, SlotTemplate.project_id).where(
  31. SlotTemplate.id == template_id
  32. )
  33. ).first()
  34. if template_row is None or template_row[1] != str(project_id):
  35. raise HTTPException(400, "Invalid template id")
  36. deps.assert_commission_ownership(session, current_user, project_id, template_row[0])
  37. @router.get("/slots", response_model=list[SlotResponse])
  38. async def list_project_slots(
  39. project_id: UUID,
  40. current_user: User = Depends(deps.require_org_role(*READ_ROLES)),
  41. session: Session = Depends(deps.get_session),
  42. ):
  43. """List slots from project"""
  44. assert_project_exists_or_404(session, project_id)
  45. results = session.execute(select(Slot).where(Slot.project_id == project_id))
  46. return results.scalars().all()
  47. @router.post("/slot", response_model=SlotResponse, openapi_extra={"sse_event": "slot_created"})
  48. async def create_slot(
  49. project_id: UUID,
  50. new_slot: SlotCreateRequest,
  51. current_user: User = Depends(deps.require_org_role(*WRITE_ROLES)),
  52. session: Session = Depends(deps.get_session),
  53. ):
  54. """Create a new slot to the project"""
  55. assert_project_exists_or_404(session, project_id)
  56. if new_slot.template_id:
  57. assert_template_from_commission(session, current_user, project_id, new_slot.template_id)
  58. input_dict = new_slot.model_dump()
  59. # Extract volunteer list from input dict
  60. volunteers: list[UUID] = []
  61. if input_dict["volunteers"] is not None:
  62. volunteers = input_dict["volunteers"]
  63. await verify_id_list(session, volunteers, project_id, Volunteer, "Invalid volunteer list")
  64. del input_dict["volunteers"]
  65. slot = Slot(project_id=project_id, **input_dict)
  66. session.add(slot)
  67. session.commit()
  68. # Add the slot to the list of volunteer
  69. if len(volunteers) > 0:
  70. session.execute(
  71. association_table_volunteer_slot.insert().values(
  72. [(volunteer_id, slot.id) for volunteer_id in volunteers]
  73. )
  74. )
  75. session.commit()
  76. return slot
  77. @router.post(
  78. "/slot/{slot_id}", response_model=SlotResponse, openapi_extra={"sse_event": "slot_updated"}
  79. )
  80. async def update_slot(
  81. project_id: UUID,
  82. slot_id: UUID,
  83. new_slot: SlotUpdateRequest,
  84. current_user: User = Depends(deps.require_org_role(*WRITE_ROLES)),
  85. session: Session = Depends(deps.get_session),
  86. ):
  87. """Update a slot from the project"""
  88. slot = session.get(Slot, slot_id)
  89. if (slot is None) or (slot.project_id != str(project_id)):
  90. raise HTTPException(status_code=404, detail="Slot not found : ")
  91. if new_slot.template_id:
  92. assert_template_from_commission(session, current_user, project_id, new_slot.template_id)
  93. if slot.template_id is not None:
  94. assert_template_from_commission(session, current_user, project_id, slot.template_id)
  95. input_dict = new_slot.model_dump(exclude_unset=True)
  96. if "volunteers" in input_dict:
  97. volunteers: list[UUID] = input_dict["volunteers"]
  98. await verify_id_list(session, volunteers, project_id, Volunteer, "Invalid volunteer list")
  99. session.execute(
  100. association_table_volunteer_slot.delete().where(
  101. association_table_volunteer_slot.c.slot_id == slot.id
  102. )
  103. )
  104. if len(volunteers) > 0:
  105. session.execute(
  106. association_table_volunteer_slot.insert().values(
  107. [(volunteer_id, slot.id) for volunteer_id in volunteers]
  108. )
  109. )
  110. del input_dict["volunteers"]
  111. if "template_id" in input_dict and input_dict["template_id"] == "":
  112. slot.template_id = None
  113. del input_dict["template_id"]
  114. update_object_from_payload(slot, input_dict)
  115. session.commit()
  116. session.refresh(slot)
  117. return slot
  118. @router.delete("/slot/{slot_id}", openapi_extra={"sse_event": "slot_deleted"})
  119. async def delete_slot(
  120. project_id: UUID,
  121. slot_id: UUID,
  122. current_user: User = Depends(deps.require_org_role(*WRITE_ROLES)),
  123. session: Session = Depends(deps.get_session),
  124. ):
  125. """Delete a slot from the project"""
  126. result = session.execute(
  127. select(Slot.id, Slot.template_id).where(Slot.id == slot_id, Slot.project_id == project_id)
  128. ).first()
  129. if result is None:
  130. raise HTTPException(status_code=404, detail="Slot not found")
  131. if result[1] is not None:
  132. assert_template_from_commission(session, current_user, project_id, result[1])
  133. session.execute(delete(Slot).where(Slot.id == slot_id))
  134. session.commit()