feat: extend notification system with batch sender, bulk dialog, and logging
Add NotificationLog schema extensions (nullable userId, email, roundId, projectId, batchId fields), batch notification sender service, and bulk notification dialog UI. Include utility scripts for debugging and seeding. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
165
scripts/seed-notification-log.ts
Normal file
165
scripts/seed-notification-log.ts
Normal file
@@ -0,0 +1,165 @@
|
||||
/**
|
||||
* Seed NotificationLog with confirmed SMTP delivery data.
|
||||
*
|
||||
* Sources:
|
||||
* 1. 33 emails confirmed delivered in Poste.io SMTP logs (2026-03-04)
|
||||
* 2. Users with status ACTIVE who are LEADs on PASSED projects
|
||||
* (they clearly received and used their invite link)
|
||||
*
|
||||
* Usage: npx tsx scripts/seed-notification-log.ts
|
||||
* Add --dry-run to preview without making changes.
|
||||
*/
|
||||
|
||||
import { PrismaClient } from '@prisma/client'
|
||||
|
||||
const prisma = new PrismaClient()
|
||||
const dryRun = process.argv.includes('--dry-run')
|
||||
|
||||
// Emails confirmed delivered via SMTP logs on 2026-03-04
|
||||
const CONFIRMED_SMTP_EMAILS = new Set([
|
||||
'fbayong@balazstudio.com',
|
||||
'gnoel@kilimora.africa',
|
||||
'amal.chebbi@pigmentoco.com',
|
||||
'nairita@yarsi.net',
|
||||
'martin.itamalo@greenbrinetechnologies.com',
|
||||
'petervegan1223@gmail.com',
|
||||
'dmarinov@redget.io',
|
||||
'adrien@seavium.com',
|
||||
'l.buob@whisper-ef.com',
|
||||
'silvia@omnivorus.com',
|
||||
'marzettisebastian@gmail.com',
|
||||
'fiona.mcomish@algae-scope.com',
|
||||
'karimeguillen@rearvora.com',
|
||||
'info@skywatt.tech',
|
||||
'julia@nereia-coatings.com',
|
||||
'info@janmaisenbacher.com',
|
||||
'xbm_0201@qq.com',
|
||||
'irinakharitonova0201@gmail.com',
|
||||
'seablocksrecif@gmail.com',
|
||||
'oscar@seafuser.com',
|
||||
'charles.maher@blueshadow.dk',
|
||||
'sabirabokhari@gmail.com',
|
||||
'munayimbabura@gmail.com',
|
||||
'amritha.ramadevu@edu.escp.eu',
|
||||
'nele.jordan@myhsba.de',
|
||||
'karl.mihhels@aalto.fi',
|
||||
'christine.a.kurz@gmail.com',
|
||||
'aki@corall.eco',
|
||||
'topias.kilpinen@hotmail.fi',
|
||||
'nina.riutta.camilla@gmail.com',
|
||||
'sofie.boggiosella@my.jcu.edu.au',
|
||||
'giambattistafigari@gmail.com',
|
||||
'mussinig0@gmail.com',
|
||||
])
|
||||
|
||||
const SENT_AT = new Date('2026-03-04T01:00:00Z')
|
||||
|
||||
async function main() {
|
||||
console.log(dryRun ? '--- DRY RUN ---\n' : 'Seeding NotificationLog...\n')
|
||||
|
||||
// Find LEAD team members on PASSED projects
|
||||
const passedLeads = await prisma.teamMember.findMany({
|
||||
where: {
|
||||
role: 'LEAD',
|
||||
project: {
|
||||
projectRoundStates: {
|
||||
some: { state: 'PASSED' },
|
||||
},
|
||||
},
|
||||
},
|
||||
select: {
|
||||
userId: true,
|
||||
projectId: true,
|
||||
project: {
|
||||
select: {
|
||||
projectRoundStates: {
|
||||
where: { state: 'PASSED' },
|
||||
select: { roundId: true },
|
||||
take: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
status: true,
|
||||
inviteToken: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
console.log(`Found ${passedLeads.length} LEAD team members on PASSED projects\n`)
|
||||
|
||||
let created = 0
|
||||
let skipped = 0
|
||||
|
||||
for (const lead of passedLeads) {
|
||||
const email = lead.user.email?.toLowerCase()
|
||||
if (!email) {
|
||||
skipped++
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if a NotificationLog already exists for this project+email
|
||||
const existing = await prisma.notificationLog.findFirst({
|
||||
where: {
|
||||
email,
|
||||
projectId: lead.projectId,
|
||||
type: 'ADVANCEMENT_NOTIFICATION',
|
||||
status: 'SENT',
|
||||
},
|
||||
})
|
||||
|
||||
if (existing) {
|
||||
skipped++
|
||||
continue
|
||||
}
|
||||
|
||||
// Determine confidence of delivery
|
||||
const isConfirmedSMTP = CONFIRMED_SMTP_EMAILS.has(email)
|
||||
const isActive = lead.user.status === 'ACTIVE'
|
||||
const isInvited = lead.user.status === 'INVITED' && !!lead.user.inviteToken
|
||||
|
||||
// Only seed for confirmed deliveries or active users
|
||||
if (!isConfirmedSMTP && !isActive && !isInvited) {
|
||||
console.log(` SKIP ${email} (status=${lead.user.status}, not in SMTP logs)`)
|
||||
skipped++
|
||||
continue
|
||||
}
|
||||
|
||||
const roundId = lead.project.projectRoundStates[0]?.roundId ?? null
|
||||
const label = isConfirmedSMTP ? 'SMTP-confirmed' : isActive ? 'user-active' : 'invite-sent'
|
||||
|
||||
console.log(` ${dryRun ? 'WOULD CREATE' : 'CREATE'} ${email} [${label}] project=${lead.projectId}`)
|
||||
|
||||
if (!dryRun) {
|
||||
await prisma.notificationLog.create({
|
||||
data: {
|
||||
userId: lead.user.id,
|
||||
channel: 'EMAIL',
|
||||
type: 'ADVANCEMENT_NOTIFICATION',
|
||||
status: 'SENT',
|
||||
email,
|
||||
projectId: lead.projectId,
|
||||
roundId,
|
||||
batchId: 'seed-2026-03-04',
|
||||
createdAt: SENT_AT,
|
||||
},
|
||||
})
|
||||
created++
|
||||
} else {
|
||||
created++
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\nDone. Created: ${created}, Skipped: ${skipped}`)
|
||||
}
|
||||
|
||||
main()
|
||||
.catch((err) => {
|
||||
console.error('Error:', err)
|
||||
process.exit(1)
|
||||
})
|
||||
.finally(() => prisma.$disconnect())
|
||||
Reference in New Issue
Block a user