aboutsummaryrefslogtreecommitdiffstats
path: root/packages/trpc
diff options
context:
space:
mode:
Diffstat (limited to 'packages/trpc')
-rw-r--r--packages/trpc/routers/bookmarks.test.ts63
-rw-r--r--packages/trpc/routers/bookmarks.ts141
2 files changed, 177 insertions, 27 deletions
diff --git a/packages/trpc/routers/bookmarks.test.ts b/packages/trpc/routers/bookmarks.test.ts
index d89f80fd..c3469acc 100644
--- a/packages/trpc/routers/bookmarks.test.ts
+++ b/packages/trpc/routers/bookmarks.test.ts
@@ -60,9 +60,70 @@ describe("Bookmark Routes", () => {
favourited: true,
});
- const res = await api.getBookmark({ bookmarkId: bookmark.id });
+ let res = await api.getBookmark({ bookmarkId: bookmark.id });
expect(res.archived).toBeTruthy();
expect(res.favourited).toBeTruthy();
+
+ // Update other common fields
+ const newDate = new Date(Date.now() - 1000 * 60 * 60 * 24); // Yesterday
+ newDate.setMilliseconds(0);
+ await api.updateBookmark({
+ bookmarkId: bookmark.id,
+ title: "New Title",
+ note: "Test Note",
+ summary: "Test Summary",
+ createdAt: newDate,
+ });
+
+ res = await api.getBookmark({ bookmarkId: bookmark.id });
+ expect(res.title).toEqual("New Title");
+ expect(res.note).toEqual("Test Note");
+ expect(res.summary).toEqual("Test Summary");
+ expect(res.createdAt).toEqual(newDate);
+
+ // Update link-specific fields
+ const linkUpdateDate = new Date(Date.now() - 1000 * 60 * 60 * 48); // 2 days ago
+ linkUpdateDate.setMilliseconds(0);
+ await api.updateBookmark({
+ bookmarkId: bookmark.id,
+ url: "https://new-google.com",
+ description: "New Description",
+ author: "New Author",
+ publisher: "New Publisher",
+ datePublished: linkUpdateDate,
+ dateModified: linkUpdateDate,
+ });
+
+ res = await api.getBookmark({ bookmarkId: bookmark.id });
+ assert(res.content.type === BookmarkTypes.LINK);
+ expect(res.content.url).toEqual("https://new-google.com");
+ expect(res.content.description).toEqual("New Description");
+ expect(res.content.author).toEqual("New Author");
+ expect(res.content.publisher).toEqual("New Publisher");
+ expect(res.content.datePublished).toEqual(linkUpdateDate);
+ expect(res.content.dateModified).toEqual(linkUpdateDate);
+ });
+
+ test<CustomTestContext>("update bookmark - non-link type error", async ({
+ apiCallers,
+ }) => {
+ const api = apiCallers[0].bookmarks;
+
+ // Create a TEXT bookmark
+ const bookmark = await api.createBookmark({
+ text: "Initial text",
+ type: BookmarkTypes.TEXT,
+ });
+
+ // Attempt to update link-specific fields
+ await expect(() =>
+ api.updateBookmark({
+ bookmarkId: bookmark.id,
+ url: "https://should-fail.com", // Link-specific field
+ }),
+ ).rejects.toThrow(
+ /Attempting to set link attributes for non-link type bookmark/,
+ );
});
test<CustomTestContext>("list bookmarks", async ({ apiCallers }) => {
diff --git a/packages/trpc/routers/bookmarks.ts b/packages/trpc/routers/bookmarks.ts
index c97383cb..9219adc6 100644
--- a/packages/trpc/routers/bookmarks.ts
+++ b/packages/trpc/routers/bookmarks.ts
@@ -54,7 +54,6 @@ import { parseSearchQuery } from "@hoarder/shared/searchQueryParser";
import {
BookmarkTypes,
DEFAULT_NUM_BOOKMARKS_PER_PAGE,
- zBareBookmarkSchema,
zBookmarkSchema,
zGetBookmarksRequestSchema,
zGetBookmarksResponseSchema,
@@ -419,35 +418,125 @@ export const bookmarksAppRouter = router({
updateBookmark: authedProcedure
.input(zUpdateBookmarksRequestSchema)
- .output(zBareBookmarkSchema)
+ .output(zBookmarkSchema)
.use(ensureBookmarkOwnership)
.mutation(async ({ input, ctx }) => {
- const res = await ctx.db
- .update(bookmarks)
- .set({
- title: input.title,
- archived: input.archived,
- favourited: input.favourited,
- note: input.note,
- summary: input.summary,
- createdAt: input.createdAt,
- })
- .where(
- and(
- eq(bookmarks.userId, ctx.user.id),
- eq(bookmarks.id, input.bookmarkId),
- ),
- )
- .returning();
- if (res.length == 0) {
- throw new TRPCError({
- code: "NOT_FOUND",
- message: "Bookmark not found",
- });
- }
+ await ctx.db.transaction(async (tx) => {
+ let somethingChanged = false;
+
+ // Update link-specific fields if any are provided
+ const linkUpdateData: Partial<{
+ url: string;
+ description: string | null;
+ author: string | null;
+ publisher: string | null;
+ datePublished: Date | null;
+ dateModified: Date | null;
+ }> = {};
+ if (input.url) {
+ linkUpdateData.url = input.url.trim();
+ }
+ if (input.description !== undefined) {
+ linkUpdateData.description = input.description;
+ }
+ if (input.author !== undefined) {
+ linkUpdateData.author = input.author;
+ }
+ if (input.publisher !== undefined) {
+ linkUpdateData.publisher = input.publisher;
+ }
+ if (input.datePublished !== undefined) {
+ linkUpdateData.datePublished = input.datePublished;
+ }
+ if (input.dateModified !== undefined) {
+ linkUpdateData.dateModified = input.dateModified;
+ }
+
+ if (Object.keys(linkUpdateData).length > 0) {
+ const result = await tx
+ .update(bookmarkLinks)
+ .set(linkUpdateData)
+ .where(eq(bookmarkLinks.id, input.bookmarkId));
+ if (result.changes == 0) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message:
+ "Attempting to set link attributes for non-link type bookmark",
+ });
+ }
+ somethingChanged = true;
+ }
+
+ if (input.text) {
+ const result = await tx
+ .update(bookmarkTexts)
+ .set({
+ text: input.text,
+ })
+ .where(eq(bookmarkLinks.id, input.bookmarkId));
+
+ if (result.changes == 0) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message:
+ "Attempting to set link attributes for non-text type bookmark",
+ });
+ }
+ somethingChanged = true;
+ }
+
+ // Update common bookmark fields
+ const commonUpdateData: Partial<{
+ title: string | null;
+ archived: boolean;
+ favourited: boolean;
+ note: string | null;
+ summary: string | null;
+ createdAt: Date;
+ modifiedAt: Date; // Always update modifiedAt
+ }> = {
+ modifiedAt: new Date(),
+ };
+ if (input.title !== undefined) {
+ commonUpdateData.title = input.title;
+ }
+ if (input.archived !== undefined) {
+ commonUpdateData.archived = input.archived;
+ }
+ if (input.favourited !== undefined) {
+ commonUpdateData.favourited = input.favourited;
+ }
+ if (input.note !== undefined) {
+ commonUpdateData.note = input.note;
+ }
+ if (input.summary !== undefined) {
+ commonUpdateData.summary = input.summary;
+ }
+ if (input.createdAt !== undefined) {
+ commonUpdateData.createdAt = input.createdAt;
+ }
+
+ if (Object.keys(commonUpdateData).length > 1 || somethingChanged) {
+ await tx
+ .update(bookmarks)
+ .set(commonUpdateData)
+ .where(
+ and(
+ eq(bookmarks.userId, ctx.user.id),
+ eq(bookmarks.id, input.bookmarkId),
+ ),
+ );
+ }
+ });
+
+ // Refetch the updated bookmark data to return the full object
+ const updatedBookmark = await getBookmark(ctx, input.bookmarkId);
+
+ // Trigger re-indexing and webhooks
await triggerSearchReindex(input.bookmarkId);
await triggerWebhook(input.bookmarkId, "edited");
- return res[0];
+
+ return updatedBookmark;
}),
updateBookmarkText: authedProcedure