diff options
Diffstat (limited to 'packages/trpc')
| -rw-r--r-- | packages/trpc/lib/attachments.ts | 5 | ||||
| -rw-r--r-- | packages/trpc/models/bookmarks.ts | 80 | ||||
| -rw-r--r-- | packages/trpc/routers/bookmarks.ts | 21 |
3 files changed, 99 insertions, 7 deletions
diff --git a/packages/trpc/lib/attachments.ts b/packages/trpc/lib/attachments.ts index 739aa8f5..e886b821 100644 --- a/packages/trpc/lib/attachments.ts +++ b/packages/trpc/lib/attachments.ts @@ -14,6 +14,7 @@ export function mapDBAssetTypeToUserType(assetType: AssetTypes): ZAssetType { [AssetTypes.LINK_PRECRAWLED_ARCHIVE]: "precrawledArchive", [AssetTypes.LINK_BANNER_IMAGE]: "bannerImage", [AssetTypes.LINK_VIDEO]: "video", + [AssetTypes.LINK_HTML_CONTENT]: "linkHtmlContent", [AssetTypes.BOOKMARK_ASSET]: "bookmarkAsset", [AssetTypes.UNKNOWN]: "bannerImage", }; @@ -31,6 +32,7 @@ export function mapSchemaAssetTypeToDB( bannerImage: AssetTypes.LINK_BANNER_IMAGE, video: AssetTypes.LINK_VIDEO, bookmarkAsset: AssetTypes.BOOKMARK_ASSET, + linkHtmlContent: AssetTypes.LINK_HTML_CONTENT, unknown: AssetTypes.UNKNOWN, }; return map[assetType]; @@ -45,6 +47,7 @@ export function humanFriendlyNameForAssertType(type: ZAssetType) { bannerImage: "Banner Image", video: "Video", bookmarkAsset: "Bookmark Asset", + linkHtmlContent: "HTML Content", unknown: "Unknown", }; return map[type]; @@ -59,6 +62,7 @@ export function isAllowedToAttachAsset(type: ZAssetType) { bannerImage: true, video: false, bookmarkAsset: false, + linkHtmlContent: false, unknown: false, }; return map[type]; @@ -73,6 +77,7 @@ export function isAllowedToDetachAsset(type: ZAssetType) { bannerImage: true, video: true, bookmarkAsset: false, + linkHtmlContent: false, unknown: false, }; return map[type]; diff --git a/packages/trpc/models/bookmarks.ts b/packages/trpc/models/bookmarks.ts index 986fca58..07b3832d 100644 --- a/packages/trpc/models/bookmarks.ts +++ b/packages/trpc/models/bookmarks.ts @@ -27,6 +27,7 @@ import { rssFeedImportsTable, tagsOnBookmarks, } from "@karakeep/db/schema"; +import { readAsset } from "@karakeep/shared/assetdb"; import serverConfig from "@karakeep/shared/config"; import { createSignedToken, @@ -46,6 +47,7 @@ import { getBookmarkLinkAssetIdOrUrl, getBookmarkTitle, } from "@karakeep/shared/utils/bookmarkUtils"; +import { htmlToPlainText } from "@karakeep/shared/utils/htmlUtils"; import { AuthedContext } from ".."; import { mapDBAssetTypeToUserType } from "../lib/attachments"; @@ -202,8 +204,11 @@ export class Bookmark implements PrivacyAware { imageUrl: row.bookmarkLinks.imageUrl, favicon: row.bookmarkLinks.favicon, htmlContent: input.includeContent - ? row.bookmarkLinks.htmlContent + ? row.bookmarkLinks.contentAssetId + ? null // Will be populated later from asset + : row.bookmarkLinks.htmlContent : null, + contentAssetId: row.bookmarkLinks.contentAssetId, crawledAt: row.bookmarkLinks.crawledAt, author: row.bookmarkLinks.author, publisher: row.bookmarkLinks.publisher, @@ -300,6 +305,33 @@ export class Bookmark implements PrivacyAware { const bookmarksArr = Object.values(bookmarksRes); + // Fetch HTML content from assets for bookmarks that have contentAssetId (large content) + if (input.includeContent) { + await Promise.all( + bookmarksArr.map(async (bookmark) => { + if ( + bookmark.content.type === BookmarkTypes.LINK && + bookmark.content.contentAssetId && + !bookmark.content.htmlContent // Only fetch if not already inline + ) { + try { + const asset = await readAsset({ + userId: ctx.user.id, + assetId: bookmark.content.contentAssetId, + }); + bookmark.content.htmlContent = asset.asset.toString("utf8"); + } catch (error) { + // If asset reading fails, keep htmlContent as null + console.warn( + `Failed to read HTML content asset ${bookmark.content.contentAssetId}:`, + error, + ); + } + } + }), + ); + } + bookmarksArr.sort((a, b) => { if (a.createdAt != b.createdAt) { return input.sortOrder === "asc" @@ -427,4 +459,50 @@ export class Bookmark implements PrivacyAware { bannerImageUrl: getBannerImageUrl(this.bookmark.content), }; } + + static async getBookmarkHtmlContent( + { + contentAssetId, + htmlContent, + }: { + contentAssetId: string | null; + htmlContent: string | null; + }, + userId: string, + ): Promise<string | null> { + if (contentAssetId) { + // Read large HTML content from asset + const asset = await readAsset({ + userId, + assetId: contentAssetId, + }); + return asset.asset.toString("utf8"); + } else if (htmlContent) { + return htmlContent; + } + return null; + } + + static async getBookmarkPlainTextContent( + { + contentAssetId, + htmlContent, + }: { + contentAssetId: string | null; + htmlContent: string | null; + }, + userId: string, + ): Promise<string | null> { + const content = await this.getBookmarkHtmlContent( + { + contentAssetId, + htmlContent, + }, + userId, + ); + if (!content) { + return null; + } + return htmlToPlainText(content); + } } diff --git a/packages/trpc/routers/bookmarks.ts b/packages/trpc/routers/bookmarks.ts index f1fe10d7..77f40878 100644 --- a/packages/trpc/routers/bookmarks.ts +++ b/packages/trpc/routers/bookmarks.ts @@ -118,7 +118,7 @@ async function getBookmark( }); } - return toZodSchema(bookmark, includeContent); + return await toZodSchema(bookmark, includeContent); } async function attemptToDedupLink(ctx: AuthedContext, url: string) { @@ -177,10 +177,10 @@ async function cleanupAssetForBookmark( ); } -function toZodSchema( +async function toZodSchema( bookmark: BookmarkQueryReturnType, includeContent: boolean, -): ZBookmark { +): Promise<ZBookmark> { const { tagsOnBookmarks, link, text, asset, assets, ...rest } = bookmark; let content: ZBookmarkContent = { @@ -208,7 +208,9 @@ function toZodSchema( description: link.description, imageUrl: link.imageUrl, favicon: link.favicon, - htmlContent: includeContent ? link.htmlContent : null, + htmlContent: includeContent + ? await Bookmark.getBookmarkHtmlContent(link, bookmark.userId) + : null, crawledAt: link.crawledAt, author: link.author, publisher: link.publisher, @@ -806,7 +808,9 @@ export const bookmarksAppRouter = router({ } return { - bookmarks: results.map((b) => toZodSchema(b, input.includeContent)), + bookmarks: await Promise.all( + results.map((b) => toZodSchema(b, input.includeContent)), + ), nextCursor: resp.hits.length + resp.offset >= resp.estimatedTotalHits ? null @@ -1052,10 +1056,15 @@ export const bookmarksAppRouter = router({ }); } + const content = await Bookmark.getBookmarkPlainTextContent( + bookmark, + ctx.user.id, + ); + const bookmarkDetails = ` Title: ${bookmark.title ?? ""} Description: ${bookmark.description ?? ""} -Content: ${bookmark.content ?? ""} +Content: ${content} Publisher: ${bookmark.publisher ?? ""} Author: ${bookmark.author ?? ""} `; |
