"""Organization management endpoints.""" from typing import List from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select, func from app.core.database import get_db from app.models.user import User from app.models.organization import Organization, OrganizationMember, MemberRole from app.schemas.organization import OrganizationCreate, OrganizationRead, OrganizationUpdate, MemberCreate, MemberRead from app.api.deps import get_current_user, require_role from app.services.email import EmailService router = APIRouter() @router.get("/", response_model=List[OrganizationRead]) async def list_organizations( user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db) ): """List organizations user belongs to.""" result = await db.execute( select(Organization) .join(OrganizationMember) .where(OrganizationMember.user_id == user.id) .where(Organization.is_active == True) ) return result.scalars().all() @router.post("/", response_model=OrganizationRead, status_code=status.HTTP_201_CREATED) async def create_organization( org_in: OrganizationCreate, user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db) ): """Create a new organization.""" # Check slug uniqueness result = await db.execute(select(Organization).where(Organization.slug == org_in.slug)) if result.scalar_one_or_none(): raise HTTPException(status_code=400, detail="Slug already exists") # Create org org = Organization( name=org_in.name, slug=org_in.slug, description=org_in.description ) db.add(org) await db.flush() # Add creator as owner member = OrganizationMember( organization_id=org.id, user_id=user.id, role=MemberRole.OWNER ) db.add(member) return org @router.get("/{org_id}", response_model=OrganizationRead) async def get_organization( org_id: int, user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db) ): """Get organization details.""" result = await db.execute(select(Organization).where(Organization.id == org_id)) org = result.scalar_one_or_none() if not org: raise HTTPException(status_code=404, detail="Organization not found") return org @router.patch("/{org_id}", response_model=OrganizationRead) async def update_organization( org_id: int, org_update: OrganizationUpdate, member: OrganizationMember = Depends(require_role("admin")), db: AsyncSession = Depends(get_db) ): """Update organization (admin only).""" result = await db.execute(select(Organization).where(Organization.id == org_id)) org = result.scalar_one_or_none() if not org: raise HTTPException(status_code=404, detail="Organization not found") for field, value in org_update.dict(exclude_unset=True).items(): setattr(org, field, value) return org @router.get("/{org_id}/members", response_model=List[MemberRead]) async def list_members( org_id: int, user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db) ): """List organization members.""" result = await db.execute( select(OrganizationMember) .where(OrganizationMember.organization_id == org_id) ) return result.scalars().all() @router.post("/{org_id}/members", response_model=MemberRead, status_code=status.HTTP_201_CREATED) async def invite_member( org_id: int, member_in: MemberCreate, current_member: OrganizationMember = Depends(require_role("admin")), db: AsyncSession = Depends(get_db) ): """Invite a new member (admin only).""" # Find or create user result = await db.execute(select(User).where(User.email == member_in.email)) user = result.scalar_one_or_none() if not user: # Create placeholder user from app.core.security import get_password_hash import secrets user = User( email=member_in.email, hashed_password=get_password_hash(secrets.token_urlsafe(32)), is_active=False # Will activate on first login ) db.add(user) await db.flush() # Check if already member result = await db.execute( select(OrganizationMember) .where(OrganizationMember.organization_id == org_id) .where(OrganizationMember.user_id == user.id) ) if result.scalar_one_or_none(): raise HTTPException(status_code=400, detail="User is already a member") # Add member member = OrganizationMember( organization_id=org_id, user_id=user.id, role=member_in.role, invited_by_id=current_member.user_id ) db.add(member) # Get org name for email org_result = await db.execute(select(Organization).where(Organization.id == org_id)) org = org_result.scalar_one() # Send welcome email await EmailService.send_welcome(user.email, user.full_name or user.email, org.name) return member