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 ,
2628)
2729from exceptions .exceptions import EmailSendFailedError
2830from utils .core .htmx import is_htmx_request , append_toast
3234from routers .core .organization import (
3335 router as org_router ,
3436) # Already imported, check usage
37+ from routers .core .user import _load_org_for_members_partial
3538
3639# Setup logger
3740logger = getLogger ("uvicorn.error" )
@@ -161,11 +164,20 @@ async def create_invitation(
161164
162165 # HTMX: return partial; non-HTMX: PRG redirect
163166 if is_htmx_request (request ):
164- active_invitations = Invitation .get_active_for_org (session , organization_id )
167+ organization , user_permissions , active_invitations = (
168+ _load_org_for_members_partial (session , organization_id , current_user )
169+ )
165170 response = templates .TemplateResponse (
166171 request ,
167- "organization/partials/invitations_list.html" ,
168- {"active_invitations" : active_invitations },
172+ "organization/partials/members_table.html" ,
173+ {
174+ "organization" : organization ,
175+ "active_invitations" : active_invitations ,
176+ "user" : current_user ,
177+ "user_permissions" : user_permissions ,
178+ "ValidPermissions" : ValidPermissions ,
179+ "all_permissions" : list (ValidPermissions ) + list (AppPermissions ),
180+ },
169181 )
170182 response .headers ["HX-Trigger" ] = "modalDismiss"
171183 return append_toast (
@@ -174,6 +186,59 @@ async def create_invitation(
174186 return RedirectResponse (url = f"/organizations/{ organization_id } " , status_code = 303 )
175187
176188
189+ @router .post ("/delete" , name = "delete_invitation" , response_class = RedirectResponse )
190+ async def delete_invitation (
191+ request : Request ,
192+ current_user : User = Depends (get_authenticated_user ),
193+ session : Session = Depends (get_session ),
194+ invitation_id : int = Form (
195+ ..., title = "Invitation ID" , description = "ID of the invitation to delete"
196+ ),
197+ organization_id : int = Form (
198+ ...,
199+ title = "Organization ID" ,
200+ description = "ID of the organization the invitation belongs to" ,
201+ ),
202+ ) -> Response :
203+ organization = session .get (Organization , organization_id )
204+ if not organization :
205+ raise OrganizationNotFoundError ()
206+
207+ if not current_user .has_permission (ValidPermissions .INVITE_USER , organization ):
208+ raise HTTPException (
209+ status_code = 403 ,
210+ detail = "You don't have permission to manage invitations for this organization" ,
211+ )
212+
213+ invitation = session .get (Invitation , invitation_id )
214+ if not invitation or invitation .organization_id != organization_id :
215+ raise InvitationNotFoundError ()
216+
217+ session .delete (invitation )
218+ session .commit ()
219+
220+ if is_htmx_request (request ):
221+ organization , user_permissions , active_invitations = (
222+ _load_org_for_members_partial (session , organization_id , current_user )
223+ )
224+ response = templates .TemplateResponse (
225+ request ,
226+ "organization/partials/members_table.html" ,
227+ {
228+ "organization" : organization ,
229+ "active_invitations" : active_invitations ,
230+ "user" : current_user ,
231+ "user_permissions" : user_permissions ,
232+ "ValidPermissions" : ValidPermissions ,
233+ "all_permissions" : list (ValidPermissions ) + list (AppPermissions ),
234+ },
235+ )
236+ return append_toast (
237+ response , request , templates , "Invitation cancelled successfully."
238+ )
239+ return RedirectResponse (url = f"/organizations/{ organization_id } " , status_code = 303 )
240+
241+
177242@router .get ("/accept" , name = "accept_invitation" )
178243async def accept_invitation (
179244 invitation : Invitation = Depends (get_valid_invitation ),
0 commit comments