SPB Git forge

spb/spb-cloud

Public
10commits 1branches 0releases
3.2 MBsize
maindefault branch
yesterdaylast push
JavaScript 56.7% TypeScript 42.6%
3.9 KB · 91 lines typescript
Raw Blame History
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