Sfoglia il codice sorgente

test: implement coedition

clovis 1 mese fa
parent
commit
4d5a9c36a2
1 ha cambiato i file con 59 aggiunte e 1 eliminazioni
  1. 59 1
      app/api/endpoints/organizations.py

+ 59 - 1
app/api/endpoints/organizations.py

@@ -1,3 +1,4 @@
+import secrets
 from uuid import UUID
 
 from fastapi import APIRouter, Depends, HTTPException
@@ -6,11 +7,16 @@ from sqlalchemy.exc import IntegrityError
 from sqlalchemy.orm import Session, selectinload
 
 from app.api import deps
+from app.api.utils import issue_reset_token
+from app.core.config import settings
+from app.core.email import EmailSender, get_email_sender
+from app.core.security import get_password_hash
 from app.models import GlobalRole, Organization, OrgRole, User, UserOrganization
 from app.schemas.requests import (
     OrganizationMembershipCreateRequest,
     OrganizationMembershipUpdateRequest,
     OrganizationRequest,
+    UserInviteRequest,
 )
 from app.schemas.responses import (
     OrganizationDetailResponse,
@@ -167,11 +173,63 @@ async def update_member_role(
     return membership
 
 
+@router.post("/{organization_id}/invite-member", response_model=OrganizationMembershipResponse)
+async def invite_member(
+    organization_id: UUID,
+    payload: UserInviteRequest,
+    current_user: User = Depends(deps.require_organization_role(OrgRole.ORG_ADMIN)),
+    session: Session = Depends(deps.get_session),
+    email_sender: EmailSender = Depends(get_email_sender),
+):
+    """Invite a user into this organization with a given role.
+    If no account exists for the email yet, one is created with an unusable placeholder password and a reset-token email is sent so the person can
+    set their own password.
+    If they already have an account, they're simply added as a member (no email sent -- they already have credentials).
+    Idempotent on membership: inviting someone already in the org is a no-op on the membership row, not an error,
+    since re-sending an invite to fix a typo'd name/role shouldn't fail outright."""
+    org = session.get(Organization, str(organization_id))
+    if org is None:
+        raise HTTPException(status_code=404, detail="Organization not found")
+
+    user = session.scalar(select(User).where(User.email == payload.email))
+    is_new_user = user is None
+
+    if is_new_user:
+        user = User(
+            email=payload.email,
+            name=payload.name,
+            phone_number=payload.phone_number,
+            hashed_password=get_password_hash(secrets.token_urlsafe(32)),  # unusable placeholder
+            must_change_password=True,
+        )
+        session.add(user)
+        session.flush()
+
+    membership = session.get(UserOrganization, (user.id, org.id))
+    if membership is None:
+        membership = UserOrganization(
+            user_id=user.id,
+            organization_id=org.id,
+            role=payload.org_role,
+        )
+        session.add(membership)
+    else:
+        membership.role = payload.org_role
+
+    session.commit()
+    session.refresh(membership)
+
+    if is_new_user:
+        await issue_reset_token(session, user, "invite", email_sender, settings.FRONT_END_URL)
+
+    return membership
+
+
 @router.delete("/{organization_id}/members/{user_id}")
 async def remove_member(
     organization_id: UUID,
     user_id: UUID,
-    current_user: User = Depends(deps.require_super_admin),
+    current_user: User = Depends(deps.require_organization_role(OrgRole.ORG_ADMIN)),
     session: Session = Depends(deps.get_session),
 ):
     """Remove a user from an organization. super_admin only."""