feat(dwg): DWG→DXF via sidecar microservice (libredwg)
Add dedicated dwg2dxf container (Debian slim + libredwg-tools + Flask) instead of modifying the Alpine base image. The ArchiTools API route proxies to the sidecar over Docker internal network. - dwg2dxf-api/: Dockerfile + Flask app (POST /convert, GET /health) - docker-compose.yml: dwg2dxf service, healthcheck, depends_on - route.ts: rewritten from local exec to HTTP proxy - .dockerignore: exclude sidecar from main build context Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,18 +1,12 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { writeFile, readFile, unlink, mkdir } from "fs/promises";
|
||||
import { exec } from "child_process";
|
||||
import { promisify } from "util";
|
||||
import { randomUUID } from "crypto";
|
||||
import { join } from "path";
|
||||
import { tmpdir } from "os";
|
||||
|
||||
const execAsync = promisify(exec);
|
||||
const DWG2DXF_URL = process.env.DWG2DXF_URL ?? "http://localhost:5001";
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
const tmpDir = join(tmpdir(), `dwg-${randomUUID()}`);
|
||||
try {
|
||||
const formData = await req.formData();
|
||||
const file = formData.get("fileInput") as File | null;
|
||||
|
||||
if (!file) {
|
||||
return NextResponse.json(
|
||||
{ error: "Lipsește fișierul DWG." },
|
||||
@@ -28,20 +22,29 @@ export async function POST(req: NextRequest) {
|
||||
);
|
||||
}
|
||||
|
||||
await mkdir(tmpDir, { recursive: true });
|
||||
// Re-package for sidecar (field name: "file")
|
||||
const sidecarForm = new FormData();
|
||||
sidecarForm.append("file", file, name);
|
||||
|
||||
const inputPath = join(tmpDir, name);
|
||||
const outputPath = inputPath.replace(/\.dwg$/i, ".dxf");
|
||||
const res = await fetch(`${DWG2DXF_URL}/convert`, {
|
||||
method: "POST",
|
||||
body: sidecarForm,
|
||||
});
|
||||
|
||||
const buffer = Buffer.from(await file.arrayBuffer());
|
||||
await writeFile(inputPath, buffer);
|
||||
if (!res.ok) {
|
||||
const data = await res.json().catch(() => ({ error: res.statusText }));
|
||||
const errorMsg =
|
||||
typeof data === "object" && data !== null && "error" in data
|
||||
? String((data as Record<string, unknown>).error)
|
||||
: `Eroare sidecar DWG: ${res.status}`;
|
||||
return NextResponse.json({ error: errorMsg }, { status: res.status });
|
||||
}
|
||||
|
||||
await execAsync(`dwg2dxf "${inputPath}"`, { timeout: 60_000 });
|
||||
|
||||
const dxfBuffer = await readFile(outputPath);
|
||||
const blob = await res.blob();
|
||||
const buffer = Buffer.from(await blob.arrayBuffer());
|
||||
const dxfName = name.replace(/\.dwg$/i, ".dxf");
|
||||
|
||||
return new NextResponse(dxfBuffer, {
|
||||
return new NextResponse(buffer, {
|
||||
status: 200,
|
||||
headers: {
|
||||
"Content-Type": "application/dxf",
|
||||
@@ -50,35 +53,24 @@ export async function POST(req: NextRequest) {
|
||||
});
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : "Unknown error";
|
||||
|
||||
if (
|
||||
message.includes("ENOENT") ||
|
||||
message.includes("not found") ||
|
||||
message.includes("not recognized")
|
||||
message.includes("ECONNREFUSED") ||
|
||||
message.includes("fetch failed") ||
|
||||
message.includes("ENOTFOUND")
|
||||
) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error:
|
||||
"Conversia DWG→DXF este disponibilă doar pe server (Docker). Local nu este instalat dwg2dxf (libredwg).",
|
||||
"Serviciul de conversie DWG nu este disponibil. Verificați că containerul dwg2dxf este pornit.",
|
||||
},
|
||||
{ status: 501 },
|
||||
{ status: 503 },
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json(
|
||||
{ error: `Eroare la conversie DWG→DXF: ${message}` },
|
||||
{ status: 500 },
|
||||
);
|
||||
} finally {
|
||||
// Clean up temp files
|
||||
try {
|
||||
const { readdir } = await import("fs/promises");
|
||||
const files = await readdir(tmpDir);
|
||||
for (const f of files) {
|
||||
await unlink(join(tmpDir, f)).catch(() => {});
|
||||
}
|
||||
const { rmdir } = await import("fs/promises");
|
||||
await rmdir(tmpDir).catch(() => {});
|
||||
} catch {
|
||||
// temp cleanup failure is non-critical
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user