JavaScript 56.7%
TypeScript 42.6%
1import { NextRequest, NextResponse } from "next/server";2import { getIronSession } from "iron-session";3import { SessionData, sessionOptions } from "@/lib/session";4import { prisma } from "@/lib/prisma";5import { saveChunk, assembleChunks, deleteFile } from "@/lib/storage";6import { logActivity } from "@/lib/activity";7import { cleanupOrphanChunks, getSpaceIdForFolder } from "@/lib/trash";8import { notifySharedSpaceUpload } from "@/lib/notifications";9import mime from "mime-types";1011export async function POST(request: NextRequest) {12 const response = NextResponse.next();13 const session = await getIronSession<SessionData>(request, response, sessionOptions);1415 const formData = await request.formData();16 const chunk = formData.get("chunk") as File | null;17 const uploadId = formData.get("uploadId") as string;18 const chunkIndex = parseInt(formData.get("chunkIndex") as string);19 const totalChunks = parseInt(formData.get("totalChunks") as string);20 const fileName = formData.get("fileName") as string;21 const folderId = formData.get("folderId") as string | null;22 const declaredSize = parseInt((formData.get("fileSize") as string) || "0");2324 if (!chunk || !uploadId || !/^[A-Za-z0-9_-]{6,80}$/.test(uploadId) || isNaN(chunkIndex) || isNaN(totalChunks) || !fileName) {25 return NextResponse.json({ error: "Missing chunk data" }, { status: 400 });26 }27 if (/[\\/]/.test(fileName)) return NextResponse.json({ error: "Nom de fichier invalide" }, { status: 400 });2829 const maxSize = parseInt(process.env.MAX_FILE_SIZE || "104857600");30 if (declaredSize > maxSize) {31 return NextResponse.json({ error: `Fichier trop volumineux (max ${Math.round(maxSize / 1024 / 1024)} Mo)` }, { status: 413 });32 }3334 const targetFolder = folderId && folderId !== "root" ? folderId : null;35 if (targetFolder && chunkIndex === 0) {36 const exists = await prisma.folder.findUnique({ where: { id: targetFolder }, select: { id: true } });37 if (!exists) return NextResponse.json({ error: "Dossier de destination introuvable" }, { status: 404 });38 }3940 const buffer = Buffer.from(await chunk.arrayBuffer());41 await saveChunk(buffer, uploadId, chunkIndex);4243 // If this is the last chunk, assemble44 if (chunkIndex === totalChunks - 1) {45 const { storagePath, size } = await assembleChunks(uploadId, totalChunks, fileName);4647 if (size > maxSize) {48 await deleteFile(storagePath);49 return NextResponse.json({ error: `Fichier trop volumineux (max ${Math.round(maxSize / 1024 / 1024)} Mo)` }, { status: 413 });50 }5152 // Quota de l'espace partagé (hérité via l'arborescence)53 const spaceId = await getSpaceIdForFolder(targetFolder);54 if (spaceId) {55 const space = await prisma.sharedSpace.findUnique({ where: { id: spaceId } });56 if (space) {57 const agg = await prisma.file.aggregate({ _sum: { size: true }, where: { deletedAt: null, folder: { sharedSpaceId: spaceId } } });58 if (Number(agg._sum.size || 0) + size > Number(space.quotaBytes)) {59 await deleteFile(storagePath);60 return NextResponse.json({ error: "Quota de l'espace partagé dépassé" }, { status: 413 });61 }62 }63 }6465 const mimeType = mime.lookup(fileName) || "application/octet-stream";6667 const dbFile = await prisma.file.create({68 data: {69 name: fileName,70 storagePath,71 size: BigInt(size),72 mimeType,73 folderId: targetFolder,74 userId: session.userId ?? null,75 isEncrypted: true,76 },77 });7879 await logActivity("UPLOAD", dbFile.id, `Uploaded ${fileName} (chunked)`);80 if (spaceId && session.userId) notifySharedSpaceUpload(spaceId, session.userId, fileName, dbFile.id).catch(() => {});81 cleanupOrphanChunks().catch(() => {});8283 return NextResponse.json(84 { ...dbFile, size: Number(dbFile.size), complete: true },85 { status: 201 }86 );87 }8889 return NextResponse.json({ chunkIndex, received: true });90}91