Files
rx.scoreodonto.com/dental-server/database.js
T
2026-05-26 02:59:11 +02:00

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; }
};