170 lines
5.5 KiB
Python
170 lines
5.5 KiB
Python
"""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)
|
|
)
|
|
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."""
|
|
import re
|
|
|
|
# Auto-generate slug if not provided
|
|
if not org_in.slug:
|
|
base_slug = re.sub(r'[^\w\s-]', '', org_in.name.lower())
|
|
base_slug = re.sub(r'[-\s]+', '-', base_slug).strip('-')
|
|
|
|
# Check uniqueness and add number if needed
|
|
slug = base_slug
|
|
counter = 1
|
|
while True:
|
|
result = await db.execute(select(Organization).where(Organization.slug == slug))
|
|
if not result.scalar_one_or_none():
|
|
break
|
|
counter += 1
|
|
slug = f"{base_slug}-{counter}"
|
|
else:
|
|
slug = org_in.slug
|
|
# Check slug uniqueness
|
|
result = await db.execute(select(Organization).where(Organization.slug == 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=slug
|
|
)
|
|
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
|