Admin interface to review, filter, and batch-invite the 95 pre-registrants from Baby Ghosts. Accept-invitation page pre-fills their data and collects circle, pronouns, motivation, contribution tier, and agreement before creating their member record.
108 lines
3.1 KiB
JavaScript
108 lines
3.1 KiB
JavaScript
import jwt from 'jsonwebtoken'
|
|
import PreRegistration from '../../models/preRegistration.js'
|
|
import Member from '../../models/member.js'
|
|
import { connectDB } from '../../utils/mongoose.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' })
|
|
}
|
|
|
|
// 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,
|
|
contributionTier: body.contributionTier,
|
|
bio: body.motivation || undefined,
|
|
status: body.contributionTier === '0' ? 'active' : 'pending_payment',
|
|
})
|
|
|
|
// 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,
|
|
})
|
|
|
|
// For free tier, issue session and redirect to welcome
|
|
if (body.contributionTier === '0') {
|
|
const sessionToken = jwt.sign(
|
|
{ memberId: member._id, email: member.email, tv: member.tokenVersion },
|
|
config.jwtSecret,
|
|
{ expiresIn: '7d' },
|
|
)
|
|
|
|
setCookie(event, 'auth-token', sessionToken, {
|
|
httpOnly: true,
|
|
secure: process.env.NODE_ENV === 'production',
|
|
sameSite: 'lax',
|
|
path: '/',
|
|
maxAge: 60 * 60 * 24 * 7,
|
|
})
|
|
|
|
return {
|
|
success: true,
|
|
requiresPayment: false,
|
|
redirectUrl: '/welcome',
|
|
member: {
|
|
id: member._id,
|
|
email: member.email,
|
|
name: member.name,
|
|
circle: member.circle,
|
|
contributionTier: member.contributionTier,
|
|
status: member.status,
|
|
}
|
|
}
|
|
}
|
|
|
|
// For paid tiers, return member info so frontend can proceed to Helcim payment
|
|
return {
|
|
success: true,
|
|
requiresPayment: true,
|
|
member: {
|
|
id: member._id,
|
|
email: member.email,
|
|
name: member.name,
|
|
circle: member.circle,
|
|
contributionTier: member.contributionTier,
|
|
status: member.status,
|
|
}
|
|
}
|
|
})
|