"""Organization management endpoints.""" from typing import Annotated from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy.orm import Session from app.core.deps import get_current_admin, get_current_user, get_db from app.models.organization import Organization from app.models.organization_member import OrganizationMember from app.models.user import User from app.schemas.organization import ( AddMemberRequest, OrganizationCreate, OrganizationMemberResponse, OrganizationResponse, OrganizationUpdate, ) router = APIRouter(prefix="/organizations", tags=["organizations"]) admin_router = APIRouter(prefix="/admin/organizations", tags=["organizations-admin"]) @router.get("", response_model=list[OrganizationResponse]) def list_organizations( db: Annotated[Session, Depends(get_db)], _: Annotated[User, Depends(get_current_user)], ) -> list[OrganizationResponse]: """List organizations (authenticated users).""" orgs = db.query(Organization).filter(Organization.is_active == True).order_by(Organization.name).all() # noqa: E712 result = [] for org in orgs: member_count = db.query(OrganizationMember).filter(OrganizationMember.organization_id == org.id).count() result.append(OrganizationResponse( id=org.id, name=org.name, description=org.description, is_active=org.is_active, created_at=org.created_at, member_count=member_count, )) return result @router.get("/{org_id}", response_model=OrganizationResponse) def get_organization( org_id: int, db: Annotated[Session, Depends(get_db)], _: Annotated[User, Depends(get_current_user)], ) -> OrganizationResponse: """Get organization detail.""" org = db.query(Organization).filter(Organization.id == org_id).first() if not org: raise HTTPException(status_code=404, detail="Organization not found") member_count = db.query(OrganizationMember).filter(OrganizationMember.organization_id == org.id).count() return OrganizationResponse( id=org.id, name=org.name, description=org.description, is_active=org.is_active, created_at=org.created_at, member_count=member_count, ) @router.get("/{org_id}/members", response_model=list[OrganizationMemberResponse]) def list_organization_members( org_id: int, db: Annotated[Session, Depends(get_db)], current_user: Annotated[User, Depends(get_current_user)], ) -> list[OrganizationMemberResponse]: """List organization members (org admin or superadmin).""" org = db.query(Organization).filter(Organization.id == org_id).first() if not org: raise HTTPException(status_code=404, detail="Organization not found") # Check permission: superadmin or org admin if current_user.role not in ("admin", "superadmin"): membership = db.query(OrganizationMember).filter( OrganizationMember.organization_id == org_id, OrganizationMember.user_id == current_user.id, OrganizationMember.role == "admin", ).first() if not membership: raise HTTPException(status_code=403, detail="Not enough permissions") members = db.query(OrganizationMember).filter(OrganizationMember.organization_id == org_id).all() result = [] for m in members: u = db.query(User).filter(User.id == m.user_id).first() result.append(OrganizationMemberResponse( id=m.id, organization_id=m.organization_id, user_id=m.user_id, role=m.role, user_name=u.full_name if u else None, user_email=u.email if u else None, )) return result @router.post("/{org_id}/members", response_model=OrganizationMemberResponse, status_code=status.HTTP_201_CREATED) def add_organization_member( org_id: int, data: AddMemberRequest, db: Annotated[Session, Depends(get_db)], current_user: Annotated[User, Depends(get_current_user)], ) -> OrganizationMemberResponse: """Add member to organization (org admin or superadmin).""" org = db.query(Organization).filter(Organization.id == org_id).first() if not org: raise HTTPException(status_code=404, detail="Organization not found") # Check permission if current_user.role not in ("admin", "superadmin"): membership = db.query(OrganizationMember).filter( OrganizationMember.organization_id == org_id, OrganizationMember.user_id == current_user.id, OrganizationMember.role == "admin", ).first() if not membership: raise HTTPException(status_code=403, detail="Not enough permissions") # Check if user exists user = db.query(User).filter(User.id == data.user_id).first() if not user: raise HTTPException(status_code=404, detail="User not found") # Check if already member existing = db.query(OrganizationMember).filter( OrganizationMember.organization_id == org_id, OrganizationMember.user_id == data.user_id, ).first() if existing: raise HTTPException(status_code=400, detail="User is already a member") member = OrganizationMember( organization_id=org_id, user_id=data.user_id, role=data.role, ) db.add(member) db.commit() db.refresh(member) return OrganizationMemberResponse( id=member.id, organization_id=member.organization_id, user_id=member.user_id, role=member.role, user_name=user.full_name, user_email=user.email, ) @router.delete("/{org_id}/members/{user_id}", status_code=status.HTTP_204_NO_CONTENT) def remove_organization_member( org_id: int, user_id: int, db: Annotated[Session, Depends(get_db)], current_user: Annotated[User, Depends(get_current_user)], ) -> None: """Remove member from organization.""" if current_user.role not in ("admin", "superadmin"): membership = db.query(OrganizationMember).filter( OrganizationMember.organization_id == org_id, OrganizationMember.user_id == current_user.id, OrganizationMember.role == "admin", ).first() if not membership: raise HTTPException(status_code=403, detail="Not enough permissions") member = db.query(OrganizationMember).filter( OrganizationMember.organization_id == org_id, OrganizationMember.user_id == user_id, ).first() if not member: raise HTTPException(status_code=404, detail="Member not found") db.delete(member) db.commit() @router.put("/{org_id}/members/{user_id}", response_model=OrganizationMemberResponse) def update_member_role( org_id: int, user_id: int, data: AddMemberRequest, db: Annotated[Session, Depends(get_db)], current_user: Annotated[User, Depends(get_current_user)], ) -> OrganizationMemberResponse: """Change member role in organization.""" if current_user.role not in ("admin", "superadmin"): membership = db.query(OrganizationMember).filter( OrganizationMember.organization_id == org_id, OrganizationMember.user_id == current_user.id, OrganizationMember.role == "admin", ).first() if not membership: raise HTTPException(status_code=403, detail="Not enough permissions") member = db.query(OrganizationMember).filter( OrganizationMember.organization_id == org_id, OrganizationMember.user_id == user_id, ).first() if not member: raise HTTPException(status_code=404, detail="Member not found") member.role = data.role db.commit() db.refresh(member) u = db.query(User).filter(User.id == user_id).first() return OrganizationMemberResponse( id=member.id, organization_id=member.organization_id, user_id=member.user_id, role=member.role, user_name=u.full_name if u else None, user_email=u.email if u else None, ) # === Superadmin endpoints === @admin_router.post("", response_model=OrganizationResponse, status_code=status.HTTP_201_CREATED) def create_organization( data: OrganizationCreate, db: Annotated[Session, Depends(get_db)], _: Annotated[User, Depends(get_current_admin)], ) -> OrganizationResponse: """Create an organization (superadmin).""" existing = db.query(Organization).filter(Organization.name == data.name).first() if existing: raise HTTPException(status_code=400, detail="Organization with this name already exists") org = Organization(name=data.name, description=data.description) db.add(org) db.commit() db.refresh(org) return OrganizationResponse( id=org.id, name=org.name, description=org.description, is_active=org.is_active, created_at=org.created_at, member_count=0, ) @admin_router.put("/{org_id}", response_model=OrganizationResponse) def update_organization( org_id: int, data: OrganizationUpdate, db: Annotated[Session, Depends(get_db)], _: Annotated[User, Depends(get_current_admin)], ) -> OrganizationResponse: """Update an organization (superadmin).""" org = db.query(Organization).filter(Organization.id == org_id).first() if not org: raise HTTPException(status_code=404, detail="Organization not found") if data.name is not None: org.name = data.name if data.description is not None: org.description = data.description db.commit() db.refresh(org) member_count = db.query(OrganizationMember).filter(OrganizationMember.organization_id == org.id).count() return OrganizationResponse( id=org.id, name=org.name, description=org.description, is_active=org.is_active, created_at=org.created_at, member_count=member_count, )