Files
ANDREW HOSFORD 0e8978f48c Update project files
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 11:59:48 -05:00

141 lines
4.7 KiB
Python

import json
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from ..auth import verify_token
from ..database import get_db
from ..models import GraphLayout
from ..schemas import (
GraphLayoutCreate,
GraphLayoutDetailResponse,
GraphLayoutResponse,
GraphLayoutUpdate,
)
router = APIRouter(prefix="/api/layouts", tags=["layouts"], dependencies=[Depends(verify_token)])
def _to_response(layout: GraphLayout) -> GraphLayoutResponse:
return GraphLayoutResponse.model_validate(layout)
def _to_detail_response(layout: GraphLayout) -> GraphLayoutDetailResponse:
base = GraphLayoutResponse.model_validate(layout)
return GraphLayoutDetailResponse(
**base.model_dump(),
node_positions=json.loads(layout.node_positions),
viewport=json.loads(layout.viewport),
)
AUTOSAVE_NAME = "__autosave__"
@router.get("", response_model=list[GraphLayoutResponse])
def list_layouts(project_id: int | None = None, db: Session = Depends(get_db)):
query = db.query(GraphLayout).filter(GraphLayout.name != AUTOSAVE_NAME)
if project_id is not None:
query = query.filter(GraphLayout.project_id == project_id)
return [_to_response(l) for l in query.order_by(GraphLayout.updated_at.desc()).all()]
@router.get("/autosave", response_model=GraphLayoutDetailResponse)
def get_autosave(db: Session = Depends(get_db)):
layout = db.query(GraphLayout).filter(GraphLayout.name == AUTOSAVE_NAME).first()
if not layout:
raise HTTPException(404, "No autosave layout found")
return _to_detail_response(layout)
@router.put("/autosave", response_model=GraphLayoutDetailResponse)
def upsert_autosave(data: GraphLayoutUpdate, db: Session = Depends(get_db)):
layout = db.query(GraphLayout).filter(GraphLayout.name == AUTOSAVE_NAME).first()
update_data = data.model_dump(exclude_unset=True)
if "node_positions" in update_data:
update_data["node_positions"] = json.dumps(update_data["node_positions"])
if "viewport" in update_data:
update_data["viewport"] = json.dumps(update_data["viewport"])
if layout:
for key, value in update_data.items():
setattr(layout, key, value)
else:
layout = GraphLayout(
name=AUTOSAVE_NAME,
description="Auto-saved working state",
node_positions=update_data.get("node_positions", "{}"),
viewport=update_data.get("viewport", "{}"),
is_default=False,
)
db.add(layout)
db.commit()
db.refresh(layout)
return _to_detail_response(layout)
@router.get("/{layout_id}", response_model=GraphLayoutDetailResponse)
def get_layout(layout_id: int, db: Session = Depends(get_db)):
layout = db.get(GraphLayout, layout_id)
if not layout:
raise HTTPException(404, "Layout not found")
return _to_detail_response(layout)
@router.post("", response_model=GraphLayoutDetailResponse, status_code=201)
def create_layout(data: GraphLayoutCreate, db: Session = Depends(get_db)):
if data.is_default:
db.query(GraphLayout).filter(
GraphLayout.project_id == data.project_id
).update({"is_default": False})
layout = GraphLayout(
name=data.name,
description=data.description,
project_id=data.project_id,
node_positions=json.dumps(data.node_positions),
viewport=json.dumps(data.viewport),
is_default=data.is_default,
)
db.add(layout)
db.commit()
db.refresh(layout)
return _to_detail_response(layout)
@router.put("/{layout_id}", response_model=GraphLayoutDetailResponse)
def update_layout(layout_id: int, data: GraphLayoutUpdate, db: Session = Depends(get_db)):
layout = db.get(GraphLayout, layout_id)
if not layout:
raise HTTPException(404, "Layout not found")
update_data = data.model_dump(exclude_unset=True)
if update_data.get("is_default"):
db.query(GraphLayout).filter(
GraphLayout.project_id == layout.project_id,
GraphLayout.id != layout_id,
).update({"is_default": False})
if "node_positions" in update_data:
update_data["node_positions"] = json.dumps(update_data["node_positions"])
if "viewport" in update_data:
update_data["viewport"] = json.dumps(update_data["viewport"])
for key, value in update_data.items():
setattr(layout, key, value)
db.commit()
db.refresh(layout)
return _to_detail_response(layout)
@router.delete("/{layout_id}", status_code=204)
def delete_layout(layout_id: int, db: Session = Depends(get_db)):
layout = db.get(GraphLayout, layout_id)
if not layout:
raise HTTPException(404, "Layout not found")
db.delete(layout)
db.commit()