style: normalize python formatting gates

This commit is contained in:
Xiaoxia AI
2026-06-21 06:52:19 +08:00
parent 0809a079c5
commit bfbaddbd9a
129 changed files with 3024 additions and 2485 deletions
+1 -2
View File
@@ -1,5 +1,3 @@
from fastapi import APIRouter
from app.api.routes.asset_libraries import router as asset_libraries_router
from app.api.routes.assets import router as assets_router
from app.api.routes.auth_simple import router as auth_router
@@ -11,6 +9,7 @@ from app.api.routes.ingest_jobs import router as ingest_jobs_router
from app.api.routes.project_management import router as project_management_router
from app.api.routes.projects import router as projects_router
from app.api.routes.upload import router as upload_router
from fastapi import APIRouter
api_router = APIRouter(prefix="/api/v1")
health_router = APIRouter()
+13 -4
View File
@@ -1,9 +1,18 @@
from fastapi import APIRouter, Depends
from typing import Any
from app.dependencies import get_asset_library_repository
from app.schemas.asset_library import AssetLibraryResponse, CreateAssetLibraryRequest, ListAssetLibrariesResponse
from typing import Any
from packages.application import CreateAssetLibraryCommand, CreateAssetLibraryUseCase, ListAssetLibrariesUseCase
from app.schemas.asset_library import (
AssetLibraryResponse,
CreateAssetLibraryRequest,
ListAssetLibrariesResponse,
)
from fastapi import APIRouter, Depends
from packages.application import (
CreateAssetLibraryCommand,
CreateAssetLibraryUseCase,
ListAssetLibrariesUseCase,
)
from packages.domain import AssetLibraryKind
router = APIRouter()
+8 -3
View File
@@ -1,9 +1,14 @@
from fastapi import APIRouter, Depends
from typing import Any
from app.dependencies import get_asset_repository
from app.schemas.asset import AssetResponse, CreateAssetRequest, ListAssetsResponse
from typing import Any
from packages.application import CreateAssetCommand, CreateAssetUseCase, ListAssetsUseCase
from fastapi import APIRouter, Depends
from packages.application import (
CreateAssetCommand,
CreateAssetUseCase,
ListAssetsUseCase,
)
from packages.domain import AssetStatus, ClassificationStatus
router = APIRouter()
+54 -50
View File
@@ -5,36 +5,35 @@ This module is intentionally disabled until the DI container/auth ports are rebu
Do not mount it directly; use `auth_simple.py` only as the current compatibility route.
"""
raise RuntimeError(
"apps.api.app.api.routes.auth is disabled: rebuild DI container before mounting full auth routes"
)
raise RuntimeError("apps.api.app.api.routes.auth is disabled: rebuild DI container before mounting full auth routes")
from fastapi import APIRouter, HTTPException, status, Depends, Request
from fastapi import APIRouter, Depends, HTTPException, Request, status
from pydantic import BaseModel, EmailStr
from packages.application.auth import (
RegisterUserUseCase,
RegisterUserRequest,
LoginUseCase,
LoginRequest,
LogoutUseCase,
LogoutRequest,
VerifyEmailUseCase,
VerifyEmailRequest,
RequestPasswordResetUseCase,
RequestPasswordResetRequest,
ResetPasswordUseCase,
ResetPasswordRequest,
)
from packages.domain.entities import User
from apps.api.app.dependencies import get_container
from apps.api.app.middleware.auth import get_current_user
from packages.application.auth import (
LoginRequest,
LoginUseCase,
LogoutRequest,
LogoutUseCase,
RegisterUserRequest,
RegisterUserUseCase,
RequestPasswordResetRequest,
RequestPasswordResetUseCase,
ResetPasswordRequest,
ResetPasswordUseCase,
VerifyEmailRequest,
VerifyEmailUseCase,
)
from packages.domain.entities import User
router = APIRouter(prefix="/auth", tags=["Authentication"])
# ==================== Request/Response Models ====================
class RegisterRequestModel(BaseModel):
email: EmailStr
password: str
@@ -77,11 +76,16 @@ class ResetPasswordModel(BaseModel):
# ==================== API Endpoints ====================
@router.post("/register", response_model=RegisterResponseModel, status_code=status.HTTP_201_CREATED)
@router.post(
"/register",
response_model=RegisterResponseModel,
status_code=status.HTTP_201_CREATED,
)
async def register(request: RegisterRequestModel):
"""
用户注册
- 邮箱必须唯一
- 用户名必须唯一
- 密码至少 8 位,包含大小写字母和数字
@@ -89,22 +93,22 @@ async def register(request: RegisterRequestModel):
"""
container = get_container()
use_case = container.get_register_user_use_case()
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,
@@ -118,7 +122,7 @@ async def register(request: RegisterRequestModel):
async def login(request: LoginRequestModel):
"""
用户登录
- 使用邮箱和密码登录
- 返回 access_token 和 refresh_token
- access_token 有效期 30 分钟
@@ -126,20 +130,20 @@ async def login(request: LoginRequestModel):
"""
container = get_container()
use_case = container.get_login_use_case()
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,
@@ -159,16 +163,16 @@ async def logout(
):
"""
用户登出
- 默认只登出当前设备
- 设置 logout_all_devices=true 可登出所有设备
"""
container = get_container()
use_case = container.get_logout_use_case()
# 从 JWT token 中提取 session_id
from packages.domain.auth import jwt_service
# 从 request 中获取 token
auth_header = request.headers.get("Authorization")
session_id = None
@@ -179,15 +183,15 @@ async def logout(
session_id = payload.get("sid") # 从 payload 提取 session_id
except:
pass # token 无效或没有 session_id,继续使用 None
req = LogoutRequest(
user_id=current_user.id,
session_id=session_id,
logout_all_devices=logout_all_devices,
)
success, error = use_case.execute(req)
if not success:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
@@ -199,23 +203,23 @@ async def logout(
async def verify_email(token: str):
"""
邮箱验证
- 通过邮件中的链接访问此接口
- 验证成功后标记邮箱为已验证
"""
container = get_container()
use_case = container.get_verify_email_use_case()
req = VerifyEmailRequest(token=token)
success, error = use_case.execute(req)
if not success:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=error,
)
return {"message": "Email verified successfully"}
@@ -223,18 +227,18 @@ async def verify_email(token: str):
async def forgot_password(request: PasswordResetRequestModel):
"""
请求密码重置
- 发送密码重置邮件
- 邮件中包含重置链接(有效期 1 小时)
- 即使邮箱不存在也返回成功(安全考虑)
"""
container = get_container()
use_case = container.get_request_password_reset_use_case()
req = RequestPasswordResetRequest(email=request.email)
success, error = use_case.execute(req)
# 不论成功失败都返回 202(安全考虑)
return {"message": "Password reset email sent if account exists"}
@@ -243,24 +247,24 @@ async def forgot_password(request: PasswordResetRequestModel):
async def reset_password(request: ResetPasswordModel):
"""
重置密码
- 使用邮件中的 token 重置密码
- 新密码必须符合密码强度要求
"""
container = get_container()
use_case = container.get_reset_password_use_case()
req = ResetPasswordRequest(
token=request.token,
new_password=request.new_password,
)
success, error = use_case.execute(req)
if not success:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=error,
)
return {"message": "Password reset successfully"}
+8 -4
View File
@@ -1,17 +1,18 @@
"""
认证 APISQLAlchemy ORM
"""
from datetime import datetime, timedelta, timezone
import hashlib
import secrets
from datetime import datetime, timedelta, timezone
import jwt
from app.config import settings
from app.dependencies import get_db_session
from fastapi import APIRouter, Depends, HTTPException, status
from pydantic import BaseModel, EmailStr
from sqlalchemy.orm import Session
from app.config import settings
from app.dependencies import get_db_session
from packages.adapters.sqlalchemy_impl.models import UserModel
from packages.domain.auth import password_hasher, password_validator
@@ -169,4 +170,7 @@ async def login(request: LoginRequest, db: Session = Depends(get_db_session)):
@router.get("/me")
async def get_current_user_info():
raise HTTPException(status_code=status.HTTP_501_NOT_IMPLEMENTED, detail="/auth/me requires bearer-token dependency integration")
raise HTTPException(
status_code=status.HTTP_501_NOT_IMPLEMENTED,
detail="/auth/me requires bearer-token dependency integration",
)
+11 -5
View File
@@ -1,12 +1,18 @@
from datetime import datetime, timezone
from fastapi import APIRouter, Depends, HTTPException
from typing import Any
from app.core.celery_app import celery_app
from app.dependencies import get_classification_job_repository
from app.schemas.classification_job import ClassificationJobResponse, SubmitClassificationJobRequest
from typing import Any
from packages.application import SubmitClassificationJobCommand, SubmitClassificationJobUseCase
from app.schemas.classification_job import (
ClassificationJobResponse,
SubmitClassificationJobRequest,
)
from fastapi import APIRouter, Depends, HTTPException
from packages.application import (
SubmitClassificationJobCommand,
SubmitClassificationJobUseCase,
)
router = APIRouter()
+3 -2
View File
@@ -1,4 +1,4 @@
from fastapi import APIRouter, Depends, HTTPException
from typing import Any
from app.core.storage import MinIOService, get_minio_service
from app.dependencies import get_generated_video_repository
@@ -7,7 +7,8 @@ from app.schemas.generated_video import (
GeneratedVideoResponse,
ListGeneratedVideosResponse,
)
from typing import Any
from fastapi import APIRouter, Depends, HTTPException
from packages.application import (
GetGeneratedVideoDownloadUrlUseCase,
GetGeneratedVideoUseCase,
+15 -5
View File
@@ -1,10 +1,20 @@
from fastapi import APIRouter, Depends, HTTPException
from typing import Any
from app.core.celery_app import celery_app
from app.dependencies import get_generation_task_repository, get_generated_video_repository
from app.schemas.generation_task import CreateGenerationTaskRequest, GenerationTaskResponse
from app.schemas.generated_video import GeneratedVideoResponse, ListGeneratedVideosResponse
from typing import Any
from app.dependencies import (
get_generated_video_repository,
get_generation_task_repository,
)
from app.schemas.generated_video import (
GeneratedVideoResponse,
ListGeneratedVideosResponse,
)
from app.schemas.generation_task import (
CreateGenerationTaskRequest,
GenerationTaskResponse,
)
from fastapi import APIRouter, Depends, HTTPException
from packages.application import (
CreateGenerationTaskCommand,
CreateGenerationTaskUseCase,
+33 -13
View File
@@ -1,12 +1,11 @@
from pydantic import BaseModel
from datetime import datetime
import psycopg2
import redis
from app.config import settings
from fastapi import APIRouter, status
from fastapi.responses import JSONResponse
from app.config import settings
from pydantic import BaseModel
router = APIRouter(tags=["Health"])
@@ -56,16 +55,28 @@ async def startup_check():
async def _check_database() -> dict:
if settings.USE_IN_MEMORY_DB:
return {"status": "healthy", "type": "in_memory", "message": "Using in-memory database"}
return {
"status": "healthy",
"type": "in_memory",
"message": "Using in-memory database",
}
try:
conn = psycopg2.connect(settings.DATABASE_URL, connect_timeout=3)
with conn.cursor() as cur:
cur.execute("SELECT 1")
cur.fetchone()
conn.close()
return {"status": "healthy", "type": "postgresql", "message": "Database connection successful"}
return {
"status": "healthy",
"type": "postgresql",
"message": "Database connection successful",
}
except Exception as error:
return {"status": "unhealthy", "type": "postgresql", "message": f"Database connection failed: {error}"}
return {
"status": "unhealthy",
"type": "postgresql",
"message": f"Database connection failed: {error}",
}
async def _check_redis() -> dict:
@@ -73,23 +84,32 @@ async def _check_redis() -> dict:
client = redis.from_url(settings.REDIS_URL, socket_connect_timeout=3)
client.ping()
client.close()
return {"status": "healthy", "type": "redis", "message": "Redis connection successful"}
return {
"status": "healthy",
"type": "redis",
"message": "Redis connection successful",
}
except Exception as error:
return {"status": "unhealthy", "type": "redis", "message": f"Redis connection failed: {error}"}
return {
"status": "unhealthy",
"type": "redis",
"message": f"Redis connection failed: {error}",
}
async def _check_migrations() -> dict:
if settings.USE_IN_MEMORY_DB:
return {"status": "healthy", "message": "Using in-memory database, no migrations needed"}
return {
"status": "healthy",
"message": "Using in-memory database, no migrations needed",
}
try:
conn = psycopg2.connect(settings.DATABASE_URL, connect_timeout=3)
with conn.cursor() as cur:
cur.execute(
"""
cur.execute("""
SELECT COUNT(*) FROM information_schema.tables
WHERE table_name IN ('projects', 'asset_libraries', 'assets', 'ingest_jobs', 'classification_jobs')
"""
)
""")
count = cur.fetchone()[0]
conn.close()
if count >= 5:
+3 -2
View File
@@ -1,9 +1,10 @@
from fastapi import APIRouter, Depends
from typing import Any
from app.core.celery_app import celery_app
from app.dependencies import get_ingest_job_repository
from app.schemas.ingest_job import IngestJobResponse, SubmitIngestJobRequest
from typing import Any
from fastapi import APIRouter, Depends
from packages.application import SubmitIngestJobCommand, SubmitIngestJobUseCase
router = APIRouter()
@@ -1,4 +1,5 @@
"""项目管理 API 路由"""
from datetime import datetime
from typing import Annotated
@@ -11,7 +12,6 @@ from packages.adapters.sqlite_tracker.project_management_repositories import (
SQLiteTaskRepository,
)
from packages.application.get_task_detail_use_case import GetTaskDetailUseCase
from packages.application.update_task_use_case import UpdateTaskUseCase
from packages.application.project_management_use_cases import (
CreateMilestoneUseCase,
CreateTaskIssueUseCase,
@@ -23,6 +23,7 @@ from packages.application.project_management_use_cases import (
UpdateTaskProgressUseCase,
UpdateTaskStatusUseCase,
)
from packages.application.update_task_use_case import UpdateTaskUseCase
from packages.domain import TaskPriority, TaskStatus
router = APIRouter()
+13 -4
View File
@@ -1,9 +1,18 @@
from fastapi import APIRouter, Depends
from typing import Any
from app.dependencies import get_project_repository
from app.schemas.project import CreateProjectRequest, ListProjectsResponse, ProjectResponse
from typing import Any
from packages.application import CreateProjectCommand, CreateProjectUseCase, ListProjectsUseCase
from app.schemas.project import (
CreateProjectRequest,
ListProjectsResponse,
ProjectResponse,
)
from fastapi import APIRouter, Depends
from packages.application import (
CreateProjectCommand,
CreateProjectUseCase,
ListProjectsUseCase,
)
router = APIRouter()
+3 -2
View File
@@ -1,11 +1,12 @@
from fastapi import APIRouter, Depends, File, Form, UploadFile
from typing import Any
from uuid import uuid4
from app.core.celery_app import celery_app
from app.core.storage import MinIOService, get_minio_service
from app.dependencies import get_ingest_job_repository
from app.schemas.upload import UploadAssetResponse
from typing import Any
from fastapi import APIRouter, Depends, File, Form, UploadFile
from packages.application import SubmitIngestJobCommand, SubmitIngestJobUseCase
router = APIRouter()
+60 -49
View File
@@ -9,21 +9,28 @@ raise RuntimeError(
"apps.api.app.api.routes.workspaces is disabled: rebuild DI container before mounting workspace routes"
)
from fastapi import APIRouter, HTTPException, status, Depends
from pydantic import BaseModel, EmailStr
from typing import List
from datetime import datetime
from typing import List
from fastapi import APIRouter, Depends, HTTPException, status
from pydantic import BaseModel, EmailStr
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,
)
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"
@@ -52,6 +59,7 @@ class UpgradeSubscriptionRequestModel(BaseModel):
# ==================== Workspace CRUD ====================
@router.post("", response_model=WorkspaceResponseModel, status_code=status.HTTP_201_CREATED)
async def create_workspace(
request: CreateWorkspaceRequestModel,
@@ -60,18 +68,18 @@ async def create_workspace(
"""创建工作空间"""
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,
@@ -86,13 +94,13 @@ 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": [
{
@@ -117,13 +125,13 @@ async def get_workspace_detail(
"""获取工作空间详情"""
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,
@@ -140,6 +148,7 @@ async def get_workspace_detail(
# ==================== Member Management ====================
@router.post("/{workspace_id}/members/invite", status_code=status.HTTP_201_CREATED)
async def invite_member(
workspace_id: str,
@@ -149,19 +158,19 @@ async def invite_member(
"""邀请成员"""
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,
@@ -178,13 +187,13 @@ async def list_members(
"""获取成员列表"""
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": [
{
@@ -211,15 +220,15 @@ async def remove_member(
"""移除成员"""
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)
@@ -232,10 +241,10 @@ async def leave_workspace(
"""离开工作空间"""
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)
@@ -250,19 +259,19 @@ async def update_member_role(
"""修改成员角色"""
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,
@@ -272,6 +281,7 @@ async def update_member_role(
# ==================== Subscription Management ====================
@router.post("/{workspace_id}/subscription/upgrade")
async def upgrade_subscription(
workspace_id: str,
@@ -281,18 +291,18 @@ async def upgrade_subscription(
"""升级订阅"""
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,
@@ -310,13 +320,13 @@ async def cancel_subscription(
"""取消订阅"""
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"}
@@ -328,24 +338,25 @@ async def get_quota_status(
"""获取配额状态"""
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,
@@ -354,13 +365,13 @@ async def accept_invitation(
"""接受邀请"""
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,
@@ -373,11 +384,11 @@ 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"}