"""Template repositories — Phase 11.4.""" from __future__ import annotations from typing import Sequence from uuid import UUID from sqlalchemy import func, select from app.models.templates import ( ExperienceTemplate, ExperienceTemplateVersion, TemplateInstantiation, ) from app.repositories.base import TenantBaseRepository from app.specifications.templates import ( InstantiationListSpec, TemplateListSpec, TemplateVersionListSpec, ) class ExperienceTemplateRepository(TenantBaseRepository[ExperienceTemplate]): model = ExperienceTemplate async def get_by_code( self, tenant_id: UUID, workspace_id: UUID, code: str ) -> ExperienceTemplate | None: stmt = select(self.model).where( self.model.tenant_id == tenant_id, self.model.workspace_id == workspace_id, self.model.code == code, self.model.is_deleted.is_(False), ) result = await self.session.execute(stmt) return result.scalar_one_or_none() async def list_filtered( self, tenant_id: UUID, *, offset: int, limit: int, spec: TemplateListSpec | None = None, ) -> tuple[Sequence[ExperienceTemplate], int]: base = select(self.model).where( self.model.tenant_id == tenant_id, self.model.is_deleted.is_(False), ) count_base = select(func.count()).select_from(self.model).where( self.model.tenant_id == tenant_id, self.model.is_deleted.is_(False), ) if spec is not None: base = spec.apply(base) for clause in spec.filter_clauses(): count_base = count_base.where(clause) else: base = base.order_by(self.model.created_at.desc()) total = int((await self.session.execute(count_base)).scalar_one()) result = await self.session.execute(base.offset(offset).limit(limit)) return result.scalars().all(), total class ExperienceTemplateVersionRepository( TenantBaseRepository[ExperienceTemplateVersion] ): model = ExperienceTemplateVersion async def max_version_number(self, tenant_id: UUID, template_id: UUID) -> int: stmt = select(func.max(self.model.version_number)).where( self.model.tenant_id == tenant_id, self.model.template_id == template_id, self.model.is_deleted.is_(False), ) result = await self.session.execute(stmt) return int(result.scalar_one() or 0) async def list_filtered( self, tenant_id: UUID, *, offset: int, limit: int, spec: TemplateVersionListSpec | None = None, ) -> tuple[Sequence[ExperienceTemplateVersion], int]: base = select(self.model).where( self.model.tenant_id == tenant_id, self.model.is_deleted.is_(False), ) count_base = select(func.count()).select_from(self.model).where( self.model.tenant_id == tenant_id, self.model.is_deleted.is_(False), ) if spec is not None: base = spec.apply(base) for clause in spec.filter_clauses(): count_base = count_base.where(clause) else: base = base.order_by(self.model.version_number.desc()) total = int((await self.session.execute(count_base)).scalar_one()) result = await self.session.execute(base.offset(offset).limit(limit)) return result.scalars().all(), total class TemplateInstantiationRepository(TenantBaseRepository[TemplateInstantiation]): model = TemplateInstantiation async def list_filtered( self, tenant_id: UUID, *, offset: int, limit: int, spec: InstantiationListSpec | None = None, ) -> tuple[Sequence[TemplateInstantiation], int]: base = select(self.model).where( self.model.tenant_id == tenant_id, self.model.is_deleted.is_(False), ) count_base = select(func.count()).select_from(self.model).where( self.model.tenant_id == tenant_id, self.model.is_deleted.is_(False), ) if spec is not None: base = spec.apply(base) for clause in spec.filter_clauses(): count_base = count_base.where(clause) else: base = base.order_by(self.model.created_at.desc()) total = int((await self.session.execute(count_base)).scalar_one()) result = await self.session.execute(base.offset(offset).limit(limit)) return result.scalars().all(), total