166 lines
4.3 KiB
TypeScript
166 lines
4.3 KiB
TypeScript
|
|
/**
|
||
|
|
* 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())
|