- monitoring.js: Added log dependency, replaced console.log with log.warn - themes.js: Added log dependency, replaced console.error with log.error - src/app.js: Pass log to monitoringRoutes and themesRoutes This fixes error messages being lost to stdout instead of proper log files.
81 lines
2.5 KiB
JavaScript
81 lines
2.5 KiB
JavaScript
const express = require('express');
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const { success } = require('../response-helpers');
|
|
const { ValidationError, NotFoundError } = require('../errors');
|
|
|
|
/**
|
|
* Themes routes factory
|
|
* @param {Object} deps - Explicit dependencies
|
|
* @param {Function} deps.asyncHandler - Async route handler wrapper
|
|
* @param {Object} deps.log - Logger instance
|
|
* @returns {express.Router}
|
|
*/
|
|
module.exports = function({ asyncHandler, log }) {
|
|
const router = express.Router();
|
|
const THEMES_DIR = process.env.THEMES_DIR || path.join(path.dirname(process.env.SERVICES_FILE || '/app/services.json'), 'themes');
|
|
|
|
// Ensure themes directory exists
|
|
if (!fs.existsSync(THEMES_DIR)) {
|
|
fs.mkdirSync(THEMES_DIR, { recursive: true });
|
|
}
|
|
|
|
function readAllThemes() {
|
|
const themes = {};
|
|
try {
|
|
const files = fs.readdirSync(THEMES_DIR).filter(f => f.endsWith('.json'));
|
|
for (const file of files) {
|
|
const slug = path.basename(file, '.json');
|
|
const data = JSON.parse(fs.readFileSync(path.join(THEMES_DIR, file), 'utf8'));
|
|
themes[slug] = data;
|
|
}
|
|
} catch (e) {
|
|
log.error('themes', 'Failed to read themes', { error: e.message });
|
|
}
|
|
return themes;
|
|
}
|
|
|
|
// Get all user themes
|
|
router.get('/themes', (req, res) => {
|
|
success(res, { themes: readAllThemes() });
|
|
});
|
|
|
|
// Save a theme (create or update)
|
|
router.post('/themes/:slug', asyncHandler(async (req, res) => {
|
|
const { slug } = req.params;
|
|
const { name, colors, lightBg } = req.body;
|
|
|
|
if (!slug || !name || !colors) {
|
|
throw new ValidationError('Missing slug, name, or colors');
|
|
}
|
|
|
|
if (!/^[a-z0-9-]+$/.test(slug)) {
|
|
throw new ValidationError('Invalid slug format (use lowercase letters, numbers, and hyphens only)', 'slug');
|
|
}
|
|
|
|
const themeData = { name, ...colors };
|
|
if (lightBg) themeData.lightBg = true;
|
|
fs.writeFileSync(path.join(THEMES_DIR, slug + '.json'), JSON.stringify(themeData, null, 2), 'utf8');
|
|
|
|
success(res, { message: name + ' theme saved' });
|
|
}));
|
|
|
|
// Delete a theme
|
|
router.delete('/themes/:slug', asyncHandler(async (req, res) => {
|
|
const { slug } = req.params;
|
|
const filePath = path.join(THEMES_DIR, slug + '.json');
|
|
|
|
if (!fs.existsSync(filePath)) {
|
|
throw new NotFoundError(`Theme ${slug}`);
|
|
}
|
|
|
|
const data = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
const name = data.name || slug;
|
|
fs.unlinkSync(filePath);
|
|
|
|
success(res, { message: name + ' theme deleted' });
|
|
}));
|
|
|
|
return router;
|
|
};
|