Ajustes 2306

This commit is contained in:
2026-06-23 12:42:14 +00:00
parent e7aa5f5333
commit cf720c37a1
18 changed files with 471 additions and 850 deletions
+3 -15
View File
@@ -2,23 +2,11 @@
# Configuracao do Chatc2 # Configuracao do Chatc2
# ============================================================ # ============================================================
# Driver padrao para novas conexoes (postgres | firebird) # Driver padrao (firebird)
DB_DRIVER=postgres DB_DRIVER=firebird
# ------------------------------------------------------------ # ------------------------------------------------------------
# Banco principal: PostgreSQL (alias "novo_local") # Banco Firebird (alias "novo_local")
# ------------------------------------------------------------
PG_HOST=127.0.0.1
PG_PORT=15433
PG_USER=postgres
PG_PASSWORD=postgres
PG_DATABASE=novo_local
# Schema a ser lido (ex.: lajedo, novo, dev). Use "public" se as tabelas
# estiverem no schema padrao.
PG_SCHEMA=public
# ------------------------------------------------------------
# Banco Firebird (alias "firebird_local" / conexoes legadas)
# ------------------------------------------------------------ # ------------------------------------------------------------
DB_HOST=localhost DB_HOST=localhost
DB_PORT=3050 DB_PORT=3050
+21 -41
View File
@@ -20,14 +20,13 @@ SERVER_DOMAIN="${SERVER_DOMAIN:-}" # Opcional: seu-dominio.com.br
JWT_SECRET="" JWT_SECRET=""
NGROK_URL="" NGROK_URL=""
# PostgreSQL EXTERNO (banco principal "novo_local"). # Firebird LOCAL (banco principal).
# Informe via variaveis de ambiente antes de rodar, ou edite o .env depois. # Informe via variaveis de ambiente antes de rodar, ou edite o .env depois.
PG_HOST="${PG_HOST:-}" DB_HOST="${DB_HOST:-localhost}"
PG_PORT="${PG_PORT:-5432}" DB_PORT="${DB_PORT:-3050}"
PG_USER="${PG_USER:-postgres}" DB_USER="${DB_USER:-SYSDBA}"
PG_PASSWORD="${PG_PASSWORD:-}" DB_PASSWORD="${DB_PASSWORD:-masterkey}"
PG_DATABASE="${PG_DATABASE:-postgres}" DB_DATABASE="${DB_DATABASE:-}"
PG_SCHEMA="${PG_SCHEMA:-public}"
# Cores # Cores
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; NC='\033[0m' RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; NC='\033[0m'
@@ -247,28 +246,18 @@ cat > "$CHATC2_DIR/.env" << EOF
# ============================================================ # ============================================================
# Driver padrao do banco principal # Driver padrao do banco principal
DB_DRIVER=postgres DB_DRIVER=firebird
# ------------------------------------------------------------ # ------------------------------------------------------------
# PostgreSQL (EXTERNO) — banco principal "novo_local" # Firebird (alias "novo_local")
# ------------------------------------------------------------ # ------------------------------------------------------------
PG_HOST=${PG_HOST} DB_HOST=${DB_HOST}
PG_PORT=${PG_PORT} DB_PORT=${DB_PORT}
PG_USER=${PG_USER} DB_USER=${DB_USER}
PG_PASSWORD=${PG_PASSWORD} DB_PASSWORD=${DB_PASSWORD}
PG_DATABASE=${PG_DATABASE}
PG_SCHEMA=${PG_SCHEMA}
# ------------------------------------------------------------
# Firebird (legado / alias "firebird_local")
# ------------------------------------------------------------
DB_HOST=localhost
DB_PORT=3050
DB_USER=SYSDBA
DB_PASSWORD=masterkey
DB_ENCODING=UTF-8 DB_ENCODING=UTF-8
# Caminho do .FDB (vazio = usa ../NOVO.FDB na raiz do projeto) # Caminho do .FDB (vazio = usa ../NOVO.FDB na raiz do projeto)
DB_DATABASE= DB_DATABASE=${DB_DATABASE}
# Servidor # Servidor
PORT=3000 PORT=3000
@@ -289,12 +278,9 @@ EXTERNAL_URL=${EXTERNAL_URL}
EOF EOF
chown "$CHATC2_USER:$CHATC2_USER" "$CHATC2_DIR/.env" chown "$CHATC2_USER:$CHATC2_USER" "$CHATC2_DIR/.env"
log "Arquivo .env criado/atualizado com JWT_SECRET seguro" log "Arquivo .env criado/atualizado com JWT_SECRET seguro"
log " PG_HOST/DB = ${PG_HOST:-(vazio)} / ${PG_DATABASE} (schema: ${PG_SCHEMA})" log " Firebird = ${DB_HOST}:${DB_PORT} (database: ${DB_DATABASE:-(vazio)})"
log " LOCAL_URL = ${LOCAL_URL}" log " LOCAL_URL = ${LOCAL_URL}"
log " EXTERNAL_URL = ${EXTERNAL_URL}" log " EXTERNAL_URL = ${EXTERNAL_URL}"
if [ -z "$PG_HOST" ] || [ -z "$PG_PASSWORD" ]; then
warn "PostgreSQL externo nao configurado: edite $CHATC2_DIR/.env (PG_HOST, PG_PASSWORD, PG_DATABASE, PG_SCHEMA) antes de iniciar."
fi
# ████████████████████████████████████████████████████ # ████████████████████████████████████████████████████
# PASSO 9: MIGRACOES DO BANCO # PASSO 9: MIGRACOES DO BANCO
@@ -314,23 +300,22 @@ else
warn "Apos copiar, execute: chown $CHATC2_USER:firebird $CHATC2_DIR/db/*.FDB && chmod 660 $CHATC2_DIR/db/*.FDB" warn "Apos copiar, execute: chown $CHATC2_USER:firebird $CHATC2_DIR/db/*.FDB && chmod 660 $CHATC2_DIR/db/*.FDB"
fi fi
info "Passo 9/12: Migracoes (Firebird legado)..." info "Passo 9/12: Migracoes (Firebird)..."
# O schema do PostgreSQL e gerenciado no banco EXTERNO — nao migramos aqui. # Migracoes Firebird rodam se houver um .FDB local (alias novo_local).
# Migracoes Firebird so rodam se houver um .FDB local (alias firebird_local).
if ls "$CHATC2_DIR/db/"*.FDB "$CHATC2_DIR/"*.FDB >/dev/null 2>&1; then if ls "$CHATC2_DIR/db/"*.FDB "$CHATC2_DIR/"*.FDB >/dev/null 2>&1; then
if [ -f "$CHATC2_DIR/scripts/migracoes.js" ]; then if [ -f "$CHATC2_DIR/scripts/migracoes.js" ]; then
su - "$CHATC2_USER" -c "cd $CHATC2_DIR && node scripts/migracoes.js firebird_local 2>&1 | tail -10" || warn "Migracoes Firebird podem ter falhado" su - "$CHATC2_USER" -c "cd $CHATC2_DIR && node scripts/migracoes.js novo_local 2>&1 | tail -10" || warn "Migracoes Firebird podem ter falhado"
log "Migracoes Firebird executadas" log "Migracoes Firebird executadas"
fi fi
else else
info "Sem .FDB local — pulando migracoes Firebird (schema do Postgres e externo)." info "Sem .FDB local — pulando migracoes Firebird."
fi fi
# ████████████████████████████████████████████████████ # ████████████████████████████████████████████████████
# PASSO 9b: HABILITAR USUARIOS ADMIN PARA WEB # PASSO 9b: HABILITAR USUARIOS ADMIN PARA WEB
# ████████████████████████████████████████████████████ # ████████████████████████████████████████████████████
info "Passo 9b/12: Habilitando usuarios admin para acesso web..." info "Passo 9b/12: Habilitando usuarios admin para acesso web..."
# Habilita acesso web para admins (Postgres principal e Firebird se presente) # Habilita acesso web para admins (Firebird)
cat > /tmp/chatc2-habilitar-web.js << 'SCRIPTJS' cat > /tmp/chatc2-habilitar-web.js << 'SCRIPTJS'
const db = require('DATABASE_PATH'); const db = require('DATABASE_PATH');
@@ -352,16 +337,11 @@ const db = require('DATABASE_PATH');
SCRIPTJS SCRIPTJS
sed -i "s|DATABASE_PATH|$CHATC2_DIR/src/database|" /tmp/chatc2-habilitar-web.js sed -i "s|DATABASE_PATH|$CHATC2_DIR/src/database|" /tmp/chatc2-habilitar-web.js
# Postgres externo (alias principal) # Firebird (alias principal)
su - "$CHATC2_USER" -c "cd $CHATC2_DIR && node /tmp/chatc2-habilitar-web.js novo_local" 2>&1 || true su - "$CHATC2_USER" -c "cd $CHATC2_DIR && node /tmp/chatc2-habilitar-web.js novo_local" 2>&1 || true
# Firebird local (apenas se houver .FDB)
if ls "$CHATC2_DIR/db/"*.FDB "$CHATC2_DIR/"*.FDB >/dev/null 2>&1; then
su - "$CHATC2_USER" -c "cd $CHATC2_DIR && node /tmp/chatc2-habilitar-web.js firebird_local" 2>&1 || true
fi
rm -f /tmp/chatc2-habilitar-web.js rm -f /tmp/chatc2-habilitar-web.js
log "Usuarios admin verificados (Postgres + Firebird se presente)" log "Usuarios admin verificados (Firebird)"
# ████████████████████████████████████████████████████ # ████████████████████████████████████████████████████
# PASSO 10: NGINX # PASSO 10: NGINX
+5 -151
View File
@@ -7,6 +7,7 @@
"": { "": {
"name": "chatc2", "name": "chatc2",
"version": "2.0.0", "version": "2.0.0",
"hasInstallScript": true,
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"cors": "^2.8.6", "cors": "^2.8.6",
@@ -14,8 +15,7 @@
"express": "^5.2.1", "express": "^5.2.1",
"ffmpeg-static": "^5.3.0", "ffmpeg-static": "^5.3.0",
"jsonwebtoken": "^9.0.3", "jsonwebtoken": "^9.0.3",
"node-firebird": "^2.3.1", "node-firebird": "^2.3.1"
"pg": "^8.21.0"
} }
}, },
"node_modules/@derhuerst/http-basic": { "node_modules/@derhuerst/http-basic": {
@@ -832,134 +832,6 @@
"url": "https://opencollective.com/express" "url": "https://opencollective.com/express"
} }
}, },
"node_modules/pg": {
"version": "8.21.0",
"resolved": "https://registry.npmjs.org/pg/-/pg-8.21.0.tgz",
"integrity": "sha512-AUP1EYJuHraQGsVoCQVIcM7TEJVGtDzxWtGFZd8rds9d+CCXlU5Js1rYgfLNvxy9iJrpHjGrRjoi/3BT9fRyiA==",
"license": "MIT",
"dependencies": {
"pg-connection-string": "^2.13.0",
"pg-pool": "^3.14.0",
"pg-protocol": "^1.14.0",
"pg-types": "2.2.0",
"pgpass": "1.0.5"
},
"engines": {
"node": ">= 16.0.0"
},
"optionalDependencies": {
"pg-cloudflare": "^1.4.0"
},
"peerDependencies": {
"pg-native": ">=3.0.1"
},
"peerDependenciesMeta": {
"pg-native": {
"optional": true
}
}
},
"node_modules/pg-cloudflare": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.4.0.tgz",
"integrity": "sha512-Vo7z/6rrQYxpNRylp4Tlob2elzbh+N/MOQbxFVWCxS7oEx6jF53GTJFxK2WWpKuBRkmiin4Mt+xofFDjx09R0A==",
"license": "MIT",
"optional": true
},
"node_modules/pg-connection-string": {
"version": "2.13.0",
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.13.0.tgz",
"integrity": "sha512-EMnU9E2fSULdsbErBbMaXJvFeD9B4+nPcM3f+4lsiCR0BHLPrLVjv3DbyM2hgQQviKJaTWIRRTjKjWlHg3p2ig==",
"license": "MIT"
},
"node_modules/pg-int8": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
"integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==",
"license": "ISC",
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/pg-pool": {
"version": "3.14.0",
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.14.0.tgz",
"integrity": "sha512-gKtPkFdQPU3DksooVLi9LsjZxrsBUZIpa+7aVx+LV5pNh0KzP4Zleud2po+ConrxbuXGBJ6Hfer6hdgpIBpBaw==",
"license": "MIT",
"peerDependencies": {
"pg": ">=8.0"
}
},
"node_modules/pg-protocol": {
"version": "1.14.0",
"resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.14.0.tgz",
"integrity": "sha512-n5taZ1kO3s9ngDTVxsEznOqCyToTgz0FLuPq0B33COy5pPpuWJpY3/2oRBVETuOgzdqRXfWpM9HIhp2LBBT1BA==",
"license": "MIT"
},
"node_modules/pg-types": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
"integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
"license": "MIT",
"dependencies": {
"pg-int8": "1.0.1",
"postgres-array": "~2.0.0",
"postgres-bytea": "~1.0.0",
"postgres-date": "~1.0.4",
"postgres-interval": "^1.1.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/pgpass": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz",
"integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==",
"license": "MIT",
"dependencies": {
"split2": "^4.1.0"
}
},
"node_modules/postgres-array": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
"integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==",
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/postgres-bytea": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz",
"integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/postgres-date": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
"integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/postgres-interval": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
"integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
"license": "MIT",
"dependencies": {
"xtend": "^4.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/progress": { "node_modules/progress": {
"version": "2.0.3", "version": "2.0.3",
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
@@ -983,9 +855,9 @@
} }
}, },
"node_modules/qs": { "node_modules/qs": {
"version": "6.15.1", "version": "6.15.2",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz", "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz",
"integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==", "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==",
"license": "BSD-3-Clause", "license": "BSD-3-Clause",
"dependencies": { "dependencies": {
"side-channel": "^1.1.0" "side-channel": "^1.1.0"
@@ -1212,15 +1084,6 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/split2": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
"integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
"license": "ISC",
"engines": {
"node": ">= 10.x"
}
},
"node_modules/statuses": { "node_modules/statuses": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
@@ -1314,15 +1177,6 @@
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"license": "ISC" "license": "ISC"
},
"node_modules/xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
"license": "MIT",
"engines": {
"node": ">=0.4"
}
} }
} }
} }
+4 -5
View File
@@ -1,14 +1,14 @@
{ {
"name": "chatc2", "name": "chatc2",
"version": "2.0.0", "version": "2.0.0",
"description": "API REST multi-banco (PostgreSQL + Firebird) para a plataforma de atendimento Chatc2", "description": "API REST Firebird para a plataforma de atendimento Chatc2",
"main": "src/server.js", "main": "src/server.js",
"scripts": { "scripts": {
"start": "node src/server.js", "start": "node src/server.js",
"dev": "node --watch src/server.js" "dev": "node --watch src/server.js",
"postinstall": "node scripts/postinstall.js"
}, },
"keywords": [ "keywords": [
"postgres",
"firebird", "firebird",
"api", "api",
"express" "express"
@@ -22,7 +22,6 @@
"express": "^5.2.1", "express": "^5.2.1",
"ffmpeg-static": "^5.3.0", "ffmpeg-static": "^5.3.0",
"jsonwebtoken": "^9.0.3", "jsonwebtoken": "^9.0.3",
"node-firebird": "^2.3.1", "node-firebird": "^2.3.1"
"pg": "^8.21.0"
} }
} }
+74
View File
@@ -0,0 +1,74 @@
/**
* Script para corrigir registros existentes com SITUACAO = NULL
*
* Uso: node scripts/corrigir-situacao.js <alias> [schema]
* Ex: node scripts/corrigir-situacao.js novo
* Ex: node scripts/corrigir-situacao.js novo at
*/
require('dotenv').config({ path: require('path').resolve(__dirname, '../.env') });
const db = require('../src/database');
async function main() {
const alias = process.argv[2] || 'novo';
const schema = process.argv[3] ? process.argv[3].trim() : '';
const dot = schema ? '.' : '';
console.log(`📌 Corrigindo registros no alias "${alias}"${schema ? ', schema "' + schema + '"' : ''}...\n`);
// 1. Corrigir CHATC2_INSTANCIAS sem INS_SITUACAO
const sql1 = `UPDATE ${schema}${dot}"CHATC2_INSTANCIAS" SET "INS_SITUACAO" = 'A' WHERE "INS_SITUACAO" IS NULL OR "INS_SITUACAO" = ''`;
console.log(`📌 ${sql1}`);
try {
const r1 = await db.execute(alias, sql1);
console.log(`✅ CHATC2_INSTANCIAS: ${r1.affectedRows} registro(s) atualizado(s)`);
} catch (e) {
console.log(`⚠️ CHATC2_INSTANCIAS: ${e.message.substring(0, 100)}`);
}
// 2. Corrigir CHATC2_CONVERSAS sem CON_SITUACAO
const sql2 = `UPDATE ${schema}${dot}"CHATC2_CONVERSAS" SET "CON_SITUACAO" = 'A' WHERE "CON_SITUACAO" IS NULL OR "CON_SITUACAO" = ''`;
console.log(`📌 ${sql2}`);
try {
const r2 = await db.execute(alias, sql2);
console.log(`✅ CHATC2_CONVERSAS: ${r2.affectedRows} registro(s) atualizado(s)`);
} catch (e) {
console.log(`⚠️ CHATC2_CONVERSAS: ${e.message.substring(0, 100)}`);
}
// 3. Corrigir CHATC2_CONVERSAS_MENSAGENS sem CME_SITUACAO
const sql3 = `UPDATE ${schema}${dot}"CHATC2_CONVERSAS_MENSAGENS" SET "CME_SITUACAO" = 'A' WHERE "CME_SITUACAO" IS NULL OR "CME_SITUACAO" = ''`;
console.log(`📌 ${sql3}`);
try {
const r3 = await db.execute(alias, sql3);
console.log(`✅ CHATC2_CONVERSAS_MENSAGENS: ${r3.affectedRows} registro(s) atualizado(s)`);
} catch (e) {
console.log(`⚠️ CHATC2_CONVERSAS_MENSAGENS: ${e.message.substring(0, 100)}`);
}
// 4. Verifica conversa 61111 especificamente
const sql4 = `SELECT "CON_CODIGO_ID", "CON_SITUACAO", "CON_STATUS" FROM ${schema}${dot}"CHATC2_CONVERSAS" WHERE "CON_CODIGO_ID" = '61111'`;
console.log(`📌 ${sql4}`);
try {
const conv = await db.query(alias, sql4);
if (conv.length > 0) {
console.log(`\n📋 Conversa 61111: SITUACAO=${conv[0].CON_SITUACAO || 'NULL'}, STATUS=${conv[0].CON_STATUS}`);
// Conta mensagens
const sql5 = `SELECT COUNT(*) AS CT FROM ${schema}${dot}"CHATC2_CONVERSAS_MENSAGENS" WHERE "CME_CONVERSA_ID" = '61111' AND "CME_SITUACAO" = 'A'`;
console.log(`📌 ${sql5}`);
const msgs = await db.query(alias, sql5);
console.log(`📋 Mensagens ativas na conversa 61111: ${msgs[0]?.CT || 0}`);
} else {
console.log(`\n❌ Conversa 61111 não encontrada`);
}
} catch (e) {
console.log(`⚠️ Verificação 61111: ${e.message.substring(0, 100)}`);
}
console.log(`\n✅ Correção concluída!`);
process.exit(0);
}
main().catch(err => {
console.error(`❌ Erro: ${err.message}`);
process.exit(1);
});
+3 -5
View File
@@ -22,9 +22,8 @@
const db = require('../src/database'); const db = require('../src/database');
// Este script aplica DDL no dialeto Firebird (BLOB SUB_TYPE, etc.). // Este script aplica DDL no dialeto Firebird (BLOB SUB_TYPE, etc.).
// O schema do PostgreSQL é gerenciado externamente (banco externo) — use este // Alias padrão: novo_local.
// script apenas para bancos Firebird. Alias padrão: firebird_local. const alias = process.argv[2] || 'novo_local';
const alias = process.argv[2] || 'firebird_local';
// ============================================================ // ============================================================
// CONTROLE DE MIGRAÇÕES // CONTROLE DE MIGRAÇÕES
@@ -428,8 +427,7 @@ async function main() {
catch (e) { console.error('❌', e.message); process.exit(1); } catch (e) { console.error('❌', e.message); process.exit(1); }
if (driver !== 'firebird') { if (driver !== 'firebird') {
console.log(`⚠️ O alias "${alias}" usa o driver "${driver}". Este script aplica DDL Firebird.`); console.log(`⚠️ O alias "${alias}" usa o driver "${driver}". Este script aplica DDL Firebird.`);
console.log(' O schema do PostgreSQL é gerenciado no banco externo — nada a fazer aqui.'); console.log(' Para migrar um banco Firebird: node scripts/migracoes.js novo_local\n');
console.log(' Para migrar um banco Firebird: node scripts/migracoes.js firebird_local\n');
process.exit(0); process.exit(0);
} }
+83
View File
@@ -0,0 +1,83 @@
#!/usr/bin/env node
/**
* postinstall.js - Aplica patches de compatibilidade no node-firebird
* após npm install, garantindo compatibilidade com Node.js 22+.
*/
const fs = require('fs');
const path = require('path');
const connPath = path.resolve(__dirname, '../node_modules/node-firebird/lib/wire/connection.js');
if (!fs.existsSync(connPath)) {
console.log('[patch] connection.js not found, skipping');
process.exit(0);
}
let content = fs.readFileSync(connPath, 'utf8');
let patched = false;
// ============================================================
// Patch 1: SRP empty buffer (Firebird 3.0 Legacy_Auth)
// ============================================================
const srpOld = (
' // TODO : Fallback Srp256 to Srp ?\n' +
' /*if (!d.buffer) {\n' +
' cnx.sendOpContAuth(\n' +
' cnx.clientKeys.public.toString(16),\n' +
' DEFAULT_ENCODING,\n' +
' accept.pluginName\n' +
' );\n' +
'\n' +
' return cb(new Error(\'login\'));\n' +
' }*/\n' +
'\n' +
' // Check buffer contains salt\n' +
' var saltLen = d.buffer.readUInt16LE(0);'
);
const srpNew = (
' // No auth data from server - server accepted the connection\n' +
' // without requiring SRP. This happens with Firebird 3.0 when\n' +
' // the server already validated the client.\n' +
' if (!d || !d.buffer) {\n' +
" accept.authData = '';\n" +
" accept.sessionKey = '';\n" +
' } else {\n' +
' // Check buffer contains salt\n' +
' var saltLen = d.buffer.readUInt16LE(0);'
);
if (content.includes(srpOld)) {
content = content.replace(srpOld, srpNew);
patched = true;
console.log('[patch] Patch 1 (SRP) applied');
}
// ============================================================
// Patch 2: Missing closing brace (Node.js 22+ syntax)
// ============================================================
const syntaxOld = (
' accept.authData = proof.authData.toString(16);\n' +
' accept.sessionKey = proof.clientSessionKey;\n' +
' } else if (accept.pluginName === Const.AUTH_PLUGIN_LEGACY) {'
);
const syntaxNew = (
' accept.authData = proof.authData.toString(16);\n' +
' accept.sessionKey = proof.clientSessionKey;\n' +
' } // fecha o else do if (!d || !d.buffer)\n' +
' } else if (accept.pluginName === Const.AUTH_PLUGIN_LEGACY) {'
);
if (content.includes(syntaxOld)) {
content = content.replace(syntaxOld, syntaxNew);
patched = true;
console.log('[patch] Patch 2 (syntax Node22) applied');
}
if (patched) {
fs.writeFileSync(connPath, content, 'utf8');
console.log('[patch] node-firebird patched successfully');
} else {
console.log('[patch] node-firebird already patched or not needed');
}
+2 -2
View File
@@ -62,9 +62,9 @@ app.use(routes);
// Rota raiz // Rota raiz
app.get('/', (req, res) => { app.get('/', (req, res) => {
res.json({ res.json({
name: 'API Firebird - Chatc2', name: 'API Chatc2 - Firebird',
version: '2.0.0', version: '2.0.0',
description: 'API multi-banco com sistema de aliases', description: 'API Firebird com sistema de aliases',
auth: { auth: {
loginPage: 'GET /app/:alias/login', loginPage: 'GET /app/:alias/login',
login: 'POST /app/:alias/login', login: 'POST /app/:alias/login',
+1 -1
View File
@@ -75,7 +75,7 @@ class AuthController {
FROM USUARIOS FROM USUARIOS
WHERE USU_LOGIN = ? WHERE USU_LOGIN = ?
AND (USU_SENHA = ? OR USU_SENHA_WEB = ?) AND (USU_SENHA = ? OR USU_SENHA_WEB = ?)
AND COALESCE(USU_ACESSO_WEB, 0) = 1 AND COALESCE(USU_ACESSO_WEB, '0') IN ('1', 'S')
AND USU_STATUS = 'A' AND USU_STATUS = 'A'
`; `;
+19 -19
View File
@@ -148,7 +148,7 @@ class ChatController {
const statusFilter = req.query.status || 'A,E'; const statusFilter = req.query.status || 'A,E';
const filter = req.query.filter || ''; // 'mine', 'unassigned', 'all' const filter = req.query.filter || ''; // 'mine', 'unassigned', 'all'
if (!req.user?.empresas?.includes(empresaId)) if (!empresaId || !req.user?.empresas?.some(function(e) { return Number(e) === Number(empresaId); }))
return res.status(403).json({ success: false, error: 'Sem permissão.' }); return res.status(403).json({ success: false, error: 'Sem permissão.' });
// Status via parâmetros (evita SQL injection). Aceita apenas A/E/F. // Status via parâmetros (evita SQL injection). Aceita apenas A/E/F.
@@ -260,7 +260,7 @@ class ChatController {
var minhasEquipes = await db.query(alias, var minhasEquipes = await db.query(alias,
'SELECT EQU_EQUIPE_ID FROM CHATC2_USU_EQUIPES WHERE EQU_USUARIO_ID = ?', [userId]); 'SELECT EQU_EQUIPE_ID FROM CHATC2_USU_EQUIPES WHERE EQU_USUARIO_ID = ?', [userId]);
if (minhasEquipes.length > 0) { if (minhasEquipes.length > 0) {
var eqIds = minhasEquipes.map(function(e) { return e.EQU_EQUIPE_ID; }).join(','); var eqIds = minhasEquipes.map(function(e) { return "'" + e.EQU_EQUIPE_ID + "'"; }).join(',');
var countEq = await db.query(alias, var countEq = await db.query(alias,
`SELECT COUNT(*) AS CT FROM CHATC2_CONVERSAS WHERE CON_EMPRESA_ID = ? AND CON_STATUS IN ('A','E') AND CON_SITUACAO = 'A' AND CON_EQUIPE_ID IN (${eqIds})`, `SELECT COUNT(*) AS CT FROM CHATC2_CONVERSAS WHERE CON_EMPRESA_ID = ? AND CON_STATUS IN ('A','E') AND CON_SITUACAO = 'A' AND CON_EQUIPE_ID IN (${eqIds})`,
[empresaId]); [empresaId]);
@@ -296,7 +296,7 @@ class ChatController {
const row = result[0]; const row = result[0];
const empresaId = row.CON_EMPRESA_ID; const empresaId = row.CON_EMPRESA_ID;
if (!req.user?.empresas?.includes(empresaId)) if (empresaId && !req.user?.empresas?.some(function(e) { return String(e) === String(empresaId); }))
return res.status(403).json({ success: false, error: 'Sem permissão.' }); return res.status(403).json({ success: false, error: 'Sem permissão.' });
// Verifica se o número pertence a um dependente // Verifica se o número pertence a um dependente
@@ -471,7 +471,7 @@ class ChatController {
if (r.length === 0) return { status: 404 }; if (r.length === 0) return { status: 404 };
const emp = r[0].CON_EMPRESA_ID; const emp = r[0].CON_EMPRESA_ID;
const minhas = (req.user && req.user.empresas) || []; const minhas = (req.user && req.user.empresas) || [];
if (emp && minhas.indexOf(emp) === -1) return { status: 403 }; if (emp && !minhas.some(function(e) { return String(e) === String(emp); })) return { status: 403 };
return { ok: true, empresaId: emp }; return { ok: true, empresaId: emp };
} }
@@ -542,7 +542,7 @@ class ChatController {
const conv = await db.query(alias, 'SELECT * FROM CHATC2_CONVERSAS WHERE CON_CODIGO_ID = ?', [id]); const conv = await db.query(alias, 'SELECT * FROM CHATC2_CONVERSAS WHERE CON_CODIGO_ID = ?', [id]);
if (conv.length === 0) return res.status(404).json({ success: false, error: 'Conversa não encontrada.' }); if (conv.length === 0) return res.status(404).json({ success: false, error: 'Conversa não encontrada.' });
if (conv[0].CON_EMPRESA_ID && !((req.user && req.user.empresas) || []).includes(conv[0].CON_EMPRESA_ID)) if (conv[0].CON_EMPRESA_ID && !((req.user && req.user.empresas) || []).some(function(e) { return String(e) === String(conv[0].CON_EMPRESA_ID); }))
return res.status(403).json({ success: false, error: 'Sem permissão.' }); return res.status(403).json({ success: false, error: 'Sem permissão.' });
// Se a conversa estiver finalizada, reabre // Se a conversa estiver finalizada, reabre
@@ -620,8 +620,8 @@ class ChatController {
// Insere mensagem no banco // Insere mensagem no banco
await db.execute(alias, ` await db.execute(alias, `
INSERT INTO CHATC2_CONVERSAS_MENSAGENS (CME_CODIGO_ID, CME_CONVERSA_ID, CME_REMETENTE, CME_USUARIO_ID, CME_TEXTO, CME_TIPO, CME_PRIVADA, CME_DT_ENVIO, CME_MIDIA_ID) INSERT INTO CHATC2_CONVERSAS_MENSAGENS (CME_CODIGO_ID, CME_CONVERSA_ID, CME_REMETENTE, CME_USUARIO_ID, CME_TEXTO, CME_TIPO, CME_PRIVADA, CME_SITUACAO, CME_DT_ENVIO, CME_MIDIA_ID)
VALUES (?, ?, 'U', ?, ?, ?, ?, CURRENT_TIMESTAMP, ?) VALUES (?, ?, 'U', ?, ?, ?, ?, 'A', CURRENT_TIMESTAMP, ?)
`, [newId, id, usuarioId, textoFinal, tipo || 'text', privada === 'S' ? 'S' : 'N', midiaId]); `, [newId, id, usuarioId, textoFinal, tipo || 'text', privada === 'S' ? 'S' : 'N', midiaId]);
// Atualiza data da última mensagem // Atualiza data da última mensagem
@@ -663,7 +663,7 @@ class ChatController {
if (conv.length === 0) return res.status(404).json({ success: false, error: 'Conversa não encontrada.' }); if (conv.length === 0) return res.status(404).json({ success: false, error: 'Conversa não encontrada.' });
const c = conv[0]; const c = conv[0];
if (c.CON_EMPRESA_ID && !((req.user && req.user.empresas) || []).includes(c.CON_EMPRESA_ID)) if (c.CON_EMPRESA_ID && !((req.user && req.user.empresas) || []).some(function(e) { return String(e) === String(c.CON_EMPRESA_ID); }))
return res.status(403).json({ success: false, error: 'Sem permissão.' }); return res.status(403).json({ success: false, error: 'Sem permissão.' });
// ===== Fluxo de Resolução: valida motivo/resolução conforme config ===== // ===== Fluxo de Resolução: valida motivo/resolução conforme config =====
@@ -781,7 +781,7 @@ class ChatController {
// Verifica permissão // Verifica permissão
const userEmpresas = req.user?.empresas || []; const userEmpresas = req.user?.empresas || [];
if (!userEmpresas.includes(parseInt(empresaId))) { if (!userEmpresas.some(function(e) { return String(e) === String(empresaId); })) {
return res.status(403).json({ success: false, error: 'Sem permissão para esta empresa.' }); return res.status(403).json({ success: false, error: 'Sem permissão para esta empresa.' });
} }
@@ -827,8 +827,8 @@ class ChatController {
const maxMsgId = await db.query(alias, 'SELECT MAX(CME_CODIGO_ID) AS ID FROM CHATC2_CONVERSAS_MENSAGENS'); const maxMsgId = await db.query(alias, 'SELECT MAX(CME_CODIGO_ID) AS ID FROM CHATC2_CONVERSAS_MENSAGENS');
const msgId = (maxMsgId[0]?.ID || 0) + 1; const msgId = (maxMsgId[0]?.ID || 0) + 1;
await db.execute(alias, ` await db.execute(alias, `
INSERT INTO CHATC2_CONVERSAS_MENSAGENS (CME_CODIGO_ID, CME_CONVERSA_ID, CME_REMETENTE, CME_USUARIO_ID, CME_TEXTO, CME_TIPO, CME_DT_ENVIO) INSERT INTO CHATC2_CONVERSAS_MENSAGENS (CME_CODIGO_ID, CME_CONVERSA_ID, CME_REMETENTE, CME_USUARIO_ID, CME_TEXTO, CME_TIPO, CME_SITUACAO, CME_DT_ENVIO)
VALUES (?, ?, 'U', ?, ?, 'text', CURRENT_TIMESTAMP) VALUES (?, ?, 'U', ?, ?, 'text', 'A', CURRENT_TIMESTAMP)
`, [msgId, conversaId, req.user?.id, mensagem]); `, [msgId, conversaId, req.user?.id, mensagem]);
await db.execute(alias, await db.execute(alias,
@@ -860,8 +860,8 @@ class ChatController {
// Insere conversa // Insere conversa
await db.execute(alias, ` await db.execute(alias, `
INSERT INTO CHATC2_CONVERSAS (CON_CODIGO_ID, CON_EMPRESA_ID, CON_INSTANCIA_ID, CON_NUMERO, INSERT INTO CHATC2_CONVERSAS (CON_CODIGO_ID, CON_EMPRESA_ID, CON_INSTANCIA_ID, CON_NUMERO,
CON_NOME_CONTATO, CON_CLIENTE_ID, CON_STATUS, CON_PRIMEIRA_MSG, CON_DT_ULTIMA_MSG) CON_NOME_CONTATO, CON_CLIENTE_ID, CON_STATUS, CON_SITUACAO, CON_PRIMEIRA_MSG, CON_DT_ULTIMA_MSG)
VALUES (?, ?, ?, ?, ?, ?, 'A', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) VALUES (?, ?, ?, ?, ?, ?, 'A', 'A', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
`, [newId, empresaId, instId, numeroLimpo, nomeContato || numeroLimpo, clienteId || null]); `, [newId, empresaId, instId, numeroLimpo, nomeContato || numeroLimpo, clienteId || null]);
// Insere primeira mensagem // Insere primeira mensagem
@@ -869,8 +869,8 @@ class ChatController {
const msgId = (maxMsgId[0]?.ID || 0) + 1; const msgId = (maxMsgId[0]?.ID || 0) + 1;
await db.execute(alias, ` await db.execute(alias, `
INSERT INTO CHATC2_CONVERSAS_MENSAGENS (CME_CODIGO_ID, CME_CONVERSA_ID, CME_REMETENTE, CME_USUARIO_ID, CME_TEXTO, CME_TIPO, CME_DT_ENVIO) INSERT INTO CHATC2_CONVERSAS_MENSAGENS (CME_CODIGO_ID, CME_CONVERSA_ID, CME_REMETENTE, CME_USUARIO_ID, CME_TEXTO, CME_TIPO, CME_SITUACAO, CME_DT_ENVIO)
VALUES (?, ?, 'U', ?, ?, 'text', CURRENT_TIMESTAMP) VALUES (?, ?, 'U', ?, ?, 'text', 'A', CURRENT_TIMESTAMP)
`, [msgId, newId, req.user?.id, mensagem]); `, [msgId, newId, req.user?.id, mensagem]);
// Envia via Evolution API // Envia via Evolution API
@@ -1058,7 +1058,7 @@ class ChatController {
mimeType = mimeType.split(';')[0].trim(); mimeType = mimeType.split(';')[0].trim();
const nomeArquivo = (m.MAT_NOME_ARQUIVO || '').trim() || 'arquivo.bin'; const nomeArquivo = (m.MAT_NOME_ARQUIVO || '').trim() || 'arquivo.bin';
// MAT_ARQUIVO chega como Buffer (postgres bytea / firebird blob) ou string // MAT_ARQUIVO chega como Buffer (firebird blob) ou string
let rawBuffer = m.MAT_ARQUIVO; let rawBuffer = m.MAT_ARQUIVO;
if (rawBuffer == null) { if (rawBuffer == null) {
return respond(404, { success: false, error: 'Mídia sem conteúdo.' }); return respond(404, { success: false, error: 'Mídia sem conteúdo.' });
@@ -1179,7 +1179,7 @@ class ChatController {
const cli = await db.query(alias, const cli = await db.query(alias,
'SELECT CLI_EMPRESA_ID FROM CLIENTES WHERE CLI_CODIGO_ID = ?', [cid]); 'SELECT CLI_EMPRESA_ID FROM CLIENTES WHERE CLI_CODIGO_ID = ?', [cid]);
if (cli.length === 0) return { status: 404 }; if (cli.length === 0) return { status: 404 };
if (chk.empresaId && cli[0].CLI_EMPRESA_ID && cli[0].CLI_EMPRESA_ID !== chk.empresaId) if (chk.empresaId && cli[0].CLI_EMPRESA_ID && String(cli[0].CLI_EMPRESA_ID) !== String(chk.empresaId))
return { status: 403 }; return { status: 403 };
return { ok: true, clienteId: cid }; return { ok: true, clienteId: cid };
} }
@@ -1282,8 +1282,8 @@ class ChatController {
const maxId = await db.query(alias, 'SELECT MAX(CME_CODIGO_ID) AS ID FROM CHATC2_CONVERSAS_MENSAGENS'); const maxId = await db.query(alias, 'SELECT MAX(CME_CODIGO_ID) AS ID FROM CHATC2_CONVERSAS_MENSAGENS');
const newId = (maxId[0]?.ID || 0) + 1; const newId = (maxId[0]?.ID || 0) + 1;
await db.execute(alias, ` await db.execute(alias, `
INSERT INTO CHATC2_CONVERSAS_MENSAGENS (CME_CODIGO_ID, CME_CONVERSA_ID, CME_REMETENTE, CME_USUARIO_ID, CME_TEXTO, CME_TIPO, CME_PRIVADA, CME_DT_ENVIO) INSERT INTO CHATC2_CONVERSAS_MENSAGENS (CME_CODIGO_ID, CME_CONVERSA_ID, CME_REMETENTE, CME_USUARIO_ID, CME_TEXTO, CME_TIPO, CME_PRIVADA, CME_SITUACAO, CME_DT_ENVIO)
VALUES (?, ?, 'U', ?, ?, 'text', 'N', CURRENT_TIMESTAMP) VALUES (?, ?, 'U', ?, ?, 'text', 'N', 'A', CURRENT_TIMESTAMP)
`, [newId, id, req.user?.id, texto]); `, [newId, id, req.user?.id, texto]);
await db.execute(alias, await db.execute(alias,
'UPDATE CHATC2_CONVERSAS SET CON_DT_ULTIMA_MSG = CURRENT_TIMESTAMP WHERE CON_CODIGO_ID = ?', [id]); 'UPDATE CHATC2_CONVERSAS SET CON_DT_ULTIMA_MSG = CURRENT_TIMESTAMP WHERE CON_CODIGO_ID = ?', [id]);
+20 -50
View File
@@ -89,59 +89,29 @@ class DatabaseController {
return res.status(400).json({ success: false, error: 'database é obrigatório.' }); return res.status(400).json({ success: false, error: 'database é obrigatório.' });
} }
var driver = (req.body.driver || DEFAULT_DRIVER).toLowerCase(); var Firebird = require('node-firebird');
var config = {
if (driver === 'postgres') { host: req.body.host || 'localhost',
var pg = require('pg'); port: req.body.port || 3050,
var schema = String(req.body.schema || 'public').trim(); database: req.body.database,
var safe = /^[A-Za-z_][A-Za-z0-9_$]*$/.test(schema) ? schema : 'public'; user: req.body.user || 'SYSDBA',
var searchPath = safe === 'public' ? 'public' : safe + ',public'; password: req.body.password || 'masterkey',
var client = new pg.Client({ encoding: 'UTF-8',
host: req.body.host || '127.0.0.1', lowercase_keys: false,
port: req.body.port || 5432, pageSize: 4096,
database: req.body.database, };
user: req.body.user || 'postgres', await new Promise(function (resolve, reject) {
password: req.body.password || 'postgres', Firebird.attach(config, function (err, conn) {
ssl: req.body.ssl ? { rejectUnauthorized: false } : false, if (err) return reject(new Error('Falha ao conectar: ' + err.message));
options: '-c search_path=' + searchPath, conn.query('SELECT 1 AS TEST FROM RDB$DATABASE', [], function (qErr, rows) {
connectionTimeoutMillis: 8000, conn.detach();
}); if (qErr) return reject(new Error('Falha na consulta: ' + qErr.message));
try { resolve(rows);
await client.connect();
await client.query('SELECT 1');
// Confirma que o schema existe
var sc = await client.query('SELECT 1 FROM information_schema.schemata WHERE schema_name = $1', [safe]);
if (sc.rowCount === 0) {
throw new Error('Schema "' + safe + '" não encontrado no banco "' + req.body.database + '".');
}
} finally {
await client.end().catch(function () {});
}
} else {
var Firebird = require('node-firebird');
var config = {
host: req.body.host || 'localhost',
port: req.body.port || 3050,
database: req.body.database,
user: req.body.user || 'SYSDBA',
password: req.body.password || 'masterkey',
encoding: 'UTF-8',
lowercase_keys: false,
pageSize: 4096,
};
await new Promise(function (resolve, reject) {
Firebird.attach(config, function (err, conn) {
if (err) return reject(new Error('Falha ao conectar: ' + err.message));
conn.query('SELECT 1 AS TEST FROM RDB$DATABASE', [], function (qErr, rows) {
conn.detach();
if (qErr) return reject(new Error('Falha na consulta: ' + qErr.message));
resolve(rows);
});
}); });
}); });
} });
res.json({ success: true, message: '✅ Conexão (' + driver + ') estabelecida com sucesso!' }); res.json({ success: true, message: '✅ Conexão (firebird) estabelecida com sucesso!' });
} catch (err) { } catch (err) {
res.status(500).json({ success: false, error: err.message }); res.status(500).json({ success: false, error: err.message });
} }
+1 -1
View File
@@ -38,7 +38,7 @@ class EmpresaController {
} catch (fe) { /* ignora e tenta o BLOB */ } } catch (fe) { /* ignora e tenta o BLOB */ }
} }
// 2. BLOB EMP_FOTO (Buffer no postgres/firebird; string base64 em alguns casos) // 2. BLOB EMP_FOTO (Buffer no firebird; string base64 em alguns casos)
const blobData = e.EMP_FOTO; const blobData = e.EMP_FOTO;
let base64; let base64;
if (Buffer.isBuffer(blobData)) { if (Buffer.isBuffer(blobData)) {
+28 -15
View File
@@ -242,18 +242,18 @@ class EvolutionController {
}); });
instanceCreated = true; instanceCreated = true;
await db.execute(alias, ` await db.execute(alias, `
INSERT INTO CHATC2_INSTANCIAS (INS_CODIGO_ID, INS_EMPRESA_ID, INS_NOME, INS_URL, INS_API_KEY, INS_INSTANCE_NAME, INS_STATUS) INSERT INTO CHATC2_INSTANCIAS (INS_CODIGO_ID, INS_EMPRESA_ID, INS_NOME, INS_URL, INS_API_KEY, INS_INSTANCE_NAME, INS_STATUS, INS_SITUACAO)
VALUES (?, ?, ?, ?, ?, ?, 'C') VALUES (?, ?, ?, ?, ?, ?, 'C', 'A')
`, [newId, INS_EMPRESA_ID || userEmpresas[0], INS_NOME, INS_URL, INS_API_KEY, INS_INSTANCE_NAME]); `, [newId, INS_EMPRESA_ID || userEmpresas[0], INS_NOME, INS_URL, INS_API_KEY, INS_INSTANCE_NAME]);
break; break;
} catch (e) { /* Tenta próximo endpoint */ } } catch (e) { /* Tenta próximo endpoint */ }
} }
if (!instanceCreated) { if (!instanceCreated) {
// Salva mesmo sem conseguir criar na Evolution // Salva mesmo sem conseguir criar na Evolution (status 'D' = desconectado)
await db.execute(alias, ` await db.execute(alias, `
INSERT INTO CHATC2_INSTANCIAS (INS_CODIGO_ID, INS_EMPRESA_ID, INS_NOME, INS_URL, INS_API_KEY, INS_INSTANCE_NAME, INS_STATUS) INSERT INTO CHATC2_INSTANCIAS (INS_CODIGO_ID, INS_EMPRESA_ID, INS_NOME, INS_URL, INS_API_KEY, INS_INSTANCE_NAME, INS_STATUS, INS_SITUACAO)
VALUES (?, ?, ?, ?, ?, ?, 'D') VALUES (?, ?, ?, ?, ?, ?, 'D', 'A')
`, [newId, INS_EMPRESA_ID || userEmpresas[0], INS_NOME, INS_URL, INS_API_KEY, INS_INSTANCE_NAME]); `, [newId, INS_EMPRESA_ID || userEmpresas[0], INS_NOME, INS_URL, INS_API_KEY, INS_INSTANCE_NAME]);
} }
@@ -270,8 +270,21 @@ class EvolutionController {
static async listInstances(req, res) { static async listInstances(req, res) {
try { try {
const { alias } = req.params; const { alias } = req.params;
const empresaId = parseInt(req.query.empresaId) || req.user?.empresas?.[0];
if (!req.user?.empresas?.includes(empresaId)) return res.status(403).json({ success: false, error: 'Sem permissão.' }); // Garante que userEmpresas está carregado (fallback para DB se JWT não tiver)
let userEmpresas = req.user?.empresas || [];
if (userEmpresas.length === 0 && req.user?.id) {
try {
const empresasDb = await db.query(alias,
'SELECT USE_EMPRESA_ID FROM USUARIOS_EMPRESA WHERE USE_USUARIO_ID = ?',
[req.user.id]
);
userEmpresas = empresasDb.map(e => e.USE_EMPRESA_ID);
} catch (e) { /* ignora falha */ }
}
const empresaId = parseInt(req.query.empresaId) || userEmpresas[0];
if (!userEmpresas.includes(empresaId)) return res.status(403).json({ success: false, error: 'Sem permissão.' });
const result = await db.query(alias, const result = await db.query(alias,
'SELECT * FROM CHATC2_INSTANCIAS WHERE INS_EMPRESA_ID = ? AND INS_SITUACAO = \'A\' ORDER BY INS_NOME', 'SELECT * FROM CHATC2_INSTANCIAS WHERE INS_EMPRESA_ID = ? AND INS_SITUACAO = \'A\' ORDER BY INS_NOME',
@@ -772,8 +785,8 @@ async function processWebhook(alias, body) {
await db.execute(alias, ` await db.execute(alias, `
INSERT INTO CHATC2_CONVERSAS (CON_CODIGO_ID, CON_EMPRESA_ID, CON_INSTANCIA_ID, CON_NUMERO, INSERT INTO CHATC2_CONVERSAS (CON_CODIGO_ID, CON_EMPRESA_ID, CON_INSTANCIA_ID, CON_NUMERO,
CON_NOME_CONTATO, CON_CLIENTE_ID, CON_STATUS, CON_PRIMEIRA_MSG, CON_DT_ULTIMA_MSG) CON_NOME_CONTATO, CON_CLIENTE_ID, CON_STATUS, CON_SITUACAO, CON_PRIMEIRA_MSG, CON_DT_ULTIMA_MSG)
VALUES (?, ?, ?, ?, ?, ?, 'E', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) VALUES (?, ?, ?, ?, ?, ?, 'E', 'A', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
`, [newId, empresaId, instanciaId, numero, nomeFinal, clienteId]); `, [newId, empresaId, instanciaId, numero, nomeFinal, clienteId]);
conversa = await db.query(alias, 'SELECT * FROM CHATC2_CONVERSAS WHERE CON_CODIGO_ID = ?', [newId]); conversa = await db.query(alias, 'SELECT * FROM CHATC2_CONVERSAS WHERE CON_CODIGO_ID = ?', [newId]);
@@ -789,8 +802,8 @@ async function processWebhook(alias, body) {
if (msgSaudacao) { if (msgSaudacao) {
const maxMsgId = await db.query(alias, 'SELECT MAX(CME_CODIGO_ID) AS ID FROM CHATC2_CONVERSAS_MENSAGENS'); const maxMsgId = await db.query(alias, 'SELECT MAX(CME_CODIGO_ID) AS ID FROM CHATC2_CONVERSAS_MENSAGENS');
await db.execute(alias, ` await db.execute(alias, `
INSERT INTO CHATC2_CONVERSAS_MENSAGENS (CME_CODIGO_ID, CME_CONVERSA_ID, CME_REMETENTE, CME_TEXTO, CME_TIPO, CME_DT_ENVIO) INSERT INTO CHATC2_CONVERSAS_MENSAGENS (CME_CODIGO_ID, CME_CONVERSA_ID, CME_REMETENTE, CME_TEXTO, CME_TIPO, CME_SITUACAO, CME_DT_ENVIO)
VALUES (?, ?, 'S', ?, 'text', CURRENT_TIMESTAMP) VALUES (?, ?, 'S', ?, 'text', 'A', CURRENT_TIMESTAMP)
`, [(maxMsgId[0]?.ID || 0) + 1, conversa.CON_CODIGO_ID, msgSaudacao]); `, [(maxMsgId[0]?.ID || 0) + 1, conversa.CON_CODIGO_ID, msgSaudacao]);
await db.execute(alias, "UPDATE CHATC2_CONVERSAS SET CON_SAUDACAO_ENVIADA = 'S' WHERE CON_CODIGO_ID = ?", [conversa.CON_CODIGO_ID]); await db.execute(alias, "UPDATE CHATC2_CONVERSAS SET CON_SAUDACAO_ENVIADA = 'S' WHERE CON_CODIGO_ID = ?", [conversa.CON_CODIGO_ID]);
@@ -1205,13 +1218,13 @@ async function processWebhook(alias, body) {
if (midiaId) { if (midiaId) {
await db.execute(alias, ` await db.execute(alias, `
INSERT INTO CHATC2_CONVERSAS_MENSAGENS (CME_CODIGO_ID, CME_CONVERSA_ID, CME_REMETENTE, CME_TEXTO, CME_TIPO, CME_DT_ENVIO, CME_MIDIA_ID) INSERT INTO CHATC2_CONVERSAS_MENSAGENS (CME_CODIGO_ID, CME_CONVERSA_ID, CME_REMETENTE, CME_TEXTO, CME_TIPO, CME_SITUACAO, CME_DT_ENVIO, CME_MIDIA_ID)
VALUES (?, ?, 'C', ?, ?, CURRENT_TIMESTAMP, ?) VALUES (?, ?, 'C', ?, ?, 'A', CURRENT_TIMESTAMP, ?)
`, [(maxMsgId[0]?.ID || 0) + 1, conversa.CON_CODIGO_ID, messageText, messageType, midiaId]); `, [(maxMsgId[0]?.ID || 0) + 1, conversa.CON_CODIGO_ID, messageText, messageType, midiaId]);
} else { } else {
await db.execute(alias, ` await db.execute(alias, `
INSERT INTO CHATC2_CONVERSAS_MENSAGENS (CME_CODIGO_ID, CME_CONVERSA_ID, CME_REMETENTE, CME_TEXTO, CME_TIPO, CME_DT_ENVIO) INSERT INTO CHATC2_CONVERSAS_MENSAGENS (CME_CODIGO_ID, CME_CONVERSA_ID, CME_REMETENTE, CME_TEXTO, CME_TIPO, CME_SITUACAO, CME_DT_ENVIO)
VALUES (?, ?, 'C', ?, ?, CURRENT_TIMESTAMP) VALUES (?, ?, 'C', ?, ?, 'A', CURRENT_TIMESTAMP)
`, [(maxMsgId[0]?.ID || 0) + 1, conversa.CON_CODIGO_ID, messageText, messageType]); `, [(maxMsgId[0]?.ID || 0) + 1, conversa.CON_CODIGO_ID, messageText, messageType]);
} }
+3 -3
View File
@@ -145,14 +145,14 @@ class RoutesController {
'GET /api/databases': { desc: 'Lista as conexões de banco cadastradas (senhas ocultas).' }, 'GET /api/databases': { desc: 'Lista as conexões de banco cadastradas (senhas ocultas).' },
'POST /api/databases': { 'POST /api/databases': {
desc: 'Adiciona ou atualiza uma conexão de banco (persistida em databases_custom.json).', desc: 'Adiciona ou atualiza uma conexão de banco (persistida em databases_custom.json).',
body: { alias: 'novo_dev', config: { driver: 'postgres', host: '127.0.0.1', port: 15433, database: 'novo_local', schema: 'dev', user: 'postgres', password: 'postgres' } }, body: { alias: 'novo_dev', config: { driver: 'firebird', host: '127.0.0.1', port: 3050, database: '/opt/chatc2/db/NOVO.FDB', user: 'SYSDBA', password: 'masterkey' } },
req: ['alias', 'config'], req: ['alias', 'config'],
note: 'driver: "postgres" (usa schema) ou "firebird" (database = caminho do .FDB).', note: 'driver: "firebird" (database = caminho do .FDB).',
}, },
'DELETE /api/databases/{alias}': { desc: 'Remove uma conexão customizada (estáticas não podem ser removidas).' }, 'DELETE /api/databases/{alias}': { desc: 'Remove uma conexão customizada (estáticas não podem ser removidas).' },
'POST /api/databases/test': { 'POST /api/databases/test': {
desc: 'Testa uma conexão sem cadastrá-la.', desc: 'Testa uma conexão sem cadastrá-la.',
body: { driver: 'postgres', host: '127.0.0.1', port: 15433, database: 'novo_local', schema: 'public', user: 'postgres', password: 'postgres' }, body: { driver: 'firebird', host: '127.0.0.1', port: 3050, database: '/opt/chatc2/db/NOVO.FDB', user: 'SYSDBA', password: 'masterkey' },
req: ['database'], req: ['database'],
}, },
+32 -32
View File
@@ -72,8 +72,8 @@ class TriageController {
// Salva mensagem do sistema // Salva mensagem do sistema
const maxMsgId = await db.query(alias, 'SELECT MAX(CME_CODIGO_ID) AS ID FROM CHATC2_CONVERSAS_MENSAGENS'); const maxMsgId = await db.query(alias, 'SELECT MAX(CME_CODIGO_ID) AS ID FROM CHATC2_CONVERSAS_MENSAGENS');
await db.execute(alias, ` await db.execute(alias, `
INSERT INTO CHATC2_CONVERSAS_MENSAGENS (CME_CODIGO_ID, CME_CONVERSA_ID, CME_REMETENTE, CME_TEXTO, CME_TIPO, CME_DT_ENVIO) INSERT INTO CHATC2_CONVERSAS_MENSAGENS (CME_CODIGO_ID, CME_CONVERSA_ID, CME_REMETENTE, CME_TEXTO, CME_TIPO, CME_SITUACAO, CME_DT_ENVIO)
VALUES (?, ?, 'S', ?, 'triagem', CURRENT_TIMESTAMP) VALUES (?, ?, 'S', ?, 'triagem', 'A', CURRENT_TIMESTAMP)
`, [(maxMsgId[0]?.ID || 0) + 1, conversaId, menuText]); `, [(maxMsgId[0]?.ID || 0) + 1, conversaId, menuText]);
// Envia via Evolution // Envia via Evolution
@@ -121,8 +121,8 @@ class TriageController {
const maxMsgId = await db.query(alias, 'SELECT MAX(CME_CODIGO_ID) AS ID FROM CHATC2_CONVERSAS_MENSAGENS'); const maxMsgId = await db.query(alias, 'SELECT MAX(CME_CODIGO_ID) AS ID FROM CHATC2_CONVERSAS_MENSAGENS');
await db.execute(alias, ` await db.execute(alias, `
INSERT INTO CHATC2_CONVERSAS_MENSAGENS (CME_CODIGO_ID, CME_CONVERSA_ID, CME_REMETENTE, CME_TEXTO, CME_TIPO, CME_DT_ENVIO) INSERT INTO CHATC2_CONVERSAS_MENSAGENS (CME_CODIGO_ID, CME_CONVERSA_ID, CME_REMETENTE, CME_TEXTO, CME_TIPO, CME_SITUACAO, CME_DT_ENVIO)
VALUES (?, ?, 'S', ?, 'triagem', CURRENT_TIMESTAMP) VALUES (?, ?, 'S', ?, 'triagem', 'A', CURRENT_TIMESTAMP)
`, [(maxMsgId[0]?.ID || 0) + 1, conversaId, menuText]); `, [(maxMsgId[0]?.ID || 0) + 1, conversaId, menuText]);
await TriageController.sendEvolution(alias, instanciaId, numero, menuText); await TriageController.sendEvolution(alias, instanciaId, numero, menuText);
@@ -246,8 +246,8 @@ class TriageController {
if (msgAfter) { if (msgAfter) {
const maxMsgId = await db.query(alias, 'SELECT MAX(CME_CODIGO_ID) AS ID FROM CHATC2_CONVERSAS_MENSAGENS'); const maxMsgId = await db.query(alias, 'SELECT MAX(CME_CODIGO_ID) AS ID FROM CHATC2_CONVERSAS_MENSAGENS');
await db.execute(alias, ` await db.execute(alias, `
INSERT INTO CHATC2_CONVERSAS_MENSAGENS (CME_CODIGO_ID, CME_CONVERSA_ID, CME_REMETENTE, CME_TEXTO, CME_TIPO, CME_DT_ENVIO) INSERT INTO CHATC2_CONVERSAS_MENSAGENS (CME_CODIGO_ID, CME_CONVERSA_ID, CME_REMETENTE, CME_TEXTO, CME_TIPO, CME_SITUACAO, CME_DT_ENVIO)
VALUES (?, ?, 'S', ?, 'text', CURRENT_TIMESTAMP) VALUES (?, ?, 'S', ?, 'text', 'A', CURRENT_TIMESTAMP)
`, [(maxMsgId[0]?.ID || 0) + 1, conversaId, msgAfter]); `, [(maxMsgId[0]?.ID || 0) + 1, conversaId, msgAfter]);
await TriageController.sendEvolution(alias, instanciaId, numero, msgAfter); await TriageController.sendEvolution(alias, instanciaId, numero, msgAfter);
} }
@@ -377,8 +377,8 @@ class TriageController {
if (textoMsg) { if (textoMsg) {
const maxMsgId = await db.query(alias, 'SELECT MAX(CME_CODIGO_ID) AS ID FROM CHATC2_CONVERSAS_MENSAGENS'); const maxMsgId = await db.query(alias, 'SELECT MAX(CME_CODIGO_ID) AS ID FROM CHATC2_CONVERSAS_MENSAGENS');
await db.execute(alias, ` await db.execute(alias, `
INSERT INTO CHATC2_CONVERSAS_MENSAGENS (CME_CODIGO_ID, CME_CONVERSA_ID, CME_REMETENTE, CME_TEXTO, CME_TIPO, CME_DT_ENVIO) INSERT INTO CHATC2_CONVERSAS_MENSAGENS (CME_CODIGO_ID, CME_CONVERSA_ID, CME_REMETENTE, CME_TEXTO, CME_TIPO, CME_SITUACAO, CME_DT_ENVIO)
VALUES (?, ?, 'S', ?, 'text', CURRENT_TIMESTAMP) VALUES (?, ?, 'S', ?, 'text', 'A', CURRENT_TIMESTAMP)
`, [(maxMsgId[0]?.ID || 0) + 1, conversaId, textoMsg]); `, [(maxMsgId[0]?.ID || 0) + 1, conversaId, textoMsg]);
await TriageController.sendEvolution(alias, instanciaId, numero, textoMsg); await TriageController.sendEvolution(alias, instanciaId, numero, textoMsg);
} }
@@ -396,8 +396,8 @@ class TriageController {
const maxMsgId = await db.query(alias, 'SELECT MAX(CME_CODIGO_ID) AS ID FROM CHATC2_CONVERSAS_MENSAGENS'); const maxMsgId = await db.query(alias, 'SELECT MAX(CME_CODIGO_ID) AS ID FROM CHATC2_CONVERSAS_MENSAGENS');
await db.execute(alias, ` await db.execute(alias, `
INSERT INTO CHATC2_CONVERSAS_MENSAGENS (CME_CODIGO_ID, CME_CONVERSA_ID, CME_REMETENTE, CME_TEXTO, CME_TIPO, CME_DT_ENVIO) INSERT INTO CHATC2_CONVERSAS_MENSAGENS (CME_CODIGO_ID, CME_CONVERSA_ID, CME_REMETENTE, CME_TEXTO, CME_TIPO, CME_SITUACAO, CME_DT_ENVIO)
VALUES (?, ?, 'S', ?, 'text', CURRENT_TIMESTAMP) VALUES (?, ?, 'S', ?, 'text', 'A', CURRENT_TIMESTAMP)
`, [(maxMsgId[0]?.ID || 0) + 1, conversaId, msgPrompt]); `, [(maxMsgId[0]?.ID || 0) + 1, conversaId, msgPrompt]);
await TriageController.sendEvolution(alias, instanciaId, numero, msgPrompt); await TriageController.sendEvolution(alias, instanciaId, numero, msgPrompt);
@@ -521,8 +521,8 @@ class TriageController {
if (resultado) { if (resultado) {
const maxMsgId = await db.query(alias, 'SELECT MAX(CME_CODIGO_ID) AS ID FROM CHATC2_CONVERSAS_MENSAGENS'); const maxMsgId = await db.query(alias, 'SELECT MAX(CME_CODIGO_ID) AS ID FROM CHATC2_CONVERSAS_MENSAGENS');
await db.execute(alias, ` await db.execute(alias, `
INSERT INTO CHATC2_CONVERSAS_MENSAGENS (CME_CODIGO_ID, CME_CONVERSA_ID, CME_REMETENTE, CME_TEXTO, CME_TIPO, CME_DT_ENVIO) INSERT INTO CHATC2_CONVERSAS_MENSAGENS (CME_CODIGO_ID, CME_CONVERSA_ID, CME_REMETENTE, CME_TEXTO, CME_TIPO, CME_SITUACAO, CME_DT_ENVIO)
VALUES (?, ?, 'S', ?, 'text', CURRENT_TIMESTAMP) VALUES (?, ?, 'S', ?, 'text', 'A', CURRENT_TIMESTAMP)
`, [(maxMsgId[0]?.ID || 0) + 1, conversaId, resultado]); `, [(maxMsgId[0]?.ID || 0) + 1, conversaId, resultado]);
await TriageController.sendEvolution(alias, instanciaId, numero, resultado); await TriageController.sendEvolution(alias, instanciaId, numero, resultado);
} }
@@ -579,8 +579,8 @@ class TriageController {
var msgNaoEncontrado = 'Este número não está associado a nenhum contrato no sistema.\n\nPor favor, selecione um setor para que um atendente possa ajudá-lo a associar seu número.'; var msgNaoEncontrado = 'Este número não está associado a nenhum contrato no sistema.\n\nPor favor, selecione um setor para que um atendente possa ajudá-lo a associar seu número.';
const maxMsgId = await db.query(alias, 'SELECT MAX(CME_CODIGO_ID) AS ID FROM CHATC2_CONVERSAS_MENSAGENS'); const maxMsgId = await db.query(alias, 'SELECT MAX(CME_CODIGO_ID) AS ID FROM CHATC2_CONVERSAS_MENSAGENS');
await db.execute(alias, ` await db.execute(alias, `
INSERT INTO CHATC2_CONVERSAS_MENSAGENS (CME_CODIGO_ID, CME_CONVERSA_ID, CME_REMETENTE, CME_TEXTO, CME_TIPO, CME_DT_ENVIO) INSERT INTO CHATC2_CONVERSAS_MENSAGENS (CME_CODIGO_ID, CME_CONVERSA_ID, CME_REMETENTE, CME_TEXTO, CME_TIPO, CME_SITUACAO, CME_DT_ENVIO)
VALUES (?, ?, 'S', ?, 'text', CURRENT_TIMESTAMP) VALUES (?, ?, 'S', ?, 'text', 'A', CURRENT_TIMESTAMP)
`, [(maxMsgId[0]?.ID || 0) + 1, conversaId, msgNaoEncontrado]); `, [(maxMsgId[0]?.ID || 0) + 1, conversaId, msgNaoEncontrado]);
await TriageController.sendEvolution(alias, instanciaId, numero, msgNaoEncontrado); await TriageController.sendEvolution(alias, instanciaId, numero, msgNaoEncontrado);
await db.execute(alias, "UPDATE CHATC2_CONVERSAS SET CON_MENU_ESTADO = 'root' WHERE CON_CODIGO_ID = ?", [conversaId]); await db.execute(alias, "UPDATE CHATC2_CONVERSAS SET CON_MENU_ESTADO = 'root' WHERE CON_CODIGO_ID = ?", [conversaId]);
@@ -605,8 +605,8 @@ class TriageController {
logTriage('Boleto - NENHUM título em aberto'); logTriage('Boleto - NENHUM título em aberto');
const maxMsgId = await db.query(alias, 'SELECT MAX(CME_CODIGO_ID) AS ID FROM CHATC2_CONVERSAS_MENSAGENS'); const maxMsgId = await db.query(alias, 'SELECT MAX(CME_CODIGO_ID) AS ID FROM CHATC2_CONVERSAS_MENSAGENS');
await db.execute(alias, ` await db.execute(alias, `
INSERT INTO CHATC2_CONVERSAS_MENSAGENS (CME_CODIGO_ID, CME_CONVERSA_ID, CME_REMETENTE, CME_TEXTO, CME_TIPO, CME_DT_ENVIO) INSERT INTO CHATC2_CONVERSAS_MENSAGENS (CME_CODIGO_ID, CME_CONVERSA_ID, CME_REMETENTE, CME_TEXTO, CME_TIPO, CME_SITUACAO, CME_DT_ENVIO)
VALUES (?, ?, 'S', ?, 'text', CURRENT_TIMESTAMP) VALUES (?, ?, 'S', ?, 'text', 'A', CURRENT_TIMESTAMP)
`, [(maxMsgId[0]?.ID || 0) + 1, conversaId, msg]); `, [(maxMsgId[0]?.ID || 0) + 1, conversaId, msg]);
await TriageController.sendEvolution(alias, instanciaId, numero, msg); await TriageController.sendEvolution(alias, instanciaId, numero, msg);
await db.execute(alias, "UPDATE CHATC2_CONVERSAS SET CON_MENU_ESTADO = 'root' WHERE CON_CODIGO_ID = ?", [conversaId]); await db.execute(alias, "UPDATE CHATC2_CONVERSAS SET CON_MENU_ESTADO = 'root' WHERE CON_CODIGO_ID = ?", [conversaId]);
@@ -624,8 +624,8 @@ class TriageController {
const maxMsgId2 = await db.query(alias, 'SELECT MAX(CME_CODIGO_ID) AS ID FROM CHATC2_CONVERSAS_MENSAGENS'); const maxMsgId2 = await db.query(alias, 'SELECT MAX(CME_CODIGO_ID) AS ID FROM CHATC2_CONVERSAS_MENSAGENS');
await db.execute(alias, ` await db.execute(alias, `
INSERT INTO CHATC2_CONVERSAS_MENSAGENS (CME_CODIGO_ID, CME_CONVERSA_ID, CME_REMETENTE, CME_TEXTO, CME_TIPO, CME_DT_ENVIO) INSERT INTO CHATC2_CONVERSAS_MENSAGENS (CME_CODIGO_ID, CME_CONVERSA_ID, CME_REMETENTE, CME_TEXTO, CME_TIPO, CME_SITUACAO, CME_DT_ENVIO)
VALUES (?, ?, 'S', ?, 'text', CURRENT_TIMESTAMP) VALUES (?, ?, 'S', ?, 'text', 'A', CURRENT_TIMESTAMP)
`, [(maxMsgId2[0]?.ID || 0) + 1, conversaId, msgBoleto]); `, [(maxMsgId2[0]?.ID || 0) + 1, conversaId, msgBoleto]);
await TriageController.sendEvolution(alias, instanciaId, numero, msgBoleto); await TriageController.sendEvolution(alias, instanciaId, numero, msgBoleto);
@@ -652,8 +652,8 @@ class TriageController {
const msg = 'Opção inválida. Tente novamente.'; const msg = 'Opção inválida. Tente novamente.';
const maxId = await db.query(alias, 'SELECT MAX(CME_CODIGO_ID) AS ID FROM CHATC2_CONVERSAS_MENSAGENS'); const maxId = await db.query(alias, 'SELECT MAX(CME_CODIGO_ID) AS ID FROM CHATC2_CONVERSAS_MENSAGENS');
await db.execute(alias, ` await db.execute(alias, `
INSERT INTO CHATC2_CONVERSAS_MENSAGENS (CME_CODIGO_ID, CME_CONVERSA_ID, CME_REMETENTE, CME_TEXTO, CME_TIPO, CME_DT_ENVIO) INSERT INTO CHATC2_CONVERSAS_MENSAGENS (CME_CODIGO_ID, CME_CONVERSA_ID, CME_REMETENTE, CME_TEXTO, CME_TIPO, CME_SITUACAO, CME_DT_ENVIO)
VALUES (?, ?, 'S', ?, 'text', CURRENT_TIMESTAMP) VALUES (?, ?, 'S', ?, 'text', 'A', CURRENT_TIMESTAMP)
`, [(maxId[0]?.ID || 0) + 1, conversaId, msg]); `, [(maxId[0]?.ID || 0) + 1, conversaId, msg]);
await TriageController.sendEvolution(alias, instanciaId, numero, msg); await TriageController.sendEvolution(alias, instanciaId, numero, msg);
return; return;
@@ -838,8 +838,8 @@ class TriageController {
const maxId = await db.query(alias, 'SELECT MAX(CME_CODIGO_ID) AS ID FROM CHATC2_CONVERSAS_MENSAGENS'); const maxId = await db.query(alias, 'SELECT MAX(CME_CODIGO_ID) AS ID FROM CHATC2_CONVERSAS_MENSAGENS');
await db.execute(alias, ` await db.execute(alias, `
INSERT INTO CHATC2_CONVERSAS_MENSAGENS (CME_CODIGO_ID, CME_CONVERSA_ID, CME_REMETENTE, CME_TEXTO, CME_TIPO, CME_DT_ENVIO) INSERT INTO CHATC2_CONVERSAS_MENSAGENS (CME_CODIGO_ID, CME_CONVERSA_ID, CME_REMETENTE, CME_TEXTO, CME_TIPO, CME_SITUACAO, CME_DT_ENVIO)
VALUES (?, ?, 'S', ?, 'text', CURRENT_TIMESTAMP) VALUES (?, ?, 'S', ?, 'text', 'A', CURRENT_TIMESTAMP)
`, [(maxId[0]?.ID || 0) + 1, conversaId, textoBoleto]); `, [(maxId[0]?.ID || 0) + 1, conversaId, textoBoleto]);
await TriageController.sendEvolution(alias, instanciaId, numero, textoBoleto); await TriageController.sendEvolution(alias, instanciaId, numero, textoBoleto);
} }
@@ -848,8 +848,8 @@ class TriageController {
var msgCont = '📄 Boleto enviado!\n\nDeseja continuar com o atendimento?\n\n1 - Sim\n2 - Não, finalizar'; var msgCont = '📄 Boleto enviado!\n\nDeseja continuar com o atendimento?\n\n1 - Sim\n2 - Não, finalizar';
const maxIdCont = await db.query(alias, 'SELECT MAX(CME_CODIGO_ID) AS ID FROM CHATC2_CONVERSAS_MENSAGENS'); const maxIdCont = await db.query(alias, 'SELECT MAX(CME_CODIGO_ID) AS ID FROM CHATC2_CONVERSAS_MENSAGENS');
await db.execute(alias, ` await db.execute(alias, `
INSERT INTO CHATC2_CONVERSAS_MENSAGENS (CME_CODIGO_ID, CME_CONVERSA_ID, CME_REMETENTE, CME_TEXTO, CME_TIPO, CME_DT_ENVIO) INSERT INTO CHATC2_CONVERSAS_MENSAGENS (CME_CODIGO_ID, CME_CONVERSA_ID, CME_REMETENTE, CME_TEXTO, CME_TIPO, CME_SITUACAO, CME_DT_ENVIO)
VALUES (?, ?, 'S', ?, 'text', CURRENT_TIMESTAMP) VALUES (?, ?, 'S', ?, 'text', 'A', CURRENT_TIMESTAMP)
`, [(maxIdCont[0]?.ID || 0) + 1, conversaId, msgCont]); `, [(maxIdCont[0]?.ID || 0) + 1, conversaId, msgCont]);
await TriageController.sendEvolution(alias, instanciaId, numero, msgCont); await TriageController.sendEvolution(alias, instanciaId, numero, msgCont);
@@ -889,8 +889,8 @@ class TriageController {
var msgFinal = 'Atendimento finalizado. Obrigado pelo contato!'; var msgFinal = 'Atendimento finalizado. Obrigado pelo contato!';
const maxId = await db.query(alias, 'SELECT MAX(CME_CODIGO_ID) AS ID FROM CHATC2_CONVERSAS_MENSAGENS'); const maxId = await db.query(alias, 'SELECT MAX(CME_CODIGO_ID) AS ID FROM CHATC2_CONVERSAS_MENSAGENS');
await db.execute(alias, ` await db.execute(alias, `
INSERT INTO CHATC2_CONVERSAS_MENSAGENS (CME_CODIGO_ID, CME_CONVERSA_ID, CME_REMETENTE, CME_TEXTO, CME_TIPO, CME_DT_ENVIO) INSERT INTO CHATC2_CONVERSAS_MENSAGENS (CME_CODIGO_ID, CME_CONVERSA_ID, CME_REMETENTE, CME_TEXTO, CME_TIPO, CME_SITUACAO, CME_DT_ENVIO)
VALUES (?, ?, 'S', ?, 'text', CURRENT_TIMESTAMP) VALUES (?, ?, 'S', ?, 'text', 'A', CURRENT_TIMESTAMP)
`, [(maxId[0]?.ID || 0) + 1, conversaId, msgFinal]); `, [(maxId[0]?.ID || 0) + 1, conversaId, msgFinal]);
await TriageController.sendEvolution(alias, instanciaId, numero, msgFinal); await TriageController.sendEvolution(alias, instanciaId, numero, msgFinal);
await db.execute(alias, "UPDATE CHATC2_CONVERSAS SET CON_STATUS = 'F', CON_DT_FINAL = CURRENT_TIMESTAMP, CON_MENU_ESTADO = NULL WHERE CON_CODIGO_ID = ?", [conversaId]); await db.execute(alias, "UPDATE CHATC2_CONVERSAS SET CON_STATUS = 'F', CON_DT_FINAL = CURRENT_TIMESTAMP, CON_MENU_ESTADO = NULL WHERE CON_CODIGO_ID = ?", [conversaId]);
@@ -898,8 +898,8 @@ class TriageController {
var msgInv = 'Por favor, responda:\n\n1 - Sim\n2 - Não, finalizar'; var msgInv = 'Por favor, responda:\n\n1 - Sim\n2 - Não, finalizar';
const maxId = await db.query(alias, 'SELECT MAX(CME_CODIGO_ID) AS ID FROM CHATC2_CONVERSAS_MENSAGENS'); const maxId = await db.query(alias, 'SELECT MAX(CME_CODIGO_ID) AS ID FROM CHATC2_CONVERSAS_MENSAGENS');
await db.execute(alias, ` await db.execute(alias, `
INSERT INTO CHATC2_CONVERSAS_MENSAGENS (CME_CODIGO_ID, CME_CONVERSA_ID, CME_REMETENTE, CME_TEXTO, CME_TIPO, CME_DT_ENVIO) INSERT INTO CHATC2_CONVERSAS_MENSAGENS (CME_CODIGO_ID, CME_CONVERSA_ID, CME_REMETENTE, CME_TEXTO, CME_TIPO, CME_SITUACAO, CME_DT_ENVIO)
VALUES (?, ?, 'S', ?, 'text', CURRENT_TIMESTAMP) VALUES (?, ?, 'S', ?, 'text', 'A', CURRENT_TIMESTAMP)
`, [(maxId[0]?.ID || 0) + 1, conversaId, msgInv]); `, [(maxId[0]?.ID || 0) + 1, conversaId, msgInv]);
await TriageController.sendEvolution(alias, instanciaId, numero, msgInv); await TriageController.sendEvolution(alias, instanciaId, numero, msgInv);
} }
@@ -916,8 +916,8 @@ class TriageController {
const msg = 'Opção inválida. Tente novamente.'; const msg = 'Opção inválida. Tente novamente.';
const maxId = await db.query(alias, 'SELECT MAX(CME_CODIGO_ID) AS ID FROM CHATC2_CONVERSAS_MENSAGENS'); const maxId = await db.query(alias, 'SELECT MAX(CME_CODIGO_ID) AS ID FROM CHATC2_CONVERSAS_MENSAGENS');
await db.execute(alias, ` await db.execute(alias, `
INSERT INTO CHATC2_CONVERSAS_MENSAGENS (CME_CODIGO_ID, CME_CONVERSA_ID, CME_REMETENTE, CME_TEXTO, CME_TIPO, CME_DT_ENVIO) INSERT INTO CHATC2_CONVERSAS_MENSAGENS (CME_CODIGO_ID, CME_CONVERSA_ID, CME_REMETENTE, CME_TEXTO, CME_TIPO, CME_SITUACAO, CME_DT_ENVIO)
VALUES (?, ?, 'S', ?, 'text', CURRENT_TIMESTAMP) VALUES (?, ?, 'S', ?, 'text', 'A', CURRENT_TIMESTAMP)
`, [(maxId[0]?.ID || 0) + 1, conversaId, msg]); `, [(maxId[0]?.ID || 0) + 1, conversaId, msg]);
await TriageController.sendEvolution(alias, instanciaId, numero, msg); await TriageController.sendEvolution(alias, instanciaId, numero, msg);
@@ -949,8 +949,8 @@ class TriageController {
if (texto) { if (texto) {
const maxId = await db.query(alias, 'SELECT MAX(CME_CODIGO_ID) AS ID FROM CHATC2_CONVERSAS_MENSAGENS'); const maxId = await db.query(alias, 'SELECT MAX(CME_CODIGO_ID) AS ID FROM CHATC2_CONVERSAS_MENSAGENS');
await db.execute(alias, ` await db.execute(alias, `
INSERT INTO CHATC2_CONVERSAS_MENSAGENS (CME_CODIGO_ID, CME_CONVERSA_ID, CME_REMETENTE, CME_TEXTO, CME_TIPO, CME_DT_ENVIO) INSERT INTO CHATC2_CONVERSAS_MENSAGENS (CME_CODIGO_ID, CME_CONVERSA_ID, CME_REMETENTE, CME_TEXTO, CME_TIPO, CME_SITUACAO, CME_DT_ENVIO)
VALUES (?, ?, 'S', ?, 'text', CURRENT_TIMESTAMP) VALUES (?, ?, 'S', ?, 'text', 'A', CURRENT_TIMESTAMP)
`, [(maxId[0]?.ID || 0) + 1, conversaId, texto]); `, [(maxId[0]?.ID || 0) + 1, conversaId, texto]);
await TriageController.sendEvolution(alias, instanciaId, numero, texto); await TriageController.sendEvolution(alias, instanciaId, numero, texto);
} }
+153 -508
View File
@@ -5,13 +5,11 @@
* *
* 1) CONFIGURAÇÃO aliases, drivers, conexões estáticas e customizadas * 1) CONFIGURAÇÃO aliases, drivers, conexões estáticas e customizadas
* 2) DRIVER FIREBIRD * 2) DRIVER FIREBIRD
* 3) DRIVER POSTGRES (pool + tradutor de SQL + schema) * 3) DISPATCHER API pública usada por todo o sistema
* 4) DISPATCHER API pública usada por todo o sistema
* *
* Suporta múltiplos drivers ('postgres' padrão, 'firebird' legado). Cada * Suporte ao banco Firebird. Cada conexão declara o campo `driver`.
* conexão declara o campo `driver`. Conexões adicionadas em runtime (via API * Conexões adicionadas em runtime (via API /api/databases) são persistidas
* /api/databases) são persistidas em databases_custom.json (ao lado deste * em databases_custom.json (ao lado deste arquivo).
* arquivo) é um arquivo de DADOS, não de código.
*/ */
const path = require('path'); const path = require('path');
const fs = require('fs'); const fs = require('fs');
@@ -31,22 +29,11 @@ try {
console.error('[Databases] Erro ao carregar databases_custom.json:', e.message); console.error('[Databases] Erro ao carregar databases_custom.json:', e.message);
} }
const DRIVERS = ['postgres', 'firebird']; const DRIVERS = ['firebird'];
const DEFAULT_DRIVER = (process.env.DB_DRIVER || 'postgres').toLowerCase(); const DEFAULT_DRIVER = 'firebird';
// Valores padrão por driver // Valores padrão para Firebird
const DRIVER_DEFAULTS = { const DRIVER_DEFAULTS = {
postgres: {
host: '127.0.0.1',
port: 5432,
user: 'postgres',
password: 'postgres',
schema: 'public',
ssl: false,
max: 10,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 10000,
},
firebird: { firebird: {
host: 'localhost', host: 'localhost',
port: 3050, port: 3050,
@@ -61,22 +48,10 @@ const DRIVER_DEFAULTS = {
/** /**
* Conexões estáticas. * Conexões estáticas.
* - `novo_local` PostgreSQL (conexão principal) * - `novo_local` Firebird (informe o CAMINHO do arquivo .FDB em `database`)
* - `firebird_local` Firebird (informe o CAMINHO do arquivo .FDB em `database`) * Sobrescrevível pelo .env (DB_*).
* Sobrescrevíveis pelo .env (PG_* para Postgres, DB_* para Firebird).
*/ */
const databases = { const databases = {
novo: {
driver: 'postgres',
host: process.env.PG_HOST || 'db.assantos.app.br',
port: parseInt(process.env.PG_PORT, 10) || 443,
user: process.env.PG_USER || 'atendimento_user',
password: process.env.PG_PASSWORD || 'PSSQDfPHilIZF1mbGzuR2RrEh0cTEGaF',
database: process.env.PG_DATABASE || 'atendimento',
schema: process.env.PG_SCHEMA || 'atendi',
ssl: process.env.PG_SSL === 'true' ? true : process.env.PG_SSL === 'false' ? false : (parseInt(process.env.PG_PORT, 10) === 443 ? { rejectUnauthorized: false } : false),
},
novo_local: { novo_local: {
driver: 'firebird', driver: 'firebird',
host: process.env.DB_HOST || 'localhost', host: process.env.DB_HOST || 'localhost',
@@ -105,30 +80,10 @@ function salvarCustomDatabases() {
/** Normaliza uma config bruta aplicando o driver e seus defaults. */ /** Normaliza uma config bruta aplicando o driver e seus defaults. */
function normalize(raw) { function normalize(raw) {
const driver = DRIVERS.includes((raw.driver || '').toLowerCase()) const d = DRIVER_DEFAULTS['firebird'];
? raw.driver.toLowerCase()
: DEFAULT_DRIVER;
const d = DRIVER_DEFAULTS[driver];
if (driver === 'postgres') {
return {
driver,
host: raw.host || d.host,
port: raw.port || d.port,
database: raw.database,
user: raw.user || d.user,
password: raw.password || d.password,
schema: (raw.schema || d.schema || 'public'),
ssl: raw.ssl !== undefined ? raw.ssl : d.ssl,
max: raw.max || d.max,
idleTimeoutMillis: raw.idleTimeoutMillis || d.idleTimeoutMillis,
connectionTimeoutMillis: raw.connectionTimeoutMillis || d.connectionTimeoutMillis,
};
}
// firebird
return { return {
driver, driver: 'firebird',
host: raw.host || d.host, host: raw.host || d.host,
port: raw.port || d.port, port: raw.port || d.port,
database: raw.database, database: raw.database,
@@ -178,14 +133,10 @@ function addDatabase(alias, config) {
throw new Error('Alias e database são obrigatórios.'); throw new Error('Alias e database são obrigatórios.');
} }
var aliasLower = alias.toLowerCase().replace(/[^a-z0-9_]/g, '_'); var aliasLower = alias.toLowerCase().replace(/[^a-z0-9_]/g, '_');
var driver = DRIVERS.includes((config.driver || '').toLowerCase())
? config.driver.toLowerCase()
: DEFAULT_DRIVER;
var stored = { driver: driver }; var stored = { driver: 'firebird' };
['host', 'port', 'database', 'schema', 'user', 'password', 'ssl', ['host', 'port', 'database', 'user', 'password',
'encoding', 'lowercase_keys', 'role', 'pageSize', 'wireCrypt', 'encoding', 'lowercase_keys', 'role', 'pageSize', 'wireCrypt']
'max', 'idleTimeoutMillis', 'connectionTimeoutMillis']
.forEach(function (k) { .forEach(function (k) {
if (config[k] !== undefined && config[k] !== null && config[k] !== '') { if (config[k] !== undefined && config[k] !== null && config[k] !== '') {
stored[k] = config[k]; stored[k] = config[k];
@@ -208,478 +159,172 @@ function removeDatabase(alias) {
// ============================================================ // ============================================================
// 2) DRIVER FIREBIRD // 2) DRIVER FIREBIRD
// ============================================================ // ============================================================
const firebirdDriver = (() => { const Firebird = require('node-firebird');
const Firebird = require('node-firebird');
function readBlob(blobFunc) { function readBlob(blobFunc) {
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
if (typeof blobFunc !== 'function') return resolve(blobFunc); if (typeof blobFunc !== 'function') return resolve(blobFunc);
blobFunc(function (err, name, emitter) { blobFunc(function (err, name, emitter) {
if (err) return reject(err); if (err) return reject(err);
if (!emitter || typeof emitter.on !== 'function') return resolve(null); if (!emitter || typeof emitter.on !== 'function') return resolve(null);
var chunks = []; var chunks = [];
var total = 0; var total = 0;
emitter.on('data', function (c) { chunks.push(c); total += c.length; }); emitter.on('data', function (c) { chunks.push(c); total += c.length; });
emitter.on('end', function () { resolve(Buffer.concat(chunks, total)); }); emitter.on('end', function () { resolve(Buffer.concat(chunks, total)); });
emitter.on('error', reject); emitter.on('error', reject);
});
}); });
} });
}
function query(config, sql, params = []) { function firebirdQuery(config, sql, params = []) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
Firebird.attach(config, (err, db) => { Firebird.attach(config, (err, db) => {
if (err) return reject(new Error(`Erro ao conectar (firebird): ${err.message}`)); if (err) return reject(new Error(`Erro ao conectar (firebird): ${err.message}`));
var allRows = []; var allRows = [];
db.sequentially(sql, params, function (row) { db.sequentially(sql, params, function (row) {
var keys = Object.keys(row); var keys = Object.keys(row);
var blobPromises = keys.map(function (key) { var blobPromises = keys.map(function (key) {
var val = row[key]; var val = row[key];
if (typeof val === 'function') { if (typeof val === 'function') {
return readBlob(val).then(function (data) { row[key] = data; }); return readBlob(val).then(function (data) { row[key] = data; });
}
return Promise.resolve();
});
return Promise.all(blobPromises).then(function () { allRows.push(row); });
}, function (queryErr) {
db.detach();
if (queryErr) return reject(new Error(`Erro na consulta (firebird): ${queryErr.message}`));
resolve(allRows);
});
});
});
}
function execute(config, sql, params = []) {
return new Promise((resolve, reject) => {
Firebird.attach(config, (err, db) => {
if (err) return reject(new Error(`Erro ao conectar (firebird): ${err.message}`));
db.transaction(Firebird.ISOLATION_READ_COMMITTED, (transErr, transaction) => {
if (transErr) {
db.detach();
return reject(new Error(`Erro ao iniciar transação (firebird): ${transErr.message}`));
} }
transaction.query(sql, params, (queryErr, result) => { return Promise.resolve();
if (queryErr) { });
transaction.rollback(); return Promise.all(blobPromises).then(function () { allRows.push(row); });
db.detach(); }, function (queryErr) {
return reject(new Error(`Erro na execução (firebird): ${queryErr.message}`)); db.detach();
} if (queryErr) return reject(new Error(`Erro na consulta (firebird): ${queryErr.message}`));
transaction.commit((commitErr) => { resolve(allRows);
db.detach(); });
if (commitErr) return reject(new Error(`Erro ao commitar (firebird): ${commitErr.message}`)); });
resolve({ affectedRows: result ? result.length : 0, result }); });
}); }
function firebirdExecute(config, sql, params = []) {
return new Promise((resolve, reject) => {
Firebird.attach(config, (err, db) => {
if (err) return reject(new Error(`Erro ao conectar (firebird): ${err.message}`));
db.transaction(Firebird.ISOLATION_READ_COMMITTED, (transErr, transaction) => {
if (transErr) {
db.detach();
return reject(new Error(`Erro ao iniciar transação (firebird): ${transErr.message}`));
}
transaction.query(sql, params, (queryErr, result) => {
if (queryErr) {
transaction.rollback();
db.detach();
return reject(new Error(`Erro na execução (firebird): ${queryErr.message}`));
}
transaction.commit((commitErr) => {
db.detach();
if (commitErr) return reject(new Error(`Erro ao commitar (firebird): ${commitErr.message}`));
resolve({ affectedRows: result ? result.length : 0, result });
}); });
}); });
}); });
}); });
} });
}
async function testConnection(config) { async function firebirdTestConnection(config) {
await query(config, 'SELECT 1 FROM RDB$DATABASE'); await firebirdQuery(config, 'SELECT 1 FROM RDB$DATABASE');
return true; return true;
} }
async function listTables(config) { async function firebirdListTables(config) {
const rows = await query(config, ` const rows = await firebirdQuery(config, `
SELECT TRIM(RDB$RELATION_NAME) AS TABLE_NAME SELECT TRIM(RDB$RELATION_NAME) AS TABLE_NAME
FROM RDB$RELATIONS FROM RDB$RELATIONS
WHERE RDB$SYSTEM_FLAG = 0 AND RDB$RELATION_TYPE = 0 WHERE RDB$SYSTEM_FLAG = 0 AND RDB$RELATION_TYPE = 0
ORDER BY RDB$RELATION_NAME ORDER BY RDB$RELATION_NAME
`); `);
return rows.map((r) => (r.TABLE_NAME || '').trim()).filter(Boolean); return rows.map((r) => (r.TABLE_NAME || '').trim()).filter(Boolean);
} }
async function tableInfo(config, tableName) { async function firebirdTableInfo(config, tableName) {
const rows = await query(config, ` const rows = await firebirdQuery(config, `
SELECT SELECT
rf.RDB$FIELD_NAME AS COLUMN_NAME, rf.RDB$FIELD_NAME AS COLUMN_NAME,
rf.RDB$FIELD_POSITION AS ORDINAL_POSITION, rf.RDB$FIELD_POSITION AS ORDINAL_POSITION,
CASE f.RDB$FIELD_TYPE CASE f.RDB$FIELD_TYPE
WHEN 7 THEN 'SMALLINT' WHEN 8 THEN 'INTEGER' WHEN 16 THEN 'BIGINT' WHEN 7 THEN 'SMALLINT' WHEN 8 THEN 'INTEGER' WHEN 16 THEN 'BIGINT'
WHEN 9 THEN 'QUAD' WHEN 10 THEN 'FLOAT' WHEN 27 THEN 'DOUBLE PRECISION' WHEN 9 THEN 'QUAD' WHEN 10 THEN 'FLOAT' WHEN 27 THEN 'DOUBLE PRECISION'
WHEN 12 THEN 'DATE' WHEN 13 THEN 'TIME' WHEN 35 THEN 'TIMESTAMP' WHEN 12 THEN 'DATE' WHEN 13 THEN 'TIME' WHEN 35 THEN 'TIMESTAMP'
WHEN 37 THEN 'VARCHAR' WHEN 40 THEN 'CSTRING' WHEN 45 THEN 'BLOB_ID' WHEN 37 THEN 'VARCHAR' WHEN 40 THEN 'CSTRING' WHEN 45 THEN 'BLOB_ID'
WHEN 261 THEN 'BLOB' WHEN 14 THEN 'CHAR' WHEN 41 THEN 'NUMERIC' WHEN 261 THEN 'BLOB' WHEN 14 THEN 'CHAR' WHEN 41 THEN 'NUMERIC'
ELSE 'UNKNOWN' ELSE 'UNKNOWN'
END AS DATA_TYPE, END AS DATA_TYPE,
f.RDB$FIELD_LENGTH AS FIELD_LENGTH, f.RDB$FIELD_LENGTH AS FIELD_LENGTH,
f.RDB$FIELD_SCALE AS FIELD_SCALE, f.RDB$FIELD_SCALE AS FIELD_SCALE,
f.RDB$FIELD_PRECISION AS FIELD_PRECISION, f.RDB$FIELD_PRECISION AS FIELD_PRECISION,
rf.RDB$NULL_FLAG AS NULL_FLAG rf.RDB$NULL_FLAG AS NULL_FLAG
FROM RDB$RELATION_FIELDS rf FROM RDB$RELATION_FIELDS rf
INNER JOIN RDB$FIELDS f ON rf.RDB$FIELD_SOURCE = f.RDB$FIELD_NAME INNER JOIN RDB$FIELDS f ON rf.RDB$FIELD_SOURCE = f.RDB$FIELD_NAME
WHERE rf.RDB$RELATION_NAME = ? WHERE rf.RDB$RELATION_NAME = ?
ORDER BY rf.RDB$FIELD_POSITION ORDER BY rf.RDB$FIELD_POSITION
`, [String(tableName).toUpperCase()]); `, [String(tableName).toUpperCase()]);
return rows.map((row) => ({ return rows.map((row) => ({
name: (row.COLUMN_NAME || '').trim(), name: (row.COLUMN_NAME || '').trim(),
position: row.ORDINAL_POSITION, position: row.ORDINAL_POSITION,
type: (row.DATA_TYPE || '').trim(), type: (row.DATA_TYPE || '').trim(),
length: row.FIELD_LENGTH, length: row.FIELD_LENGTH,
precision: row.FIELD_PRECISION, precision: row.FIELD_PRECISION,
scale: row.FIELD_SCALE, scale: row.FIELD_SCALE,
nullable: row.NULL_FLAG !== 1, nullable: row.NULL_FLAG !== 1,
})); }));
} }
async function tableExists(config, tableName) { async function firebirdTableExists(config, tableName) {
const r = await query(config, const r = await firebirdQuery(config,
"SELECT COUNT(*) AS CT FROM RDB$RELATIONS WHERE RDB$RELATION_NAME = ?", "SELECT COUNT(*) AS CT FROM RDB$RELATIONS WHERE RDB$RELATION_NAME = ?",
[String(tableName).toUpperCase()]); [String(tableName).toUpperCase()]);
return (r[0] && r[0].CT > 0) || false; return (r[0] && r[0].CT > 0) || false;
} }
async function columnExists(config, tableName, columnName) { async function firebirdColumnExists(config, tableName, columnName) {
const r = await query(config, const r = await firebirdQuery(config,
"SELECT COUNT(*) AS CT FROM RDB$RELATION_FIELDS WHERE RDB$RELATION_NAME = ? AND RDB$FIELD_NAME = ?", "SELECT COUNT(*) AS CT FROM RDB$RELATION_FIELDS WHERE RDB$RELATION_NAME = ? AND RDB$FIELD_NAME = ?",
[String(tableName).toUpperCase(), String(columnName).toUpperCase()]); [String(tableName).toUpperCase(), String(columnName).toUpperCase()]);
return (r[0] && r[0].CT > 0) || false; return (r[0] && r[0].CT > 0) || false;
} }
async function close() { /* Firebird abre conexão por consulta; nada a encerrar */ } async function firebirdClose() { /* Firebird abre conexão por consulta; nada a encerrar */ }
return { query, execute, testConnection, listTables, tableInfo, tableExists, columnExists, close };
})();
// ============================================================ // ============================================================
// 3) DRIVER POSTGRES (pool + tradutor de SQL Firebird->PG + schema) // 3) DISPATCHER — API pública
// ============================================================ // ============================================================
const postgresDriver = (() => { const driver = {
const pg = require('pg'); query: firebirdQuery,
execute: firebirdExecute,
testConnection: firebirdTestConnection,
listTables: firebirdListTables,
tableInfo: firebirdTableInfo,
tableExists: firebirdTableExists,
columnExists: firebirdColumnExists,
close: firebirdClose,
};
// COUNT()/bigint chegam como string no pg; o código histórico usa números. function getConn(alias) {
pg.types.setTypeParser(20, (v) => (v === null ? null : parseInt(v, 10))); // int8 / bigint
pg.types.setTypeParser(1700, (v) => (v === null ? null : parseFloat(v))); // numeric / decimal
const pools = new Map();
function safeSchema(schema) {
const s = String(schema || 'public').trim();
return /^[A-Za-z_][A-Za-z0-9_$]*$/.test(s) ? s : 'public';
}
function poolKey(config) {
return [config.host, config.port, config.database, config.user, safeSchema(config.schema)].join('|');
}
// Schemas que o pool enxerga (configurado + public como fallback)
function schemasFor(config) {
const schema = safeSchema(config.schema);
return schema === 'public' ? ['public'] : [schema, 'public'];
}
function getEntry(config) {
const key = poolKey(config);
let entry = pools.get(key);
if (!entry) {
const schemas = schemasFor(config);
const pool = new pg.Pool({
host: config.host,
port: config.port,
database: config.database,
user: config.user,
password: config.password,
ssl: config.ssl ? { rejectUnauthorized: false } : false,
options: '-c search_path=' + schemas.join(','),
max: config.max || 10,
idleTimeoutMillis: config.idleTimeoutMillis || 30000,
connectionTimeoutMillis: config.connectionTimeoutMillis || 10000,
});
pool.on('error', (err) => console.error('[postgres] Erro inesperado no pool:', err.message));
entry = { pool, schemas, tableSet: null, loadingTables: null };
pools.set(key, entry);
}
return entry;
}
// Carrega (uma vez por pool) os nomes de tabela MAIÚSCULOS p/ decidir aspas
async function ensureTableSet(entry) {
if (entry.tableSet) return entry.tableSet;
if (!entry.loadingTables) {
entry.loadingTables = entry.pool
.query('SELECT table_name FROM information_schema.tables WHERE table_schema = ANY($1)', [entry.schemas])
.then((res) => {
entry.tableSet = new Set(res.rows.map((r) => String(r.table_name).toUpperCase()));
return entry.tableSet;
})
.catch((err) => {
console.error('[postgres] Falha ao carregar nomes de tabela:', err.message);
entry.tableSet = new Set();
return entry.tableSet;
});
}
return entry.loadingTables;
}
// ? -> $1, $2, ... (ignora literais de string)
function convertPlaceholders(sql) {
let out = '', i = 0, n = 1, inStr = false;
while (i < sql.length) {
const ch = sql[i];
if (inStr) {
out += ch;
if (ch === "'") {
if (sql[i + 1] === "'") { out += sql[i + 1]; i += 2; continue; }
inStr = false;
}
i++;
continue;
}
if (ch === "'") { inStr = true; out += ch; i++; continue; }
if (ch === '?') { out += '$' + (n++); i++; continue; }
out += ch; i++;
}
return out;
}
// Aspas nos nomes de tabela conhecidos (MAIÚSCULOS) após FROM/JOIN/INTO/UPDATE/ALTER/DROP
function quoteTableNames(sql, tableSet) {
if (!tableSet || tableSet.size === 0) return sql;
return sql.replace(
/(\b(?:FROM|JOIN|INTO|UPDATE|ALTER\s+TABLE|DROP\s+TABLE)\s+)("?)([A-Za-z_][A-Za-z0-9_$]*)("?)/gi,
(match, kw, q1, name, q2) => {
if (q1 === '"' || q2 === '"') return match;
if (tableSet.has(name.toUpperCase())) return kw + '"' + name.toUpperCase() + '"';
return match;
}
);
}
// Rede de segurança: FIRST n [SKIP m] no topo -> LIMIT/OFFSET (SQL legado)
function translateFirstSkip(sql) {
return sql.replace(
/^(\s*SELECT\s+)FIRST\s+(\d+)(?:\s+SKIP\s+(\d+))?\s+/i,
(m, sel, first, skip) => sel + ' LIMITTAIL LIMIT ' + first + (skip ? ' OFFSET ' + skip : '') + ' '
);
}
function applyLimitTail(sql) {
const marker = / LIMITTAIL( LIMIT \d+(?: OFFSET \d+)?) /;
const m = sql.match(marker);
if (!m) return sql;
return sql.replace(marker, ' ').trimEnd() + m[1];
}
// Coloca aspas em nomes de colunas MAIÚSCULOS (formato NOME_COLUNA)
// Reconhece padrao: letras_maiusculas com _ (ex: USU_CODIGO_ID, CON_NUMERO)
// Ignora SQL keywords, literais string e numeros
function quoteColumnNames(sql) {
// Lista de keywords SQL que nao devem ser colocadas em aspas
var sqlKeywords = ['AS', 'ON', 'AND', 'OR', 'NOT', 'IS', 'IN', 'NULL', 'TRUE', 'FALSE',
'ASC', 'DESC', 'DISTINCT', 'ALL', 'ANY', 'SOME', 'BETWEEN', 'LIKE', 'ILIKE',
'FIRST', 'SKIP', 'LIMIT', 'OFFSET', 'ORDER', 'GROUP', 'HAVING', 'CROSS', 'INNER',
'LEFT', 'RIGHT', 'FULL', 'OUTER', 'NATURAL', 'USING', 'EXCEPT', 'INTERSECT', 'UNION',
'COUNT', 'SUM', 'AVG', 'MIN', 'MAX', 'COALESCE', 'CAST', 'CASE', 'WHEN', 'THEN', 'ELSE',
'END', 'EXISTS', 'CURRENT_TIMESTAMP', 'CURRENT_DATE', 'CURRENT_TIME', 'NOW', 'UPPER', 'LOWER', 'TRIM',
'ROW', 'ROWS', 'RANGE', 'FETCH', 'NEXT', 'ONLY', 'PERCENT', 'WITH', 'TIES', 'OVER', 'PARTITION',
'RETURNING', 'ACTION', 'ADD', 'ALTER', 'COLUMN', 'CONSTRAINT', 'CREATE', 'DELETE',
'DROP', 'EXECUTE', 'INSERT', 'INTO', 'TABLE', 'TRUNCATE', 'UPDATE', 'VALUES', 'WHERE',
'SET', 'SELECT', 'FROM', 'JOIN', 'PRIMARY', 'KEY', 'FOREIGN', 'REFERENCES', 'UNIQUE',
'CHECK', 'DEFAULT', 'INDEX', 'VIEW', 'MATERIALIZED', 'SCHEMA', 'DATABASE', 'GRANT',
'REVOKE', 'ABSOLUTE', 'ACTION', 'ADMIN', 'AFTER', 'AGGREGATE', 'ALSO', 'ANALYSE',
'ANALYZE', 'BEFORE', 'BEGIN', 'BINARY', 'BOTH', 'CALLED', 'CASCADE', 'CASCADED', 'CATALOG',
'CHAIN', 'CHARACTER', 'CHECKPOINT', 'CLASS', 'CLOSE', 'CLUSTER', 'COMMENT', 'COMMIT',
'COMMITTED', 'CONCURRENTLY', 'CONFIGURATION', 'CONFLICT', 'CONNECTION', 'CONSTRAINTS',
'CONTENT', 'CONTINUE', 'COPY', 'COSTS', 'CURRENT', 'CURSOR', 'CYCLE', 'DATA', 'DATABASE',
'DAY', 'DEALLOCATE', 'DEC', 'DECLARE', 'DEFERRABLE', 'DEFERRED', 'DEFINER', 'DELETE',
'DELIMITER', 'DELIMITERS', 'DEPENDS', 'DESCRIBE', 'DICTIONARY', 'DISABLE', 'DISCARD',
'DOMAIN', 'DOUBLE', 'DROP', 'EACH', 'ENABLE', 'ENCODING', 'ENCRYPTED', 'ENUM', 'ESCAPE',
'EVENT', 'EXCHANGE', 'EXCLUDE', 'EXCLUDING', 'EXCLUSIVE', 'EXECUTE', 'EXPLAIN',
'EXTENSION', 'EXTERNAL', 'FAMILY', 'FILTER', 'FIRST', 'FLOOR', 'FOLLOWING', 'FORCE',
'FORMAT', 'FORWARD', 'FREEZE', 'FUNCTION', 'FUNCTIONS', 'GLOBAL', 'GRANTED', 'GREATEST',
'GROUPING', 'GROUPS', 'HANDLER', 'HEADER', 'HOLD', 'HOUR', 'IDENTITY', 'IF', 'IMMEDIATE',
'IMMUTABLE', 'IMPLICIT', 'IMPORT', 'INCLUDING', 'INCREMENT', 'INDENT', 'INDEXES',
'INHERIT', 'INHERITS', 'INLINE', 'INPUT', 'INSENSITIVE', 'INSERTION', 'INSTEAD',
'INVOKER', 'ISOLATION', 'KEY', 'LABEL', 'LANGUAGE', 'LARGE', 'LAST', 'LEADING', 'LEAKPROOF',
'LEAST', 'LEVEL', 'LISTEN', 'LOAD', 'LOCAL', 'LOCATION', 'LOCK', 'LOCKED', 'LOGGED',
'MAPPING', 'MATCH', 'MATCHED', 'MAXVALUE', 'METHOD', 'MINUTE', 'MINVALUE', 'MODE', 'MONTH',
'MOVE', 'NAMES', 'NESTED', 'NEW', 'NEXT', 'NO', 'NONE', 'NOTHING', 'NOTIFY', 'NOWAIT',
'NULLS', 'OBJECT', 'OF', 'OFF', 'OIDS', 'OLD', 'OPERATOR', 'OPTION', 'OPTIONS', 'ORDERED',
'ORDINALITY', 'OTHERS', 'OUT', 'OVERLAY', 'OVERRIDING', 'OWNED', 'OWNER', 'PARALLEL',
'PARSER', 'PARTITION', 'PASSING', 'PASSWORD', 'PLACING', 'PLANS', 'POLICY', 'POSITION',
'PRECEDING', 'PRECISION', 'PREPARE', 'PREPARED', 'PRESERVE', 'PRIOR', 'PRIVILEGES',
'PROCEDURAL', 'PROCEDURE', 'PROCEDURES', 'PROGRAM', 'PUBLICATION', 'QUOTE', 'RANGE',
'READ', 'REASSIGN', 'RECHECK', 'RECURSIVE', 'REFERENCING', 'REFRESH', 'REINDEX',
'RELATIVE', 'RELEASE', 'RENAME', 'REPEATABLE', 'REPLACE', 'REPLICA', 'RESET',
'RESTART', 'RESTRICT', 'RETURN', 'RETURNS', 'REVOKE', 'ROLE', 'ROLLBACK', 'ROLLUP',
'ROUTINE', 'ROUTINES', 'RULE', 'SAVEPOINT', 'SCROLL', 'SEARCH', 'SECOND',
'SECURITY', 'SEQUENCE', 'SEQUENCES', 'SERIALIZABLE', 'SERVER', 'SESSION', 'SHARE',
'SHOW', 'SHUTDOWN', 'SIMPLE', 'SKIP', 'SNAPSHOT', 'SOME', 'SOURCE', 'SPACE', 'SPECIFIC',
'SPECIFICTYPE', 'SQL', 'SQLCODE', 'SQLERROR', 'SQLEXCEPTION', 'SQLSTATE', 'SQLWARNING',
'STABLE', 'STANDALONE', 'START', 'STATE', 'STATEMENT', 'STATIC', 'STATISTICS', 'STDIN',
'STDOUT', 'STORAGE', 'STRICT', 'STRIP', 'SUBSCRIPTION', 'SYSID', 'SYSTEM', 'TABLES',
'TABLESAMPLE', 'TABLESPACE', 'TEMP', 'TEMPLATE', 'TEMPORARY', 'TEXT', 'TEXT_SEARCH',
'THEN', 'TIME', 'TRANSACTION', 'TRIGGER', 'TRUNCATE', 'TRUSTED', 'TYPE', 'TYPES',
'UNBOUNDED', 'UNCOMMITTED', 'UNDER', 'UNENCRYPTED', 'UNKNOWN', 'UNLISTEN', 'UNLOGGED',
'UNTIL', 'UPDATE', 'USE', 'USER', 'USERS', 'VACUUM', 'VALID', 'VALIDATE', 'VALIDATOR',
'VALUE', 'VARIADIC', 'VARYING', 'VERBOSE', 'VERSION', 'VIEW', 'VIEWS', 'VOLATILE',
'WHITESPACE', 'WITHIN', 'WITHOUT', 'WORK', 'WRAPPER', 'WRITE', 'XML', 'XMLATTRIBUTES',
'XMLCONCAT', 'XMLELEMENT', 'XMLEXISTS', 'XMLFOREST', 'XMLNAMESPACES', 'XMLPARSE',
'XMLPI', 'XMLROOT', 'XMLSERIALIZE', 'XMLTABLE', 'YEAR', 'YES', 'ZONE'];
var kwSet = new Set(sqlKeywords.map(function(k) { return k.toUpperCase(); }));
return sql.replace(/("?)([A-Z][A-Z_0-9]{2,})("?)(?=[\s,)]|$)/g, function(match, q1, name, q2) {
if (q1 === '"' || q2 === '"') return match; // ja tem aspas
if (kwSet.has(name)) return match; // SQL keyword
if (/^\d/.test(name)) return match;
return '"' + name + '"';
});
}
function translateSql(sql, tableSet) {
let out = sql;
out = out.replace(/\bCONTAINING\s+(\?|\$\d+|'(?:[^']|'')*')/gi, "ILIKE ('%' || $1 || '%')");
out = out.replace(/\bFROM\s+RDB\$DATABASE\b/gi, '');
out = quoteTableNames(out, tableSet);
out = quoteColumnNames(out);
out = translateFirstSkip(out);
out = applyLimitTail(out);
out = convertPlaceholders(out);
return out;
}
function upperKeys(rows) {
return rows.map((row) => {
const o = {};
for (const k in row) o[k.toUpperCase()] = row[k];
return o;
});
}
async function query(config, sql, params = []) {
const entry = getEntry(config);
await ensureTableSet(entry);
const text = translateSql(sql, entry.tableSet);
try {
const res = await entry.pool.query(text, params);
return upperKeys(res.rows);
} catch (err) {
throw new Error(`Erro na consulta (postgres): ${err.message}`);
}
}
async function execute(config, sql, params = []) {
const entry = getEntry(config);
await ensureTableSet(entry);
const text = translateSql(sql, entry.tableSet);
try {
const res = await entry.pool.query(text, params);
return { affectedRows: res.rowCount || 0, result: upperKeys(res.rows || []) };
} catch (err) {
throw new Error(`Erro na execução (postgres): ${err.message}`);
}
}
async function testConnection(config) {
const entry = getEntry(config);
await entry.pool.query('SELECT 1');
return true;
}
async function listTables(config) {
const entry = getEntry(config);
const res = await entry.pool.query(
`SELECT table_name FROM information_schema.tables
WHERE table_schema = $1 AND table_type = 'BASE TABLE'
ORDER BY table_name`,
[safeSchema(config.schema)]
);
return res.rows.map((r) => r.table_name);
}
async function tableInfo(config, tableName) {
const entry = getEntry(config);
// Resolve no primeiro schema do search_path que contém a tabela
const res = await entry.pool.query(
`SELECT column_name, ordinal_position, data_type,
character_maximum_length, numeric_precision, numeric_scale, is_nullable
FROM information_schema.columns
WHERE UPPER(table_name) = UPPER($2)
AND table_schema = (
SELECT table_schema FROM information_schema.tables
WHERE UPPER(table_name) = UPPER($2) AND table_schema = ANY($1)
ORDER BY array_position($1, table_schema) LIMIT 1
)
ORDER BY ordinal_position`,
[entry.schemas, tableName]
);
return res.rows.map((row) => ({
name: row.column_name,
position: row.ordinal_position,
type: (row.data_type || '').toUpperCase(),
length: row.character_maximum_length,
precision: row.numeric_precision,
scale: row.numeric_scale,
nullable: row.is_nullable === 'YES',
}));
}
async function tableExists(config, tableName) {
const entry = getEntry(config);
await ensureTableSet(entry);
if (entry.tableSet) return entry.tableSet.has(String(tableName).toUpperCase());
const res = await entry.pool.query(
`SELECT 1 FROM information_schema.tables
WHERE table_schema = ANY($1) AND UPPER(table_name) = UPPER($2) LIMIT 1`,
[entry.schemas, tableName]
);
return res.rowCount > 0;
}
async function columnExists(config, tableName, columnName) {
const entry = getEntry(config);
const res = await entry.pool.query(
`SELECT 1 FROM information_schema.columns
WHERE table_schema = ANY($1) AND UPPER(table_name) = UPPER($2)
AND UPPER(column_name) = UPPER($3) LIMIT 1`,
[entry.schemas, tableName, columnName]
);
return res.rowCount > 0;
}
async function close() {
const all = Array.from(pools.values()).map((e) => e.pool.end().catch(() => {}));
pools.clear();
await Promise.all(all);
}
return {
query, execute, testConnection, listTables, tableInfo, tableExists, columnExists, close,
_translateSql: translateSql, // exportado para testes
};
})();
// ============================================================
// 4) DISPATCHER — API pública
// ============================================================
const drivers = { postgres: postgresDriver, firebird: firebirdDriver };
function getDriver(alias) {
const config = getConfig(alias); const config = getConfig(alias);
const driver = drivers[config.driver];
if (!driver) {
throw new Error(`Driver "${config.driver}" não suportado para o alias "${alias}".`);
}
return { config, driver }; return { config, driver };
} }
function query(alias, sql, params = []) { function query(alias, sql, params = []) {
let d; let d;
try { d = getDriver(alias); } catch (err) { return Promise.reject(err); } try { d = getConn(alias); } catch (err) { return Promise.reject(err); }
return d.driver.query(d.config, sql, params).catch((err) => { throw new Error(`[${alias}] ${err.message}`); }); return d.driver.query(d.config, sql, params).catch((err) => { throw new Error(`[${alias}] ${err.message}`); });
} }
function execute(alias, sql, params = []) { function execute(alias, sql, params = []) {
let d; let d;
try { d = getDriver(alias); } catch (err) { return Promise.reject(err); } try { d = getConn(alias); } catch (err) { return Promise.reject(err); }
return d.driver.execute(d.config, sql, params).catch((err) => { throw new Error(`[${alias}] ${err.message}`); }); return d.driver.execute(d.config, sql, params).catch((err) => { throw new Error(`[${alias}] ${err.message}`); });
} }
async function testConnection(alias) { async function testConnection(alias) {
try { try {
const { config, driver } = getDriver(alias); const { config } = getConn(alias);
await driver.testConnection(config); await driver.testConnection(config);
return true; return true;
} catch (err) { } catch (err) {
@@ -697,33 +342,33 @@ async function testAllConnections() {
} }
function listTables(alias) { function listTables(alias) {
const { config, driver } = getDriver(alias); const { config } = getConn(alias);
return driver.listTables(config); return driver.listTables(config);
} }
function tableInfo(alias, tableName) { function tableInfo(alias, tableName) {
const { config, driver } = getDriver(alias); const { config } = getConn(alias);
return driver.tableInfo(config, tableName); return driver.tableInfo(config, tableName);
} }
function tableExists(alias, tableName) { function tableExists(alias, tableName) {
const { config, driver } = getDriver(alias); const { config } = getConn(alias);
return driver.tableExists(config, tableName); return driver.tableExists(config, tableName);
} }
function columnExists(alias, tableName, columnName) { function columnExists(alias, tableName, columnName) {
const { config, driver } = getDriver(alias); const { config } = getConn(alias);
return driver.columnExists(config, tableName, columnName); return driver.columnExists(config, tableName, columnName);
} }
/** Nome do driver de um alias (ex.: 'postgres'). */ /** Nome do driver de um alias (sempre 'firebird'). */
function driverOf(alias) { function driverOf(alias) {
return getConfig(alias).driver; return 'firebird';
} }
/** Encerra os pools de todos os drivers (shutdown gracioso). */ /** Encerra recursos (noop para Firebird). */
async function closeAll() { async function closeAll() {
await Promise.all(Object.values(drivers).map((d) => d.close && d.close())); await driver.close();
} }
module.exports = { module.exports = {
+18 -1
View File
@@ -50,6 +50,23 @@ async function authenticateToken(req, res, next) {
.map(function (e) { return Number(e); }) .map(function (e) { return Number(e); })
.filter(function (n) { return !isNaN(n); }); .filter(function (n) { return !isNaN(n); });
} }
// Se o JWT não tem o array empresas (tokens legados), tenta carregar do BD
if (!Array.isArray(decoded.empresas) || decoded.empresas.length === 0) {
const alias = req.params.alias;
if (alias && decoded.id) {
try {
const empresas = await db.query(alias,
'SELECT USE_EMPRESA_ID FROM USUARIOS_EMPRESA WHERE USE_USUARIO_ID = ?',
[decoded.id]
);
decoded.empresas = empresas.map(e => Number(e.USE_EMPRESA_ID)).filter(n => !isNaN(n));
} catch (e) {
decoded.empresas = [];
}
} else {
decoded.empresas = [];
}
}
req.user = decoded; req.user = decoded;
req.authType = 'jwt'; req.authType = 'jwt';
return next(); return next();
@@ -90,7 +107,7 @@ async function authenticateToken(req, res, next) {
FROM USUARIOS FROM USUARIOS
WHERE USU_TOKEN = ? WHERE USU_TOKEN = ?
AND USU_STATUS = 'A' AND USU_STATUS = 'A'
AND COALESCE(USU_ACESSO_WEB, 0) = 1`, AND COALESCE(USU_ACESSO_WEB, '0') IN ('1', 'S')`,
[token] [token]
); );
+1 -1
View File
@@ -1,6 +1,6 @@
/** /**
* Estrutura do "Fluxo de Resolução" (motivos + resolução do atendimento). * Estrutura do "Fluxo de Resolução" (motivos + resolução do atendimento).
* Cria de forma idempotente, em Postgres ou Firebird: * Cria de forma idempotente no Firebird:
* - Tabela CHATC2_MOTIVOS_ATENDIMENTO * - Tabela CHATC2_MOTIVOS_ATENDIMENTO
* - Flags em CHATC2_CONFIGURACOES_EMPRESA (CFE_MOTIVO_*, CFE_RESOLUCAO_*) * - Flags em CHATC2_CONFIGURACOES_EMPRESA (CFE_MOTIVO_*, CFE_RESOLUCAO_*)
* - Colunas CON_MOTIVO_ID / CON_RESOLUCAO em CHATC2_CONVERSAS * - Colunas CON_MOTIVO_ID / CON_RESOLUCAO em CHATC2_CONVERSAS