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