Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
102 commits
Select commit Hold shift + click to select a range
9a24c31
WIP messages
TWilson023 Aug 27, 2025
f32a594
Add left panel empty state
TWilson023 Aug 27, 2025
ec4050a
Messages list panel
TWilson023 Aug 28, 2025
db77984
WIP messages
TWilson023 Aug 28, 2025
c15077b
Active items + partner info
TWilson023 Aug 28, 2025
b4a76ba
Partner info
TWilson023 Aug 28, 2025
1a55045
small updates to messages layout
steven-tey Aug 28, 2025
34eb01f
Merge branch 'bounties' into messages
steven-tey Aug 28, 2025
d708c10
Update get-partner-for-program.ts
steven-tey Aug 28, 2025
cbe7d07
Merge branch 'bounties' into messages
steven-tey Aug 28, 2025
665c4ef
Update partner-info-stats.tsx
TWilson023 Aug 29, 2025
2e3e857
Add message box w/ emoji picker
TWilson023 Aug 29, 2025
c5daa02
WIP messages UI
TWilson023 Aug 29, 2025
dae7abd
WIP messages UI
TWilson023 Aug 29, 2025
c6edd29
Add loading/error states
TWilson023 Aug 29, 2025
264487b
Merge branch 'main' into messages
steven-tey Aug 30, 2025
82f520a
Merge branch 'main' into messages
steven-tey Aug 30, 2025
cffdf98
Merge branch 'main' into messages
steven-tey Aug 30, 2025
8cb62e7
Merge branch 'main' into messages
steven-tey Aug 31, 2025
d48e725
Merge branch 'main' into messages
steven-tey Aug 31, 2025
1c1da38
Merge branch 'main' into messages
steven-tey Sep 2, 2025
2ec5eb5
Update partner-info-stats.tsx
steven-tey Sep 2, 2025
c0c8670
Read statuses
TWilson023 Sep 2, 2025
8ccffe6
Hook up messages backend
TWilson023 Sep 2, 2025
1db5bbc
More actions
TWilson023 Sep 2, 2025
1d1e528
Message sending
TWilson023 Sep 3, 2025
83ba924
Add links
TWilson023 Sep 3, 2025
32d6e4f
Update route.ts
TWilson023 Sep 3, 2025
d329e2e
New conversations
TWilson023 Sep 3, 2025
452d64d
Update page-client.tsx
TWilson023 Sep 3, 2025
57e80bb
Merge branch 'main' into messages
steven-tey Sep 3, 2025
dc5cccb
Misc. fixes
TWilson023 Sep 3, 2025
64e4bbb
Merge branch 'messages' of github.com:dubinc/dub into messages
TWilson023 Sep 3, 2025
d0b1ce9
Merge branch 'main' into messages
steven-tey Sep 3, 2025
7bcf41b
Message updates
TWilson023 Sep 4, 2025
0f11f4a
Mark messages as read
TWilson023 Sep 4, 2025
3aaafed
Upsell + program logos
TWilson023 Sep 4, 2025
418fdf1
WIP partners side
TWilson023 Sep 4, 2025
d5210df
Update layout.tsx
TWilson023 Sep 4, 2025
e53b83d
Update messages-panel.tsx
TWilson023 Sep 4, 2025
cf66b01
Partner message sending
TWilson023 Sep 4, 2025
306ab1c
Mark as read from partner side
TWilson023 Sep 4, 2025
3cfad10
Persist partner user IDs
TWilson023 Sep 5, 2025
065895e
Update mark-program-messages-read.ts
TWilson023 Sep 5, 2025
04ad2cd
Tweaks from review
TWilson023 Sep 5, 2025
5ed0906
Merge branch 'main' into messages
TWilson023 Sep 5, 2025
e6c11bf
Update layout.tsx
TWilson023 Sep 5, 2025
5113717
Add program selector
TWilson023 Sep 5, 2025
0489394
Update messages.ts
TWilson023 Sep 5, 2025
687e7f4
Program info panel
TWilson023 Sep 5, 2025
e04059a
Update page-client.tsx
TWilson023 Sep 5, 2025
991da0b
Add email templates
TWilson023 Sep 5, 2025
b13530c
Unread message badges/notifications
TWilson023 Sep 5, 2025
2eec587
Merge branch 'main' into messages
steven-tey Sep 5, 2025
a614c9c
set shorter refreshInterval + refreshWhenHidden
steven-tey Sep 6, 2025
b9ebffe
Merge branch 'messages' of https://github.com/dubinc/dub into messages
steven-tey Sep 6, 2025
0978298
Update send keyboard shortcut
TWilson023 Sep 8, 2025
9775b08
Scrolling tweak
TWilson023 Sep 8, 2025
296c4e8
Update messages-panel.tsx
TWilson023 Sep 8, 2025
766e6e7
Partner group truncation
TWilson023 Sep 8, 2025
8dde32b
New message flow updates
TWilson023 Sep 8, 2025
56a173e
Update page-client.tsx
TWilson023 Sep 8, 2025
db41320
Combobox tweak
TWilson023 Sep 8, 2025
fd014c1
Message email notifications
TWilson023 Sep 8, 2025
b5790fb
Add notification preference
TWilson023 Sep 8, 2025
eaae58e
Add unread count to partners side nav
TWilson023 Sep 8, 2025
248d389
Add partner notification preference
TWilson023 Sep 8, 2025
46062ef
Hide programs w/o messaging
TWilson023 Sep 8, 2025
7aefb1d
Fix optional chaining
TWilson023 Sep 8, 2025
44d21fd
Merge branch 'main' into messages
steven-tey Sep 9, 2025
f2681b4
hide fraud & risk for now
steven-tey Sep 9, 2025
abd3f0d
NotificationEmail updates
TWilson023 Sep 9, 2025
8fbe1dd
Sync message read statuses
TWilson023 Sep 9, 2025
e904044
Panel toggle + truncation improvements
TWilson023 Sep 9, 2025
8e57a36
Update message.prisma
TWilson023 Sep 9, 2025
e5fb6a7
Merge branch 'main' into messages
steven-tey Sep 10, 2025
873e9f8
add "use client" to EmojiPicker
steven-tey Sep 10, 2025
f0ac1e4
Merge branch 'main' into messages
devkiran Sep 10, 2025
4523385
refactor resend webhooks
devkiran Sep 10, 2025
90f3f02
Create message.prisma
devkiran Sep 10, 2025
27670a4
Update mark-program-messages-read.ts
devkiran Sep 10, 2025
14f4402
Update message-partner.ts
devkiran Sep 10, 2025
4e01900
Update message-program.ts
devkiran Sep 10, 2025
5999e7e
remove updatePartnerMessageAction - no longer using
devkiran Sep 10, 2025
8ae07f5
update email template preview message
devkiran Sep 10, 2025
7414c77
Update route.ts
devkiran Sep 10, 2025
723cb3c
format Prisma
devkiran Sep 10, 2025
9dd8b06
Update notification-email.prisma
devkiran Sep 10, 2025
812596d
Update route.ts
devkiran Sep 10, 2025
be66a00
Update message.prisma
TWilson023 Sep 10, 2025
8f35c61
Merge branch 'main' into messages
steven-tey Sep 12, 2025
186486b
Merge branch 'main' into messages
steven-tey Sep 15, 2025
824879e
fix constructPartnerLink
steven-tey Sep 15, 2025
106330f
small changes
steven-tey Sep 15, 2025
ceaefd8
drop redundant indexes
steven-tey Sep 15, 2025
8a35338
Merge branch 'main' into messages
steven-tey Sep 15, 2025
162fb5b
use svix for webhook verification
steven-tey Sep 15, 2025
210a5e2
Merge branch 'main' into messages
steven-tey Sep 15, 2025
ca0fc12
add messages to partnersidebarnav
steven-tey Sep 15, 2025
c1355e9
make isRightPanelOpen true by default
steven-tey Sep 15, 2025
c982ce0
final changes
steven-tey Sep 15, 2025
3103a45
Update messages-panel.tsx
TWilson023 Sep 15, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
166 changes: 166 additions & 0 deletions apps/web/app/(ee)/api/cron/messages/notify-partner/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import { handleAndReturnErrorResponse } from "@/lib/api/errors";
import { verifyQstashSignature } from "@/lib/cron/verify-qstash";
import { resend } from "@dub/email/resend";
import { VARIANT_TO_FROM_MAP } from "@dub/email/resend/constants";
import NewMessageFromProgram from "@dub/email/templates/new-message-from-program";
import { prisma } from "@dub/prisma";
import { NotificationEmailType } from "@dub/prisma/client";
import { log } from "@dub/utils";
import { subDays } from "date-fns";
import { z } from "zod";
import { logAndRespond } from "../../utils";

export const dynamic = "force-dynamic";

const schema = z.object({
programId: z.string(),
partnerId: z.string(),
lastMessageId: z.string(),
});

// POST /api/cron/messages/notify-partner
// Notify a partner about unread messages from a program
export async function POST(req: Request) {
try {
const rawBody = await req.text();

await verifyQstashSignature({
req,
rawBody,
});

const { programId, partnerId, lastMessageId } = schema.parse(
JSON.parse(rawBody),
);

const programEnrollment = await prisma.programEnrollment.findUniqueOrThrow({
where: {
partnerId_programId: {
partnerId,
programId,
},
status: "approved",
},
include: {
Comment on lines +36 to +44
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Prisma: findUniqueOrThrow cannot filter by non-unique field "status".

Use findFirstOrThrow with a composite where instead.

-const programEnrollment = await prisma.programEnrollment.findUniqueOrThrow({
-  where: {
-    partnerId_programId: {
-      partnerId,
-      programId,
-    },
-    status: "approved",
-  },
+const programEnrollment = await prisma.programEnrollment.findFirstOrThrow({
+  where: {
+    partnerId,
+    programId,
+    status: "approved",
+  },
   include: {
     program: true,
     partner: {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const programEnrollment = await prisma.programEnrollment.findUniqueOrThrow({
where: {
partnerId_programId: {
partnerId,
programId,
},
status: "approved",
},
include: {
const programEnrollment = await prisma.programEnrollment.findFirstOrThrow({
where: {
partnerId,
programId,
status: "approved",
},
include: {
program: true,
partner: {
/* existing partner include fields */
},
},
});
🤖 Prompt for AI Agents
In apps/web/app/(ee)/api/cron/messages/notify-partner/route.ts around lines 35
to 43, the code uses prisma.programEnrollment.findUniqueOrThrow with a filter on
non-unique field "status"; replace findUniqueOrThrow with findFirstOrThrow and
supply a normal where object that includes partnerId, programId and status:
"approved" (or an explicit AND) so Prisma can filter by the non-unique status
field and still return or throw correctly.

program: true,
partner: {
include: {
messages: {
where: {
programId,
senderPartnerId: null, // Not sent by the partner
createdAt: {
gt: subDays(new Date(), 3), // Sent in the last 3 days
},
readInApp: null, // Unread
readInEmail: null, // Unread
emails: {
none: {}, // No emails sent yet
},
},
include: {
senderUser: true,
},
},
users: {
include: {
user: true,
},
where: {
notificationPreferences: {
newMessageFromProgram: true,
},
},
},
},
},
},
});

const unreadMessages = programEnrollment.partner.messages.sort(
(a, b) => a.createdAt.getTime() - b.createdAt.getTime(),
);

if (unreadMessages.length === 0)
return logAndRespond(
`No unread messages found for partner ${partnerId} in program ${programId}. Skipping...`,
);

if (unreadMessages[unreadMessages.length - 1].id !== lastMessageId)
return logAndRespond(
`There is a more recent unread message than ${lastMessageId}. Skipping...`,
);

const partnerEmailsToNotify = programEnrollment.partner.users
.map(({ user }) => user.email)
.filter(Boolean) as string[];

if (partnerEmailsToNotify.length === 0)
return logAndRespond(
`No partner emails to notify for partner ${partnerId}. Skipping...`,
);

const program = programEnrollment.program;

const { data, error } = await resend.batch.send(
partnerEmailsToNotify.map((email) => ({
subject: `${program.name} sent ${unreadMessages.length === 1 ? "a message" : `${unreadMessages.length} messages`}`,
from: VARIANT_TO_FROM_MAP.notifications,
to: email,
react: NewMessageFromProgram({
program: {
name: program.name,
logo: program.logo,
slug: program.slug,
},
messages: unreadMessages.map((message) => ({
text: message.text,
createdAt: message.createdAt,
user: message.senderUser.name
? {
name: message.senderUser.name,
image: message.senderUser.image,
}
: {
name: program.name,
image: program.logo,
},
})),
Comment on lines +116 to +128
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Possible NPE: senderUser can be null.

Guard with optional chaining before accessing name/image.

           messages: unreadMessages.map((message) => ({
             text: message.text,
             createdAt: message.createdAt,
-            user: message.senderUser.name
-              ? {
-                  name: message.senderUser.name,
-                  image: message.senderUser.image,
-                }
-              : {
-                  name: program.name,
-                  image: program.logo,
-                },
+            user: message.senderUser?.name
+              ? {
+                  name: message.senderUser.name,
+                  image: message.senderUser.image,
+                }
+              : {
+                  name: program.name,
+                  image: program.logo,
+                },
           })),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
messages: unreadMessages.map((message) => ({
text: message.text,
createdAt: message.createdAt,
user: message.senderUser.name
? {
name: message.senderUser.name,
image: message.senderUser.image,
}
: {
name: program.name,
image: program.logo,
},
})),
messages: unreadMessages.map((message) => ({
text: message.text,
createdAt: message.createdAt,
user: message.senderUser?.name
? {
name: message.senderUser.name,
image: message.senderUser.image,
}
: {
name: program.name,
image: program.logo,
},
})),
🤖 Prompt for AI Agents
In apps/web/app/(ee)/api/cron/messages/notify-partner/route.ts around lines 115
to 127, the mapping reads message.senderUser.name and message.senderUser.image
directly which can throw when senderUser is null; update the mapping to use
optional chaining (e.g. message.senderUser?.name and message.senderUser?.image)
and fall back to program.name/program.logo when those values are falsy so you
never dereference a null senderUser.

email,
}),
tags: [{ name: "type", value: "message-notification" }],
})),
);

if (error)
throw new Error(
`Error sending message emails to partner ${partnerId}: ${error.message}`,
);

if (!data)
throw new Error(
`No data received from sending message emails to partner ${partnerId}`,
);

await prisma.notificationEmail.createMany({
data: unreadMessages.flatMap((message) =>
data.data.map(({ id }) => ({
type: NotificationEmailType.Message,
emailId: id,
messageId: message.id,
})),
),
});

return logAndRespond(
`Emails sent for messages from program ${programId} to partner ${partnerId}.`,
);
} catch (error) {
await log({
message: `Error notifying partner of new messages: ${error.message}`,
type: "alerts",
});

return handleAndReturnErrorResponse(error);
}
}
167 changes: 167 additions & 0 deletions apps/web/app/(ee)/api/cron/messages/notify-program/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import { handleAndReturnErrorResponse } from "@/lib/api/errors";
import { verifyQstashSignature } from "@/lib/cron/verify-qstash";
import { resend } from "@dub/email/resend";
import { VARIANT_TO_FROM_MAP } from "@dub/email/resend/constants";
import NewMessageFromPartner from "@dub/email/templates/new-message-from-partner";
import { prisma } from "@dub/prisma";
import { NotificationEmailType } from "@dub/prisma/client";
import { log } from "@dub/utils";
import { subDays } from "date-fns";
import { z } from "zod";
import { logAndRespond } from "../../utils";

export const dynamic = "force-dynamic";

const schema = z.object({
programId: z.string(),
partnerId: z.string(),
lastMessageId: z.string(),
});

// POST /api/cron/messages/notify-program
// Notify a program about unread messages from a partner
export async function POST(req: Request) {
try {
const rawBody = await req.text();

await verifyQstashSignature({
req,
rawBody,
});

const { programId, partnerId, lastMessageId } = schema.parse(
JSON.parse(rawBody),
);

const programEnrollment = await prisma.programEnrollment.findUniqueOrThrow({
where: {
partnerId_programId: {
partnerId,
programId,
},
status: "approved",
},
include: {
Comment on lines +36 to +44
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix non-unique Prisma query (use findFirstOrThrow).

findUniqueOrThrow cannot take non-unique filters like status. Use findFirstOrThrow with a composite where.

-    const programEnrollment = await prisma.programEnrollment.findUniqueOrThrow({
-      where: {
-        partnerId_programId: {
-          partnerId,
-          programId,
-        },
-        status: "approved",
-      },
+    const programEnrollment = await prisma.programEnrollment.findFirstOrThrow({
+      where: {
+        partnerId,
+        programId,
+        status: "approved",
+      },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const programEnrollment = await prisma.programEnrollment.findUniqueOrThrow({
where: {
partnerId_programId: {
partnerId,
programId,
},
status: "approved",
},
include: {
const programEnrollment = await prisma.programEnrollment.findFirstOrThrow({
where: {
partnerId,
programId,
status: "approved",
},
include: {
🤖 Prompt for AI Agents
In apps/web/app/(ee)/api/cron/messages/notify-program/route.ts around lines 35
to 43, the code uses findUniqueOrThrow with a non-unique filter (status) which
is invalid; replace findUniqueOrThrow with findFirstOrThrow and move the filters
into a single where object that includes partnerId, programId and status (e.g.,
where: { partnerId, programId, status: "approved" }), preserving the include
block and behavior otherwise so the query returns the first matching enrollment
or throws.

program: {
include: {
workspace: {
include: {
users: {
include: {
user: true,
},
where: {
notificationPreference: {
newMessageFromPartner: true,
},
},
Comment on lines +53 to +57
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Typo in relation filter: notificationPreferences (plural).

The partner-facing counterpart uses notificationPreferences; singular will fail.

-                    notificationPreference: {
+                    notificationPreferences: {
                       newMessageFromPartner: true,
                     },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
where: {
notificationPreference: {
newMessageFromPartner: true,
},
},
where: {
notificationPreferences: {
newMessageFromPartner: true,
},
},
🤖 Prompt for AI Agents
In apps/web/app/(ee)/api/cron/messages/notify-program/route.ts around lines 52
to 56, the relation filter uses notificationPreference (singular) which is
incorrect and will fail; change the relation key to notificationPreferences
(plural) so it matches the partner-facing counterpart, ensuring the where clause
targets notificationPreferences: { newMessageFromPartner: true }.

},
},
},
},
},
partner: {
include: {
messages: {
where: {
programId,
senderPartnerId: {
not: null, // Sent by the partner
},
createdAt: {
gt: subDays(new Date(), 3), // Sent in the last 3 days
},
readInApp: null, // Unread
readInEmail: null, // Unread
emails: {
none: {}, // No emails sent yet
},
},
include: {
senderPartner: true,
},
},
},
},
},
});

const unreadMessages = programEnrollment.partner.messages.sort(
(a, b) => a.createdAt.getTime() - b.createdAt.getTime(),
);

if (unreadMessages.length === 0)
return logAndRespond(
`No unread messages found from partner ${partnerId} in program ${programId}. Skipping...`,
);

if (unreadMessages[unreadMessages.length - 1].id !== lastMessageId)
return logAndRespond(
`There is a more recent unread message than ${lastMessageId}. Skipping...`,
);

const userEmailsToNotify = programEnrollment.program.workspace.users
.map(({ user }) => user.email)
.filter(Boolean) as string[];

if (userEmailsToNotify.length === 0)
return logAndRespond(
`No program user emails to notify from partner ${partnerId}. Skipping...`,
);

const { program, partner } = programEnrollment;

const { data, error } = await resend.batch.send(
userEmailsToNotify.map((email) => ({
subject: `${unreadMessages.length === 1 ? "New message from" : `${unreadMessages.length} new messages from`} ${partner.name}`,
from: VARIANT_TO_FROM_MAP.notifications,
to: email,
react: NewMessageFromPartner({
workspaceSlug: program.workspace.slug,
partner: {
id: partner.id,
name: partner.name,
image: partner.image,
},
messages: unreadMessages.map((message) => ({
text: message.text,
createdAt: message.createdAt,
})),
email,
}),
tags: [{ name: "type", value: "message-notification" }],
})),
);

if (error)
throw new Error(
`Error sending message emails to program ${programId} users: ${error.message}`,
);

if (!data)
throw new Error(
`No data received from sending message emails to program ${programId} users`,
);

await prisma.notificationEmail.createMany({
data: unreadMessages.flatMap((message) =>
data.data.map(({ id }) => ({
type: NotificationEmailType.Message,
emailId: id,
messageId: message.id,
})),
),
});
Comment on lines +146 to +154
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Make writes idempotent on retries.

Enable skipDuplicates to guard against replays/partial failures.

-    await prisma.notificationEmail.createMany({
-      data: unreadMessages.flatMap((message) =>
+    await prisma.notificationEmail.createMany({
+      skipDuplicates: true,
+      data: unreadMessages.flatMap((message) =>
         data.data.map(({ id }) => ({
           type: NotificationEmailType.Message,
           emailId: id,
           messageId: message.id,
         })),
       ),
     });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
await prisma.notificationEmail.createMany({
data: unreadMessages.flatMap((message) =>
data.data.map(({ id }) => ({
type: NotificationEmailType.Message,
emailId: id,
messageId: message.id,
})),
),
});
await prisma.notificationEmail.createMany({
skipDuplicates: true,
data: unreadMessages.flatMap((message) =>
data.data.map(({ id }) => ({
type: NotificationEmailType.Message,
emailId: id,
messageId: message.id,
})),
),
});
🤖 Prompt for AI Agents
In apps/web/app/(ee)/api/cron/messages/notify-program/route.ts around lines 146
to 154 the createMany call can create duplicate notificationEmail rows on
retries; make the write idempotent by enabling skipDuplicates: true on the
createMany options (and ensure a suitable unique constraint/index exists on the
combination of fields used to identify duplicates, e.g. emailId+messageId+type,
so skipDuplicates can work as intended).


return logAndRespond(
`Emails sent for messages from partner ${partnerId} to program ${programId} users.`,
);
} catch (error) {
await log({
message: `Error notifying program users of new messages: ${error.message}`,
type: "alerts",
});

return handleAndReturnErrorResponse(error);
}
}
39 changes: 39 additions & 0 deletions apps/web/app/(ee)/api/messages/count/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { getDefaultProgramIdOrThrow } from "@/lib/api/programs/get-default-program-id-or-throw";
import { withWorkspace } from "@/lib/auth";
import { countMessagesQuerySchema } from "@/lib/zod/schemas/messages";
import { prisma } from "@dub/prisma";
import { NextResponse } from "next/server";

// GET /api/messages/count - count messages for a program
export const GET = withWorkspace(
async ({ workspace, searchParams }) => {
const programId = getDefaultProgramIdOrThrow(workspace);

const { unread } = countMessagesQuerySchema.parse(searchParams);

const count = await prisma.message.count({
where: {
programId,
...(unread !== undefined && {
// Only count messages from the partner
senderPartnerId: {
not: null,
},
readInApp: unread
? // Only count unread messages
null
: {
// Only count read messages
not: null,
},
}),
},
});
Comment on lines +14 to +31
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add supporting DB indexes for this count query.

This path filters on programId, senderPartnerId not null, and readInApp (null/not null). Ensure the Message model defines a composite index like:

@@index([programId, senderPartnerId, readInApp])

Without it, this endpoint can degrade under load as volume grows.

🤖 Prompt for AI Agents
In apps/web/app/(ee)/api/messages/count/route.ts around lines 14 to 31, the
count query filters by programId, senderPartnerId not null, and readInApp
null/not-null but the Message model lacks a composite DB index; add a composite
index on the Message Prisma model with the columns in the query order
(programId, senderPartnerId, readInApp) e.g. @@index([programId,
senderPartnerId, readInApp]), then run prisma migrate dev (or deploy migration)
to apply the index so the database can use it for this query and avoid table
scans under load.


return NextResponse.json(count);
},
{
requiredPermissions: ["messages.read"],
requiredPlan: ["advanced", "enterprise"],
},
);
Loading
Loading