442 lines
15 KiB
JavaScript
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);
|
|
});
|