"""SQLAlchemy implementation of VoiceCloneProfileRepository.""" from __future__ import annotations from typing import Dict, List, Optional from sqlalchemy.orm import Session from packages.adapters.sqlalchemy_impl.models import VoiceCloneProfileModel from packages.domain.voice_clone_profile import VoiceCloneProfile, VoiceCloneStatus class SQLAlchemyVoiceCloneProfileRepository: """SQLAlchemy 音色克隆档案仓储。""" def __init__(self, session: Session) -> None: self.session = session def create(self, profile: VoiceCloneProfile) -> VoiceCloneProfile: model = VoiceCloneProfileModel( id=profile.id, user_id=profile.user_id, name=profile.name, description=profile.description, source_audio_url=profile.source_audio_url, voice_id=profile.voice_id, voice_model=profile.voice_model, language=profile.language, gender=profile.gender, status=profile.status, error_message=profile.error_message, retry_count=profile.retry_count, max_retries=profile.max_retries, metadata_=profile.metadata, ) self.session.add(model) self.session.commit() self.session.refresh(model) return self._model_to_entity(model) def get(self, profile_id: str) -> Optional[VoiceCloneProfile]: model = ( self.session.query(VoiceCloneProfileModel) .filter( VoiceCloneProfileModel.id == profile_id, VoiceCloneProfileModel.status != "deleted", ) .first() ) if model is None: return None return self._model_to_entity(model) def update(self, profile: VoiceCloneProfile) -> VoiceCloneProfile: model = self.session.query(VoiceCloneProfileModel).filter(VoiceCloneProfileModel.id == profile.id).first() if model is None: raise ValueError(f"VoiceCloneProfile {profile.id} not found") model.name = profile.name model.description = profile.description model.source_audio_url = profile.source_audio_url model.voice_id = profile.voice_id model.voice_model = profile.voice_model model.language = profile.language model.gender = profile.gender model.status = profile.status model.error_message = profile.error_message model.retry_count = profile.retry_count model.max_retries = profile.max_retries model.metadata_ = profile.metadata self.session.commit() self.session.refresh(model) return self._model_to_entity(model) def delete(self, profile_id: str) -> bool: model = self.session.query(VoiceCloneProfileModel).filter(VoiceCloneProfileModel.id == profile_id).first() if model is None: return False model.status = "deleted" self.session.commit() return True def list_by_user( self, user_id: str, *, status: Optional[str] = None, limit: int = 50, offset: int = 0, ) -> List[VoiceCloneProfile]: query = self.session.query(VoiceCloneProfileModel).filter( VoiceCloneProfileModel.user_id == user_id, VoiceCloneProfileModel.status != "deleted", ) if status: query = query.filter(VoiceCloneProfileModel.status == status) query = query.order_by(VoiceCloneProfileModel.created_at.desc()) models = query.offset(offset).limit(limit).all() return [self._model_to_entity(m) for m in models] def count_by_user(self, user_id: str, *, status: Optional[str] = None) -> int: query = self.session.query(VoiceCloneProfileModel).filter( VoiceCloneProfileModel.user_id == user_id, VoiceCloneProfileModel.status != "deleted", ) if status: query = query.filter(VoiceCloneProfileModel.status == status) return query.count() def find_by_voice_id(self, voice_id: str) -> Optional[VoiceCloneProfile]: model = ( self.session.query(VoiceCloneProfileModel) .filter( VoiceCloneProfileModel.voice_id == voice_id, VoiceCloneProfileModel.status != "deleted", ) .first() ) if model is None: return None return self._model_to_entity(model) def find_profile_ids_by_voice_ids(self, voice_ids: List[str]) -> Dict[str, str]: """批量查询 voice_id → profile_id 映射。用于填充统一列表的 voice_clone_profile_id。""" if not voice_ids: return {} rows = ( self.session.query( VoiceCloneProfileModel.voice_id, VoiceCloneProfileModel.id, ) .filter( VoiceCloneProfileModel.voice_id.in_(voice_ids), VoiceCloneProfileModel.status != "deleted", ) .all() ) return {voice_id: profile_id for voice_id, profile_id in rows} @staticmethod def _model_to_entity(model: VoiceCloneProfileModel) -> VoiceCloneProfile: return VoiceCloneProfile( id=model.id, user_id=model.user_id, name=model.name, description=model.description or "", source_audio_url=model.source_audio_url or "", voice_id=model.voice_id or "", voice_model=model.voice_model or "", language=model.language or "zh-CN", gender=model.gender or "unknown", status=VoiceCloneStatus(model.status) if model.status else VoiceCloneStatus.PENDING, error_message=model.error_message or "", retry_count=model.retry_count or 0, max_retries=model.max_retries or 3, metadata=model.metadata_ or {}, created_at=model.created_at, updated_at=model.updated_at, )