aboutsummaryrefslogtreecommitdiffstats
path: root/packages/trpc
diff options
context:
space:
mode:
Diffstat (limited to 'packages/trpc')
-rw-r--r--packages/trpc/lib/__tests__/search.test.ts14
-rw-r--r--packages/trpc/routers/bookmarks.ts30
-rw-r--r--packages/trpc/routers/lists.ts59
3 files changed, 79 insertions, 24 deletions
diff --git a/packages/trpc/lib/__tests__/search.test.ts b/packages/trpc/lib/__tests__/search.test.ts
index bf32bcb1..468aef83 100644
--- a/packages/trpc/lib/__tests__/search.test.ts
+++ b/packages/trpc/lib/__tests__/search.test.ts
@@ -130,10 +130,16 @@ beforeEach(async () => {
]);
await db.insert(bookmarkLists).values([
- { id: "l1", userId: testUserId, name: "list1", icon: "🚀" },
- { id: "l2", userId: testUserId, name: "list2", icon: "🚀" },
- { id: "l3", userId: testUserId, name: "favorites", icon: "⭐" },
- { id: "l4", userId: testUserId, name: "work", icon: "💼" },
+ { id: "l1", userId: testUserId, name: "list1", icon: "🚀", type: "manual" },
+ { id: "l2", userId: testUserId, name: "list2", icon: "🚀", type: "manual" },
+ {
+ id: "l3",
+ userId: testUserId,
+ name: "favorites",
+ icon: "⭐",
+ type: "manual",
+ },
+ { id: "l4", userId: testUserId, name: "work", icon: "💼", type: "manual" },
]);
await db.insert(bookmarksInLists).values([
diff --git a/packages/trpc/routers/bookmarks.ts b/packages/trpc/routers/bookmarks.ts
index 3320b3b9..47ba623b 100644
--- a/packages/trpc/routers/bookmarks.ts
+++ b/packages/trpc/routers/bookmarks.ts
@@ -14,6 +14,7 @@ import {
AssetTypes,
bookmarkAssets,
bookmarkLinks,
+ bookmarkLists,
bookmarks,
bookmarksInLists,
bookmarkTags,
@@ -33,6 +34,7 @@ import {
triggerSearchReindex,
} from "@hoarder/shared/queues";
import { getSearchIdxClient } from "@hoarder/shared/search";
+import { parseSearchQuery } from "@hoarder/shared/searchQueryParser";
import {
BookmarkTypes,
DEFAULT_NUM_BOOKMARKS_PER_PAGE,
@@ -625,6 +627,34 @@ export const bookmarksAppRouter = router({
if (!input.limit) {
input.limit = DEFAULT_NUM_BOOKMARKS_PER_PAGE;
}
+ if (input.listId) {
+ const list = await ctx.db.query.bookmarkLists.findFirst({
+ where: and(
+ eq(bookmarkLists.id, input.listId),
+ eq(bookmarkLists.userId, ctx.user.id),
+ ),
+ });
+ if (!list) {
+ throw new TRPCError({
+ code: "NOT_FOUND",
+ message: "List not found",
+ });
+ }
+ if (list.type === "smart") {
+ invariant(list.query);
+ const query = parseSearchQuery(list.query);
+ if (query.result !== "full") {
+ throw new TRPCError({
+ code: "INTERNAL_SERVER_ERROR",
+ message: "Found an invalid smart list query",
+ });
+ }
+ if (query.matcher) {
+ input.ids = await getBookmarkIdsFromMatcher(ctx, query.matcher);
+ delete input.listId;
+ }
+ }
+ }
const sq = ctx.db.$with("bookmarksSq").as(
ctx.db
diff --git a/packages/trpc/routers/lists.ts b/packages/trpc/routers/lists.ts
index 0cc937b1..ec7cb10f 100644
--- a/packages/trpc/routers/lists.ts
+++ b/packages/trpc/routers/lists.ts
@@ -1,12 +1,14 @@
import assert from "node:assert";
import { experimental_trpcMiddleware, TRPCError } from "@trpc/server";
import { and, eq } from "drizzle-orm";
+import invariant from "tiny-invariant";
import { z } from "zod";
import { SqliteError } from "@hoarder/db";
import { bookmarkLists, bookmarksInLists } from "@hoarder/db/schema";
import {
zBookmarkListSchema,
+ zEditBookmarkListSchemaWithValidation,
zNewBookmarkListSchema,
} from "@hoarder/shared/types/lists";
@@ -58,28 +60,40 @@ export const listsAppRouter = router({
icon: input.icon,
userId: ctx.user.id,
parentId: input.parentId,
+ type: input.type,
+ query: input.query,
})
.returning();
return result;
}),
edit: authedProcedure
- .input(
- zNewBookmarkListSchema
- .partial()
- .merge(z.object({ listId: z.string() }))
- .refine((val) => val.parentId != val.listId, {
- message: "List can't be its own parent",
- path: ["parentId"],
- }),
- )
+ .input(zEditBookmarkListSchemaWithValidation)
.output(zBookmarkListSchema)
+ .use(ensureListOwnership)
.mutation(async ({ input, ctx }) => {
+ if (input.query) {
+ const list = await ctx.db.query.bookmarkLists.findFirst({
+ where: and(
+ eq(bookmarkLists.id, input.listId),
+ eq(bookmarkLists.userId, ctx.user.id),
+ ),
+ });
+ // List must exist given that we passed the ownership check
+ invariant(list);
+ if (list.type !== "smart") {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Manual lists cannot have a query",
+ });
+ }
+ }
const result = await ctx.db
.update(bookmarkLists)
.set({
name: input.name,
icon: input.icon,
parentId: input.parentId,
+ query: input.query,
})
.where(
and(
@@ -123,6 +137,19 @@ export const listsAppRouter = router({
.use(ensureListOwnership)
.use(ensureBookmarkOwnership)
.mutation(async ({ input, ctx }) => {
+ const list = await ctx.db.query.bookmarkLists.findFirst({
+ where: and(
+ eq(bookmarkLists.id, input.listId),
+ eq(bookmarkLists.userId, ctx.user.id),
+ ),
+ });
+ invariant(list);
+ if (list.type === "smart") {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Smart lists cannot be added to",
+ });
+ }
try {
await ctx.db.insert(bookmarksInLists).values({
listId: input.listId,
@@ -174,13 +201,7 @@ export const listsAppRouter = router({
listId: z.string(),
}),
)
- .output(
- zBookmarkListSchema.merge(
- z.object({
- bookmarks: z.array(z.string()),
- }),
- ),
- )
+ .output(zBookmarkListSchema)
.use(ensureListOwnership)
.query(async ({ input, ctx }) => {
const res = await ctx.db.query.bookmarkLists.findFirst({
@@ -188,9 +209,6 @@ export const listsAppRouter = router({
eq(bookmarkLists.id, input.listId),
eq(bookmarkLists.userId, ctx.user.id),
),
- with: {
- bookmarksInLists: true,
- },
});
if (!res) {
throw new TRPCError({ code: "NOT_FOUND" });
@@ -201,7 +219,8 @@ export const listsAppRouter = router({
name: res.name,
icon: res.icon,
parentId: res.parentId,
- bookmarks: res.bookmarksInLists.map((b) => b.bookmarkId),
+ type: res.type,
+ query: res.query,
};
}),
list: authedProcedure