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)
continuous-integration/webhook Deploy concluido (rx.scoreodonto.com)
This commit is contained in:
@@ -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');
|
||||
|
||||
@@ -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
@@ -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
|
||||
// ================================================================
|
||||
|
||||
Reference in New Issue
Block a user