jira-ai-fixer/app/api/organizations.py

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