9c0474ef9c
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 1s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 3s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 3s
CI/CD Pipeline / Check push changed paths (push) Successful in 7s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 41s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 42s
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Successful in 1m0s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 34s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 2m32s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m50s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 2m53s
CI/CD Pipeline / Integration Tests (push) Successful in 2m56s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 2m43s
CI/CD Pipeline / CI Gate (pull_request) Successful in 1s
CI/CD Pipeline / Validate - Style (push) Successful in 3m19s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 50s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 4m7s
CI/CD Pipeline / Validate - Security (push) Successful in 5m45s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 1m54s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 6m6s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 2m13s
AI Code Review / AI Code Review (pull_request) Successful in 6m38s
CI/CD Pipeline / Unit Tests (push) Failing after 7m58s
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
CI/CD Pipeline / Canary Release to Production (push) Has been cancelled
CI/CD Pipeline / Deploy Production (push) Failing after 198h32m29s
CI/CD Pipeline / CI Gate (push) Failing after 198h32m31s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 198h32m31s
CI/CD Pipeline / Build Production Web Image (push) Failing after 198h32m31s
CI/CD Pipeline / Build Production API Image (push) Failing after 198h32m31s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Failing after 198h36m16s
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Failing after 198h37m22s
CI/CD Pipeline / Retag skipped Staging Web Image (push) Failing after 198h37m23s
CI/CD Pipeline / Canary Release to Production (pull_request) Failing after 198h39m3s
CI/CD Pipeline / Retag skipped Staging API Image (push) Failing after 198h37m23s
CI/CD Pipeline / ACR Image Cleanup (pull_request) Failing after 198h39m11s
CI/CD Pipeline / Staging API Integration Tests (pull_request) Failing after 198h39m14s
CI/CD Pipeline / Staging E2E Tests (pull_request) Failing after 198h39m20s
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Failing after 198h39m40s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Failing after 198h39m27s
CI/CD Pipeline / Deploy Production (pull_request) Failing after 198h39m33s
CI/CD Pipeline / Build Production Worker Image (pull_request) Failing after 198h39m42s
CI/CD Pipeline / Build Production API Image (pull_request) Failing after 198h39m45s
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Failing after 198h39m39s
CI/CD Pipeline / Build Staging Worker Image (pull_request) Failing after 198h40m27s
CI/CD Pipeline / Build Staging API Image (pull_request) Failing after 198h40m28s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Failing after 198h40m29s
CI/CD Pipeline / Integration Tests (pull_request) Failing after 198h40m29s
CI/CD Pipeline / Unit Tests (pull_request) Failing after 198h40m29s
CI/CD Pipeline / Validate - Security (pull_request) Failing after 198h40m30s
CI/CD Pipeline / Validate - Style (pull_request) Failing after 198h40m30s
CI/CD Pipeline / Frontend Lint (push) Failing after 198h40m30s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 198h40m37s
CI/CD Pipeline / PR Build API Image (push) Failing after 198h40m37s
CI/CD Pipeline / Check push changed paths (pull_request) Failing after 198h40m37s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 198h40m39s
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Failing after 199h14m21s
CI/CD Pipeline / Build Production Web Image (pull_request) Failing after 199h14m26s
CI/CD Pipeline / Build Staging Web Image (pull_request) Failing after 199h15m10s
CI/CD Pipeline / Frontend Lint (pull_request) Failing after 199h15m11s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Failing after 199h15m11s
CI/CD Pipeline / PR Build Web Image (push) Failing after 199h15m19s
Squash merge PR #1783 into develop
165 lines
6.1 KiB
Python
165 lines
6.1 KiB
Python
from sqlalchemy.orm import Session
|
||
|
||
from packages.adapters.sqlalchemy_impl.models import AssetLibraryModel
|
||
from packages.domain import AssetLibrary, AssetLibraryKind
|
||
|
||
|
||
class SQLAlchemyAssetLibraryRepository:
|
||
def __init__(self, session: Session):
|
||
self.session = session
|
||
|
||
def get(self, library_id: str) -> AssetLibrary | None:
|
||
model = self.session.query(AssetLibraryModel).filter(AssetLibraryModel.id == library_id).first()
|
||
if model is None:
|
||
return None
|
||
return AssetLibrary(
|
||
id=model.id,
|
||
project_id=model.project_id,
|
||
name=model.name,
|
||
kind=AssetLibraryKind(model.kind),
|
||
asset_count=int(model.asset_count or 0),
|
||
total_size=int(model.total_size or 0),
|
||
created_at=model.created_at,
|
||
updated_at=model.updated_at,
|
||
)
|
||
|
||
def find_by_id(self, library_id: str) -> AssetLibrary | None:
|
||
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()
|
||
return [
|
||
AssetLibrary(
|
||
id=model.id,
|
||
project_id=model.project_id,
|
||
name=model.name,
|
||
kind=AssetLibraryKind(model.kind),
|
||
asset_count=int(model.asset_count or 0),
|
||
total_size=int(model.total_size or 0),
|
||
created_at=model.created_at,
|
||
updated_at=model.updated_at,
|
||
)
|
||
for model in models
|
||
]
|
||
|
||
def create(self, library: AssetLibrary) -> AssetLibrary:
|
||
model = AssetLibraryModel(
|
||
id=library.id,
|
||
project_id=library.project_id,
|
||
name=library.name,
|
||
kind=library.kind.value,
|
||
asset_count=library.asset_count,
|
||
total_size=library.total_size,
|
||
created_at=library.created_at,
|
||
updated_at=library.updated_at,
|
||
)
|
||
self.session.add(model)
|
||
self.session.commit()
|
||
return library
|
||
|
||
def update(self, library: AssetLibrary) -> AssetLibrary:
|
||
model = self.session.query(AssetLibraryModel).filter(AssetLibraryModel.id == library.id).first()
|
||
if model:
|
||
model.project_id = library.project_id
|
||
model.name = library.name
|
||
model.kind = library.kind.value
|
||
model.asset_count = library.asset_count
|
||
model.total_size = library.total_size
|
||
model.updated_at = library.updated_at
|
||
self.session.commit()
|
||
return library
|
||
|
||
def delete(self, library_id: str) -> bool:
|
||
model = self.session.query(AssetLibraryModel).filter(AssetLibraryModel.id == library_id).first()
|
||
if model:
|
||
self.session.delete(model)
|
||
self.session.commit()
|
||
return True
|
||
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()
|
||
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()
|
||
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)
|
||
self.session.commit()
|
||
|
||
def get_or_create_default_library(
|
||
self,
|
||
project_id: str,
|
||
kind: AssetLibraryKind,
|
||
*,
|
||
name: str | None = None,
|
||
) -> AssetLibrary:
|
||
"""幂等获取/创建项目下指定 kind 的默认素材库(Issue #1775)。
|
||
|
||
依赖唯一约束 uq_asset_libraries_project_kind(project_id, kind):
|
||
并发创建只有一个成功,其余 IntegrityError 后回滚重查,
|
||
保证同一项目同 kind 永远只有一个素材库。
|
||
"""
|
||
from sqlalchemy.exc import IntegrityError
|
||
|
||
default_names = {
|
||
AssetLibraryKind.VIDEO: "视频素材库",
|
||
AssetLibraryKind.VOICE: "配音素材库",
|
||
AssetLibraryKind.IMAGE: "图片素材库",
|
||
}
|
||
library_name = name or default_names.get(kind, f"{kind.value}素材库")
|
||
|
||
# 快速路径
|
||
existing = (
|
||
self.session.query(AssetLibraryModel)
|
||
.filter(AssetLibraryModel.project_id == project_id, AssetLibraryModel.kind == kind.value)
|
||
.first()
|
||
)
|
||
if existing:
|
||
return self._to_entity(existing)
|
||
|
||
library = AssetLibrary.create(project_id=project_id, name=library_name, kind=kind)
|
||
model = AssetLibraryModel(
|
||
id=library.id,
|
||
project_id=library.project_id,
|
||
name=library.name,
|
||
kind=library.kind.value,
|
||
asset_count=0,
|
||
total_size=0,
|
||
created_at=library.created_at,
|
||
updated_at=library.updated_at,
|
||
)
|
||
try:
|
||
self.session.add(model)
|
||
self.session.commit()
|
||
return library
|
||
except IntegrityError:
|
||
self.session.rollback()
|
||
existing = (
|
||
self.session.query(AssetLibraryModel)
|
||
.filter(
|
||
AssetLibraryModel.project_id == project_id,
|
||
AssetLibraryModel.kind == kind.value,
|
||
)
|
||
.first()
|
||
)
|
||
if existing:
|
||
return self._to_entity(existing)
|
||
raise
|
||
|
||
def _to_entity(self, model: AssetLibraryModel) -> AssetLibrary:
|
||
return AssetLibrary(
|
||
id=model.id,
|
||
project_id=model.project_id,
|
||
name=model.name,
|
||
kind=AssetLibraryKind(model.kind),
|
||
asset_count=int(model.asset_count or 0),
|
||
total_size=int(model.total_size or 0),
|
||
created_at=model.created_at,
|
||
updated_at=model.updated_at,
|
||
)
|