179 lines
6.3 KiB
Python
179 lines
6.3 KiB
Python
"""Workspace API routes for VoIdea."""
|
|
|
|
from typing import Annotated
|
|
from uuid import UUID
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, status
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.core.dependencies import get_current_user, get_db
|
|
from app.models.user import User
|
|
from app.schemas.workspace import (
|
|
AddMemberRequest,
|
|
MemberResponse,
|
|
WorkspaceCreate,
|
|
WorkspaceResponse,
|
|
WorkspaceUpdate,
|
|
)
|
|
from app.services.workspace_service import WorkspaceError, WorkspaceLimitError, WorkspaceService
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
def _ws_to_response(ws, member_count=0) -> WorkspaceResponse:
|
|
return WorkspaceResponse(
|
|
id=str(ws.id),
|
|
name=ws.name,
|
|
type=ws.type,
|
|
owner_id=str(ws.owner_id),
|
|
description=ws.description,
|
|
member_count=member_count,
|
|
created_at=ws.created_at,
|
|
updated_at=ws.updated_at,
|
|
)
|
|
|
|
|
|
@router.get("/", response_model=list[WorkspaceResponse])
|
|
async def list_workspaces(
|
|
user: Annotated[User, Depends(get_current_user)],
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
svc = WorkspaceService(db)
|
|
|
|
personal = await svc.create_personal(user.id)
|
|
workspaces = await svc.list_user_workspaces(user.id)
|
|
|
|
result = []
|
|
for ws in workspaces:
|
|
members = await svc.list_members(ws.id)
|
|
result.append(_ws_to_response(ws, member_count=len(members)))
|
|
return result
|
|
|
|
|
|
@router.post("/", response_model=WorkspaceResponse, status_code=status.HTTP_201_CREATED)
|
|
async def create_workspace(
|
|
body: WorkspaceCreate,
|
|
user: Annotated[User, Depends(get_current_user)],
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
svc = WorkspaceService(db)
|
|
try:
|
|
if body.type == "personal":
|
|
ws = await svc.create_personal(user.id, body.name)
|
|
else:
|
|
ws = await svc.create_team(body.name, user.id)
|
|
members = await svc.list_members(ws.id)
|
|
return _ws_to_response(ws, member_count=len(members))
|
|
except WorkspaceLimitError as e:
|
|
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=str(e))
|
|
except WorkspaceError as e:
|
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
|
|
|
|
|
@router.get("/{workspace_id}", response_model=WorkspaceResponse)
|
|
async def get_workspace(
|
|
workspace_id: str,
|
|
user: Annotated[User, Depends(get_current_user)],
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
svc = WorkspaceService(db)
|
|
ws = await svc.get_by_id(UUID(workspace_id))
|
|
if not ws:
|
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Workspace not found")
|
|
membership = await svc.get_membership(ws.id, user.id)
|
|
if not membership:
|
|
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Not a member")
|
|
members = await svc.list_members(ws.id)
|
|
return _ws_to_response(ws, member_count=len(members))
|
|
|
|
|
|
@router.patch("/{workspace_id}", response_model=WorkspaceResponse)
|
|
async def update_workspace(
|
|
workspace_id: str,
|
|
body: WorkspaceUpdate,
|
|
user: Annotated[User, Depends(get_current_user)],
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
svc = WorkspaceService(db)
|
|
try:
|
|
ws = await svc.update_workspace(
|
|
UUID(workspace_id), user.id, body.model_dump(exclude_none=True)
|
|
)
|
|
if not ws:
|
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Workspace not found")
|
|
members = await svc.list_members(ws.id)
|
|
return _ws_to_response(ws, member_count=len(members))
|
|
except WorkspaceError as e:
|
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
|
|
|
|
|
@router.delete("/{workspace_id}", status_code=status.HTTP_204_NO_CONTENT)
|
|
async def delete_workspace(
|
|
workspace_id: str,
|
|
user: Annotated[User, Depends(get_current_user)],
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
svc = WorkspaceService(db)
|
|
try:
|
|
success = await svc.delete_workspace(UUID(workspace_id), user.id)
|
|
if not success:
|
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Workspace not found")
|
|
except WorkspaceError as e:
|
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
|
|
|
|
|
# ── Members ──
|
|
|
|
|
|
@router.get("/{workspace_id}/members", response_model=list[MemberResponse])
|
|
async def list_members(
|
|
workspace_id: str,
|
|
user: Annotated[User, Depends(get_current_user)],
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
svc = WorkspaceService(db)
|
|
ws = await svc.get_by_id(UUID(workspace_id))
|
|
if not ws:
|
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Workspace not found")
|
|
membership = await svc.get_membership(ws.id, user.id)
|
|
if not membership:
|
|
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Not a member")
|
|
members = await svc.list_members(ws.id)
|
|
return [MemberResponse(**m) for m in members]
|
|
|
|
|
|
@router.post("/{workspace_id}/members", response_model=MemberResponse, status_code=status.HTTP_201_CREATED)
|
|
async def add_member(
|
|
workspace_id: str,
|
|
body: AddMemberRequest,
|
|
user: Annotated[User, Depends(get_current_user)],
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
svc = WorkspaceService(db)
|
|
try:
|
|
member = await svc.add_member(UUID(workspace_id), UUID(body.user_id), body.role)
|
|
members = await svc.list_members(ws_id := UUID(workspace_id))
|
|
m = next((m for m in members if m["id"] == str(member.id)), None)
|
|
return MemberResponse(**m) if m else MemberResponse(
|
|
id=str(member.id), user_id=str(member.user_id),
|
|
workspace_id=str(member.workspace_id), role=member.role,
|
|
joined_at=member.created_at,
|
|
)
|
|
except WorkspaceLimitError as e:
|
|
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=str(e))
|
|
except WorkspaceError as e:
|
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
|
|
|
|
|
@router.delete("/{workspace_id}/members/{member_id}", status_code=status.HTTP_204_NO_CONTENT)
|
|
async def remove_member(
|
|
workspace_id: str,
|
|
member_id: str,
|
|
user: Annotated[User, Depends(get_current_user)],
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
svc = WorkspaceService(db)
|
|
success = await svc.remove_member(UUID(workspace_id), UUID(member_id))
|
|
if not success:
|
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Member not found")
|