Feat announcement discussion notifier#53
Conversation
…nto feat-announcement-discussion-notifier
whao37
left a comment
There was a problem hiding this comment.
There might be some little bugs I've missed but these are the big ones I've seen. Please double check though since I might've missed reference files.
| export async function POST(req: Request) { | ||
| try { | ||
| const session = await auth(); | ||
| if (!session || !session.user) { | ||
| return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); | ||
| } | ||
|
|
||
| const { announcementId } = await req.json(); | ||
|
|
||
| if (!announcementId) { | ||
| return NextResponse.json( | ||
| { error: 'Announcement ID is required' }, | ||
| { status: 400 } | ||
| ); | ||
| } | ||
|
|
||
| const announcement = await prisma.announcement.findUnique({ | ||
| where: { id: announcementId }, | ||
| include: { | ||
| author: { select: { name: true } }, | ||
| }, | ||
| }); | ||
|
|
||
| if (!announcement) { | ||
| return NextResponse.json( | ||
| { error: 'Announcement not found' }, | ||
| { status: 404 } | ||
| ); | ||
| } | ||
|
|
||
| // Build role filter based on groupType; exclude users who opted out of emails | ||
| const roleFilter = | ||
| announcement.groupType === GroupType.ALL | ||
| ? {} | ||
| : { role: announcement.groupType as unknown as UserRole }; | ||
|
|
||
| const users = await prisma.user.findMany({ | ||
| where: { ...roleFilter, announcementEmailOptOut: false }, | ||
| select: { email: true, name: true }, | ||
| }); |
There was a problem hiding this comment.
I believe any authenticated user can POST with an announcementId or threadId can trigger resent to every user who aren't opted out. I'm not sure if there is an admin/staff check already here but this could cause abuse from bad actors.
There was a problem hiding this comment.
/api/announcement-emails: now requires role === ADMIN; any other authenticated user gets a 401.
/api/discussion-emails: now verifies the caller is the thread's author thread.author.id === session.user.id; mismatched callers get a 401.
| export async function POST(req: Request) { | ||
| try { | ||
| const session = await auth(); | ||
| if (!session || !session.user) { | ||
| return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); | ||
| } | ||
|
|
||
| const { threadId } = await req.json(); | ||
|
|
||
| if (!threadId) { | ||
| return NextResponse.json( | ||
| { error: 'Thread ID is required' }, | ||
| { status: 400 } | ||
| ); | ||
| } | ||
|
|
||
| const thread = await prisma.thread.findUnique({ | ||
| where: { id: threadId }, | ||
| include: { | ||
| author: { select: { name: true } }, | ||
| }, | ||
| }); | ||
|
|
||
| if (!thread) { | ||
| return NextResponse.json({ error: 'Thread not found' }, { status: 404 }); | ||
| } | ||
|
|
||
| // Build role filter based on groupType; exclude users who opted out of emails | ||
| const roleFilter = | ||
| thread.groupType === GroupType.ALL ? {} : { role: thread.groupType }; | ||
|
|
||
| const users = await prisma.user.findMany({ | ||
| where: { ...roleFilter, discussionEmailOptOut: false }, | ||
| select: { email: true, name: true }, | ||
| }); |
There was a problem hiding this comment.
same as abaove
|
@TrickkyRicky To reiterate something we talked about synchronously: I think this PR looks good, but I know there's a bit more testing you want to do. Happy to approve and merge when you are ready. |
Summary
Adds automated email notifications whenever a new announcement or discussion thread is created, so platform members are immediately informed without having to check the app manually.
Email Notifications
New email templates
src/emails/AnnouncementNotification.tsx- email template for new announcementssrc/emails/NewThreadNotification.tsx- email template for new discussion threadsNew API routes
src/app/api/announcement-emails/route.ts-POSTendpoint that looks up an announcement by ID, queries users filtered by the announcement'sgroupType(ALL,NONPROFIT,SUPPLIER,ADMIN), and sends a batch email via Resendsrc/app/api/discussion-emails/route.ts- same pattern for discussion threadsTrigger integration
src/app/announcements/announcements-grid.tsx- firesPOST /api/announcement-emailsafter a new announcement is createdsrc/app/discussion/discussion-grid.tsx- firesPOST /api/discussion-emailsafter a new thread is created; both calls are fire-and-forgetImages