Skip to content

Commit c306fea

Browse files
committed
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.
1 parent 46fb559 commit c306fea

5 files changed

Lines changed: 36 additions & 31 deletions

File tree

src/backend/models/domain-models.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ export enum DIRECTORY_NAME {
196196
}
197197

198198
export interface Reaction {
199+
id?: string;
199200
resource_id: string;
200201
resource_type: "ARTICLE" | "COMMENT" | "GIST";
201202
reaction_type: REACTION_TYPE;

src/backend/services/comment.action.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,11 @@ export const createMyComment = async (
4747

4848
await assertCommentResourceExists(resource_id, resource_type);
4949

50+
const commentId = input.comment_id ?? crypto.randomUUID();
51+
5052
const created = await persistenceRepository.comment.insert([
5153
{
52-
id: input.comment_id ?? crypto.randomUUID(),
54+
id: commentId,
5355
body,
5456
resource_id,
5557
resource_type,
@@ -59,6 +61,7 @@ export const createMyComment = async (
5961

6062
inngest
6163
.send({
64+
id: `notif:comment:${commentId}`,
6265
name: "app/notification.requested",
6366
data: {
6467
actor_id: sessionId,

src/backend/services/reaction.actions.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,12 @@ export async function toogleReaction(
6969
};
7070
}
7171

72+
const reactionId = crypto.randomUUID();
73+
7274
// If reaction does not exist, create it
7375
await persistenceRepository.reaction.insert([
7476
{
77+
id: reactionId,
7578
resource_id: input.resource_id,
7679
resource_type: input.resource_type,
7780
reaction_type: input.reaction_type,
@@ -83,6 +86,7 @@ export async function toogleReaction(
8386
// Send notification event for insert path only (log errors, don't fail mutation)
8487
inngest
8588
.send({
89+
id: `notif:reaction:${reactionId}`,
8690
name: "app/notification.requested",
8791
data: {
8892
actor_id: sessionUserId,

src/lib/inngest.ts

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -122,13 +122,13 @@ export const persistNotificationFn = inngest.createFunction(
122122
id: "persist-notification",
123123
triggers: [{ event: "app/notification.requested" }],
124124
},
125-
async ({ event }: { event: { data: NotificationEventData } }) => {
125+
async ({ event, step }) => {
126126
const parsed = notificationEventSchema.safeParse(event.data);
127127
if (!parsed.success) {
128128
return { skipped: true, reason: "invalid-notification-payload" };
129129
}
130130

131-
let data = parsed.data;
131+
let data: NotificationEventData = parsed.data;
132132

133133
if (data.reaction_request && data.actor_id) {
134134
const built = await buildPersistableNotification({
@@ -182,23 +182,26 @@ export const persistNotificationFn = inngest.createFunction(
182182
return { skipped: true, reason: "self-notification" };
183183
}
184184

185-
await persistenceRepository.notification.insert([
186-
{
187-
recipient_id: data.recipient_id,
188-
actor_id: data.actor_id ?? null,
189-
type: data.type as NotificationType,
190-
payload: (data.payload ?? null) as NotificationPayload | null,
191-
created_at: new Date(),
192-
},
193-
]);
185+
const row = {
186+
recipient_id: data.recipient_id,
187+
actor_id: data.actor_id ?? null,
188+
type: data.type as NotificationType,
189+
payload: (data.payload ?? null) as NotificationPayload | null,
190+
created_at: new Date(),
191+
};
194192

195-
// Broadcast a lightweight signal so the recipient's browser can invalidate
196-
// its TanStack Query caches without polling.
197-
await publishMessage(
198-
`private-user.${data.recipient_id}`,
199-
REALTIME_PUSHER_EVENTS.NOTIFICATION_NEW,
200-
{ scope: "notifications" },
201-
);
193+
// Durable step: retries must not insert duplicate rows when a later line fails.
194+
await step.run("insert-notification-row", async () => {
195+
await persistenceRepository.notification.insert([row]);
196+
});
197+
198+
await step.run("publish-notification-realtime", async () => {
199+
await publishMessage(
200+
`private-user.${data.recipient_id}`,
201+
REALTIME_PUSHER_EVENTS.NOTIFICATION_NEW,
202+
{ scope: "notifications" },
203+
);
204+
});
202205

203206
return { success: true };
204207
},

src/lib/pusher/pusher.server.ts

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -37,16 +37,10 @@ export async function publishMessage(
3737
event: RealtimePusherEvent,
3838
data: Record<string, unknown> = {},
3939
): Promise<void> {
40-
console.log(`
41-
[pusher] Publishing message to channel ${channel} with event ${event} and data ${JSON.stringify(data)}
42-
`);
43-
44-
pusherServer
45-
?.trigger(channel, event, data)
46-
.then((data) => {
47-
console.log("[pusher] Published message successfully");
48-
})
49-
.catch((err) => {
50-
console.error("[pusher] Failed to publish message:", JSON.stringify(err));
51-
});
40+
if (!pusherServer) return;
41+
try {
42+
await pusherServer.trigger(channel, event, data);
43+
} catch (err) {
44+
console.error("[pusher] Failed to publish message:", JSON.stringify(err));
45+
}
5246
}

0 commit comments

Comments
 (0)