import os, uuid, shutil from pathlib import Path from typing import Dict, Tuple, Any from fastapi import FastAPI, UploadFile, File, HTTPException from fastapi.middleware.cors import CORSMiddleware from fastapi.staticfiles import StaticFiles from pydantic import BaseModel from PIL import Image CWD = Path(__file__).resolve().parent.parent ASSETS_DIR = Path(os.getenv("ASSETS_DIR", CWD / "mock_assets")) TMP_DIR = Path(os.getenv("TMP_DIR", CWD / "tmp")) FRONT_DIR_ENV = os.getenv("FRONT_DIR", str(CWD / "frontend")) for d in (TMP_DIR / "uploads", TMP_DIR / "previews", TMP_DIR / "orders"): d.mkdir(parents=True, exist_ok=True) UPLOADS_DIR = TMP_DIR / "uploads" PREVIEWS_DIR = TMP_DIR / "previews" ORDERS_DIR = TMP_DIR / "orders" app = FastAPI(title="Clodesigner API (restored)") app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"]) front_dir = Path(FRONT_DIR_ENV); front_dir.mkdir(parents=True, exist_ok=True) app.mount("/", StaticFiles(directory=str(front_dir), html=True), name="app") app.mount("/files", StaticFiles(directory=str(CWD)), name="files") class PreviewInput(BaseModel): model: str = "MT" view: str = "front" src: str tile: bool = False offset_x: int = 0 offset_y: int = 0 scale: float = 1.0 class OrderInput(PreviewInput): details: Dict[str, Any] = {} def _ensure_rgba(img: Image.Image) -> Image.Image: return img.convert("RGBA") if img.mode != "RGBA" else img def _resize_to(img: Image.Image, size: Tuple[int, int]) -> Image.Image: return img.resize(size, Image.BICUBIC) def _load_view_assets(model: str, view: str): req = ["background.png", "mask.png", "overlay.png"] opt = ["mask_s1.png", "mask_s2.png"] candidates = [ASSETS_DIR / model / view, ASSETS_DIR / view] base = next((p for p in candidates if p.exists()), None) if not base: tried = " | ".join(map(str, candidates)) raise HTTPException(400, f"Assets dir not found for view='{view}'. Tried: {tried}") missing = [n for n in req if not (base / n).exists()] if missing: raise HTTPException(400, f"Missing assets in {base}: {', '.join(missing)}") files = {n: base/n for n in req} for n in opt: p = base / n if p.exists(): files[n] = p return files def _compose_preview(src_img_path: Path, assets): bg = _ensure_rgba(Image.open(assets["background.png"])) canvas = Image.new("RGBA", bg.size, (0,0,0,0)) canvas.alpha_composite(bg) user = _ensure_rgba(Image.open(src_img_path)).resize((1200,900), Image.BICUBIC) x = (canvas.width - user.width)//2 y = (canvas.height - user.height)//2 base_mask = Image.open(assets["mask.png"]).convert("L") user_on = Image.new("RGBA", canvas.size, (0,0,0,0)) user_on.paste(user, (x,y), user) canvas.paste(user_on, (0,0), base_mask) for s in ("mask_s1.png","mask_s2.png"): if s in assets: m = Image.open(assets[s]).convert("L") tmp = Image.new("RGBA", canvas.size, (0,0,0,0)) tmp.paste(user, (x,y), user) canvas.paste(tmp, (0,0), m) overlay = _ensure_rgba(Image.open(assets["overlay.png"])) canvas.alpha_composite(overlay) return canvas @app.post("/api/upload") async def upload(file: UploadFile = File(...)): name = file.filename or "upload.bin" data = await file.read() if not name.lower().endswith((".png",".jpg",".jpeg")): raise HTTPException(400, "Only PNG/JPG allowed") if len(data) > 50*1024*1024: raise HTTPException(400, "File too large (>50MB)") up = UPLOADS_DIR / f"{uuid.uuid4().hex}_{name}" up.write_bytes(data) rel = up.relative_to(CWD) return {"path": str(rel).replace("\\","/"), "url": f"/files/{rel}".replace("\\","/")} @app.post("/api/preview") async def preview(p: PreviewInput): src = (CWD / p.src) if not Path(p.src).is_absolute() else Path(p.src) if not src.exists(): raise HTTPException(400, f"Uploaded file not found: {src}") img = _compose_preview(src, _load_view_assets(p.model, p.view)) out = PREVIEWS_DIR / f"{uuid.uuid4().hex[:8]}_{p.model}_{p.view}.png" img.save(out, "PNG") rel = out.relative_to(CWD) return {"ok": True, "url": f"/files/{rel}".replace("\\","/")} @app.post("/api/order") async def order(p: OrderInput): src = (CWD / p.src) if not Path(p.src).is_absolute() else Path(p.src) if not src.exists(): raise HTTPException(400, f"Uploaded file not found: {src}") oid = uuid.uuid4().hex[:8] base = (ORDERS_DIR / oid); (base/"mockups").mkdir(parents=True, exist_ok=True); (base/"sources").mkdir(parents=True, exist_ok=True) shutil.copy2(src, base/"sources"/Path(src.name)) img = _compose_preview(src, _load_view_assets(p.model, p.view)) out = base/"mockups"/f"{p.model}_{p.view}.png"; img.save(out, "PNG") return {"ok": True, "order": oid, "mockups": [f"/files/{out.relative_to(CWD)}".replace("\\","/")], "mockups_dir": f"/files/{(base/'mockups').relative_to(CWD)}".replace("\\","/")}