TorbatYar/backend/services/experience/app/policies/components.py
Mortezakoohjani 203671a7bf feat(experience): ship Experience Platform phases 11.0-11.10
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>
2026-07-27 11:43:10 +03:30

86 lines
3.0 KiB
Python

"""Component domain policies — Phase 11.2."""
from __future__ import annotations
from typing import Any
from app.models.types import (
ComponentStatus,
ComponentVersionStatus,
)
from app.validators.components import (
ensure_component_status_transition,
ensure_version_mutable,
ensure_version_status_transition,
)
from shared.exceptions import AppError
class ComponentLifecyclePolicy:
"""Enforces component catalog status transitions."""
def assert_status_change(
self, *, current: ComponentStatus, target: ComponentStatus
) -> ComponentStatus:
ensure_component_status_transition(current, target)
return target
def assert_can_soft_delete(self, *, status: ComponentStatus) -> None:
if status == ComponentStatus.ARCHIVED:
return
# Soft-delete allowed from any status; archive is preferred path.
return
class ComponentVersionPolicy:
"""Enforces immutability and publish/retire rules."""
def assert_can_update(self, version: Any) -> None:
ensure_version_mutable(version)
def assert_publish(
self, *, current: ComponentVersionStatus
) -> ComponentVersionStatus:
ensure_version_status_transition(current, ComponentVersionStatus.PUBLISHED)
return ComponentVersionStatus.PUBLISHED
def assert_retire(
self, *, current: ComponentVersionStatus
) -> ComponentVersionStatus:
ensure_version_status_transition(current, ComponentVersionStatus.RETIRED)
return ComponentVersionStatus.RETIRED
class PagePlacementPolicy:
"""Composition shells may only pin published (non-retired) versions."""
def assert_version_placeable(self, version: Any) -> None:
status = getattr(version, "status", None)
if status != ComponentVersionStatus.PUBLISHED:
raise AppError(
"فقط نسخه منتشرشده قابل جایگذاری است",
error_code="component_version_not_published",
status_code=409,
details={"status": getattr(status, "value", status)},
)
if getattr(version, "is_deleted", False):
raise AppError(
"نسخه حذف‌شده قابل جایگذاری نیست",
error_code="component_version_deleted",
status_code=409,
)
def assert_component_placeable(self, component: Any) -> None:
status = getattr(component, "status", None)
if status in (ComponentStatus.ARCHIVED,):
raise AppError(
"کامپوننت بایگانی‌شده قابل جایگذاری نیست",
error_code="component_archived",
status_code=409,
)
if getattr(component, "is_deleted", False):
raise AppError(
"کامپوننت حذف‌شده قابل جایگذاری نیست",
error_code="component_deleted",
status_code=409,
)