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();
|
||||
setSaving(true);
|
||||
try {
|
||||
await api.post('/images/patients', form);
|
||||
showToast('Paciente criado com sucesso!', 'success');
|
||||
const response = await api.post('/images/patients', form);
|
||||
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: '' });
|
||||
onClose();
|
||||
onCreated?.();
|
||||
|
||||
@@ -25,8 +25,14 @@ export default function EditPatientModal({ isOpen, onClose, patient, onSaved })
|
||||
if (!form.newName.trim()) return showToast('Nome é obrigatório.', 'error');
|
||||
setSaving(true);
|
||||
try {
|
||||
await api.put(`/images/patients/${encodeURIComponent(patient.patient_name)}`, form);
|
||||
showToast('Paciente atualizado com sucesso!', 'success');
|
||||
const response = await api.put(`/images/patients/${encodeURIComponent(patient.patient_name)}`, form);
|
||||
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?.();
|
||||
onClose();
|
||||
} 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;
|
||||
showToast('Excluindo imagens do paciente...', 'info');
|
||||
try {
|
||||
await api.delete(`/images/by-patient?name=${encodeURIComponent(patientName)}`);
|
||||
showToast('✅ Todas as imagens foram excluídas!', 'success');
|
||||
const response = await api.delete(`/images/by-patient?name=${encodeURIComponent(patientName)}`);
|
||||
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();
|
||||
loadClientsList();
|
||||
} catch (e) { showToast(e.response?.data?.error || 'Falha ao excluir.', 'error'); }
|
||||
|
||||
@@ -83,8 +83,14 @@ export default function PatientsPage() {
|
||||
const deletePatient = async (patientName) => {
|
||||
if (!confirm(`⚠️ Excluir permanentemente todas as imagens de "${patientName}"?\n\nEsta ação não pode ser desfeita.`)) return;
|
||||
try {
|
||||
await api.delete(`/images/by-patient?name=${encodeURIComponent(patientName)}`);
|
||||
showToast('Paciente excluído com sucesso!', 'success');
|
||||
const response = await api.delete(`/images/by-patient?name=${encodeURIComponent(patientName)}`);
|
||||
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);
|
||||
} catch (e) {
|
||||
showToast(e.response?.data?.error || 'Falha ao excluir.', 'error');
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "dental-image-server",
|
||||
"version": "2.0.5",
|
||||
"version": "2.1.4",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "dental-image-server",
|
||||
"version": "2.0.5",
|
||||
"version": "2.1.4",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.1055.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"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",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
|
||||
@@ -2,10 +2,44 @@ const express = require('express');
|
||||
const router = express.Router();
|
||||
const db = require('../database');
|
||||
const imageProcessor = require('../image-processor');
|
||||
const crypto = require('crypto');
|
||||
const path = require('path');
|
||||
const fs = require('fs').promises;
|
||||
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
|
||||
// ================================================================
|
||||
@@ -217,7 +251,6 @@ router.post('/patients', async (req, res) => {
|
||||
const fullName = `${firstName || ''} ${lastName || ''}`.trim();
|
||||
if (!fullName) return res.status(400).json({ error: 'Nome obrigatório' });
|
||||
|
||||
const crypto = require('crypto');
|
||||
const dummyGuid = crypto.randomUUID();
|
||||
|
||||
await db.run(
|
||||
@@ -235,14 +268,11 @@ router.post('/patients', async (req, res) => {
|
||||
);
|
||||
|
||||
const io = req.app.get('io') || global.io;
|
||||
if (io) {
|
||||
io.emit('remote-crud-patient', {
|
||||
action: 'create',
|
||||
patient: { id: dummyGuid, firstName, lastName, doctor, birthday, gender, phone, remark, fullName }
|
||||
});
|
||||
}
|
||||
const syncResult = await emitAndAwaitAck(io, '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) {
|
||||
console.error('Erro ao criar paciente:', error);
|
||||
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;
|
||||
if (io) {
|
||||
io.emit('remote-crud-patient', {
|
||||
action: 'update',
|
||||
oldName: oldName,
|
||||
patient: { newName: newName?.trim(), doctor, remark }
|
||||
});
|
||||
}
|
||||
|
||||
res.json({ success: true, updated: result.changes });
|
||||
const syncResult = await emitAndAwaitAck(io, 'update', {
|
||||
oldName: oldName,
|
||||
patient: { newName: newName?.trim(), doctor, remark }
|
||||
});
|
||||
|
||||
res.json({ ...syncResult, updated: result.changes });
|
||||
} catch (error) {
|
||||
console.error('Erro ao editar paciente:', error);
|
||||
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]);
|
||||
|
||||
const io = req.app.get('io') || global.io;
|
||||
if (io) {
|
||||
io.emit('remote-crud-patient', { action: 'delete', patientName });
|
||||
}
|
||||
const syncResult = await emitAndAwaitAck(io, 'delete', { patientName });
|
||||
|
||||
res.json({ success: true, count: images.length });
|
||||
res.json({ ...syncResult, count: images.length });
|
||||
} catch (error) {
|
||||
console.error('Erro ao excluir imagens do paciente:', error);
|
||||
res.status(500).json({ error: error.message });
|
||||
|
||||
@@ -42,6 +42,10 @@ const io = socketIo(server, {
|
||||
maxHttpBufferSize: 5e7 // 50MB para suportar raios-x grandes
|
||||
});
|
||||
|
||||
app.set('io', io);
|
||||
global.io = io;
|
||||
global.pendingAcks = new Map();
|
||||
|
||||
const connectedClients = [];
|
||||
|
||||
const PORT = process.env.PORT || 3000;
|
||||
@@ -851,6 +855,15 @@ io.on('connection', (socket) => {
|
||||
}
|
||||
}, 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
|
||||
socket.on('client-identify', (data) => {
|
||||
console.log('👤 [client-identify] Evento recebido de:', socket.id);
|
||||
|
||||
Reference in New Issue
Block a user