195 lines
6.9 KiB
JavaScript
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,
|
|
};
|