Publish Docker Image / build-and-push (./build/Dockerfile, git.lzstealth.com/lzstealth/pulse-signage-web, web) (push) Successful in 1m15s
Publish Docker Image / build-and-push (./build/Dockerfile.player, git.lzstealth.com/lzstealth/pulse-signage-player, player) (push) Successful in 32s
246 lines
9.1 KiB
JavaScript
246 lines
9.1 KiB
JavaScript
// Settings route registration for managed fonts.
|
|
|
|
const fs = require('fs');
|
|
const { hasAnyPermission, PERMISSION_DENIED_MESSAGE } = require('#src/rbac');
|
|
const fontLibrary = require('#src/web/lib/media/font-library');
|
|
const { buildPagination } = require('../../lib/pagination');
|
|
const { createSearchMatcher, getSearchQuery, getSortDirectionQuery, getSortQuery, parsePageNumber, sortRows } = require('../../lib/list-query');
|
|
|
|
const LIST_PAGE_SIZE = 25;
|
|
|
|
function normalizeFontFamilyName(value) {
|
|
return String(value || '').replace(/\s+/g, ' ').trim().toLowerCase();
|
|
}
|
|
|
|
async function fetchUsedFontFamilies(pool, fonts) {
|
|
const usedFamilies = new Set();
|
|
|
|
for (let i = 0; i < (Array.isArray(fonts) ? fonts.length : 0); i += 1) {
|
|
const family = normalizeFontFamilyName(fonts[i] && (fonts[i].family || fonts[i].name));
|
|
if (!family) {
|
|
continue;
|
|
}
|
|
|
|
const [rows] = await pool.query(
|
|
'SELECT COUNT(*) AS match_count FROM c_slides WHERE LOCATE(LOWER(?), LOWER(COALESCE(content_json, ""))) > 0',
|
|
[family]
|
|
);
|
|
if (Number(rows && rows[0] && rows[0].match_count) > 0) {
|
|
usedFamilies.add(family);
|
|
}
|
|
}
|
|
|
|
return usedFamilies;
|
|
}
|
|
|
|
async function isFontUsed(pool, family) {
|
|
const normalizedFamily = normalizeFontFamilyName(family);
|
|
if (!normalizedFamily) {
|
|
return false;
|
|
}
|
|
|
|
const [rows] = await pool.query(
|
|
'SELECT COUNT(*) AS match_count FROM c_slides WHERE LOCATE(LOWER(?), LOWER(COALESCE(content_json, ""))) > 0',
|
|
[normalizedFamily]
|
|
);
|
|
|
|
return Number(rows && rows[0] && rows[0].match_count) > 0;
|
|
}
|
|
|
|
async function buildFontsPageData(pool, uploadDir, req, common) {
|
|
const library = fontLibrary.loadFontLibrary(uploadDir);
|
|
const usedFamilies = await fetchUsedFontFamilies(pool, library.fonts);
|
|
const search = common && typeof common.getSearchQuery === 'function' ? common.getSearchQuery(req) : getSearchQuery(req);
|
|
const sort = common && typeof common.getSortQuery === 'function' ? common.getSortQuery(req) : getSortQuery(req);
|
|
const direction = common && typeof common.getSortDirectionQuery === 'function' ? common.getSortDirectionQuery(req) : getSortDirectionQuery(req);
|
|
const searchableFonts = sortRows(library.fonts.map(function (font) {
|
|
const family = normalizeFontFamilyName(font.family || font.name);
|
|
return Object.assign({}, font, {
|
|
inUse: usedFamilies.has(family)
|
|
});
|
|
}), function (font) {
|
|
if (sort === 'file') {
|
|
return String(font && font.fileName || '').trim();
|
|
}
|
|
if (sort === 'format') {
|
|
return String(font && font.format || '').trim();
|
|
}
|
|
if (sort === 'status') {
|
|
return font && font.enabled ? 'Enabled' : 'Disabled';
|
|
}
|
|
|
|
return String(font && font.family || font && font.name || '').trim();
|
|
}, direction).filter(createSearchMatcher(search, [
|
|
'family',
|
|
'name',
|
|
'fileName',
|
|
'format',
|
|
function (font) {
|
|
return font && font.enabled ? 'enabled' : 'disabled';
|
|
}
|
|
]));
|
|
const pagination = buildPagination(searchableFonts.length, parsePageNumber(req && req.query && req.query.page), 'page', { search: search, sort: sort, direction: direction }, LIST_PAGE_SIZE, 'fonts', 'Font pages');
|
|
const startIndex = (pagination.currentPage - 1) * LIST_PAGE_SIZE;
|
|
|
|
return {
|
|
fonts: searchableFonts.slice(startIndex, startIndex + LIST_PAGE_SIZE),
|
|
pagination: pagination,
|
|
stylesheetHref: library.stylesheetHref
|
|
};
|
|
}
|
|
|
|
function redirectToLogin(res, setAuthMessageCookie) {
|
|
if (typeof setAuthMessageCookie === 'function') {
|
|
setAuthMessageCookie(res, 'Please sign in to continue.');
|
|
return res.redirect('/login');
|
|
}
|
|
|
|
return res.redirect('/login');
|
|
}
|
|
|
|
function requireFontsAccess(setAuthMessageCookie) {
|
|
return function (req, res, next) {
|
|
if (!req.currentUser) {
|
|
return redirectToLogin(res, setAuthMessageCookie);
|
|
}
|
|
|
|
if (hasAnyPermission(req.currentUser, ['fonts.read', 'fonts.create', 'fonts.delete'])) {
|
|
return next();
|
|
}
|
|
|
|
const error = new Error(PERMISSION_DENIED_MESSAGE);
|
|
error.statusCode = 403;
|
|
error.expose = true;
|
|
next(error);
|
|
};
|
|
}
|
|
|
|
module.exports = function registerFontRoutes(app, deps) {
|
|
const pool = deps.pool;
|
|
const common = deps.common || {};
|
|
const pages = deps.pages;
|
|
const upload = deps.upload;
|
|
const uploadDir = deps.uploadDir;
|
|
const backgroundTaskQueue = deps.backgroundTaskQueue;
|
|
const setAuthMessageCookie = deps.setAuthMessageCookie;
|
|
|
|
const FONT_FAMILY_MAX_LENGTH = 255;
|
|
|
|
if (!pool || !pages || !upload || !uploadDir) {
|
|
throw new Error('registerFontRoutes requires pool, pages, upload, and uploadDir.');
|
|
}
|
|
|
|
app.get('/settings/fonts', requireFontsAccess(setAuthMessageCookie), async function (req, res, next) {
|
|
try {
|
|
const data = await buildFontsPageData(pool, uploadDir, req, common);
|
|
res.send(pages.renderFontsPage(data, req.query.message ? String(req.query.message) : '', req.currentUser));
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
});
|
|
|
|
app.post('/settings/fonts', requireFontsAccess(setAuthMessageCookie), upload.single('font_file'), async function (req, res, next) {
|
|
try {
|
|
if (!req.file) {
|
|
return res.status(400).send('No font file was uploaded.');
|
|
}
|
|
|
|
const result = await fontLibrary.addFontFile(uploadDir, req.file, String(req.body && req.body.font_family || '').trim().slice(0, FONT_FAMILY_MAX_LENGTH));
|
|
if (backgroundTaskQueue && typeof backgroundTaskQueue.enqueueTask === 'function') {
|
|
await backgroundTaskQueue.enqueueTask({
|
|
key: `font-sync:${result && result.font && result.font.id ? result.font.id : Date.now()}`,
|
|
title: 'Font sync',
|
|
category: 'fonts',
|
|
taskType: 'font-sync',
|
|
payload: {
|
|
uploadDir: uploadDir,
|
|
operations: fontLibrary.collectFontLibrarySyncOperations(uploadDir)
|
|
},
|
|
persist: true
|
|
});
|
|
}
|
|
|
|
res.redirect('/settings/fonts?message=' + encodeURIComponent('Font uploaded.'));
|
|
} catch (error) {
|
|
if (req.file && req.file.path) {
|
|
try {
|
|
await fs.promises.unlink(req.file.path);
|
|
} catch (_cleanupError) {}
|
|
}
|
|
|
|
if (error && /already exists/i.test(String(error.message || ''))) {
|
|
const data = await buildFontsPageData(pool, uploadDir, req, common);
|
|
|
|
return res.status(400).send(pages.renderFontsPage(data, String(error.message || 'Font already exists.'), req.currentUser));
|
|
}
|
|
|
|
next(error);
|
|
}
|
|
});
|
|
|
|
app.post('/settings/fonts/:id/toggle', requireFontsAccess(setAuthMessageCookie), async function (req, res, next) {
|
|
try {
|
|
const enabled = String(req.body && req.body.enabled || '').trim() !== '0';
|
|
const updated = await fontLibrary.setFontEnabled(uploadDir, String(req.params.id || '').trim(), enabled);
|
|
if (!updated) {
|
|
return res.redirect('/settings/fonts?message=' + encodeURIComponent('That font was not found.'));
|
|
}
|
|
|
|
if (backgroundTaskQueue && typeof backgroundTaskQueue.enqueueTask === 'function') {
|
|
await backgroundTaskQueue.enqueueTask({
|
|
key: `font-sync:toggle:${updated && updated.id ? updated.id : Date.now()}`,
|
|
title: 'Font sync',
|
|
category: 'fonts',
|
|
taskType: 'font-sync',
|
|
payload: {
|
|
uploadDir: uploadDir,
|
|
operations: fontLibrary.collectFontLibrarySyncOperations(uploadDir)
|
|
},
|
|
persist: true
|
|
});
|
|
}
|
|
|
|
res.redirect('/settings/fonts?message=' + encodeURIComponent(enabled ? 'Font enabled.' : 'Font disabled.'));
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
});
|
|
|
|
app.post('/settings/fonts/:id/delete', requireFontsAccess(setAuthMessageCookie), async function (req, res, next) {
|
|
try {
|
|
const fontId = String(req.params.id || '').trim();
|
|
const currentFont = fontLibrary.loadFontLibrary(uploadDir).fonts.find(function (font) {
|
|
return String(font.id || font.fileName || '').trim() === fontId || String(font.fileName || '').trim() === fontId;
|
|
});
|
|
const family = currentFont ? String(currentFont.family || currentFont.name || '').trim() : '';
|
|
if (family && await isFontUsed(pool, family)) {
|
|
return res.redirect('/settings/fonts?message=' + encodeURIComponent('That font is still used by one or more slides.'));
|
|
}
|
|
|
|
const removed = await fontLibrary.deleteFontFile(uploadDir, fontId);
|
|
if (!removed) {
|
|
return res.redirect('/settings/fonts?message=' + encodeURIComponent('That font was not found.'));
|
|
}
|
|
|
|
if (backgroundTaskQueue && typeof backgroundTaskQueue.enqueueTask === 'function') {
|
|
await backgroundTaskQueue.enqueueTask({
|
|
key: `font-sync:delete:${removed && removed.id ? removed.id : Date.now()}`,
|
|
title: 'Font sync',
|
|
category: 'fonts',
|
|
taskType: 'font-sync',
|
|
payload: {
|
|
uploadDir: uploadDir,
|
|
operations: [
|
|
removed.fileName ? { type: 'delete', uploadPath: '/media/fonts/' + removed.fileName } : null
|
|
].concat(fontLibrary.collectFontLibrarySyncOperations(uploadDir)).filter(Boolean)
|
|
},
|
|
persist: true
|
|
});
|
|
}
|
|
|
|
res.redirect('/settings/fonts?message=' + encodeURIComponent('Font removed.'));
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
});
|
|
}; |