Ajustes 2306
This commit is contained in:
+3
-15
@@ -2,23 +2,11 @@
|
||||
# Configuracao do Chatc2
|
||||
# ============================================================
|
||||
|
||||
# Driver padrao para novas conexoes (postgres | firebird)
|
||||
DB_DRIVER=postgres
|
||||
# Driver padrao (firebird)
|
||||
DB_DRIVER=firebird
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# Banco principal: PostgreSQL (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)
|
||||
# Banco Firebird (alias "novo_local")
|
||||
# ------------------------------------------------------------
|
||||
DB_HOST=localhost
|
||||
DB_PORT=3050
|
||||
|
||||
+21
-41
@@ -20,14 +20,13 @@ SERVER_DOMAIN="${SERVER_DOMAIN:-}" # Opcional: seu-dominio.com.br
|
||||
JWT_SECRET=""
|
||||
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.
|
||||
PG_HOST="${PG_HOST:-}"
|
||||
PG_PORT="${PG_PORT:-5432}"
|
||||
PG_USER="${PG_USER:-postgres}"
|
||||
PG_PASSWORD="${PG_PASSWORD:-}"
|
||||
PG_DATABASE="${PG_DATABASE:-postgres}"
|
||||
PG_SCHEMA="${PG_SCHEMA:-public}"
|
||||
DB_HOST="${DB_HOST:-localhost}"
|
||||
DB_PORT="${DB_PORT:-3050}"
|
||||
DB_USER="${DB_USER:-SYSDBA}"
|
||||
DB_PASSWORD="${DB_PASSWORD:-masterkey}"
|
||||
DB_DATABASE="${DB_DATABASE:-}"
|
||||
|
||||
# Cores
|
||||
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
|
||||
DB_DRIVER=postgres
|
||||
DB_DRIVER=firebird
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# PostgreSQL (EXTERNO) — banco principal "novo_local"
|
||||
# Firebird (alias "novo_local")
|
||||
# ------------------------------------------------------------
|
||||
PG_HOST=${PG_HOST}
|
||||
PG_PORT=${PG_PORT}
|
||||
PG_USER=${PG_USER}
|
||||
PG_PASSWORD=${PG_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_HOST=${DB_HOST}
|
||||
DB_PORT=${DB_PORT}
|
||||
DB_USER=${DB_USER}
|
||||
DB_PASSWORD=${DB_PASSWORD}
|
||||
DB_ENCODING=UTF-8
|
||||
# Caminho do .FDB (vazio = usa ../NOVO.FDB na raiz do projeto)
|
||||
DB_DATABASE=
|
||||
DB_DATABASE=${DB_DATABASE}
|
||||
|
||||
# Servidor
|
||||
PORT=3000
|
||||
@@ -289,12 +278,9 @@ EXTERNAL_URL=${EXTERNAL_URL}
|
||||
EOF
|
||||
chown "$CHATC2_USER:$CHATC2_USER" "$CHATC2_DIR/.env"
|
||||
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 " 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
|
||||
@@ -314,23 +300,22 @@ else
|
||||
warn "Apos copiar, execute: chown $CHATC2_USER:firebird $CHATC2_DIR/db/*.FDB && chmod 660 $CHATC2_DIR/db/*.FDB"
|
||||
fi
|
||||
|
||||
info "Passo 9/12: Migracoes (Firebird legado)..."
|
||||
# O schema do PostgreSQL e gerenciado no banco EXTERNO — nao migramos aqui.
|
||||
# Migracoes Firebird so rodam se houver um .FDB local (alias firebird_local).
|
||||
info "Passo 9/12: Migracoes (Firebird)..."
|
||||
# Migracoes Firebird rodam se houver um .FDB local (alias novo_local).
|
||||
if ls "$CHATC2_DIR/db/"*.FDB "$CHATC2_DIR/"*.FDB >/dev/null 2>&1; 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"
|
||||
fi
|
||||
else
|
||||
info "Sem .FDB local — pulando migracoes Firebird (schema do Postgres e externo)."
|
||||
info "Sem .FDB local — pulando migracoes Firebird."
|
||||
fi
|
||||
|
||||
# ████████████████████████████████████████████████████
|
||||
# PASSO 9b: HABILITAR USUARIOS ADMIN PARA 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'
|
||||
const db = require('DATABASE_PATH');
|
||||
|
||||
@@ -352,16 +337,11 @@ const db = require('DATABASE_PATH');
|
||||
SCRIPTJS
|
||||
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
|
||||
|
||||
# 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
|
||||
log "Usuarios admin verificados (Postgres + Firebird se presente)"
|
||||
log "Usuarios admin verificados (Firebird)"
|
||||
|
||||
# ████████████████████████████████████████████████████
|
||||
# PASSO 10: NGINX
|
||||
|
||||
Generated
+5
-151
@@ -7,6 +7,7 @@
|
||||
"": {
|
||||
"name": "chatc2",
|
||||
"version": "2.0.0",
|
||||
"hasInstallScript": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"cors": "^2.8.6",
|
||||
@@ -14,8 +15,7 @@
|
||||
"express": "^5.2.1",
|
||||
"ffmpeg-static": "^5.3.0",
|
||||
"jsonwebtoken": "^9.0.3",
|
||||
"node-firebird": "^2.3.1",
|
||||
"pg": "^8.21.0"
|
||||
"node-firebird": "^2.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@derhuerst/http-basic": {
|
||||
@@ -832,134 +832,6 @@
|
||||
"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": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
|
||||
@@ -983,9 +855,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.15.1",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz",
|
||||
"integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==",
|
||||
"version": "6.15.2",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz",
|
||||
"integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"side-channel": "^1.1.0"
|
||||
@@ -1212,15 +1084,6 @@
|
||||
"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": {
|
||||
"version": "2.0.2",
|
||||
"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",
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||
"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
@@ -1,14 +1,14 @@
|
||||
{
|
||||
"name": "chatc2",
|
||||
"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",
|
||||
"scripts": {
|
||||
"start": "node src/server.js",
|
||||
"dev": "node --watch src/server.js"
|
||||
"dev": "node --watch src/server.js",
|
||||
"postinstall": "node scripts/postinstall.js"
|
||||
},
|
||||
"keywords": [
|
||||
"postgres",
|
||||
"firebird",
|
||||
"api",
|
||||
"express"
|
||||
@@ -22,7 +22,6 @@
|
||||
"express": "^5.2.1",
|
||||
"ffmpeg-static": "^5.3.0",
|
||||
"jsonwebtoken": "^9.0.3",
|
||||
"node-firebird": "^2.3.1",
|
||||
"pg": "^8.21.0"
|
||||
"node-firebird": "^2.3.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
@@ -22,9 +22,8 @@
|
||||
const db = require('../src/database');
|
||||
|
||||
// Este script aplica DDL no dialeto Firebird (BLOB SUB_TYPE, etc.).
|
||||
// O schema do PostgreSQL é gerenciado externamente (banco externo) — use este
|
||||
// script apenas para bancos Firebird. Alias padrão: firebird_local.
|
||||
const alias = process.argv[2] || 'firebird_local';
|
||||
// Alias padrão: novo_local.
|
||||
const alias = process.argv[2] || 'novo_local';
|
||||
|
||||
// ============================================================
|
||||
// CONTROLE DE MIGRAÇÕES
|
||||
@@ -428,8 +427,7 @@ async function main() {
|
||||
catch (e) { console.error('❌', e.message); process.exit(1); }
|
||||
if (driver !== '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 firebird_local\n');
|
||||
console.log(' Para migrar um banco Firebird: node scripts/migracoes.js novo_local\n');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -62,9 +62,9 @@ app.use(routes);
|
||||
// Rota raiz
|
||||
app.get('/', (req, res) => {
|
||||
res.json({
|
||||
name: 'API Firebird - Chatc2',
|
||||
name: 'API Chatc2 - Firebird',
|
||||
version: '2.0.0',
|
||||
description: 'API multi-banco com sistema de aliases',
|
||||
description: 'API Firebird com sistema de aliases',
|
||||
auth: {
|
||||
loginPage: 'GET /app/:alias/login',
|
||||
login: 'POST /app/:alias/login',
|
||||
|
||||
@@ -75,7 +75,7 @@ class AuthController {
|
||||
FROM USUARIOS
|
||||
WHERE USU_LOGIN = ?
|
||||
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'
|
||||
`;
|
||||
|
||||
|
||||
@@ -148,7 +148,7 @@ class ChatController {
|
||||
const statusFilter = req.query.status || 'A,E';
|
||||
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.' });
|
||||
|
||||
// Status via parâmetros (evita SQL injection). Aceita apenas A/E/F.
|
||||
@@ -260,7 +260,7 @@ class ChatController {
|
||||
var minhasEquipes = await db.query(alias,
|
||||
'SELECT EQU_EQUIPE_ID FROM CHATC2_USU_EQUIPES WHERE EQU_USUARIO_ID = ?', [userId]);
|
||||
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,
|
||||
`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]);
|
||||
@@ -296,7 +296,7 @@ class ChatController {
|
||||
const row = result[0];
|
||||
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.' });
|
||||
|
||||
// Verifica se o número pertence a um dependente
|
||||
@@ -471,7 +471,7 @@ class ChatController {
|
||||
if (r.length === 0) return { status: 404 };
|
||||
const emp = r[0].CON_EMPRESA_ID;
|
||||
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 };
|
||||
}
|
||||
|
||||
@@ -542,7 +542,7 @@ class ChatController {
|
||||
|
||||
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[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.' });
|
||||
|
||||
// Se a conversa estiver finalizada, reabre
|
||||
@@ -620,8 +620,8 @@ class ChatController {
|
||||
|
||||
// Insere mensagem no banco
|
||||
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)
|
||||
VALUES (?, ?, 'U', ?, ?, ?, ?, CURRENT_TIMESTAMP, ?)
|
||||
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', ?, ?, ?, ?, 'A', CURRENT_TIMESTAMP, ?)
|
||||
`, [newId, id, usuarioId, textoFinal, tipo || 'text', privada === 'S' ? 'S' : 'N', midiaId]);
|
||||
|
||||
// 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.' });
|
||||
|
||||
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.' });
|
||||
|
||||
// ===== Fluxo de Resolução: valida motivo/resolução conforme config =====
|
||||
@@ -781,7 +781,7 @@ class ChatController {
|
||||
|
||||
// Verifica permissão
|
||||
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.' });
|
||||
}
|
||||
|
||||
@@ -827,8 +827,8 @@ class ChatController {
|
||||
const maxMsgId = await db.query(alias, 'SELECT MAX(CME_CODIGO_ID) AS ID FROM CHATC2_CONVERSAS_MENSAGENS');
|
||||
const msgId = (maxMsgId[0]?.ID || 0) + 1;
|
||||
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)
|
||||
VALUES (?, ?, 'U', ?, ?, 'text', CURRENT_TIMESTAMP)
|
||||
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', 'A', CURRENT_TIMESTAMP)
|
||||
`, [msgId, conversaId, req.user?.id, mensagem]);
|
||||
|
||||
await db.execute(alias,
|
||||
@@ -860,8 +860,8 @@ class ChatController {
|
||||
// Insere conversa
|
||||
await db.execute(alias, `
|
||||
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)
|
||||
VALUES (?, ?, ?, ?, ?, ?, 'A', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
|
||||
CON_NOME_CONTATO, CON_CLIENTE_ID, CON_STATUS, CON_SITUACAO, CON_PRIMEIRA_MSG, CON_DT_ULTIMA_MSG)
|
||||
VALUES (?, ?, ?, ?, ?, ?, 'A', 'A', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
|
||||
`, [newId, empresaId, instId, numeroLimpo, nomeContato || numeroLimpo, clienteId || null]);
|
||||
|
||||
// Insere primeira mensagem
|
||||
@@ -869,8 +869,8 @@ class ChatController {
|
||||
const msgId = (maxMsgId[0]?.ID || 0) + 1;
|
||||
|
||||
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)
|
||||
VALUES (?, ?, 'U', ?, ?, 'text', CURRENT_TIMESTAMP)
|
||||
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', 'A', CURRENT_TIMESTAMP)
|
||||
`, [msgId, newId, req.user?.id, mensagem]);
|
||||
|
||||
// Envia via Evolution API
|
||||
@@ -1058,7 +1058,7 @@ class ChatController {
|
||||
mimeType = mimeType.split(';')[0].trim();
|
||||
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;
|
||||
if (rawBuffer == null) {
|
||||
return respond(404, { success: false, error: 'Mídia sem conteúdo.' });
|
||||
@@ -1179,7 +1179,7 @@ class ChatController {
|
||||
const cli = await db.query(alias,
|
||||
'SELECT CLI_EMPRESA_ID FROM CLIENTES WHERE CLI_CODIGO_ID = ?', [cid]);
|
||||
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 { 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 newId = (maxId[0]?.ID || 0) + 1;
|
||||
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)
|
||||
VALUES (?, ?, 'U', ?, ?, 'text', 'N', CURRENT_TIMESTAMP)
|
||||
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', 'A', CURRENT_TIMESTAMP)
|
||||
`, [newId, id, req.user?.id, texto]);
|
||||
await db.execute(alias,
|
||||
'UPDATE CHATC2_CONVERSAS SET CON_DT_ULTIMA_MSG = CURRENT_TIMESTAMP WHERE CON_CODIGO_ID = ?', [id]);
|
||||
|
||||
@@ -89,59 +89,29 @@ class DatabaseController {
|
||||
return res.status(400).json({ success: false, error: 'database é obrigatório.' });
|
||||
}
|
||||
|
||||
var driver = (req.body.driver || DEFAULT_DRIVER).toLowerCase();
|
||||
|
||||
if (driver === 'postgres') {
|
||||
var pg = require('pg');
|
||||
var schema = String(req.body.schema || 'public').trim();
|
||||
var safe = /^[A-Za-z_][A-Za-z0-9_$]*$/.test(schema) ? schema : 'public';
|
||||
var searchPath = safe === 'public' ? 'public' : safe + ',public';
|
||||
var client = new pg.Client({
|
||||
host: req.body.host || '127.0.0.1',
|
||||
port: req.body.port || 5432,
|
||||
database: req.body.database,
|
||||
user: req.body.user || 'postgres',
|
||||
password: req.body.password || 'postgres',
|
||||
ssl: req.body.ssl ? { rejectUnauthorized: false } : false,
|
||||
options: '-c search_path=' + searchPath,
|
||||
connectionTimeoutMillis: 8000,
|
||||
});
|
||||
try {
|
||||
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);
|
||||
});
|
||||
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) {
|
||||
res.status(500).json({ success: false, error: err.message });
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ class EmpresaController {
|
||||
} 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;
|
||||
let base64;
|
||||
if (Buffer.isBuffer(blobData)) {
|
||||
|
||||
@@ -242,18 +242,18 @@ class EvolutionController {
|
||||
});
|
||||
instanceCreated = true;
|
||||
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)
|
||||
VALUES (?, ?, ?, ?, ?, ?, 'C')
|
||||
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', 'A')
|
||||
`, [newId, INS_EMPRESA_ID || userEmpresas[0], INS_NOME, INS_URL, INS_API_KEY, INS_INSTANCE_NAME]);
|
||||
break;
|
||||
} catch (e) { /* Tenta próximo endpoint */ }
|
||||
}
|
||||
|
||||
if (!instanceCreated) {
|
||||
// Salva mesmo sem conseguir criar na Evolution
|
||||
// Salva mesmo sem conseguir criar na Evolution (status 'D' = desconectado)
|
||||
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)
|
||||
VALUES (?, ?, ?, ?, ?, ?, 'D')
|
||||
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', 'A')
|
||||
`, [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) {
|
||||
try {
|
||||
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,
|
||||
'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, `
|
||||
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)
|
||||
VALUES (?, ?, ?, ?, ?, ?, 'E', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
|
||||
CON_NOME_CONTATO, CON_CLIENTE_ID, CON_STATUS, CON_SITUACAO, CON_PRIMEIRA_MSG, CON_DT_ULTIMA_MSG)
|
||||
VALUES (?, ?, ?, ?, ?, ?, 'E', 'A', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
|
||||
`, [newId, empresaId, instanciaId, numero, nomeFinal, clienteId]);
|
||||
|
||||
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) {
|
||||
const maxMsgId = await db.query(alias, 'SELECT MAX(CME_CODIGO_ID) AS ID FROM CHATC2_CONVERSAS_MENSAGENS');
|
||||
await db.execute(alias, `
|
||||
INSERT INTO CHATC2_CONVERSAS_MENSAGENS (CME_CODIGO_ID, CME_CONVERSA_ID, CME_REMETENTE, CME_TEXTO, CME_TIPO, CME_DT_ENVIO)
|
||||
VALUES (?, ?, 'S', ?, 'text', CURRENT_TIMESTAMP)
|
||||
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', 'A', CURRENT_TIMESTAMP)
|
||||
`, [(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]);
|
||||
|
||||
@@ -1205,13 +1218,13 @@ async function processWebhook(alias, body) {
|
||||
|
||||
if (midiaId) {
|
||||
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)
|
||||
VALUES (?, ?, 'C', ?, ?, CURRENT_TIMESTAMP, ?)
|
||||
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', ?, ?, 'A', CURRENT_TIMESTAMP, ?)
|
||||
`, [(maxMsgId[0]?.ID || 0) + 1, conversa.CON_CODIGO_ID, messageText, messageType, midiaId]);
|
||||
} else {
|
||||
await db.execute(alias, `
|
||||
INSERT INTO CHATC2_CONVERSAS_MENSAGENS (CME_CODIGO_ID, CME_CONVERSA_ID, CME_REMETENTE, CME_TEXTO, CME_TIPO, CME_DT_ENVIO)
|
||||
VALUES (?, ?, 'C', ?, ?, CURRENT_TIMESTAMP)
|
||||
INSERT INTO CHATC2_CONVERSAS_MENSAGENS (CME_CODIGO_ID, CME_CONVERSA_ID, CME_REMETENTE, CME_TEXTO, CME_TIPO, CME_SITUACAO, CME_DT_ENVIO)
|
||||
VALUES (?, ?, 'C', ?, ?, 'A', CURRENT_TIMESTAMP)
|
||||
`, [(maxMsgId[0]?.ID || 0) + 1, conversa.CON_CODIGO_ID, messageText, messageType]);
|
||||
}
|
||||
|
||||
|
||||
@@ -145,14 +145,14 @@ class RoutesController {
|
||||
'GET /api/databases': { desc: 'Lista as conexões de banco cadastradas (senhas ocultas).' },
|
||||
'POST /api/databases': {
|
||||
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'],
|
||||
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).' },
|
||||
'POST /api/databases/test': {
|
||||
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'],
|
||||
},
|
||||
|
||||
|
||||
@@ -72,8 +72,8 @@ class TriageController {
|
||||
// Salva mensagem do sistema
|
||||
const maxMsgId = await db.query(alias, 'SELECT MAX(CME_CODIGO_ID) AS ID FROM CHATC2_CONVERSAS_MENSAGENS');
|
||||
await db.execute(alias, `
|
||||
INSERT INTO CHATC2_CONVERSAS_MENSAGENS (CME_CODIGO_ID, CME_CONVERSA_ID, CME_REMETENTE, CME_TEXTO, CME_TIPO, CME_DT_ENVIO)
|
||||
VALUES (?, ?, 'S', ?, 'triagem', CURRENT_TIMESTAMP)
|
||||
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', 'A', CURRENT_TIMESTAMP)
|
||||
`, [(maxMsgId[0]?.ID || 0) + 1, conversaId, menuText]);
|
||||
|
||||
// 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');
|
||||
await db.execute(alias, `
|
||||
INSERT INTO CHATC2_CONVERSAS_MENSAGENS (CME_CODIGO_ID, CME_CONVERSA_ID, CME_REMETENTE, CME_TEXTO, CME_TIPO, CME_DT_ENVIO)
|
||||
VALUES (?, ?, 'S', ?, 'triagem', CURRENT_TIMESTAMP)
|
||||
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', 'A', CURRENT_TIMESTAMP)
|
||||
`, [(maxMsgId[0]?.ID || 0) + 1, conversaId, menuText]);
|
||||
await TriageController.sendEvolution(alias, instanciaId, numero, menuText);
|
||||
|
||||
@@ -246,8 +246,8 @@ class TriageController {
|
||||
if (msgAfter) {
|
||||
const maxMsgId = await db.query(alias, 'SELECT MAX(CME_CODIGO_ID) AS ID FROM CHATC2_CONVERSAS_MENSAGENS');
|
||||
await db.execute(alias, `
|
||||
INSERT INTO CHATC2_CONVERSAS_MENSAGENS (CME_CODIGO_ID, CME_CONVERSA_ID, CME_REMETENTE, CME_TEXTO, CME_TIPO, CME_DT_ENVIO)
|
||||
VALUES (?, ?, 'S', ?, 'text', CURRENT_TIMESTAMP)
|
||||
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', 'A', CURRENT_TIMESTAMP)
|
||||
`, [(maxMsgId[0]?.ID || 0) + 1, conversaId, msgAfter]);
|
||||
await TriageController.sendEvolution(alias, instanciaId, numero, msgAfter);
|
||||
}
|
||||
@@ -377,8 +377,8 @@ class TriageController {
|
||||
if (textoMsg) {
|
||||
const maxMsgId = await db.query(alias, 'SELECT MAX(CME_CODIGO_ID) AS ID FROM CHATC2_CONVERSAS_MENSAGENS');
|
||||
await db.execute(alias, `
|
||||
INSERT INTO CHATC2_CONVERSAS_MENSAGENS (CME_CODIGO_ID, CME_CONVERSA_ID, CME_REMETENTE, CME_TEXTO, CME_TIPO, CME_DT_ENVIO)
|
||||
VALUES (?, ?, 'S', ?, 'text', CURRENT_TIMESTAMP)
|
||||
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', 'A', CURRENT_TIMESTAMP)
|
||||
`, [(maxMsgId[0]?.ID || 0) + 1, conversaId, 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');
|
||||
await db.execute(alias, `
|
||||
INSERT INTO CHATC2_CONVERSAS_MENSAGENS (CME_CODIGO_ID, CME_CONVERSA_ID, CME_REMETENTE, CME_TEXTO, CME_TIPO, CME_DT_ENVIO)
|
||||
VALUES (?, ?, 'S', ?, 'text', CURRENT_TIMESTAMP)
|
||||
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', 'A', CURRENT_TIMESTAMP)
|
||||
`, [(maxMsgId[0]?.ID || 0) + 1, conversaId, msgPrompt]);
|
||||
await TriageController.sendEvolution(alias, instanciaId, numero, msgPrompt);
|
||||
|
||||
@@ -521,8 +521,8 @@ class TriageController {
|
||||
if (resultado) {
|
||||
const maxMsgId = await db.query(alias, 'SELECT MAX(CME_CODIGO_ID) AS ID FROM CHATC2_CONVERSAS_MENSAGENS');
|
||||
await db.execute(alias, `
|
||||
INSERT INTO CHATC2_CONVERSAS_MENSAGENS (CME_CODIGO_ID, CME_CONVERSA_ID, CME_REMETENTE, CME_TEXTO, CME_TIPO, CME_DT_ENVIO)
|
||||
VALUES (?, ?, 'S', ?, 'text', CURRENT_TIMESTAMP)
|
||||
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', 'A', CURRENT_TIMESTAMP)
|
||||
`, [(maxMsgId[0]?.ID || 0) + 1, conversaId, 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.';
|
||||
const maxMsgId = await db.query(alias, 'SELECT MAX(CME_CODIGO_ID) AS ID FROM CHATC2_CONVERSAS_MENSAGENS');
|
||||
await db.execute(alias, `
|
||||
INSERT INTO CHATC2_CONVERSAS_MENSAGENS (CME_CODIGO_ID, CME_CONVERSA_ID, CME_REMETENTE, CME_TEXTO, CME_TIPO, CME_DT_ENVIO)
|
||||
VALUES (?, ?, 'S', ?, 'text', CURRENT_TIMESTAMP)
|
||||
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', 'A', CURRENT_TIMESTAMP)
|
||||
`, [(maxMsgId[0]?.ID || 0) + 1, conversaId, 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]);
|
||||
@@ -605,8 +605,8 @@ class TriageController {
|
||||
logTriage('Boleto - NENHUM título em aberto');
|
||||
const maxMsgId = await db.query(alias, 'SELECT MAX(CME_CODIGO_ID) AS ID FROM CHATC2_CONVERSAS_MENSAGENS');
|
||||
await db.execute(alias, `
|
||||
INSERT INTO CHATC2_CONVERSAS_MENSAGENS (CME_CODIGO_ID, CME_CONVERSA_ID, CME_REMETENTE, CME_TEXTO, CME_TIPO, CME_DT_ENVIO)
|
||||
VALUES (?, ?, 'S', ?, 'text', CURRENT_TIMESTAMP)
|
||||
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', 'A', CURRENT_TIMESTAMP)
|
||||
`, [(maxMsgId[0]?.ID || 0) + 1, conversaId, 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]);
|
||||
@@ -624,8 +624,8 @@ class TriageController {
|
||||
|
||||
const maxMsgId2 = await db.query(alias, 'SELECT MAX(CME_CODIGO_ID) AS ID FROM CHATC2_CONVERSAS_MENSAGENS');
|
||||
await db.execute(alias, `
|
||||
INSERT INTO CHATC2_CONVERSAS_MENSAGENS (CME_CODIGO_ID, CME_CONVERSA_ID, CME_REMETENTE, CME_TEXTO, CME_TIPO, CME_DT_ENVIO)
|
||||
VALUES (?, ?, 'S', ?, 'text', CURRENT_TIMESTAMP)
|
||||
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', 'A', CURRENT_TIMESTAMP)
|
||||
`, [(maxMsgId2[0]?.ID || 0) + 1, conversaId, msgBoleto]);
|
||||
await TriageController.sendEvolution(alias, instanciaId, numero, msgBoleto);
|
||||
|
||||
@@ -652,8 +652,8 @@ class TriageController {
|
||||
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');
|
||||
await db.execute(alias, `
|
||||
INSERT INTO CHATC2_CONVERSAS_MENSAGENS (CME_CODIGO_ID, CME_CONVERSA_ID, CME_REMETENTE, CME_TEXTO, CME_TIPO, CME_DT_ENVIO)
|
||||
VALUES (?, ?, 'S', ?, 'text', CURRENT_TIMESTAMP)
|
||||
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', 'A', CURRENT_TIMESTAMP)
|
||||
`, [(maxId[0]?.ID || 0) + 1, conversaId, msg]);
|
||||
await TriageController.sendEvolution(alias, instanciaId, numero, msg);
|
||||
return;
|
||||
@@ -838,8 +838,8 @@ class TriageController {
|
||||
|
||||
const maxId = await db.query(alias, 'SELECT MAX(CME_CODIGO_ID) AS ID FROM CHATC2_CONVERSAS_MENSAGENS');
|
||||
await db.execute(alias, `
|
||||
INSERT INTO CHATC2_CONVERSAS_MENSAGENS (CME_CODIGO_ID, CME_CONVERSA_ID, CME_REMETENTE, CME_TEXTO, CME_TIPO, CME_DT_ENVIO)
|
||||
VALUES (?, ?, 'S', ?, 'text', CURRENT_TIMESTAMP)
|
||||
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', 'A', CURRENT_TIMESTAMP)
|
||||
`, [(maxId[0]?.ID || 0) + 1, conversaId, 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';
|
||||
const maxIdCont = await db.query(alias, 'SELECT MAX(CME_CODIGO_ID) AS ID FROM CHATC2_CONVERSAS_MENSAGENS');
|
||||
await db.execute(alias, `
|
||||
INSERT INTO CHATC2_CONVERSAS_MENSAGENS (CME_CODIGO_ID, CME_CONVERSA_ID, CME_REMETENTE, CME_TEXTO, CME_TIPO, CME_DT_ENVIO)
|
||||
VALUES (?, ?, 'S', ?, 'text', CURRENT_TIMESTAMP)
|
||||
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', 'A', CURRENT_TIMESTAMP)
|
||||
`, [(maxIdCont[0]?.ID || 0) + 1, conversaId, msgCont]);
|
||||
await TriageController.sendEvolution(alias, instanciaId, numero, msgCont);
|
||||
|
||||
@@ -889,8 +889,8 @@ class TriageController {
|
||||
var msgFinal = 'Atendimento finalizado. Obrigado pelo contato!';
|
||||
const maxId = await db.query(alias, 'SELECT MAX(CME_CODIGO_ID) AS ID FROM CHATC2_CONVERSAS_MENSAGENS');
|
||||
await db.execute(alias, `
|
||||
INSERT INTO CHATC2_CONVERSAS_MENSAGENS (CME_CODIGO_ID, CME_CONVERSA_ID, CME_REMETENTE, CME_TEXTO, CME_TIPO, CME_DT_ENVIO)
|
||||
VALUES (?, ?, 'S', ?, 'text', CURRENT_TIMESTAMP)
|
||||
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', 'A', CURRENT_TIMESTAMP)
|
||||
`, [(maxId[0]?.ID || 0) + 1, conversaId, 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]);
|
||||
@@ -898,8 +898,8 @@ class TriageController {
|
||||
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');
|
||||
await db.execute(alias, `
|
||||
INSERT INTO CHATC2_CONVERSAS_MENSAGENS (CME_CODIGO_ID, CME_CONVERSA_ID, CME_REMETENTE, CME_TEXTO, CME_TIPO, CME_DT_ENVIO)
|
||||
VALUES (?, ?, 'S', ?, 'text', CURRENT_TIMESTAMP)
|
||||
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', 'A', CURRENT_TIMESTAMP)
|
||||
`, [(maxId[0]?.ID || 0) + 1, conversaId, msgInv]);
|
||||
await TriageController.sendEvolution(alias, instanciaId, numero, msgInv);
|
||||
}
|
||||
@@ -916,8 +916,8 @@ class TriageController {
|
||||
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');
|
||||
await db.execute(alias, `
|
||||
INSERT INTO CHATC2_CONVERSAS_MENSAGENS (CME_CODIGO_ID, CME_CONVERSA_ID, CME_REMETENTE, CME_TEXTO, CME_TIPO, CME_DT_ENVIO)
|
||||
VALUES (?, ?, 'S', ?, 'text', CURRENT_TIMESTAMP)
|
||||
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', 'A', CURRENT_TIMESTAMP)
|
||||
`, [(maxId[0]?.ID || 0) + 1, conversaId, msg]);
|
||||
await TriageController.sendEvolution(alias, instanciaId, numero, msg);
|
||||
|
||||
@@ -949,8 +949,8 @@ class TriageController {
|
||||
if (texto) {
|
||||
const maxId = await db.query(alias, 'SELECT MAX(CME_CODIGO_ID) AS ID FROM CHATC2_CONVERSAS_MENSAGENS');
|
||||
await db.execute(alias, `
|
||||
INSERT INTO CHATC2_CONVERSAS_MENSAGENS (CME_CODIGO_ID, CME_CONVERSA_ID, CME_REMETENTE, CME_TEXTO, CME_TIPO, CME_DT_ENVIO)
|
||||
VALUES (?, ?, 'S', ?, 'text', CURRENT_TIMESTAMP)
|
||||
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', 'A', CURRENT_TIMESTAMP)
|
||||
`, [(maxId[0]?.ID || 0) + 1, conversaId, texto]);
|
||||
await TriageController.sendEvolution(alias, instanciaId, numero, texto);
|
||||
}
|
||||
|
||||
+153
-508
@@ -5,13 +5,11 @@
|
||||
*
|
||||
* 1) CONFIGURAÇÃO — aliases, drivers, conexões estáticas e customizadas
|
||||
* 2) DRIVER FIREBIRD
|
||||
* 3) DRIVER POSTGRES (pool + tradutor de SQL + schema)
|
||||
* 4) DISPATCHER — API pública usada por todo o sistema
|
||||
* 3) DISPATCHER — API pública usada por todo o sistema
|
||||
*
|
||||
* Suporta múltiplos drivers ('postgres' padrão, 'firebird' legado). Cada
|
||||
* conexão declara o campo `driver`. Conexões adicionadas em runtime (via API
|
||||
* /api/databases) são persistidas em databases_custom.json (ao lado deste
|
||||
* arquivo) — é um arquivo de DADOS, não de código.
|
||||
* Suporte ao banco Firebird. Cada conexão declara o campo `driver`.
|
||||
* Conexões adicionadas em runtime (via API /api/databases) são persistidas
|
||||
* em databases_custom.json (ao lado deste arquivo).
|
||||
*/
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
@@ -31,22 +29,11 @@ try {
|
||||
console.error('[Databases] Erro ao carregar databases_custom.json:', e.message);
|
||||
}
|
||||
|
||||
const DRIVERS = ['postgres', 'firebird'];
|
||||
const DEFAULT_DRIVER = (process.env.DB_DRIVER || 'postgres').toLowerCase();
|
||||
const DRIVERS = ['firebird'];
|
||||
const DEFAULT_DRIVER = 'firebird';
|
||||
|
||||
// Valores padrão por driver
|
||||
// Valores padrão para Firebird
|
||||
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: {
|
||||
host: 'localhost',
|
||||
port: 3050,
|
||||
@@ -61,22 +48,10 @@ const DRIVER_DEFAULTS = {
|
||||
|
||||
/**
|
||||
* Conexões estáticas.
|
||||
* - `novo_local` → PostgreSQL (conexão principal)
|
||||
* - `firebird_local`→ Firebird (informe o CAMINHO do arquivo .FDB em `database`)
|
||||
* Sobrescrevíveis pelo .env (PG_* para Postgres, DB_* para Firebird).
|
||||
* - `novo_local` → Firebird (informe o CAMINHO do arquivo .FDB em `database`)
|
||||
* Sobrescrevível pelo .env (DB_*).
|
||||
*/
|
||||
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: {
|
||||
driver: 'firebird',
|
||||
host: process.env.DB_HOST || 'localhost',
|
||||
@@ -105,30 +80,10 @@ function salvarCustomDatabases() {
|
||||
|
||||
/** Normaliza uma config bruta aplicando o driver e seus defaults. */
|
||||
function normalize(raw) {
|
||||
const driver = DRIVERS.includes((raw.driver || '').toLowerCase())
|
||||
? raw.driver.toLowerCase()
|
||||
: DEFAULT_DRIVER;
|
||||
const d = DRIVER_DEFAULTS[driver];
|
||||
const d = DRIVER_DEFAULTS['firebird'];
|
||||
|
||||
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 {
|
||||
driver,
|
||||
driver: 'firebird',
|
||||
host: raw.host || d.host,
|
||||
port: raw.port || d.port,
|
||||
database: raw.database,
|
||||
@@ -178,14 +133,10 @@ function addDatabase(alias, config) {
|
||||
throw new Error('Alias e database são obrigatórios.');
|
||||
}
|
||||
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 };
|
||||
['host', 'port', 'database', 'schema', 'user', 'password', 'ssl',
|
||||
'encoding', 'lowercase_keys', 'role', 'pageSize', 'wireCrypt',
|
||||
'max', 'idleTimeoutMillis', 'connectionTimeoutMillis']
|
||||
var stored = { driver: 'firebird' };
|
||||
['host', 'port', 'database', 'user', 'password',
|
||||
'encoding', 'lowercase_keys', 'role', 'pageSize', 'wireCrypt']
|
||||
.forEach(function (k) {
|
||||
if (config[k] !== undefined && config[k] !== null && config[k] !== '') {
|
||||
stored[k] = config[k];
|
||||
@@ -208,478 +159,172 @@ function removeDatabase(alias) {
|
||||
// ============================================================
|
||||
// 2) DRIVER FIREBIRD
|
||||
// ============================================================
|
||||
const firebirdDriver = (() => {
|
||||
const Firebird = require('node-firebird');
|
||||
const Firebird = require('node-firebird');
|
||||
|
||||
function readBlob(blobFunc) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
if (typeof blobFunc !== 'function') return resolve(blobFunc);
|
||||
blobFunc(function (err, name, emitter) {
|
||||
if (err) return reject(err);
|
||||
if (!emitter || typeof emitter.on !== 'function') return resolve(null);
|
||||
var chunks = [];
|
||||
var total = 0;
|
||||
emitter.on('data', function (c) { chunks.push(c); total += c.length; });
|
||||
emitter.on('end', function () { resolve(Buffer.concat(chunks, total)); });
|
||||
emitter.on('error', reject);
|
||||
});
|
||||
function readBlob(blobFunc) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
if (typeof blobFunc !== 'function') return resolve(blobFunc);
|
||||
blobFunc(function (err, name, emitter) {
|
||||
if (err) return reject(err);
|
||||
if (!emitter || typeof emitter.on !== 'function') return resolve(null);
|
||||
var chunks = [];
|
||||
var total = 0;
|
||||
emitter.on('data', function (c) { chunks.push(c); total += c.length; });
|
||||
emitter.on('end', function () { resolve(Buffer.concat(chunks, total)); });
|
||||
emitter.on('error', reject);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function query(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}`));
|
||||
var allRows = [];
|
||||
db.sequentially(sql, params, function (row) {
|
||||
var keys = Object.keys(row);
|
||||
var blobPromises = keys.map(function (key) {
|
||||
var val = row[key];
|
||||
if (typeof val === 'function') {
|
||||
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}`));
|
||||
function firebirdQuery(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}`));
|
||||
var allRows = [];
|
||||
db.sequentially(sql, params, function (row) {
|
||||
var keys = Object.keys(row);
|
||||
var blobPromises = keys.map(function (key) {
|
||||
var val = row[key];
|
||||
if (typeof val === 'function') {
|
||||
return readBlob(val).then(function (data) { row[key] = data; });
|
||||
}
|
||||
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 });
|
||||
});
|
||||
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 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) {
|
||||
await query(config, 'SELECT 1 FROM RDB$DATABASE');
|
||||
return true;
|
||||
}
|
||||
async function firebirdTestConnection(config) {
|
||||
await firebirdQuery(config, 'SELECT 1 FROM RDB$DATABASE');
|
||||
return true;
|
||||
}
|
||||
|
||||
async function listTables(config) {
|
||||
const rows = await query(config, `
|
||||
SELECT TRIM(RDB$RELATION_NAME) AS TABLE_NAME
|
||||
FROM RDB$RELATIONS
|
||||
WHERE RDB$SYSTEM_FLAG = 0 AND RDB$RELATION_TYPE = 0
|
||||
ORDER BY RDB$RELATION_NAME
|
||||
`);
|
||||
return rows.map((r) => (r.TABLE_NAME || '').trim()).filter(Boolean);
|
||||
}
|
||||
async function firebirdListTables(config) {
|
||||
const rows = await firebirdQuery(config, `
|
||||
SELECT TRIM(RDB$RELATION_NAME) AS TABLE_NAME
|
||||
FROM RDB$RELATIONS
|
||||
WHERE RDB$SYSTEM_FLAG = 0 AND RDB$RELATION_TYPE = 0
|
||||
ORDER BY RDB$RELATION_NAME
|
||||
`);
|
||||
return rows.map((r) => (r.TABLE_NAME || '').trim()).filter(Boolean);
|
||||
}
|
||||
|
||||
async function tableInfo(config, tableName) {
|
||||
const rows = await query(config, `
|
||||
SELECT
|
||||
rf.RDB$FIELD_NAME AS COLUMN_NAME,
|
||||
rf.RDB$FIELD_POSITION AS ORDINAL_POSITION,
|
||||
CASE f.RDB$FIELD_TYPE
|
||||
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 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 261 THEN 'BLOB' WHEN 14 THEN 'CHAR' WHEN 41 THEN 'NUMERIC'
|
||||
ELSE 'UNKNOWN'
|
||||
END AS DATA_TYPE,
|
||||
f.RDB$FIELD_LENGTH AS FIELD_LENGTH,
|
||||
f.RDB$FIELD_SCALE AS FIELD_SCALE,
|
||||
f.RDB$FIELD_PRECISION AS FIELD_PRECISION,
|
||||
rf.RDB$NULL_FLAG AS NULL_FLAG
|
||||
FROM RDB$RELATION_FIELDS rf
|
||||
INNER JOIN RDB$FIELDS f ON rf.RDB$FIELD_SOURCE = f.RDB$FIELD_NAME
|
||||
WHERE rf.RDB$RELATION_NAME = ?
|
||||
ORDER BY rf.RDB$FIELD_POSITION
|
||||
`, [String(tableName).toUpperCase()]);
|
||||
async function firebirdTableInfo(config, tableName) {
|
||||
const rows = await firebirdQuery(config, `
|
||||
SELECT
|
||||
rf.RDB$FIELD_NAME AS COLUMN_NAME,
|
||||
rf.RDB$FIELD_POSITION AS ORDINAL_POSITION,
|
||||
CASE f.RDB$FIELD_TYPE
|
||||
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 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 261 THEN 'BLOB' WHEN 14 THEN 'CHAR' WHEN 41 THEN 'NUMERIC'
|
||||
ELSE 'UNKNOWN'
|
||||
END AS DATA_TYPE,
|
||||
f.RDB$FIELD_LENGTH AS FIELD_LENGTH,
|
||||
f.RDB$FIELD_SCALE AS FIELD_SCALE,
|
||||
f.RDB$FIELD_PRECISION AS FIELD_PRECISION,
|
||||
rf.RDB$NULL_FLAG AS NULL_FLAG
|
||||
FROM RDB$RELATION_FIELDS rf
|
||||
INNER JOIN RDB$FIELDS f ON rf.RDB$FIELD_SOURCE = f.RDB$FIELD_NAME
|
||||
WHERE rf.RDB$RELATION_NAME = ?
|
||||
ORDER BY rf.RDB$FIELD_POSITION
|
||||
`, [String(tableName).toUpperCase()]);
|
||||
|
||||
return rows.map((row) => ({
|
||||
name: (row.COLUMN_NAME || '').trim(),
|
||||
position: row.ORDINAL_POSITION,
|
||||
type: (row.DATA_TYPE || '').trim(),
|
||||
length: row.FIELD_LENGTH,
|
||||
precision: row.FIELD_PRECISION,
|
||||
scale: row.FIELD_SCALE,
|
||||
nullable: row.NULL_FLAG !== 1,
|
||||
}));
|
||||
}
|
||||
return rows.map((row) => ({
|
||||
name: (row.COLUMN_NAME || '').trim(),
|
||||
position: row.ORDINAL_POSITION,
|
||||
type: (row.DATA_TYPE || '').trim(),
|
||||
length: row.FIELD_LENGTH,
|
||||
precision: row.FIELD_PRECISION,
|
||||
scale: row.FIELD_SCALE,
|
||||
nullable: row.NULL_FLAG !== 1,
|
||||
}));
|
||||
}
|
||||
|
||||
async function tableExists(config, tableName) {
|
||||
const r = await query(config,
|
||||
"SELECT COUNT(*) AS CT FROM RDB$RELATIONS WHERE RDB$RELATION_NAME = ?",
|
||||
[String(tableName).toUpperCase()]);
|
||||
return (r[0] && r[0].CT > 0) || false;
|
||||
}
|
||||
async function firebirdTableExists(config, tableName) {
|
||||
const r = await firebirdQuery(config,
|
||||
"SELECT COUNT(*) AS CT FROM RDB$RELATIONS WHERE RDB$RELATION_NAME = ?",
|
||||
[String(tableName).toUpperCase()]);
|
||||
return (r[0] && r[0].CT > 0) || false;
|
||||
}
|
||||
|
||||
async function columnExists(config, tableName, columnName) {
|
||||
const r = await query(config,
|
||||
"SELECT COUNT(*) AS CT FROM RDB$RELATION_FIELDS WHERE RDB$RELATION_NAME = ? AND RDB$FIELD_NAME = ?",
|
||||
[String(tableName).toUpperCase(), String(columnName).toUpperCase()]);
|
||||
return (r[0] && r[0].CT > 0) || false;
|
||||
}
|
||||
async function firebirdColumnExists(config, tableName, columnName) {
|
||||
const r = await firebirdQuery(config,
|
||||
"SELECT COUNT(*) AS CT FROM RDB$RELATION_FIELDS WHERE RDB$RELATION_NAME = ? AND RDB$FIELD_NAME = ?",
|
||||
[String(tableName).toUpperCase(), String(columnName).toUpperCase()]);
|
||||
return (r[0] && r[0].CT > 0) || false;
|
||||
}
|
||||
|
||||
async function close() { /* Firebird abre conexão por consulta; nada a encerrar */ }
|
||||
|
||||
return { query, execute, testConnection, listTables, tableInfo, tableExists, columnExists, close };
|
||||
})();
|
||||
async function firebirdClose() { /* Firebird abre conexão por consulta; nada a encerrar */ }
|
||||
|
||||
// ============================================================
|
||||
// 3) DRIVER POSTGRES (pool + tradutor de SQL Firebird->PG + schema)
|
||||
// 3) DISPATCHER — API pública
|
||||
// ============================================================
|
||||
const postgresDriver = (() => {
|
||||
const pg = require('pg');
|
||||
const driver = {
|
||||
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.
|
||||
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) {
|
||||
function getConn(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 };
|
||||
}
|
||||
|
||||
function query(alias, sql, params = []) {
|
||||
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}`); });
|
||||
}
|
||||
|
||||
function execute(alias, sql, params = []) {
|
||||
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}`); });
|
||||
}
|
||||
|
||||
async function testConnection(alias) {
|
||||
try {
|
||||
const { config, driver } = getDriver(alias);
|
||||
const { config } = getConn(alias);
|
||||
await driver.testConnection(config);
|
||||
return true;
|
||||
} catch (err) {
|
||||
@@ -697,33 +342,33 @@ async function testAllConnections() {
|
||||
}
|
||||
|
||||
function listTables(alias) {
|
||||
const { config, driver } = getDriver(alias);
|
||||
const { config } = getConn(alias);
|
||||
return driver.listTables(config);
|
||||
}
|
||||
|
||||
function tableInfo(alias, tableName) {
|
||||
const { config, driver } = getDriver(alias);
|
||||
const { config } = getConn(alias);
|
||||
return driver.tableInfo(config, tableName);
|
||||
}
|
||||
|
||||
function tableExists(alias, tableName) {
|
||||
const { config, driver } = getDriver(alias);
|
||||
const { config } = getConn(alias);
|
||||
return driver.tableExists(config, tableName);
|
||||
}
|
||||
|
||||
function columnExists(alias, tableName, columnName) {
|
||||
const { config, driver } = getDriver(alias);
|
||||
const { config } = getConn(alias);
|
||||
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) {
|
||||
return getConfig(alias).driver;
|
||||
return 'firebird';
|
||||
}
|
||||
|
||||
/** Encerra os pools de todos os drivers (shutdown gracioso). */
|
||||
/** Encerra recursos (noop para Firebird). */
|
||||
async function closeAll() {
|
||||
await Promise.all(Object.values(drivers).map((d) => d.close && d.close()));
|
||||
await driver.close();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
||||
+18
-1
@@ -50,6 +50,23 @@ async function authenticateToken(req, res, next) {
|
||||
.map(function (e) { return Number(e); })
|
||||
.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.authType = 'jwt';
|
||||
return next();
|
||||
@@ -90,7 +107,7 @@ async function authenticateToken(req, res, next) {
|
||||
FROM USUARIOS
|
||||
WHERE USU_TOKEN = ?
|
||||
AND USU_STATUS = 'A'
|
||||
AND COALESCE(USU_ACESSO_WEB, 0) = 1`,
|
||||
AND COALESCE(USU_ACESSO_WEB, '0') IN ('1', 'S')`,
|
||||
[token]
|
||||
);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* 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
|
||||
* - Flags em CHATC2_CONFIGURACOES_EMPRESA (CFE_MOTIVO_*, CFE_RESOLUCAO_*)
|
||||
* - Colunas CON_MOTIVO_ID / CON_RESOLUCAO em CHATC2_CONVERSAS
|
||||
|
||||
Reference in New Issue
Block a user