482 lines
15 KiB
JavaScript
482 lines
15 KiB
JavaScript
const mysql = require('mysql2/promise');
|
|
const fs = require('fs').promises;
|
|
const path = require('path');
|
|
|
|
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
|
|
// ================================================================
|
|
|
|
async function initSQLite() {
|
|
const { DatabaseSync } = require('node:sqlite');
|
|
const dbPath = path.join(__dirname, 'dental_images.db');
|
|
|
|
sqliteDb = new DatabaseSync(dbPath);
|
|
console.log(`✅ Banco de dados SQLite criado/inicializado em: ${dbPath}`);
|
|
|
|
// 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
|
|
);
|
|
`);
|
|
|
|
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!');
|
|
}
|
|
} catch (error) {
|
|
console.error('⚠️ Erro ao criar admin padrão:', error.message);
|
|
}
|
|
}
|
|
|
|
async function initDatabase() {
|
|
if (isSqlite) {
|
|
await initSQLite();
|
|
return;
|
|
}
|
|
|
|
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';
|
|
|
|
// Criar pool temporário SEM database para poder criar o banco
|
|
const tempPool = mysql.createPool({
|
|
...dbConfig,
|
|
waitForConnections: true,
|
|
connectionLimit: 10,
|
|
queueLimit: 0
|
|
});
|
|
|
|
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
|
|
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?');
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// ================================================================
|
|
// CRIAR TABELAS MYSQL
|
|
// ================================================================
|
|
|
|
async function createTables() {
|
|
const connection = await pool.getConnection();
|
|
|
|
try {
|
|
// Tabela de usuários
|
|
await connection.query(`
|
|
CREATE TABLE IF NOT EXISTS users (
|
|
id INT AUTO_INCREMENT 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
|
|
`);
|
|
console.log('✅ Tabela users criada/verificada');
|
|
|
|
// Tabela de imagens
|
|
await connection.query(`
|
|
CREATE TABLE IF NOT EXISTS images (
|
|
id INT AUTO_INCREMENT 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,
|
|
tooth_number VARCHAR(10),
|
|
tooth_side VARCHAR(10),
|
|
patient_name_resumed VARCHAR(50),
|
|
original_image_id INT,
|
|
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
|
|
`);
|
|
console.log('✅ Tabela images criada/verificada');
|
|
|
|
// Tabela GTOs
|
|
await connection.query(`
|
|
CREATE TABLE IF NOT EXISTS gtos (
|
|
id INT AUTO_INCREMENT 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
|
|
`);
|
|
console.log('✅ Tabela gtos criada/verificada');
|
|
|
|
// Tabela de vínculo GTO-Images
|
|
await connection.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
|
|
`);
|
|
console.log('✅ Tabela gto_images criada/verificada');
|
|
|
|
// Verificar e adicionar colunas se não existirem (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'
|
|
`);
|
|
|
|
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
|
|
`);
|
|
}
|
|
} catch (error) {
|
|
console.warn('⚠️ Erro ao verificar/adicionar coluna doctor:', error.message);
|
|
}
|
|
|
|
// Migração para coluna remark
|
|
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
|
|
`);
|
|
}
|
|
} 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);
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('Erro ao criar tabelas:', error);
|
|
} finally {
|
|
connection.release();
|
|
}
|
|
}
|
|
|
|
// ================================================================
|
|
// FUNÇÕES DE CONSULTA
|
|
// ================================================================
|
|
|
|
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);
|
|
} catch (error) {
|
|
reject(error);
|
|
}
|
|
});
|
|
}
|
|
|
|
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);
|
|
} catch (error) {
|
|
reject(error);
|
|
}
|
|
});
|
|
}
|
|
|
|
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);
|
|
resolve({
|
|
lastID: result.insertId,
|
|
changes: result.affectedRows
|
|
});
|
|
} catch (error) {
|
|
reject(error);
|
|
}
|
|
});
|
|
}
|
|
|
|
// ================================================================
|
|
// EXPORT
|
|
// ================================================================
|
|
|
|
module.exports = {
|
|
initDatabase,
|
|
get,
|
|
all,
|
|
run,
|
|
get db() { return isSqlite ? sqliteDb : pool; }
|
|
}; |