429 lines
13 KiB
JavaScript
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; }
|
|
}; |