Add functions to commission form
This commit is contained in:
139
src/app/api/v1/commissions/route.ts
Normal file
139
src/app/api/v1/commissions/route.ts
Normal file
@ -0,0 +1,139 @@
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { s3 } from "@/lib/s3";
|
||||
import { PutObjectCommand } from "@aws-sdk/client-s3";
|
||||
import { NextResponse } from "next/server";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
export const runtime = "nodejs"; // required for AWS SDK on many setups
|
||||
export const dynamic = "force-dynamic"; // API endpoint should be dynamic
|
||||
|
||||
const payloadSchema = z.object({
|
||||
typeId: z.string().min(1).optional().nullable(),
|
||||
optionId: z.string().min(1).optional().nullable(),
|
||||
extraIds: z.array(z.string().min(1)).default([]),
|
||||
|
||||
customerName: z.string().min(1).max(200),
|
||||
customerEmail: z.string().email().max(320),
|
||||
customerSocials: z.string().max(2000).optional().nullable(),
|
||||
message: z.string().min(1).max(20_000),
|
||||
|
||||
// customFields: z.record(z.string(), z.unknown()).optional(),
|
||||
});
|
||||
|
||||
function safeJsonParse(input: string) {
|
||||
try {
|
||||
return JSON.parse(input) as unknown;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(request: Request) {
|
||||
// Optional: basic origin allowlist check (recommended)
|
||||
// const origin = request.headers.get("origin");
|
||||
// if (origin && !["https://domain.com", "https://www.domain.com"].includes(origin)) {
|
||||
// return NextResponse.json({ error: "Invalid origin" }, { status: 403 });
|
||||
// }
|
||||
|
||||
const form = await request.formData();
|
||||
|
||||
const payloadRaw = form.get("payload");
|
||||
if (typeof payloadRaw !== "string") {
|
||||
return NextResponse.json({ error: "Missing payload" }, { status: 400 });
|
||||
}
|
||||
|
||||
const parsedJson = safeJsonParse(payloadRaw);
|
||||
if (!parsedJson) {
|
||||
return NextResponse.json({ error: "Invalid payload JSON" }, { status: 400 });
|
||||
}
|
||||
|
||||
const payload = payloadSchema.safeParse(parsedJson);
|
||||
if (!payload.success) {
|
||||
return NextResponse.json(
|
||||
{ error: "Validation error", issues: payload.error.issues },
|
||||
{ status: 422 }
|
||||
);
|
||||
}
|
||||
|
||||
const files = form.getAll("files").filter((v): v is File => v instanceof File);
|
||||
|
||||
// Optional: enforce limits
|
||||
const MAX_FILES = 10;
|
||||
const MAX_BYTES_EACH = 10 * 1024 * 1024; // 10MB
|
||||
if (files.length > MAX_FILES) {
|
||||
return NextResponse.json({ error: "Too many files" }, { status: 413 });
|
||||
}
|
||||
for (const f of files) {
|
||||
if (f.size > MAX_BYTES_EACH) {
|
||||
return NextResponse.json({ error: `File too large: ${f.name}` }, { status: 413 });
|
||||
}
|
||||
}
|
||||
|
||||
// Capture basic metadata
|
||||
const ipAddress =
|
||||
request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() ?? null;
|
||||
const userAgent = request.headers.get("user-agent") ?? null;
|
||||
|
||||
// Create request first to get requestId; then upload files and store file rows
|
||||
// Use a transaction to keep DB consistent.
|
||||
const result = await prisma.$transaction(async (tx) => {
|
||||
const created = await tx.commissionRequest.create({
|
||||
data: {
|
||||
status: "NEW",
|
||||
customerName: payload.data.customerName,
|
||||
customerEmail: payload.data.customerEmail,
|
||||
customerSocials: payload.data.customerSocials ?? null,
|
||||
message: payload.data.message,
|
||||
|
||||
typeId: payload.data.typeId ?? null,
|
||||
optionId: payload.data.optionId ?? null,
|
||||
|
||||
ipAddress,
|
||||
userAgent,
|
||||
|
||||
// customFields: payload.data.customFields ?? undefined,
|
||||
|
||||
// Extras are M:N; connect by ids
|
||||
extras: payload.data.extraIds?.length
|
||||
? { connect: payload.data.extraIds.map((id) => ({ id })) }
|
||||
: undefined,
|
||||
},
|
||||
select: { id: true, createdAt: true },
|
||||
});
|
||||
|
||||
for (const f of files) {
|
||||
const fileKey = uuidv4();
|
||||
const fileType = f.type;
|
||||
const realFileType = fileType.split("/")[1];
|
||||
const originalKey = `commissions/${fileKey}.${realFileType}`;
|
||||
|
||||
const arrayBuffer = await f.arrayBuffer();
|
||||
const buffer = Buffer.from(arrayBuffer);
|
||||
|
||||
await s3.send(
|
||||
new PutObjectCommand({
|
||||
Bucket: `${process.env.BUCKET_NAME}`,
|
||||
Key: originalKey,
|
||||
Body: buffer,
|
||||
ContentType: f.type || "application/octet-stream",
|
||||
})
|
||||
);
|
||||
|
||||
await tx.commissionRequestFile.create({
|
||||
data: {
|
||||
requestId: created.id,
|
||||
fileKey: originalKey,
|
||||
originalFile: f.name,
|
||||
fileType: fileType || "application/octet-stream",
|
||||
fileSize: f.size,
|
||||
uploadDate: created.createdAt,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return created;
|
||||
});
|
||||
|
||||
return NextResponse.json(result, { status: 201 });
|
||||
}
|
||||
Reference in New Issue
Block a user