aboutsummaryrefslogtreecommitdiffstats
path: root/packages
diff options
context:
space:
mode:
authorMohamedBassem <me@mbassem.com>2024-03-17 13:12:11 +0000
committerMohamedBassem <me@mbassem.com>2024-03-17 13:12:11 +0000
commitfc8eb79b98bbea558bd614dc71dd66b72ab9b0c0 (patch)
treee868b500b8b5d00cf82011013b68dd3e669bbec3 /packages
parente86f6a9cf0eb271abfc7cf53ec10ef372d52f0bd (diff)
downloadkarakeep-fc8eb79b98bbea558bd614dc71dd66b72ab9b0c0.tar.zst
feature: Implemente pagination support
Diffstat (limited to 'packages')
-rw-r--r--packages/trpc/routers/bookmarks.ts122
-rw-r--r--packages/trpc/types/bookmarks.ts5
2 files changed, 77 insertions, 50 deletions
diff --git a/packages/trpc/routers/bookmarks.ts b/packages/trpc/routers/bookmarks.ts
index f91c8c6a..c2d78c7f 100644
--- a/packages/trpc/routers/bookmarks.ts
+++ b/packages/trpc/routers/bookmarks.ts
@@ -1,5 +1,5 @@
import { experimental_trpcMiddleware, TRPCError } from "@trpc/server";
-import { and, desc, eq, exists, inArray } from "drizzle-orm";
+import { and, desc, eq, exists, inArray, lte } from "drizzle-orm";
import invariant from "tiny-invariant";
import { z } from "zod";
@@ -21,6 +21,7 @@ import { getSearchIdxClient } from "@hoarder/shared/search";
import { authedProcedure, Context, router } from "../index";
import {
+ DEFAULT_NUM_BOOKMARKS_PER_PAGE,
zBareBookmarkSchema,
ZBookmark,
ZBookmarkContent,
@@ -322,7 +323,7 @@ export const bookmarksAppRouter = router({
});
if (resp.hits.length == 0) {
- return { bookmarks: [] };
+ return { bookmarks: [], nextCursor: null };
}
const results = await ctx.db.query.bookmarks.findMany({
where: and(
@@ -343,66 +344,79 @@ export const bookmarksAppRouter = router({
},
});
- return { bookmarks: results.map(toZodSchema) };
+ return { bookmarks: results.map(toZodSchema), nextCursor: null };
}),
getBookmarks: authedProcedure
.input(zGetBookmarksRequestSchema)
.output(zGetBookmarksResponseSchema)
.query(async ({ input, ctx }) => {
if (input.ids && input.ids.length == 0) {
- return { bookmarks: [] };
+ return { bookmarks: [], nextCursor: null };
}
+ if (!input.limit) {
+ input.limit = DEFAULT_NUM_BOOKMARKS_PER_PAGE;
+ }
+
+ const sq = ctx.db.$with("bookmarksSq").as(
+ ctx.db
+ .select()
+ .from(bookmarks)
+ .where(
+ and(
+ eq(bookmarks.userId, ctx.user.id),
+ input.archived !== undefined
+ ? eq(bookmarks.archived, input.archived)
+ : undefined,
+ input.favourited !== undefined
+ ? eq(bookmarks.favourited, input.favourited)
+ : undefined,
+ input.ids ? inArray(bookmarks.id, input.ids) : undefined,
+ input.tagId !== undefined
+ ? exists(
+ ctx.db
+ .select()
+ .from(tagsOnBookmarks)
+ .where(
+ and(
+ eq(tagsOnBookmarks.bookmarkId, bookmarks.id),
+ eq(tagsOnBookmarks.tagId, input.tagId),
+ ),
+ ),
+ )
+ : undefined,
+ input.listId !== undefined
+ ? exists(
+ ctx.db
+ .select()
+ .from(bookmarksInLists)
+ .where(
+ and(
+ eq(bookmarksInLists.bookmarkId, bookmarks.id),
+ eq(bookmarksInLists.listId, input.listId),
+ ),
+ ),
+ )
+ : undefined,
+ input.cursor ? lte(bookmarks.createdAt, input.cursor) : undefined,
+ ),
+ )
+ .limit(input.limit + 1)
+ .orderBy(desc(bookmarks.createdAt)),
+ );
// TODO: Consider not inlining the tags in the response of getBookmarks as this query is getting kinda expensive
const results = await ctx.db
+ .with(sq)
.select()
- .from(bookmarks)
- .where(
- and(
- eq(bookmarks.userId, ctx.user.id),
- input.archived !== undefined
- ? eq(bookmarks.archived, input.archived)
- : undefined,
- input.favourited !== undefined
- ? eq(bookmarks.favourited, input.favourited)
- : undefined,
- input.ids ? inArray(bookmarks.id, input.ids) : undefined,
- input.tagId !== undefined
- ? exists(
- ctx.db
- .select()
- .from(tagsOnBookmarks)
- .where(
- and(
- eq(tagsOnBookmarks.bookmarkId, bookmarks.id),
- eq(tagsOnBookmarks.tagId, input.tagId),
- ),
- ),
- )
- : undefined,
- input.listId !== undefined
- ? exists(
- ctx.db
- .select()
- .from(bookmarksInLists)
- .where(
- and(
- eq(bookmarksInLists.bookmarkId, bookmarks.id),
- eq(bookmarksInLists.listId, input.listId),
- ),
- ),
- )
- : undefined,
- ),
- )
- .leftJoin(tagsOnBookmarks, eq(bookmarks.id, tagsOnBookmarks.bookmarkId))
+ .from(sq)
+ .leftJoin(tagsOnBookmarks, eq(sq.id, tagsOnBookmarks.bookmarkId))
.leftJoin(bookmarkTags, eq(tagsOnBookmarks.tagId, bookmarkTags.id))
- .leftJoin(bookmarkLinks, eq(bookmarkLinks.id, bookmarks.id))
- .leftJoin(bookmarkTexts, eq(bookmarkTexts.id, bookmarks.id))
- .orderBy(desc(bookmarks.createdAt));
+ .leftJoin(bookmarkLinks, eq(bookmarkLinks.id, sq.id))
+ .leftJoin(bookmarkTexts, eq(bookmarkTexts.id, sq.id))
+ .orderBy(desc(sq.createdAt));
const bookmarksRes = results.reduce<Record<string, ZBookmark>>(
(acc, row) => {
- const bookmarkId = row.bookmarks.id;
+ const bookmarkId = row.bookmarksSq.id;
if (!acc[bookmarkId]) {
let content: ZBookmarkContent;
if (row.bookmarkLinks) {
@@ -413,7 +427,7 @@ export const bookmarksAppRouter = router({
throw new Error("Unknown content type");
}
acc[bookmarkId] = {
- ...row.bookmarks,
+ ...row.bookmarksSq,
content,
tags: [],
};
@@ -435,7 +449,15 @@ export const bookmarksAppRouter = router({
{},
);
- return { bookmarks: Object.values(bookmarksRes) };
+ const bookmarksArr = Object.values(bookmarksRes);
+
+ let nextCursor = null;
+ if (bookmarksArr.length > input.limit) {
+ const nextItem = bookmarksArr.pop();
+ nextCursor = nextItem?.createdAt ?? null;
+ }
+
+ return { bookmarks: bookmarksArr, nextCursor };
}),
updateTags: authedProcedure
diff --git a/packages/trpc/types/bookmarks.ts b/packages/trpc/types/bookmarks.ts
index e23d6b4b..e366859e 100644
--- a/packages/trpc/types/bookmarks.ts
+++ b/packages/trpc/types/bookmarks.ts
@@ -47,17 +47,22 @@ export type ZNewBookmarkRequest = z.infer<typeof zNewBookmarkRequestSchema>;
// GET /v1/bookmarks
+export const DEFAULT_NUM_BOOKMARKS_PER_PAGE = 20;
+
export const zGetBookmarksRequestSchema = z.object({
ids: z.array(z.string()).optional(),
archived: z.boolean().optional(),
favourited: z.boolean().optional(),
tagId: z.string().optional(),
listId: z.string().optional(),
+ limit: z.number().max(100).optional(),
+ cursor: z.date().nullish(),
});
export type ZGetBookmarksRequest = z.infer<typeof zGetBookmarksRequestSchema>;
export const zGetBookmarksResponseSchema = z.object({
bookmarks: z.array(zBookmarkSchema),
+ nextCursor: z.date().nullable(),
});
export type ZGetBookmarksResponse = z.infer<typeof zGetBookmarksResponseSchema>;