"""Filtering, search and sorting specifications — Phase 11.8.""" from dataclasses import dataclass from uuid import UUID from sqlalchemy import asc, desc, or_ from app.models.bundles import ( ExperienceBundleDefinition, ExperienceFeatureToggle, ExperienceLicenseBinding, ExperienceTenantBundle, ) BUNDLE_DEFINITION_SORT = frozenset({"created_at", "updated_at", "code", "name", "status", "bundle_key"}) TENANT_BUNDLE_SORT = frozenset({"created_at", "updated_at", "status", "bundle_key", "expires_at"}) LICENSE_BINDING_SORT = frozenset({"created_at", "updated_at", "status", "starts_at", "ends_at"}) FEATURE_TOGGLE_SORT = frozenset({"created_at", "updated_at", "feature_key", "status", "enabled"}) @dataclass(frozen=True, slots=True) class BundleListSpec: workspace_id: UUID | None = None site_id: UUID | None = None status: str | None = None bundle_key: str | None = None q: str | None = None sort_by: str = "created_at" sort_dir: str = "desc" model = None sort_fields = frozenset() def filter_clauses(self): m = self.model clauses = [] for field in ("workspace_id", "site_id", "status", "bundle_key"): value = getattr(self, field) if value is not None and hasattr(m, field): clauses.append(getattr(m, field) == value) if self.q: term = f"%{self.q.strip()}%" searchable = [ getattr(m, name).ilike(term) for name in ("code", "name", "description", "feature_key", "core_entitlement_ref") if hasattr(m, name) ] if searchable: clauses.append(or_(*searchable)) return clauses def apply(self, stmt): clauses = self.filter_clauses() if clauses: stmt = stmt.where(*clauses) key = self.sort_by if self.sort_by in self.sort_fields else "created_at" column = getattr(self.model, key) return stmt.order_by(asc(column) if self.sort_dir.lower() == "asc" else desc(column)) @dataclass(frozen=True, slots=True) class BundleDefinitionListSpec(BundleListSpec): model = ExperienceBundleDefinition sort_fields = BUNDLE_DEFINITION_SORT @dataclass(frozen=True, slots=True) class TenantBundleListSpec(BundleListSpec): model = ExperienceTenantBundle sort_fields = TENANT_BUNDLE_SORT @dataclass(frozen=True, slots=True) class LicenseBindingListSpec(BundleListSpec): model = ExperienceLicenseBinding sort_fields = LICENSE_BINDING_SORT @dataclass(frozen=True, slots=True) class FeatureToggleListSpec(BundleListSpec): model = ExperienceFeatureToggle sort_fields = FEATURE_TOGGLE_SORT