aboutsummaryrefslogtreecommitdiffstats
path: root/packages/api
diff options
context:
space:
mode:
Diffstat (limited to 'packages/api')
-rw-r--r--packages/api/index.ts4
-rw-r--r--packages/api/middlewares/auth.ts17
-rw-r--r--packages/api/package.json2
-rw-r--r--packages/api/routes/rss.ts51
-rw-r--r--packages/api/utils/rss.ts54
5 files changed, 126 insertions, 2 deletions
diff --git a/packages/api/index.ts b/packages/api/index.ts
index 00919f3e..a3ba8d42 100644
--- a/packages/api/index.ts
+++ b/packages/api/index.ts
@@ -9,6 +9,7 @@ import assets from "./routes/assets";
import bookmarks from "./routes/bookmarks";
import highlights from "./routes/highlights";
import lists from "./routes/lists";
+import rss from "./routes/rss";
import tags from "./routes/tags";
import users from "./routes/users";
@@ -22,7 +23,8 @@ const v1 = new Hono<{
.route("/lists", lists)
.route("/tags", tags)
.route("/users", users)
- .route("/assets", assets);
+ .route("/assets", assets)
+ .route("/rss", rss);
const app = new Hono<{
Variables: {
diff --git a/packages/api/middlewares/auth.ts b/packages/api/middlewares/auth.ts
index 7f39a6f9..42bca6c8 100644
--- a/packages/api/middlewares/auth.ts
+++ b/packages/api/middlewares/auth.ts
@@ -1,11 +1,26 @@
import { createMiddleware } from "hono/factory";
import { HTTPException } from "hono/http-exception";
-import { AuthedContext, createCallerFactory } from "@karakeep/trpc";
+import { AuthedContext, Context, createCallerFactory } from "@karakeep/trpc";
import { appRouter } from "@karakeep/trpc/routers/_app";
const createCaller = createCallerFactory(appRouter);
+export const unauthedMiddleware = createMiddleware<{
+ Variables: {
+ ctx: Context;
+ api: ReturnType<typeof createCaller>;
+ };
+}>(async (c, next) => {
+ if (!c.var.ctx) {
+ throw new HTTPException(401, {
+ message: "Unauthorized",
+ });
+ }
+ c.set("api", createCaller(c.get("ctx")));
+ await next();
+});
+
export const authMiddleware = createMiddleware<{
Variables: {
ctx: AuthedContext;
diff --git a/packages/api/package.json b/packages/api/package.json
index f968ed94..82b2b9d0 100644
--- a/packages/api/package.json
+++ b/packages/api/package.json
@@ -18,6 +18,7 @@
"@karakeep/shared": "workspace:*",
"@karakeep/trpc": "workspace:*",
"hono": "^4.7.10",
+ "rss": "^1.2.2",
"zod": "^3.24.2"
},
"devDependencies": {
@@ -26,6 +27,7 @@
"@karakeep/tsconfig": "workspace:^0.1.0",
"@types/bcryptjs": "^2.4.6",
"@types/deep-equal": "^1.0.4",
+ "@types/rss": "^0.0.32",
"vite-tsconfig-paths": "^4.3.1",
"vitest": "^1.6.1"
},
diff --git a/packages/api/routes/rss.ts b/packages/api/routes/rss.ts
new file mode 100644
index 00000000..81c9756c
--- /dev/null
+++ b/packages/api/routes/rss.ts
@@ -0,0 +1,51 @@
+import { zValidator } from "@hono/zod-validator";
+import { Hono } from "hono";
+import { z } from "zod";
+
+import serverConfig from "@karakeep/shared/config";
+import { MAX_NUM_BOOKMARKS_PER_PAGE } from "@karakeep/shared/types/bookmarks";
+import { List } from "@karakeep/trpc/models/lists";
+
+import { unauthedMiddleware } from "../middlewares/auth";
+import { toRSS } from "../utils/rss";
+
+const app = new Hono().get(
+ "/lists/:listId",
+ zValidator(
+ "query",
+ z.object({
+ token: z.string().min(1),
+ limit: z.coerce
+ .number()
+ .min(1)
+ .max(MAX_NUM_BOOKMARKS_PER_PAGE)
+ .optional(),
+ }),
+ ),
+ unauthedMiddleware,
+ async (c) => {
+ const listId = c.req.param("listId");
+ const searchParams = c.req.valid("query");
+ const token = searchParams.token;
+
+ const res = await List.getForRss(c.var.ctx, listId, token, {
+ limit: searchParams.limit ?? 20,
+ });
+ const list = res.list;
+
+ const rssFeed = toRSS(
+ {
+ title: `Bookmarks from ${list.icon} ${list.name}`,
+ feedUrl: `${serverConfig.publicApiUrl}/v1/rss/lists/${listId}`,
+ siteUrl: `${serverConfig.publicUrl}/dashboard/lists/${listId}`,
+ description: list.description ?? undefined,
+ },
+ res.bookmarks,
+ );
+
+ c.header("Content-Type", "application/rss+xml");
+ return c.body(rssFeed);
+ },
+);
+
+export default app;
diff --git a/packages/api/utils/rss.ts b/packages/api/utils/rss.ts
new file mode 100644
index 00000000..079b3f5a
--- /dev/null
+++ b/packages/api/utils/rss.ts
@@ -0,0 +1,54 @@
+import RSS from "rss";
+
+import serverConfig from "@karakeep/shared/config";
+import {
+ BookmarkTypes,
+ ZPublicBookmark,
+} from "@karakeep/shared/types/bookmarks";
+import { getAssetUrl } from "@karakeep/shared/utils/assetUtils";
+
+export function toRSS(
+ params: {
+ title: string;
+ description?: string;
+ feedUrl: string;
+ siteUrl: string;
+ },
+ bookmarks: ZPublicBookmark[],
+) {
+ const feed = new RSS({
+ title: params.title,
+ feed_url: params.feedUrl,
+ site_url: params.siteUrl,
+ description: params.description,
+ generator: "Karakeep",
+ });
+
+ bookmarks
+ .filter(
+ (b) =>
+ b.content.type === BookmarkTypes.LINK ||
+ b.content.type === BookmarkTypes.ASSET,
+ )
+ .forEach((bookmark) => {
+ feed.item({
+ date: bookmark.createdAt,
+ title: bookmark.title ?? "",
+ url:
+ bookmark.content.type === BookmarkTypes.LINK
+ ? bookmark.content.url
+ : bookmark.content.type === BookmarkTypes.ASSET
+ ? `${serverConfig.publicUrl}${getAssetUrl(bookmark.content.assetId)}`
+ : "",
+ guid: bookmark.id,
+ author:
+ bookmark.content.type === BookmarkTypes.LINK
+ ? (bookmark.content.author ?? undefined)
+ : undefined,
+ categories: bookmark.tags,
+ description: bookmark.description ?? "",
+ });
+ });
+
+ return feed.xml({ indent: true });
+}