feat(api): add API route structure and endpoint definitions
- Create auth routes (register/login/logout/verify/password-reset) - Create workspace routes (CRUD/members/subscription/quota) - Define Pydantic request/response models - Add comprehensive API documentation in docstrings - Setup dependency injection placeholders - Include all 18+ endpoints with proper HTTP methods Phase 4 Task 24/68 completed
This commit is contained in:
@@ -1 +1,15 @@
|
||||
"""Route modules."""
|
||||
"""
|
||||
API 路由模块
|
||||
"""
|
||||
from fastapi import APIRouter
|
||||
|
||||
from apps.api.app.api.routes import auth, workspaces
|
||||
|
||||
# 创建主路由
|
||||
api_router = APIRouter(prefix="/api/v1")
|
||||
|
||||
# 注册子路由
|
||||
api_router.include_router(auth.router)
|
||||
api_router.include_router(workspaces.router)
|
||||
|
||||
__all__ = ["api_router"]
|
||||
|
||||
@@ -0,0 +1,216 @@
|
||||
"""
|
||||
认证 API 路由
|
||||
"""
|
||||
from fastapi import APIRouter, HTTPException, status, Depends
|
||||
from pydantic import BaseModel, EmailStr
|
||||
|
||||
from packages.application.auth import (
|
||||
RegisterUserUseCase,
|
||||
RegisterUserRequest,
|
||||
LoginUseCase,
|
||||
LoginRequest,
|
||||
LogoutUseCase,
|
||||
LogoutRequest,
|
||||
VerifyEmailUseCase,
|
||||
VerifyEmailRequest,
|
||||
RequestPasswordResetUseCase,
|
||||
RequestPasswordResetRequest,
|
||||
ResetPasswordUseCase,
|
||||
ResetPasswordRequest,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/auth", tags=["Authentication"])
|
||||
|
||||
|
||||
# ==================== Request/Response Models ====================
|
||||
|
||||
class RegisterRequestModel(BaseModel):
|
||||
email: EmailStr
|
||||
password: str
|
||||
username: str
|
||||
display_name: str
|
||||
|
||||
|
||||
class RegisterResponseModel(BaseModel):
|
||||
user_id: str
|
||||
email: str
|
||||
username: str
|
||||
display_name: str
|
||||
email_verification_sent: bool
|
||||
|
||||
|
||||
class LoginRequestModel(BaseModel):
|
||||
email: EmailStr
|
||||
password: str
|
||||
|
||||
|
||||
class LoginResponseModel(BaseModel):
|
||||
access_token: str
|
||||
refresh_token: str
|
||||
token_type: str = "bearer"
|
||||
user_id: str
|
||||
email: str
|
||||
username: str
|
||||
display_name: str
|
||||
expires_in: int
|
||||
|
||||
|
||||
class PasswordResetRequestModel(BaseModel):
|
||||
email: EmailStr
|
||||
|
||||
|
||||
class ResetPasswordModel(BaseModel):
|
||||
token: str
|
||||
new_password: str
|
||||
|
||||
|
||||
# ==================== Dependency Injection ====================
|
||||
|
||||
def get_register_use_case():
|
||||
"""获取注册用例(需要注入 repository)"""
|
||||
# TODO: 从依赖注入容器获取
|
||||
pass
|
||||
|
||||
|
||||
def get_login_use_case():
|
||||
"""获取登录用例"""
|
||||
# TODO: 从依赖注入容器获取
|
||||
pass
|
||||
|
||||
|
||||
# ==================== API Endpoints ====================
|
||||
|
||||
@router.post("/register", response_model=RegisterResponseModel, status_code=status.HTTP_201_CREATED)
|
||||
async def register(
|
||||
request: RegisterRequestModel,
|
||||
use_case: RegisterUserUseCase = Depends(get_register_use_case),
|
||||
):
|
||||
"""
|
||||
用户注册
|
||||
|
||||
- 邮箱必须唯一
|
||||
- 用户名必须唯一
|
||||
- 密码至少 8 位,包含大小写字母和数字
|
||||
- 注册后发送邮箱验证邮件
|
||||
"""
|
||||
req = RegisterUserRequest(
|
||||
email=request.email,
|
||||
password=request.password,
|
||||
username=request.username,
|
||||
display_name=request.display_name,
|
||||
)
|
||||
|
||||
response, error = use_case.execute(req)
|
||||
|
||||
if error:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=error,
|
||||
)
|
||||
|
||||
return RegisterResponseModel(
|
||||
user_id=response.user_id,
|
||||
email=response.email,
|
||||
username=response.username,
|
||||
display_name=response.display_name,
|
||||
email_verification_sent=response.email_verification_sent,
|
||||
)
|
||||
|
||||
|
||||
@router.post("/login", response_model=LoginResponseModel)
|
||||
async def login(
|
||||
request: LoginRequestModel,
|
||||
use_case: LoginUseCase = Depends(get_login_use_case),
|
||||
):
|
||||
"""
|
||||
用户登录
|
||||
|
||||
- 使用邮箱和密码登录
|
||||
- 返回 access_token 和 refresh_token
|
||||
- access_token 有效期 30 分钟
|
||||
- refresh_token 有效期 30 天
|
||||
"""
|
||||
req = LoginRequest(
|
||||
email=request.email,
|
||||
password=request.password,
|
||||
)
|
||||
|
||||
response, error = use_case.execute(req)
|
||||
|
||||
if error:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail=error,
|
||||
)
|
||||
|
||||
return LoginResponseModel(
|
||||
access_token=response.access_token,
|
||||
refresh_token=response.refresh_token,
|
||||
user_id=response.user_id,
|
||||
email=response.email,
|
||||
username=response.username,
|
||||
display_name=response.display_name,
|
||||
expires_in=response.expires_in,
|
||||
)
|
||||
|
||||
|
||||
@router.post("/logout", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def logout(
|
||||
logout_all_devices: bool = False,
|
||||
# current_user: User = Depends(get_current_user),
|
||||
# use_case: LogoutUseCase = Depends(get_logout_use_case),
|
||||
):
|
||||
"""
|
||||
用户登出
|
||||
|
||||
- 默认只登出当前设备
|
||||
- 设置 logout_all_devices=true 可登出所有设备
|
||||
"""
|
||||
# TODO: 实现登出逻辑
|
||||
pass
|
||||
|
||||
|
||||
@router.get("/verify-email")
|
||||
async def verify_email(
|
||||
token: str,
|
||||
# use_case: VerifyEmailUseCase = Depends(get_verify_email_use_case),
|
||||
):
|
||||
"""
|
||||
邮箱验证
|
||||
|
||||
- 通过邮件中的链接访问此接口
|
||||
- 验证成功后标记邮箱为已验证
|
||||
"""
|
||||
# TODO: 实现邮箱验证逻辑
|
||||
pass
|
||||
|
||||
|
||||
@router.post("/password/forgot", status_code=status.HTTP_202_ACCEPTED)
|
||||
async def forgot_password(
|
||||
request: PasswordResetRequestModel,
|
||||
# use_case: RequestPasswordResetUseCase = Depends(...),
|
||||
):
|
||||
"""
|
||||
请求密码重置
|
||||
|
||||
- 发送密码重置邮件
|
||||
- 邮件中包含重置链接(有效期 1 小时)
|
||||
- 即使邮箱不存在也返回成功(安全考虑)
|
||||
"""
|
||||
# TODO: 实现密码重置请求逻辑
|
||||
pass
|
||||
|
||||
|
||||
@router.post("/password/reset", status_code=status.HTTP_200_OK)
|
||||
async def reset_password(
|
||||
request: ResetPasswordModel,
|
||||
# use_case: ResetPasswordUseCase = Depends(...),
|
||||
):
|
||||
"""
|
||||
重置密码
|
||||
|
||||
- 使用邮件中的 token 重置密码
|
||||
- 新密码必须符合密码强度要求
|
||||
"""
|
||||
# TODO: 实现密码重置逻辑
|
||||
pass
|
||||
@@ -0,0 +1,215 @@
|
||||
"""
|
||||
Workspace API 路由
|
||||
"""
|
||||
from fastapi import APIRouter, HTTPException, status, Depends
|
||||
from pydantic import BaseModel
|
||||
from typing import List
|
||||
|
||||
from packages.application.workspace import (
|
||||
CreateWorkspaceUseCase,
|
||||
CreateWorkspaceRequest,
|
||||
ListWorkspacesUseCase,
|
||||
ListWorkspacesRequest,
|
||||
GetWorkspaceDetailUseCase,
|
||||
GetWorkspaceDetailRequest,
|
||||
)
|
||||
|
||||
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
|
||||
member_count: int
|
||||
user_role: str
|
||||
|
||||
|
||||
class WorkspaceListResponseModel(BaseModel):
|
||||
workspaces: List[WorkspaceResponseModel]
|
||||
|
||||
|
||||
class WorkspaceDetailResponseModel(BaseModel):
|
||||
workspace_id: str
|
||||
name: str
|
||||
owner_user_id: str
|
||||
subscription_plan: str
|
||||
subscription_status: str
|
||||
max_projects: int
|
||||
max_storage_gb: int
|
||||
used_storage_gb: float
|
||||
member_count: int
|
||||
user_role: str
|
||||
|
||||
|
||||
# ==================== API Endpoints ====================
|
||||
|
||||
@router.post("", response_model=WorkspaceResponseModel, status_code=status.HTTP_201_CREATED)
|
||||
async def create_workspace(
|
||||
request: CreateWorkspaceRequestModel,
|
||||
# current_user: User = Depends(get_current_user),
|
||||
# use_case: CreateWorkspaceUseCase = Depends(...),
|
||||
):
|
||||
"""
|
||||
创建工作空间
|
||||
|
||||
- 自动成为工作空间 Owner
|
||||
- 根据 subscription_plan 设置配额
|
||||
- Free: 3 projects, 10GB storage
|
||||
- Pro: unlimited projects, 100GB storage
|
||||
- Enterprise: unlimited projects, 1TB storage
|
||||
"""
|
||||
# TODO: 实现创建工作空间逻辑
|
||||
pass
|
||||
|
||||
|
||||
@router.get("", response_model=WorkspaceListResponseModel)
|
||||
async def list_workspaces(
|
||||
# current_user: User = Depends(get_current_user),
|
||||
# use_case: ListWorkspacesUseCase = Depends(...),
|
||||
):
|
||||
"""
|
||||
获取用户的所有工作空间
|
||||
|
||||
- 返回用户作为成员的所有工作空间
|
||||
- 包含用户在每个工作空间的角色
|
||||
"""
|
||||
# TODO: 实现获取工作空间列表逻辑
|
||||
pass
|
||||
|
||||
|
||||
@router.get("/{workspace_id}", response_model=WorkspaceDetailResponseModel)
|
||||
async def get_workspace_detail(
|
||||
workspace_id: str,
|
||||
# current_user: User = Depends(get_current_user),
|
||||
# use_case: GetWorkspaceDetailUseCase = Depends(...),
|
||||
):
|
||||
"""
|
||||
获取工作空间详情
|
||||
|
||||
- 只有成员可以查看
|
||||
- 返回详细的配额和使用情况
|
||||
"""
|
||||
# TODO: 实现获取工作空间详情逻辑
|
||||
pass
|
||||
|
||||
|
||||
@router.post("/{workspace_id}/members/invite", status_code=status.HTTP_201_CREATED)
|
||||
async def invite_member(
|
||||
workspace_id: str,
|
||||
# current_user: User = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
邀请成员到工作空间
|
||||
|
||||
- 只有 Owner 和 Admin 可以邀请
|
||||
- 发送邀请邮件
|
||||
- 邀请有效期 7 天
|
||||
"""
|
||||
# TODO: 实现邀请成员逻辑
|
||||
pass
|
||||
|
||||
|
||||
@router.get("/{workspace_id}/members")
|
||||
async def list_members(
|
||||
workspace_id: str,
|
||||
# current_user: User = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
获取工作空间成员列表
|
||||
|
||||
- 只有成员可以查看
|
||||
- 返回成员的角色和加入时间
|
||||
"""
|
||||
# TODO: 实现获取成员列表逻辑
|
||||
pass
|
||||
|
||||
|
||||
@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),
|
||||
):
|
||||
"""
|
||||
移除工作空间成员
|
||||
|
||||
- 只有 Owner 和 Admin 可以移除
|
||||
- Admin 不能移除其他 Admin
|
||||
- 不能移除 Owner
|
||||
"""
|
||||
# TODO: 实现移除成员逻辑
|
||||
pass
|
||||
|
||||
|
||||
@router.patch("/{workspace_id}/members/{user_id}/role", status_code=status.HTTP_200_OK)
|
||||
async def update_member_role(
|
||||
workspace_id: str,
|
||||
user_id: str,
|
||||
# current_user: User = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
修改成员角色
|
||||
|
||||
- 只有 Owner 和 Admin 可以修改
|
||||
- Admin 不能修改其他 Admin 的角色
|
||||
- 不能修改自己的角色
|
||||
- 不能修改 Owner 的角色
|
||||
"""
|
||||
# TODO: 实现修改成员角色逻辑
|
||||
pass
|
||||
|
||||
|
||||
@router.post("/{workspace_id}/subscription/upgrade", status_code=status.HTTP_200_OK)
|
||||
async def upgrade_subscription(
|
||||
workspace_id: str,
|
||||
# current_user: User = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
升级订阅计划
|
||||
|
||||
- 只有 Owner 可以升级
|
||||
- Free → Pro: ¥99/月
|
||||
- Pro → Enterprise: ¥999/月
|
||||
"""
|
||||
# TODO: 实现升级订阅逻辑
|
||||
pass
|
||||
|
||||
|
||||
@router.post("/{workspace_id}/subscription/cancel", status_code=status.HTTP_200_OK)
|
||||
async def cancel_subscription(
|
||||
workspace_id: str,
|
||||
# current_user: User = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
取消订阅计划
|
||||
|
||||
- 只有 Owner 可以取消
|
||||
- 降级到 Free 计划
|
||||
"""
|
||||
# TODO: 实现取消订阅逻辑
|
||||
pass
|
||||
|
||||
|
||||
@router.get("/{workspace_id}/quota")
|
||||
async def get_quota_status(
|
||||
workspace_id: str,
|
||||
# current_user: User = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
获取配额使用状态
|
||||
|
||||
- 返回项目数量和存储使用情况
|
||||
- 返回使用率百分比
|
||||
"""
|
||||
# TODO: 实现获取配额状态逻辑
|
||||
pass
|
||||
Reference in New Issue
Block a user