commissions.py 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. from uuid import UUID
  2. from fastapi import APIRouter, Depends, HTTPException
  3. from sqlalchemy import select
  4. from sqlalchemy.orm import Session
  5. from app.api import deps
  6. from app.api.utils import assert_project_exists_or_404
  7. from app.models import Commission, CommissionMember, OrgRole, User
  8. from app.schemas.requests import (
  9. CommissionCreateRequest,
  10. CommissionMembershipRequest,
  11. CommissionUpdateRequest,
  12. )
  13. from app.schemas.responses import CommissionResponse
  14. router = APIRouter(prefix="/project/{project_id}", tags=["commissions"])
  15. READ_COMMISSIONS = (OrgRole.ORG_ADMIN, OrgRole.RESPO_BENEVOLE, OrgRole.RESPO_COMMISSION)
  16. MANAGE_COMMISSIONS = (OrgRole.ORG_ADMIN, OrgRole.RESPO_BENEVOLE)
  17. def _get_commission_or_404(session: Session, project_id: UUID, commission_id: UUID) -> Commission:
  18. commission = session.get(Commission, commission_id)
  19. if commission is None or commission.project_id != str(project_id):
  20. raise HTTPException(status_code=404, detail="Commission not found")
  21. return commission
  22. @router.get("/commissions", response_model=list[CommissionResponse])
  23. async def list_project_commissions(
  24. project_id: UUID,
  25. current_user: User = Depends(deps.require_org_role(*READ_COMMISSIONS)),
  26. session: Session = Depends(deps.get_session),
  27. ):
  28. """List all commissions for a specific project"""
  29. assert_project_exists_or_404(session, project_id)
  30. results = session.execute(select(Commission).where(Commission.project_id == project_id))
  31. return results.scalars().all()
  32. @router.post("/commission", response_model=CommissionResponse)
  33. async def create_commission(
  34. project_id: UUID,
  35. new_commission: CommissionCreateRequest,
  36. current_user: User = Depends(deps.require_org_role(*MANAGE_COMMISSIONS)),
  37. session: Session = Depends(deps.get_session),
  38. ):
  39. """Create a new commission in the project"""
  40. assert_project_exists_or_404(session, project_id)
  41. commission = Commission(project_id=project_id, **new_commission.model_dump())
  42. session.add(commission)
  43. session.commit()
  44. session.refresh(commission)
  45. return commission
  46. @router.get("/commission/{commission_id}", response_model=CommissionResponse)
  47. async def get_commission(
  48. project_id: UUID,
  49. commission_id: UUID,
  50. current_user: User = Depends(deps.require_org_role(*READ_COMMISSIONS)),
  51. session: Session = Depends(deps.get_session),
  52. ):
  53. """Get a single commission"""
  54. return _get_commission_or_404(session, project_id, commission_id)
  55. @router.patch("/commission/{commission_id}", response_model=CommissionResponse)
  56. async def update_commission(
  57. project_id: UUID,
  58. commission_id: UUID,
  59. edit_commission: CommissionUpdateRequest,
  60. current_user: User = Depends(deps.require_org_role(*MANAGE_COMMISSIONS)),
  61. session: Session = Depends(deps.get_session),
  62. ):
  63. """Edit a commission's details"""
  64. commission = _get_commission_or_404(session, project_id, commission_id)
  65. commission.name = edit_commission.name
  66. session.commit()
  67. session.refresh(commission)
  68. return commission
  69. @router.delete("/commission/{commission_id}")
  70. async def delete_commission(
  71. project_id: UUID,
  72. commission_id: UUID,
  73. current_user: User = Depends(deps.require_org_role(*MANAGE_COMMISSIONS)),
  74. session: Session = Depends(deps.get_session),
  75. ):
  76. """Delete a commission (automatically deletes CommissionMember links via cascade)"""
  77. commission = _get_commission_or_404(session, project_id, commission_id)
  78. session.delete(commission)
  79. session.commit()
  80. @router.post("/commission/{commission_id}/members", response_model=CommissionResponse)
  81. async def add_members_to_commission(
  82. project_id: UUID,
  83. commission_id: UUID,
  84. payload: CommissionMembershipRequest,
  85. current_user: User = Depends(deps.require_org_role(*MANAGE_COMMISSIONS)),
  86. session: Session = Depends(deps.get_session),
  87. ):
  88. """Bulk-add users as members to a commission (idempotent)"""
  89. commission = _get_commission_or_404(session, project_id, commission_id)
  90. # Verify users exist
  91. valid_users = session.scalars(select(User).where(User.id.in_(payload.user_ids))).all()
  92. if len(valid_users) != len(payload.user_ids):
  93. raise HTTPException(status_code=400, detail="One or more invalid user IDs")
  94. existing_user_ids = {str(m.user_id) for m in commission.members}
  95. for user in valid_users:
  96. if str(user.id) in existing_user_ids:
  97. continue
  98. new_member = CommissionMember(commission_id=commission.id, user_id=user.id)
  99. commission.members.append(new_member)
  100. session.commit()
  101. session.refresh(commission)
  102. return commission
  103. @router.delete("/commission/{commission_id}/member/{user_id}", response_model=CommissionResponse)
  104. async def remove_member_from_commission(
  105. project_id: UUID,
  106. commission_id: UUID,
  107. user_id: UUID,
  108. current_user: User = Depends(deps.require_org_role(*MANAGE_COMMISSIONS)),
  109. session: Session = Depends(deps.get_session),
  110. ):
  111. """Remove a single user from a commission"""
  112. commission = _get_commission_or_404(session, project_id, commission_id)
  113. member_to_remove = next((m for m in commission.members if str(m.user_id) == str(user_id)), None)
  114. if member_to_remove:
  115. session.delete(member_to_remove)
  116. session.commit()
  117. session.refresh(commission)
  118. return commission