fix: corrige loading infinito no login - pg.Pool + logs debug
This commit is contained in:
parent
41a9626f20
commit
0fad3e05f7
|
|
@ -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)
|
||||
|
|
|
|||
15
db/index.ts
15
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 });
|
||||
|
|
|
|||
|
|
@ -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 |
|
||||
|
|
|
|||
|
|
@ -58,24 +58,49 @@ export function setupAuth(app: Express) {
|
|||
|
||||
passport.use(
|
||||
new LocalStrategy(async (username, password, done) => {
|
||||
console.log(`[Auth] Tentativa de login: ${username}`);
|
||||
try {
|
||||
const user = await storage.getUserByUsername(username);
|
||||
if (!user || !(await comparePasswords(password, user.password))) {
|
||||
if (!user) {
|
||||
console.log(`[Auth] Usuário não encontrado: ${username}`);
|
||||
return done(null, false);
|
||||
} else {
|
||||
}
|
||||
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) => {
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
||||
app.post("/api/register", async (req, res, next) => {
|
||||
|
|
@ -101,8 +126,15 @@ export function setupAuth(app: Express) {
|
|||
});
|
||||
|
||||
app.post("/api/login", passport.authenticate("local"), async (req, res) => {
|
||||
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) => {
|
||||
|
|
|
|||
|
|
@ -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,11 +307,15 @@ class LearningService {
|
|||
} else {
|
||||
errors++;
|
||||
}
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
errors++;
|
||||
if (error.name === 'AbortError') {
|
||||
console.warn(`[Learning] Timeout indexing interaction ${interaction.id} (3s exceeded)`);
|
||||
} else {
|
||||
console.error(`[Learning] Error indexing interaction ${interaction.id}:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (indexedIds.length > 0) {
|
||||
await this.markAsIndexed(indexedIds);
|
||||
|
|
@ -324,14 +333,19 @@ class LearningService {
|
|||
}
|
||||
|
||||
async searchSimilarInteractions(query: string): Promise<any[]> {
|
||||
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) {
|
||||
} 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 [];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,8 +10,12 @@ const AUTO_START = process.env.PLUS_AUTO_START !== "false";
|
|||
export async function setupPlusProxy(app: Express): Promise<void> {
|
||||
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
|
||||
|
|
|
|||
|
|
@ -43,23 +43,31 @@ export class DatabaseStorage implements IStorage {
|
|||
}
|
||||
|
||||
async getUser(id: string): Promise<User | undefined> {
|
||||
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<User | undefined> {
|
||||
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<any> {
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue