SysOps: deploy-all — 2026-06-09 10:41 UTC
This commit is contained in:
+201
-40
@@ -1,18 +1,21 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional
|
||||
|
||||
import httpx
|
||||
from fastapi import APIRouter, HTTPException, Request
|
||||
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
|
||||
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"))
|
||||
TOOLS = settings.TOOLS_API_URL.rstrip("/")
|
||||
|
||||
|
||||
class PackagingGenerateBody(BaseModel):
|
||||
@@ -20,68 +23,226 @@ class PackagingGenerateBody(BaseModel):
|
||||
width_mm: float = Field(default=120, gt=0)
|
||||
height_mm: float = Field(default=80, gt=0)
|
||||
depth_mm: float = Field(default=40, ge=0)
|
||||
bleed_mm: float = Field(default=3, ge=0)
|
||||
elements: dict[str, bool] = Field(default_factory=dict)
|
||||
brand: dict[str, str] = Field(default_factory=dict)
|
||||
barcode_value: str | None = Field(default=None)
|
||||
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")
|
||||
async def packaging_page(request: Request):
|
||||
async def packaging_page(request: Request, project_id: Optional[int] = None):
|
||||
return templates.TemplateResponse(
|
||||
"packaging.html",
|
||||
{"request": request, "page_title": "Packaging Studio"},
|
||||
{"request": request, "page_title": "Packaging Studio", "initial_project_id": project_id},
|
||||
)
|
||||
|
||||
|
||||
@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:
|
||||
async with httpx.AsyncClient(timeout=60.0) as client:
|
||||
r = await client.post(
|
||||
f"{settings.TOOLS_API_URL.rstrip('/')}/packaging/generate",
|
||||
json=body.model_dump(),
|
||||
)
|
||||
r.raise_for_status()
|
||||
return r.json()
|
||||
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:
|
||||
detail = exc.response.text[:400] if exc.response else str(exc)
|
||||
raise HTTPException(status_code=exc.response.status_code if exc.response else 502, detail=detail) from exc
|
||||
except Exception as exc:
|
||||
raise HTTPException(status_code=502, detail=str(exc)) from exc
|
||||
raise _tools_error(exc) from exc
|
||||
|
||||
|
||||
@router.get("/api/packaging/projects")
|
||||
async def proxy_packaging_projects(limit: int = 30):
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=30.0) as client:
|
||||
r = await client.get(
|
||||
f"{settings.TOOLS_API_URL.rstrip('/')}/packaging/projects",
|
||||
params={"limit": limit},
|
||||
)
|
||||
r.raise_for_status()
|
||||
return r.json()
|
||||
r = await _tools_request("GET", "/packaging/projects", params={"limit": limit})
|
||||
r.raise_for_status()
|
||||
return r.json()
|
||||
except httpx.HTTPStatusError as exc:
|
||||
detail = exc.response.text[:400] if exc.response else str(exc)
|
||||
raise HTTPException(status_code=exc.response.status_code if exc.response else 502, detail=detail) from exc
|
||||
except Exception as exc:
|
||||
raise HTTPException(status_code=502, detail=str(exc)) from exc
|
||||
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:
|
||||
async with httpx.AsyncClient(timeout=120.0) as client:
|
||||
r = await client.get(
|
||||
f"{settings.TOOLS_API_URL.rstrip('/')}/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)
|
||||
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:
|
||||
detail = exc.response.text[:400] if exc.response else str(exc)
|
||||
raise HTTPException(status_code=exc.response.status_code if exc.response else 502, detail=detail) from exc
|
||||
except Exception as exc:
|
||||
raise HTTPException(status_code=502, detail=str(exc)) from exc
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user