Migrate to PostgreSQL and add DragonflyDB support
This commit is contained in:
+131
-360
@@ -1,248 +1,109 @@
|
||||
const mysql = require('mysql2/promise');
|
||||
const fs = require('fs').promises;
|
||||
const path = require('path');
|
||||
const { Pool } = require('pg');
|
||||
|
||||
let pool = null;
|
||||
let sqliteDb = null;
|
||||
const isSqlite = process.env.DB_TYPE === 'sqlite';
|
||||
|
||||
// Helper to rewrite MySQL-specific syntax to SQLite
|
||||
function rewriteQuery(sql) {
|
||||
if (!isSqlite) return sql;
|
||||
|
||||
let query = sql;
|
||||
// Replace NOW() with datetime('now')
|
||||
query = query.replace(/\bNOW\(\)/gi, "datetime('now')");
|
||||
|
||||
// Rewrite ON DUPLICATE KEY UPDATE for users
|
||||
if (query.includes('ON DUPLICATE KEY UPDATE')) {
|
||||
query = `INSERT INTO users (username, email, password_hash, created_at)
|
||||
VALUES (?, ?, ?, datetime('now'))
|
||||
ON CONFLICT(username) DO UPDATE SET
|
||||
email = excluded.email,
|
||||
password_hash = excluded.password_hash`;
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// INICIALIZAR BANCO DE DADOS
|
||||
// FUNÇÕES AUXILIARES DE COMPATIBILIDADE (MYSQL -> POSTGRES)
|
||||
// ================================================================
|
||||
|
||||
async function initSQLite() {
|
||||
const { DatabaseSync } = require('node:sqlite');
|
||||
const dbPath = process.env.DB_PATH || path.join(__dirname, 'dental_images.db');
|
||||
function rewriteToPostgres(sql) {
|
||||
let rewritten = sql;
|
||||
|
||||
sqliteDb = new DatabaseSync(dbPath);
|
||||
console.log(`✅ Banco de dados SQLite criado/inicializado em: ${dbPath}`);
|
||||
// 1. Converter MySQL NOW() para Postgres CURRENT_TIMESTAMP
|
||||
rewritten = rewritten.replace(/\bNOW\(\)/gi, 'CURRENT_TIMESTAMP');
|
||||
|
||||
// Criar tabelas para SQLite
|
||||
sqliteDb.exec(`
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username TEXT UNIQUE NOT NULL,
|
||||
email TEXT UNIQUE NOT NULL,
|
||||
password_hash TEXT NOT NULL,
|
||||
clinic_name TEXT,
|
||||
pc_name TEXT,
|
||||
is_admin INTEGER DEFAULT 0,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
`);
|
||||
// 2. Converter parâmetros de ? para $1, $2, etc.
|
||||
let paramIndex = 1;
|
||||
rewritten = rewritten.replace(/\?/g, () => `$${paramIndex++}`);
|
||||
|
||||
sqliteDb.exec(`
|
||||
CREATE TABLE IF NOT EXISTS images (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
image_guid TEXT NOT NULL,
|
||||
patient_name TEXT,
|
||||
client_name TEXT,
|
||||
original_filename TEXT,
|
||||
filename TEXT NOT NULL,
|
||||
enabled INTEGER DEFAULT 1,
|
||||
tooth_number TEXT,
|
||||
tooth_side TEXT,
|
||||
patient_name_resumed TEXT,
|
||||
original_image_id INTEGER,
|
||||
rotation INTEGER DEFAULT 0,
|
||||
flip_horizontal INTEGER DEFAULT 0,
|
||||
flip_vertical INTEGER DEFAULT 0,
|
||||
doctor TEXT,
|
||||
remark TEXT,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
`);
|
||||
|
||||
sqliteDb.exec(`
|
||||
CREATE TABLE IF NOT EXISTS gtos (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
gto_number TEXT NOT NULL,
|
||||
patient_name TEXT NOT NULL,
|
||||
description TEXT,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
`);
|
||||
|
||||
sqliteDb.exec(`
|
||||
CREATE TABLE IF NOT EXISTS gto_images (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
gto_id INTEGER NOT NULL,
|
||||
image_id INTEGER NOT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY(gto_id) REFERENCES gtos(id),
|
||||
FOREIGN KEY(image_id) REFERENCES images(id)
|
||||
);
|
||||
`);
|
||||
|
||||
// Migrações para adicionar colunas se o banco já existia
|
||||
try {
|
||||
sqliteDb.exec("ALTER TABLE images ADD COLUMN doctor TEXT");
|
||||
} catch (e) {
|
||||
// Coluna já existe
|
||||
}
|
||||
|
||||
try {
|
||||
sqliteDb.exec("ALTER TABLE images ADD COLUMN remark TEXT");
|
||||
} catch (e) {
|
||||
// Coluna já existe
|
||||
}
|
||||
|
||||
console.log('✅ Tabelas SQLite users e images criadas/verificadas');
|
||||
|
||||
// Migrações para adicionar colunas em users se o banco já existia
|
||||
try { sqliteDb.exec("ALTER TABLE users ADD COLUMN clinic_name TEXT"); } catch (e) {}
|
||||
try { sqliteDb.exec("ALTER TABLE users ADD COLUMN pc_name TEXT"); } catch (e) {}
|
||||
try { sqliteDb.exec("ALTER TABLE users ADD COLUMN is_admin INTEGER DEFAULT 0"); } catch (e) {}
|
||||
|
||||
// Criar administrador padrão se não houver usuários
|
||||
try {
|
||||
const stmtCount = sqliteDb.prepare('SELECT COUNT(*) as count FROM users');
|
||||
const userCount = stmtCount.get().count;
|
||||
if (userCount === 0) {
|
||||
console.log('⚠️ Nenhum usuário encontrado no SQLite. Criando admin padrão (admin / admin1234)...');
|
||||
const bcrypt = require('bcrypt');
|
||||
const passwordHash = bcrypt.hashSync('admin1234', 10);
|
||||
const stmtInsert = sqliteDb.prepare(`
|
||||
INSERT INTO users (username, email, password_hash, clinic_name, pc_name, is_admin)
|
||||
VALUES (?, ?, ?, ?, ?, 1)
|
||||
`);
|
||||
stmtInsert.run('admin', 'admin@dental.local', passwordHash, 'Admin Clinic', 'Admin PC');
|
||||
console.log('✅ Usuário admin padrão criado com sucesso!');
|
||||
// 3. Adicionar RETURNING id nas inserções se não houver
|
||||
if (rewritten.trim().toUpperCase().startsWith('INSERT INTO')) {
|
||||
if (!rewritten.toUpperCase().includes('RETURNING')) {
|
||||
rewritten += ' RETURNING id';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('⚠️ Erro ao criar admin padrão:', error.message);
|
||||
}
|
||||
|
||||
// 4. Reescrever ON DUPLICATE KEY UPDATE do MySQL
|
||||
if (rewritten.includes('ON DUPLICATE KEY UPDATE')) {
|
||||
// Isso é específico para a query de inserção de usuário default
|
||||
rewritten = `INSERT INTO users (username, email, password_hash, created_at)
|
||||
VALUES ($1, $2, $3, CURRENT_TIMESTAMP)
|
||||
ON CONFLICT (username) DO UPDATE SET
|
||||
email = EXCLUDED.email,
|
||||
password_hash = EXCLUDED.password_hash
|
||||
RETURNING id`;
|
||||
}
|
||||
|
||||
return rewritten;
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// INICIALIZAR BANCO DE DADOS POSTGRESQL
|
||||
// ================================================================
|
||||
|
||||
async function initDatabase() {
|
||||
if (isSqlite) {
|
||||
await initSQLite();
|
||||
return;
|
||||
const dbConfig = {
|
||||
host: process.env.DB_HOST || '10.99.0.3',
|
||||
port: process.env.DB_PORT || 5432,
|
||||
user: process.env.DB_USER || 'postgres',
|
||||
database: process.env.DB_NAME || 'dental_images',
|
||||
max: 20, // Max number of clients in the pool
|
||||
idleTimeoutMillis: 30000,
|
||||
};
|
||||
|
||||
if (process.env.DB_PASSWORD) {
|
||||
dbConfig.password = process.env.DB_PASSWORD;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
const dbConfig = {
|
||||
host: process.env.DB_HOST || 'localhost',
|
||||
port: process.env.DB_PORT || 3306,
|
||||
user: process.env.DB_USER || 'root',
|
||||
password: process.env.DB_PASSWORD || '',
|
||||
};
|
||||
const dbName = process.env.DB_NAME || 'dental_images';
|
||||
pool = new Pool(dbConfig);
|
||||
|
||||
// Criar pool temporário SEM database para poder criar o banco
|
||||
const tempPool = mysql.createPool({
|
||||
...dbConfig,
|
||||
waitForConnections: true,
|
||||
connectionLimit: 10,
|
||||
queueLimit: 0
|
||||
});
|
||||
// Testa a conexão
|
||||
const client = await pool.connect();
|
||||
console.log(`✅ Conectado ao PostgreSQL (Host: ${dbConfig.host}, DB: ${dbConfig.database})`);
|
||||
client.release();
|
||||
|
||||
console.log('✅ Pool de conexões MySQL criado');
|
||||
|
||||
// Verificar se banco existe, se não criar
|
||||
try {
|
||||
const [databases] = await tempPool.query(
|
||||
`SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = ?`,
|
||||
[dbName]
|
||||
);
|
||||
|
||||
if (databases.length === 0) {
|
||||
console.log(`⚠️ Banco ${dbName} não encontrado. Criando...`);
|
||||
await tempPool.query(`CREATE DATABASE ${dbName} CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci`);
|
||||
console.log(`✅ Banco ${dbName} criado!`);
|
||||
} else {
|
||||
console.log(`✅ Banco ${dbName} já existe`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ Erro ao verificar/criar banco:', error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Fechar pool temporário
|
||||
await tempPool.end();
|
||||
|
||||
// Criar pool definitivo COM database
|
||||
pool = mysql.createPool({
|
||||
...dbConfig,
|
||||
database: dbName,
|
||||
waitForConnections: true,
|
||||
connectionLimit: 10,
|
||||
queueLimit: 0,
|
||||
enableKeepAlive: true,
|
||||
keepAliveInitialDelay: 0
|
||||
});
|
||||
|
||||
// Testar conexão
|
||||
const connection = await pool.getConnection();
|
||||
console.log('✅ Conectado ao MySQL');
|
||||
connection.release();
|
||||
|
||||
// Criar tabelas
|
||||
// Criar as tabelas
|
||||
await createTables();
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Erro ao conectar ao MySQL:', error.message);
|
||||
console.error('Verifique:');
|
||||
console.error('1. MySQL está rodando? (sudo systemctl status mysql)');
|
||||
console.error('2. Credenciais corretas no .env?');
|
||||
console.error('3. Usuário tem permissão para criar databases?');
|
||||
console.error('❌ Erro ao conectar ao PostgreSQL:', error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// CRIAR TABELAS MYSQL
|
||||
// CRIAR TABELAS POSTGRESQL
|
||||
// ================================================================
|
||||
|
||||
async function createTables() {
|
||||
const connection = await pool.getConnection();
|
||||
const client = await pool.connect();
|
||||
|
||||
try {
|
||||
// Tabela de usuários
|
||||
await connection.query(`
|
||||
await client.query(`
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
id SERIAL PRIMARY KEY,
|
||||
username VARCHAR(50) UNIQUE NOT NULL,
|
||||
email VARCHAR(100) UNIQUE NOT NULL,
|
||||
password_hash TEXT NOT NULL,
|
||||
clinic_name VARCHAR(100) NULL,
|
||||
pc_name VARCHAR(50) NULL,
|
||||
is_admin TINYINT(1) DEFAULT 0,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||||
clinic_name VARCHAR(100),
|
||||
pc_name VARCHAR(50),
|
||||
is_admin BOOLEAN DEFAULT false,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
`);
|
||||
console.log('✅ Tabela users criada/verificada');
|
||||
console.log('✅ Tabela users criada/verificada no PostgreSQL');
|
||||
|
||||
// Tabela de imagens
|
||||
await connection.query(`
|
||||
await client.query(`
|
||||
CREATE TABLE IF NOT EXISTS images (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
id SERIAL PRIMARY KEY,
|
||||
image_guid VARCHAR(255) NOT NULL,
|
||||
patient_name TEXT,
|
||||
client_name VARCHAR(100),
|
||||
original_filename TEXT,
|
||||
filename TEXT NOT NULL,
|
||||
enabled TINYINT(1) DEFAULT 1,
|
||||
enabled BOOLEAN DEFAULT true,
|
||||
tooth_number VARCHAR(10),
|
||||
tooth_side VARCHAR(10),
|
||||
patient_name_resumed VARCHAR(50),
|
||||
@@ -250,167 +111,99 @@ async function createTables() {
|
||||
rotation INT DEFAULT 0,
|
||||
flip_horizontal INT DEFAULT 0,
|
||||
flip_vertical INT DEFAULT 0,
|
||||
doctor VARCHAR(255) NULL,
|
||||
remark TEXT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
INDEX idx_images_enabled (enabled),
|
||||
INDEX idx_images_original (original_image_id),
|
||||
INDEX idx_patient_name (patient_name(100)),
|
||||
INDEX idx_image_guid (image_guid),
|
||||
INDEX idx_client_name (client_name),
|
||||
INDEX idx_created_at (created_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||||
doctor VARCHAR(255),
|
||||
remark TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
`);
|
||||
console.log('✅ Tabela images criada/verificada');
|
||||
|
||||
// Criação de índices no PostgreSQL
|
||||
await client.query('CREATE INDEX IF NOT EXISTS idx_images_enabled ON images(enabled)');
|
||||
await client.query('CREATE INDEX IF NOT EXISTS idx_images_original ON images(original_image_id)');
|
||||
await client.query('CREATE INDEX IF NOT EXISTS idx_image_guid ON images(image_guid)');
|
||||
await client.query('CREATE INDEX IF NOT EXISTS idx_client_name ON images(client_name)');
|
||||
await client.query('CREATE INDEX IF NOT EXISTS idx_created_at ON images(created_at)');
|
||||
|
||||
console.log('✅ Tabela images criada/verificada no PostgreSQL');
|
||||
|
||||
// Tabela GTOs
|
||||
await connection.query(`
|
||||
await client.query(`
|
||||
CREATE TABLE IF NOT EXISTS gtos (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
id SERIAL PRIMARY KEY,
|
||||
gto_number VARCHAR(100) NOT NULL,
|
||||
patient_name VARCHAR(255) NOT NULL,
|
||||
description TEXT,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
INDEX idx_gtos_patient (patient_name)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
`);
|
||||
console.log('✅ Tabela gtos criada/verificada');
|
||||
await client.query('CREATE INDEX IF NOT EXISTS idx_gtos_patient ON gtos(patient_name)');
|
||||
console.log('✅ Tabela gtos criada/verificada no PostgreSQL');
|
||||
|
||||
// Tabela de vínculo GTO-Images
|
||||
await connection.query(`
|
||||
await client.query(`
|
||||
CREATE TABLE IF NOT EXISTS gto_images (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
gto_id INT NOT NULL,
|
||||
image_id INT NOT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (gto_id) REFERENCES gtos(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (image_id) REFERENCES images(id) ON DELETE CASCADE,
|
||||
UNIQUE INDEX idx_gto_image (gto_id, image_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||||
id SERIAL PRIMARY KEY,
|
||||
gto_id INT NOT NULL REFERENCES gtos(id) ON DELETE CASCADE,
|
||||
image_id INT NOT NULL REFERENCES images(id) ON DELETE CASCADE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(gto_id, image_id)
|
||||
)
|
||||
`);
|
||||
console.log('✅ Tabela gto_images criada/verificada');
|
||||
console.log('✅ Tabela gto_images criada/verificada no PostgreSQL');
|
||||
|
||||
// Verificar e adicionar colunas se não existirem (migração)
|
||||
// Verificar colunas faltantes em users (migração)
|
||||
try {
|
||||
const columns = await connection.query(`
|
||||
SELECT COLUMN_NAME
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = 'images'
|
||||
AND COLUMN_NAME = 'client_name'
|
||||
const res = await client.query(`
|
||||
SELECT column_name
|
||||
FROM information_schema.columns
|
||||
WHERE table_name = 'users' AND column_name = 'clinic_name'
|
||||
`);
|
||||
|
||||
if (columns[0].length === 0) {
|
||||
console.log('⚠️ Coluna client_name não encontrada. Adicionando...');
|
||||
await connection.query(`
|
||||
ALTER TABLE images
|
||||
ADD COLUMN client_name VARCHAR(100) NULL
|
||||
AFTER patient_name
|
||||
`);
|
||||
await connection.query(`
|
||||
ALTER TABLE images
|
||||
ADD INDEX idx_client_name (client_name)
|
||||
`);
|
||||
console.log('✅ Coluna client_name adicionada');
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('⚠️ Erro ao verificar/adicionar coluna client_name:', error.message);
|
||||
}
|
||||
|
||||
// Migração para coluna doctor
|
||||
try {
|
||||
const columnsDoctor = await connection.query(`
|
||||
SELECT COLUMN_NAME
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = 'images'
|
||||
AND COLUMN_NAME = 'doctor'
|
||||
`);
|
||||
if (columnsDoctor[0].length === 0) {
|
||||
console.log('⚠️ Coluna doctor não encontrada. Adicionando...');
|
||||
await connection.query(`
|
||||
ALTER TABLE images
|
||||
ADD COLUMN doctor VARCHAR(255) NULL
|
||||
AFTER client_name
|
||||
if (res.rows.length === 0) {
|
||||
console.log('⚠️ Adicionando novas colunas na tabela users...');
|
||||
await client.query(`
|
||||
ALTER TABLE users
|
||||
ADD COLUMN clinic_name VARCHAR(100),
|
||||
ADD COLUMN pc_name VARCHAR(50),
|
||||
ADD COLUMN is_admin BOOLEAN DEFAULT false
|
||||
`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('⚠️ Erro ao verificar/adicionar coluna doctor:', error.message);
|
||||
} catch (e) {
|
||||
console.warn('⚠️ Erro ao migrar tabela users:', e.message);
|
||||
}
|
||||
|
||||
// Migração para coluna remark
|
||||
// Criar administrador padrão se não houver usuários
|
||||
try {
|
||||
const columnsRemark = await connection.query(`
|
||||
SELECT COLUMN_NAME
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = 'images'
|
||||
AND COLUMN_NAME = 'remark'
|
||||
`);
|
||||
if (columnsRemark[0].length === 0) {
|
||||
console.log('⚠️ Coluna remark não encontrada. Adicionando...');
|
||||
await connection.query(`
|
||||
ALTER TABLE images
|
||||
ADD COLUMN remark TEXT NULL
|
||||
AFTER doctor
|
||||
`);
|
||||
const res = await client.query('SELECT COUNT(*) as count FROM users');
|
||||
if (parseInt(res.rows[0].count) === 0) {
|
||||
console.log('⚠️ Nenhum usuário encontrado. Criando admin padrão (admin / admin1234)...');
|
||||
const bcrypt = require('bcrypt');
|
||||
const passwordHash = bcrypt.hashSync('admin1234', 10);
|
||||
await client.query(`
|
||||
INSERT INTO users (username, email, password_hash, clinic_name, pc_name, is_admin)
|
||||
VALUES ($1, $2, $3, $4, $5, true)
|
||||
`, ['admin', 'admin@dental.local', passwordHash, 'Admin Clinic', 'Admin PC']);
|
||||
console.log('✅ Usuário admin padrão criado com sucesso!');
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('⚠️ Erro ao verificar/adicionar coluna remark:', error.message);
|
||||
}
|
||||
|
||||
// Migração para colunas da tabela users (clinic_name, pc_name, is_admin)
|
||||
try {
|
||||
const columnsUsers = await connection.query(`
|
||||
SELECT COLUMN_NAME
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = 'users'
|
||||
AND COLUMN_NAME = 'clinic_name'
|
||||
`);
|
||||
if (columnsUsers[0].length === 0) {
|
||||
console.log('⚠️ Colunas de clínica em users não encontradas. Adicionando...');
|
||||
await connection.query(`
|
||||
ALTER TABLE users
|
||||
ADD COLUMN clinic_name VARCHAR(100) NULL AFTER password_hash,
|
||||
ADD COLUMN pc_name VARCHAR(50) NULL AFTER clinic_name,
|
||||
ADD COLUMN is_admin TINYINT(1) DEFAULT 0 AFTER pc_name
|
||||
`);
|
||||
// O primeiro usuário (admin padrão) deve virar admin se já existe
|
||||
await connection.query(`UPDATE users SET is_admin = 1 ORDER BY id ASC LIMIT 1`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('⚠️ Erro ao verificar/adicionar colunas de clínica em users:', error.message);
|
||||
console.error('⚠️ Erro ao criar admin padrão:', error.message);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Erro ao criar tabelas:', error);
|
||||
console.error('❌ Erro ao criar tabelas no PostgreSQL:', error);
|
||||
} finally {
|
||||
connection.release();
|
||||
client.release();
|
||||
}
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// FUNÇÕES DE CONSULTA
|
||||
// FUNÇÕES DE CONSULTA (ENVOLVENDO O POOL DO PG)
|
||||
// ================================================================
|
||||
|
||||
function get(query, params = []) {
|
||||
if (isSqlite) {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
const stmt = sqliteDb.prepare(rewriteQuery(query));
|
||||
const row = stmt.get(...params);
|
||||
resolve(row || null);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const [rows] = await pool.query(query, params);
|
||||
resolve(rows[0] || null);
|
||||
const res = await pool.query(rewriteToPostgres(query), params);
|
||||
resolve(res.rows[0] || null);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
@@ -418,22 +211,10 @@ function get(query, params = []) {
|
||||
}
|
||||
|
||||
function all(query, params = []) {
|
||||
if (isSqlite) {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
const stmt = sqliteDb.prepare(rewriteQuery(query));
|
||||
const rows = stmt.all(...params);
|
||||
resolve(rows);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const [rows] = await pool.query(query, params);
|
||||
resolve(rows);
|
||||
const res = await pool.query(rewriteToPostgres(query), params);
|
||||
resolve(res.rows || []);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
@@ -441,27 +222,17 @@ function all(query, params = []) {
|
||||
}
|
||||
|
||||
function run(query, params = []) {
|
||||
if (isSqlite) {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
const stmt = sqliteDb.prepare(rewriteQuery(query));
|
||||
const result = stmt.run(...params);
|
||||
resolve({
|
||||
lastID: result.lastInsertRowid,
|
||||
changes: result.changes
|
||||
});
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const [result] = await pool.query(query, params);
|
||||
const rewritten = rewriteToPostgres(query);
|
||||
const res = await pool.query(rewritten, params);
|
||||
|
||||
// No PostgreSQL, se adicionamos RETURNING id, o id estará no res.rows[0]
|
||||
const insertedId = (res.rows && res.rows[0] && res.rows[0].id) ? res.rows[0].id : null;
|
||||
|
||||
resolve({
|
||||
lastID: result.insertId,
|
||||
changes: result.affectedRows
|
||||
lastID: insertedId,
|
||||
changes: res.rowCount
|
||||
});
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
@@ -478,5 +249,5 @@ module.exports = {
|
||||
get,
|
||||
all,
|
||||
run,
|
||||
get db() { return isSqlite ? sqliteDb : pool; }
|
||||
get db() { return pool; }
|
||||
};
|
||||
@@ -16,16 +16,17 @@
|
||||
"author": "Rcesar",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"socket.io": "^4.7.2",
|
||||
"express": "^4.18.2",
|
||||
"sharp": "^0.32.6",
|
||||
"mysql2": "^3.6.0",
|
||||
"dotenv": "^16.3.1",
|
||||
"cors": "^2.8.5",
|
||||
"bcrypt": "^5.1.1",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"express-session": "^1.17.3"
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.3.1",
|
||||
"express": "^4.18.2",
|
||||
"express-session": "^1.17.3",
|
||||
"ioredis": "^5.10.1",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"pg": "^8.21.0",
|
||||
"sharp": "^0.32.6",
|
||||
"socket.io": "^4.7.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0",
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
const Redis = require('ioredis');
|
||||
|
||||
let redisClient = null;
|
||||
|
||||
function initRedis() {
|
||||
const host = process.env.REDIS_HOST || '10.99.0.3';
|
||||
const port = process.env.REDIS_PORT || 6379;
|
||||
const password = process.env.REDIS_PASSWORD || '';
|
||||
|
||||
redisClient = new Redis({
|
||||
host,
|
||||
port,
|
||||
password,
|
||||
retryStrategy: (times) => {
|
||||
// Reconecta exponencialmente com limite de 3 segundos
|
||||
const delay = Math.min(times * 50, 3000);
|
||||
return delay;
|
||||
}
|
||||
});
|
||||
|
||||
redisClient.on('connect', () => {
|
||||
console.log(`✅ Conectado ao DragonflyDB/Redis em ${host}:${port}`);
|
||||
});
|
||||
|
||||
redisClient.on('error', (err) => {
|
||||
console.error('❌ Erro de conexão no DragonflyDB/Redis:', err.message);
|
||||
});
|
||||
}
|
||||
|
||||
function getClient() {
|
||||
if (!redisClient) {
|
||||
initRedis();
|
||||
}
|
||||
return redisClient;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
initRedis,
|
||||
getClient
|
||||
};
|
||||
@@ -18,6 +18,7 @@ const patientsRouter = require('./routes/patients');
|
||||
const gtosRouter = require('./routes/gtos');
|
||||
const systemRoutes = require('./routes/system');
|
||||
const { checkInstallation } = require('./installer/check-installation');
|
||||
const redis = require('./redis');
|
||||
|
||||
// ================================================================
|
||||
// CONFIGURAÇÃO DO SERVIDOR
|
||||
@@ -72,6 +73,18 @@ app.get('/status', (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
// DragonflyDB Ping check
|
||||
app.get('/api/redis/ping', async (req, res) => {
|
||||
try {
|
||||
const client = redis.getClient();
|
||||
await client.set('ping_test', 'pong', 'EX', 10);
|
||||
const value = await client.get('ping_test');
|
||||
res.json({ success: true, dragonfly_db: 'connected', test_value: value });
|
||||
} catch (error) {
|
||||
res.status(500).json({ success: false, error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Verificar se o nome do computador/cliente já existe
|
||||
app.get('/api/clients/check-name', async (req, res) => {
|
||||
try {
|
||||
@@ -911,6 +924,9 @@ async function start() {
|
||||
console.log('⚠️ Banco de dados não configurado. Execute a instalação em /install');
|
||||
}
|
||||
|
||||
// Inicializar cliente DragonflyDB/Redis
|
||||
redis.initRedis();
|
||||
|
||||
// Iniciar servidor
|
||||
server.listen(PORT, () => {
|
||||
console.log('═══════════════════════════════════════════════════════');
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ services:
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- PORT=3000
|
||||
- DB_TYPE=sqlite
|
||||
- DB_TYPE=postgres\n - DB_HOST=10.99.0.3\n - REDIS_HOST=10.99.0.3\n - REDIS_PORT=6379\n - REDIS_PASSWORD=clube67_db_pass_9903
|
||||
- DB_PATH=/app/data/dental_images.db
|
||||
- DB_PORT=5432
|
||||
- DB_USER=clube67
|
||||
|
||||
Reference in New Issue
Block a user