retrotoon-studio/server/uploadRoute.ts

165 lines
5.3 KiB
TypeScript

import { Router, Request, Response } from "express";
import { storagePut } from "./storage";
import { nanoid } from "nanoid";
const uploadRouter = Router();
// Video upload endpoint
uploadRouter.post("/api/upload/video", async (req: Request, res: Response) => {
try {
// Handle multipart form data
const chunks: Buffer[] = [];
req.on("data", (chunk: Buffer) => {
chunks.push(chunk);
});
req.on("end", async () => {
try {
const body = Buffer.concat(chunks);
// Parse multipart boundary
const contentType = req.headers["content-type"] || "";
const boundary = contentType.split("boundary=")[1];
if (!boundary) {
res.status(400).json({ error: "Invalid content type" });
return;
}
// Simple multipart parsing - extract file data
const bodyStr = body.toString("binary");
const parts = bodyStr.split(`--${boundary}`);
let fileBuffer: Buffer | null = null;
let fileName = "video.mp4";
let projectName = "";
for (const part of parts) {
if (part.includes('name="file"')) {
const headerEnd = part.indexOf("\r\n\r\n");
if (headerEnd !== -1) {
// Extract filename
const filenameMatch = part.match(/filename="([^"]+)"/);
if (filenameMatch) fileName = filenameMatch[1];
// Extract file content
const content = part.substring(headerEnd + 4);
// Remove trailing \r\n
const trimmed = content.endsWith("\r\n") ? content.slice(0, -2) : content;
fileBuffer = Buffer.from(trimmed, "binary");
}
} else if (part.includes('name="name"')) {
const headerEnd = part.indexOf("\r\n\r\n");
if (headerEnd !== -1) {
projectName = part.substring(headerEnd + 4).trim().replace(/\r\n$/, "");
}
}
}
if (!fileBuffer) {
res.status(400).json({ error: "No file provided" });
return;
}
// Upload to S3 storage
const fileId = nanoid(12);
const ext = fileName.split(".").pop() || "mp4";
const storageKey = `videos/${fileId}.${ext}`;
const { url } = await storagePut(storageKey, fileBuffer, `video/${ext}`);
res.json({
success: true,
url,
fileName,
fileId,
size: fileBuffer.length,
});
} catch (err: any) {
console.error("[Upload] Processing error:", err);
res.status(500).json({ error: "Upload processing failed" });
}
});
} catch (err: any) {
console.error("[Upload] Error:", err);
res.status(500).json({ error: "Upload failed" });
}
});
// Image/asset upload endpoint (for reference sheets, etc.)
uploadRouter.post("/api/upload/asset", async (req: Request, res: Response) => {
try {
const chunks: Buffer[] = [];
req.on("data", (chunk: Buffer) => {
chunks.push(chunk);
});
req.on("end", async () => {
try {
const body = Buffer.concat(chunks);
const contentType = req.headers["content-type"] || "";
const boundary = contentType.split("boundary=")[1];
if (!boundary) {
res.status(400).json({ error: "Invalid content type" });
return;
}
const bodyStr = body.toString("binary");
const parts = bodyStr.split(`--${boundary}`);
let fileBuffer: Buffer | null = null;
let fileName = "asset.png";
let assetType = "reference";
for (const part of parts) {
if (part.includes('name="file"')) {
const headerEnd = part.indexOf("\r\n\r\n");
if (headerEnd !== -1) {
const filenameMatch = part.match(/filename="([^"]+)"/);
if (filenameMatch) fileName = filenameMatch[1];
const content = part.substring(headerEnd + 4);
const trimmed = content.endsWith("\r\n") ? content.slice(0, -2) : content;
fileBuffer = Buffer.from(trimmed, "binary");
}
} else if (part.includes('name="type"')) {
const headerEnd = part.indexOf("\r\n\r\n");
if (headerEnd !== -1) {
assetType = part.substring(headerEnd + 4).trim().replace(/\r\n$/, "");
}
}
}
if (!fileBuffer) {
res.status(400).json({ error: "No file provided" });
return;
}
const fileId = nanoid(12);
const ext = fileName.split(".").pop() || "png";
const storageKey = `assets/${assetType}/${fileId}.${ext}`;
const mimeType = ext === "png" ? "image/png" : ext === "jpg" || ext === "jpeg" ? "image/jpeg" : `image/${ext}`;
const { url } = await storagePut(storageKey, fileBuffer, mimeType);
res.json({
success: true,
url,
fileName,
fileId,
size: fileBuffer.length,
});
} catch (err: any) {
console.error("[Upload Asset] Processing error:", err);
res.status(500).json({ error: "Asset upload processing failed" });
}
});
} catch (err: any) {
console.error("[Upload Asset] Error:", err);
res.status(500).json({ error: "Asset upload failed" });
}
});
export default uploadRouter;