Files
xiaoxia-saas/apps/api/app/api/routes/workspaces.py
T
Xiaoxia AI ca798703af feat(api): implement complete workspace API routes
- Workspace CRUD: create/list/get_detail
- Member management: invite/list/remove/leave/update_role
- Invitation flow: accept/decline
- Subscription: upgrade/cancel
- Quota status: get usage info
- Connect all 13 endpoints to use cases
- Add proper authentication and permission checks
- Return structured JSON responses

Phase 4 Task 27/68 completed
2026-06-17 07:34:10 +08:00

376 lines
11 KiB
Python

"""
Workspace API 路由(完整实现)
"""
from fastapi import APIRouter, HTTPException, status, Depends
from pydantic import BaseModel, EmailStr
from typing import List
from datetime import datetime
from packages.application.workspace import *
from packages.domain.entities import User
from apps.api.app.dependencies import get_container
from apps.api.app.middleware.auth import get_current_user, require_workspace_access, require_workspace_admin, require_workspace_owner
router = APIRouter(prefix="/workspaces", tags=["Workspaces"])
# ==================== Request/Response Models ====================
class CreateWorkspaceRequestModel(BaseModel):
name: str
subscription_plan: str = "free"
class WorkspaceResponseModel(BaseModel):
workspace_id: str
name: str
subscription_plan: str
max_projects: int
max_storage_gb: int
class InviteMemberRequestModel(BaseModel):
email: EmailStr
role: str # admin, member, viewer
class UpdateMemberRoleRequestModel(BaseModel):
role: str
class UpgradeSubscriptionRequestModel(BaseModel):
new_plan: str # pro, enterprise
# ==================== Workspace CRUD ====================
@router.post("", response_model=WorkspaceResponseModel, status_code=status.HTTP_201_CREATED)
async def create_workspace(
request: CreateWorkspaceRequestModel,
current_user: User = Depends(get_current_user),
):
"""创建工作空间"""
container = get_container()
use_case = container.get_create_workspace_use_case()
req = CreateWorkspaceRequest(
name=request.name,
owner_user_id=current_user.id,
subscription_plan=request.subscription_plan,
)
response, error = use_case.execute(req)
if error:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=error)
return WorkspaceResponseModel(
workspace_id=response.workspace_id,
name=response.name,
subscription_plan=response.subscription_plan,
max_projects=response.max_projects,
max_storage_gb=response.max_storage_gb,
)
@router.get("")
async def list_workspaces(current_user: User = Depends(get_current_user)):
"""获取用户的所有工作空间"""
container = get_container()
use_case = container.get_list_workspaces_use_case()
req = ListWorkspacesRequest(user_id=current_user.id)
response, error = use_case.execute(req)
if error:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=error)
return {
"workspaces": [
{
"workspace_id": ws.workspace_id,
"name": ws.name,
"subscription_plan": ws.subscription_plan,
"max_projects": ws.max_projects,
"max_storage_gb": ws.max_storage_gb,
"member_count": ws.member_count,
"user_role": ws.user_role,
}
for ws in response.workspaces
]
}
@router.get("/{workspace_id}")
async def get_workspace_detail(
workspace_id: str,
current_user: User = Depends(get_current_user),
):
"""获取工作空间详情"""
container = get_container()
use_case = container.get_get_workspace_detail_use_case()
req = GetWorkspaceDetailRequest(workspace_id=workspace_id, user_id=current_user.id)
detail, error = use_case.execute(req)
if error:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=error)
return {
"workspace_id": detail.workspace_id,
"name": detail.name,
"owner_user_id": detail.owner_user_id,
"subscription_plan": detail.subscription_plan,
"subscription_status": detail.subscription_status,
"max_projects": detail.max_projects,
"max_storage_gb": detail.max_storage_gb,
"used_storage_gb": detail.used_storage_gb,
"member_count": detail.member_count,
"user_role": detail.user_role,
}
# ==================== Member Management ====================
@router.post("/{workspace_id}/members/invite", status_code=status.HTTP_201_CREATED)
async def invite_member(
workspace_id: str,
request: InviteMemberRequestModel,
current_user: User = Depends(get_current_user),
):
"""邀请成员"""
container = get_container()
use_case = container.get_invite_member_use_case()
req = InviteMemberRequest(
workspace_id=workspace_id,
inviter_user_id=current_user.id,
invitee_email=request.email,
role=request.role,
)
response, error = use_case.execute(req)
if error:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=error)
return {
"invitation_id": response.invitation_id,
"invitee_email": response.invitee_email,
"role": response.role,
"expires_at": response.expires_at.isoformat(),
}
@router.get("/{workspace_id}/members")
async def list_members(
workspace_id: str,
current_user: User = Depends(get_current_user),
):
"""获取成员列表"""
container = get_container()
use_case = container.get_list_members_use_case()
req = ListMembersRequest(workspace_id=workspace_id, requester_user_id=current_user.id)
response, error = use_case.execute(req)
if error:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=error)
return {
"members": [
{
"member_id": m.member_id,
"user_id": m.user_id,
"username": m.username,
"email": m.email,
"display_name": m.display_name,
"role": m.role,
"invited_by": m.invited_by,
"joined_at": m.joined_at.isoformat(),
}
for m in response.members
]
}
@router.delete("/{workspace_id}/members/{user_id}", status_code=status.HTTP_204_NO_CONTENT)
async def remove_member(
workspace_id: str,
user_id: str,
current_user: User = Depends(get_current_user),
):
"""移除成员"""
container = get_container()
use_case = container.get_remove_member_use_case()
req = RemoveMemberRequest(
workspace_id=workspace_id,
requester_user_id=current_user.id,
target_user_id=user_id,
)
success, error = use_case.execute(req)
if not success:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=error)
@router.post("/{workspace_id}/leave", status_code=status.HTTP_204_NO_CONTENT)
async def leave_workspace(
workspace_id: str,
current_user: User = Depends(get_current_user),
):
"""离开工作空间"""
container = get_container()
use_case = container.get_leave_workspace_use_case()
req = LeaveWorkspaceRequest(workspace_id=workspace_id, user_id=current_user.id)
success, error = use_case.execute(req)
if not success:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=error)
@router.patch("/{workspace_id}/members/{user_id}/role")
async def update_member_role(
workspace_id: str,
user_id: str,
request: UpdateMemberRoleRequestModel,
current_user: User = Depends(get_current_user),
):
"""修改成员角色"""
container = get_container()
use_case = container.get_update_member_role_use_case()
req = UpdateMemberRoleRequest(
workspace_id=workspace_id,
requester_user_id=current_user.id,
target_user_id=user_id,
new_role=request.role,
)
response, error = use_case.execute(req)
if error:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=error)
return {
"user_id": response.user_id,
"old_role": response.old_role,
"new_role": response.new_role,
}
# ==================== Subscription Management ====================
@router.post("/{workspace_id}/subscription/upgrade")
async def upgrade_subscription(
workspace_id: str,
request: UpgradeSubscriptionRequestModel,
current_user: User = Depends(get_current_user),
):
"""升级订阅"""
container = get_container()
use_case = container.get_upgrade_subscription_use_case()
req = UpgradeSubscriptionRequest(
workspace_id=workspace_id,
requester_user_id=current_user.id,
new_plan=request.new_plan,
)
response, error = use_case.execute(req)
if error:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=error)
return {
"workspace_id": response.workspace_id,
"old_plan": response.old_plan,
"new_plan": response.new_plan,
"max_projects": response.max_projects,
"max_storage_gb": response.max_storage_gb,
}
@router.post("/{workspace_id}/subscription/cancel")
async def cancel_subscription(
workspace_id: str,
current_user: User = Depends(get_current_user),
):
"""取消订阅"""
container = get_container()
use_case = container.get_cancel_subscription_use_case()
req = CancelSubscriptionRequest(workspace_id=workspace_id, requester_user_id=current_user.id)
success, error = use_case.execute(req)
if not success:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=error)
return {"message": "Subscription cancelled successfully"}
@router.get("/{workspace_id}/quota")
async def get_quota_status(
workspace_id: str,
current_user: User = Depends(get_current_user),
):
"""获取配额状态"""
container = get_container()
quota_checker = container.quota_checker
# 检查权限
permission_checker = container.permission_checker
has_access, _ = permission_checker.check_workspace_access(workspace_id, current_user.id)
if not has_access:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Access denied")
status = quota_checker.get_quota_status(workspace_id)
if not status:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Workspace not found")
return status
# ==================== Invitation Acceptance ====================
@router.post("/invitations/{token}/accept")
async def accept_invitation(
token: str,
current_user: User = Depends(get_current_user),
):
"""接受邀请"""
container = get_container()
use_case = container.get_accept_invitation_use_case()
req = AcceptInvitationRequest(invitation_token=token, user_id=current_user.id)
response, error = use_case.execute(req)
if error:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=error)
return {
"workspace_id": response.workspace_id,
"workspace_name": response.workspace_name,
"role": response.role,
}
@router.post("/invitations/{token}/decline")
async def decline_invitation(token: str):
"""拒绝邀请"""
container = get_container()
use_case = container.get_decline_invitation_use_case()
req = DeclineInvitationRequest(invitation_token=token)
success, error = use_case.execute(req)
if not success:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=error)
return {"message": "Invitation declined"}