From fa7b2c262cef6e2f941fa556c8a117f4e219dd7d Mon Sep 17 00:00:00 2001 From: VPS 4 Builder Date: Tue, 26 May 2026 15:52:18 +0200 Subject: [PATCH] refactor: blindagem da arquitetura de roteamento (APIs 100% JSON sem redirects para html), cache buster dinamico e correcao de ordem do middleware de instalacao --- dental-server/auth.js | 9 ++- dental-server/public/admin-clinics.html | 4 +- dental-server/server.js | 79 +++++++++++++++++++------ 3 files changed, 69 insertions(+), 23 deletions(-) diff --git a/dental-server/auth.js b/dental-server/auth.js index aa2342f..1b16349 100644 --- a/dental-server/auth.js +++ b/dental-server/auth.js @@ -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'); diff --git a/dental-server/public/admin-clinics.html b/dental-server/public/admin-clinics.html index aecfdef..a0f0450 100644 --- a/dental-server/public/admin-clinics.html +++ b/dental-server/public/admin-clinics.html @@ -14,7 +14,7 @@ - + @@ -196,6 +196,6 @@
Notificação
- + diff --git a/dental-server/server.js b/dental-server/server.js index 1c54869..c15c990 100644 --- a/dental-server/server.js +++ b/dental-server/server.js @@ -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 // ================================================================