Files

249 lines
8.5 KiB
Python
Raw Permalink Normal View History

from __future__ import annotations
from pathlib import Path
2026-06-09 10:41:13 +00:00
from typing import Any, Optional
import httpx
2026-06-09 10:41:13 +00:00
from fastapi import APIRouter, HTTPException, Query, Request
from fastapi.responses import Response
from fastapi.templating import Jinja2Templates
from pydantic import BaseModel, Field
from app.config import settings
2026-06-09 10:41:13 +00:00
from app.services import packaging_nas, projects
router = APIRouter(tags=["packaging"])
BASE_DIR = Path(__file__).resolve().parent.parent.parent
templates = Jinja2Templates(directory=str(BASE_DIR / "templates"))
2026-06-09 10:41:13 +00:00
TOOLS = settings.TOOLS_API_URL.rstrip("/")
class PackagingGenerateBody(BaseModel):
type: str = Field(default="folding_box")
width_mm: float = Field(default=120, gt=0)
height_mm: float = Field(default=80, gt=0)
depth_mm: float = Field(default=40, ge=0)
2026-06-09 10:41:13 +00:00
bleed_mm: float = Field(default=3, ge=0)
elements: dict[str, bool] = Field(default_factory=dict)
brand: dict[str, str] = Field(default_factory=dict)
2026-06-09 10:41:13 +00:00
text: dict[str, Any] = Field(default_factory=dict)
barcode_value: str | None = None
qr_value: str | None = None
design_name: str | None = None
project_id: int | None = None
project_name: str | None = None
client_id: int | None = None
created_by: str = "ceo"
class PackagingEmailBody(BaseModel):
to: list[str] = Field(..., min_length=1)
subject: str = Field(..., min_length=1)
body: str = ""
cc: list[str] = Field(default_factory=list)
client_id: int | None = None
class CopyToProjectBody(BaseModel):
target_project_id: int
async def _tools_request(method: str, path: str, **kwargs) -> httpx.Response:
async with httpx.AsyncClient(timeout=120.0) as client:
r = await client.request(method, f"{TOOLS}{path}", **kwargs)
return r
def _tools_error(exc: httpx.HTTPStatusError) -> HTTPException:
detail = exc.response.text[:500] if exc.response else str(exc)
return HTTPException(status_code=exc.response.status_code if exc.response else 502, detail=detail)
async def _ensure_project(body: PackagingGenerateBody) -> PackagingGenerateBody:
if body.project_id or not body.project_name:
return body
proj = projects.create_project(
body.project_name.strip(),
client_id=body.client_id,
description="Packaging design project",
project_type="packaging",
ensure_nas=True,
)
data = body.model_dump()
data["project_id"] = proj.get("id")
return PackagingGenerateBody(**data)
async def _post_nas_export(result: dict[str, Any]) -> dict[str, Any]:
pid = result.get("cockpit_project_id")
packaging_id = result.get("id")
svg = result.get("svg") or ""
spec = result.get("spec") or {}
if not pid or not packaging_id or not svg:
return result
png_bytes = pdf_bytes = None
try:
png_r = await _tools_request("GET", f"/packaging/download/{packaging_id}", params={"format": "png"})
if png_r.status_code < 400:
png_bytes = png_r.content
pdf_r = await _tools_request("GET", f"/packaging/download/{packaging_id}", params={"format": "pdf"})
if pdf_r.status_code < 400:
pdf_bytes = pdf_r.content
except Exception:
pass
try:
nas_info = packaging_nas.export_packaging_files(packaging_id, int(pid), svg, spec, png_bytes, pdf_bytes)
result["nas"] = nas_info
except Exception as exc:
result["nas"] = {"ok": False, "error": str(exc)}
return result
@router.get("/packaging")
2026-06-09 10:41:13 +00:00
async def packaging_page(request: Request, project_id: Optional[int] = None):
return templates.TemplateResponse(
"packaging.html",
2026-06-09 10:41:13 +00:00
{"request": request, "page_title": "Packaging Studio", "initial_project_id": project_id},
)
2026-06-09 10:41:13 +00:00
@router.get("/api/packaging/types")
async def proxy_packaging_types():
try:
r = await _tools_request("GET", "/packaging/types")
r.raise_for_status()
return r.json()
except httpx.HTTPStatusError as exc:
raise _tools_error(exc) from exc
@router.post("/api/packaging/preview")
async def proxy_packaging_preview(body: PackagingGenerateBody):
try:
r = await _tools_request("POST", "/packaging/preview", json=body.model_dump())
r.raise_for_status()
return r.json()
except httpx.HTTPStatusError as exc:
raise _tools_error(exc) from exc
@router.post("/api/packaging/generate")
async def proxy_packaging_generate(body: PackagingGenerateBody):
try:
2026-06-09 10:41:13 +00:00
body = await _ensure_project(body)
r = await _tools_request("POST", "/packaging/generate", json=body.model_dump())
r.raise_for_status()
result = r.json()
return await _post_nas_export(result)
except httpx.HTTPStatusError as exc:
2026-06-09 10:41:13 +00:00
raise _tools_error(exc) from exc
@router.get("/api/packaging/projects")
async def proxy_packaging_projects(limit: int = 30):
try:
2026-06-09 10:41:13 +00:00
r = await _tools_request("GET", "/packaging/projects", params={"limit": limit})
r.raise_for_status()
return r.json()
except httpx.HTTPStatusError as exc:
2026-06-09 10:41:13 +00:00
raise _tools_error(exc) from exc
@router.get("/api/packaging/download/{project_id}")
async def proxy_packaging_download(project_id: str, format: str = "svg"):
try:
2026-06-09 10:41:13 +00:00
r = await _tools_request("GET", f"/packaging/download/{project_id}", params={"format": format})
r.raise_for_status()
media = r.headers.get("content-type", "application/octet-stream")
disposition = r.headers.get("content-disposition")
headers = {"content-disposition": disposition} if disposition else {}
return Response(content=r.content, media_type=media, headers=headers)
except httpx.HTTPStatusError as exc:
2026-06-09 10:41:13 +00:00
raise _tools_error(exc) from exc
@router.get("/api/packaging/{packaging_id}")
async def proxy_packaging_get(packaging_id: str):
try:
r = await _tools_request("GET", f"/packaging/{packaging_id}")
r.raise_for_status()
return r.json()
except httpx.HTTPStatusError as exc:
raise _tools_error(exc) from exc
@router.patch("/api/packaging/{packaging_id}")
async def proxy_packaging_update(packaging_id: str, body: PackagingGenerateBody):
try:
r = await _tools_request("PATCH", f"/packaging/{packaging_id}", json={"spec": body.model_dump()})
r.raise_for_status()
result = r.json()
return await _post_nas_export(result)
except httpx.HTTPStatusError as exc:
raise _tools_error(exc) from exc
@router.post("/api/packaging/{packaging_id}/duplicate")
async def proxy_packaging_duplicate(packaging_id: str):
try:
r = await _tools_request("POST", f"/packaging/{packaging_id}/duplicate")
r.raise_for_status()
result = r.json()
return await _post_nas_export(result)
except httpx.HTTPStatusError as exc:
raise _tools_error(exc) from exc
@router.post("/api/packaging/{packaging_id}/copy-to-project")
async def proxy_packaging_copy(packaging_id: str, body: CopyToProjectBody):
try:
r = await _tools_request(
"POST",
f"/packaging/{packaging_id}/copy-to-project",
json=body.model_dump(),
)
r.raise_for_status()
result = r.json()
return await _post_nas_export(result)
except httpx.HTTPStatusError as exc:
raise _tools_error(exc) from exc
@router.delete("/api/packaging/{packaging_id}")
async def proxy_packaging_delete(packaging_id: str):
try:
r = await _tools_request("DELETE", f"/packaging/{packaging_id}")
r.raise_for_status()
return r.json()
except httpx.HTTPStatusError as exc:
raise _tools_error(exc) from exc
@router.post("/api/packaging/{packaging_id}/email")
async def proxy_packaging_email(packaging_id: str, body: PackagingEmailBody):
base = settings.COCKPIT_PUBLIC_URL if hasattr(settings, "COCKPIT_PUBLIC_URL") else "http://10.4.7.18:8600"
links = "\n".join(
f"- {fmt.upper()}: {base}/api/packaging/download/{packaging_id}?format={fmt}"
for fmt in ("svg", "png", "pdf")
)
email_body = (body.body or "").strip()
if email_body:
email_body += "\n\n"
email_body += f"Packaging downloads:\n{links}"
try:
r = await _tools_request(
"POST",
"/emails/send",
json={
"to": body.to,
"cc": body.cc,
"subject": body.subject,
"body": email_body,
"client_id": body.client_id,
},
)
r.raise_for_status()
return r.json()
except httpx.HTTPStatusError as exc:
raise _tools_error(exc) from exc