#!/usr/bin/env node /** * Configure static microsite v2 for an event using crest-novel template + site.json content. * * Usage: * node scripts/setup-crest-novel-microsite.js [--publish] [--event-id=] [--subdomain=] */ const path = require('path'); require('dotenv').config({ path: path.join(__dirname, '..', '.env') }); const fs = require('fs'); const mongoose = require('mongoose'); const { connectDB } = require('../src/config/database'); const Event = require('../src/models/Event'); const Form = require('../src/models/Form'); const MicrositeV2Template = require('../src/models/MicrositeV2Template'); const EventStaticMicrosite = require('../src/models/EventStaticMicrosite'); const User = require('../src/models/User'); const { normalizeSubdomain, subdomainAvailability } = require('../src/utils/subdomainAvailability'); const { uploadTemplateAsset } = require('../src/utils/s3MicrositeV2'); const { publishStaticMicrosite } = require('../src/utils/publishStaticMicrosite'); const { resolveConfigAssetUrls } = require('../src/utils/micrositeConfigAssets'); const DEFAULT_EVENT_ID = '6a4cb75a7475c040cc6f2dd7'; const DEFAULT_SUBDOMAIN = 'novel2026-goa-8-9-aug'; const TEMPLATE_SLUG = 'crest-novel'; const SITE_JSON = path.join(__dirname, '..', '..', 'microsites', 'crest-novel', 'site.json'); const PUBLIC_WEB_BASE = (process.env.PUBLIC_WEB_BASE || 'https://web.eventbuzz.live').replace(/\/$/, ''); function argValue(name, fallback) { const hit = process.argv.find((a) => a.startsWith(`--${name}=`)); return hit ? hit.split('=').slice(1).join('=').trim() : fallback; } function deepClone(obj) { return JSON.parse(JSON.stringify(obj)); } function replaceEventIds(obj, oldId, newId) { if (obj == null) return obj; if (typeof obj === 'string') { return obj.split(oldId).join(newId); } if (Array.isArray(obj)) return obj.map((v) => replaceEventIds(v, oldId, newId)); if (typeof obj === 'object') { const out = {}; for (const [k, v] of Object.entries(obj)) out[k] = replaceEventIds(v, oldId, newId); return out; } return obj; } function siteJsonToConfig(site, eventId) { const theme = site.theme || {}; const headingColor = theme.heading?.color || theme.headingColor || '#015AAA'; return { meta: { title: site.meta?.title || 'NOVEL Lung Health Summit', eventId: String(eventId), }, theme: { accent: theme.accent || '#189A95', headingColor, }, sections: deepClone(site.components || {}), }; } async function findSeedUser() { const userIdArg = argValue('user-id', ''); if (userIdArg) { const u = await User.findById(userIdArg); if (u) return u; } const platform = await User.findOne({ platform_admin: 'yes', isActive: { $ne: false } }).sort({ createdAt: 1 }); if (platform) return platform; const superAdmin = await User.findOne({ role: 'super_admin' }).sort({ createdAt: 1 }); if (superAdmin) return superAdmin; throw new Error('No user found for updatedBy/publish. Pass --user-id='); } async function findRegistrationForm(eventId) { return Form.findOne({ event: eventId, type: 'registration', 'settings.isActive': { $ne: false } }) .sort({ createdAt: 1 }) .select('_id name') .lean(); } async function findFeedbackForm(eventId) { return Form.findOne({ event: eventId, type: 'feedback', 'settings.isActive': { $ne: false } }) .sort({ createdAt: 1 }) .select('_id name') .lean(); } function applyEventUrls(config, eventId, registrationFormId, feedbackFormId, eventDoc) { const regUrl = registrationFormId ? `${PUBLIC_WEB_BASE}/event/${eventId}/form/${registrationFormId}` : `${PUBLIC_WEB_BASE}/event/${eventId}/register`; const feedbackUrl = feedbackFormId ? `${PUBLIC_WEB_BASE}/event/${eventId}/form/${feedbackFormId}` : null; const sections = config.sections || {}; if (sections.hero?.cta) { sections.hero.cta.url = regUrl; sections.hero.cta.target = '_blank'; } if (sections.footer?.links && Array.isArray(sections.footer.links)) { for (const link of sections.footer.links) { if (link.text && /register/i.test(link.text)) link.url = regUrl; if (link.text && /feedback/i.test(link.text) && feedbackUrl) link.url = feedbackUrl; } } if (sections.feedback && feedbackUrl) { sections.feedback.url = feedbackUrl; } if (eventDoc?.banner && sections.hero?.image) { sections.hero.image.url = eventDoc.banner; } return config; } async function syncTemplateSiteConfigJs() { const siteConfigPath = path.join( __dirname, '..', 'microsite-v2-templates', TEMPLATE_SLUG, 'assets', 'js', 'site-config.js', ); if (!fs.existsSync(siteConfigPath)) { throw new Error(`Missing site-config.js at ${siteConfigPath}`); } await uploadTemplateAsset( TEMPLATE_SLUG, 'js/site-config.js', fs.readFileSync(siteConfigPath), 'application/javascript', ); console.log('Uploaded crest-novel site-config.js to template assets'); } async function main() { const eventId = argValue('event-id', DEFAULT_EVENT_ID); const subdomainRaw = argValue('subdomain', DEFAULT_SUBDOMAIN); const shouldPublish = process.argv.includes('--publish'); if (!mongoose.Types.ObjectId.isValid(eventId)) { throw new Error(`Invalid event id: ${eventId}`); } if (!fs.existsSync(SITE_JSON)) { throw new Error(`Missing site.json at ${SITE_JSON}`); } await connectDB(); const event = await Event.findById(eventId).lean(); if (!event) throw new Error(`Event not found: ${eventId}`); const template = await MicrositeV2Template.findOne({ slug: TEMPLATE_SLUG, isActive: true }); if (!template) { throw new Error(`Active template "${TEMPLATE_SLUG}" not found. Run: npm run seed:crest-novel-template`); } const user = await findSeedUser(); const subdomain = normalizeSubdomain(subdomainRaw); if (!subdomain) throw new Error(`Invalid subdomain: ${subdomainRaw}`); const existing = await EventStaticMicrosite.findOne({ event: eventId }); const avail = await subdomainAvailability(subdomain, eventId, existing?._id?.toString()); if (!avail.available) throw new Error(avail.message || 'Subdomain not available'); const site = JSON.parse(fs.readFileSync(SITE_JSON, 'utf8')); const oldEventId = site.meta?.eventId || '6a43e05fdc940a0d6e057f7b'; let config = siteJsonToConfig(site, eventId); config = replaceEventIds(config, oldEventId, eventId); const registrationForm = await findRegistrationForm(eventId); const feedbackForm = await findFeedbackForm(eventId); config = applyEventUrls(config, eventId, registrationForm?._id, feedbackForm?._id, event); config.sections = resolveConfigAssetUrls(config.sections || {}, template.assetsBaseUrl); await syncTemplateSiteConfigJs(); const payload = { event: event._id, organization: event.organization, subdomain, template: template._id, config, status: 'draft', updatedBy: user._id, }; let siteDoc; if (existing) { Object.assign(existing, payload); await existing.save(); siteDoc = existing; console.log(`Updated EventStaticMicrosite: ${existing._id}`); } else { siteDoc = await EventStaticMicrosite.create(payload); console.log(`Created EventStaticMicrosite: ${siteDoc._id}`); } console.log(`Event: ${event.name} (${eventId})`); console.log(`Template: ${template.name} (${template.slug})`); console.log(`Subdomain: ${subdomain}`); console.log(`Reg form: ${registrationForm?._id || '(none — using /register fallback)'}`); console.log(`Feedback: ${feedbackForm?._id || '(none)'}`); if (shouldPublish) { console.log('Publishing...'); const populated = await EventStaticMicrosite.findById(siteDoc._id).populate('template'); const result = await publishStaticMicrosite({ staticMicrosite: populated, template: populated.template, userId: user._id, }); Object.assign(populated, result); await populated.save(); console.log(`Published: ${result.publicUrl}`); } else { console.log('Saved as draft. Re-run with --publish to publish.'); } await mongoose.disconnect(); } main().catch((err) => { console.error(err); process.exit(1); });