From 013ca67c151b51575151424084f6358522b83579 Mon Sep 17 00:00:00 2001 From: Mohamed Bassem Date: Wed, 24 Dec 2025 13:58:37 +0200 Subject: refactor: move assets to their own model (#2301) * refactor: move assets to their own model * move asset privacy checks to the model --- packages/trpc/routers/assets.ts | 160 +++---------------------------------- packages/trpc/routers/bookmarks.ts | 19 ++--- 2 files changed, 18 insertions(+), 161 deletions(-) (limited to 'packages/trpc/routers') diff --git a/packages/trpc/routers/assets.ts b/packages/trpc/routers/assets.ts index 7be85446..c75f1e2e 100644 --- a/packages/trpc/routers/assets.ts +++ b/packages/trpc/routers/assets.ts @@ -1,57 +1,20 @@ -import { TRPCError } from "@trpc/server"; -import { and, desc, eq, sql } from "drizzle-orm"; import { z } from "zod"; -import { assets, bookmarks } from "@karakeep/db/schema"; -import { deleteAsset } from "@karakeep/shared/assetdb"; import { zAssetSchema, zAssetTypesSchema, } from "@karakeep/shared/types/bookmarks"; -import { authedProcedure, Context, router } from "../index"; -import { - isAllowedToAttachAsset, - isAllowedToDetachAsset, - mapDBAssetTypeToUserType, - mapSchemaAssetTypeToDB, -} from "../lib/attachments"; +import { authedProcedure, router } from "../index"; +import { Asset } from "../models/assets"; import { ensureBookmarkOwnership } from "./bookmarks"; -export const ensureAssetOwnership = async (opts: { - ctx: Context; - assetId: string; -}) => { - const asset = await opts.ctx.db.query.assets.findFirst({ - where: eq(bookmarks.id, opts.assetId), - }); - if (!opts.ctx.user) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "User is not authorized", - }); - } - if (!asset) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Asset not found", - }); - } - if (asset.userId != opts.ctx.user.id) { - throw new TRPCError({ - code: "FORBIDDEN", - message: "User is not allowed to access resource", - }); - } - return asset; -}; - export const assetsAppRouter = router({ list: authedProcedure .input( z.object({ limit: z.number().min(1).max(100).default(20), - cursor: z.number().nullish(), // page number + cursor: z.number().nullish(), }), ) .output( @@ -71,29 +34,10 @@ export const assetsAppRouter = router({ }), ) .query(async ({ input, ctx }) => { - const page = input.cursor ?? 1; - const [results, totalCount] = await Promise.all([ - ctx.db - .select() - .from(assets) - .where(eq(assets.userId, ctx.user.id)) - .orderBy(desc(assets.size)) - .limit(input.limit) - .offset((page - 1) * input.limit), - ctx.db - .select({ count: sql`count(*)` }) - .from(assets) - .where(eq(assets.userId, ctx.user.id)), - ]); - - return { - assets: results.map((a) => ({ - ...a, - assetType: mapDBAssetTypeToUserType(a.assetType), - })), - nextCursor: page * input.limit < totalCount[0].count ? page + 1 : null, - totalCount: totalCount[0].count, - }; + return await Asset.list(ctx, { + limit: input.limit, + cursor: input.cursor ?? null, + }); }), attachAsset: authedProcedure .input( @@ -108,29 +52,7 @@ export const assetsAppRouter = router({ .output(zAssetSchema) .use(ensureBookmarkOwnership) .mutation(async ({ input, ctx }) => { - await ensureAssetOwnership({ ctx, assetId: input.asset.id }); - if (!isAllowedToAttachAsset(input.asset.assetType)) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "You can't attach this type of asset", - }); - } - const [updatedAsset] = await ctx.db - .update(assets) - .set({ - assetType: mapSchemaAssetTypeToDB(input.asset.assetType), - bookmarkId: input.bookmarkId, - }) - .where( - and(eq(assets.id, input.asset.id), eq(assets.userId, ctx.user.id)), - ) - .returning(); - - return { - id: updatedAsset.id, - assetType: mapDBAssetTypeToUserType(updatedAsset.assetType), - fileName: updatedAsset.fileName, - }; + return await Asset.attachAsset(ctx, input); }), replaceAsset: authedProcedure .input( @@ -143,41 +65,7 @@ export const assetsAppRouter = router({ .output(z.void()) .use(ensureBookmarkOwnership) .mutation(async ({ input, ctx }) => { - await Promise.all([ - ensureAssetOwnership({ ctx, assetId: input.oldAssetId }), - ensureAssetOwnership({ ctx, assetId: input.newAssetId }), - ]); - const [oldAsset] = await ctx.db - .select() - .from(assets) - .where( - and(eq(assets.id, input.oldAssetId), eq(assets.userId, ctx.user.id)), - ) - .limit(1); - if ( - !isAllowedToAttachAsset(mapDBAssetTypeToUserType(oldAsset.assetType)) - ) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "You can't attach this type of asset", - }); - } - - await ctx.db.transaction(async (tx) => { - await tx.delete(assets).where(eq(assets.id, input.oldAssetId)); - await tx - .update(assets) - .set({ - bookmarkId: input.bookmarkId, - assetType: oldAsset.assetType, - }) - .where(eq(assets.id, input.newAssetId)); - }); - - await deleteAsset({ - userId: ctx.user.id, - assetId: input.oldAssetId, - }).catch(() => ({})); + await Asset.replaceAsset(ctx, input); }), detachAsset: authedProcedure .input( @@ -189,34 +77,6 @@ export const assetsAppRouter = router({ .output(z.void()) .use(ensureBookmarkOwnership) .mutation(async ({ input, ctx }) => { - await ensureAssetOwnership({ ctx, assetId: input.assetId }); - const [oldAsset] = await ctx.db - .select() - .from(assets) - .where( - and(eq(assets.id, input.assetId), eq(assets.userId, ctx.user.id)), - ); - if ( - !isAllowedToDetachAsset(mapDBAssetTypeToUserType(oldAsset.assetType)) - ) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "You can't deattach this type of asset", - }); - } - const result = await ctx.db - .delete(assets) - .where( - and( - eq(assets.id, input.assetId), - eq(assets.bookmarkId, input.bookmarkId), - ), - ); - if (result.changes == 0) { - throw new TRPCError({ code: "NOT_FOUND" }); - } - await deleteAsset({ userId: ctx.user.id, assetId: input.assetId }).catch( - () => ({}), - ); + await Asset.detachAsset(ctx, input); }), }); diff --git a/packages/trpc/routers/bookmarks.ts b/packages/trpc/routers/bookmarks.ts index 65d401e2..a9d0df38 100644 --- a/packages/trpc/routers/bookmarks.ts +++ b/packages/trpc/routers/bookmarks.ts @@ -48,9 +48,9 @@ import { normalizeTagName } from "@karakeep/shared/utils/tag"; import type { AuthedContext } from "../index"; import { authedProcedure, createRateLimitMiddleware, router } from "../index"; import { getBookmarkIdsFromMatcher } from "../lib/search"; +import { Asset } from "../models/assets"; import { BareBookmark, Bookmark } from "../models/bookmarks"; import { ImportSession } from "../models/importSessions"; -import { ensureAssetOwnership } from "./assets"; export const ensureBookmarkOwnership = experimental_trpcMiddleware<{ ctx: AuthedContext; @@ -178,10 +178,7 @@ export const bookmarksAppRouter = router({ .returning() )[0]; if (input.precrawledArchiveId) { - await ensureAssetOwnership({ - ctx, - assetId: input.precrawledArchiveId, - }); + await Asset.ensureOwnership(ctx, input.precrawledArchiveId); await tx .update(assets) .set({ @@ -232,13 +229,13 @@ export const bookmarksAppRouter = router({ sourceUrl: null, }) .returning(); - const uploadedAsset = await ensureAssetOwnership({ - ctx, - assetId: input.assetId, - }); + const uploadedAsset = await Asset.fromId(ctx, input.assetId); + uploadedAsset.ensureOwnership(); if ( - !uploadedAsset.contentType || - !SUPPORTED_BOOKMARK_ASSET_TYPES.has(uploadedAsset.contentType) + !uploadedAsset.asset.contentType || + !SUPPORTED_BOOKMARK_ASSET_TYPES.has( + uploadedAsset.asset.contentType, + ) ) { throw new TRPCError({ code: "BAD_REQUEST", -- cgit v1.2.3-70-g09d2