diff options
| author | Mohamed Bassem <me@mbassem.com> | 2024-12-31 13:58:23 +0000 |
|---|---|---|
| committer | Mohamed Bassem <me@mbassem.com> | 2024-12-31 14:01:09 +0000 |
| commit | 4deda9d477141e864f472ba95003e3974346f10d (patch) | |
| tree | f2cee981872359b2d601a3009fd27e8dc4daec91 /packages/trpc | |
| parent | 17af22bb6df42e1f42809261db3eda45fb2ffe3f (diff) | |
| download | karakeep-4deda9d477141e864f472ba95003e3974346f10d.tar.zst | |
feat: Add support for negative search terms
Diffstat (limited to 'packages/trpc')
| -rw-r--r-- | packages/trpc/lib/__tests__/search.test.ts | 91 | ||||
| -rw-r--r-- | packages/trpc/lib/search.ts | 72 |
2 files changed, 142 insertions, 21 deletions
diff --git a/packages/trpc/lib/__tests__/search.test.ts b/packages/trpc/lib/__tests__/search.test.ts index aa57527b..31f87dfd 100644 --- a/packages/trpc/lib/__tests__/search.test.ts +++ b/packages/trpc/lib/__tests__/search.test.ts @@ -160,53 +160,130 @@ beforeEach(async () => { describe("getBookmarkIdsFromMatcher", () => { it("should handle tagName matcher", async () => { - const matcher: Matcher = { type: "tagName", tagName: "tag1" }; + const matcher: Matcher = { + type: "tagName", + tagName: "tag1", + inverse: false, + }; const result = await getBookmarkIdsFromMatcher(mockCtx, matcher); expect(result).toEqual(["b1"]); }); + it("should handle tagName matcher with inverse=true", async () => { + const matcher: Matcher = { + type: "tagName", + tagName: "tag1", + inverse: true, + }; + const result = await getBookmarkIdsFromMatcher(mockCtx, matcher); + expect(result.sort()).toEqual(["b2", "b3", "b4", "b5", "b6"]); + }); + it("should handle listName matcher", async () => { - const matcher: Matcher = { type: "listName", listName: "list1" }; + const matcher: Matcher = { + type: "listName", + listName: "list1", + inverse: false, + }; const result = await getBookmarkIdsFromMatcher(mockCtx, matcher); expect(result).toEqual(["b1", "b6"]); }); + it("should handle listName matcher with inverse=true", async () => { + const matcher: Matcher = { + type: "listName", + listName: "list1", + inverse: true, + }; + const result = await getBookmarkIdsFromMatcher(mockCtx, matcher); + expect(result.sort()).toEqual(["b2", "b3", "b4", "b5"]); + }); + it("should handle archived matcher", async () => { const matcher: Matcher = { type: "archived", archived: true }; const result = await getBookmarkIdsFromMatcher(mockCtx, matcher); expect(result).toEqual(["b2", "b3", "b6"]); }); + it("should handle archived matcher archived=false", async () => { + const matcher: Matcher = { type: "archived", archived: false }; + const result = await getBookmarkIdsFromMatcher(mockCtx, matcher); + expect(result).toEqual(["b1", "b4", "b5"]); + }); + it("should handle favourited matcher", async () => { const matcher: Matcher = { type: "favourited", favourited: true }; const result = await getBookmarkIdsFromMatcher(mockCtx, matcher); expect(result).toEqual(["b2", "b4"]); }); + it("should handle favourited matcher favourited=false", async () => { + const matcher: Matcher = { type: "favourited", favourited: false }; + const result = await getBookmarkIdsFromMatcher(mockCtx, matcher); + expect(result).toEqual(["b1", "b3", "b5", "b6"]); + }); + it("should handle url matcher", async () => { - const matcher: Matcher = { type: "url", url: "example.com" }; + const matcher: Matcher = { + type: "url", + url: "example.com", + inverse: false, + }; const result = await getBookmarkIdsFromMatcher(mockCtx, matcher); expect(result).toEqual(["b1", "b4"]); }); + it("should handle url matcher with inverse=true", async () => { + const matcher: Matcher = { + type: "url", + url: "example.com", + inverse: true, + }; + const result = await getBookmarkIdsFromMatcher(mockCtx, matcher); + // Not that only bookmarks of type link are returned + expect(result.sort()).toEqual(["b2"]); + }); + it("should handle dateAfter matcher", async () => { const matcher: Matcher = { type: "dateAfter", dateAfter: new Date("2024-01-02"), + inverse: false, }; const result = await getBookmarkIdsFromMatcher(mockCtx, matcher); expect(result).toEqual(["b2", "b3", "b4", "b5", "b6"]); }); + it("should handle dateAfter matcher with inverse=true", async () => { + const matcher: Matcher = { + type: "dateAfter", + dateAfter: new Date("2024-01-02"), + inverse: true, + }; + const result = await getBookmarkIdsFromMatcher(mockCtx, matcher); + expect(result).toEqual(["b1"]); + }); + it("should handle dateBefore matcher", async () => { const matcher: Matcher = { type: "dateBefore", dateBefore: new Date("2024-01-02"), + inverse: false, }; const result = await getBookmarkIdsFromMatcher(mockCtx, matcher); expect(result).toEqual(["b1", "b2"]); }); + it("should handle dateBefore matcher with inverse=true", async () => { + const matcher: Matcher = { + type: "dateBefore", + dateBefore: new Date("2024-01-02"), + inverse: true, + }; + const result = await getBookmarkIdsFromMatcher(mockCtx, matcher); + expect(result.sort()).toEqual(["b3", "b4", "b5", "b6"]); + }); + it("should handle AND matcher", async () => { const matcher: Matcher = { type: "and", @@ -235,8 +312,8 @@ describe("getBookmarkIdsFromMatcher", () => { const matcher: Matcher = { type: "or", matchers: [ - { type: "listName", listName: "favorites" }, - { type: "tagName", tagName: "work" }, + { type: "listName", listName: "favorites", inverse: false }, + { type: "tagName", tagName: "work", inverse: false }, ], }; const result = await getBookmarkIdsFromMatcher(mockCtx, matcher); @@ -250,8 +327,8 @@ describe("getBookmarkIdsFromMatcher", () => { { type: "or", matchers: [ - { type: "listName", listName: "favorites" }, - { type: "tagName", tagName: "work" }, + { type: "listName", listName: "favorites", inverse: false }, + { type: "tagName", tagName: "work", inverse: false }, ], }, { diff --git a/packages/trpc/lib/search.ts b/packages/trpc/lib/search.ts index 0ee9c76e..fcc5abda 100644 --- a/packages/trpc/lib/search.ts +++ b/packages/trpc/lib/search.ts @@ -1,4 +1,15 @@ -import { and, eq, gte, like, lte, sql } from "drizzle-orm"; +import { + and, + eq, + exists, + gt, + gte, + like, + lt, + lte, + notExists, + notLike, +} from "drizzle-orm"; import { bookmarkLinks, @@ -76,26 +87,56 @@ async function getIds( ): Promise<BookmarkQueryReturnType[]> { switch (matcher.type) { case "tagName": { + const comp = matcher.inverse ? notExists : exists; return db - .select({ id: sql<string>`${tagsOnBookmarks.bookmarkId}`.as("id") }) - .from(tagsOnBookmarks) - .innerJoin(bookmarkTags, eq(tagsOnBookmarks.tagId, bookmarkTags.id)) + .selectDistinct({ id: bookmarks.id }) + .from(bookmarks) .where( and( - eq(bookmarkTags.userId, userId), - eq(bookmarkTags.name, matcher.tagName), + eq(bookmarks.userId, userId), + comp( + db + .select() + .from(tagsOnBookmarks) + .innerJoin( + bookmarkTags, + eq(tagsOnBookmarks.tagId, bookmarkTags.id), + ) + .where( + and( + eq(tagsOnBookmarks.bookmarkId, bookmarks.id), + eq(bookmarkTags.userId, userId), + eq(bookmarkTags.name, matcher.tagName), + ), + ), + ), ), ); } case "listName": { + const comp = matcher.inverse ? notExists : exists; return db - .select({ id: sql<string>`${bookmarksInLists.bookmarkId}`.as("id") }) - .from(bookmarksInLists) - .innerJoin(bookmarkLists, eq(bookmarksInLists.listId, bookmarkLists.id)) + .selectDistinct({ id: bookmarks.id }) + .from(bookmarks) .where( and( - eq(bookmarkLists.userId, userId), - eq(bookmarkLists.name, matcher.listName), + eq(bookmarks.userId, userId), + comp( + db + .select() + .from(bookmarksInLists) + .innerJoin( + bookmarkLists, + eq(bookmarksInLists.listId, bookmarkLists.id), + ) + .where( + and( + eq(bookmarksInLists.bookmarkId, bookmarks.id), + eq(bookmarkLists.userId, userId), + eq(bookmarkLists.name, matcher.listName), + ), + ), + ), ), ); } @@ -111,6 +152,7 @@ async function getIds( ); } case "url": { + const comp = matcher.inverse ? notLike : like; return db .select({ id: bookmarkLinks.id }) .from(bookmarkLinks) @@ -118,7 +160,7 @@ async function getIds( .where( and( eq(bookmarks.userId, userId), - like(bookmarkLinks.url, `%${matcher.url}%`), + comp(bookmarkLinks.url, `%${matcher.url}%`), ), ); } @@ -134,24 +176,26 @@ async function getIds( ); } case "dateAfter": { + const comp = matcher.inverse ? lt : gte; return db .select({ id: bookmarks.id }) .from(bookmarks) .where( and( eq(bookmarks.userId, userId), - gte(bookmarks.createdAt, matcher.dateAfter), + comp(bookmarks.createdAt, matcher.dateAfter), ), ); } case "dateBefore": { + const comp = matcher.inverse ? gt : lte; return db .select({ id: bookmarks.id }) .from(bookmarks) .where( and( eq(bookmarks.userId, userId), - lte(bookmarks.createdAt, matcher.dateBefore), + comp(bookmarks.createdAt, matcher.dateBefore), ), ); } |
