Files
rx.scoreodonto.com/dental-server/database.js
T

429 lines
13 KiB
JavaScript

const { Pool } = require('pg');
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')");
return query;
}
// Convert MySQL ? to Postgres $1, $2
function convertSqlForPg(sql) {
let i = 1;
return sql.replace(/\?/g, () => '$' + (i++));
}
// ================================================================
// 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,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
`);
sqliteDb.exec(`
CREATE TABLE IF NOT EXISTS settings (
key TEXT PRIMARY KEY,
value TEXT NOT NULL
);
`);
console.log('✅ Tabela settings SQLite criada/verificada');
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,
thumb_filename 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) {}
try {
sqliteDb.exec("ALTER TABLE images ADD COLUMN remark TEXT");
} catch (e) {}
try {
sqliteDb.exec("ALTER TABLE images ADD COLUMN thumb_filename TEXT");
} catch (e) {}
console.log('✅ Tabelas SQLite users e images criadas/verificadas');
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)
VALUES (?, ?, ?)
`);
stmtInsert.run('admin', 'admin@dental.local', passwordHash);
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 || 5432,
user: process.env.DB_USER || 'postgres',
password: process.env.DB_PASSWORD || '',
};
const dbName = process.env.DB_NAME || 'dental_images';
const tempPool = new Pool({
...dbConfig,
database: 'postgres',
});
try {
const res = await tempPool.query(`SELECT 1 FROM pg_database WHERE datname = $1`, [dbName]);
if (res.rowCount === 0) {
console.log(`⚠️ Banco ${dbName} não encontrado. Criando...`);
await tempPool.query(`CREATE DATABASE "${dbName}"`);
console.log(`✅ Banco ${dbName} criado!`);
} else {
console.log(`✅ Banco ${dbName} já existe`);
}
} catch (error) {
console.error('❌ Erro ao verificar/criar banco no PostgreSQL:', error.message);
} finally {
await tempPool.end();
}
pool = new Pool({
...dbConfig,
database: dbName,
});
const client = await pool.connect();
console.log('✅ Conectado ao PostgreSQL');
client.release();
await createTables();
} catch (error) {
console.error('❌ Erro ao conectar ao PostgreSQL:', error.message);
throw error;
}
}
// ================================================================
// CRIAR TABELAS POSTGRESQL
// ================================================================
async function createTables() {
const client = await pool.connect();
try {
// Tabela de usuários
await client.query(`
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
username VARCHAR(50) UNIQUE NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
password_hash TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
`);
// Tabela de configurações
await client.query(`
CREATE TABLE IF NOT EXISTS settings (
key VARCHAR(100) PRIMARY KEY,
value TEXT NOT NULL
)
`);
console.log('✅ Tabela settings PostgreSQL criada/verificada');
console.log('✅ Tabela users criada/verificada');
// Tabela de imagens
await client.query(`
CREATE TABLE IF NOT EXISTS images (
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 SMALLINT 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 SMALLINT DEFAULT 0,
flip_vertical SMALLINT DEFAULT 0,
doctor VARCHAR(255) NULL,
remark TEXT NULL,
thumb_filename VARCHAR(500) NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
`);
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_patient_name ON images (patient_name)`);
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');
// Tabela GTOs
await client.query(`
CREATE TABLE IF NOT EXISTS gtos (
id SERIAL PRIMARY KEY,
gto_number VARCHAR(100) NOT NULL,
patient_name VARCHAR(255) NOT NULL,
description TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
`);
await client.query(`CREATE INDEX IF NOT EXISTS idx_gtos_patient ON gtos (patient_name)`);
console.log('✅ Tabela gtos criada/verificada');
// Tabela de vínculo GTO-Images
await client.query(`
CREATE TABLE IF NOT EXISTS gto_images (
id SERIAL PRIMARY KEY,
gto_id INT NOT NULL,
image_id INT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (gto_id) REFERENCES gtos(id) ON DELETE CASCADE,
FOREIGN KEY (image_id) REFERENCES images(id) ON DELETE CASCADE
)
`);
await client.query(`CREATE UNIQUE INDEX IF NOT EXISTS idx_gto_image ON gto_images (gto_id, image_id)`);
console.log('✅ Tabela gto_images criada/verificada');
try {
await client.query(`ALTER TABLE images ADD COLUMN client_name VARCHAR(100)`);
await client.query(`CREATE INDEX idx_client_name ON images (client_name)`);
console.log('✅ Coluna client_name adicionada');
} catch (e) { /* já existe */ }
try {
await client.query(`ALTER TABLE images ADD COLUMN doctor VARCHAR(255)`);
} catch (e) { /* já existe */ }
try {
await client.query(`ALTER TABLE images ADD COLUMN remark TEXT`);
} catch (e) { /* já existe */ }
try {
await client.query(`ALTER TABLE images ADD COLUMN thumb_filename VARCHAR(500)`);
console.log('✅ Coluna thumb_filename adicionada');
} catch (e) { /* já existe */ }
// Inserir usuários padrões se a tabela estiver vazia
try {
const userCountRes = await client.query('SELECT COUNT(*) as count FROM users');
const userCount = parseInt(userCountRes.rows[0].count, 10);
if (userCount === 0) {
console.log('⚠️ Nenhum usuário encontrado no PostgreSQL. Criando usuários padrões...');
const bcrypt = require('bcrypt');
const hash1 = await bcrypt.hash('Rc362514', 10);
const hash2 = await bcrypt.hash('admin1234', 10);
await client.query(
`INSERT INTO users (username, email, password_hash) VALUES ($1, $2, $3)`,
['rcesar', 'rcesar@rcesar.com', hash1]
);
await client.query(
`INSERT INTO users (username, email, password_hash) VALUES ($1, $2, $3)`,
['admin', 'admin@dental.local', hash2]
);
console.log('✅ Usuários padrões (rcesar e admin) criados com sucesso no PostgreSQL!');
}
} catch (e) {
console.error('⚠️ Erro ao inicializar usuários padrões no PostgreSQL:', e.message);
}
} catch (error) {
console.error('Erro ao criar tabelas:', error);
} finally {
client.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 pgQuery = convertSqlForPg(query);
const res = await pool.query(pgQuery, params);
resolve(res.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 pgQuery = convertSqlForPg(query);
const res = await pool.query(pgQuery, params);
resolve(res.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 {
let pgQuery = convertSqlForPg(query);
// Auto-append RETURNING id for INSERT if not present (except for settings table)
if (pgQuery.trim().toUpperCase().startsWith('INSERT') &&
!pgQuery.toUpperCase().includes('RETURNING') &&
!pgQuery.toUpperCase().includes('SETTINGS')) {
pgQuery += ' RETURNING id';
}
const res = await pool.query(pgQuery, params);
let lastID = null;
if (res.rows && res.rows.length > 0 && res.rows[0].id) {
lastID = res.rows[0].id;
}
resolve({
lastID: lastID,
changes: res.rowCount
});
} catch (error) {
reject(error);
}
});
}
// ================================================================
// EXPORT
// ================================================================
module.exports = {
initDatabase,
get,
all,
run,
get db() { return isSqlite ? sqliteDb : pool; }
};