diff --git a/server/api/admin/members/import.post.js b/server/api/admin/members/import.post.js new file mode 100644 index 0000000..08ebeb8 --- /dev/null +++ b/server/api/admin/members/import.post.js @@ -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 } +}) diff --git a/server/models/member.js b/server/models/member.js index aa00bf2..e1bf537 100644 --- a/server/models/member.js +++ b/server/models/member.js @@ -133,6 +133,9 @@ const memberSchema = new mongoose.Schema({ }, }, + inviteEmailSent: { type: Boolean, default: false }, + inviteEmailSentAt: Date, + createdAt: { type: Date, default: Date.now }, lastLogin: Date, }); diff --git a/server/utils/schemas.js b/server/utils/schemas.js index 5bd2522..8d87089 100644 --- a/server/utils/schemas.js +++ b/server/utils/schemas.js @@ -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) +})