Replaces the per-file inviteToSlack helpers with a single auto-flag call. Self-serve activation paths now check for pre-existing workspace membership (silent on miss) instead of attempting an admin-only invite. - helcim/subscription.post.js: removed local inviteToSlack; both free- and paid-tier activation branches now call the helper, then notifyNewMember with the canonical 'manual_invitation_required' arg. - members/create.post.js: same shape — helper + canonical notify arg. - invite/accept.post.js (free-tier branch): added the helper call after member creation. Free-tier had no prior Slack call (audit confirmed); paid-tier remains untouched and activates via the Helcim webhook. Admin-created and CSV-imported members intentionally do NOT call the helper — admins flip the flag manually after sending the invite. Test stub for autoFlagPreExistingSlackAccess added to server setup.
121 lines
3.9 KiB
JavaScript
121 lines
3.9 KiB
JavaScript
import jwt from 'jsonwebtoken'
|
|
import PreRegistration from '../../models/preRegistration.js'
|
|
import Member from '../../models/member.js'
|
|
import { connectDB } from '../../utils/mongoose.js'
|
|
import { setAuthCookie } from '../../utils/auth.js'
|
|
import { assignMemberNumber } from '../../utils/memberNumber.js'
|
|
import { createHelcimCustomer } from '../../utils/helcim.js'
|
|
|
|
export default defineEventHandler(async (event) => {
|
|
const body = await validateBody(event, inviteAcceptSchema)
|
|
const config = useRuntimeConfig(event)
|
|
await connectDB()
|
|
|
|
// Re-verify the token is still valid (not expired)
|
|
let decoded
|
|
try {
|
|
decoded = jwt.verify(body.token, config.jwtSecret)
|
|
} catch {
|
|
throw createError({ statusCode: 401, statusMessage: 'Invalid or expired invitation link' })
|
|
}
|
|
|
|
if (decoded.type !== 'prereg-invite' || decoded.preRegistrationId !== body.preRegistrationId) {
|
|
throw createError({ statusCode: 401, statusMessage: 'Invalid or expired invitation link' })
|
|
}
|
|
|
|
const preReg = await PreRegistration.findById(body.preRegistrationId)
|
|
if (!preReg) {
|
|
throw createError({ statusCode: 404, statusMessage: 'Pre-registration not found' })
|
|
}
|
|
|
|
if (preReg.status === 'accepted') {
|
|
throw createError({ statusCode: 400, statusMessage: 'This invitation has already been accepted' })
|
|
}
|
|
|
|
// Check no existing member with this email
|
|
const existingMember = await Member.findOne({ email: preReg.email })
|
|
if (existingMember) {
|
|
throw createError({ statusCode: 409, statusMessage: 'A member with this email already exists' })
|
|
}
|
|
|
|
// For paid invites, create the Helcim customer up front so we can store the ID
|
|
// on the Member at creation time. Done before Member.create so a Helcim failure
|
|
// doesn't leave us with an orphan Member doc.
|
|
let helcimCustomer = null
|
|
if (body.contributionAmount > 0) {
|
|
helcimCustomer = await createHelcimCustomer({
|
|
customerType: 'PERSON',
|
|
contactName: body.name,
|
|
email: preReg.email,
|
|
})
|
|
}
|
|
|
|
// Create the member
|
|
const member = await Member.create({
|
|
email: preReg.email,
|
|
name: body.name,
|
|
pronouns: body.pronouns || undefined,
|
|
location: body.location || undefined,
|
|
circle: body.circle,
|
|
contributionAmount: body.contributionAmount,
|
|
bio: body.motivation || undefined,
|
|
status: body.contributionAmount === 0 ? 'active' : 'pending_payment',
|
|
helcimCustomerId: helcimCustomer?.id,
|
|
helcimCustomerCode: helcimCustomer?.customerCode,
|
|
agreement: { acceptedAt: new Date() },
|
|
})
|
|
|
|
await assignMemberNumber(member._id)
|
|
|
|
// Update pre-registration
|
|
await PreRegistration.findByIdAndUpdate(preReg._id, {
|
|
$set: {
|
|
status: 'accepted',
|
|
acceptedAt: new Date(),
|
|
memberId: member._id,
|
|
}
|
|
})
|
|
|
|
logActivity(member._id, 'member_joined', {
|
|
source: 'pre-registration',
|
|
preRegistrationId: preReg._id,
|
|
})
|
|
|
|
// Issue session cookie so the member is authenticated for any follow-up calls
|
|
// (Helcim initialize-payment for paid flow, dashboard fetches for free flow).
|
|
setAuthCookie(event, member)
|
|
|
|
// For free tier, redirect to welcome
|
|
if (body.contributionAmount === 0) {
|
|
await autoFlagPreExistingSlackAccess(member)
|
|
return {
|
|
success: true,
|
|
requiresPayment: false,
|
|
redirectUrl: '/member/dashboard',
|
|
member: {
|
|
id: member._id,
|
|
email: member.email,
|
|
name: member.name,
|
|
circle: member.circle,
|
|
contributionAmount: member.contributionAmount,
|
|
status: member.status,
|
|
}
|
|
}
|
|
}
|
|
|
|
// For paid tiers, return member + Helcim customer so frontend can proceed to payment
|
|
return {
|
|
success: true,
|
|
requiresPayment: true,
|
|
customerId: helcimCustomer.id,
|
|
customerCode: helcimCustomer.customerCode,
|
|
member: {
|
|
id: member._id,
|
|
email: member.email,
|
|
name: member.name,
|
|
circle: member.circle,
|
|
contributionAmount: member.contributionAmount,
|
|
status: member.status,
|
|
}
|
|
}
|
|
})
|