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/lists.ts | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) (limited to 'packages/trpc/models/lists.ts') diff --git a/packages/trpc/models/lists.ts b/packages/trpc/models/lists.ts index 4413a8cd..2631ca7e 100644 --- a/packages/trpc/models/lists.ts +++ b/packages/trpc/models/lists.ts @@ -1,6 +1,6 @@ import crypto from "node:crypto"; import { TRPCError } from "@trpc/server"; -import { and, count, eq } from "drizzle-orm"; +import { and, count, eq, or } from "drizzle-orm"; import invariant from "tiny-invariant"; import { z } from "zod"; @@ -8,11 +8,13 @@ import { SqliteError } from "@karakeep/db"; import { bookmarkLists, bookmarksInLists } from "@karakeep/db/schema"; import { triggerRuleEngineOnEvent } from "@karakeep/shared/queues"; import { parseSearchQuery } from "@karakeep/shared/searchQueryParser"; +import { ZSortOrder } from "@karakeep/shared/types/bookmarks"; import { ZBookmarkList, zEditBookmarkListSchemaWithValidation, zNewBookmarkListSchema, } from "@karakeep/shared/types/lists"; +import { ZCursor } from "@karakeep/shared/types/pagination"; import { AuthedContext, Context } from ".."; import { buildImpersonatingAuthedContext } from "../lib/impersonate"; @@ -61,18 +63,23 @@ export abstract class List implements PrivacyAware { } } - static async getForRss( + static async getPublicListContents( ctx: Context, listId: string, - token: string, + token: string | null, pagination: { limit: number; + order: Exclude; + cursor: ZCursor | null | undefined; }, ) { const listdb = await ctx.db.query.bookmarkLists.findFirst({ where: and( eq(bookmarkLists.id, listId), - eq(bookmarkLists.rssToken, token), + or( + eq(bookmarkLists.public, true), + token !== null ? eq(bookmarkLists.rssToken, token) : undefined, + ), ), }); if (!listdb) { @@ -85,7 +92,6 @@ export abstract class List implements PrivacyAware { // The token here acts as an authed context, so we can create // an impersonating context for the list owner as long as // we don't leak the context. - const authedCtx = await buildImpersonatingAuthedContext(listdb.userId); const list = List.fromData(authedCtx, listdb); const bookmarkIds = await list.getBookmarkIds(); @@ -94,7 +100,8 @@ export abstract class List implements PrivacyAware { ids: bookmarkIds, includeContent: false, limit: pagination.limit, - sortOrder: "desc", + sortOrder: pagination.order, + cursor: pagination.cursor, }); return { @@ -102,8 +109,10 @@ export abstract class List implements PrivacyAware { icon: list.list.icon, name: list.list.name, description: list.list.description, + numItems: bookmarkIds.length, }, bookmarks: bookmarks.bookmarks.map((b) => b.asPublicBookmark()), + nextCursor: bookmarks.nextCursor, }; } @@ -185,6 +194,7 @@ export abstract class List implements PrivacyAware { icon: input.icon, parentId: input.parentId, query: input.query, + public: input.public, }) .where( and( -- cgit v1.2.3-70-g09d2