style: apply black formatting to pass CI validation (#126)
CI/CD Pipeline / Validate Code Quality And Tests (push) Has been cancelled
CI/CD Pipeline / Frontend Lint (push) Has been cancelled
Deploy / Deploy Staging (push) Has been cancelled
Deploy / Build Production Runtime Images (push) Has been cancelled
Deploy / Deploy Production (push) Has been cancelled
Deploy / Production Browser E2E (push) Has been cancelled

This commit was merged in pull request #126.
This commit is contained in:
2026-06-30 17:23:08 +08:00
parent 1e6f78a346
commit 52ff2f80ad
107 changed files with 1517 additions and 1010 deletions
@@ -27,9 +27,7 @@ class SQLAlchemyAssetLibraryRepository:
return self.get(library_id)
def find_by_project(self, project_id: str) -> list[AssetLibrary]:
models = self.session.query(AssetLibraryModel).filter(
AssetLibraryModel.project_id == project_id
).all()
models = self.session.query(AssetLibraryModel).filter(AssetLibraryModel.project_id == project_id).all()
return [
AssetLibrary(
id=model.id,
@@ -60,9 +58,7 @@ class SQLAlchemyAssetLibraryRepository:
return library
def update(self, library: AssetLibrary) -> AssetLibrary:
model = self.session.query(AssetLibraryModel).filter(
AssetLibraryModel.id == library.id
).first()
model = self.session.query(AssetLibraryModel).filter(AssetLibraryModel.id == library.id).first()
if model:
model.project_id = library.project_id
model.name = library.name
@@ -74,9 +70,7 @@ class SQLAlchemyAssetLibraryRepository:
return library
def delete(self, library_id: str) -> bool:
model = self.session.query(AssetLibraryModel).filter(
AssetLibraryModel.id == library_id
).first()
model = self.session.query(AssetLibraryModel).filter(AssetLibraryModel.id == library_id).first()
if model:
self.session.delete(model)
self.session.commit()
@@ -84,18 +78,14 @@ class SQLAlchemyAssetLibraryRepository:
return False
async def increment_asset_count(self, library_id: str, size_delta: int) -> None:
model = self.session.query(AssetLibraryModel).filter(
AssetLibraryModel.id == library_id
).first()
model = self.session.query(AssetLibraryModel).filter(AssetLibraryModel.id == library_id).first()
if model:
model.asset_count = (model.asset_count or 0) + 1
model.total_size = (model.total_size or 0) + size_delta
self.session.commit()
async def decrement_asset_count(self, library_id: str, size_delta: int) -> None:
model = self.session.query(AssetLibraryModel).filter(
AssetLibraryModel.id == library_id
).first()
model = self.session.query(AssetLibraryModel).filter(AssetLibraryModel.id == library_id).first()
if model:
model.asset_count = max(0, (model.asset_count or 0) - 1)
model.total_size = max(0, (model.total_size or 0) - size_delta)
@@ -17,9 +17,13 @@ class SQLAlchemyAssetRepository:
skip: int = 0,
limit: int = 100,
) -> list[Asset]:
models = self.session.query(AssetModel).filter(
AssetModel.asset_library_id == library_id
).offset(skip).limit(limit).all()
models = (
self.session.query(AssetModel)
.filter(AssetModel.asset_library_id == library_id)
.offset(skip)
.limit(limit)
.all()
)
return [self._to_domain(model) for model in models]
def find_by_project(
@@ -28,9 +32,9 @@ class SQLAlchemyAssetRepository:
skip: int = 0,
limit: int = 100,
) -> list[Asset]:
models = self.session.query(AssetModel).filter(
AssetModel.project_id == project_id
).offset(skip).limit(limit).all()
models = (
self.session.query(AssetModel).filter(AssetModel.project_id == project_id).offset(skip).limit(limit).all()
)
return [self._to_domain(model) for model in models]
def find_by_id(self, asset_id: str) -> Asset | None:
@@ -101,24 +105,23 @@ class SQLAlchemyAssetRepository:
return False
def count_by_project(self, project_id: str) -> int:
return self.session.query(AssetModel).filter(
AssetModel.project_id == project_id
).count()
return self.session.query(AssetModel).filter(AssetModel.project_id == project_id).count()
def count_by_project_ids(self, project_ids: list[str]) -> int:
if not project_ids:
return 0
return self.session.query(AssetModel).filter(
AssetModel.project_id.in_(project_ids)
).count()
return self.session.query(AssetModel).filter(AssetModel.project_id.in_(project_ids)).count()
def sum_storage_by_project_ids(self, project_ids: list[str]) -> int:
if not project_ids:
return 0
from sqlalchemy import func
result = self.session.query(func.coalesce(func.sum(AssetModel.file_size), 0)).filter(
AssetModel.project_id.in_(project_ids)
).scalar()
result = (
self.session.query(func.coalesce(func.sum(AssetModel.file_size), 0))
.filter(AssetModel.project_id.in_(project_ids))
.scalar()
)
return int(result or 0)
def _to_domain(self, model: AssetModel) -> Asset:
@@ -1,4 +1,5 @@
"""查重记录 SQLAlchemy 仓库实现。"""
from __future__ import annotations
import json
@@ -34,9 +35,7 @@ class SQLAlchemyDuplicationRecordRepository:
return record
def get(self, record_id: str) -> DuplicationRecord | None:
model = self.session.query(DuplicationRecordModel).filter(
DuplicationRecordModel.id == record_id
).first()
model = self.session.query(DuplicationRecordModel).filter(DuplicationRecordModel.id == record_id).first()
if model is None:
return None
return self._to_domain(model)
@@ -53,9 +52,7 @@ class SQLAlchemyDuplicationRecordRepository:
return [self._to_domain(m) for m in models]
def update(self, record: DuplicationRecord) -> DuplicationRecord:
model = self.session.query(DuplicationRecordModel).filter(
DuplicationRecordModel.id == record.id
).first()
model = self.session.query(DuplicationRecordModel).filter(DuplicationRecordModel.id == record.id).first()
if model is None:
return record
model.status = record.status
@@ -66,9 +63,7 @@ class SQLAlchemyDuplicationRecordRepository:
model.updated_at = record.updated_at
# 更新 segments:先删后建
self.session.query(DuplicationSegmentModel).filter(
DuplicationSegmentModel.record_id == record.id
).delete()
self.session.query(DuplicationSegmentModel).filter(DuplicationSegmentModel.record_id == record.id).delete()
for seg in record.segments:
seg_model = DuplicationSegmentModel(
id=seg.id,
@@ -87,20 +82,14 @@ class SQLAlchemyDuplicationRecordRepository:
return record
def delete(self, record_id: str) -> bool:
count = self.session.query(DuplicationRecordModel).filter(
DuplicationRecordModel.id == record_id
).delete()
self.session.query(DuplicationSegmentModel).filter(
DuplicationSegmentModel.record_id == record_id
).delete()
count = self.session.query(DuplicationRecordModel).filter(DuplicationRecordModel.id == record_id).delete()
self.session.query(DuplicationSegmentModel).filter(DuplicationSegmentModel.record_id == record_id).delete()
self.session.commit()
return count > 0
def _to_domain(self, model: DuplicationRecordModel) -> DuplicationRecord:
segment_models = (
self.session.query(DuplicationSegmentModel)
.filter(DuplicationSegmentModel.record_id == model.id)
.all()
self.session.query(DuplicationSegmentModel).filter(DuplicationSegmentModel.record_id == model.id).all()
)
segments = [
DuplicateSegment(
@@ -80,11 +80,7 @@ class SQLAlchemyGenerationTaskRepository:
return [_to_domain(m) for m in models]
def count_by_user(self, user_id: str) -> int:
return (
self.session.query(GenerationTaskModel)
.filter(GenerationTaskModel.created_by_user_id == user_id)
.count()
)
return self.session.query(GenerationTaskModel).filter(GenerationTaskModel.created_by_user_id == user_id).count()
def list_recent_by_user(self, user_id: str, limit: int = 5) -> list[GenerationTask]:
models = (
+12 -18
View File
@@ -39,7 +39,7 @@ class ProjectModel(Base):
name = Column(String(100), nullable=False)
description = Column(Text, nullable=False, default="")
shared_users = Column(JSON, nullable=False, default=list) # 被共享的用户 ID 列表
extra_meta = Column('metadata', JSON, nullable=False, default=dict)
extra_meta = Column("metadata", JSON, nullable=False, default=dict)
created_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc))
@@ -52,7 +52,7 @@ class AssetLibraryModel(Base):
kind = Column(String(20), nullable=False, index=True)
asset_count = Column(Float, nullable=False, default=0)
total_size = Column(Float, nullable=False, default=0)
extra_meta = Column('metadata', JSON, nullable=False, default=dict)
extra_meta = Column("metadata", JSON, nullable=False, default=dict)
created_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc))
updated_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc))
@@ -80,12 +80,11 @@ class AssetModel(Base):
classification_result = Column(Text, nullable=True)
quality_score = Column(Float, nullable=True)
uploaded_by_user_id = Column(String(36), nullable=False)
extra_meta = Column('metadata', JSON, nullable=False, default=dict)
extra_meta = Column("metadata", JSON, nullable=False, default=dict)
created_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc), index=True)
updated_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc))
class EditTemplateModel(Base):
__tablename__ = "edit_templates"
@@ -97,14 +96,11 @@ class EditTemplateModel(Base):
clip_count = Column(Integer, nullable=False, default=3)
is_active = Column(Boolean, nullable=False, default=True)
created_by_user_id = Column(String(32), nullable=False, default="")
extra_meta = Column('metadata', JSON, nullable=False, default=dict)
extra_meta = Column("metadata", JSON, nullable=False, default=dict)
created_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc))
updated_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc))
class IngestJobModel(Base):
__tablename__ = "ingest_jobs"
@@ -145,7 +141,9 @@ class GenerationTaskModel(Base):
asset_ids = Column(JSON, nullable=False, default=list)
title_ids = Column(JSON, nullable=False, default=list)
voice_ids = Column(JSON, nullable=False, default=list)
editing_mode = Column(String(20), nullable=False, default="one_take", index=True) # 剪辑模式: one_take, pip, voice_over, voice_pip
editing_mode = Column(
String(20), nullable=False, default="one_take", index=True
) # 剪辑模式: one_take, pip, voice_over, voice_pip
status = Column(String(20), nullable=False, default="pending", index=True)
progress = Column(Float, nullable=False, default=0.0)
result_count = Column(Float, nullable=False, default=0)
@@ -153,7 +151,7 @@ class GenerationTaskModel(Base):
started_at = Column(DateTime, nullable=True)
completed_at = Column(DateTime, nullable=True)
created_by_user_id = Column(String(32), nullable=False, default="", index=True)
extra_meta = Column('metadata', JSON, nullable=False, default=dict)
extra_meta = Column("metadata", JSON, nullable=False, default=dict)
created_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc))
@@ -184,9 +182,6 @@ class GeneratedVideoModel(Base):
duplicate_of = Column(String(32), nullable=True)
class TitleLibraryModel(Base):
__tablename__ = "title_libraries"
@@ -199,7 +194,7 @@ class TitleLibraryModel(Base):
tags = Column(JSON, nullable=False, default=list)
usage_count = Column(Integer, nullable=False, default=0)
is_active = Column(Boolean, nullable=False, default=True, index=True)
extra_meta = Column('metadata', JSON, nullable=False, default=dict)
extra_meta = Column("metadata", JSON, nullable=False, default=dict)
created_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc))
updated_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc))
@@ -220,7 +215,7 @@ class VoiceLibraryModel(Base):
file_size = Column(Integer, nullable=False, default=0)
status = Column(String(20), nullable=False, default="completed", index=True)
tags = Column(JSON, nullable=False, default=list)
extra_meta = Column('metadata', JSON, nullable=False, default=dict)
extra_meta = Column("metadata", JSON, nullable=False, default=dict)
created_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc))
updated_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc))
@@ -267,7 +262,7 @@ class RecipeModel(Base):
template_id = Column(String(36), nullable=False, default="")
generation_params = Column(JSON, nullable=False, default=dict)
is_active = Column(Boolean, nullable=False, default=True)
extra_meta = Column('metadata', JSON, nullable=False, default=dict)
extra_meta = Column("metadata", JSON, nullable=False, default=dict)
created_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc))
updated_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc))
@@ -280,7 +275,7 @@ class RecipeItemModel(Base):
item_type = Column(String(20), nullable=False)
item_id = Column(String(36), nullable=False)
position = Column(Integer, nullable=False, default=0)
extra_meta = Column('metadata', JSON, nullable=False, default=dict)
extra_meta = Column("metadata", JSON, nullable=False, default=dict)
class TemplateModel(Base):
@@ -321,4 +316,3 @@ class TemplateCategoryModel(Base):
user_id = Column(String(36), nullable=False, index=True)
name = Column(String(100), nullable=False)
created_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc))
@@ -47,9 +47,7 @@ class SQLAlchemyProjectRepository:
def find_by_owner_user_id(self, owner_user_id: str) -> list[Project]:
"""根据所有者用户 ID 查找项目"""
models = self.session.query(ProjectModel).filter(
ProjectModel.owner_user_id == owner_user_id
).all()
models = self.session.query(ProjectModel).filter(ProjectModel.owner_user_id == owner_user_id).all()
return [self._to_entity(model) for model in models]
def find_accessible_projects(self, user_id: str) -> list[Project]:
@@ -57,19 +55,18 @@ class SQLAlchemyProjectRepository:
from sqlalchemy import or_, cast
from sqlalchemy.dialects.postgresql import JSONB
models = self.session.query(ProjectModel).filter(
or_(
ProjectModel.owner_user_id == user_id,
cast(ProjectModel.shared_users, JSONB).contains([user_id])
models = (
self.session.query(ProjectModel)
.filter(
or_(ProjectModel.owner_user_id == user_id, cast(ProjectModel.shared_users, JSONB).contains([user_id]))
)
).all()
.all()
)
return [self._to_entity(model) for model in models]
def count_by_owner(self, owner_user_id: str) -> int:
"""统计用户的项目数量"""
return self.session.query(ProjectModel).filter(
ProjectModel.owner_user_id == owner_user_id
).count()
return self.session.query(ProjectModel).filter(ProjectModel.owner_user_id == owner_user_id).count()
def delete(self, project_id: str) -> bool:
"""删除项目"""
@@ -1,4 +1,5 @@
"""SQLAlchemy implementation of RecipeRepository."""
from __future__ import annotations
from typing import List, Optional
@@ -144,11 +145,7 @@ class SQLAlchemyRecipeRepository:
return items
def delete_items_by_recipe(self, recipe_id: str) -> int:
count = (
self.session.query(RecipeItemModel)
.filter(RecipeItemModel.recipe_id == recipe_id)
.delete()
)
count = self.session.query(RecipeItemModel).filter(RecipeItemModel.recipe_id == recipe_id).delete()
self.session.commit()
return count
@@ -1,4 +1,5 @@
"""SQLAlchemy implementation of TemplateRepository."""
from __future__ import annotations
from typing import List, Optional
@@ -178,9 +179,7 @@ class SQLAlchemyTemplateRepository:
def delete_segments_by_template(self, template_id: str) -> int:
count = (
self.session.query(TemplateSegmentModel)
.filter(TemplateSegmentModel.template_id == template_id)
.delete()
self.session.query(TemplateSegmentModel).filter(TemplateSegmentModel.template_id == template_id).delete()
)
self.session.commit()
return count
@@ -1,4 +1,5 @@
"""SQLAlchemy implementation of TitleLibraryRepository."""
from __future__ import annotations
from typing import List, Optional
@@ -35,10 +36,14 @@ class SQLAlchemyTitleLibraryRepository:
return [self._model_to_entity(m) for m in models]
def get(self, title_id: str, user_id: str) -> Optional[TitleLibraryItem]:
model = self.session.query(TitleLibraryModel).filter(
TitleLibraryModel.id == title_id,
TitleLibraryModel.user_id == user_id,
).first()
model = (
self.session.query(TitleLibraryModel)
.filter(
TitleLibraryModel.id == title_id,
TitleLibraryModel.user_id == user_id,
)
.first()
)
if model is None:
return None
return self._model_to_entity(model)
@@ -62,10 +67,14 @@ class SQLAlchemyTitleLibraryRepository:
return self._model_to_entity(model)
def update(self, item: TitleLibraryItem) -> TitleLibraryItem:
model = self.session.query(TitleLibraryModel).filter(
TitleLibraryModel.id == item.id,
TitleLibraryModel.user_id == item.user_id,
).first()
model = (
self.session.query(TitleLibraryModel)
.filter(
TitleLibraryModel.id == item.id,
TitleLibraryModel.user_id == item.user_id,
)
.first()
)
if model is None:
raise ValueError(f"TitleLibraryItem {item.id} not found")
model.name = item.name
@@ -80,10 +89,14 @@ class SQLAlchemyTitleLibraryRepository:
return self._model_to_entity(model)
def delete(self, title_id: str, user_id: str) -> bool:
model = self.session.query(TitleLibraryModel).filter(
TitleLibraryModel.id == title_id,
TitleLibraryModel.user_id == user_id,
).first()
model = (
self.session.query(TitleLibraryModel)
.filter(
TitleLibraryModel.id == title_id,
TitleLibraryModel.user_id == user_id,
)
.first()
)
if model is None:
return False
model.is_active = False
@@ -91,10 +104,14 @@ class SQLAlchemyTitleLibraryRepository:
return True
def count_by_user(self, user_id: str, is_active: bool = True) -> int:
return self.session.query(TitleLibraryModel).filter(
TitleLibraryModel.user_id == user_id,
TitleLibraryModel.is_active == is_active,
).count()
return (
self.session.query(TitleLibraryModel)
.filter(
TitleLibraryModel.user_id == user_id,
TitleLibraryModel.is_active == is_active,
)
.count()
)
@staticmethod
def _model_to_entity(model: TitleLibraryModel) -> TitleLibraryItem:
@@ -1,4 +1,5 @@
"""SQLAlchemy implementation of VoiceLibraryRepository."""
from __future__ import annotations
from typing import List, Optional
@@ -33,10 +34,14 @@ class SQLAlchemyVoiceLibraryRepository:
return [self._model_to_entity(m) for m in models]
def get(self, voice_id: str, user_id: str) -> Optional[VoiceLibraryItem]:
model = self.session.query(VoiceLibraryModel).filter(
VoiceLibraryModel.id == voice_id,
VoiceLibraryModel.user_id == user_id,
).first()
model = (
self.session.query(VoiceLibraryModel)
.filter(
VoiceLibraryModel.id == voice_id,
VoiceLibraryModel.user_id == user_id,
)
.first()
)
if model is None:
return None
return self._model_to_entity(model)
@@ -64,10 +69,14 @@ class SQLAlchemyVoiceLibraryRepository:
return self._model_to_entity(model)
def update(self, item: VoiceLibraryItem) -> VoiceLibraryItem:
model = self.session.query(VoiceLibraryModel).filter(
VoiceLibraryModel.id == item.id,
VoiceLibraryModel.user_id == item.user_id,
).first()
model = (
self.session.query(VoiceLibraryModel)
.filter(
VoiceLibraryModel.id == item.id,
VoiceLibraryModel.user_id == item.user_id,
)
.first()
)
if model is None:
raise ValueError(f"VoiceLibraryItem {item.id} not found")
model.name = item.name
@@ -86,10 +95,14 @@ class SQLAlchemyVoiceLibraryRepository:
return self._model_to_entity(model)
def delete(self, voice_id: str, user_id: str) -> bool:
model = self.session.query(VoiceLibraryModel).filter(
VoiceLibraryModel.id == voice_id,
VoiceLibraryModel.user_id == user_id,
).first()
model = (
self.session.query(VoiceLibraryModel)
.filter(
VoiceLibraryModel.id == voice_id,
VoiceLibraryModel.user_id == user_id,
)
.first()
)
if model is None:
return False
# Soft delete by setting status to deleted
@@ -98,10 +111,14 @@ class SQLAlchemyVoiceLibraryRepository:
return True
def count_by_user(self, user_id: str) -> int:
return self.session.query(VoiceLibraryModel).filter(
VoiceLibraryModel.user_id == user_id,
VoiceLibraryModel.status != "deleted",
).count()
return (
self.session.query(VoiceLibraryModel)
.filter(
VoiceLibraryModel.user_id == user_id,
VoiceLibraryModel.status != "deleted",
)
.count()
)
@staticmethod
def _model_to_entity(model: VoiceLibraryModel) -> VoiceLibraryItem: