653 lines
23 KiB
JavaScript
653 lines
23 KiB
JavaScript
const mongoose = require('mongoose');
|
|
const Event = require('../models/Event');
|
|
const EventStaticMicrosite = require('../models/EventStaticMicrosite');
|
|
const MicrositeV2Template = require('../models/MicrositeV2Template');
|
|
const { success, error } = require('../utils/response');
|
|
const { normalizeSubdomain, subdomainAvailability } = require('../utils/subdomainAvailability');
|
|
const {
|
|
uploadTemplateFile,
|
|
uploadTemplateAsset,
|
|
templateAssetsBaseUrl,
|
|
templateKey,
|
|
cloudfrontUrlForKey,
|
|
replaceSlugInS3Key,
|
|
copyTemplateFilesBetweenSlugs,
|
|
} = require('../utils/s3MicrositeV2');
|
|
const { parseTemplateSchema, buildDefaultConfig } = require('../utils/templateSchema');
|
|
const {
|
|
renderStaticMicrositeHtml,
|
|
publishStaticMicrosite,
|
|
loadTemplateSchema,
|
|
} = require('../utils/publishStaticMicrosite');
|
|
const { renderHandlebarsTemplate } = require('../utils/handlebarsRender');
|
|
const {
|
|
isLocalMicrositeV2TemplatesEnabled,
|
|
localPreviewAssetsBaseUrl,
|
|
resolveLocalAssetFile,
|
|
mimeForLocalAsset,
|
|
} = require('../utils/localMicrositeV2Templates');
|
|
const fs = require('fs');
|
|
|
|
function requirePlatformAdmin(user) {
|
|
return user.platform_admin === 'yes';
|
|
}
|
|
|
|
function toObjectId(id) {
|
|
if (id == null || id === '') return null;
|
|
const s = String(id);
|
|
if (!mongoose.Types.ObjectId.isValid(s)) return null;
|
|
return new mongoose.Types.ObjectId(s);
|
|
}
|
|
|
|
function orgIdOf(user) {
|
|
return toObjectId(user.organization?._id || user.organization);
|
|
}
|
|
|
|
function refIdString(ref) {
|
|
if (ref == null) return '';
|
|
if (typeof ref === 'object' && ref._id != null) return String(ref._id);
|
|
return String(ref);
|
|
}
|
|
|
|
function templateAccessibleToOrg(template, orgId) {
|
|
if (!template || template.visibility !== 'private') return true;
|
|
const oid = toObjectId(orgId);
|
|
if (!oid) return false;
|
|
const target = String(oid);
|
|
const allowed = template.allowedOrganizations || [];
|
|
return allowed.some((id) => refIdString(id) === target);
|
|
}
|
|
|
|
function normalizeOrgIds(raw) {
|
|
if (!Array.isArray(raw)) return [];
|
|
const ids = raw.map((id) => toObjectId(id)).filter(Boolean);
|
|
const unique = [...new Set(ids.map((oid) => oid.toString()))];
|
|
return unique.map((s) => new mongoose.Types.ObjectId(s));
|
|
}
|
|
|
|
function buildTemplateCatalogQuery(organizationId) {
|
|
const base = { isActive: true };
|
|
const publicClause = [{ visibility: { $exists: false } }, { visibility: 'public' }];
|
|
if (!organizationId) {
|
|
return { ...base, $or: publicClause };
|
|
}
|
|
return {
|
|
...base,
|
|
$or: [...publicClause, { visibility: 'private', allowedOrganizations: organizationId }],
|
|
};
|
|
}
|
|
|
|
async function resolveOrganizationForTemplateList(request) {
|
|
const user = request.auth.credentials.user;
|
|
const { eventId } = request.query || {};
|
|
|
|
if (eventId && mongoose.Types.ObjectId.isValid(eventId)) {
|
|
const { event, forbidden } = await loadEventForUser(eventId, user);
|
|
if (forbidden) return { error: 'FORBIDDEN' };
|
|
if (!event) return { error: 'NOT_FOUND' };
|
|
return { organizationId: toObjectId(event.organization) };
|
|
}
|
|
|
|
return { organizationId: orgIdOf(user) };
|
|
}
|
|
|
|
async function loadEventForUser(eventId, user) {
|
|
const event = await Event.findById(eventId);
|
|
if (!event) return { event: null, forbidden: false };
|
|
if (orgMismatch(event, user)) return { event: null, forbidden: true };
|
|
return { event, forbidden: false };
|
|
}
|
|
|
|
function extractBufferPart(part, fallbackName) {
|
|
if (!part) return null;
|
|
if (Buffer.isBuffer(part)) {
|
|
return { buffer: part, filename: fallbackName, mimeType: 'application/octet-stream' };
|
|
}
|
|
if (typeof part === 'object' && part.payload && Buffer.isBuffer(part.payload)) {
|
|
return {
|
|
buffer: part.payload,
|
|
filename: part.filename || fallbackName,
|
|
mimeType: part.headers?.['content-type'] || 'application/octet-stream',
|
|
};
|
|
}
|
|
if (typeof part === 'object' && part._data && Buffer.isBuffer(part._data)) {
|
|
return {
|
|
buffer: part._data,
|
|
filename: part.filename || fallbackName,
|
|
mimeType: part.headers?.['content-type'] || 'application/octet-stream',
|
|
};
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function orgMismatch(event, user) {
|
|
if (user.role === 'super_admin') return false;
|
|
return event.organization?.toString() !== (user.organization?._id || user.organization)?.toString();
|
|
}
|
|
|
|
// --- Template catalog (read) ---
|
|
|
|
async function assertTemplateReadable(request, template) {
|
|
const user = request.auth.credentials.user;
|
|
if (requirePlatformAdmin(user)) return null;
|
|
|
|
const { eventId } = request.query || {};
|
|
if (eventId && mongoose.Types.ObjectId.isValid(eventId)) {
|
|
const { event, forbidden } = await loadEventForUser(eventId, user);
|
|
if (!forbidden && event) {
|
|
const existingSite = await EventStaticMicrosite.findOne({
|
|
event: event._id,
|
|
template: template._id,
|
|
})
|
|
.select('_id')
|
|
.lean();
|
|
if (existingSite) return null;
|
|
}
|
|
|
|
const scoped = await resolveOrganizationForTemplateList(request);
|
|
if (scoped.error === 'FORBIDDEN') {
|
|
return { status: 403, message: 'Access denied' };
|
|
}
|
|
if (scoped.error === 'NOT_FOUND') {
|
|
return { status: 404, message: 'Event not found' };
|
|
}
|
|
if (templateAccessibleToOrg(template, scoped.organizationId)) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
if (templateAccessibleToOrg(template, orgIdOf(user))) {
|
|
return null;
|
|
}
|
|
|
|
return {
|
|
status: 403,
|
|
message: 'This template is not available for your organization',
|
|
};
|
|
}
|
|
|
|
const listTemplates = async (request, h) => {
|
|
const scoped = await resolveOrganizationForTemplateList(request);
|
|
if (scoped.error === 'FORBIDDEN') {
|
|
return h.response(error('FORBIDDEN', 'Access denied')).code(403);
|
|
}
|
|
if (scoped.error === 'NOT_FOUND') {
|
|
return h.response(error('NOT_FOUND', 'Event not found')).code(404);
|
|
}
|
|
|
|
const query = buildTemplateCatalogQuery(scoped.organizationId);
|
|
const rows = await MicrositeV2Template.find(query)
|
|
.sort({ createdAt: -1 })
|
|
.select('slug name description thumbnailUrl version isActive visibility createdAt')
|
|
.lean();
|
|
return h.response(success(rows));
|
|
};
|
|
|
|
const listAdminTemplates = async (request, h) => {
|
|
const user = request.auth.credentials.user;
|
|
if (!requirePlatformAdmin(user)) {
|
|
return h.response(error('FORBIDDEN', 'Only platform admins can list all templates')).code(403);
|
|
}
|
|
const rows = await MicrositeV2Template.find({})
|
|
.sort({ createdAt: -1 })
|
|
.populate('createdBy', 'name email')
|
|
.populate('allowedOrganizations', 'name email')
|
|
.lean();
|
|
return h.response(success(rows));
|
|
};
|
|
|
|
const getTemplate = async (request, h) => {
|
|
const template = await MicrositeV2Template.findById(request.params.id)
|
|
.populate('allowedOrganizations', 'name email')
|
|
.lean();
|
|
if (!template) return h.response(error('NOT_FOUND', 'Template not found')).code(404);
|
|
|
|
const access = await assertTemplateReadable(request, template);
|
|
if (access) {
|
|
return h.response(error('FORBIDDEN', access.message)).code(access.status);
|
|
}
|
|
|
|
let schema;
|
|
try {
|
|
schema = await loadTemplateSchema(template);
|
|
} catch {
|
|
schema = template.schema || null;
|
|
}
|
|
|
|
return h.response(success({ ...template, schema }));
|
|
};
|
|
|
|
// --- Template admin ---
|
|
|
|
const createTemplate = async (request, h) => {
|
|
const user = request.auth.credentials.user;
|
|
if (!requirePlatformAdmin(user)) {
|
|
return h.response(error('FORBIDDEN', 'Only platform admins can create templates')).code(403);
|
|
}
|
|
|
|
const { slug, name, description, version, visibility, allowedOrganizations } = request.payload;
|
|
const normalizedSlug = normalizeSubdomain(slug);
|
|
if (!normalizedSlug) {
|
|
return h.response(error('VALIDATION_ERROR', 'Valid slug is required')).code(400);
|
|
}
|
|
|
|
const vis = visibility === 'private' ? 'private' : 'public';
|
|
const orgIds = vis === 'private' ? normalizeOrgIds(allowedOrganizations) : [];
|
|
if (vis === 'private' && orgIds.length === 0) {
|
|
return h.response(error('VALIDATION_ERROR', 'Select at least one organization for a private template')).code(400);
|
|
}
|
|
|
|
const existing = await MicrositeV2Template.findOne({ slug: normalizedSlug }).lean();
|
|
if (existing) {
|
|
return h.response(error('CONFLICT', 'Template slug already exists')).code(409);
|
|
}
|
|
|
|
const template = await MicrositeV2Template.create({
|
|
slug: normalizedSlug,
|
|
name: name || normalizedSlug,
|
|
description: description || '',
|
|
version: version || 1,
|
|
visibility: vis,
|
|
allowedOrganizations: orgIds,
|
|
isActive: false,
|
|
createdBy: user._id,
|
|
});
|
|
|
|
return h.response(success(template, 'Template created', 201)).code(201);
|
|
};
|
|
|
|
const updateTemplate = async (request, h) => {
|
|
const user = request.auth.credentials.user;
|
|
if (!requirePlatformAdmin(user)) {
|
|
return h.response(error('FORBIDDEN', 'Only super_admin can update templates')).code(403);
|
|
}
|
|
|
|
const template = await MicrositeV2Template.findById(request.params.id);
|
|
if (!template) return h.response(error('NOT_FOUND', 'Template not found')).code(404);
|
|
|
|
const { name, description, isActive, version, visibility, allowedOrganizations } = request.payload;
|
|
if (name !== undefined) template.name = name;
|
|
if (description !== undefined) template.description = description;
|
|
if (isActive !== undefined) template.isActive = isActive;
|
|
if (version !== undefined) template.version = version;
|
|
if (visibility !== undefined) {
|
|
template.visibility = visibility === 'private' ? 'private' : 'public';
|
|
}
|
|
if (allowedOrganizations !== undefined) {
|
|
template.allowedOrganizations = normalizeOrgIds(allowedOrganizations);
|
|
}
|
|
if (template.visibility === 'private' && (!template.allowedOrganizations || template.allowedOrganizations.length === 0)) {
|
|
return h.response(error('VALIDATION_ERROR', 'Select at least one organization for a private template')).code(400);
|
|
}
|
|
if (template.visibility === 'public') {
|
|
template.allowedOrganizations = [];
|
|
}
|
|
|
|
await template.save();
|
|
const populated = await MicrositeV2Template.findById(template._id)
|
|
.populate('allowedOrganizations', 'name email')
|
|
.lean();
|
|
return h.response(success(populated));
|
|
};
|
|
|
|
const duplicateTemplate = async (request, h) => {
|
|
const user = request.auth.credentials.user;
|
|
if (!requirePlatformAdmin(user)) {
|
|
return h.response(error('FORBIDDEN', 'Only super_admin can duplicate templates')).code(403);
|
|
}
|
|
|
|
const source = await MicrositeV2Template.findById(request.params.id);
|
|
if (!source) return h.response(error('NOT_FOUND', 'Template not found')).code(404);
|
|
|
|
const payload = request.payload || {};
|
|
let newSlug = payload.slug ? normalizeSubdomain(payload.slug) : `${source.slug}-copy`;
|
|
if (!newSlug) {
|
|
return h.response(error('VALIDATION_ERROR', 'Valid slug is required')).code(400);
|
|
}
|
|
|
|
const baseSlug = newSlug;
|
|
let suffix = 1;
|
|
while (await MicrositeV2Template.findOne({ slug: newSlug }).lean()) {
|
|
suffix += 1;
|
|
newSlug = `${baseSlug}-${suffix}`;
|
|
}
|
|
|
|
const newName = (payload.name || `${source.name} (Copy)`).trim();
|
|
|
|
if (source.htmlS3Key || source.schemaS3Key || source.assetsS3Prefix) {
|
|
await copyTemplateFilesBetweenSlugs(source.slug, newSlug);
|
|
}
|
|
|
|
const doc = source.toObject();
|
|
delete doc._id;
|
|
delete doc.createdAt;
|
|
delete doc.updatedAt;
|
|
|
|
doc.slug = newSlug;
|
|
doc.name = newName;
|
|
doc.createdBy = user._id;
|
|
doc.htmlS3Key = replaceSlugInS3Key(doc.htmlS3Key, source.slug, newSlug);
|
|
doc.schemaS3Key = replaceSlugInS3Key(doc.schemaS3Key, source.slug, newSlug);
|
|
if (doc.assetsS3Prefix) {
|
|
doc.assetsS3Prefix = replaceSlugInS3Key(doc.assetsS3Prefix, source.slug, newSlug);
|
|
}
|
|
doc.assetsBaseUrl = templateAssetsBaseUrl(newSlug);
|
|
if (source.thumbnailUrl) {
|
|
const extMatch = String(source.thumbnailUrl).match(/thumbnail\.(\w+)/i);
|
|
const ext = extMatch ? extMatch[1] : 'jpg';
|
|
doc.thumbnailUrl = cloudfrontUrlForKey(templateKey(newSlug, `thumbnail.${ext}`));
|
|
}
|
|
|
|
const created = await MicrositeV2Template.create(doc);
|
|
const populated = await MicrositeV2Template.findById(created._id)
|
|
.populate('allowedOrganizations', 'name email')
|
|
.lean();
|
|
|
|
return h.response(success(populated, 'Template duplicated', 201)).code(201);
|
|
};
|
|
|
|
const removeTemplate = async (request, h) => {
|
|
const user = request.auth.credentials.user;
|
|
if (!requirePlatformAdmin(user)) {
|
|
return h.response(error('FORBIDDEN', 'Only super_admin can delete templates')).code(403);
|
|
}
|
|
|
|
const template = await MicrositeV2Template.findById(request.params.id);
|
|
if (!template) return h.response(error('NOT_FOUND', 'Template not found')).code(404);
|
|
|
|
const inUse = await EventStaticMicrosite.countDocuments({ template: template._id });
|
|
if (inUse > 0) {
|
|
return h.response(error('CONFLICT', 'Template is in use by event microsites')).code(409);
|
|
}
|
|
|
|
await template.deleteOne();
|
|
return h.response(success(null, 'Template deleted'));
|
|
};
|
|
|
|
const uploadTemplateFiles = async (request, h) => {
|
|
const user = request.auth.credentials.user;
|
|
if (!requirePlatformAdmin(user)) {
|
|
return h.response(error('FORBIDDEN', 'Only super_admin can upload templates')).code(403);
|
|
}
|
|
|
|
const template = await MicrositeV2Template.findById(request.params.id);
|
|
if (!template) return h.response(error('NOT_FOUND', 'Template not found')).code(404);
|
|
|
|
const payload = request.payload || {};
|
|
const slug = template.slug;
|
|
|
|
const htmlPart = extractBufferPart(payload.templateHtml, 'template.html');
|
|
const jsonPart = extractBufferPart(payload.templateJson, 'template.json');
|
|
const thumbPart = extractBufferPart(payload.thumbnail, 'thumbnail.jpg');
|
|
const zipPart = extractBufferPart(payload.assetsZip, 'assets.zip');
|
|
|
|
if (htmlPart) {
|
|
const up = await uploadTemplateFile(slug, 'template.html', htmlPart.buffer, 'text/html; charset=utf-8');
|
|
template.htmlS3Key = up.s3Key;
|
|
}
|
|
if (jsonPart) {
|
|
const schema = parseTemplateSchema(jsonPart.buffer);
|
|
const up = await uploadTemplateFile(slug, 'template.json', jsonPart.buffer, 'application/json');
|
|
template.schemaS3Key = up.s3Key;
|
|
template.schema = schema;
|
|
|
|
if (htmlPart) {
|
|
try {
|
|
const htmlStr = htmlPart.buffer.toString('utf8');
|
|
const defaultConfig = buildDefaultConfig(schema);
|
|
renderHandlebarsTemplate(htmlStr, {
|
|
meta: defaultConfig.meta,
|
|
theme: defaultConfig.theme,
|
|
sections: defaultConfig.sections,
|
|
assetsBaseUrl: template.assetsBaseUrl || templateAssetsBaseUrl(slug),
|
|
eventId: '',
|
|
subdomain: '',
|
|
templateSlug: slug,
|
|
});
|
|
} catch (e) {
|
|
return h.response(error('VALIDATION_ERROR', `Handlebars compile failed: ${e.message}`)).code(400);
|
|
}
|
|
}
|
|
}
|
|
if (thumbPart) {
|
|
const ext = (thumbPart.filename || '').split('.').pop() || 'jpg';
|
|
const mime = thumbPart.mimeType.startsWith('image/') ? thumbPart.mimeType : `image/${ext}`;
|
|
const up = await uploadTemplateFile(slug, `thumbnail.${ext}`, thumbPart.buffer, mime);
|
|
template.thumbnailUrl = up.url;
|
|
}
|
|
if (zipPart) {
|
|
// Lazy require — only needed for super-admin template zip upload.
|
|
// eslint-disable-next-line global-require
|
|
const AdmZip = require('adm-zip');
|
|
const zip = new AdmZip(zipPart.buffer);
|
|
const entries = zip.getEntries();
|
|
for (const entry of entries) {
|
|
if (entry.isDirectory) continue;
|
|
const rel = entry.entryName.replace(/^\/+/, '');
|
|
const data = entry.getData();
|
|
const mime = rel.endsWith('.css')
|
|
? 'text/css'
|
|
: rel.endsWith('.js')
|
|
? 'application/javascript'
|
|
: 'application/octet-stream';
|
|
await uploadTemplateAsset(slug, rel, data, mime);
|
|
}
|
|
template.assetsS3Prefix = `templates/${slug}/assets`;
|
|
}
|
|
|
|
template.assetsBaseUrl = templateAssetsBaseUrl(slug);
|
|
if (template.htmlS3Key && template.schemaS3Key) {
|
|
template.isActive = true;
|
|
}
|
|
|
|
await template.save();
|
|
return h.response(success(template, 'Template files uploaded'));
|
|
};
|
|
|
|
const previewTemplateHtml = async (request, h) => {
|
|
const user = request.auth.credentials.user;
|
|
if (!requirePlatformAdmin(user)) {
|
|
return h.response(error('FORBIDDEN', 'Only super_admin can preview templates')).code(403);
|
|
}
|
|
|
|
const template = await MicrositeV2Template.findById(request.params.id).lean();
|
|
if (!template) return h.response(error('NOT_FOUND', 'Template not found')).code(404);
|
|
|
|
try {
|
|
const schema = await loadTemplateSchema(template);
|
|
const defaultConfig = buildDefaultConfig(schema);
|
|
const assetsBaseUrl = localPreviewAssetsBaseUrl(template.slug, request) || undefined;
|
|
const html = await renderStaticMicrositeHtml({
|
|
template,
|
|
config: defaultConfig,
|
|
assetsBaseUrl,
|
|
});
|
|
return h.response(html).type('text/html');
|
|
} catch (e) {
|
|
return h.response(error('RENDER_ERROR', e.message)).code(400);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Serve template CSS/JS/images from local disk when MICROSITE_V2_READ_FROM_LOCAL is on.
|
|
* Public (no JWT) so blob-URL previews can load assets.
|
|
*/
|
|
const serveLocalTemplateAsset = async (request, h) => {
|
|
if (!isLocalMicrositeV2TemplatesEnabled()) {
|
|
return h.response(error('NOT_FOUND', 'Local microsite assets are disabled')).code(404);
|
|
}
|
|
|
|
const slug = String(request.params.slug || '').trim().toLowerCase();
|
|
const assetPath = Array.isArray(request.params.assetPath)
|
|
? request.params.assetPath.join('/')
|
|
: String(request.params.assetPath || '');
|
|
|
|
const fullPath = resolveLocalAssetFile(slug, assetPath);
|
|
if (!fullPath) {
|
|
return h.response(error('NOT_FOUND', 'Asset not found')).code(404);
|
|
}
|
|
|
|
try {
|
|
const buf = fs.readFileSync(fullPath);
|
|
return h
|
|
.response(buf)
|
|
.type(mimeForLocalAsset(fullPath))
|
|
.header('Cache-Control', 'no-store, max-age=0')
|
|
.header('Access-Control-Allow-Origin', '*');
|
|
} catch (e) {
|
|
return h.response(error('SERVER_ERROR', e.message || 'Failed to read asset')).code(500);
|
|
}
|
|
};
|
|
|
|
// --- Event static microsite ---
|
|
|
|
const getForEvent = async (request, h) => {
|
|
const { event, forbidden } = await loadEventForUser(request.params.eventId, request.auth.credentials.user);
|
|
if (forbidden) return h.response(error('FORBIDDEN', 'Access denied')).code(403);
|
|
if (!event) return h.response(error('NOT_FOUND', 'Event not found')).code(404);
|
|
|
|
const site = await EventStaticMicrosite.findOne({ event: event._id })
|
|
.populate('template', 'slug name description thumbnailUrl version assetsBaseUrl')
|
|
.lean();
|
|
|
|
if (!site) return h.response(error('NOT_FOUND', 'Static microsite not configured')).code(404);
|
|
return h.response(success(site));
|
|
};
|
|
|
|
const upsertForEvent = async (request, h) => {
|
|
const user = request.auth.credentials.user;
|
|
const { event, forbidden } = await loadEventForUser(request.params.eventId, user);
|
|
if (forbidden) return h.response(error('FORBIDDEN', 'Access denied')).code(403);
|
|
if (!event) return h.response(error('NOT_FOUND', 'Event not found')).code(404);
|
|
|
|
const { subdomain, templateId, config } = request.payload;
|
|
const normalized = normalizeSubdomain(subdomain);
|
|
if (!normalized) {
|
|
return h.response(error('VALIDATION_ERROR', 'Valid subdomain is required')).code(400);
|
|
}
|
|
if (!templateId || !mongoose.Types.ObjectId.isValid(templateId)) {
|
|
return h.response(error('VALIDATION_ERROR', 'Valid template id is required')).code(400);
|
|
}
|
|
|
|
const template = await MicrositeV2Template.findOne({ _id: templateId, isActive: true });
|
|
if (!template) return h.response(error('NOT_FOUND', 'Template not found or inactive')).code(404);
|
|
if (!templateAccessibleToOrg(template, event.organization)) {
|
|
return h.response(error('FORBIDDEN', 'This template is not available for your organization')).code(403);
|
|
}
|
|
|
|
let site = await EventStaticMicrosite.findOne({ event: event._id });
|
|
const avail = await subdomainAvailability(normalized, event._id.toString(), site?._id?.toString());
|
|
if (!avail.available) {
|
|
return h.response(error('VALIDATION_ERROR', avail.message)).code(400);
|
|
}
|
|
|
|
if (!site) {
|
|
site = new EventStaticMicrosite({
|
|
event: event._id,
|
|
organization: event.organization,
|
|
subdomain: normalized,
|
|
template: template._id,
|
|
config: config || {},
|
|
updatedBy: user._id,
|
|
});
|
|
} else {
|
|
site.subdomain = normalized;
|
|
site.template = template._id;
|
|
if (config !== undefined) site.config = config;
|
|
site.updatedBy = user._id;
|
|
}
|
|
|
|
const wasNew = site.isNew;
|
|
await site.save();
|
|
const populated = await EventStaticMicrosite.findById(site._id)
|
|
.populate('template', 'slug name description thumbnailUrl version assetsBaseUrl')
|
|
.lean();
|
|
|
|
return h.response(success(populated, wasNew ? 'Created' : 'Updated'));
|
|
};
|
|
|
|
const checkSubdomain = async (request, h) => {
|
|
const { event, forbidden } = await loadEventForUser(request.params.eventId, request.auth.credentials.user);
|
|
if (forbidden) return h.response(error('FORBIDDEN', 'Access denied')).code(403);
|
|
if (!event) return h.response(error('NOT_FOUND', 'Event not found')).code(404);
|
|
|
|
const normalized = normalizeSubdomain(request.query.subdomain);
|
|
const site = await EventStaticMicrosite.findOne({ event: event._id }).select('_id').lean();
|
|
const avail = await subdomainAvailability(
|
|
normalized,
|
|
event._id.toString(),
|
|
site?._id?.toString(),
|
|
);
|
|
return h.response(success(avail));
|
|
};
|
|
|
|
const publishForEvent = async (request, h) => {
|
|
const user = request.auth.credentials.user;
|
|
const { event, forbidden } = await loadEventForUser(request.params.eventId, user);
|
|
if (forbidden) return h.response(error('FORBIDDEN', 'Access denied')).code(403);
|
|
if (!event) return h.response(error('NOT_FOUND', 'Event not found')).code(404);
|
|
|
|
const site = await EventStaticMicrosite.findOne({ event: event._id }).populate('template');
|
|
if (!site) return h.response(error('NOT_FOUND', 'Static microsite not configured')).code(404);
|
|
|
|
try {
|
|
const result = await publishStaticMicrosite({
|
|
staticMicrosite: site,
|
|
template: site.template,
|
|
userId: user._id,
|
|
});
|
|
Object.assign(site, result);
|
|
await site.save();
|
|
|
|
const populated = await EventStaticMicrosite.findById(site._id)
|
|
.populate('template', 'slug name description thumbnailUrl version assetsBaseUrl')
|
|
.lean();
|
|
|
|
return h.response(success({ ...populated, publicUrl: result.publicUrl }, 'Published'));
|
|
} catch (e) {
|
|
return h.response(error('PUBLISH_ERROR', e.message)).code(400);
|
|
}
|
|
};
|
|
|
|
const previewForEvent = async (request, h) => {
|
|
const { event, forbidden } = await loadEventForUser(request.params.eventId, request.auth.credentials.user);
|
|
if (forbidden) return h.response(error('FORBIDDEN', 'Access denied')).code(403);
|
|
if (!event) return h.response(error('NOT_FOUND', 'Event not found')).code(404);
|
|
|
|
const site = await EventStaticMicrosite.findOne({ event: event._id }).populate('template');
|
|
if (!site) return h.response(error('NOT_FOUND', 'Static microsite not configured')).code(404);
|
|
|
|
try {
|
|
const assetsBaseUrl =
|
|
localPreviewAssetsBaseUrl(site.template?.slug, request) || undefined;
|
|
const html = await renderStaticMicrositeHtml({
|
|
template: site.template,
|
|
config: site.config,
|
|
eventId: event._id,
|
|
subdomain: site.subdomain,
|
|
assetsBaseUrl,
|
|
});
|
|
return h.response(html).type('text/html');
|
|
} catch (e) {
|
|
return h.response(error('RENDER_ERROR', e.message)).code(400);
|
|
}
|
|
};
|
|
|
|
module.exports = {
|
|
listTemplates,
|
|
listAdminTemplates,
|
|
getTemplate,
|
|
createTemplate,
|
|
updateTemplate,
|
|
duplicateTemplate,
|
|
removeTemplate,
|
|
uploadTemplateFiles,
|
|
previewTemplateHtml,
|
|
serveLocalTemplateAsset,
|
|
getForEvent,
|
|
upsertForEvent,
|
|
checkSubdomain,
|
|
publishForEvent,
|
|
previewForEvent,
|
|
};
|