Add missing schemas, member model fields, and import endpoint

Adds memberInviteSchema and bulkMemberImportSchema needed by the invite
and CSV import endpoints. Adds inviteEmailSent/inviteEmailSentAt fields
to member model. Adds the bulk import API route.
This commit is contained in:
Jennie Robinson Faber 2026-03-19 11:44:49 +00:00
parent 5b4ca1b41d
commit 2705d171bd
3 changed files with 70 additions and 0 deletions

View file

@ -0,0 +1,53 @@
import Member from '../../../models/member.js'
import { connectDB } from '../../../utils/mongoose.js'
export default defineEventHandler(async (event) => {
await requireAdmin(event)
const { members } = await validateBody(event, bulkMemberImportSchema)
await connectDB()
// Check for duplicate emails within the batch
const batchEmails = members.map(m => m.email)
const uniqueEmails = new Set(batchEmails)
if (uniqueEmails.size !== batchEmails.length) {
throw createError({
statusCode: 400,
statusMessage: 'Duplicate emails found in import batch'
})
}
// Check which emails already exist in the DB
const existingMembers = await Member.find(
{ email: { $in: batchEmails } },
{ email: 1 }
)
const existingEmails = new Set(existingMembers.map(m => m.email))
const results = []
for (const row of members) {
if (existingEmails.has(row.email)) {
results.push({ email: row.email, success: false, error: 'Email already exists' })
continue
}
try {
const member = new Member({
name: row.name,
email: row.email,
circle: row.circle,
contributionTier: row.contributionTier,
slackInvited: false
})
const saved = await member.save()
results.push({ email: row.email, success: true, memberId: saved._id })
} catch (err) {
results.push({ email: row.email, success: false, error: err.message })
}
}
const created = results.filter(r => r.success).length
const failed = results.filter(r => !r.success).length
return { created, failed, results }
})

View file

@ -133,6 +133,9 @@ const memberSchema = new mongoose.Schema({
},
},
inviteEmailSent: { type: Boolean, default: false },
inviteEmailSentAt: Date,
createdAt: { type: Date, default: Date.now },
lastLogin: Date,
});

View file

@ -330,3 +330,17 @@ export const adminMemberCreateSchema = z.object({
export const adminRoleUpdateSchema = z.object({
role: z.enum(['admin', 'member'])
})
export const bulkMemberImportSchema = z.object({
members: z.array(z.object({
name: z.string().min(1).max(200),
email: z.string().trim().toLowerCase().email(),
circle: z.enum(['community', 'founder', 'practitioner']),
contributionTier: z.enum(['0', '5', '15', '30', '50'])
})).min(1).max(100)
})
export const memberInviteSchema = z.object({
memberIds: z.array(z.string().min(1)).min(1).max(100),
emailTemplate: z.string().min(1).max(10000)
})