refactor: blindagem da arquitetura de roteamento (APIs 100% JSON sem redirects para html), cache buster dinamico e correcao de ordem do middleware de instalacao
continuous-integration/webhook Deploy concluido (rx.scoreodonto.com)

This commit is contained in:
VPS 4 Builder
2026-05-26 15:52:18 +02:00
parent 105db109fe
commit fa7b2c262c
3 changed files with 69 additions and 23 deletions
+6 -3
View File
@@ -13,10 +13,13 @@ function authenticateToken(req, res, next) {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN
// Identifica se a rota é uma API (via URL ou cabeçalho Accept)
const isApiRoute = req.originalUrl.includes('/api') ||
(req.headers.accept && req.headers.accept.includes('application/json'));
// Se não tem token, verifica se está tentando acessar uma página
if (!token) {
const isApiRoute = req.originalUrl.startsWith('/api');
const isLoginRoute = req.originalUrl.startsWith('/login') || req.originalUrl === '/auth/login.html';
const isLoginRoute = req.originalUrl.includes('/login') || req.originalUrl === '/auth/login.html';
if (isLoginRoute || req.originalUrl === '/status') {
return next();
@@ -32,7 +35,7 @@ function authenticateToken(req, res, next) {
// Verificar token
jwt.verify(token, JWT_SECRET, (err, user) => {
if (err) {
if (req.originalUrl.startsWith('/api')) {
if (isApiRoute) {
return res.status(403).json({ error: 'Token inválido' });
}
return res.redirect('/login');
+2 -2
View File
@@ -14,7 +14,7 @@
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<!-- Estilos -->
<link rel="stylesheet" href="admin-clinics.css?v=2.0.1">
<link rel="stylesheet" href="admin-clinics.css">
</head>
<body>
@@ -196,6 +196,6 @@
<div id="toast" class="toast">Notificação</div>
<script src="admin-clinics.js?v=2.0.1"></script>
<script src="admin-clinics.js"></script>
</body>
</html>
+61 -18
View File
@@ -415,18 +415,18 @@ TRUST_PROXY=true
console.log('✅ Arquivo .env atualizado');
}
// ================================================================
// SERVIDOR DE FRONTEND ESTÁTICO COM CACHE BUSTER AUTOMÁTICO
// ================================================================
// Página de instalação
app.get('/install', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'install.html'));
serveHtmlWithCacheBuster(res, path.join(__dirname, 'public', 'install.html'));
});
// ================================================================
// ROTA PRINCIPAL (Página web) - PRIMEIRA ROTA A SER DEFINIDA
// ================================================================
// ROTA PRINCIPAL (Página web)
// Página web principal
app.get('/', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'index.html'));
serveHtmlWithCacheBuster(res, path.join(__dirname, 'public', 'index.html'));
});
// Favicon redirect/fallback
@@ -436,17 +436,17 @@ app.get('/favicon.ico', (req, res) => {
// Página de clientes conectados
app.get('/clients', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'clients.html'));
serveHtmlWithCacheBuster(res, path.join(__dirname, 'public', 'clients.html'));
});
// Página de reset de fábrica
app.get('/reset', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'reset.html'));
serveHtmlWithCacheBuster(res, path.join(__dirname, 'public', 'reset.html'));
});
// Página de Clínicas
app.get('/admin-clinics', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'admin-clinics.html'));
serveHtmlWithCacheBuster(res, path.join(__dirname, 'public', 'admin-clinics.html'));
});
// Rotas de autenticação (públicas)
@@ -455,20 +455,40 @@ app.use('/api/client', clientAuthRoutes);
// Página de login
app.get('/login', (req, res) => {
res.sendFile(path.join(__dirname, 'auth', 'login.html'));
serveHtmlWithCacheBuster(res, path.join(__dirname, 'auth', 'login.html'));
});
app.use('/login', express.static('auth'));
// Serve static files
app.use(express.static('public'));
app.use('/uploads', express.static(UPLOAD_DIR));
app.use('/uploads', express.static(PROCESSED_DIR));
// Middleware de autenticação estática (movemos pro final depois)
// Função utilitária de Cache Buster
async function serveHtmlWithCacheBuster(res, filePath) {
try {
const pkg = require('./package.json');
const APP_VERSION = pkg.version || Date.now();
let content = await fs.readFile(filePath, 'utf8');
// Injeta versão em tags de script e css
content = content.replace(/src="([^"]+\.js)(?:\?v=[^"]*)?"/g, `src="$1?v=${APP_VERSION}"`);
content = content.replace(/href="([^"]+\.css)(?:\?v=[^"]*)?"/g, `href="$1?v=${APP_VERSION}"`);
res.set('Content-Type', 'text/html');
res.send(content);
} catch (err) {
console.error('Erro ao ler HTML:', err);
res.status(500).send('Erro interno ao carregar a página.');
}
}
// ================================================================
// MIDDLEWARE DE VERIFICAÇÃO DE INSTALAÇÃO (APENAS PARA APIs)
// ================================================================
async function checkInstallationMiddleware(req, res, next) {
// Identificar se é rota da API
const isApiRoute = req.originalUrl.includes('/api') ||
(req.headers.accept && req.headers.accept.includes('application/json'));
// SEMPRE permitir rotas públicas, static files e Socket.IO
if (req.path === '/' ||
req.path.startsWith('/install') ||
@@ -477,12 +497,13 @@ async function checkInstallationMiddleware(req, res, next) {
req.path.startsWith('/socket.io') ||
req.path.startsWith('/api/auth') ||
req.path.startsWith('/api/install') ||
req.path.startsWith('/api/clients/check-name')) {
req.path.startsWith('/api/clients/check-name') ||
req.path.match(/\.(js|css|svg|png|jpg|jpeg|woff2?|ttf|eot)$/)) {
return next();
}
// Para APIs que não são de instalação ou auth, verificar instalação
if (req.path.startsWith('/api')) {
if (isApiRoute) {
try {
const installedFile = path.join(__dirname, '.installed');
const fsSync = require('fs');
@@ -497,6 +518,18 @@ async function checkInstallationMiddleware(req, res, next) {
error: 'Erro ao verificar instalação.'
});
}
} else {
// Se não for API e não for rota pública liberada acima, e estiver sem .installed,
// manda pro install.
try {
const installedFile = path.join(__dirname, '.installed');
const fsSync = require('fs');
if (!fsSync.existsSync(installedFile)) {
return res.redirect('/install');
}
} catch (error) {
// Ignora erro e prossegue
}
}
next();
@@ -525,6 +558,16 @@ app.use('/api/system', systemRoutes);
app.use('/api/admin/clinics', authenticateToken);
app.use('/api/admin/clinics', adminClinicsRoutes);
// ================================================================
// MIDDLEWARES DE ARQUIVOS ESTÁTICOS (Movidos para o fim)
// ================================================================
// Serve static files e rotas estáticas
app.use('/login', express.static('auth'));
app.use(express.static('public'));
app.use('/uploads', express.static(UPLOAD_DIR));
app.use('/uploads', express.static(PROCESSED_DIR));
// ================================================================
// SOCKET.IO - CONEXÕES EM TEMPO REAL
// ================================================================