first commit
This commit is contained in:
172
backend/app/application/editor/service.py
Normal file
172
backend/app/application/editor/service.py
Normal file
@@ -0,0 +1,172 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import asdict
|
||||
from datetime import datetime, timezone
|
||||
from uuid import uuid4
|
||||
|
||||
from app.domain.editor import EditorSession, VariantChange
|
||||
from app.domain.editor_builder import build_runtime_payload_from_changes
|
||||
from app.repositories.directus.variant_changes import VariantChangeRepository
|
||||
from app.repositories.native.editor_sessions import EditorSessionRepository
|
||||
from app.schemas.auth import AuthenticatedUser
|
||||
from app.schemas.editor import (
|
||||
BuildPreviewRequest,
|
||||
BuildPreviewResponse,
|
||||
EditorChangeListResponse,
|
||||
EditorChangeRead,
|
||||
EditorSessionCreateRequest,
|
||||
EditorSessionRead,
|
||||
EditorSessionUpdateRequest,
|
||||
SaveVariantChangesRequest,
|
||||
)
|
||||
|
||||
|
||||
class EditorService:
|
||||
"""Application service for visual-editor workflows."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
change_repository: VariantChangeRepository | None = None,
|
||||
session_repository: EditorSessionRepository | None = None,
|
||||
) -> None:
|
||||
self.change_repository = change_repository or VariantChangeRepository()
|
||||
self.session_repository = session_repository or EditorSessionRepository()
|
||||
|
||||
async def create_session(
|
||||
self,
|
||||
request: EditorSessionCreateRequest,
|
||||
current_user: AuthenticatedUser,
|
||||
) -> EditorSessionRead:
|
||||
session = EditorSession(
|
||||
variant_id=request.variant_id,
|
||||
mode=request.mode,
|
||||
base_url=request.base_url,
|
||||
actor_id=current_user.id,
|
||||
actor_email=current_user.email,
|
||||
)
|
||||
await self.session_repository.create(session)
|
||||
return EditorSessionRead.model_validate(asdict(session))
|
||||
|
||||
async def get_session(self, session_id: str) -> EditorSessionRead | None:
|
||||
session = await self.session_repository.get(session_id)
|
||||
if not session:
|
||||
return None
|
||||
return EditorSessionRead.model_validate(asdict(session))
|
||||
|
||||
async def update_session(
|
||||
self,
|
||||
session_id: str,
|
||||
request: EditorSessionUpdateRequest,
|
||||
) -> EditorSessionRead | None:
|
||||
session = await self.session_repository.get(session_id)
|
||||
if not session:
|
||||
return None
|
||||
|
||||
if request.status is not None:
|
||||
session.status = request.status
|
||||
if request.draft_changes is not None:
|
||||
session.draft_changes = request.draft_changes
|
||||
session.updated_at = datetime.now(timezone.utc)
|
||||
|
||||
await self.session_repository.update(session)
|
||||
return EditorSessionRead.model_validate(asdict(session))
|
||||
|
||||
async def delete_session(self, session_id: str) -> None:
|
||||
await self.session_repository.delete(session_id)
|
||||
|
||||
async def list_changes(self, variant_id: str) -> EditorChangeListResponse:
|
||||
items = await self.change_repository.list(params={"filter[variant_id][_eq]": variant_id})
|
||||
mapped = [EditorChangeRead.model_validate(item) for item in items]
|
||||
return EditorChangeListResponse(items=mapped)
|
||||
|
||||
async def list_changes_with_access_token(
|
||||
self,
|
||||
variant_id: str,
|
||||
access_token: str,
|
||||
) -> EditorChangeListResponse:
|
||||
items = await self.change_repository.list(
|
||||
params={"filter[variant_id][_eq]": variant_id},
|
||||
access_token=access_token,
|
||||
)
|
||||
mapped = [EditorChangeRead.model_validate(item) for item in items]
|
||||
return EditorChangeListResponse(items=mapped)
|
||||
|
||||
async def save_changes(
|
||||
self,
|
||||
variant_id: str,
|
||||
request: SaveVariantChangesRequest,
|
||||
) -> EditorChangeListResponse:
|
||||
# Full-replace semantics: delete any existing changes not present in the request.
|
||||
existing = await self.change_repository.list(params={"filter[variant_id][_eq]": variant_id})
|
||||
incoming_ids = {item.id for item in request.items if item.id}
|
||||
for existing_item in existing:
|
||||
if str(existing_item["id"]) not in incoming_ids:
|
||||
await self.change_repository.delete(str(existing_item["id"]))
|
||||
|
||||
saved_items: list[EditorChangeRead] = []
|
||||
for item in request.items:
|
||||
payload = item.model_dump()
|
||||
payload["variant_id"] = variant_id
|
||||
|
||||
change_id = payload.pop("id", None)
|
||||
if change_id:
|
||||
saved = await self.change_repository.update(change_id, payload)
|
||||
else:
|
||||
payload["id"] = str(uuid4())
|
||||
saved = await self.change_repository.create(payload)
|
||||
|
||||
saved_items.append(EditorChangeRead.model_validate(saved))
|
||||
|
||||
return EditorChangeListResponse(items=saved_items)
|
||||
|
||||
async def save_changes_with_access_token(
|
||||
self,
|
||||
variant_id: str,
|
||||
request: SaveVariantChangesRequest,
|
||||
access_token: str,
|
||||
) -> EditorChangeListResponse:
|
||||
# Full-replace semantics: delete any existing changes not present in the request.
|
||||
existing = await self.change_repository.list(
|
||||
params={"filter[variant_id][_eq]": variant_id},
|
||||
access_token=access_token,
|
||||
)
|
||||
incoming_ids = {item.id for item in request.items if item.id}
|
||||
for existing_item in existing:
|
||||
if str(existing_item["id"]) not in incoming_ids:
|
||||
await self.change_repository.delete(str(existing_item["id"]), access_token=access_token)
|
||||
|
||||
saved_items: list[EditorChangeRead] = []
|
||||
for item in request.items:
|
||||
payload = item.model_dump()
|
||||
payload["variant_id"] = variant_id
|
||||
|
||||
change_id = payload.pop("id", None)
|
||||
if change_id:
|
||||
saved = await self.change_repository.update(change_id, payload, access_token=access_token)
|
||||
else:
|
||||
payload["id"] = str(uuid4())
|
||||
saved = await self.change_repository.create(payload, access_token=access_token)
|
||||
|
||||
saved_items.append(EditorChangeRead.model_validate(saved))
|
||||
|
||||
return EditorChangeListResponse(items=saved_items)
|
||||
|
||||
async def build_preview(self, request: BuildPreviewRequest) -> BuildPreviewResponse:
|
||||
changes = [
|
||||
VariantChange(
|
||||
id=item.id or str(uuid4()),
|
||||
variant_id=request.variant_id,
|
||||
change_type=item.change_type,
|
||||
selector_type=item.selector_type,
|
||||
selector_value=item.selector_value,
|
||||
sort_order=item.sort_order,
|
||||
payload=item.payload,
|
||||
)
|
||||
for item in request.items
|
||||
]
|
||||
preview = build_runtime_payload_from_changes(request.variant_id, changes)
|
||||
return BuildPreviewResponse(
|
||||
variant_id=preview.variant_id,
|
||||
generated_at=preview.generated_at,
|
||||
operations=[asdict(operation) for operation in preview.operations],
|
||||
)
|
||||
Reference in New Issue
Block a user