From c306fea68532425858d3f2322cf414a49679c705 Mon Sep 17 00:00:00 2001 From: kingrayhan Date: Fri, 3 Apr 2026 02:46:34 +0600 Subject: [PATCH 1/3] feat: enhance reaction and comment handling with unique IDs - Added optional `id` field to the Reaction interface for better tracking. - Updated comment creation to generate a unique comment ID, improving notification consistency. - Introduced unique reaction IDs during reaction creation to enhance notification handling. - Refactored notification persistence to ensure reliable insertion and real-time publishing without duplicates. --- src/backend/models/domain-models.ts | 1 + src/backend/services/comment.action.ts | 5 ++- src/backend/services/reaction.actions.ts | 4 +++ src/lib/inngest.ts | 39 +++++++++++++----------- src/lib/pusher/pusher.server.ts | 18 ++++------- 5 files changed, 36 insertions(+), 31 deletions(-) diff --git a/src/backend/models/domain-models.ts b/src/backend/models/domain-models.ts index b9fc34c..ea16048 100644 --- a/src/backend/models/domain-models.ts +++ b/src/backend/models/domain-models.ts @@ -196,6 +196,7 @@ export enum DIRECTORY_NAME { } export interface Reaction { + id?: string; resource_id: string; resource_type: "ARTICLE" | "COMMENT" | "GIST"; reaction_type: REACTION_TYPE; diff --git a/src/backend/services/comment.action.ts b/src/backend/services/comment.action.ts index fe84df7..d5b404a 100644 --- a/src/backend/services/comment.action.ts +++ b/src/backend/services/comment.action.ts @@ -47,9 +47,11 @@ export const createMyComment = async ( await assertCommentResourceExists(resource_id, resource_type); + const commentId = input.comment_id ?? crypto.randomUUID(); + const created = await persistenceRepository.comment.insert([ { - id: input.comment_id ?? crypto.randomUUID(), + id: commentId, body, resource_id, resource_type, @@ -59,6 +61,7 @@ export const createMyComment = async ( inngest .send({ + id: `notif:comment:${commentId}`, name: "app/notification.requested", data: { actor_id: sessionId, diff --git a/src/backend/services/reaction.actions.ts b/src/backend/services/reaction.actions.ts index cba6f87..ae99c0e 100644 --- a/src/backend/services/reaction.actions.ts +++ b/src/backend/services/reaction.actions.ts @@ -69,9 +69,12 @@ export async function toogleReaction( }; } + const reactionId = crypto.randomUUID(); + // If reaction does not exist, create it await persistenceRepository.reaction.insert([ { + id: reactionId, resource_id: input.resource_id, resource_type: input.resource_type, reaction_type: input.reaction_type, @@ -83,6 +86,7 @@ export async function toogleReaction( // Send notification event for insert path only (log errors, don't fail mutation) inngest .send({ + id: `notif:reaction:${reactionId}`, name: "app/notification.requested", data: { actor_id: sessionUserId, diff --git a/src/lib/inngest.ts b/src/lib/inngest.ts index f8c6349..69d58a7 100644 --- a/src/lib/inngest.ts +++ b/src/lib/inngest.ts @@ -122,13 +122,13 @@ export const persistNotificationFn = inngest.createFunction( id: "persist-notification", triggers: [{ event: "app/notification.requested" }], }, - async ({ event }: { event: { data: NotificationEventData } }) => { + async ({ event, step }) => { const parsed = notificationEventSchema.safeParse(event.data); if (!parsed.success) { return { skipped: true, reason: "invalid-notification-payload" }; } - let data = parsed.data; + let data: NotificationEventData = parsed.data; if (data.reaction_request && data.actor_id) { const built = await buildPersistableNotification({ @@ -182,23 +182,26 @@ export const persistNotificationFn = inngest.createFunction( return { skipped: true, reason: "self-notification" }; } - await persistenceRepository.notification.insert([ - { - recipient_id: data.recipient_id, - actor_id: data.actor_id ?? null, - type: data.type as NotificationType, - payload: (data.payload ?? null) as NotificationPayload | null, - created_at: new Date(), - }, - ]); + const row = { + recipient_id: data.recipient_id, + actor_id: data.actor_id ?? null, + type: data.type as NotificationType, + payload: (data.payload ?? null) as NotificationPayload | null, + created_at: new Date(), + }; - // Broadcast a lightweight signal so the recipient's browser can invalidate - // its TanStack Query caches without polling. - await publishMessage( - `private-user.${data.recipient_id}`, - REALTIME_PUSHER_EVENTS.NOTIFICATION_NEW, - { scope: "notifications" }, - ); + // Durable step: retries must not insert duplicate rows when a later line fails. + await step.run("insert-notification-row", async () => { + await persistenceRepository.notification.insert([row]); + }); + + await step.run("publish-notification-realtime", async () => { + await publishMessage( + `private-user.${data.recipient_id}`, + REALTIME_PUSHER_EVENTS.NOTIFICATION_NEW, + { scope: "notifications" }, + ); + }); return { success: true }; }, diff --git a/src/lib/pusher/pusher.server.ts b/src/lib/pusher/pusher.server.ts index c57b473..b4df670 100644 --- a/src/lib/pusher/pusher.server.ts +++ b/src/lib/pusher/pusher.server.ts @@ -37,16 +37,10 @@ export async function publishMessage( event: RealtimePusherEvent, data: Record = {}, ): Promise { - console.log(` - [pusher] Publishing message to channel ${channel} with event ${event} and data ${JSON.stringify(data)} - `); - - pusherServer - ?.trigger(channel, event, data) - .then((data) => { - console.log("[pusher] Published message successfully"); - }) - .catch((err) => { - console.error("[pusher] Failed to publish message:", JSON.stringify(err)); - }); + if (!pusherServer) return; + try { + await pusherServer.trigger(channel, event, data); + } catch (err) { + console.error("[pusher] Failed to publish message:", JSON.stringify(err)); + } } From 802dbb3ef2538359b5cadeffe90c6ef22c4ca2fb Mon Sep 17 00:00:00 2001 From: kingrayhan Date: Fri, 3 Apr 2026 02:49:10 +0600 Subject: [PATCH 2/3] refactor: update publishMessage calls for consistency and error handling - Changed `void publishMessage` to `await publishMessage` in comment actions to ensure proper asynchronous handling. - Updated the `publishMessage` function in pusher.server.ts to return a value, improving error handling and consistency in message publishing. - Adjusted notification function in inngest.ts to return the result of `publishMessage`, enhancing clarity in notification flow. --- src/backend/services/comment.action.ts | 6 +++--- src/lib/inngest.ts | 2 +- src/lib/pusher/pusher.server.ts | 5 +++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/backend/services/comment.action.ts b/src/backend/services/comment.action.ts index d5b404a..d7f6309 100644 --- a/src/backend/services/comment.action.ts +++ b/src/backend/services/comment.action.ts @@ -75,7 +75,7 @@ export const createMyComment = async ( console.error("[inngest] Failed to send notification event:", err); }); - void publishMessage( + await publishMessage( `resource.${resource_type}.${resource_id}`, REALTIME_PUSHER_EVENTS.COMMENT_CREATED, { scope: "comments" }, @@ -110,7 +110,7 @@ export const updateMyComment = async ( data: { body: input.body, updated_at: new Date() }, }); - void publishMessage( + await publishMessage( `resource.${existing.resource_type}.${existing.resource_id}`, REALTIME_PUSHER_EVENTS.COMMENT_UPDATED, { scope: "comments" }, @@ -168,7 +168,7 @@ export const deleteMyComment = async ( where: inArray("id", ids), }); - void publishMessage( + await publishMessage( `resource.${root.resource_type}.${root.resource_id}`, REALTIME_PUSHER_EVENTS.COMMENT_DELETED, { scope: "comments" }, diff --git a/src/lib/inngest.ts b/src/lib/inngest.ts index 69d58a7..6227132 100644 --- a/src/lib/inngest.ts +++ b/src/lib/inngest.ts @@ -196,7 +196,7 @@ export const persistNotificationFn = inngest.createFunction( }); await step.run("publish-notification-realtime", async () => { - await publishMessage( + return await publishMessage( `private-user.${data.recipient_id}`, REALTIME_PUSHER_EVENTS.NOTIFICATION_NEW, { scope: "notifications" }, diff --git a/src/lib/pusher/pusher.server.ts b/src/lib/pusher/pusher.server.ts index b4df670..c6fcfb9 100644 --- a/src/lib/pusher/pusher.server.ts +++ b/src/lib/pusher/pusher.server.ts @@ -36,11 +36,12 @@ export async function publishMessage( channel: string, event: RealtimePusherEvent, data: Record = {}, -): Promise { +) { if (!pusherServer) return; try { - await pusherServer.trigger(channel, event, data); + return await pusherServer.trigger(channel, event, data); } catch (err) { console.error("[pusher] Failed to publish message:", JSON.stringify(err)); + return null; } } From 9f59d155eaac74fd05ed382bcdc94d7babff852b Mon Sep 17 00:00:00 2001 From: kingrayhan Date: Fri, 3 Apr 2026 02:56:19 +0600 Subject: [PATCH 3/3] refactor: improve error handling in notification persistence and publishing - Enhanced the `persistNotificationFn` to include error handling for both notification row insertion and message publishing. - Updated the insert and publish operations to return success status and messages, improving clarity and debugging capabilities. --- src/lib/inngest.ts | 34 +++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/src/lib/inngest.ts b/src/lib/inngest.ts index 6227132..92fb52a 100644 --- a/src/lib/inngest.ts +++ b/src/lib/inngest.ts @@ -190,17 +190,37 @@ export const persistNotificationFn = inngest.createFunction( created_at: new Date(), }; - // Durable step: retries must not insert duplicate rows when a later line fails. await step.run("insert-notification-row", async () => { - await persistenceRepository.notification.insert([row]); + try { + const result = await persistenceRepository.notification.insert([row]); + return { + insertedRow: result?.rows?.[0], + }; + } catch (err) { + return { + insertedRow: null, + message: "Failed to insert notification row", + }; + } }); await step.run("publish-notification-realtime", async () => { - return await publishMessage( - `private-user.${data.recipient_id}`, - REALTIME_PUSHER_EVENTS.NOTIFICATION_NEW, - { scope: "notifications" }, - ); + try { + await publishMessage( + `private-user.${data.recipient_id}`, + REALTIME_PUSHER_EVENTS.NOTIFICATION_NEW, + { scope: "notifications" }, + ); + return { + published: true, + message: "Notification published successfully", + }; + } catch (err) { + return { + published: false, + message: "Failed to publish notification", + }; + } }); return { success: true };