#!/usr/bin/env node /** * Seed the CREST NOVEL microsite-v2 template. * * Usage: node scripts/seed-crest-novel-template.js [--user-id=] [--db-only] */ require('dotenv').config(); const fs = require('fs'); const path = require('path'); const mongoose = require('mongoose'); const { connectDB } = require('../src/config/database'); const MicrositeV2Template = require('../src/models/MicrositeV2Template'); const User = require('../src/models/User'); const { uploadTemplateFile, uploadTemplateAsset, templateAssetsBaseUrl, } = require('../src/utils/s3MicrositeV2'); const { parseTemplateSchema } = require('../src/utils/templateSchema'); const SLUG = 'crest-novel'; const TEMPLATE_DIR = path.join(__dirname, '..', 'microsite-v2-templates', SLUG); const TEMPLATE_ASSETS = path.join(TEMPLATE_DIR, 'assets'); const SOURCE_ASSETS = path.join(__dirname, '..', '..', 'microsites', 'crest-novel', 'assets'); async function findSeedUser(userIdArg) { if (userIdArg) { const u = await User.findById(userIdArg); if (u) return u; } const superAdmin = await User.findOne({ role: 'super_admin' }).sort({ createdAt: 1 }); if (superAdmin) return superAdmin; throw new Error('No super_admin user found. Pass --user-id='); } function walkDir(dir, base = dir) { const files = []; if (!fs.existsSync(dir)) return files; for (const name of fs.readdirSync(dir)) { const full = path.join(dir, name); const stat = fs.statSync(full); if (stat.isDirectory()) { files.push(...walkDir(full, base)); } else { files.push({ full, rel: path.relative(base, full).replace(/\\/g, '/') }); } } return files; } function mimeFor(filePath) { if (filePath.endsWith('.css')) return 'text/css'; if (filePath.endsWith('.js')) return 'application/javascript'; if (filePath.endsWith('.woff')) return 'font/woff'; if (filePath.endsWith('.woff2')) return 'font/woff2'; if (filePath.endsWith('.ttf')) return 'font/ttf'; if (filePath.endsWith('.eot')) return 'application/vnd.ms-fontobject'; if (filePath.endsWith('.png')) return 'image/png'; if (filePath.endsWith('.jpg') || filePath.endsWith('.jpeg')) return 'image/jpeg'; if (filePath.endsWith('.svg')) return 'image/svg+xml'; return 'application/octet-stream'; } /** * Theme packs often bake cache-busters into filenames (LineIcons.woff?y2l643, * fontawesome-webfont.eot?v=4.7.0). Strip those so S3 keys match clean @font-face URLs. */ function normalizeAssetRelPath(rel) { const base = String(rel).replace(/\\/g, '/'); return base.replace(/(\.(?:woff2?|ttf|eot|svg|otf))\?[^/]*$/i, '$1'); } async function main() { const dbOnly = process.argv.includes('--db-only'); const userIdArg = (process.argv.find((a) => a.startsWith('--user-id=')) || '').split('=')[1]; await connectDB(); const user = await findSeedUser(userIdArg); console.log(`Using user: ${user.email || user._id}`); const htmlPath = path.join(TEMPLATE_DIR, 'template.html'); const jsonPath = path.join(TEMPLATE_DIR, 'template.json'); if (!fs.existsSync(htmlPath) || !fs.existsSync(jsonPath)) { throw new Error(`Missing template files in ${TEMPLATE_DIR}`); } const htmlBuf = fs.readFileSync(htmlPath); const jsonBuf = fs.readFileSync(jsonPath); const schema = parseTemplateSchema(jsonBuf); let htmlS3Key; let schemaS3Key; let thumbnailUrl; let assetsBaseUrl = templateAssetsBaseUrl(SLUG); if (!dbOnly && process.env.AWS_BUCKET) { console.log('Uploading template.html...'); const htmlUp = await uploadTemplateFile(SLUG, 'template.html', htmlBuf, 'text/html; charset=utf-8'); htmlS3Key = htmlUp.s3Key; console.log('Uploading template.json...'); const jsonUp = await uploadTemplateFile(SLUG, 'template.json', jsonBuf, 'application/json'); schemaS3Key = jsonUp.s3Key; const thumbCandidates = [ path.join(TEMPLATE_DIR, 'thumbnail.jpg'), path.join(TEMPLATE_DIR, 'thumbnail.png'), path.join(SOURCE_ASSETS, 'img', 'hero.jpg'), path.join(SOURCE_ASSETS, 'img', 'novel-logo.png'), ]; for (const tp of thumbCandidates) { if (fs.existsSync(tp)) { const ext = path.extname(tp).slice(1) || 'jpg'; const thumbUp = await uploadTemplateFile( SLUG, `thumbnail.${ext}`, fs.readFileSync(tp), `image/${ext === 'jpg' ? 'jpeg' : ext}`, ); thumbnailUrl = thumbUp.url; break; } } if (!thumbnailUrl) { const placeholder = Buffer.from( 'CREST NOVEL', ); const thumbUp = await uploadTemplateFile(SLUG, 'thumbnail.svg', placeholder, 'image/svg+xml'); thumbnailUrl = thumbUp.url; } const assetRoots = [TEMPLATE_ASSETS, SOURCE_ASSETS]; for (const root of assetRoots) { if (!fs.existsSync(root)) continue; console.log(`Uploading assets from ${root}...`); for (const { full, rel } of walkDir(root)) { const normalizedRel = normalizeAssetRelPath(rel); if (normalizedRel !== rel && fs.existsSync(path.join(root, normalizedRel))) { continue; } await uploadTemplateAsset(SLUG, normalizedRel, fs.readFileSync(full), mimeFor(normalizedRel)); } } assetsBaseUrl = templateAssetsBaseUrl(SLUG); console.log('S3 upload complete.'); } else { console.log('Skipping S3 upload (--db-only or AWS_BUCKET not set).'); } const existing = await MicrositeV2Template.findOne({ slug: SLUG }); const doc = { slug: SLUG, name: 'CREST NOVEL', description: 'CREST / NOVEL lung health summit microsite — marketing popup, dual about sections, expert videos, tabbed speakers, schedule, sponsors, contact form, and feedback FAB.', thumbnailUrl: thumbnailUrl || existing?.thumbnailUrl || '', htmlS3Key: htmlS3Key || existing?.htmlS3Key || '', schemaS3Key: schemaS3Key || existing?.schemaS3Key || '', schema, assetsBaseUrl, assetsS3Prefix: `templates/${SLUG}/assets`, isActive: Boolean((htmlS3Key || existing?.htmlS3Key) && (schemaS3Key || existing?.schemaS3Key)), version: schema.version || 1, createdBy: user._id, }; if (existing) { const preservedOrgs = existing.allowedOrganizations; Object.assign(existing, doc); if (preservedOrgs && preservedOrgs.length) { existing.allowedOrganizations = preservedOrgs; } await existing.save(); console.log(`Updated MicrositeV2Template: ${existing._id}`); } else { const created = await MicrositeV2Template.create(doc); console.log(`Created MicrositeV2Template: ${created._id}`); } await mongoose.disconnect(); console.log('Done.'); } main().catch((err) => { console.error(err); process.exit(1); });