EventBuzz_Backend/scripts/copy-event-variant.js

550 lines
20 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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);
});