feat: deploy infrastructure + disk/drive, calendar, presentation, workspaces, onboarding, demo user
This commit is contained in:
@@ -0,0 +1,178 @@
|
||||
"""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")
|
||||
Reference in New Issue
Block a user