feat(admin): add member onboarding alert detectors

This commit is contained in:
Jennie Robinson Faber 2026-04-08 11:08:09 +01:00
parent d3a961f765
commit 824364d526
2 changed files with 192 additions and 2 deletions

View file

@ -34,6 +34,86 @@ export function computeSignature(ids) {
return hash.digest('hex')
}
// Alert functions land here in tasks 47.
function memberItem(member, sublabel) {
return {
id: String(member._id),
label: member.name,
sublabel: sublabel ?? member.email,
href: `/admin/members/${member._id}`
}
}
export async function detectSlackInviteFailed() {
await connectDB()
const members = await Member
.find({ slackInviteStatus: 'failed' })
.select('name email')
.lean()
return {
type: 'slack_invite_failed',
severity: 'critical',
title: 'Slack invites failed',
items: members.map((m) => memberItem(m))
}
}
export async function detectNoSlackHandleAfterWeek() {
await connectDB()
const cutoff = daysAgo(ALERT_THRESHOLDS.NO_SLACK_DAYS)
const members = await Member
.find({
status: 'active',
createdAt: { $lte: cutoff },
$or: [
{ slackUserId: { $exists: false } },
{ slackUserId: null },
{ slackUserId: '' }
]
})
.select('name email createdAt')
.lean()
return {
type: 'no_slack_handle_week',
severity: 'attention',
title: 'Active members without a Slack handle',
items: members.map((m) =>
memberItem(m, `${daysSince(m.createdAt)} days since joining`)
)
}
}
export async function detectStuckPendingPayment() {
await connectDB()
const cutoff = daysAgo(ALERT_THRESHOLDS.STUCK_PAYMENT_DAYS)
const members = await Member
.find({
status: 'pending_payment',
createdAt: { $lte: cutoff }
})
.select('name email createdAt')
.lean()
return {
type: 'stuck_pending_payment',
severity: 'attention',
title: 'Members stuck in pending payment',
items: members.map((m) =>
memberItem(m, `${daysSince(m.createdAt)} days stuck`)
)
}
}
export async function detectSuspendedMembers() {
await connectDB()
const members = await Member
.find({ status: 'suspended' })
.select('name email')
.lean()
return {
type: 'member_suspended',
severity: 'attention',
title: 'Suspended members',
items: members.map((m) => memberItem(m))
}
}
// Aggregator lands in task 8.