Files
rx.scoreodonto.com/dental-server/database.js
T
VPS 4 Deploy Agent d86def2f12
continuous-integration/webhook Deploy concluído (VPS4)
feat(observation): separar observacao de imagem da observacao global do paciente (image_remark)
2026-06-01 03:48:34 +02:00

252 lines
8.3 KiB
JavaScript

const { Pool } = require('pg');
require('dotenv').config();
let pool = null;
// ================================================================
// INICIALIZAR BANCO DE DADOS POSTGRESQL
// ================================================================
async function initDatabase() {
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 {
pool = new Pool(dbConfig);
// Testa a conexão
const client = await pool.connect();
console.log(`✅ Conectado ao PostgreSQL (Host: ${dbConfig.host}, DB: ${dbConfig.database})`);
client.release();
// Criar as tabelas
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,
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 no PostgreSQL');
// 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 BOOLEAN DEFAULT true,
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),
remark TEXT,
thumb_filename TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
`);
// 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)');
// Garantir que a coluna exista caso a tabela já tenha sido criada antes
await client.query('ALTER TABLE images ADD COLUMN IF NOT EXISTS thumb_filename TEXT;');
await client.query('ALTER TABLE images ADD COLUMN IF NOT EXISTS clinic_name VARCHAR(100);');
await client.query('ALTER TABLE images ADD COLUMN IF NOT EXISTS image_remark TEXT;');
console.log('✅ Tabela images criada/verificada no PostgreSQL');
// 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)');
await client.query('ALTER TABLE gtos ADD COLUMN IF NOT EXISTS sent BOOLEAN DEFAULT FALSE;');
await client.query('ALTER TABLE gtos ADD COLUMN IF NOT EXISTS sent_at TIMESTAMP;');
console.log('✅ Tabela gtos criada/verificada no PostgreSQL');
// 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 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 no PostgreSQL');
// Tabela de Clínicas / Dispositivos
await client.query(`
CREATE TABLE IF NOT EXISTS clinics_devices (
id SERIAL PRIMARY KEY,
clinic_name VARCHAR(255) NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
password_hash TEXT NOT NULL,
pc_name VARCHAR(100),
machine_token UUID NOT NULL UNIQUE,
last_ip VARCHAR(50),
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
`);
console.log('✅ Tabela clinics_devices criada/verificada no PostgreSQL');
// Tabela de configurações do sistema (key-value)
await client.query(`
CREATE TABLE IF NOT EXISTS system_config (
key VARCHAR(100) PRIMARY KEY,
value TEXT,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
`);
console.log('✅ Tabela system_config criada/verificada no PostgreSQL');
// Verificar colunas faltantes em users (migração)
try {
const res = await client.query(`
SELECT column_name
FROM information_schema.columns
WHERE table_name = 'users' AND column_name = 'clinic_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 (e) {
console.warn('⚠️ Erro ao migrar tabela users:', e.message);
}
// Criar administrador padrão se não houver usuários
try {
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.error('⚠️ Erro ao criar admin padrão:', error.message);
}
} catch (error) {
console.error('❌ Erro ao criar tabelas no PostgreSQL:', error);
} finally {
client.release();
}
}
// ================================================================
// FUNÇÕES DE CONSULTA (ENVOLVENDO O POOL DO PG)
// ================================================================
function get(query, params = []) {
return new Promise(async (resolve, reject) => {
try {
const res = await pool.query(query, params);
resolve(res.rows[0] || null);
} catch (error) {
reject(error);
}
});
}
function all(query, params = []) {
return new Promise(async (resolve, reject) => {
try {
const res = await pool.query(query, params);
resolve(res.rows || []);
} catch (error) {
reject(error);
}
});
}
function run(query, params = []) {
return new Promise(async (resolve, reject) => {
try {
const res = await pool.query(query, 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: insertedId,
changes: res.rowCount
});
} catch (error) {
reject(error);
}
});
}
// ================================================================
// EXPORT
// ================================================================
module.exports = {
initDatabase,
get,
all,
run,
get db() { return pool; }
};