import jwt from 'jsonwebtoken' import { Resend } from 'resend' import Member from '../../../models/member.js' import { connectDB } from '../../../utils/mongoose.js' const resend = new Resend(process.env.RESEND_API_KEY) export default defineEventHandler(async (event) => { await requireAdmin(event) const { memberIds, emailTemplate } = await validateBody(event, memberInviteSchema) await connectDB() const config = useRuntimeConfig(event) const headers = getHeaders(event) const baseUrl = process.env.BASE_URL || `${headers.host?.includes('localhost') ? 'http' : 'https'}://${headers.host}` const members = await Member.find({ _id: { $in: memberIds } }) if (members.length === 0) { throw createError({ statusCode: 404, statusMessage: 'No members found for the provided IDs' }) } const results = [] for (const member of members) { try { // Generate 48-hour magic login token (same format as login.post.js) const token = jwt.sign( { memberId: member._id, redirect: 'wiki' }, config.jwtSecret, { expiresIn: '48h' } ) const loginLink = `${baseUrl}/api/auth/verify?token=${token}` // Interpolate template variables const emailText = emailTemplate .replace(/\{name\}/g, member.name) .replace(/\{loginLink\}/g, loginLink) .replace(/\{circle\}/g, member.circle) // Build HTML version: escape user content, linkify plain URLs, then insert button (unescaped) last const loginButton = `Sign in to Ghost Guild` const emailHtml = emailTemplate .replace(/&/g, '&') .replace(//g, '>') .replace(/\{name\}/g, member.name) .replace(/\{circle\}/g, member.circle) .replace(/(https?:\/\/[^\s<]+)/g, '$1') .replace(/\n/g, '
') .replace(/\{loginLink\}/g, loginButton) const { error: sendError } = await resend.emails.send({ from: 'Ghost Guild ', to: [member.email], subject: 'You\'re invited to Ghost Guild', text: emailText, html: emailHtml }) if (sendError) { results.push({ memberId: member._id, email: member.email, success: false, error: sendError.message }) continue } // Mark member as active and record invite sent member.status = 'active' member.inviteEmailSent = true member.inviteEmailSentAt = new Date() await member.save() results.push({ memberId: member._id, email: member.email, success: true }) } catch (err) { results.push({ memberId: member._id, email: member.email, success: false, error: err.message }) } } const sent = results.filter(r => r.success).length const failed = results.filter(r => !r.success).length return { sent, failed, results } })