import { createContextFromRequest } from "@/server/api/client"; import { TRPCError } from "@trpc/server"; import type { ZUploadResponse } from "@lifetracker/shared/types/uploads"; import { assets, AssetTypes } from "@lifetracker/db/schema"; import { newAssetId, saveAsset, SUPPORTED_UPLOAD_ASSET_TYPES, } from "@lifetracker/shared/assetdb"; import serverConfig from "@lifetracker/shared/config"; const MAX_UPLOAD_SIZE_BYTES = serverConfig.maxAssetSizeMb * 1024 * 1024; export const dynamic = "force-dynamic"; export async function POST(request: Request) { const ctx = await createContextFromRequest(request); if (!ctx.user) { return Response.json({ error: "Unauthorized" }, { status: 401 }); } if (serverConfig.demoMode) { throw new TRPCError({ message: "Mutations are not allowed in demo mode", code: "FORBIDDEN", }); } const formData = await request.formData(); const data = formData.get("file") ?? formData.get("image"); let buffer; let contentType; if (data instanceof File) { contentType = data.type; if (!SUPPORTED_UPLOAD_ASSET_TYPES.has(contentType)) { return Response.json( { error: "Unsupported asset type" }, { status: 400 }, ); } if (data.size > MAX_UPLOAD_SIZE_BYTES) { return Response.json({ error: "Asset is too big" }, { status: 413 }); } buffer = Buffer.from(await data.arrayBuffer()); } else { return Response.json({ error: "Bad request" }, { status: 400 }); } const fileName = data.name; const [assetDb] = await ctx.db .insert(assets) .values({ id: newAssetId(), // Initially, uploads are uploaded for unknown purpose // And without an attached bookmark. assetType: AssetTypes.UNKNOWN, bookmarkId: null, userId: ctx.user.id, contentType, size: data.size, fileName, }) .returning(); const assetId = assetDb.id; await saveAsset({ userId: ctx.user.id, assetId, metadata: { contentType, fileName }, asset: buffer, }); return Response.json({ assetId, contentType, size: buffer.byteLength, fileName, } satisfies ZUploadResponse); }