/** * Deep-copy an event (structure only — no submissions, participants, or responses) * into another account's organization. * * Run: node scripts/copy-event-to-account.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 User = require('../src/models/User'); const OrganizationUserMap = require('../src/models/OrganizationUserMap'); const { syncQuizPollToFirebase } = require('../src/utils/quizPollFirebaseSync'); const SOURCE_EVENT_ID = '6a43e05fdc940a0d6e057f7b'; const TARGET_EMAIL = 'anil.patil@stratawiz.com'; const CREATOR_EVENT_ROLE = 'Organizer'; 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)); }, has(oldId) { return map.has(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; } async function assignCreatorToEventStaff(creatorUserId, orgId, eventId) { if (!creatorUserId || !orgId || !eventId) return; const target = await User.findById(creatorUserId).select('organization role').lean(); if (!target?.organization || oid(target.organization) !== oid(orgId)) return; let map = await OrganizationUserMap.findOne({ user: creatorUserId, organization: orgId }); if (!map) { map = await OrganizationUserMap.create({ user: creatorUserId, organization: orgId, role: target.role || 'viewer', eventAccess: [], }); } const eid = oid(eventId); if (map.eventAccess.some((e) => oid(e.event) === eid)) return; map.eventAccess.push({ event: eventId, sessionIds: [], eventRole: CREATOR_EVENT_ROLE }); await map.save(); } 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 targetUser = await User.findOne({ email: TARGET_EMAIL }).lean(); if (!targetUser) { console.error('Target user not found:', TARGET_EMAIL); process.exit(1); } const targetOrgId = targetUser.organization; if (!targetOrgId) { console.error('Target user has no organization'); process.exit(1); } const idMap = buildIdMap(); const summary = { sourceEventId: SOURCE_EVENT_ID, sourceEventName: sourceEvent.name, targetEmail: TARGET_EMAIL, targetOrganizationId: oid(targetOrgId), copied: {}, }; // --- Event shell --- const eventPayload = stripDoc(sourceEvent); eventPayload.organization = targetOrgId; eventPayload.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, enablementFeePaidAt: eventPayload.ticketingManualPayment.enablementFeePaidAt || null, }; } if (eventPayload.website?.folder) { eventPayload.website.folder = await uniqueValue(eventPayload.website.folder, async (slug) => Boolean(await Event.findOne({ 'website.folder': slug }).select('_id').lean()), ); } const newEvent = await Event.create(eventPayload); summary.copied.eventId = oid(newEvent._id); // --- Participant lists referenced by forms / tickets --- 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 = targetOrgId; payload.createdBy = targetUser._id; payload.participantIds = []; const created = await ParticipantList.create(payload); idMap.set(listId, created._id); } summary.copied.participantLists = listIds.size; // --- Forms (no submissions) --- for (const form of sourceForms) { const payload = stripDoc(form); payload.tenantOrganization = targetOrgId; 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; // --- Speakers referenced by sessions --- 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) { const speaker = await Speaker.findById(speakerId).lean(); if (!speaker) continue; const existing = await Speaker.findOne({ organization: targetOrgId, email: speaker.email }).lean(); if (existing) { idMap.set(speakerId, existing._id); continue; } const payload = stripDoc(speaker); payload.organization = targetOrgId; payload.sessions = []; const created = await Speaker.create(payload); idMap.set(speakerId, created._id); } summary.copied.speakers = speakerIds.size; // --- Tracks --- 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; // --- Sessions --- 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); delete payload.joinCode; const created = await Session.create(payload); idMap.set(session._id, created._id); } summary.copied.sessions = sourceSessions.length; // Update speaker.session back-references for (const speakerId of speakerIds) { const newSpeakerId = idMap.get(speakerId); if (!newSpeakerId) continue; 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(newSpeakerId, { $addToSet: { sessions: { $each: sessionRefs } } }); } } // --- Quiz / poll definitions (no responses) --- 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; // --- Event pages --- 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.createdBy = targetUser._id; payload.slug = await uniqueValue(`${page.slug}-${oid(newEvent._id).slice(-6)}`, 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; // --- Event microsites --- 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.createdBy = targetUser._id; payload.registrationFormId = idMap.remap(ms.registrationFormId); payload.sourcePageId = idMap.remap(ms.sourcePageId); if (payload.publishFolder) { payload.publishFolder = await uniqueValue(payload.publishFolder, 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; // --- Static microsite v2 --- const sourceStatic = await EventStaticMicrosite.findOne({ event: SOURCE_EVENT_ID }).lean(); if (sourceStatic) { const payload = stripDoc(sourceStatic); payload.event = newEvent._id; payload.organization = targetOrgId; payload.subdomain = await uniqueValue(sourceStatic.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.updatedBy = targetUser._id; payload.config = idMap.deep(payload.config || {}); await EventStaticMicrosite.create(payload); summary.copied.staticMicrosite = payload.subdomain; } // --- Tickets --- for (const ticket of sourceTickets) { const payload = stripDoc(ticket); payload.event = newEvent._id; payload.organization = targetOrgId; payload.registrationForm = idMap.remap(ticket.registrationForm); payload.participantList = idMap.remap(ticket.participantList); await EventTicket.create(payload); } summary.copied.tickets = sourceTickets.length; // --- Knowledge sources + chunks (concierge) --- 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; // --- Remap website registration form on event --- 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(); } } await assignCreatorToEventStaff(targetUser._id, targetOrgId, newEvent._id); summary.newEventId = oid(newEvent._id); summary.newEventName = newEvent.name; summary.skipped = { formSubmissions: true, quizResponses: true, participants: true, attendance: true, payments: true, }; console.log('Event copied successfully'); console.log(JSON.stringify(summary, null, 2)); await mongoose.disconnect(); } run().catch((err) => { console.error(err); process.exit(1); });