Add the experience service with sites through analytics/AI hooks, migrations through 0011, and phase docs/manifests marking the track complete. Co-authored-by: Cursor <cursoragent@cursor.com>
169 lines
6.0 KiB
Python
169 lines
6.0 KiB
Python
"""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)
|