EventBuzz_Backend/scripts/copy-event-to-account.js

442 lines
15 KiB
JavaScript

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