211 lines
7.9 KiB
JavaScript
211 lines
7.9 KiB
JavaScript
require('dotenv').config();
|
|
const express = require('express');
|
|
const http = require('http');
|
|
const bodyParser = require('body-parser');
|
|
const cors = require('cors');
|
|
const path = require('path');
|
|
const session = require('express-session');
|
|
const SQLiteStore = require('connect-sqlite3')(session);
|
|
const multer = require('multer');
|
|
|
|
const app = express();
|
|
const PORT = process.env.PORT || 3000;
|
|
|
|
app.set('view engine', 'ejs');
|
|
app.set('views', path.join(__dirname, 'views'));
|
|
app.use(express.static(path.join(__dirname, 'public')));
|
|
app.use(bodyParser.urlencoded({ extended: true }));
|
|
app.use(bodyParser.json());
|
|
app.use(cors());
|
|
|
|
const { localeMiddleware } = require('./locales');
|
|
const { crmContextMiddleware, getClientHubLinks } = require('./lib/crm');
|
|
const { getNavSections } = require('./lib/nav');
|
|
const { initAiSchema, syncAiConfigFile } = require('./lib/ai');
|
|
|
|
app.use(localeMiddleware);
|
|
|
|
try { initAiSchema(); syncAiConfigFile(); } catch (e) { console.warn('AI init:', e.message); }
|
|
|
|
app.use(session({
|
|
store: new SQLiteStore({ db: 'sessions.db', dir: path.join(__dirname, 'data') }),
|
|
secret: process.env.SESSION_SECRET || 'mek-tech-consulting-secret',
|
|
resave: false,
|
|
saveUninitialized: false,
|
|
cookie: { maxAge: 7 * 24 * 60 * 60 * 1000, sameSite: 'lax' }
|
|
}));
|
|
|
|
app.use((req, res, next) => {
|
|
crmContextMiddleware(req, res, () => {
|
|
res.locals.navSections = getNavSections(req.path);
|
|
res.locals.showCrmBar = !req.path.startsWith('/auth') && req.path !== '/login';
|
|
if (res.locals.activeClient) {
|
|
res.locals.clientHubLinks = getClientHubLinks(res.locals.activeClient.id);
|
|
}
|
|
next();
|
|
});
|
|
});
|
|
|
|
app.use((req, res, next) => {
|
|
const publicPaths = ['/login', '/auth/login', '/css/dynamic.css', '/js/theme.js', '/health'];
|
|
const publicPrefixes = ['/wachtwoord-vergeten', '/wachtwoord-reset/', '/css/', '/js/', '/health'];
|
|
if (!publicPaths.includes(req.path) && !publicPrefixes.some(p => req.path.startsWith(p)) && !req.session.userId) {
|
|
// API-aanroepen krijgen 401 JSON i.p.v. een HTML-redirect (o.a. voor de React SPA)
|
|
if (req.path.startsWith('/api/')) {
|
|
return res.status(401).json({ error: 'Niet ingelogd' });
|
|
}
|
|
return res.redirect('/login');
|
|
}
|
|
res.locals.user = req.session.userId ? { username: req.session.username } : null;
|
|
next();
|
|
});
|
|
|
|
app.use('/uploads', express.static(path.join(__dirname, 'uploads')));
|
|
|
|
const QS_HOST = process.env.QS_HOST || 'python-quality';
|
|
const QS_PORT = parseInt(process.env.QS_PORT || '3002', 10);
|
|
app.use('/python-quality', (req, res) => {
|
|
const proxyPath = '/python-quality' + req.url;
|
|
const proxyReq = http.request({
|
|
hostname: QS_HOST,
|
|
port: QS_PORT,
|
|
path: proxyPath,
|
|
method: req.method,
|
|
headers: { ...req.headers, host: `${QS_HOST}:${QS_PORT}` }
|
|
}, (proxyRes) => {
|
|
res.writeHead(proxyRes.statusCode, proxyRes.headers);
|
|
proxyRes.pipe(res);
|
|
});
|
|
proxyReq.on('error', () => {
|
|
res.status(502).type('html').send(`<!DOCTYPE html><html lang="nl"><head><meta charset="UTF-8"><title>Service offline</title><link rel="stylesheet" href="/css/style.css"><link rel="stylesheet" href="/css/dynamic.css"></head><body style="padding:2rem;font-family:Inter,sans-serif"><h1>Data Quality service offline</h1><p>De Python Quality engine is momenteel niet bereikbaar.</p><p><a href="/quality">Ga naar Quality Dashboard</a></p></body></html>`);
|
|
});
|
|
if (req.method === 'GET' || req.method === 'HEAD') proxyReq.end();
|
|
else req.pipe(proxyReq);
|
|
});
|
|
|
|
app.use((req, res, next) => {
|
|
try {
|
|
const { getSettings } = require('./db');
|
|
const settings = getSettings();
|
|
res.locals.auto_refresh = settings.auto_refresh || '0';
|
|
res.locals.settings_data = JSON.stringify(settings);
|
|
} catch (e) {
|
|
res.locals.auto_refresh = '0';
|
|
res.locals.settings_data = '{}';
|
|
}
|
|
next();
|
|
});
|
|
|
|
app.get('/css/dynamic.css', (req, res) => {
|
|
try {
|
|
const { getSettings } = require('./db');
|
|
const { THEME_PRESETS, THEME_CSS_KEYS } = require('./themes');
|
|
const settings = getSettings();
|
|
let css = '/* Mek-Tech Dynamic Settings */\n';
|
|
|
|
const themeKey = settings.theme || 'mek-neon-blue';
|
|
const preset = THEME_PRESETS[themeKey] || THEME_PRESETS['mek-neon-blue'];
|
|
const customColors = settings.theme_colors || {};
|
|
|
|
const resolved = {};
|
|
css += ':root{\n';
|
|
THEME_CSS_KEYS.forEach(k => {
|
|
const val = customColors[k] || preset[k];
|
|
if (val) {
|
|
resolved[k] = val;
|
|
css += `--${k.replace(/_/g, '-')}:${val};\n`;
|
|
}
|
|
});
|
|
css += '}\n';
|
|
|
|
const bg = resolved.bg || preset.bg;
|
|
const bgNav = resolved.bg_nav || preset.bg_nav;
|
|
const accent = resolved.accent || preset.accent;
|
|
|
|
css += `
|
|
/* User theme overrides */
|
|
body{background-color:${bg}!important}
|
|
.navbar{background:${bgNav}!important;border-bottom-color:var(--border)!important}
|
|
.sidebar{background:var(--bg-nav)!important}
|
|
.sb-item.active{background:rgba(0,212,255,.12)!important;color:${accent}!important}
|
|
.kpi-card:hover,.client-card:hover,.card:hover{box-shadow:0 0 28px rgba(0,212,255,.15)!important}
|
|
`;
|
|
|
|
// Icon colors
|
|
if (settings.icon_colors && typeof settings.icon_colors === 'object') {
|
|
for (const [icon, color] of Object.entries(settings.icon_colors)) {
|
|
css += `[data-lucide="${icon}"]{color:${color}!important}\n`;
|
|
}
|
|
}
|
|
|
|
res.type('css').send(css);
|
|
} catch (e) {
|
|
console.error('dynamic.css error:', e.message);
|
|
res.type('css').send('/* no settings */');
|
|
}
|
|
});
|
|
|
|
app.use('/', require('./routes/auth'));
|
|
app.use('/', require('./routes/dashboard'));
|
|
app.use('/', require('./routes/password'));
|
|
app.use('/clients', require('./routes/clients'));
|
|
app.use('/engagements', require('./routes/engagements'));
|
|
app.use('/tasks', require('./routes/tasks'));
|
|
app.use('/assess', require('./routes/assess'));
|
|
app.use('/racks', require('./routes/racks'));
|
|
app.use('/networking', require('./routes/networking'));
|
|
app.use('/architecture', require('./routes/architecture'));
|
|
app.use('/sizing', require('./routes/sizing'));
|
|
app.use('/infrastructure', require('./routes/infrastructure'));
|
|
app.use('/finance', require('./routes/finance'));
|
|
app.use('/settings', require('./routes/settings').router);
|
|
app.use('/backup', require('./routes/backup'));
|
|
app.use('/users', require('./routes/users'));
|
|
app.use('/time', require('./routes/time'));
|
|
app.use('/invoices', require('./routes/invoices'));
|
|
app.use('/notifications', require('./routes/notifications').router);
|
|
app.use('/calendar', require('./routes/calendar'));
|
|
app.use('/search', require('./routes/search'));
|
|
app.use('/audit', require('./routes/audit'));
|
|
app.use('/email', require('./routes/email'));
|
|
app.use('/quality', require('./routes/quality'));
|
|
app.use('/market', require('./routes/market'));
|
|
app.use('/consulting', require('./routes/consulting'));
|
|
app.use('/projects', require('./routes/projects'));
|
|
app.use('/reports', require('./routes/reports'));
|
|
app.use('/integrations', require('./routes/integrations'));
|
|
app.use('/ai', require('./routes/ai'));
|
|
app.use('/api/v1', require('./routes/api'));
|
|
app.use('/api/spa', require('./routes/spa'));
|
|
|
|
// ---- React SPA (production build in public/spa) ----
|
|
const spaDir = path.join(__dirname, 'public', 'spa');
|
|
if (require('fs').existsSync(spaDir)) {
|
|
app.use('/app', express.static(spaDir));
|
|
// History-API fallback: alle niet-bestand paden onder /app -> index.html
|
|
app.get('/app/*', (req, res) => {
|
|
res.sendFile(path.join(spaDir, 'index.html'));
|
|
});
|
|
app.get('/app', (req, res) => {
|
|
res.sendFile(path.join(spaDir, 'index.html'));
|
|
});
|
|
}
|
|
|
|
app.get('/health', async (req, res) => {
|
|
try {
|
|
const { getServicesStatus } = require('./lib/services');
|
|
const services = await getServicesStatus();
|
|
res.json({ status: 'ok', services, ts: new Date().toISOString() });
|
|
} catch (e) {
|
|
res.status(500).json({ status: 'error', error: e.message });
|
|
}
|
|
});
|
|
|
|
app.use((req, res) => {
|
|
res.status(404).render('404', { path: req.path });
|
|
});
|
|
|
|
app.listen(PORT, '0.0.0.0', () => {
|
|
console.log(`Mek-Tech Consulting Framework running on http://0.0.0.0:${PORT}`);
|
|
});
|