11from uuid import uuid4
22from typing import Optional
33from fastapi import APIRouter , Depends , Form , Query , Request , status
4- from fastapi .responses import RedirectResponse
4+ from fastapi .responses import RedirectResponse , Response
55from fastapi .templating import Jinja2Templates
66from fastapi .exceptions import HTTPException
77from pydantic import EmailStr
1515)
1616from utils .core .models import User , Role , Account , Invitation , Organization
1717from utils .core .enums import ValidPermissions
18+ from utils .app .enums import AppPermissions
1819from utils .core .invitations import send_invitation_email , process_invitation
1920from exceptions .http_exceptions import (
2021 UserIsAlreadyMemberError ,
2324 OrganizationNotFoundError ,
2425 InvitationEmailSendError ,
2526 InvalidInvitationTokenError ,
27+ InvitationNotFoundError ,
28+ InsufficientPermissionsError ,
29+ RoleNotFoundError ,
2630)
2731from exceptions .exceptions import EmailSendFailedError
2832from utils .core .htmx import is_htmx_request , append_toast
29-
30- # Import the account router to generate URLs for login/register
33+ from utils .core .organizations import load_org_for_members_partial
3134from routers .core .account import router as account_router
32- from routers .core .organization import (
33- router as org_router ,
34- ) # Already imported, check usage
35+ from routers .core .organization import router as org_router
3536
3637# Setup logger
3738logger = getLogger ("uvicorn.error" )
@@ -80,15 +81,14 @@ async def create_invitation(
8081
8182 # Check if the current user has permission to invite users to this organization
8283 if not current_user .has_permission (ValidPermissions .INVITE_USER , organization ):
83- raise HTTPException (
84- status_code = 403 ,
85- detail = "You don't have permission to invite users to this organization" ,
84+ raise InsufficientPermissionsError (
85+ "You don't have permission to invite users to this organization"
8686 )
8787
8888 # Verify the role exists and belongs to this organization
8989 role = session .get (Role , role_id )
9090 if not role :
91- raise HTTPException ( status_code = 404 , detail = "Role not found" )
91+ raise RoleNotFoundError ( )
9292 if role .organization_id != organization_id :
9393 raise InvalidRoleForOrganizationError ()
9494
@@ -161,11 +161,20 @@ async def create_invitation(
161161
162162 # HTMX: return partial; non-HTMX: PRG redirect
163163 if is_htmx_request (request ):
164- active_invitations = Invitation .get_active_for_org (session , organization_id )
164+ organization , user_permissions , active_invitations = (
165+ load_org_for_members_partial (session , organization_id , current_user )
166+ )
165167 response = templates .TemplateResponse (
166168 request ,
167- "organization/partials/invitations_list.html" ,
168- {"active_invitations" : active_invitations },
169+ "organization/partials/members_table.html" ,
170+ {
171+ "organization" : organization ,
172+ "active_invitations" : active_invitations ,
173+ "user" : current_user ,
174+ "user_permissions" : user_permissions ,
175+ "ValidPermissions" : ValidPermissions ,
176+ "all_permissions" : list (ValidPermissions ) + list (AppPermissions ),
177+ },
169178 )
170179 response .headers ["HX-Trigger" ] = "modalDismiss"
171180 return append_toast (
@@ -174,6 +183,58 @@ async def create_invitation(
174183 return RedirectResponse (url = f"/organizations/{ organization_id } " , status_code = 303 )
175184
176185
186+ @router .post ("/delete" , name = "delete_invitation" , response_class = RedirectResponse )
187+ async def delete_invitation (
188+ request : Request ,
189+ current_user : User = Depends (get_authenticated_user ),
190+ session : Session = Depends (get_session ),
191+ invitation_id : int = Form (
192+ ..., title = "Invitation ID" , description = "ID of the invitation to delete"
193+ ),
194+ organization_id : int = Form (
195+ ...,
196+ title = "Organization ID" ,
197+ description = "ID of the organization the invitation belongs to" ,
198+ ),
199+ ) -> Response :
200+ organization = session .get (Organization , organization_id )
201+ if not organization :
202+ raise OrganizationNotFoundError ()
203+
204+ if not current_user .has_permission (ValidPermissions .INVITE_USER , organization ):
205+ raise InsufficientPermissionsError (
206+ "You don't have permission to cancel invitations for this organization"
207+ )
208+
209+ invitation = session .get (Invitation , invitation_id )
210+ if not invitation or invitation .organization_id != organization_id :
211+ raise InvitationNotFoundError ()
212+
213+ session .delete (invitation )
214+ session .commit ()
215+
216+ if is_htmx_request (request ):
217+ organization , user_permissions , active_invitations = (
218+ load_org_for_members_partial (session , organization_id , current_user )
219+ )
220+ response = templates .TemplateResponse (
221+ request ,
222+ "organization/partials/members_table.html" ,
223+ {
224+ "organization" : organization ,
225+ "active_invitations" : active_invitations ,
226+ "user" : current_user ,
227+ "user_permissions" : user_permissions ,
228+ "ValidPermissions" : ValidPermissions ,
229+ "all_permissions" : list (ValidPermissions ) + list (AppPermissions ),
230+ },
231+ )
232+ return append_toast (
233+ response , request , templates , "Invitation cancelled successfully."
234+ )
235+ return RedirectResponse (url = f"/organizations/{ organization_id } " , status_code = 303 )
236+
237+
177238@router .get ("/accept" , name = "accept_invitation" )
178239async def accept_invitation (
179240 invitation : Invitation = Depends (get_valid_invitation ),
0 commit comments