/** * Copy an event within the same organization with overrides (name, dates, venue). * Copies forms, agenda, microsite config, tickets, pages, etc. — no submissions/responses. * * Usage: * node scripts/copy-event-variant.js */ const path = require('path'); require('dotenv').config({ path: path.join(__dirname, '..', '.env') }); const mongoose = require('mongoose'); const Event = require('../src/models/Event'); const Form = require('../src/models/Form'); const Track = require('../src/models/Track'); const Session = require('../src/models/Session'); const Speaker = require('../src/models/Speaker'); const EventPage = require('../src/models/EventPage'); const EventMicrosite = require('../src/models/EventMicrosite'); const EventStaticMicrosite = require('../src/models/EventStaticMicrosite'); const EventTicket = require('../src/models/EventTicket'); const QuizPoll = require('../src/models/QuizPoll'); const ParticipantList = require('../src/models/ParticipantList'); const KnowledgeSource = require('../src/models/KnowledgeSource'); const ContextChunk = require('../src/models/ContextChunk'); const OrganizationUserMap = require('../src/models/OrganizationUserMap'); const { syncQuizPollToFirebase } = require('../src/utils/quizPollFirebaseSync'); const SOURCE_EVENT_ID = '6a4cb75a7475c040cc6f2dd7'; const PUBLIC_WEB_BASE = (process.env.PUBLIC_WEB_BASE || 'https://web.eventbuzz.live').replace(/\/$/, ''); const TARGETS = [ { name: 'NOVEL Lung Health Summit - Kochi - 22nd & 23rd August 2026', startDate: new Date('2026-08-22T12:00:00.000Z'), endDate: new Date('2026-08-23T12:00:00.000Z'), location: 'Marriott, Kochi', subdomain: 'novel2026-kochi-22-23-aug', shortPlace: 'Kochi', dateLabel: '22nd & 23rd August 2026', dateShort: 'Aug 22–23, 2026', countdownDate: '2026-08-22', scheduleDay1: 'August 22, 2026', scheduleDay2: 'August 23, 2026', mapQuery: 'Marriott+Hotel+Kochi', sourceSessionBase: new Date('2026-08-08T00:00:00.000Z'), sessionBase: new Date('2026-08-22T00:00:00.000Z'), }, { name: 'NOVEL Lung Health Summit - Dehradun - 19th & 20th September 2026', startDate: new Date('2026-09-19T12:00:00.000Z'), endDate: new Date('2026-09-20T12:00:00.000Z'), location: 'Hyatt Regency, Dehradun', subdomain: 'novel2026-dehradun-19-20-sep', shortPlace: 'Dehradun', dateLabel: '19th & 20th September 2026', dateShort: 'Sep 19–20, 2026', countdownDate: '2026-09-19', scheduleDay1: 'September 19, 2026', scheduleDay2: 'September 20, 2026', mapQuery: 'Hyatt+Regency+Dehradun', sourceSessionBase: new Date('2026-08-08T00:00:00.000Z'), sessionBase: new Date('2026-09-19T00:00:00.000Z'), }, ]; function oid(v) { return v != null ? String(v) : ''; } function stripDoc(doc) { const o = { ...doc }; delete o._id; delete o.__v; delete o.createdAt; delete o.updatedAt; return o; } function buildIdMap() { const map = new Map(); return { set(oldId, newId) { if (oldId != null && newId != null) map.set(oid(oldId), oid(newId)); }, get(oldId) { return map.get(oid(oldId)); }, remap(value) { if (value == null) return value; const s = oid(value); if (!mongoose.Types.ObjectId.isValid(s)) return value; const mapped = map.get(s); if (!mapped) return value; return mongoose.Types.ObjectId.isValid(mapped) ? new mongoose.Types.ObjectId(mapped) : mapped; }, remapString(value) { if (value == null) return value; const s = String(value).trim(); if (!mongoose.Types.ObjectId.isValid(s)) return value; return map.get(s) || value; }, deep(obj) { if (obj == null) return obj; if (Array.isArray(obj)) return obj.map((item) => this.deep(item)); if (obj instanceof mongoose.Types.ObjectId) return this.remap(obj); if (obj instanceof Date) return obj; if (typeof obj === 'string' && mongoose.Types.ObjectId.isValid(obj) && map.has(obj)) { return map.get(obj); } if (typeof obj === 'object') { const out = {}; for (const [k, v] of Object.entries(obj)) out[k] = this.deep(v); return out; } return obj; }, }; } async function uniqueValue(base, exists) { const normalized = String(base || 'item') .trim() .toLowerCase() .replace(/[^a-z0-9-]+/g, '-') .replace(/-+/g, '-') .replace(/^-|-$/g, ''); let candidate = normalized || 'copy'; let i = 0; while (await exists(candidate)) { i += 1; candidate = `${normalized || 'copy'}-copy${i > 1 ? `-${i}` : ''}`; } return candidate; } function shiftSessionTime(date, sourceBase, targetBase) { if (!date) return date; const d = new Date(date); const offsetMs = targetBase.getTime() - sourceBase.getTime(); return new Date(d.getTime() + offsetMs); } function replaceStringsDeep(obj, replacements) { if (obj == null) return obj; if (typeof obj === 'string') { let out = obj; for (const [from, to] of replacements) { if (from) out = out.split(from).join(to); } return out; } if (Array.isArray(obj)) return obj.map((item) => replaceStringsDeep(item, replacements)); if (typeof obj === 'object') { const out = {}; for (const [k, v] of Object.entries(obj)) out[k] = replaceStringsDeep(v, replacements); return out; } return obj; } function applyMicrositeOverrides(config, target, newEventId, idMap) { let next = idMap.deep(config || {}); next.meta = next.meta || {}; next.meta.title = target.name; next.meta.eventId = String(newEventId); const replacements = [ [SOURCE_EVENT_ID, String(newEventId)], ['6a43e05fdc940a0d6e057f7b', String(newEventId)], ['NOVEL Lung Health Summit - Goa - 7th & 8th August 2026', target.name], ['NOVEL Lung Health Summit - Goa - 8th & 9th August 2026', target.name], ['Goa - 7th & 8th August 2026', `${target.shortPlace} - ${target.dateLabel}`], ['Goa - 8th & 9th August 2026', `${target.shortPlace} - ${target.dateLabel}`], ['DoubleTree by Hilton Panjim, Goa', target.location], ['Aug 7–8, 2026', target.dateShort], ['Aug 8–9, 2026', target.dateShort], ['2026-08-07', target.countdownDate], ['2026-08-08', target.countdownDate === '2026-08-22' ? '2026-08-23' : '2026-09-20'], ['August 7, 2026', target.scheduleDay1], ['August 8, 2026', target.scheduleDay2], [ 'DoubleTree+by+Hilton+Goa+Panaji,+Chimbel,+Goa', target.mapQuery, ], ]; next = replaceStringsDeep(next, replacements); const sections = next.sections || {}; if (sections.hero) { sections.hero.date_venue = target.location; sections.hero.event_name = `${target.shortPlace} - ${target.dateLabel}`; if (sections.hero.countdown) { sections.hero.countdown.date = target.countdownDate; } } if (sections.informationBar?.items) { for (const item of sections.informationBar.items) { if (item.label === 'Location') item.value = target.location; if (item.label === 'Date & Time') { item.value = target.dateShort; if (item.calendar) { item.calendar.title = target.name; item.calendar.start = target.countdownDate; item.calendar.end = target.countdownDate === '2026-08-22' ? '2026-08-23' : '2026-09-20'; item.calendar.location = target.location; } } } } if (sections.map) { sections.map.embedUrl = `https://maps.google.com/maps?q=${target.mapQuery}&hl=en&z=15&output=embed`; } if (sections.event_schedule?.days) { const days = sections.event_schedule.days; if (days[0]) days[0].date = target.scheduleDay1; if (days[1]) days[1].date = target.scheduleDay2; } return next; } async function copyEventAccess(sourceEventId, newEventId, orgId) { const maps = await OrganizationUserMap.find({ organization: orgId }).lean(); let copied = 0; for (const map of maps) { const access = (map.eventAccess || []).find((e) => oid(e.event) === oid(sourceEventId)); if (!access) continue; const doc = await OrganizationUserMap.findById(map._id); if (!doc) continue; const exists = doc.eventAccess.some((e) => oid(e.event) === oid(newEventId)); if (exists) continue; doc.eventAccess.push({ event: newEventId, sessionIds: [], eventRole: access.eventRole || 'Organizer', }); await doc.save(); copied += 1; } return copied; } async function copyEventVariant(sourceEvent, target, summaryRoot) { const orgId = sourceEvent.organization; const idMap = buildIdMap(); const summary = { ...summaryRoot, copied: {} }; const eventPayload = stripDoc(sourceEvent); eventPayload.organization = orgId; eventPayload.name = target.name; eventPayload.startDate = target.startDate; eventPayload.endDate = target.endDate; eventPayload.location = target.location; eventPayload.status = sourceEvent.status || 'draft'; delete eventPayload.joinCode; if (eventPayload.metadata) { eventPayload.metadata = { ...eventPayload.metadata, totalRevenue: 0, rating: 0, ratingCount: 0, }; } if (eventPayload.ticketingManualPayment) { eventPayload.ticketingManualPayment = { ...eventPayload.ticketingManualPayment, enablementTransaction: null, }; } if (eventPayload.website?.folder) { eventPayload.website.folder = await uniqueValue( `${eventPayload.website.folder}-${target.shortPlace.toLowerCase()}`, async (slug) => Boolean(await Event.findOne({ 'website.folder': slug }).select('_id').lean()), ); } const newEvent = await Event.create(eventPayload); summary.copied.eventId = oid(newEvent._id); const sourceForms = await Form.find({ event: SOURCE_EVENT_ID }).lean(); const sourceTickets = await EventTicket.find({ event: SOURCE_EVENT_ID }).lean(); const listIds = new Set(); for (const f of sourceForms) { const lid = f.settings?.onSubmitParticipantListId; if (lid) listIds.add(oid(lid)); } for (const t of sourceTickets) { if (t.participantList) listIds.add(oid(t.participantList)); } for (const listId of listIds) { const list = await ParticipantList.findById(listId).lean(); if (!list) continue; const payload = stripDoc(list); payload.tenantOrganization = orgId; payload.participantIds = []; const created = await ParticipantList.create(payload); idMap.set(listId, created._id); } summary.copied.participantLists = listIds.size; for (const form of sourceForms) { const payload = stripDoc(form); payload.tenantOrganization = orgId; payload.event = newEvent._id; if (payload.settings?.onSubmitParticipantListId) { payload.settings.onSubmitParticipantListId = idMap.remap(payload.settings.onSubmitParticipantListId); } const created = await Form.create(payload); idMap.set(form._id, created._id); } summary.copied.forms = sourceForms.length; const sourceSessions = await Session.find({ event: SOURCE_EVENT_ID }).lean(); const speakerIds = new Set(); for (const s of sourceSessions) { for (const sp of s.speakers || []) speakerIds.add(oid(sp)); if (s.moderator) speakerIds.add(oid(s.moderator)); } for (const speakerId of speakerIds) { idMap.set(speakerId, speakerId); } summary.copied.speakers = speakerIds.size; const sourceTracks = await Track.find({ event: SOURCE_EVENT_ID }).sort({ sortOrder: 1, createdAt: 1 }).lean(); for (const track of sourceTracks) { const payload = stripDoc(track); payload.event = newEvent._id; delete payload.joinCode; const created = await Track.create(payload); idMap.set(track._id, created._id); } summary.copied.tracks = sourceTracks.length; for (const session of sourceSessions) { const payload = stripDoc(session); payload.event = newEvent._id; payload.track = idMap.remap(session.track); payload.speakers = (session.speakers || []).map((sp) => idMap.remap(sp)).filter(Boolean); payload.keyPersons = (session.keyPersons || []) .map((kp) => { const speaker = idMap.remap(kp?.speaker); if (!speaker) return null; return { speaker, role: kp?.role || 'Speaker' }; }) .filter(Boolean); payload.moderator = idMap.remap(session.moderator); payload.audienceFeedbackForm = idMap.remap(session.audienceFeedbackForm); payload.audienceRatingsForm = idMap.remap(session.audienceRatingsForm); payload.startTime = shiftSessionTime(session.startTime, target.sourceSessionBase, target.sessionBase); payload.endTime = shiftSessionTime(session.endTime, target.sourceSessionBase, target.sessionBase); delete payload.joinCode; const created = await Session.create(payload); idMap.set(session._id, created._id); } summary.copied.sessions = sourceSessions.length; for (const speakerId of speakerIds) { const sessionRefs = sourceSessions .filter((s) => (s.speakers || []).some((sp) => oid(sp) === speakerId) || oid(s.moderator) === speakerId) .map((s) => idMap.get(s._id)) .filter(Boolean); if (sessionRefs.length) { await Speaker.findByIdAndUpdate(speakerId, { $addToSet: { sessions: { $each: sessionRefs } } }); } } const sourceQuizzes = await QuizPoll.find({ event: SOURCE_EVENT_ID, deletedAt: null }).lean(); for (const quiz of sourceQuizzes) { const payload = stripDoc(quiz); payload.event = newEvent._id; payload.session = idMap.remap(quiz.session); payload.settings = { ...(payload.settings || {}), presentationToken: QuizPoll.generatePresentationToken(), isLive: false, liveQuestionId: null, }; const created = await QuizPoll.create(payload); try { await syncQuizPollToFirebase(created); } catch (err) { console.warn('Firebase sync skipped for quiz', oid(created._id), err.message); } idMap.set(quiz._id, created._id); } summary.copied.quizzes = sourceQuizzes.length; const sourcePages = await EventPage.find({ event: SOURCE_EVENT_ID }).sort({ 'settings.menuOrder': 1 }).lean(); for (const page of sourcePages) { const payload = stripDoc(page); payload.event = newEvent._id; payload.slug = await uniqueValue(`${page.slug}-${target.shortPlace.toLowerCase()}`, async (slug) => Boolean(await EventPage.findOne({ slug }).select('_id').lean()), ); payload.sections = idMap.deep(payload.sections || []); if (payload.content) payload.content = idMap.deep(payload.content); const created = await EventPage.create(payload); idMap.set(page._id, created._id); } summary.copied.eventPages = sourcePages.length; const sourceMicrosites = await EventMicrosite.find({ event: SOURCE_EVENT_ID }).sort({ createdAt: 1 }).lean(); for (const ms of sourceMicrosites) { const payload = stripDoc(ms); payload.event = newEvent._id; payload.registrationFormId = idMap.remap(ms.registrationFormId); payload.sourcePageId = idMap.remap(ms.sourcePageId); if (payload.publishFolder) { payload.publishFolder = await uniqueValue(`${payload.publishFolder}-${target.shortPlace.toLowerCase()}`, async (slug) => Boolean(await EventMicrosite.findOne({ publishFolder: slug }).select('_id').lean()), ); } const created = await EventMicrosite.create(payload); idMap.set(ms._id, created._id); } summary.copied.eventMicrosites = sourceMicrosites.length; const sourceStatic = await EventStaticMicrosite.findOne({ event: SOURCE_EVENT_ID }).lean(); if (sourceStatic) { const payload = stripDoc(sourceStatic); payload.event = newEvent._id; payload.organization = orgId; payload.subdomain = await uniqueValue(target.subdomain, async (sub) => Boolean(await EventStaticMicrosite.findOne({ subdomain: sub }).select('_id').lean()), ); payload.publishedHtmlUrl = null; payload.publishedHtmlS3Key = null; payload.publishedConfigS3Key = null; payload.publishedHtmlPath = null; payload.publishedConfigPath = null; payload.publishedAt = null; payload.publishedBy = null; payload.status = 'draft'; payload.config = applyMicrositeOverrides( sourceStatic.config, target, newEvent._id, idMap, ); const regForm = sourceForms.find((f) => f.type === 'registration'); const feedbackForm = sourceForms.find((f) => f.type === 'feedback'); const newRegId = regForm ? idMap.get(regForm._id) : null; const newFeedbackId = feedbackForm ? idMap.get(feedbackForm._id) : null; if (newRegId && payload.config?.sections?.hero?.cta) { payload.config.sections.hero.cta.url = `${PUBLIC_WEB_BASE}/event/${newEvent._id}/form/${newRegId}`; } if (newFeedbackId && payload.config?.sections?.feedback) { payload.config.sections.feedback.url = `${PUBLIC_WEB_BASE}/event/${newEvent._id}/form/${newFeedbackId}`; } if (newRegId && payload.config?.sections?.footer?.links) { for (const link of payload.config.sections.footer.links) { if (link.text && /register/i.test(link.text)) { link.url = `${PUBLIC_WEB_BASE}/event/${newEvent._id}/form/${newRegId}`; } } } await EventStaticMicrosite.create(payload); summary.copied.staticMicrosite = payload.subdomain; } for (const ticket of sourceTickets) { const payload = stripDoc(ticket); payload.event = newEvent._id; payload.organization = orgId; payload.registrationForm = idMap.remap(ticket.registrationForm); payload.participantList = idMap.remap(ticket.participantList); await EventTicket.create(payload); } summary.copied.tickets = sourceTickets.length; const sourceKnowledge = await KnowledgeSource.find({ eventId: SOURCE_EVENT_ID }).lean(); for (const ks of sourceKnowledge) { const payload = stripDoc(ks); payload.eventId = newEvent._id; const createdKs = await KnowledgeSource.create(payload); idMap.set(ks._id, createdKs._id); const chunks = await ContextChunk.find({ sourceId: ks._id }).lean(); if (chunks.length) { await ContextChunk.insertMany( chunks.map((chunk) => { const c = stripDoc(chunk); c.eventId = newEvent._id; c.sourceId = createdKs._id; return c; }), ); } } summary.copied.knowledgeSources = sourceKnowledge.length; if (newEvent.website?.registrationFormId) { newEvent.website.registrationFormId = idMap.remapString(newEvent.website.registrationFormId); newEvent.markModified('website'); await newEvent.save(); } else { const regForm = sourceForms.find((f) => f.type === 'registration'); if (regForm && idMap.get(regForm._id)) { newEvent.website = newEvent.website || {}; newEvent.website.registrationFormId = idMap.get(regForm._id); newEvent.markModified('website'); await newEvent.save(); } } summary.copied.eventAccessGrants = await copyEventAccess(SOURCE_EVENT_ID, newEvent._id, orgId); summary.newEventId = oid(newEvent._id); summary.newEventName = newEvent.name; summary.location = target.location; summary.subdomain = summary.copied.staticMicrosite; return summary; } async function run() { const uri = process.env.MONGODB_URI; if (!uri) { console.error('MONGODB_URI is not set'); process.exit(1); } await mongoose.connect(uri); const sourceEvent = await Event.findById(SOURCE_EVENT_ID).lean(); if (!sourceEvent) { console.error('Source event not found:', SOURCE_EVENT_ID); process.exit(1); } const results = []; for (const target of TARGETS) { console.log(`\nCopying → ${target.name}...`); const summary = await copyEventVariant(sourceEvent, target, { sourceEventId: SOURCE_EVENT_ID, sourceEventName: sourceEvent.name, organizationId: oid(sourceEvent.organization), }); results.push(summary); console.log(JSON.stringify(summary, null, 2)); } console.log('\nAll events created:'); console.log(JSON.stringify(results, null, 2)); await mongoose.disconnect(); } run().catch((err) => { console.error(err); process.exit(1); });