diff --git a/.gitea/workflows/ci-cd.yml b/.gitea/workflows/ci-cd.yml old mode 100755 new mode 100644 index 69868e967..50c56a4f7 --- a/.gitea/workflows/ci-cd.yml +++ b/.gitea/workflows/ci-cd.yml @@ -92,9 +92,9 @@ jobs: shell: sh run: | set -eu - python3 -m pip install --break-system-packages -q -r requirements-base.txt - python3 -m pip install --break-system-packages -q -r requirements.txt - python3 -m pip install --break-system-packages -q -r requirements-dev.txt + python3 -m pip install -q -r requirements-base.txt + python3 -m pip install -q -r requirements.txt + python3 -m pip install -q -r requirements-dev.txt python3 -m black --version python3 -m isort --version-number python3 -m flake8 --version @@ -169,6 +169,10 @@ jobs: env: USE_IN_MEMORY_DB: "true" + OSS_ACCESS_KEY_ID: placeholder + OSS_ACCESS_KEY_SECRET: placeholder + OSS_BUCKET_NAME: xiaoxia-autocut + OSS_ENDPOINT: oss-cn-hangzhou.aliyuncs.com steps: - name: Checkout code @@ -217,13 +221,39 @@ jobs: tar.extract(member, '.') PY + - name: Install ffmpeg + shell: sh + run: | + set +e + if command -v ffmpeg > /dev/null 2>&1; then + echo "ffmpeg already installed: $(ffmpeg -version | head -1)" + exit 0 + fi + if command -v apt-get > /dev/null 2>&1; then + apt-get update -qq && apt-get install -y -qq ffmpeg + elif command -v yum > /dev/null 2>&1; then + yum install -y -q epel-release 2>/dev/null + yum install -y -q ffmpeg 2>/dev/null + if [ $? -ne 0 ] && command -v dnf > /dev/null 2>&1; then + dnf install -y -q --nogpgcheck https://download1.rpmfusion.org/free/el/rpmfusion-free-release-$(rpm -E %rhel).noarch.rpm 2>/dev/null + dnf install -y -q ffmpeg 2>/dev/null + fi + elif command -v dnf > /dev/null 2>&1; then + dnf install -y -q ffmpeg 2>/dev/null + fi + if command -v ffmpeg > /dev/null 2>&1; then + echo "ffmpeg installed successfully: $(ffmpeg -version | head -1)" + else + echo "Warning: ffmpeg installation failed or not available, some tests may be skipped" + fi + - name: Install dependencies shell: sh run: | set -eu - python3 -m pip install --break-system-packages -q -r requirements-base.txt - python3 -m pip install --break-system-packages -q -r requirements.txt - python3 -m pip install --break-system-packages -q -r requirements-dev.txt + python3 -m pip install -q -r requirements-base.txt + python3 -m pip install -q -r requirements.txt + python3 -m pip install -q -r requirements-dev.txt pytest --version - name: Run unit tests with coverage @@ -260,6 +290,10 @@ jobs: env: DATABASE_URL: postgresql+psycopg://postgres:postgres@127.0.0.1:5432/xiaoxia_saas USE_IN_MEMORY_DB: "false" + OSS_ACCESS_KEY_ID: placeholder + OSS_ACCESS_KEY_SECRET: placeholder + OSS_BUCKET_NAME: xiaoxia-autocut + OSS_ENDPOINT: oss-cn-hangzhou.aliyuncs.com steps: - name: Checkout code @@ -320,11 +354,37 @@ jobs: shell: sh run: | set -eu - python3 -m pip install --break-system-packages -q -r requirements-base.txt - python3 -m pip install --break-system-packages -q -r requirements.txt - python3 -m pip install --break-system-packages -q -r requirements-dev.txt + python3 -m pip install -q -r requirements-base.txt + python3 -m pip install -q -r requirements.txt + python3 -m pip install -q -r requirements-dev.txt pytest --version + - name: Install ffmpeg + shell: sh + run: | + set +e + if command -v ffmpeg > /dev/null 2>&1; then + echo "ffmpeg already installed: $(ffmpeg -version | head -1)" + exit 0 + fi + if command -v apt-get > /dev/null 2>&1; then + apt-get update -qq && apt-get install -y -qq ffmpeg + elif command -v yum > /dev/null 2>&1; then + yum install -y -q epel-release 2>/dev/null + yum install -y -q ffmpeg 2>/dev/null + if [ $? -ne 0 ] && command -v dnf > /dev/null 2>&1; then + dnf install -y -q --nogpgcheck https://download1.rpmfusion.org/free/el/rpmfusion-free-release-$(rpm -E %rhel).noarch.rpm 2>/dev/null + dnf install -y -q ffmpeg 2>/dev/null + fi + elif command -v dnf > /dev/null 2>&1; then + dnf install -y -q ffmpeg 2>/dev/null + fi + if command -v ffmpeg > /dev/null 2>&1; then + echo "ffmpeg installed successfully: $(ffmpeg -version | head -1)" + else + echo "Warning: ffmpeg installation failed or not available, some tests may be skipped" + fi + - name: Start Redis shell: sh run: | @@ -394,7 +454,7 @@ jobs: shell: sh run: | set -eu - pip install --break-system-packages -q pytest-rerunfailures + python3 -m pip install -q pytest-rerunfailures PYTHONPATH="$PWD/apps/api:$PWD" python3 -m coverage run --append \ --source=apps/api/app,packages \ --omit="*/migrations/*,*/tests/*,*/test_*.py,*/site-packages/*" \ diff --git a/apps/api/app/api/routes/asset_libraries.py b/apps/api/app/api/routes/asset_libraries.py index 5b9841c0c..6b7c297b6 100644 --- a/apps/api/app/api/routes/asset_libraries.py +++ b/apps/api/app/api/routes/asset_libraries.py @@ -12,7 +12,7 @@ from app.schemas.asset_library import ( EnsureDefaultLibraryRequest, ListAssetLibrariesResponse, ) -from fastapi import APIRouter, Depends, HTTPException, Query, status +from fastapi import APIRouter, Depends, HTTPException, Query, Response, status from packages.application import ( CreateAssetLibraryCommand, @@ -146,7 +146,7 @@ def ensure_default_library( return _to_asset_library_response(created) -@router.delete("/{library_id}", status_code=status.HTTP_204_NO_CONTENT) +@router.delete("/{library_id}", status_code=status.HTTP_204_NO_CONTENT, response_class=Response) def delete_asset_library( library_id: str, authenticated_user: AuthenticatedUser = Depends(get_current_user), diff --git a/apps/api/app/api/routes/assets.py b/apps/api/app/api/routes/assets.py index 1ec5e7239..0861a2cfb 100644 --- a/apps/api/app/api/routes/assets.py +++ b/apps/api/app/api/routes/assets.py @@ -1,6 +1,7 @@ import logging from typing import Any, Optional +from app.api.routes._helpers import check_project_access from app.auth import AuthenticatedUser, get_current_user from app.core.storage import get_storage_service from app.dependencies import ( @@ -19,7 +20,7 @@ from app.schemas.asset import ( UpdateAssetReviewRequest, ) from app.schemas.tag import TagAssetsRequest -from fastapi import APIRouter, Depends, HTTPException, Query +from fastapi import APIRouter, Depends, HTTPException, Query, Response from packages.application import ( CreateAssetCommand, @@ -27,8 +28,6 @@ from packages.application import ( ) from packages.domain import AssetStatus, ClassificationStatus -from app.api.routes._helpers import check_project_access - logger = logging.getLogger(__name__) router = APIRouter() @@ -74,7 +73,6 @@ def _to_asset_response(item, storage_service=None) -> AssetResponse: ) - @router.get("", response_model=ListAssetsResponse) def list_assets( library_id: Optional[str] = Query(None), @@ -330,7 +328,7 @@ def update_asset( return _to_asset_response(updated) -@router.delete("/{asset_id}", status_code=204) +@router.delete("/{asset_id}", status_code=204, response_class=Response) def delete_asset( asset_id: str, authenticated_user: AuthenticatedUser = Depends(get_current_user), @@ -369,7 +367,7 @@ def tag_asset( return _to_asset_response(updated) -@router.delete("/{asset_id}/tags/{tag_id}", status_code=204) +@router.delete("/{asset_id}/tags/{tag_id}", status_code=204, response_class=Response) def untag_asset( asset_id: str, tag_id: str, diff --git a/apps/api/app/api/routes/chunked_upload.py b/apps/api/app/api/routes/chunked_upload.py index c9cd064b5..1db5a1647 100644 --- a/apps/api/app/api/routes/chunked_upload.py +++ b/apps/api/app/api/routes/chunked_upload.py @@ -13,6 +13,7 @@ from pathlib import Path from typing import Any from uuid import uuid4 +from app.api.routes._helpers import require_project_and_library from app.auth import AuthenticatedUser, get_current_user from app.core.celery_app import celery_app from app.core.storage import OSSStorageService, get_storage_service @@ -34,8 +35,6 @@ from fastapi.params import File from packages.application import GetProjectUseCase, SubmitIngestJobCommand, SubmitIngestJobUseCase -from app.api.routes._helpers import require_project_and_library - router = APIRouter() logger = logging.getLogger(__name__) diff --git a/apps/api/app/api/routes/duplication.py b/apps/api/app/api/routes/duplication.py index 427d369fa..bdf9094f9 100644 --- a/apps/api/app/api/routes/duplication.py +++ b/apps/api/app/api/routes/duplication.py @@ -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, response_model=None) +@router.delete("/records/{record_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None, response_class=Response) def delete_duplication_record( record_id: str, authenticated_user: AuthenticatedUser = Depends(get_current_user), @@ -257,7 +257,7 @@ def delete_duplication_record( use_case = DeleteDuplicationRecordUseCase(duplication_repository) use_case.execute(record_id) - return Response(status_code=204) + return @router.post("/records/{record_id}/retry", response_model=DuplicationUploadResponse) diff --git a/apps/api/app/api/routes/edit_plans.py b/apps/api/app/api/routes/edit_plans.py old mode 100755 new mode 100644 index 9d7c9f2d3..85ba23b6a --- a/apps/api/app/api/routes/edit_plans.py +++ b/apps/api/app/api/routes/edit_plans.py @@ -25,14 +25,15 @@ from app.auth import AuthenticatedUser, get_current_user from app.dependencies import get_db_session, get_project_repository from app.schemas.generation_task import GenerationTaskResponse from app.services import EditPlanService -from fastapi import APIRouter, Depends, HTTPException, Query, status +from fastapi import APIRouter, Depends, HTTPException, Query, Response, status from pydantic import BaseModel, Field from sqlalchemy.orm import Session -from ._helpers import check_project_access from packages.domain.config_schemas import normalize_plan_config from packages.domain.edit_plan import EditPlan, EditPlanStatus +from ._helpers import check_project_access + logger = logging.getLogger(__name__) router = APIRouter() @@ -421,7 +422,7 @@ def update_plan( return _to_response(result) -@router.delete("/{plan_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None) +@router.delete("/{plan_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None, response_class=Response) def delete_plan( plan_id: str, db: Session = Depends(get_db_session), diff --git a/apps/api/app/api/routes/edit_plans_generation.py b/apps/api/app/api/routes/edit_plans_generation.py index 3aae7abfa..7d7e37039 100644 --- a/apps/api/app/api/routes/edit_plans_generation.py +++ b/apps/api/app/api/routes/edit_plans_generation.py @@ -15,8 +15,8 @@ from app.api.routes._helpers import check_project_access from app.api.routes.edit_plans import ( ClipStatusItem, EditPlanGenerateResponse, - EditPlanGenerationStatusResponse, EditPlanGenerationsResponse, + EditPlanGenerationStatusResponse, ) from app.auth import AuthenticatedUser, get_current_user from app.core.celery_app import celery_app diff --git a/apps/api/app/api/routes/feature_flags.py b/apps/api/app/api/routes/feature_flags.py index c5d1c2822..a3dff32f0 100755 --- a/apps/api/app/api/routes/feature_flags.py +++ b/apps/api/app/api/routes/feature_flags.py @@ -173,7 +173,7 @@ async def update_feature_flag( raise HTTPException(status_code=500, detail=f"Failed to update flag: {exc}") -@router.delete("/{name}", status_code=status.HTTP_204_NO_CONTENT, response_model=None) +@router.delete("/{name}", status_code=status.HTTP_204_NO_CONTENT, response_model=None, response_class=Response) async def delete_feature_flag( name: str, _: bool = Depends(_verify_internal_api_key), diff --git a/apps/api/app/api/routes/generation_tasks.py b/apps/api/app/api/routes/generation_tasks.py old mode 100755 new mode 100644 index 04d509b02..22c031f14 --- a/apps/api/app/api/routes/generation_tasks.py +++ b/apps/api/app/api/routes/generation_tasks.py @@ -3,6 +3,7 @@ import random import uuid from typing import Any +from app.api.routes._helpers import check_project_access from app.auth import AuthenticatedUser, get_current_user from app.core.storage import OSSStorageService, get_storage_service from app.core.task_enqueue import ( @@ -31,8 +32,6 @@ from app.schemas.generation_task import ( ) from fastapi import APIRouter, Depends, HTTPException -from app.api.routes._helpers import check_project_access - from packages.application import ( CreateGenerationTaskCommand, CreateGenerationTaskUseCase, diff --git a/apps/api/app/api/routes/projects.py b/apps/api/app/api/routes/projects.py old mode 100755 new mode 100644 index 17be8f438..b873d5557 --- a/apps/api/app/api/routes/projects.py +++ b/apps/api/app/api/routes/projects.py @@ -7,7 +7,7 @@ from app.schemas.project import ( ListProjectsResponse, ProjectResponse, ) -from fastapi import APIRouter, Depends, HTTPException, status +from fastapi import APIRouter, Depends, HTTPException, Response, status from packages.application import ( CreateProjectCommand, @@ -72,7 +72,7 @@ def create_project( return _to_project_response(project) -@router.delete("/{project_id}") +@router.delete("/{project_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None, response_class=Response) def delete_project( project_id: str, authenticated_user: AuthenticatedUser = Depends(get_current_user), @@ -88,4 +88,4 @@ def delete_project( ) if not deleted: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Project not found") - return {"message": "Project deleted successfully"} + return diff --git a/apps/api/app/api/routes/tags.py b/apps/api/app/api/routes/tags.py index 7de3e278b..45da212a5 100644 --- a/apps/api/app/api/routes/tags.py +++ b/apps/api/app/api/routes/tags.py @@ -10,7 +10,7 @@ from app.schemas.tag import ( ListTagsResponse, TagResponse, ) -from fastapi import APIRouter, Depends, HTTPException +from fastapi import APIRouter, Depends, HTTPException, Response from packages.domain import Tag @@ -52,7 +52,7 @@ def create_tag( return TagResponse(id=created.id, name=created.name, created_at=created.created_at) -@router.delete("/{tag_id}", status_code=204) +@router.delete("/{tag_id}", status_code=204, response_class=Response) def delete_tag( tag_id: str, authenticated_user: AuthenticatedUser = Depends(get_current_user), diff --git a/apps/api/app/api/routes/templates.py b/apps/api/app/api/routes/templates.py index 6d5c5185f..1cfecd123 100644 --- a/apps/api/app/api/routes/templates.py +++ b/apps/api/app/api/routes/templates.py @@ -206,7 +206,7 @@ def update_template( return _to_response(template) -@router.delete("/{template_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None) +@router.delete("/{template_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None, response_class=Response) def delete_template( template_id: str, authenticated_user: AuthenticatedUser = Depends(get_current_user), @@ -217,7 +217,7 @@ def delete_template( deleted = use_case.execute(template_id, user_id) if not deleted: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Template not found") - return Response(status_code=204) + return @router.post("/{template_id}/toggle-favorite", response_model=ToggleFavoriteResponse) @@ -307,7 +307,7 @@ def create_category( ) -@router.delete("/categories/{category_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None) +@router.delete("/categories/{category_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None, response_class=Response) def delete_category( category_id: str, authenticated_user: AuthenticatedUser = Depends(get_current_user), @@ -318,4 +318,4 @@ def delete_category( deleted = use_case.execute(category_id, user_id) if not deleted: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Category not found") - return Response(status_code=204) + return diff --git a/apps/api/app/api/routes/titles.py b/apps/api/app/api/routes/titles.py index f65f3cdea..e81730aa3 100644 --- a/apps/api/app/api/routes/titles.py +++ b/apps/api/app/api/routes/titles.py @@ -4,6 +4,7 @@ from __future__ import annotations from typing import Optional +from app.api.routes._helpers import get_user_plan from app.auth import AuthenticatedUser, get_current_user from app.dependencies import get_db_session, get_user_repository from app.schemas.title_library import ( @@ -28,8 +29,6 @@ from packages.application.title_library.use_cases import ( ) from packages.ports.user_repository import UserRepository -from app.api.routes._helpers import get_user_plan - router = APIRouter() @@ -138,7 +137,7 @@ def update_title( return _to_response(item) -@router.delete("/{title_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None) +@router.delete("/{title_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None, response_class=Response) def delete_title( title_id: str, authenticated_user: AuthenticatedUser = Depends(get_current_user), @@ -149,4 +148,4 @@ def delete_title( deleted = use_case.execute(title_id, user_id) if not deleted: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Title not found") - return Response(status_code=204) + return diff --git a/apps/api/app/api/routes/tts.py b/apps/api/app/api/routes/tts.py index 77f9991e2..40c665685 100755 --- a/apps/api/app/api/routes/tts.py +++ b/apps/api/app/api/routes/tts.py @@ -241,7 +241,7 @@ def get_tts_job_status( ) -@router.delete("/jobs/{job_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None) +@router.delete("/jobs/{job_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None, response_class=Response) def delete_tts_job( job_id: str, authenticated_user: AuthenticatedUser = Depends(get_current_user), @@ -253,7 +253,7 @@ def delete_tts_job( deleted = use_case.execute(job_id, user_id) if not deleted: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="TTS job not found") - return Response(status_code=204) + return @router.post( diff --git a/apps/api/app/api/routes/upload.py b/apps/api/app/api/routes/upload.py index 97a83bfcd..c67add1eb 100644 --- a/apps/api/app/api/routes/upload.py +++ b/apps/api/app/api/routes/upload.py @@ -2,6 +2,7 @@ import logging from typing import Any from uuid import uuid4 +from app.api.routes._helpers import require_project_and_library from app.auth import AuthenticatedUser, get_current_user from app.config import get_settings from app.core.celery_app import celery_app @@ -23,8 +24,6 @@ from fastapi import APIRouter, Depends, File, Form, HTTPException, UploadFile, s from packages.application import SubmitIngestJobCommand, SubmitIngestJobUseCase -from app.api.routes._helpers import require_project_and_library - logger = logging.getLogger(__name__) router = APIRouter() diff --git a/apps/api/app/api/routes/voice_clones.py b/apps/api/app/api/routes/voice_clones.py index 8a138c12f..19fcf8a21 100644 --- a/apps/api/app/api/routes/voice_clones.py +++ b/apps/api/app/api/routes/voice_clones.py @@ -172,6 +172,7 @@ def get_voice_clone_status( "/{clone_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None, + response_class=Response, ) def delete_voice_clone( clone_id: str, @@ -184,7 +185,7 @@ def delete_voice_clone( 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") - return Response(status_code=204) + return @router.post("/{clone_id}/retry", response_model=VoiceCloneProfileResponse) diff --git a/apps/api/app/api/routes/voices.py b/apps/api/app/api/routes/voices.py old mode 100755 new mode 100644 index a8830a709..9a7d8e5d4 --- a/apps/api/app/api/routes/voices.py +++ b/apps/api/app/api/routes/voices.py @@ -7,6 +7,7 @@ from __future__ import annotations from typing import Literal, Optional +from app.api.routes._helpers import get_user_plan from app.auth import AuthenticatedUser, get_current_user from app.dependencies import get_audio_url_signer, get_db_session, get_user_repository from app.schemas.voice import ( @@ -39,8 +40,6 @@ from packages.application.voice_library.use_cases import ( from packages.domain.preset_voices import PRESET_VOICES from packages.ports.user_repository import UserRepository -from app.api.routes._helpers import get_user_plan - router = APIRouter() @@ -323,7 +322,7 @@ def update_voice( return _to_response(item, sign_url) -@router.delete("/{voice_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None) +@router.delete("/{voice_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None, response_class=Response) def delete_voice( voice_id: str, authenticated_user: AuthenticatedUser = Depends(get_current_user), @@ -334,4 +333,4 @@ def delete_voice( deleted = use_case.execute(voice_id, user_id) if not deleted: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Voice not found") - return Response(status_code=204) + return diff --git a/apps/api/app/core/storage.py b/apps/api/app/core/storage.py index 8c39c13f8..af20e14b9 100644 --- a/apps/api/app/core/storage.py +++ b/apps/api/app/core/storage.py @@ -5,8 +5,8 @@ This module keeps old import paths working so existing code does not need to change. """ +from packages.shared.storage import SharedStorageService as OSSStorageService from packages.shared.storage import ( - SharedStorageService as OSSStorageService, get_shared_storage_service, get_storage_service, ) diff --git a/apps/api/app/services/job_service.py b/apps/api/app/services/job_service.py old mode 100755 new mode 100644 index d24ee32eb..ee80937e2 --- a/apps/api/app/services/job_service.py +++ b/apps/api/app/services/job_service.py @@ -13,7 +13,6 @@ from __future__ import annotations import logging from typing import Any - from packages.application.jobs import ( CancelJobUseCase, CompleteJobCommand, diff --git a/apps/web/src/pages/accounts/Accounts.tsx b/apps/web/src/pages/accounts/Accounts.tsx index d2b541b92..8252a30bb 100644 --- a/apps/web/src/pages/accounts/Accounts.tsx +++ b/apps/web/src/pages/accounts/Accounts.tsx @@ -13,11 +13,7 @@ import "./accounts.css"; /* ── 类型定义 ───────────────────────────────────────────── */ -export type PlatformId = - | "douyin" - | "kuaishou" - | "xiaohongshu" - | "wechat"; +export type PlatformId = "douyin" | "kuaishou" | "xiaohongshu" | "wechat"; export interface Platform { id: PlatformId; diff --git a/apps/web/src/pages/dashboard/Dashboard.tsx b/apps/web/src/pages/dashboard/Dashboard.tsx index 6b477c24d..95239e755 100644 --- a/apps/web/src/pages/dashboard/Dashboard.tsx +++ b/apps/web/src/pages/dashboard/Dashboard.tsx @@ -39,7 +39,11 @@ const Dashboard: React.FC = () => {

最近任务

-
@@ -51,7 +55,10 @@ const Dashboard: React.FC = () => {
{/* 使用统计 */} -
+

使用统计

@@ -66,13 +73,18 @@ const Dashboard: React.FC = () => {
{/* 公告 */} -
+

公告

- 官方 + + 官方 +

欢迎使用小应 SaaS 平台

diff --git a/apps/web/src/pages/titles/TitleLibrary.tsx b/apps/web/src/pages/titles/TitleLibrary.tsx index 740f9f62c..1905472cb 100644 --- a/apps/web/src/pages/titles/TitleLibrary.tsx +++ b/apps/web/src/pages/titles/TitleLibrary.tsx @@ -416,7 +416,6 @@ const TitleLibrary: React.FC = () => { [deleteMutation], ); - /* 新建标题 */ const handleCreateTitle = () => { if (!newTitleContent.trim()) { @@ -503,7 +502,6 @@ const TitleLibrary: React.FC = () => { {cat.count} 条
-
))} @@ -614,8 +612,6 @@ const TitleLibrary: React.FC = () => { - - {/* ─── 新建标题弹窗 ─── */} 0: return min(clip.duration, clip.actual_duration) if clip.actual_duration > 0 else clip.duration return clip.actual_duration if clip.actual_duration > 0 else 0.0 - diff --git a/apps/worker/worker_app/tasks/generation.py b/apps/worker/worker_app/tasks/generation.py index 443ddc464..5d7f6e356 100644 --- a/apps/worker/worker_app/tasks/generation.py +++ b/apps/worker/worker_app/tasks/generation.py @@ -67,7 +67,11 @@ def _update_task_status(task_id: str, status_action: str, **kwargs) -> bool: action(**kwargs) repo.update(task) - logger.info("GenerationTask 状态更新成功: task_id=%s action=%s", task_id, status_action) + logger.info( + "GenerationTask 状态更新成功: task_id=%s action=%s", + task_id, + status_action, + ) return True finally: session.close() @@ -458,7 +462,10 @@ def _download_library_assets( if not storage_key: failed_assets.append(f"{asset.name}({asset.id})") logger.warning( - "[task_id=%s] 素材缺少 file_url, 跳过: asset_id=%s name=%s", task_id, asset.id, asset.name + "[task_id=%s] 素材缺少 file_url, 跳过: asset_id=%s name=%s", + task_id, + asset.id, + asset.name, ) if gen_task: gen_task.append_log( @@ -504,7 +511,12 @@ def _download_library_assets( ) else: failed_assets.append(f"{asset.name}({asset.id})") - logger.warning("[task_id=%s] Failed to download asset: %s (id=%s)", task_id, asset.name, asset.id) + logger.warning( + "[task_id=%s] Failed to download asset: %s (id=%s)", + task_id, + asset.name, + asset.id, + ) if gen_task: gen_task.append_log( "下载素材", @@ -910,10 +922,12 @@ def _upload_and_record( key = normalize_storage_key(file_url) if not (bucket and bucket.object_exists(key)): raise RuntimeError( - f"OSS 上传后 URL 不可访问且 object_exists 失败: file_url={file_url}, " - f"storage_key={storage_key}" + f"OSS 上传后 URL 不可访问且 object_exists 失败: file_url={file_url}, " f"storage_key={storage_key}" ) - logger.info("URL 校验失败但 object_exists 确认文件存在,视为上传成功: storage_key=%s", key) + logger.info( + "URL 校验失败但 object_exists 确认文件存在,视为上传成功: storage_key=%s", + key, + ) logger.info( "[task_id=%s] [OSS上传] 成功: 耗时=%.1fs, file_url=%s", diff --git a/scripts/check_migration_safety.py b/scripts/check_migration_safety.py index 6ec4b784d..747181e8f 100644 --- a/scripts/check_migration_safety.py +++ b/scripts/check_migration_safety.py @@ -48,10 +48,19 @@ HIGH_RISK_PATTERNS = [ # 中风险模式:可能导致数据丢失或兼容性问题 MEDIUM_RISK_PATTERNS = [ - (r"op\.alter_column\([^)]*nullable\s*=\s*False", "新增 NOT NULL 约束 - 旧数据可能为空导致迁移失败"), + ( + r"op\.alter_column\([^)]*nullable\s*=\s*False", + "新增 NOT NULL 约束 - 旧数据可能为空导致迁移失败", + ), (r"op\.alter_column\([^)]*type_\s*=", "列类型变更 - 可能导致数据截断或转换失败"), - (r"\bop\.rename_table\(", "op.rename_table() - 重命名表,可能导致依赖该表的代码报错"), - (r"\bop\.rename_column\(", "op.rename_column() - 重命名列,可能导致依赖该列的代码报错"), + ( + r"\bop\.rename_table\(", + "op.rename_table() - 重命名表,可能导致依赖该表的代码报错", + ), + ( + r"\bop\.rename_column\(", + "op.rename_column() - 重命名列,可能导致依赖该列的代码报错", + ), (r"\bop\.drop_index\(", "op.drop_index() - 删除索引,可能影响查询性能"), (r"\bop\.drop_constraint\(", "op.drop_constraint() - 删除约束,可能影响数据完整性"), ] @@ -95,7 +104,16 @@ def get_new_migrations_via_diff(diff_target: str) -> List[Path]: """ try: result = subprocess.run( - ["git", "diff", "--name-only", "--diff-filter=A", diff_target, "HEAD", "--", "alembic/versions/"], + [ + "git", + "diff", + "--name-only", + "--diff-filter=A", + diff_target, + "HEAD", + "--", + "alembic/versions/", + ], cwd=str(REPO_ROOT), capture_output=True, text=True, @@ -247,4 +265,3 @@ def main() -> int: if __name__ == "__main__": sys.exit(main()) - diff --git a/tests/integration/test_auth.py b/tests/integration/test_auth.py index 97a8d6d02..36a83d66e 100755 --- a/tests/integration/test_auth.py +++ b/tests/integration/test_auth.py @@ -308,7 +308,7 @@ class TestPasswordReset: ) response = client.post( - "/api/v1/auth/password/forgot", + "/api/v1/auth/forgot-password", json={"email": test_email}, ) @@ -318,7 +318,7 @@ class TestPasswordReset: def test_request_password_reset_nonexistent_user(self): """测试请求不存在的用户密码重置""" response = client.post( - "/api/v1/auth/password/forgot", + "/api/v1/auth/forgot-password", json={"email": "nonexistent@example.com"}, ) diff --git a/tests/unit/test_asset_library_delete.py b/tests/unit/test_asset_library_delete.py index 9c4178be6..3b595b079 100644 --- a/tests/unit/test_asset_library_delete.py +++ b/tests/unit/test_asset_library_delete.py @@ -219,7 +219,7 @@ class TestDeleteAssetLibrary: response = client.delete("/api/v1/asset-libraries/lib-1") assert response.status_code == 403 - assert "Access denied" in response.json()["detail"] + assert "无权访问该项目" in response.json()["detail"] # 库未被删除 assert lib_repo.find_by_id("lib-1") is not None diff --git a/tests/unit/test_config_oss.py b/tests/unit/test_config_oss.py old mode 100644 new mode 100755 index 0ddcad23e..bf6259041 --- a/tests/unit/test_config_oss.py +++ b/tests/unit/test_config_oss.py @@ -37,7 +37,7 @@ def _fresh_settings(**env_overrides: dict[str, str]): "JWT_SECRET_KEY": "unit-test-secret-key-12345", **env_overrides, } - with patch.dict(os.environ, env, clear=False): + with patch.dict(os.environ, env, clear=True): Settings = _load_settings_class() return Settings() diff --git a/tests/unit/test_edit_plan_generation_api.py b/tests/unit/test_edit_plan_generation_api.py old mode 100755 new mode 100644 index 8f05cd720..1479514d9 --- a/tests/unit/test_edit_plan_generation_api.py +++ b/tests/unit/test_edit_plan_generation_api.py @@ -318,7 +318,7 @@ class TestGeneratePlan: clip = _make_clip(plan.id, order=1) clip_repo.create(clip) - with patch("app.api.routes.edit_plans.celery_app") as mock_celery: + with patch("app.api.routes.edit_plans_generation.celery_app") as mock_celery: mock_celery.send_task = MagicMock() resp = client.post(f"/api/v1/edit-plans/{plan.id}/generate") @@ -406,7 +406,7 @@ class TestGeneratePlan: clip = _make_clip(plan.id, order=1, status=EditPlanClipStatus.READY) clip_repo.create(clip) - with patch("app.api.routes.edit_plans.celery_app") as mock_celery: + with patch("app.api.routes.edit_plans_generation.celery_app") as mock_celery: mock_celery.send_task = MagicMock() resp = client.post(f"/api/v1/edit-plans/{plan.id}/generate") @@ -427,7 +427,7 @@ class TestGeneratePlan: clip = _make_clip(plan.id, order=i + 1) clip_repo.create(clip) - with patch("app.api.routes.edit_plans.celery_app") as mock_celery: + with patch("app.api.routes.edit_plans_generation.celery_app") as mock_celery: mock_celery.send_task = MagicMock() resp = client.post(f"/api/v1/edit-plans/{plan.id}/generate") @@ -580,7 +580,7 @@ class TestResponseSchema: clip = _make_clip(plan.id, order=1) clip_repo.create(clip) - with patch("app.api.routes.edit_plans.celery_app") as mock_celery: + with patch("app.api.routes.edit_plans_generation.celery_app") as mock_celery: mock_celery.send_task = MagicMock() resp = client.post(f"/api/v1/edit-plans/{plan.id}/generate") @@ -623,7 +623,7 @@ class TestGeneratePlanErrorHandling: clip = _make_clip(plan.id, order=1) clip_repo.create(clip) - with patch("app.api.routes.edit_plans.celery_app") as mock_celery: + with patch("app.api.routes.edit_plans_generation.celery_app") as mock_celery: # 模拟 Celery 调度失败 mock_celery.send_task.side_effect = RuntimeError("Redis 连接超时") resp = client.post(f"/api/v1/edit-plans/{plan.id}/generate") @@ -647,7 +647,7 @@ class TestGeneratePlanErrorHandling: clip = _make_clip(plan.id, order=1) clip_repo.create(clip) - with patch("app.api.routes.edit_plans.celery_app") as mock_celery: + with patch("app.api.routes.edit_plans_generation.celery_app") as mock_celery: mock_celery.send_task.side_effect = RuntimeError("调度失败") resp = client.post(f"/api/v1/edit-plans/{plan.id}/generate") @@ -668,7 +668,7 @@ class TestGeneratePlanErrorHandling: clip = _make_clip(plan.id, order=1) clip_repo.create(clip) - with patch("app.api.routes.edit_plans.celery_app") as mock_celery: + with patch("app.api.routes.edit_plans_generation.celery_app") as mock_celery: mock_celery.send_task.side_effect = ConnectionError("Broker 不可达") resp = client.post(f"/api/v1/edit-plans/{plan.id}/generate") diff --git a/tests/unit/test_feature_flag.py b/tests/unit/test_feature_flag.py old mode 100755 new mode 100644 index a349cc3b2..8bab8c04e --- a/tests/unit/test_feature_flag.py +++ b/tests/unit/test_feature_flag.py @@ -7,7 +7,6 @@ from __future__ import annotations from unittest.mock import MagicMock - from packages.adapters.redis.feature_flag_store import ( FeatureFlagConfig, InMemoryFeatureFlagStore, diff --git a/tests/unit/test_oss_direct_upload.py b/tests/unit/test_oss_direct_upload.py index 57317b1d1..9f9f7152b 100644 --- a/tests/unit/test_oss_direct_upload.py +++ b/tests/unit/test_oss_direct_upload.py @@ -8,9 +8,12 @@ sys.path.insert(0, str(Path(__file__).resolve().parents[2] / "apps" / "api")) import app.config as app_config from app.core.storage import OSSStorageService +import packages.shared.config as shared_config + def _reset_settings() -> None: app_config._settings = None + shared_config._settings = None def test_create_direct_upload_post_limits_key_and_size(monkeypatch): diff --git a/tests/unit/test_tts_oss_transfer.py b/tests/unit/test_tts_oss_transfer.py index 67271ad44..99d5fc292 100644 --- a/tests/unit/test_tts_oss_transfer.py +++ b/tests/unit/test_tts_oss_transfer.py @@ -9,7 +9,6 @@ from __future__ import annotations from datetime import datetime, timezone from unittest.mock import MagicMock, patch - from packages.application.cosyvoice_service import CosyVoiceService from packages.application.tts_job.workflow import TTSWorkflowService from packages.domain.tts_job import TTSJob, TTSJobStatus