Merge branch 'develop'
CI/CD Pipeline / Frontend Lint (push) Failing after 118h26m35s
CI/CD Pipeline / Validate Code Quality And Tests (push) Failing after 118h26m35s
Deploy / Deploy Staging (push) Failing after 1777h0m35s
Deploy / Build Production Runtime Images (push) Failing after 118h24m29s
Deploy / Deploy Production (push) Failing after 118h8m17s
Deploy / Production Browser E2E (push) Failing after 118h7m33s
Deploy / Staging E2E Tests (push) Failing after 1776h53m6s
CI/CD Pipeline / Frontend Lint (push) Failing after 118h26m35s
CI/CD Pipeline / Validate Code Quality And Tests (push) Failing after 118h26m35s
Deploy / Deploy Staging (push) Failing after 1777h0m35s
Deploy / Build Production Runtime Images (push) Failing after 118h24m29s
Deploy / Deploy Production (push) Failing after 118h8m17s
Deploy / Production Browser E2E (push) Failing after 118h7m33s
Deploy / Staging E2E Tests (push) Failing after 1776h53m6s
This commit is contained in:
@@ -11,16 +11,16 @@ from app.api.routes.edit_templates import router as edit_templates_router
|
||||
from app.api.routes.generated_videos import router as generated_videos_router
|
||||
from app.api.routes.generation_tasks import router as generation_tasks_router
|
||||
from app.api.routes.health import router as health_check_router
|
||||
from app.api.routes.jobs import router as jobs_router
|
||||
from app.api.routes.ingest_jobs import router as ingest_jobs_router
|
||||
from app.api.routes.jobs import router as jobs_router
|
||||
from app.api.routes.projects import router as projects_router
|
||||
from app.api.routes.recipes import router as recipes_router
|
||||
from app.api.routes.subscription import router as subscription_router
|
||||
from app.api.routes.task_center import router as task_center_router
|
||||
from app.api.routes.templates import router as templates_router
|
||||
from app.api.routes.titles import router as titles_router
|
||||
from app.api.routes.upload import router as upload_router
|
||||
from app.api.routes.tts import router as tts_router
|
||||
from app.api.routes.upload import router as upload_router
|
||||
from app.api.routes.voice_clones import router as voice_clones_router
|
||||
from app.api.routes.voices import router as voices_router
|
||||
from fastapi import APIRouter
|
||||
|
||||
@@ -5,10 +5,10 @@ The route layer is intentionally thin: repository construction lives in
|
||||
app.dependencies and authentication behavior lives in application use cases.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
import jwt
|
||||
|
||||
from app.auth import AuthenticatedUser, blacklist_token, get_current_user
|
||||
from app.config import settings
|
||||
from app.dependencies import get_auth_email_service, get_auth_session_store, get_user_repository
|
||||
@@ -31,7 +31,6 @@ from packages.application.auth.password_reset_use_case import (
|
||||
from packages.application.auth.register_user_use_case import RegisterUserRequest as RegisterUseCaseRequest
|
||||
from packages.application.auth.register_user_use_case import RegisterUserUseCase, VerifyEmailRequest, VerifyEmailUseCase
|
||||
from packages.ports.user_repository import UserRepository
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -237,8 +236,6 @@ async def reset_password(
|
||||
return MessageResponse(message="密码重置成功")
|
||||
|
||||
|
||||
|
||||
|
||||
@router.post("/logout")
|
||||
async def logout(
|
||||
credentials: HTTPAuthorizationCredentials | None = Depends(bearer_scheme),
|
||||
@@ -246,6 +243,7 @@ async def logout(
|
||||
):
|
||||
"""登出 - 将当前 token 加入黑名单"""
|
||||
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
||||
|
||||
if credentials:
|
||||
try:
|
||||
payload = jwt.decode(credentials.credentials, settings.JWT_SECRET_KEY, algorithms=["HS256"])
|
||||
@@ -255,6 +253,7 @@ async def logout(
|
||||
logger.warning(f"Operation failed in apps/api/app/api/routes/auth.py: {e}", exc_info=True)
|
||||
return MessageResponse(message="已登出")
|
||||
|
||||
|
||||
@router.get("/me", response_model=CurrentUserResponse)
|
||||
async def get_current_user_info(
|
||||
authenticated_user: AuthenticatedUser = Depends(get_current_user),
|
||||
|
||||
@@ -239,7 +239,7 @@ def get_duplication_detail(
|
||||
return _to_detail_response(record)
|
||||
|
||||
|
||||
@router.delete("/records/{record_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.delete("/records/{record_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None)
|
||||
def delete_duplication_record(
|
||||
record_id: str,
|
||||
authenticated_user: AuthenticatedUser = Depends(get_current_user),
|
||||
|
||||
@@ -162,10 +162,7 @@ def list_plans(
|
||||
except ValueError:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=(
|
||||
f"无效的状态值: {status_filter},"
|
||||
f"可选值: draft, editing, rendering, completed, failed"
|
||||
),
|
||||
detail=(f"无效的状态值: {status_filter}," f"可选值: draft, editing, rendering, completed, failed"),
|
||||
)
|
||||
|
||||
skip = (page - 1) * page_size
|
||||
@@ -262,16 +259,19 @@ def update_plan(
|
||||
except ValueError:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=(
|
||||
f"无效的状态值: {body.status},"
|
||||
f"可选值: draft, editing, rendering, completed, failed"
|
||||
),
|
||||
detail=(f"无效的状态值: {body.status}," f"可选值: draft, editing, rendering, completed, failed"),
|
||||
)
|
||||
svc.transition_status(plan_id, target_status)
|
||||
except ValueError as exc:
|
||||
err_msg = str(exc)
|
||||
if "不存在" in err_msg:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=err_msg,
|
||||
)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=str(exc),
|
||||
detail=err_msg,
|
||||
)
|
||||
|
||||
# 返回最新状态
|
||||
@@ -280,7 +280,7 @@ def update_plan(
|
||||
return _to_response(result)
|
||||
|
||||
|
||||
@router.delete("/{plan_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.delete("/{plan_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None)
|
||||
def delete_plan(
|
||||
plan_id: str,
|
||||
db: Session = Depends(get_db_session),
|
||||
@@ -324,7 +324,13 @@ def generate_plan(
|
||||
svc = EditPlanService(db)
|
||||
|
||||
# 检查是否可生成
|
||||
can_gen, reason = svc.can_generate(plan_id)
|
||||
try:
|
||||
can_gen, reason = svc.can_generate(plan_id)
|
||||
except ValueError as exc:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=str(exc),
|
||||
)
|
||||
if not can_gen:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
@@ -388,7 +394,13 @@ def get_generation_status(
|
||||
返回计划状态、关联的 GenerationTask ID、以及每个片段的状态。
|
||||
"""
|
||||
svc = EditPlanService(db)
|
||||
gen_status = svc.get_generation_status(plan_id)
|
||||
try:
|
||||
gen_status = svc.get_generation_status(plan_id)
|
||||
except ValueError as exc:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=str(exc),
|
||||
)
|
||||
|
||||
plan = gen_status["plan"]
|
||||
clips = gen_status["clips"]
|
||||
@@ -421,7 +433,7 @@ class TimelineSceneResponse(BaseModel):
|
||||
"""时间线场景"""
|
||||
|
||||
scene: str = Field(..., description="场景描述")
|
||||
time: str = Field(..., description="时间范围,如 \"0:00 - 0:05\"")
|
||||
time: str = Field(..., description='时间范围,如 "0:00 - 0:05"')
|
||||
duration: float = Field(..., ge=0, description="时长(秒)")
|
||||
color: str = Field(..., description="展示颜色")
|
||||
clip_id: str = Field(default="", description="关联的片段 ID")
|
||||
|
||||
@@ -228,15 +228,21 @@ def update_template(
|
||||
status=status_enum,
|
||||
)
|
||||
except ValueError as exc:
|
||||
err_msg = str(exc)
|
||||
if "不存在" in err_msg:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=err_msg,
|
||||
)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=str(exc),
|
||||
detail=err_msg,
|
||||
)
|
||||
logger.info("更新模板: id=%s by user=%s", template_id, current_user.user.id)
|
||||
return _to_response(result)
|
||||
|
||||
|
||||
@router.delete("/{template_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.delete("/{template_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None)
|
||||
def delete_template(
|
||||
template_id: str,
|
||||
db: Session = Depends(get_db_session),
|
||||
|
||||
@@ -97,8 +97,7 @@ def create_job(
|
||||
except ValueError:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"不支持的任务类型: {request.job_type},"
|
||||
f"可选值: {[t.value for t in JobType]}",
|
||||
detail=f"不支持的任务类型: {request.job_type}," f"可选值: {[t.value for t in JobType]}",
|
||||
)
|
||||
|
||||
use_case = CreateJobUseCase(job_repo)
|
||||
|
||||
Regular → Executable
+22
@@ -12,6 +12,7 @@ from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from packages.application import (
|
||||
CreateProjectCommand,
|
||||
CreateProjectUseCase,
|
||||
DeleteProjectUseCase,
|
||||
GetProjectUseCase,
|
||||
ListProjectsUseCase,
|
||||
)
|
||||
@@ -39,6 +40,8 @@ def get_project(
|
||||
project = use_case.execute(project_id)
|
||||
if project is None:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Project not found")
|
||||
if not project.can_access(authenticated_user.user.id):
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Access denied to project")
|
||||
return _to_project_response(project)
|
||||
|
||||
|
||||
@@ -67,3 +70,22 @@ def create_project(
|
||||
owner_user_id=authenticated_user.user.id,
|
||||
)
|
||||
return _to_project_response(project)
|
||||
|
||||
|
||||
@router.delete("/{project_id}")
|
||||
def delete_project(
|
||||
project_id: str,
|
||||
authenticated_user: AuthenticatedUser = Depends(get_current_user),
|
||||
project_repository: Any = Depends(get_project_repository),
|
||||
):
|
||||
use_case = DeleteProjectUseCase(project_repository)
|
||||
try:
|
||||
deleted = use_case.execute(project_id, authenticated_user.user.id)
|
||||
except PermissionError:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Only the project owner can delete this project",
|
||||
)
|
||||
if not deleted:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Project not found")
|
||||
return {"message": "Project deleted successfully"}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
from app.auth import AuthenticatedUser, get_current_user
|
||||
from app.dependencies import get_db_session, get_user_repository
|
||||
from app.schemas.recipe import (
|
||||
@@ -173,7 +172,7 @@ def update_recipe(
|
||||
return _to_response(recipe)
|
||||
|
||||
|
||||
@router.delete("/{recipe_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.delete("/{recipe_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None)
|
||||
def delete_recipe(
|
||||
recipe_id: str,
|
||||
authenticated_user: AuthenticatedUser = Depends(get_current_user),
|
||||
|
||||
@@ -2,10 +2,9 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List
|
||||
|
||||
from dataclasses import replace
|
||||
from datetime import datetime, timezone
|
||||
from typing import List
|
||||
|
||||
from app.auth import AuthenticatedUser, get_current_user
|
||||
from app.dependencies import get_user_repository
|
||||
@@ -205,8 +204,6 @@ async def cancel_subscription(
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
@router.post("/payment-callback")
|
||||
async def payment_callback(
|
||||
user_id: str,
|
||||
@@ -220,10 +217,11 @@ async def payment_callback(
|
||||
|
||||
注意:生产环境需要验证支付签名
|
||||
"""
|
||||
import uuid
|
||||
from datetime import timedelta
|
||||
|
||||
from packages.adapters.sqlalchemy_impl.billing_repository import SQLAlchemyBillingRepository
|
||||
from packages.adapters.sqlalchemy_impl.session import SessionLocal
|
||||
import uuid
|
||||
|
||||
if SessionLocal is None:
|
||||
raise HTTPException(status_code=500, detail="Database not available")
|
||||
@@ -234,14 +232,16 @@ async def payment_callback(
|
||||
|
||||
# 创建账单记录
|
||||
record_id = uuid.uuid4().hex
|
||||
record = repo.create({
|
||||
"id": record_id,
|
||||
"user_id": user_id,
|
||||
"plan_name": _get_plan_name(plan),
|
||||
"amount": amount,
|
||||
"billing_cycle": billing_cycle,
|
||||
"status": "pending",
|
||||
})
|
||||
record = repo.create(
|
||||
{
|
||||
"id": record_id,
|
||||
"user_id": user_id,
|
||||
"plan_name": _get_plan_name(plan),
|
||||
"amount": amount,
|
||||
"billing_cycle": billing_cycle,
|
||||
"status": "pending",
|
||||
}
|
||||
)
|
||||
|
||||
# 在事务中标记支付成功并更新订阅
|
||||
repo.mark_paid(record_id, payment_method, payment_id)
|
||||
@@ -258,6 +258,7 @@ async def payment_callback(
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
|
||||
@router.post("/toggle-auto-renew", response_model=SimpleResponse)
|
||||
async def toggle_auto_renew(
|
||||
request: ToggleAutoRenewRequest,
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
from app.auth import AuthenticatedUser, get_current_user
|
||||
from app.dependencies import get_db_session
|
||||
from app.schemas.template import (
|
||||
@@ -194,7 +193,7 @@ def update_template(
|
||||
return _to_response(template)
|
||||
|
||||
|
||||
@router.delete("/{template_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.delete("/{template_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None)
|
||||
def delete_template(
|
||||
template_id: str,
|
||||
authenticated_user: AuthenticatedUser = Depends(get_current_user),
|
||||
@@ -272,7 +271,7 @@ def create_category(
|
||||
)
|
||||
|
||||
|
||||
@router.delete("/categories/{category_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.delete("/categories/{category_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None)
|
||||
def delete_category(
|
||||
category_id: str,
|
||||
authenticated_user: AuthenticatedUser = Depends(get_current_user),
|
||||
|
||||
@@ -143,7 +143,7 @@ def update_title(
|
||||
return _to_response(item)
|
||||
|
||||
|
||||
@router.delete("/{title_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.delete("/{title_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None)
|
||||
def delete_title(
|
||||
title_id: str,
|
||||
authenticated_user: AuthenticatedUser = Depends(get_current_user),
|
||||
|
||||
@@ -8,10 +8,10 @@ from app.auth import AuthenticatedUser, get_current_user
|
||||
from app.dependencies import get_cosyvoice_service, get_db_session
|
||||
from app.schemas.tts import (
|
||||
ListTTSJobResponse,
|
||||
TTSSynthesizeRequest,
|
||||
TTSSynthesizeResponse,
|
||||
TTSJobResponse,
|
||||
TTSStatusResponse,
|
||||
TTSSynthesizeRequest,
|
||||
TTSSynthesizeResponse,
|
||||
)
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, Response, status
|
||||
from sqlalchemy.orm import Session
|
||||
@@ -88,7 +88,8 @@ def synthesize(
|
||||
|
||||
# 提交 CosyVoice 合成任务
|
||||
workflow = TTSWorkflowService(
|
||||
repository=repository, cosyvoice_service=cosyvoice_service,
|
||||
repository=repository,
|
||||
cosyvoice_service=cosyvoice_service,
|
||||
)
|
||||
job = workflow.start_synthesis(job.id)
|
||||
|
||||
@@ -98,12 +99,11 @@ def synthesize(
|
||||
if task_id:
|
||||
try:
|
||||
from worker_app.tasks import process_tts_synthesis
|
||||
|
||||
process_tts_synthesis.delay(job.id)
|
||||
except Exception as e:
|
||||
# Celery 调度失败,标记 job 为 failed
|
||||
workflow.process_synthesis_failure(
|
||||
job.id, f"Celery 任务调度失败: {e}"
|
||||
)
|
||||
workflow.process_synthesis_failure(job.id, f"Celery 任务调度失败: {e}")
|
||||
|
||||
return TTSSynthesizeResponse(
|
||||
job_id=job.id,
|
||||
@@ -124,9 +124,7 @@ def list_tts_jobs(
|
||||
user_id = authenticated_user.user.id
|
||||
use_case = ListTTSJobsUseCase(repository)
|
||||
skip = (page - 1) * page_size
|
||||
items, total = use_case.execute(
|
||||
user_id, status=status_filter, skip=skip, limit=page_size
|
||||
)
|
||||
items, total = use_case.execute(user_id, status=status_filter, skip=skip, limit=page_size)
|
||||
return ListTTSJobResponse(
|
||||
items=[_to_response(j) for j in items],
|
||||
total=total,
|
||||
@@ -176,7 +174,7 @@ def get_tts_job_status(
|
||||
)
|
||||
|
||||
|
||||
@router.delete("/jobs/{job_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.delete("/jobs/{job_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None)
|
||||
def delete_tts_job(
|
||||
job_id: str,
|
||||
authenticated_user: AuthenticatedUser = Depends(get_current_user),
|
||||
|
||||
@@ -54,6 +54,7 @@ ALLOWED_MIME_TYPES = frozenset(
|
||||
"image/webp",
|
||||
"image/bmp",
|
||||
"image/tiff",
|
||||
"image/svg+xml",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -59,14 +59,10 @@ def _to_response(profile) -> VoiceCloneProfileResponse:
|
||||
|
||||
|
||||
def _get_workflow_service(
|
||||
repository: SQLAlchemyVoiceCloneProfileRepository = Depends(
|
||||
get_voice_clone_profile_repository
|
||||
),
|
||||
repository: SQLAlchemyVoiceCloneProfileRepository = Depends(get_voice_clone_profile_repository),
|
||||
cosyvoice_service: CosyVoiceService = Depends(get_cosyvoice_service),
|
||||
) -> VoiceCloneWorkflowService:
|
||||
return VoiceCloneWorkflowService(
|
||||
repository=repository, cosyvoice_service=cosyvoice_service
|
||||
)
|
||||
return VoiceCloneWorkflowService(repository=repository, cosyvoice_service=cosyvoice_service)
|
||||
|
||||
|
||||
@router.post(
|
||||
@@ -109,13 +105,9 @@ def create_voice_clone(
|
||||
logger.error(f"Failed to dispatch Celery task: {e}")
|
||||
# P2-3: Celery 调度失败时标记 profile 为 failed,避免永久卡在 processing
|
||||
try:
|
||||
workflow.process_clone_failure(
|
||||
profile.id, f"Celery 任务调度失败: {e}"
|
||||
)
|
||||
workflow.process_clone_failure(profile.id, f"Celery 任务调度失败: {e}")
|
||||
except Exception as inner_e:
|
||||
logger.error(
|
||||
f"Failed to mark profile as failed after dispatch error: {inner_e}"
|
||||
)
|
||||
logger.error(f"Failed to mark profile as failed after dispatch error: {inner_e}")
|
||||
|
||||
return _to_response(profile)
|
||||
|
||||
@@ -126,16 +118,12 @@ def list_voice_clones(
|
||||
skip: int = Query(0, ge=0),
|
||||
limit: int = Query(50, ge=1, le=200),
|
||||
authenticated_user: AuthenticatedUser = Depends(get_current_user),
|
||||
repository: SQLAlchemyVoiceCloneProfileRepository = Depends(
|
||||
get_voice_clone_profile_repository
|
||||
),
|
||||
repository: SQLAlchemyVoiceCloneProfileRepository = Depends(get_voice_clone_profile_repository),
|
||||
) -> ListVoiceCloneResponse:
|
||||
"""获取用户的音色克隆列表。"""
|
||||
user_id = authenticated_user.user.id
|
||||
use_case = ListVoiceClonesUseCase(repository)
|
||||
items, total = use_case.execute(
|
||||
user_id, status=status_filter, skip=skip, limit=limit
|
||||
)
|
||||
items, total = use_case.execute(user_id, status=status_filter, skip=skip, limit=limit)
|
||||
return ListVoiceCloneResponse(
|
||||
items=[_to_response(p) for p in items],
|
||||
total=total,
|
||||
@@ -146,9 +134,7 @@ def list_voice_clones(
|
||||
def get_voice_clone(
|
||||
clone_id: str,
|
||||
authenticated_user: AuthenticatedUser = Depends(get_current_user),
|
||||
repository: SQLAlchemyVoiceCloneProfileRepository = Depends(
|
||||
get_voice_clone_profile_repository
|
||||
),
|
||||
repository: SQLAlchemyVoiceCloneProfileRepository = Depends(get_voice_clone_profile_repository),
|
||||
) -> VoiceCloneProfileResponse:
|
||||
"""获取音色克隆详情。"""
|
||||
user_id = authenticated_user.user.id
|
||||
@@ -156,9 +142,7 @@ def get_voice_clone(
|
||||
try:
|
||||
profile = use_case.execute(clone_id, user_id)
|
||||
except VoiceCloneNotFoundError:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Voice clone not found"
|
||||
)
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Voice clone not found")
|
||||
return _to_response(profile)
|
||||
|
||||
|
||||
@@ -166,9 +150,7 @@ def get_voice_clone(
|
||||
def get_voice_clone_status(
|
||||
clone_id: str,
|
||||
authenticated_user: AuthenticatedUser = Depends(get_current_user),
|
||||
repository: SQLAlchemyVoiceCloneProfileRepository = Depends(
|
||||
get_voice_clone_profile_repository
|
||||
),
|
||||
repository: SQLAlchemyVoiceCloneProfileRepository = Depends(get_voice_clone_profile_repository),
|
||||
) -> VoiceCloneStatusResponse:
|
||||
"""查询音色克隆状态(用于前端轮询)。"""
|
||||
user_id = authenticated_user.user.id
|
||||
@@ -176,9 +158,7 @@ def get_voice_clone_status(
|
||||
try:
|
||||
profile = use_case.execute(clone_id, user_id)
|
||||
except VoiceCloneNotFoundError:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Voice clone not found"
|
||||
)
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Voice clone not found")
|
||||
return VoiceCloneStatusResponse(
|
||||
id=profile.id,
|
||||
status=profile.status,
|
||||
@@ -191,22 +171,19 @@ def get_voice_clone_status(
|
||||
@router.delete(
|
||||
"/{clone_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
response_model=None,
|
||||
)
|
||||
def delete_voice_clone(
|
||||
clone_id: str,
|
||||
authenticated_user: AuthenticatedUser = Depends(get_current_user),
|
||||
repository: SQLAlchemyVoiceCloneProfileRepository = Depends(
|
||||
get_voice_clone_profile_repository
|
||||
),
|
||||
repository: SQLAlchemyVoiceCloneProfileRepository = Depends(get_voice_clone_profile_repository),
|
||||
) -> Response:
|
||||
"""删除音色克隆档案。"""
|
||||
user_id = authenticated_user.user.id
|
||||
use_case = DeleteVoiceCloneUseCase(repository)
|
||||
deleted = use_case.execute(clone_id, user_id)
|
||||
if not deleted:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Voice clone not found"
|
||||
)
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Voice clone not found")
|
||||
return Response(status_code=204)
|
||||
|
||||
|
||||
@@ -224,9 +201,7 @@ def retry_voice_clone(
|
||||
try:
|
||||
profile = workflow.retry_clone(clone_id, user_id)
|
||||
except VoiceCloneNotFoundError:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Voice clone not found"
|
||||
)
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Voice clone not found")
|
||||
except VoiceCloneNotRetryableError:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
@@ -245,12 +220,8 @@ def retry_voice_clone(
|
||||
logger.error(f"Failed to dispatch Celery task: {e}")
|
||||
# P2-3: Celery 调度失败时标记 profile 为 failed,避免永久卡在 processing
|
||||
try:
|
||||
workflow.process_clone_failure(
|
||||
profile.id, f"Celery 任务调度失败: {e}"
|
||||
)
|
||||
workflow.process_clone_failure(profile.id, f"Celery 任务调度失败: {e}")
|
||||
except Exception as inner_e:
|
||||
logger.error(
|
||||
f"Failed to mark profile as failed after dispatch error: {inner_e}"
|
||||
)
|
||||
logger.error(f"Failed to mark profile as failed after dispatch error: {inner_e}")
|
||||
|
||||
return _to_response(profile)
|
||||
|
||||
@@ -316,7 +316,7 @@ def update_voice(
|
||||
return _to_response(item)
|
||||
|
||||
|
||||
@router.delete("/{voice_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.delete("/{voice_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None)
|
||||
def delete_voice(
|
||||
voice_id: str,
|
||||
authenticated_user: AuthenticatedUser = Depends(get_current_user),
|
||||
|
||||
Reference in New Issue
Block a user