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>
86 lines
3.0 KiB
Python
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,
|
|
)
|