103 lines
3.8 KiB
Python
103 lines
3.8 KiB
Python
from datetime import datetime, timezone
|
|
from typing import Any
|
|
|
|
from fastapi import APIRouter, Depends, Query
|
|
from sqlalchemy import select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.api.deps import get_current_user
|
|
from app.db.database import get_db
|
|
from app.db.models import CanvasState, Design, Edge, Node
|
|
from app.schemas.canvas import CanvasSaveRequest, CanvasStateResponse
|
|
from app.schemas.edges import EdgeResponse
|
|
from app.schemas.nodes import NodeResponse
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
@router.get("", response_model=CanvasStateResponse)
|
|
async def load_canvas(
|
|
design_id: str | None = Query(None, description="Design ID to load; uses first design if omitted"),
|
|
db: AsyncSession = Depends(get_db),
|
|
_: str = Depends(get_current_user),
|
|
) -> CanvasStateResponse:
|
|
if design_id is None:
|
|
first = (await db.execute(select(Design).order_by(Design.created_at).limit(1))).scalar()
|
|
design_id = first.id if first else None
|
|
if design_id is None:
|
|
return CanvasStateResponse(nodes=[], edges=[], viewport={"x": 0, "y": 0, "zoom": 1}, custom_style=None)
|
|
|
|
nodes = (await db.execute(select(Node).where(Node.design_id == design_id))).scalars().all()
|
|
edges = (await db.execute(select(Edge).where(Edge.design_id == design_id))).scalars().all()
|
|
state = await db.get(CanvasState, design_id)
|
|
viewport: dict[str, Any] = state.viewport if state else {"x": 0, "y": 0, "zoom": 1}
|
|
return CanvasStateResponse(
|
|
nodes=[NodeResponse.model_validate(n) for n in nodes],
|
|
edges=[EdgeResponse.model_validate(e) for e in edges],
|
|
viewport=viewport,
|
|
custom_style=state.custom_style if state else None,
|
|
)
|
|
|
|
|
|
@router.post("/save")
|
|
async def save_canvas(
|
|
body: CanvasSaveRequest, db: AsyncSession = Depends(get_db), _: str = Depends(get_current_user)
|
|
) -> dict[str, bool | str]:
|
|
design_id = body.design_id
|
|
if design_id is None:
|
|
first = (await db.execute(select(Design).order_by(Design.created_at).limit(1))).scalar()
|
|
design_id = first.id if first else None
|
|
if design_id is None:
|
|
return {"saved": False, "error": "No design found"}
|
|
|
|
incoming_node_ids = {n.id for n in body.nodes}
|
|
incoming_edge_ids = {e.id for e in body.edges}
|
|
|
|
# Delete nodes removed from canvas (only within this design)
|
|
existing_nodes = (await db.execute(select(Node).where(Node.design_id == design_id))).scalars().all()
|
|
for node in existing_nodes:
|
|
if node.id not in incoming_node_ids:
|
|
await db.delete(node)
|
|
|
|
# Delete edges removed from canvas (only within this design)
|
|
existing_edges = (await db.execute(select(Edge).where(Edge.design_id == design_id))).scalars().all()
|
|
for edge in existing_edges:
|
|
if edge.id not in incoming_edge_ids:
|
|
await db.delete(edge)
|
|
|
|
await db.flush()
|
|
|
|
# Upsert nodes
|
|
for node_data in body.nodes:
|
|
db_node = await db.get(Node, node_data.id)
|
|
payload = node_data.model_dump()
|
|
payload["design_id"] = design_id
|
|
if db_node:
|
|
for field, value in payload.items():
|
|
setattr(db_node, field, value)
|
|
else:
|
|
db.add(Node(**payload))
|
|
|
|
# Upsert edges
|
|
for edge_data in body.edges:
|
|
db_edge = await db.get(Edge, edge_data.id)
|
|
payload = edge_data.model_dump()
|
|
payload["design_id"] = design_id
|
|
if db_edge:
|
|
for field, value in payload.items():
|
|
setattr(db_edge, field, value)
|
|
else:
|
|
db.add(Edge(**payload))
|
|
|
|
# Upsert viewport + custom style
|
|
state = await db.get(CanvasState, design_id)
|
|
if state:
|
|
state.viewport = body.viewport
|
|
state.custom_style = body.custom_style
|
|
state.saved_at = datetime.now(timezone.utc)
|
|
else:
|
|
db.add(CanvasState(design_id=design_id, viewport=body.viewport, custom_style=body.custom_style))
|
|
|
|
await db.commit()
|
|
return {"saved": True}
|