From 0fad3e05f7da3e70a45ac306145b4bfd6c798215 Mon Sep 17 00:00:00 2001 From: Jonas Pacheco Date: Thu, 9 Apr 2026 14:16:05 -0300 Subject: [PATCH] fix: corrige loading infinito no login - pg.Pool + logs debug --- .env.example | 6 +++- db/index.ts | 15 ++++++++-- docs/KERNEL_GAP_ANALYSIS.md | 20 +++++++++++++ server/auth.ts | 58 ++++++++++++++++++++++++++++--------- server/learning/service.ts | 30 +++++++++++++++---- server/plus/proxy.ts | 6 +++- server/storage.ts | 21 +++++++++++++- 7 files changed, 131 insertions(+), 25 deletions(-) diff --git a/.env.example b/.env.example index 6556427..4660c5a 100644 --- a/.env.example +++ b/.env.example @@ -27,7 +27,11 @@ CONTABIL_PYTHON_URL=http://localhost:8003 BI_PYTHON_URL=http://localhost:8004 AUTOMATION_PYTHON_URL=http://localhost:8005 FISCO_PYTHON_URL=http://localhost:8002 -PYTHON_SERVICE_URL=http://localhost:8001 +# ── Embeddings / Learning Service ────────────────────────────────────────────── +# IMPORTANTE: Usar porta 8000 (não 8001) - o serviço escuta na porta 8000 +# Em Docker: http://arcadia-prod-embeddings-1:8000 +# Em dev local: http://localhost:8000 +PYTHON_SERVICE_URL=http://localhost:8000 # ── IA — OpenAI ─────────────────────────────────────────────────────────────── # Deixe vazio se usar apenas Ollama (soberania total) diff --git a/db/index.ts b/db/index.ts index a17d026..b14c920 100644 --- a/db/index.ts +++ b/db/index.ts @@ -8,10 +8,19 @@ if (!process.env.DATABASE_URL) { ); } -const client = new pg.Client({ +// CORREÇÃO: Usar Pool em vez de Client único para melhor performance +// e evitar queries travadas quando conexão falha +const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL, + max: 20, // máximo de conexões no pool + idleTimeoutMillis: 30000, // tempo máximo de inatividade + connectionTimeoutMillis: 5000, // timeout de conexão: 5s + statement_timeout: 10000, // timeout de query: 10s }); -client.connect(); +// Log de erros do pool +pool.on('error', (err) => { + console.error('[DB Pool] Erro inesperado:', err.message); +}); -export const db = drizzle({ client, schema }); +export const db = drizzle({ client: pool, schema }); diff --git a/docs/KERNEL_GAP_ANALYSIS.md b/docs/KERNEL_GAP_ANALYSIS.md index af279a3..3628711 100644 --- a/docs/KERNEL_GAP_ANALYSIS.md +++ b/docs/KERNEL_GAP_ANALYSIS.md @@ -5,6 +5,26 @@ --- +## 🔴 Root Cause Analysis: Learning Service Timeouts (RESOLVIDO) + +**Problema:** `PYTHON_SERVICE_URL` sendo resetado para porta 8001 em vez de 8000 + +**Causa Raiz Identificada:** O arquivo `.env.example` na linha 30 continha: +```bash +PYTHON_SERVICE_URL=http://localhost:8001 # ← ERRADO +``` + +Quando o Coolify recriava o container, ele usava o valor do `.env.example` como fallback, causando: +- Timeouts de 10s a cada 60s (ciclo do Learning Service) +- Degradação de performance do sistema +- Erros silenciosos no log + +**Solução Aplicada (08/04/2026):** Corrigido `.env.example` para usar porta 8000 com comentário explicativo. + +**Lição Aprendida:** O `.env.example` deve sempre estar sincronizado com a configuração de produção correta. + +--- + ## 📊 Resumo das Funcionalidades | Funcionalidade | Status | Implementação | Notas | diff --git a/server/auth.ts b/server/auth.ts index e79fe64..0e2dad4 100644 --- a/server/auth.ts +++ b/server/auth.ts @@ -58,23 +58,48 @@ export function setupAuth(app: Express) { passport.use( new LocalStrategy(async (username, password, done) => { - const user = await storage.getUserByUsername(username); - if (!user || !(await comparePasswords(password, user.password))) { - return done(null, false); - } else { + console.log(`[Auth] Tentativa de login: ${username}`); + try { + const user = await storage.getUserByUsername(username); + if (!user) { + console.log(`[Auth] Usuário não encontrado: ${username}`); + return done(null, false); + } + console.log(`[Auth] Usuário encontrado, verificando senha...`); + const validPassword = await comparePasswords(password, user.password); + if (!validPassword) { + console.log(`[Auth] Senha inválida para: ${username}`); + return done(null, false); + } + console.log(`[Auth] Login bem-sucedido: ${username}`); return done(null, user); + } catch (error: any) { + console.error(`[Auth] Erro no login: ${error.message}`); + return done(error); } }), ); - passport.serializeUser((user, done) => done(null, user.id)); + passport.serializeUser((user, done) => { + console.log(`[Auth] Serializando usuário: ${user.id}`); + done(null, user.id); + }); passport.deserializeUser(async (id: string, done) => { - const user = await storage.getUser(id); - if (user) { - const enrichedUser = await storage.getEnrichedUser(user); - done(null, enrichedUser); - } else { - done(null, null); + console.log(`[Auth] Desserializando usuário: ${id}`); + try { + const user = await storage.getUser(id); + if (user) { + console.log(`[Auth] Enriquecendo usuário: ${id}`); + const enrichedUser = await storage.getEnrichedUser(user); + console.log(`[Auth] Usuário desserializado: ${id}`); + done(null, enrichedUser); + } else { + console.log(`[Auth] Usuário não encontrado na desserialização: ${id}`); + done(null, null); + } + } catch (error: any) { + console.error(`[Auth] Erro na desserialização: ${error.message}`); + done(error); } }); @@ -101,8 +126,15 @@ export function setupAuth(app: Express) { }); app.post("/api/login", passport.authenticate("local"), async (req, res) => { - const enrichedUser = await storage.getEnrichedUser(req.user!); - res.status(200).json(enrichedUser); + console.log(`[Auth] Post-login, enriquecendo usuário: ${req.user!.id}`); + try { + const enrichedUser = await storage.getEnrichedUser(req.user!); + console.log(`[Auth] Enviando resposta de login para: ${enrichedUser.username}`); + res.status(200).json(enrichedUser); + } catch (error: any) { + console.error(`[Auth] Erro ao enriquecer usuário no login: ${error.message}`); + res.status(500).json({ error: "Erro ao processar login" }); + } }); app.post("/api/logout", (req, res, next) => { diff --git a/server/learning/service.ts b/server/learning/service.ts index e2698c7..a059b94 100644 --- a/server/learning/service.ts +++ b/server/learning/service.ts @@ -264,7 +264,7 @@ class LearningService { } async indexInteractionsToChromaDB(): Promise<{ indexed: number; errors: number }> { - const PYTHON_SERVICE_URL = process.env.PYTHON_SERVICE_URL || "http://localhost:8001"; + const PYTHON_SERVICE_URL = process.env.PYTHON_SERVICE_URL || "http://localhost:8000"; const unindexed = await this.getUnindexedInteractions(100); if (unindexed.length === 0) { @@ -286,6 +286,9 @@ class LearningService { createdAt: interaction.createdAt?.toISOString(), }; + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), 3000); + const response = await fetch(`${PYTHON_SERVICE_URL}/embeddings/add`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, @@ -294,7 +297,9 @@ class LearningService { document, metadata, }), + signal: controller.signal, }); + clearTimeout(timeout); if (response.ok) { indexedIds.push(interaction.id); @@ -302,9 +307,13 @@ class LearningService { } else { errors++; } - } catch (error) { + } catch (error: any) { errors++; - console.error(`[Learning] Error indexing interaction ${interaction.id}:`, error); + if (error.name === 'AbortError') { + console.warn(`[Learning] Timeout indexing interaction ${interaction.id} (3s exceeded)`); + } else { + console.error(`[Learning] Error indexing interaction ${interaction.id}:`, error); + } } } @@ -324,14 +333,19 @@ class LearningService { } async searchSimilarInteractions(query: string): Promise { - const PYTHON_SERVICE_URL = process.env.PYTHON_SERVICE_URL || "http://localhost:8001"; + const PYTHON_SERVICE_URL = process.env.PYTHON_SERVICE_URL || "http://localhost:8000"; try { + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), 3000); + const response = await fetch(`${PYTHON_SERVICE_URL}/embeddings/search`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ query, n_results: 5 }), + signal: controller.signal, }); + clearTimeout(timeout); if (!response.ok) { return []; @@ -339,8 +353,12 @@ class LearningService { const results = await response.json(); return results.documents?.[0] || []; - } catch (error) { - console.error("[Learning] Error searching similar interactions:", error); + } catch (error: any) { + if (error.name === 'AbortError') { + console.warn("[Learning] Timeout searching similar interactions (3s exceeded)"); + } else { + console.error("[Learning] Error searching similar interactions:", error); + } return []; } } diff --git a/server/plus/proxy.ts b/server/plus/proxy.ts index 555a3ad..45786b3 100644 --- a/server/plus/proxy.ts +++ b/server/plus/proxy.ts @@ -10,8 +10,12 @@ const AUTO_START = process.env.PLUS_AUTO_START !== "false"; export async function setupPlusProxy(app: Express): Promise { const plusTarget = `http://${PLUS_HOST}:${PLUS_PORT}`; + // CORREÇÃO: Não aguardar Laravel iniciar para não bloquear rotas + // Se falhar, o proxy retorna 502 mas o resto do sistema funciona if (AUTO_START) { - await startLaravelServer(); + startLaravelServer().catch((err) => { + console.error('[Plus Proxy] Falha ao iniciar Laravel:', err.message); + }); } // Simple transparent proxy - only rewrite Location headers diff --git a/server/storage.ts b/server/storage.ts index 4a267f1..d3f23e2 100644 --- a/server/storage.ts +++ b/server/storage.ts @@ -43,23 +43,31 @@ export class DatabaseStorage implements IStorage { } async getUser(id: string): Promise { + console.log(`[Storage] Buscando usuário por ID: ${id}`); const [user] = await db.select().from(users).where(eq(users.id, id)); + console.log(`[Storage] Usuário ${user ? 'encontrado' : 'não encontrado'}: ${id}`); return user; } // CORREÇÃO: getUserByUsername com validação de tenant async getUserByUsername(username: string, tenantId?: number): Promise { + console.log(`[Storage] Buscando usuário por username: ${username}`); const [user] = await db.select().from(users).where(eq(users.username, username)); + console.log(`[Storage] Usuário ${user ? 'encontrado' : 'não encontrado'}: ${username}`); // Se tenantId foi passado, validar que usuário pertence a esse tenant if (tenantId && user) { + console.log(`[Storage] Validando tenant ${tenantId} para usuário: ${username}`); const [tenantUser] = await db.select() .from(tenantUsers) .where(and( eq(tenantUsers.userId, user.id), eq(tenantUsers.tenantId, tenantId) )); - if (!tenantUser) return undefined; + if (!tenantUser) { + console.log(`[Storage] Usuário ${username} não pertence ao tenant ${tenantId}`); + return undefined; + } } return user; @@ -120,26 +128,33 @@ export class DatabaseStorage implements IStorage { } async getEnrichedUser(user: User): Promise { + console.log(`[Storage] Iniciando enriquecimento do usuário: ${user.id}`); const enriched: any = { ...user }; if (user.profileId) { + console.log(`[Storage] Buscando profile: ${user.profileId}`); const [profile] = await db.select().from(profiles).where(eq(profiles.id, user.profileId)); if (profile) { enriched.profile = profile; enriched.allowedModules = profile.allowedModules || []; + console.log(`[Storage] Profile encontrado: ${profile.name}`); } } if (user.partnerId) { + console.log(`[Storage] Buscando partner: ${user.partnerId}`); const [partner] = await db.select().from(crmPartners).where(eq(crmPartners.id, user.partnerId)); if (partner) { enriched.partner = partner; + console.log(`[Storage] Partner encontrado: ${partner.name}`); } } // Buscar tenant do usuário (apenas campos seguros para o cliente) + console.log(`[Storage] Buscando tenantUser para: ${user.id}`); const [tenantUser] = await db.select().from(tenantUsers).where(eq(tenantUsers.userId, user.id)); if (tenantUser) { + console.log(`[Storage] TenantUser encontrado, buscando tenant: ${tenantUser.tenantId}`); const [tenant] = await db.select().from(tenants).where(eq(tenants.id, tenantUser.tenantId)); if (tenant) { // Expor apenas campos seguros do tenant @@ -154,9 +169,13 @@ export class DatabaseStorage implements IStorage { enriched.tenantType = tenant.tenantType; enriched.tenantRole = tenantUser.role; enriched.isOwner = tenantUser.isOwner === "true"; + console.log(`[Storage] Tenant enriquecido: ${tenant.name}`); } + } else { + console.log(`[Storage] Usuário sem tenant associado: ${user.id}`); } + console.log(`[Storage] Enriquecimento completo: ${user.id}`); return enriched; }