diff --git a/src/lib/server/services/file.ts b/src/lib/server/services/file.ts index 8a05eb6..b3086f9 100644 --- a/src/lib/server/services/file.ts +++ b/src/lib/server/services/file.ts @@ -115,10 +115,8 @@ export const uploadFile = async ( } catch (e) { await safeUnlink(path); - if (e instanceof IntegrityError) { - if (e.message === "Inactive MEK version") { - error(400, "Invalid MEK version"); - } + if (e instanceof IntegrityError && e.message === "Inactive MEK version") { + error(400, "Invalid MEK version"); } throw e; } diff --git a/src/routes/(main)/directory/[[id]]/+page.svelte b/src/routes/(main)/directory/[[id]]/+page.svelte index 866adbe..c5e9f8b 100644 --- a/src/routes/(main)/directory/[[id]]/+page.svelte +++ b/src/routes/(main)/directory/[[id]]/+page.svelte @@ -66,9 +66,9 @@ info = getDirectoryInfo(data.id, $masterKeyStore?.get(1)?.key!); window.alert("파일이 업로드되었어요."); }) - .catch(() => { + .catch((e: Error) => { // TODO: FIXME - window.alert("파일 업로드에 실패했어요."); + window.alert(`파일 업로드에 실패했어요.\n${e.message}`); }); }; diff --git a/src/routes/api/file/upload/+server.ts b/src/routes/api/file/upload/+server.ts index 66624f6..b7b38ea 100644 --- a/src/routes/api/file/upload/+server.ts +++ b/src/routes/api/file/upload/+server.ts @@ -6,53 +6,10 @@ import { fileUploadRequest } from "$lib/server/schemas"; import { uploadFile } from "$lib/server/services/file"; import type { RequestHandler } from "./$types"; -const parseFormData = async (contentType: string, body: ReadableStream) => { - return new Promise<{ metadata: string; content: Readable }>((resolve, reject) => { - let metadata: string | null = null; - let content: Readable | null = null; +type FileMetadata = Parameters[0]; - const bb = Busboy({ headers: { "content-type": contentType } }); - bb.on("field", (fieldname, val) => { - if (fieldname !== "metadata") return reject(new Error("Invalid request body")); - if (metadata || content) return reject(new Error("Invalid request body")); // metadata must be first - metadata = val; - }); - bb.on("file", (fieldname, file) => { - if (fieldname !== "content") return reject(new Error("Invalid request body")); - if (!metadata || content) return reject(new Error("Invalid request body")); // metadata must be first - content = file; - resolve({ metadata, content }); - }); - bb.on("finish", () => reject(new Error("Invalid request body"))); - bb.on("error", (e) => reject(e)); - - body.pipeTo(Writable.toWeb(bb)); - }); -}; - -export const POST: RequestHandler = async ({ locals, request }) => { - const { userId } = await authorize(locals, "activeClient"); - - const contentTypeHeader = request.headers.get("Content-Type"); - if (!contentTypeHeader?.startsWith("multipart/form-data") || !request.body) { - error(400, "Invalid request body"); - } - - let metadata; - let content; - - try { - const formData = await parseFormData(contentTypeHeader, request.body); - metadata = formData.metadata; - content = formData.content; - } catch (e) { - if (e instanceof Error && e.message === "Invalid request body") { - error(400, "Invalid request body"); - } - throw e; - } - - const zodRes = fileUploadRequest.safeParse(JSON.parse(metadata)); +const parseFileMetadata = (userId: number, json: string) => { + const zodRes = fileUploadRequest.safeParse(JSON.parse(json)); if (!zodRes.success) error(400, "Invalid request body"); const { parentId, @@ -73,25 +30,65 @@ export const POST: RequestHandler = async ({ locals, request }) => { if ((createdAt && !createdAtIv) || (!createdAt && createdAtIv)) error(400, "Invalid request body"); - await uploadFile( - { - userId, - parentId, - mekVersion, - encDek: dek, - dekVersion: new Date(dekVersion), - hskVersion, - contentHmac, - contentType, - encContentIv: contentIv, - encName: name, - encNameIv: nameIv, - encCreatedAt: createdAt ?? null, - encCreatedAtIv: createdAtIv ?? null, - encLastModifiedAt: lastModifiedAt, - encLastModifiedAtIv: lastModifiedAtIv, - }, - content, - ); - return text("File uploaded", { headers: { "Content-Type": "text/plain" } }); + return { + userId, + parentId, + mekVersion, + encDek: dek, + dekVersion: new Date(dekVersion), + hskVersion, + contentHmac, + contentType, + encContentIv: contentIv, + encName: name, + encNameIv: nameIv, + encCreatedAt: createdAt ?? null, + encCreatedAtIv: createdAtIv ?? null, + encLastModifiedAt: lastModifiedAt, + encLastModifiedAtIv: lastModifiedAtIv, + } satisfies FileMetadata; +}; + +export const POST: RequestHandler = async ({ locals, request }) => { + const { userId } = await authorize(locals, "activeClient"); + + const contentType = request.headers.get("Content-Type"); + if (!contentType?.startsWith("multipart/form-data") || !request.body) { + error(400, "Invalid request body"); + } + + return new Promise((resolve, reject) => { + const bb = Busboy({ headers: { "content-type": contentType } }); + const handler = + (f: (...args: T) => Promise) => + (...args: T) => { + f(...args).catch(reject); + }; + + let metadata: FileMetadata | null = null; + let content: Readable | null = null; + + bb.on( + "field", + handler(async (fieldname, val) => { + if (fieldname !== "metadata") error(400, "Invalid request body"); + if (metadata || content) error(400, "Invalid request body"); + metadata = parseFileMetadata(userId, val); + }), + ); + bb.on( + "file", + handler(async (fieldname, file) => { + if (fieldname !== "content") error(400, "Invalid request body"); + if (!metadata || content) error(400, "Invalid request body"); + content = file; + + await uploadFile(metadata, content); + resolve(text("File uploaded", { headers: { "Content-Type": "text/plain" } })); + }), + ); + bb.on("error", (e) => content?.emit("error", e) ?? reject(e)); + + request.body!.pipeTo(Writable.toWeb(bb)).catch(() => {}); // busboy will handle the error + }); };