aboutsummaryrefslogtreecommitdiffstats
path: root/packages/trpc
diff options
context:
space:
mode:
Diffstat (limited to 'packages/trpc')
-rw-r--r--packages/trpc/lib/attachments.ts5
-rw-r--r--packages/trpc/models/bookmarks.ts80
-rw-r--r--packages/trpc/routers/bookmarks.ts21
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 ?? ""}
`;