EventBuzz_Backend/scripts/lib/seedOneMicrositeV2Template.js

195 lines
6.9 KiB
JavaScript

const fs = require('fs');
const path = require('path');
const {
uploadTemplateFile,
uploadTemplateAsset,
templateAssetsBaseUrl,
} = require('../../src/utils/s3MicrositeV2');
const { parseTemplateSchema } = require('../../src/utils/templateSchema');
require('../../src/models/Organization');
require('../../src/models/User');
const MicrositeV2Template = require('../../src/models/MicrositeV2Template');
const { getGlobalMicrositeV2Template } = require('../../src/config/globalMicrositeV2Templates');
const BACKEND_ROOT = path.join(__dirname, '..', '..');
const TEMPLATES_ROOT = path.join(BACKEND_ROOT, 'microsite-v2-templates');
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';
if (filePath.endsWith('.gif')) return 'image/gif';
if (filePath.endsWith('.webp')) return 'image/webp';
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, '/');
// Strip query suffix from any path segment (literal ? in filename or ?query).
return base.replace(/(\.(?:woff2?|ttf|eot|svg|otf))\?[^/]*$/i, '$1');
}
function svgPlaceholder(label, color) {
const safeLabel = String(label || 'Template').slice(0, 40);
const fill = String(color || '#5c6ac4');
return Buffer.from(
`<svg xmlns="http://www.w3.org/2000/svg" width="400" height="240"><rect fill="${fill}" width="400" height="240"/><text x="50%" y="50%" fill="#fff" text-anchor="middle" dy=".3em" font-family="sans-serif" font-size="22">${safeLabel}</text></svg>`,
);
}
/**
* Upload template files to S3 and upsert MicrositeV2Template row.
* @param {{ slug: string, user: import('mongoose').Document, dbOnly?: boolean }} opts
*/
async function seedOneMicrositeV2Template({ slug, user, dbOnly = false }) {
const def = getGlobalMicrositeV2Template(slug);
if (!def) {
throw new Error(`Unknown global microsite template slug: ${slug}`);
}
const templateDir = path.join(TEMPLATES_ROOT, def.slug);
const templateAssets = path.join(templateDir, 'assets');
const htmlPath = path.join(templateDir, 'template.html');
const jsonPath = path.join(templateDir, 'template.json');
if (!fs.existsSync(htmlPath) || !fs.existsSync(jsonPath)) {
throw new Error(`Missing template.html or template.json in ${templateDir}`);
}
const htmlBuf = fs.readFileSync(htmlPath);
const jsonBuf = fs.readFileSync(jsonPath);
const schema = parseTemplateSchema(jsonBuf);
let htmlS3Key;
let schemaS3Key;
let thumbnailUrl;
let assetsBaseUrl = templateAssetsBaseUrl(def.slug);
if (!dbOnly && process.env.AWS_BUCKET) {
console.log(`[${def.slug}] Uploading template.html...`);
const htmlUp = await uploadTemplateFile(def.slug, 'template.html', htmlBuf, 'text/html; charset=utf-8');
htmlS3Key = htmlUp.s3Key;
console.log(`[${def.slug}] Uploading template.json...`);
const jsonUp = await uploadTemplateFile(def.slug, 'template.json', jsonBuf, 'application/json');
schemaS3Key = jsonUp.s3Key;
for (const rel of def.thumbnailCandidates || []) {
const tp = path.join(templateDir, rel);
if (!fs.existsSync(tp)) continue;
const ext = path.extname(tp).slice(1) || 'jpg';
const thumbUp = await uploadTemplateFile(
def.slug,
`thumbnail.${ext}`,
fs.readFileSync(tp),
`image/${ext === 'jpg' ? 'jpeg' : ext}`,
);
thumbnailUrl = thumbUp.url;
break;
}
if (!thumbnailUrl) {
const thumbUp = await uploadTemplateFile(
def.slug,
'thumbnail.svg',
svgPlaceholder(def.placeholderSvgLabel || def.name, def.placeholderSvgColor),
'image/svg+xml',
);
thumbnailUrl = thumbUp.url;
}
const assetRoots = [];
if (fs.existsSync(templateAssets)) assetRoots.push(templateAssets);
for (const externalRel of def.externalAssetRoots || []) {
const externalRoot = path.isAbsolute(externalRel)
? externalRel
: path.join(BACKEND_ROOT, '..', externalRel);
if (fs.existsSync(externalRoot)) assetRoots.push(externalRoot);
}
for (const root of assetRoots) {
console.log(`[${def.slug}] 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(def.slug, normalizedRel, fs.readFileSync(full), mimeFor(normalizedRel));
}
}
assetsBaseUrl = templateAssetsBaseUrl(def.slug);
console.log(`[${def.slug}] S3 upload complete.`);
} else {
console.log(`[${def.slug}] Skipping S3 upload (--db-only or AWS_BUCKET not set).`);
}
const existing = await MicrositeV2Template.findOne({ slug: def.slug }).select('_id htmlS3Key schemaS3Key thumbnailUrl').lean();
const doc = {
slug: def.slug,
name: def.name,
description: def.description,
thumbnailUrl: thumbnailUrl || existing?.thumbnailUrl || '',
htmlS3Key: htmlS3Key || existing?.htmlS3Key || '',
schemaS3Key: schemaS3Key || existing?.schemaS3Key || '',
schema,
assetsBaseUrl,
assetsS3Prefix: `templates/${def.slug}/assets`,
isActive: Boolean((htmlS3Key || existing?.htmlS3Key) && (schemaS3Key || existing?.schemaS3Key)),
version: schema.version || 1,
createdBy: user._id,
};
const now = new Date();
const setFields = {
...doc,
updatedAt: now,
};
await MicrositeV2Template.collection.updateOne(
{ slug: def.slug },
{
$set: setFields,
$setOnInsert: {
allowedOrganizations: [],
visibility: 'public',
createdAt: now,
},
},
{ upsert: true },
);
const saved = await MicrositeV2Template.findOne({ slug: def.slug }).select('_id slug').lean();
console.log(
`[${def.slug}] ${existing ? 'Updated' : 'Created'} MicrositeV2Template: ${saved?._id}`,
);
return saved;
}
module.exports = {
seedOneMicrositeV2Template,
};