feat: Adicionado timeout assíncrono para ACK do Cliente Windows
continuous-integration/webhook Deploy concluído (VPS4)
continuous-integration/webhook Deploy concluído (VPS4)
This commit is contained in:
@@ -1 +1 @@
|
|||||||
2.1.63
|
2.1.65
|
||||||
|
|||||||
@@ -14,8 +14,14 @@ export default function CreatePatientModal({ isOpen, onClose, onCreated }) {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setSaving(true);
|
setSaving(true);
|
||||||
try {
|
try {
|
||||||
await api.post('/images/patients', form);
|
const response = await api.post('/images/patients', form);
|
||||||
showToast('Paciente criado com sucesso!', 'success');
|
if (response.data?.warning) {
|
||||||
|
showToast(response.data.warning, 'warning');
|
||||||
|
} else if (response.data?.message) {
|
||||||
|
showToast(response.data.message, 'success');
|
||||||
|
} else {
|
||||||
|
showToast('Paciente criado com sucesso!', 'success');
|
||||||
|
}
|
||||||
setForm({ firstName: '', lastName: '', doctor: '', birthday: '', gender: '0', phone: '', remark: '' });
|
setForm({ firstName: '', lastName: '', doctor: '', birthday: '', gender: '0', phone: '', remark: '' });
|
||||||
onClose();
|
onClose();
|
||||||
onCreated?.();
|
onCreated?.();
|
||||||
|
|||||||
@@ -25,8 +25,14 @@ export default function EditPatientModal({ isOpen, onClose, patient, onSaved })
|
|||||||
if (!form.newName.trim()) return showToast('Nome é obrigatório.', 'error');
|
if (!form.newName.trim()) return showToast('Nome é obrigatório.', 'error');
|
||||||
setSaving(true);
|
setSaving(true);
|
||||||
try {
|
try {
|
||||||
await api.put(`/images/patients/${encodeURIComponent(patient.patient_name)}`, form);
|
const response = await api.put(`/images/patients/${encodeURIComponent(patient.patient_name)}`, form);
|
||||||
showToast('Paciente atualizado com sucesso!', 'success');
|
if (response.data?.warning) {
|
||||||
|
showToast(response.data.warning, 'warning');
|
||||||
|
} else if (response.data?.message) {
|
||||||
|
showToast(response.data.message, 'success');
|
||||||
|
} else {
|
||||||
|
showToast('Paciente atualizado com sucesso!', 'success');
|
||||||
|
}
|
||||||
onSaved?.();
|
onSaved?.();
|
||||||
onClose();
|
onClose();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -253,8 +253,14 @@ export default function DashboardPage() {
|
|||||||
if (!confirm(`⚠️ Excluir permanentemente todas as imagens de "${patientName}"?\n\nEsta ação não pode ser desfeita.`)) return;
|
if (!confirm(`⚠️ Excluir permanentemente todas as imagens de "${patientName}"?\n\nEsta ação não pode ser desfeita.`)) return;
|
||||||
showToast('Excluindo imagens do paciente...', 'info');
|
showToast('Excluindo imagens do paciente...', 'info');
|
||||||
try {
|
try {
|
||||||
await api.delete(`/images/by-patient?name=${encodeURIComponent(patientName)}`);
|
const response = await api.delete(`/images/by-patient?name=${encodeURIComponent(patientName)}`);
|
||||||
showToast('✅ Todas as imagens foram excluídas!', 'success');
|
if (response.data?.warning) {
|
||||||
|
showToast(response.data.warning, 'warning');
|
||||||
|
} else if (response.data?.message) {
|
||||||
|
showToast(response.data.message, 'success');
|
||||||
|
} else {
|
||||||
|
showToast('✅ Todas as imagens foram excluídas!', 'success');
|
||||||
|
}
|
||||||
loadPatients();
|
loadPatients();
|
||||||
loadClientsList();
|
loadClientsList();
|
||||||
} catch (e) { showToast(e.response?.data?.error || 'Falha ao excluir.', 'error'); }
|
} catch (e) { showToast(e.response?.data?.error || 'Falha ao excluir.', 'error'); }
|
||||||
|
|||||||
@@ -83,8 +83,14 @@ export default function PatientsPage() {
|
|||||||
const deletePatient = async (patientName) => {
|
const deletePatient = async (patientName) => {
|
||||||
if (!confirm(`⚠️ Excluir permanentemente todas as imagens de "${patientName}"?\n\nEsta ação não pode ser desfeita.`)) return;
|
if (!confirm(`⚠️ Excluir permanentemente todas as imagens de "${patientName}"?\n\nEsta ação não pode ser desfeita.`)) return;
|
||||||
try {
|
try {
|
||||||
await api.delete(`/images/by-patient?name=${encodeURIComponent(patientName)}`);
|
const response = await api.delete(`/images/by-patient?name=${encodeURIComponent(patientName)}`);
|
||||||
showToast('Paciente excluído com sucesso!', 'success');
|
if (response.data?.warning) {
|
||||||
|
showToast(response.data.warning, 'warning');
|
||||||
|
} else if (response.data?.message) {
|
||||||
|
showToast(response.data.message, 'success');
|
||||||
|
} else {
|
||||||
|
showToast('Paciente excluído com sucesso!', 'success');
|
||||||
|
}
|
||||||
loadPatients(true);
|
loadPatients(true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showToast(e.response?.data?.error || 'Falha ao excluir.', 'error');
|
showToast(e.response?.data?.error || 'Falha ao excluir.', 'error');
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "dental-image-server",
|
"name": "dental-image-server",
|
||||||
"version": "2.0.5",
|
"version": "2.1.4",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "dental-image-server",
|
"name": "dental-image-server",
|
||||||
"version": "2.0.5",
|
"version": "2.1.4",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-s3": "^3.1055.0",
|
"@aws-sdk/client-s3": "^3.1055.0",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "dental-image-server",
|
"name": "dental-image-server",
|
||||||
"version": "2.1.3",
|
"version": "2.1.4",
|
||||||
"description": "Servidor Socket.IO para receber e processar imagens dentais com interface web",
|
"description": "Servidor Socket.IO para receber e processar imagens dentais com interface web",
|
||||||
"main": "server.js",
|
"main": "server.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -2,10 +2,44 @@ const express = require('express');
|
|||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const db = require('../database');
|
const db = require('../database');
|
||||||
const imageProcessor = require('../image-processor');
|
const imageProcessor = require('../image-processor');
|
||||||
|
const crypto = require('crypto');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const fs = require('fs').promises;
|
const fs = require('fs').promises;
|
||||||
const storage = require('../storage');
|
const storage = require('../storage');
|
||||||
|
|
||||||
|
async function emitAndAwaitAck(io, action, payload) {
|
||||||
|
if (!io) return { success: true, warning: 'Socket.IO não inicializado no servidor.' };
|
||||||
|
|
||||||
|
const eventId = crypto.randomUUID();
|
||||||
|
payload.eventId = eventId;
|
||||||
|
payload.action = action;
|
||||||
|
|
||||||
|
const ackPromise = new Promise((resolve) => {
|
||||||
|
if (global.pendingAcks) {
|
||||||
|
global.pendingAcks.set(eventId, resolve);
|
||||||
|
setTimeout(() => {
|
||||||
|
if (global.pendingAcks.has(eventId)) {
|
||||||
|
global.pendingAcks.delete(eventId);
|
||||||
|
resolve(null); // Timeout
|
||||||
|
}
|
||||||
|
}, 4000);
|
||||||
|
} else {
|
||||||
|
resolve(null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
io.emit('remote-crud-patient', payload);
|
||||||
|
|
||||||
|
const result = await ackPromise;
|
||||||
|
if (result && result.success) {
|
||||||
|
return { success: true, message: `Sincronizado com PC: ${result.pcName || 'Desconhecido'}`, ackInfo: result };
|
||||||
|
} else if (result && !result.success) {
|
||||||
|
return { success: true, warning: `Erro no app Windows (${result.pcName || 'Desconhecido'}): ${result.error || 'Erro local'}` };
|
||||||
|
} else {
|
||||||
|
return { success: true, warning: 'Salvo na nuvem, porém o App Windows não respondeu. Verifique se o PC está ligado e conectado.' };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ================================================================
|
// ================================================================
|
||||||
// GET /api/images/unique-clients - Buscar lista leve de clínicas
|
// GET /api/images/unique-clients - Buscar lista leve de clínicas
|
||||||
// ================================================================
|
// ================================================================
|
||||||
@@ -217,7 +251,6 @@ router.post('/patients', async (req, res) => {
|
|||||||
const fullName = `${firstName || ''} ${lastName || ''}`.trim();
|
const fullName = `${firstName || ''} ${lastName || ''}`.trim();
|
||||||
if (!fullName) return res.status(400).json({ error: 'Nome obrigatório' });
|
if (!fullName) return res.status(400).json({ error: 'Nome obrigatório' });
|
||||||
|
|
||||||
const crypto = require('crypto');
|
|
||||||
const dummyGuid = crypto.randomUUID();
|
const dummyGuid = crypto.randomUUID();
|
||||||
|
|
||||||
await db.run(
|
await db.run(
|
||||||
@@ -235,14 +268,11 @@ router.post('/patients', async (req, res) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const io = req.app.get('io') || global.io;
|
const io = req.app.get('io') || global.io;
|
||||||
if (io) {
|
const syncResult = await emitAndAwaitAck(io, 'create', {
|
||||||
io.emit('remote-crud-patient', {
|
patient: { id: dummyGuid, firstName, lastName, doctor, birthday, gender, phone, remark, fullName }
|
||||||
action: 'create',
|
});
|
||||||
patient: { id: dummyGuid, firstName, lastName, doctor, birthday, gender, phone, remark, fullName }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
res.json({ success: true, message: 'Paciente criado com sucesso' });
|
res.json(syncResult);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Erro ao criar paciente:', error);
|
console.error('Erro ao criar paciente:', error);
|
||||||
res.status(500).json({ error: error.message });
|
res.status(500).json({ error: error.message });
|
||||||
@@ -286,15 +316,12 @@ router.put('/patients/:name', async (req, res) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const io = req.app.get('io') || global.io;
|
const io = req.app.get('io') || global.io;
|
||||||
if (io) {
|
const syncResult = await emitAndAwaitAck(io, 'update', {
|
||||||
io.emit('remote-crud-patient', {
|
oldName: oldName,
|
||||||
action: 'update',
|
patient: { newName: newName?.trim(), doctor, remark }
|
||||||
oldName: oldName,
|
});
|
||||||
patient: { newName: newName?.trim(), doctor, remark }
|
|
||||||
});
|
res.json({ ...syncResult, updated: result.changes });
|
||||||
}
|
|
||||||
|
|
||||||
res.json({ success: true, updated: result.changes });
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Erro ao editar paciente:', error);
|
console.error('Erro ao editar paciente:', error);
|
||||||
res.status(500).json({ error: error.message });
|
res.status(500).json({ error: error.message });
|
||||||
@@ -318,11 +345,9 @@ router.delete('/by-patient', async (req, res) => {
|
|||||||
await db.run('DELETE FROM images WHERE patient_name = $1', [patientName]);
|
await db.run('DELETE FROM images WHERE patient_name = $1', [patientName]);
|
||||||
|
|
||||||
const io = req.app.get('io') || global.io;
|
const io = req.app.get('io') || global.io;
|
||||||
if (io) {
|
const syncResult = await emitAndAwaitAck(io, 'delete', { patientName });
|
||||||
io.emit('remote-crud-patient', { action: 'delete', patientName });
|
|
||||||
}
|
|
||||||
|
|
||||||
res.json({ success: true, count: images.length });
|
res.json({ ...syncResult, count: images.length });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Erro ao excluir imagens do paciente:', error);
|
console.error('Erro ao excluir imagens do paciente:', error);
|
||||||
res.status(500).json({ error: error.message });
|
res.status(500).json({ error: error.message });
|
||||||
|
|||||||
@@ -42,6 +42,10 @@ const io = socketIo(server, {
|
|||||||
maxHttpBufferSize: 5e7 // 50MB para suportar raios-x grandes
|
maxHttpBufferSize: 5e7 // 50MB para suportar raios-x grandes
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.set('io', io);
|
||||||
|
global.io = io;
|
||||||
|
global.pendingAcks = new Map();
|
||||||
|
|
||||||
const connectedClients = [];
|
const connectedClients = [];
|
||||||
|
|
||||||
const PORT = process.env.PORT || 3000;
|
const PORT = process.env.PORT || 3000;
|
||||||
@@ -851,6 +855,15 @@ io.on('connection', (socket) => {
|
|||||||
}
|
}
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
|
// Ouvinte para receber confirmações do CRUD remoto
|
||||||
|
socket.on('remote-crud-ack', (data) => {
|
||||||
|
if (data && data.eventId && global.pendingAcks && global.pendingAcks.has(data.eventId)) {
|
||||||
|
const resolve = global.pendingAcks.get(data.eventId);
|
||||||
|
resolve(data);
|
||||||
|
global.pendingAcks.delete(data.eventId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Identificação de clientes
|
// Identificação de clientes
|
||||||
socket.on('client-identify', (data) => {
|
socket.on('client-identify', (data) => {
|
||||||
console.log('👤 [client-identify] Evento recebido de:', socket.id);
|
console.log('👤 [client-identify] Evento recebido de:', socket.id);
|
||||||
|
|||||||
Reference in New Issue
Block a user