From ea1d0023bfee55358ebb1a96f3d06e783a219c0d Mon Sep 17 00:00:00 2001 From: Mohamed Bassem Date: Sun, 1 Jun 2025 20:46:41 +0100 Subject: feat: Add support for public lists (#1511) * WIP: public lists * Drop viewing modes * Add the public endpoint for assets * regen the openapi spec * proper handling for different asset types * Add num bookmarks and a no bookmark banner * Correctly set page title * Add a not-found page * merge the RSS and public list endpoints * Add e2e tests for the public endpoints * Redesign the share list modal * Make NEXTAUTH_SECRET not required * propery render text bookmarks * rebase migration * fix public token tests * Add more tests --- packages/trpc/models/bookmarks.ts | 59 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) (limited to 'packages/trpc/models/bookmarks.ts') diff --git a/packages/trpc/models/bookmarks.ts b/packages/trpc/models/bookmarks.ts index 524749f9..6e9e5651 100644 --- a/packages/trpc/models/bookmarks.ts +++ b/packages/trpc/models/bookmarks.ts @@ -27,6 +27,9 @@ import { rssFeedImportsTable, tagsOnBookmarks, } from "@karakeep/db/schema"; +import serverConfig from "@karakeep/shared/config"; +import { createSignedToken } from "@karakeep/shared/signedTokens"; +import { zAssetSignedTokenSchema } from "@karakeep/shared/types/assets"; import { BookmarkTypes, DEFAULT_NUM_BOOKMARKS_PER_PAGE, @@ -36,7 +39,10 @@ import { ZPublicBookmark, } from "@karakeep/shared/types/bookmarks"; import { ZCursor } from "@karakeep/shared/types/pagination"; -import { getBookmarkTitle } from "@karakeep/shared/utils/bookmarkUtils"; +import { + getBookmarkLinkAssetIdOrUrl, + getBookmarkTitle, +} from "@karakeep/shared/utils/bookmarkUtils"; import { AuthedContext } from ".."; import { mapDBAssetTypeToUserType } from "../lib/attachments"; @@ -321,6 +327,14 @@ export class Bookmark implements PrivacyAware { } asPublicBookmark(): ZPublicBookmark { + const getPublicSignedAssetUrl = (assetId: string) => { + const payload: z.infer = { + assetId, + userId: this.ctx.user.id, + }; + const signedToken = createSignedToken(payload); + return `${serverConfig.publicApiUrl}/public/assets/${assetId}?token=${signedToken}`; + }; const getContent = ( content: ZBookmarkContent, ): ZPublicBookmark["content"] => { @@ -342,6 +356,7 @@ export class Bookmark implements PrivacyAware { type: BookmarkTypes.ASSET, assetType: content.assetType, assetId: content.assetId, + assetUrl: getPublicSignedAssetUrl(content.assetId), fileName: content.fileName, sourceUrl: content.sourceUrl, }; @@ -352,6 +367,47 @@ export class Bookmark implements PrivacyAware { } }; + const getBannerImageUrl = (content: ZBookmarkContent): string | null => { + switch (content.type) { + case BookmarkTypes.LINK: { + const assetIdOrUrl = getBookmarkLinkAssetIdOrUrl(content); + if (!assetIdOrUrl) { + return null; + } + if (assetIdOrUrl.localAsset) { + return getPublicSignedAssetUrl(assetIdOrUrl.assetId); + } else { + return assetIdOrUrl.url; + } + } + case BookmarkTypes.TEXT: { + return null; + } + case BookmarkTypes.ASSET: { + switch (content.assetType) { + case "image": + return `${getPublicSignedAssetUrl(content.assetId)}`; + case "pdf": { + const screenshotAssetId = this.bookmark.assets.find( + (r) => r.assetType === "assetScreenshot", + )?.id; + if (!screenshotAssetId) { + return null; + } + return getPublicSignedAssetUrl(screenshotAssetId); + } + default: { + const _exhaustiveCheck: never = content.assetType; + return null; + } + } + } + default: { + throw new Error("Unknown bookmark content type"); + } + } + }; + // WARNING: Everything below is exposed in the public APIs, don't use spreads! return { id: this.bookmark.id, @@ -360,6 +416,7 @@ export class Bookmark implements PrivacyAware { title: getBookmarkTitle(this.bookmark), tags: this.bookmark.tags.map((t) => t.name), content: getContent(this.bookmark.content), + bannerImageUrl: getBannerImageUrl(this.bookmark.content), }; } } -- cgit v1.2.3-70-g09d2