"""Template query specifications — Phase 11.4.""" from __future__ import annotations from dataclasses import dataclass from uuid import UUID from sqlalchemy import Select, asc, desc, or_ from sqlalchemy.sql import ColumnElement from app.models.templates import ( ExperienceTemplate, ExperienceTemplateVersion, TemplateInstantiation, ) from app.models.types import ( TemplateInstantiationStatus, TemplateStatus, TemplateVersionStatus, ) TEMPLATE_SORT_FIELDS = frozenset( { "created_at", "updated_at", "code", "name", "status", "page_type", "category", } ) TEMPLATE_VERSION_SORT_FIELDS = frozenset( {"created_at", "updated_at", "version_number", "status", "published_at"} ) INSTANTIATION_SORT_FIELDS = frozenset( {"created_at", "updated_at", "completed_at", "status"} ) @dataclass(frozen=True, slots=True) class TemplateListSpec: workspace_id: UUID | None = None status: TemplateStatus | str | None = None page_type: str | None = None is_starter_pack: bool | None = None category: str | None = None q: str | None = None sort_by: str = "created_at" sort_dir: str = "desc" def filter_clauses(self) -> list[ColumnElement]: clauses: list[ColumnElement] = [] if self.workspace_id is not None: clauses.append(ExperienceTemplate.workspace_id == self.workspace_id) if self.status is not None: status = ( self.status if isinstance(self.status, TemplateStatus) else TemplateStatus(str(self.status)) ) clauses.append(ExperienceTemplate.status == status) if self.page_type: clauses.append(ExperienceTemplate.page_type == self.page_type) if self.is_starter_pack is not None: clauses.append(ExperienceTemplate.is_starter_pack.is_(self.is_starter_pack)) if self.category: clauses.append(ExperienceTemplate.category == self.category) if self.q: term = f"%{self.q.strip()}%" clauses.append( or_( ExperienceTemplate.code.ilike(term), ExperienceTemplate.name.ilike(term), ExperienceTemplate.description.ilike(term), ExperienceTemplate.category.ilike(term), ) ) return clauses def apply(self, stmt: Select) -> Select: clauses = self.filter_clauses() if clauses: stmt = stmt.where(*clauses) sort_key = ( self.sort_by if self.sort_by in TEMPLATE_SORT_FIELDS else "created_at" ) column = getattr(ExperienceTemplate, sort_key) order = desc(column) if self.sort_dir.lower() != "asc" else asc(column) return stmt.order_by(order) @dataclass(frozen=True, slots=True) class TemplateVersionListSpec: template_id: UUID | None = None status: TemplateVersionStatus | str | None = None q: str | None = None sort_by: str = "version_number" sort_dir: str = "desc" def filter_clauses(self) -> list[ColumnElement]: clauses: list[ColumnElement] = [] if self.template_id is not None: clauses.append(ExperienceTemplateVersion.template_id == self.template_id) if self.status is not None: status = ( self.status if isinstance(self.status, TemplateVersionStatus) else TemplateVersionStatus(str(self.status)) ) clauses.append(ExperienceTemplateVersion.status == status) if self.q: term = f"%{self.q.strip()}%" clauses.append(ExperienceTemplateVersion.changelog.ilike(term)) return clauses def apply(self, stmt: Select) -> Select: clauses = self.filter_clauses() if clauses: stmt = stmt.where(*clauses) sort_key = ( self.sort_by if self.sort_by in TEMPLATE_VERSION_SORT_FIELDS else "version_number" ) column = getattr(ExperienceTemplateVersion, sort_key) order = desc(column) if self.sort_dir.lower() != "asc" else asc(column) return stmt.order_by(order) @dataclass(frozen=True, slots=True) class InstantiationListSpec: template_id: UUID | None = None site_id: UUID | None = None page_id: UUID | None = None status: TemplateInstantiationStatus | str | None = None workspace_id: UUID | None = None sort_by: str = "created_at" sort_dir: str = "desc" def filter_clauses(self) -> list[ColumnElement]: clauses: list[ColumnElement] = [] if self.template_id is not None: clauses.append(TemplateInstantiation.template_id == self.template_id) if self.site_id is not None: clauses.append(TemplateInstantiation.site_id == self.site_id) if self.page_id is not None: clauses.append(TemplateInstantiation.page_id == self.page_id) if self.workspace_id is not None: clauses.append(TemplateInstantiation.workspace_id == self.workspace_id) if self.status is not None: status = ( self.status if isinstance(self.status, TemplateInstantiationStatus) else TemplateInstantiationStatus(str(self.status)) ) clauses.append(TemplateInstantiation.status == status) return clauses def apply(self, stmt: Select) -> Select: clauses = self.filter_clauses() if clauses: stmt = stmt.where(*clauses) sort_key = ( self.sort_by if self.sort_by in INSTANTIATION_SORT_FIELDS else "created_at" ) column = getattr(TemplateInstantiation, sort_key) order = desc(column) if self.sort_dir.lower() != "asc" else asc(column) return stmt.order_by(order)