diff --git a/dental-server/public/app.js b/dental-server/public/app.js index 487c1ae..e36d4bb 100644 --- a/dental-server/public/app.js +++ b/dental-server/public/app.js @@ -451,7 +451,7 @@ async function loadPatients(silent = false, loadMore = false) { // Template de um cartão de paciente (reutilizado no render completo e no append) function patientCardHTML(p, index) { const fullName = p.patient_name || 'Sem nome'; - const thumb = p.thumb_filename ? `/uploads/${p.thumb_filename}` : ''; + const thumb = p.thumb_url || (p.thumb_filename ? `/uploads/${p.thumb_filename}` : ''); const count = p.image_count || 0; const date = formatDate(p.last_date); const doctor = p.doctor || '—'; @@ -610,7 +610,7 @@ async function loadPatientImages(patientName) { function renderPatientImages() { imagesGrid.innerHTML = patientImages.map(image => { - const thumbUrl = image.thumb_filename ? `/uploads/${image.thumb_filename}` : `/uploads/${image.filename}`; + const thumbUrl = image.thumb_url || (image.thumb_filename ? `/uploads/${image.thumb_filename}` : (image.file_url || `/uploads/${image.filename}`)); return `
diff --git a/dental-server/routes/images.js b/dental-server/routes/images.js index fbe5028..ec426b0 100644 --- a/dental-server/routes/images.js +++ b/dental-server/routes/images.js @@ -159,6 +159,21 @@ router.get('/patients', async (req, res) => { params ); + // Converter thumb_filename para URL completa (Wasabi ou local) + for (const row of rows) { + if (row.thumb_filename) { + try { + const thumbUrl = await storage.getImageUrl(row.thumb_filename, row.client_name, row.patient_name); + row.thumb_url = thumbUrl || `/uploads/${row.thumb_filename}`; + } catch (e) { + console.error('Erro ao gerar URL de thumbnail:', e); + row.thumb_url = `/uploads/${row.thumb_filename}`; + } + } else { + row.thumb_url = ''; + } + } + res.json(rows); } catch (error) { console.error('Erro ao listar pacientes:', error); @@ -186,6 +201,24 @@ router.get('/by-patient', async (req, res) => { [patientName, showDisabled ? 0 : 1] ); + // Converter filenames para URLs completas (Wasabi ou local) + for (const image of images) { + try { + if (image.filename) { + const fileUrl = await storage.getImageUrl(image.filename, image.client_name, image.patient_name); + image.file_url = fileUrl || `/uploads/${image.filename}`; + } + if (image.thumb_filename) { + const thumbUrl = await storage.getImageUrl(image.thumb_filename, image.client_name, image.patient_name); + image.thumb_url = thumbUrl || `/uploads/${image.thumb_filename}`; + } + } catch (e) { + console.error('Erro ao gerar URL para imagem:', e); + image.file_url = image.file_url || `/uploads/${image.filename}`; + image.thumb_url = image.thumb_url || `/uploads/${image.thumb_filename}`; + } + } + res.json(images); } catch (error) { console.error('Erro ao buscar imagens do paciente:', error); @@ -462,11 +495,22 @@ router.delete('/:id', async (req, res) => { // ================================================================ router.get('/:id/file-url', async (req, res) => { try { - const image = await db.get('SELECT filename FROM images WHERE id = $1', [req.params.id]); + const image = await db.get('SELECT filename, client_name, patient_name FROM images WHERE id = $1', [req.params.id]); if (!image) return res.status(404).json({ error: 'Imagem não encontrada' }); - const url = image.filename.startsWith('http') - ? image.filename - : `/uploads/${image.filename}`; + + let url; + if (image.filename.startsWith('http')) { + url = image.filename; + } else { + // Se o arquivo está no Wasabi, reconstroem a URL + try { + const wasabiUrl = await storage.getImageUrl(image.filename, image.client_name, image.patient_name); + url = wasabiUrl || `/uploads/${image.filename}`; + } catch (e) { + console.error('Erro ao gerar URL Wasabi:', e); + url = `/uploads/${image.filename}`; + } + } res.json({ url }); } catch (e) { res.status(500).json({ error: e.message }); diff --git a/dental-server/storage.js b/dental-server/storage.js index ed063b1..f40af73 100644 --- a/dental-server/storage.js +++ b/dental-server/storage.js @@ -105,6 +105,32 @@ function sanitizePathSegment(segment) { return segment.trim().replace(/[\/\\?#%*:"<>|]/g, '_'); } +/** + * Retorna a URL assinada de uma imagem no Wasabi (ou null se não estiver lá ou Wasabi desativado) + */ +async function getImageUrl(filename, clientName, patientName, isProcessed = false, expirySeconds = 3600) { + if (!s3 || !currentBucket || !clientName || !patientName) { + return null; + } + + try { + const cleanClient = sanitizePathSegment(clientName); + const cleanPatient = sanitizePathSegment(patientName); + const key = `${cleanClient}/${cleanPatient}/${filename}`; + + // Gerar URL assinada com expiração (padrão: 1 hora) + const url = await getSignedUrl( + s3, + new GetObjectCommand({ Bucket: currentBucket, Key: key }), + { expiresIn: expirySeconds } + ); + return url; + } catch (error) { + console.error(`⚠️ [Wasabi] Erro ao gerar URL assinada para ${filename}:`, error.message); + return null; + } +} + /** * Salva a imagem. Primeiro no disco local e depois, se o Wasabi estiver ativo, faz o upload. * Remove a versão local temporária se o upload pro Wasabi der certo. @@ -335,6 +361,7 @@ module.exports = { loadConfigFromDb, saveImage, getImageBuffer, + getImageUrl, deleteImage, getDownloadPresignedUrl, getUploadPresignedUrl, diff --git a/dental-server/test-bcrypt.js b/dental-server/test-bcrypt.js new file mode 100644 index 0000000..5dd1f5a --- /dev/null +++ b/dental-server/test-bcrypt.js @@ -0,0 +1,13 @@ +const bcrypt = require('bcrypt'); +const hash = '$2b$10$hE94QZePo4G3a0L.ybjHUOtTLmGFcIpicXbstb6rvsV78Yehzez9S'; +const pass = 'admin1234'; +const pass2 = 'Scoreodonto'; +const pass3 = 'scoreodonto'; + +async function test() { + console.log("admin1234:", await bcrypt.compare('admin1234', hash)); + console.log("Scoreodonto:", await bcrypt.compare('Scoreodonto', hash)); + console.log("scoreodonto:", await bcrypt.compare('scoreodonto', hash)); + process.exit(0); +} +test(); diff --git a/dental-server/test-db.js b/dental-server/test-db.js new file mode 100644 index 0000000..927c90a --- /dev/null +++ b/dental-server/test-db.js @@ -0,0 +1,26 @@ +const { Pool } = require('pg'); + +const pool = new Pool({ + host: process.env.DB_HOST || '10.99.0.3', + port: process.env.DB_PORT || 5432, + user: process.env.DB_USER || 'clube67', + database: process.env.DB_NAME || 'dental_images', + password: process.env.DB_PASSWORD || '' +}); + +async function run() { + try { + const res = await pool.query(` + SELECT column_name, data_type + FROM information_schema.columns + WHERE table_name = 'images'; + `); + console.table(res.rows); + } catch(e) { + console.error(e); + } finally { + pool.end(); + } +} + +run(); diff --git a/dental-server/test-db2.js b/dental-server/test-db2.js new file mode 100644 index 0000000..48a2cb4 --- /dev/null +++ b/dental-server/test-db2.js @@ -0,0 +1,18 @@ +const { Pool } = require('pg'); +const pool = new Pool({ + host: '10.99.0.3', + port: 5432, + user: 'clube67', + password: 'clube67_db_pass_9903', + database: 'dental_images' +}); + +async function test() { + const token = 'bab18d5a-09af-4759-b742-4e1eeb651694'; + const email = 'ruibto@gmail.com'; + console.log("Looking up token:", token); + const res = await pool.query(`SELECT * FROM clinics_devices WHERE machine_token = $1`, [token]); + console.log("Device found:", res.rows[0] || 'NOT FOUND'); + process.exit(0); +} +test(); diff --git a/dental-server/test-wasabi-url.js b/dental-server/test-wasabi-url.js new file mode 100644 index 0000000..f3b33f8 --- /dev/null +++ b/dental-server/test-wasabi-url.js @@ -0,0 +1,19 @@ +const db = require('./database'); +const storage = require('./storage'); + +async function test() { + await db.initDatabase(); + await storage.loadConfigFromDb(); + const url = await storage.getDownloadPresignedUrl('1779978903531_1779978896458-6xwema4i0.png'); + console.log('Presigned URL:', url); + + if (url) { + const fetch = require('node-fetch'); + const res = await fetch(url); + console.log('Status:', res.status); + const text = await res.text(); + console.log('Response body:', text.substring(0, 200)); + } +} + +test().catch(console.error).finally(() => process.exit(0)); diff --git a/dental-server/updates/ScoreOdonto Dental Client Setup 1.2.5.exe b/dental-server/updates/ScoreOdonto Dental Client Setup 1.2.5.exe new file mode 100644 index 0000000..72441cc Binary files /dev/null and b/dental-server/updates/ScoreOdonto Dental Client Setup 1.2.5.exe differ diff --git a/dental-server/updates/ScoreOdonto Dental Client Setup 1.2.6.exe b/dental-server/updates/ScoreOdonto Dental Client Setup 1.2.6.exe new file mode 100644 index 0000000..2310ecf Binary files /dev/null and b/dental-server/updates/ScoreOdonto Dental Client Setup 1.2.6.exe differ diff --git a/dental-server/updates/latest.yml b/dental-server/updates/latest.yml new file mode 100644 index 0000000..f9ab2c0 --- /dev/null +++ b/dental-server/updates/latest.yml @@ -0,0 +1,9 @@ +version: 1.2.6 +files: + - url: ScoreOdonto Dental Client Setup 1.2.6.exe + sha512: zEYuIRBUTlpGa1QtdUy7NOIKPJ6g5BtfvMIJRnfV6kCstcSfc0YhOaT+I5YlNu/96peTloPnpkESxMQxg0qhBA== + size: 102459655 + isAdminRightsRequired: true +path: ScoreOdonto Dental Client Setup 1.2.6.exe +sha512: zEYuIRBUTlpGa1QtdUy7NOIKPJ6g5BtfvMIJRnfV6kCstcSfc0YhOaT+I5YlNu/96peTloPnpkESxMQxg0qhBA== +releaseDate: '2026-05-28T03:45:37.308Z'