lifetracker/apps/web/app/api/assets/route.ts

78 lines
2.1 KiB
TypeScript

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);
}