aboutsummaryrefslogtreecommitdiffstats
path: root/apps/web/app/api/assets/route.ts
diff options
context:
space:
mode:
authorMohamed Bassem <me@mbassem.com>2025-05-18 16:58:08 +0100
committerGitHub <noreply@github.com>2025-05-18 16:58:08 +0100
commit3505cb7d6416d101a4fcb1be27fc22e0171bacd2 (patch)
treeef9f55504b8a5b20add8c0ebe916972ab4ab0178 /apps/web/app/api/assets/route.ts
parent74e74fa6425f072107de3a9bc9dd8f91c5ac9a7d (diff)
downloadkarakeep-3505cb7d6416d101a4fcb1be27fc22e0171bacd2.tar.zst
refactor: Migrate from NextJs's API routes to Hono based routes for the API (#1432)
* Setup Hono and migrate the highlights API there * Implement the tags and lists endpoint * Implement the bookmarks and users endpoints * Add the trpc error code adapter * Remove the old nextjs handlers * fix api key not found handling * Fix trpc error handling * Fix 204 handling * Fix search ordering * Implement the singlefile endpoint * Implement the asset serving endpoints * Implement webauth * Add hono as a catch all route under api * fix tests
Diffstat (limited to 'apps/web/app/api/assets/route.ts')
-rw-r--r--apps/web/app/api/assets/route.ts124
1 files changed, 0 insertions, 124 deletions
diff --git a/apps/web/app/api/assets/route.ts b/apps/web/app/api/assets/route.ts
deleted file mode 100644
index e2e1e63e..00000000
--- a/apps/web/app/api/assets/route.ts
+++ /dev/null
@@ -1,124 +0,0 @@
-import * as fs from "fs";
-import * as os from "os";
-import * as path from "path";
-import { Readable } from "stream";
-import { pipeline } from "stream/promises";
-import { createContextFromRequest } from "@/server/api/client";
-import { TRPCError } from "@trpc/server";
-
-import type { ZUploadResponse } from "@karakeep/shared/types/uploads";
-import { assets, AssetTypes } from "@karakeep/db/schema";
-import {
- newAssetId,
- saveAssetFromFile,
- SUPPORTED_UPLOAD_ASSET_TYPES,
-} from "@karakeep/shared/assetdb";
-import serverConfig from "@karakeep/shared/config";
-import { AuthedContext } from "@karakeep/trpc";
-
-const MAX_UPLOAD_SIZE_BYTES = serverConfig.maxAssetSizeMb * 1024 * 1024;
-
-export const dynamic = "force-dynamic";
-
-// Helper to convert Web Stream to Node Stream (requires Node >= 16.5 / 14.18)
-function webStreamToNode(webStream: ReadableStream<Uint8Array>): Readable {
- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any
- return Readable.fromWeb(webStream as any); // Type assertion might be needed
-}
-
-export async function uploadFromPostData(
- user: AuthedContext["user"],
- db: AuthedContext["db"],
- formData: FormData,
-): Promise<
- | { error: string; status: number }
- | {
- assetId: string;
- contentType: string;
- fileName: string;
- size: number;
- }
-> {
- const data = formData.get("file") ?? formData.get("image");
-
- if (!(data instanceof File)) {
- return { error: "Bad request", status: 400 };
- }
-
- const contentType = data.type;
- const fileName = data.name;
- if (!SUPPORTED_UPLOAD_ASSET_TYPES.has(contentType)) {
- return { error: "Unsupported asset type", status: 400 };
- }
- if (data.size > MAX_UPLOAD_SIZE_BYTES) {
- return { error: "Asset is too big", status: 413 };
- }
-
- let tempFilePath: string | undefined;
-
- try {
- tempFilePath = path.join(os.tmpdir(), `karakeep-upload-${Date.now()}`);
- await pipeline(
- webStreamToNode(data.stream()),
- fs.createWriteStream(tempFilePath),
- );
- const [assetDb] = await db
- .insert(assets)
- .values({
- id: newAssetId(),
- // Initially, uploads are uploaded for unknown purpose
- // And without an attached bookmark.
- assetType: AssetTypes.UNKNOWN,
- bookmarkId: null,
- userId: user.id,
- contentType,
- size: data.size,
- fileName,
- })
- .returning();
-
- await saveAssetFromFile({
- userId: user.id,
- assetId: assetDb.id,
- assetPath: tempFilePath,
- metadata: { contentType, fileName },
- });
-
- return {
- assetId: assetDb.id,
- contentType,
- size: data.size,
- fileName,
- };
- } finally {
- if (tempFilePath) {
- await fs.promises.unlink(tempFilePath).catch(() => ({}));
- }
- }
-}
-
-export async function POST(request: Request) {
- const ctx = await createContextFromRequest(request);
- if (ctx.user === null) {
- 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 resp = await uploadFromPostData(ctx.user, ctx.db, formData);
- if ("error" in resp) {
- return Response.json({ error: resp.error }, { status: resp.status });
- }
-
- return Response.json({
- assetId: resp.assetId,
- contentType: resp.contentType,
- size: resp.size,
- fileName: resp.fileName,
- } satisfies ZUploadResponse);
-}