676 lines
21 KiB
JavaScript
676 lines
21 KiB
JavaScript
/**
|
|
* EM2RP Cloud Functions
|
|
* Architecture backend sécurisée avec authentification et permissions
|
|
*/
|
|
|
|
const { onRequest } = require("firebase-functions/v2/https");
|
|
const logger = require("firebase-functions/logger");
|
|
const admin = require('firebase-admin');
|
|
const { Storage } = require('@google-cloud/storage');
|
|
|
|
// Utilitaires
|
|
const auth = require('./utils/auth');
|
|
const helpers = require('./utils/helpers');
|
|
|
|
// Initialisation
|
|
admin.initializeApp();
|
|
const storage = new Storage();
|
|
const db = admin.firestore();
|
|
|
|
// ============================================================================
|
|
// STORAGE - Move Event File
|
|
// ============================================================================
|
|
exports.moveEventFileV2 = onRequest({ cors: true }, async (req, res) => {
|
|
try {
|
|
const decodedToken = await auth.authenticateUser(req);
|
|
const { sourcePath, destinationPath } = req.body.data || {};
|
|
|
|
if (!sourcePath || !destinationPath) {
|
|
res.status(400).json({ error: 'Source and destination paths are required.' });
|
|
return;
|
|
}
|
|
|
|
const bucketName = admin.storage().bucket().name;
|
|
const bucket = storage.bucket(bucketName);
|
|
|
|
await bucket.file(sourcePath).copy(bucket.file(destinationPath));
|
|
await bucket.file(sourcePath).delete();
|
|
const [url] = await bucket.file(destinationPath).getSignedUrl({
|
|
action: 'read',
|
|
expires: '03-01-2500',
|
|
});
|
|
|
|
res.status(200).json({ url });
|
|
} catch (error) {
|
|
logger.error("Error moving file:", error);
|
|
res.status(500).json({ error: error.message });
|
|
}
|
|
});
|
|
|
|
// ============================================================================
|
|
// EQUIPMENT - CRUD
|
|
// ============================================================================
|
|
|
|
// Créer un équipement (admin ou manage_equipment)
|
|
exports.createEquipment = onRequest({ cors: true }, async (req, res) => {
|
|
try {
|
|
const decodedToken = await auth.authenticateUser(req);
|
|
const hasAccess = await auth.hasPermission(decodedToken.uid, 'manage_equipment');
|
|
|
|
if (!hasAccess) {
|
|
res.status(403).json({ error: 'Forbidden: Requires manage_equipment permission' });
|
|
return;
|
|
}
|
|
|
|
const equipmentData = req.body.data;
|
|
const equipmentId = equipmentData.id;
|
|
|
|
if (!equipmentId) {
|
|
res.status(400).json({ error: 'Equipment ID is required' });
|
|
return;
|
|
}
|
|
|
|
// Vérifier unicité de l'ID
|
|
const existingDoc = await db.collection('equipments').doc(equipmentId).get();
|
|
if (existingDoc.exists) {
|
|
res.status(409).json({ error: 'Equipment ID already exists' });
|
|
return;
|
|
}
|
|
|
|
// Convertir les timestamps
|
|
const dataToSave = helpers.deserializeTimestamps(equipmentData, [
|
|
'createdAt', 'updatedAt', 'purchaseDate', 'lastMaintenanceDate', 'nextMaintenanceDate'
|
|
]);
|
|
|
|
await db.collection('equipments').doc(equipmentId).set(dataToSave);
|
|
|
|
res.status(201).json({ id: equipmentId, message: 'Equipment created successfully' });
|
|
} catch (error) {
|
|
logger.error("Error creating equipment:", error);
|
|
res.status(500).json({ error: error.message });
|
|
}
|
|
});
|
|
|
|
// Mettre à jour un équipement
|
|
exports.updateEquipment = onRequest({ cors: true }, async (req, res) => {
|
|
try {
|
|
const decodedToken = await auth.authenticateUser(req);
|
|
const hasAccess = await auth.hasPermission(decodedToken.uid, 'manage_equipment');
|
|
|
|
if (!hasAccess) {
|
|
res.status(403).json({ error: 'Forbidden: Requires manage_equipment permission' });
|
|
return;
|
|
}
|
|
|
|
const { equipmentId, data } = req.body.data;
|
|
|
|
if (!equipmentId) {
|
|
res.status(400).json({ error: 'Equipment ID is required' });
|
|
return;
|
|
}
|
|
|
|
// Empêcher la modification de l'ID
|
|
delete data.id;
|
|
|
|
// Ajouter updatedAt
|
|
data.updatedAt = admin.firestore.Timestamp.now();
|
|
|
|
const dataToSave = helpers.deserializeTimestamps(data, [
|
|
'purchaseDate', 'lastMaintenanceDate', 'nextMaintenanceDate'
|
|
]);
|
|
|
|
await db.collection('equipments').doc(equipmentId).update(dataToSave);
|
|
|
|
res.status(200).json({ message: 'Equipment updated successfully' });
|
|
} catch (error) {
|
|
logger.error("Error updating equipment:", error);
|
|
res.status(500).json({ error: error.message });
|
|
}
|
|
});
|
|
|
|
// Supprimer un équipement
|
|
exports.deleteEquipment = onRequest({ cors: true }, async (req, res) => {
|
|
try {
|
|
const decodedToken = await auth.authenticateUser(req);
|
|
const hasAccess = await auth.hasPermission(decodedToken.uid, 'manage_equipment');
|
|
|
|
if (!hasAccess) {
|
|
res.status(403).json({ error: 'Forbidden: Requires manage_equipment permission' });
|
|
return;
|
|
}
|
|
|
|
const { equipmentId } = req.body.data;
|
|
|
|
if (!equipmentId) {
|
|
res.status(400).json({ error: 'Equipment ID is required' });
|
|
return;
|
|
}
|
|
|
|
// Vérifier si l'équipement est utilisé dans des événements actifs
|
|
const eventsSnapshot = await db.collection('events')
|
|
.where('status', '!=', 'CANCELLED')
|
|
.get();
|
|
|
|
for (const eventDoc of eventsSnapshot.docs) {
|
|
const eventData = eventDoc.data();
|
|
const assignedEquipment = eventData.assignedEquipment || [];
|
|
|
|
if (assignedEquipment.some(eq => eq.equipmentId === equipmentId)) {
|
|
res.status(409).json({
|
|
error: 'Cannot delete equipment: it is assigned to active events',
|
|
eventId: eventDoc.id
|
|
});
|
|
return;
|
|
}
|
|
}
|
|
|
|
await db.collection('equipments').doc(equipmentId).delete();
|
|
|
|
res.status(200).json({ message: 'Equipment deleted successfully' });
|
|
} catch (error) {
|
|
logger.error("Error deleting equipment:", error);
|
|
res.status(500).json({ error: error.message });
|
|
}
|
|
});
|
|
|
|
// Récupérer un équipement par ID
|
|
exports.getEquipment = onRequest({ cors: true }, async (req, res) => {
|
|
try {
|
|
const decodedToken = await auth.authenticateUser(req);
|
|
const hasViewAccess = await auth.hasPermission(decodedToken.uid, 'view_equipment');
|
|
const hasManageAccess = await auth.hasPermission(decodedToken.uid, 'manage_equipment');
|
|
|
|
if (!hasViewAccess && !hasManageAccess) {
|
|
res.status(403).json({ error: 'Forbidden: Requires view_equipment permission' });
|
|
return;
|
|
}
|
|
|
|
const { equipmentId } = req.body.data || req.query;
|
|
|
|
if (!equipmentId) {
|
|
res.status(400).json({ error: 'Equipment ID is required' });
|
|
return;
|
|
}
|
|
|
|
const doc = await db.collection('equipments').doc(equipmentId).get();
|
|
|
|
if (!doc.exists) {
|
|
res.status(404).json({ error: 'Equipment not found' });
|
|
return;
|
|
}
|
|
|
|
let data = { id: doc.id, ...doc.data() };
|
|
data = helpers.serializeTimestamps(data);
|
|
data = helpers.serializeReferences(data);
|
|
|
|
// Masquer les prix si pas de permission manage_equipment
|
|
data = helpers.maskSensitiveFields(data, hasManageAccess);
|
|
|
|
res.status(200).json({ equipment: data });
|
|
} catch (error) {
|
|
logger.error("Error getting equipment:", error);
|
|
res.status(500).json({ error: error.message });
|
|
}
|
|
});
|
|
|
|
// ============================================================================
|
|
// CONTAINERS - CRUD
|
|
// ============================================================================
|
|
|
|
// Créer un container
|
|
exports.createContainer = onRequest({ cors: true }, async (req, res) => {
|
|
try {
|
|
const decodedToken = await auth.authenticateUser(req);
|
|
const hasAccess = await auth.hasPermission(decodedToken.uid, 'manage_equipment');
|
|
|
|
if (!hasAccess) {
|
|
res.status(403).json({ error: 'Forbidden: Requires manage_equipment permission' });
|
|
return;
|
|
}
|
|
|
|
const containerData = req.body.data;
|
|
const containerId = containerData.id;
|
|
|
|
if (!containerId) {
|
|
res.status(400).json({ error: 'Container ID is required' });
|
|
return;
|
|
}
|
|
|
|
const existingDoc = await db.collection('containers').doc(containerId).get();
|
|
if (existingDoc.exists) {
|
|
res.status(409).json({ error: 'Container ID already exists' });
|
|
return;
|
|
}
|
|
|
|
const dataToSave = helpers.deserializeTimestamps(containerData, ['createdAt', 'updatedAt']);
|
|
|
|
await db.collection('containers').doc(containerId).set(dataToSave);
|
|
|
|
res.status(201).json({ id: containerId, message: 'Container created successfully' });
|
|
} catch (error) {
|
|
logger.error("Error creating container:", error);
|
|
res.status(500).json({ error: error.message });
|
|
}
|
|
});
|
|
|
|
// Mettre à jour un container
|
|
exports.updateContainer = onRequest({ cors: true }, async (req, res) => {
|
|
try {
|
|
const decodedToken = await auth.authenticateUser(req);
|
|
const hasAccess = await auth.hasPermission(decodedToken.uid, 'manage_equipment');
|
|
|
|
if (!hasAccess) {
|
|
res.status(403).json({ error: 'Forbidden: Requires manage_equipment permission' });
|
|
return;
|
|
}
|
|
|
|
const { containerId, data } = req.body.data;
|
|
|
|
if (!containerId) {
|
|
res.status(400).json({ error: 'Container ID is required' });
|
|
return;
|
|
}
|
|
|
|
delete data.id;
|
|
data.updatedAt = admin.firestore.Timestamp.now();
|
|
|
|
await db.collection('containers').doc(containerId).update(data);
|
|
|
|
res.status(200).json({ message: 'Container updated successfully' });
|
|
} catch (error) {
|
|
logger.error("Error updating container:", error);
|
|
res.status(500).json({ error: error.message });
|
|
}
|
|
});
|
|
|
|
// Supprimer un container
|
|
exports.deleteContainer = onRequest({ cors: true }, async (req, res) => {
|
|
try {
|
|
const decodedToken = await auth.authenticateUser(req);
|
|
const hasAccess = await auth.hasPermission(decodedToken.uid, 'manage_equipment');
|
|
|
|
if (!hasAccess) {
|
|
res.status(403).json({ error: 'Forbidden: Requires manage_equipment permission' });
|
|
return;
|
|
}
|
|
|
|
const { containerId } = req.body.data;
|
|
|
|
if (!containerId) {
|
|
res.status(400).json({ error: 'Container ID is required' });
|
|
return;
|
|
}
|
|
|
|
await db.collection('containers').doc(containerId).delete();
|
|
|
|
res.status(200).json({ message: 'Container deleted successfully' });
|
|
} catch (error) {
|
|
logger.error("Error deleting container:", error);
|
|
res.status(500).json({ error: error.message });
|
|
}
|
|
});
|
|
|
|
// ============================================================================
|
|
// EVENTS - CRUD
|
|
// ============================================================================
|
|
|
|
// Créer un événement
|
|
exports.createEvent = onRequest({ cors: true }, async (req, res) => {
|
|
try {
|
|
const decodedToken = await auth.authenticateUser(req);
|
|
const hasAccess = await auth.hasPermission(decodedToken.uid, 'edit_event');
|
|
|
|
if (!hasAccess) {
|
|
res.status(403).json({ error: 'Forbidden: Requires edit_event permission' });
|
|
return;
|
|
}
|
|
|
|
const eventData = req.body.data;
|
|
|
|
const dataToSave = helpers.deserializeTimestamps(eventData, [
|
|
'startDateTime', 'endDateTime', 'createdAt', 'updatedAt'
|
|
]);
|
|
|
|
const docRef = await db.collection('events').add(dataToSave);
|
|
|
|
res.status(201).json({ id: docRef.id, message: 'Event created successfully' });
|
|
} catch (error) {
|
|
logger.error("Error creating event:", error);
|
|
res.status(500).json({ error: error.message });
|
|
}
|
|
});
|
|
|
|
// Mettre à jour un événement
|
|
exports.updateEvent = onRequest({ cors: true }, async (req, res) => {
|
|
try {
|
|
const decodedToken = await auth.authenticateUser(req);
|
|
const hasAccess = await auth.hasPermission(decodedToken.uid, 'edit_event');
|
|
|
|
if (!hasAccess) {
|
|
res.status(403).json({ error: 'Forbidden: Requires edit_event permission' });
|
|
return;
|
|
}
|
|
|
|
const { eventId, data } = req.body.data;
|
|
|
|
if (!eventId) {
|
|
res.status(400).json({ error: 'Event ID is required' });
|
|
return;
|
|
}
|
|
|
|
delete data.id;
|
|
data.updatedAt = admin.firestore.Timestamp.now();
|
|
|
|
const dataToSave = helpers.deserializeTimestamps(data, [
|
|
'startDateTime', 'endDateTime'
|
|
]);
|
|
|
|
await db.collection('events').doc(eventId).update(dataToSave);
|
|
|
|
res.status(200).json({ message: 'Event updated successfully' });
|
|
} catch (error) {
|
|
logger.error("Error updating event:", error);
|
|
res.status(500).json({ error: error.message });
|
|
}
|
|
});
|
|
|
|
// Supprimer un événement
|
|
exports.deleteEvent = onRequest({ cors: true }, async (req, res) => {
|
|
try {
|
|
const decodedToken = await auth.authenticateUser(req);
|
|
const hasAccess = await auth.hasPermission(decodedToken.uid, 'delete_event');
|
|
|
|
if (!hasAccess) {
|
|
res.status(403).json({ error: 'Forbidden: Requires delete_event permission' });
|
|
return;
|
|
}
|
|
|
|
const { eventId } = req.body.data;
|
|
|
|
if (!eventId) {
|
|
res.status(400).json({ error: 'Event ID is required' });
|
|
return;
|
|
}
|
|
|
|
await db.collection('events').doc(eventId).delete();
|
|
|
|
res.status(200).json({ message: 'Event deleted successfully' });
|
|
} catch (error) {
|
|
logger.error("Error deleting event:", error);
|
|
res.status(500).json({ error: error.message });
|
|
}
|
|
});
|
|
|
|
// ============================================================================
|
|
// MAINTENANCES - CRUD
|
|
// ============================================================================
|
|
|
|
// Créer une maintenance
|
|
exports.createMaintenance = onRequest({ cors: true }, async (req, res) => {
|
|
try {
|
|
const decodedToken = await auth.authenticateUser(req);
|
|
const hasAccess = await auth.hasPermission(decodedToken.uid, 'manage_maintenances');
|
|
|
|
if (!hasAccess) {
|
|
res.status(403).json({ error: 'Forbidden: Requires manage_maintenances permission' });
|
|
return;
|
|
}
|
|
|
|
const maintenanceData = req.body.data;
|
|
|
|
const dataToSave = helpers.deserializeTimestamps(maintenanceData, [
|
|
'scheduledDate', 'completedDate', 'createdAt', 'updatedAt'
|
|
]);
|
|
|
|
const docRef = await db.collection('maintenances').add(dataToSave);
|
|
|
|
res.status(201).json({ id: docRef.id, message: 'Maintenance created successfully' });
|
|
} catch (error) {
|
|
logger.error("Error creating maintenance:", error);
|
|
res.status(500).json({ error: error.message });
|
|
}
|
|
});
|
|
|
|
// Mettre à jour une maintenance
|
|
exports.updateMaintenance = onRequest({ cors: true }, async (req, res) => {
|
|
try {
|
|
const decodedToken = await auth.authenticateUser(req);
|
|
const hasAccess = await auth.hasPermission(decodedToken.uid, 'manage_maintenances');
|
|
|
|
if (!hasAccess) {
|
|
res.status(403).json({ error: 'Forbidden: Requires manage_maintenances permission' });
|
|
return;
|
|
}
|
|
|
|
const { maintenanceId, data } = req.body.data;
|
|
|
|
if (!maintenanceId) {
|
|
res.status(400).json({ error: 'Maintenance ID is required' });
|
|
return;
|
|
}
|
|
|
|
delete data.id;
|
|
data.updatedAt = admin.firestore.Timestamp.now();
|
|
|
|
const dataToSave = helpers.deserializeTimestamps(data, [
|
|
'scheduledDate', 'completedDate'
|
|
]);
|
|
|
|
await db.collection('maintenances').doc(maintenanceId).update(dataToSave);
|
|
|
|
res.status(200).json({ message: 'Maintenance updated successfully' });
|
|
} catch (error) {
|
|
logger.error("Error updating maintenance:", error);
|
|
res.status(500).json({ error: error.message });
|
|
}
|
|
});
|
|
|
|
// ============================================================================
|
|
// OPTIONS - CRUD
|
|
// ============================================================================
|
|
|
|
// Créer une option
|
|
exports.createOption = onRequest({ cors: true }, async (req, res) => {
|
|
try {
|
|
const decodedToken = await auth.authenticateUser(req);
|
|
const isAdminUser = await auth.isAdmin(decodedToken.uid);
|
|
|
|
if (!isAdminUser) {
|
|
res.status(403).json({ error: 'Forbidden: Admin access required' });
|
|
return;
|
|
}
|
|
|
|
const optionData = req.body.data;
|
|
const optionId = optionData.id;
|
|
|
|
if (!optionId) {
|
|
res.status(400).json({ error: 'Option ID is required' });
|
|
return;
|
|
}
|
|
|
|
await db.collection('options').doc(optionId).set(optionData);
|
|
|
|
res.status(201).json({ id: optionId, message: 'Option created successfully' });
|
|
} catch (error) {
|
|
logger.error("Error creating option:", error);
|
|
res.status(500).json({ error: error.message });
|
|
}
|
|
});
|
|
|
|
// Mettre à jour une option
|
|
exports.updateOption = onRequest({ cors: true }, async (req, res) => {
|
|
try {
|
|
const decodedToken = await auth.authenticateUser(req);
|
|
const isAdminUser = await auth.isAdmin(decodedToken.uid);
|
|
|
|
if (!isAdminUser) {
|
|
res.status(403).json({ error: 'Forbidden: Admin access required' });
|
|
return;
|
|
}
|
|
|
|
const { optionId, data } = req.body.data;
|
|
|
|
if (!optionId) {
|
|
res.status(400).json({ error: 'Option ID is required' });
|
|
return;
|
|
}
|
|
|
|
delete data.id;
|
|
|
|
await db.collection('options').doc(optionId).update(data);
|
|
|
|
res.status(200).json({ message: 'Option updated successfully' });
|
|
} catch (error) {
|
|
logger.error("Error updating option:", error);
|
|
res.status(500).json({ error: error.message });
|
|
}
|
|
});
|
|
|
|
// Supprimer une option
|
|
exports.deleteOption = onRequest({ cors: true }, async (req, res) => {
|
|
try {
|
|
const decodedToken = await auth.authenticateUser(req);
|
|
const isAdminUser = await auth.isAdmin(decodedToken.uid);
|
|
|
|
if (!isAdminUser) {
|
|
res.status(403).json({ error: 'Forbidden: Admin access required' });
|
|
return;
|
|
}
|
|
|
|
const { optionId } = req.body.data;
|
|
|
|
if (!optionId) {
|
|
res.status(400).json({ error: 'Option ID is required' });
|
|
return;
|
|
}
|
|
|
|
await db.collection('options').doc(optionId).delete();
|
|
|
|
res.status(200).json({ message: 'Option deleted successfully' });
|
|
} catch (error) {
|
|
logger.error("Error deleting option:", error);
|
|
res.status(500).json({ error: error.message });
|
|
}
|
|
});
|
|
|
|
// ============================================================================
|
|
// USERS - CRUD
|
|
// ============================================================================
|
|
|
|
// Créer un utilisateur
|
|
exports.createUser = onRequest({ cors: true }, async (req, res) => {
|
|
try {
|
|
const decodedToken = await auth.authenticateUser(req);
|
|
const isAdminUser = await auth.isAdmin(decodedToken.uid);
|
|
|
|
if (!isAdminUser) {
|
|
res.status(403).json({ error: 'Forbidden: Admin access required' });
|
|
return;
|
|
}
|
|
|
|
const userData = req.body.data;
|
|
const userId = userData.uid;
|
|
|
|
if (!userId) {
|
|
res.status(400).json({ error: 'User ID is required' });
|
|
return;
|
|
}
|
|
|
|
await db.collection('users').doc(userId).set(userData);
|
|
|
|
res.status(201).json({ id: userId, message: 'User created successfully' });
|
|
} catch (error) {
|
|
logger.error("Error creating user:", error);
|
|
res.status(500).json({ error: error.message });
|
|
}
|
|
});
|
|
|
|
// Mettre à jour un utilisateur
|
|
exports.updateUser = onRequest({ cors: true }, async (req, res) => {
|
|
try {
|
|
const decodedToken = await auth.authenticateUser(req);
|
|
const { userId, data } = req.body.data;
|
|
|
|
if (!userId) {
|
|
res.status(400).json({ error: 'User ID is required' });
|
|
return;
|
|
}
|
|
|
|
// Vérifier si l'utilisateur met à jour son propre profil ou est admin
|
|
const isOwnProfile = decodedToken.uid === userId;
|
|
const isAdminUser = await auth.isAdmin(decodedToken.uid);
|
|
const hasEditPermission = await auth.hasPermission(decodedToken.uid, 'edit_user');
|
|
|
|
if (!isOwnProfile && !isAdminUser && !hasEditPermission) {
|
|
res.status(403).json({ error: 'Forbidden: Cannot edit other users' });
|
|
return;
|
|
}
|
|
|
|
// Si mise à jour propre profil, limiter les champs modifiables
|
|
if (isOwnProfile && !isAdminUser) {
|
|
const allowedFields = ['firstName', 'lastName', 'phoneNumber', 'profilePhotoUrl'];
|
|
const filteredData = {};
|
|
|
|
for (const field of allowedFields) {
|
|
if (data[field] !== undefined) {
|
|
filteredData[field] = data[field];
|
|
}
|
|
}
|
|
|
|
await db.collection('users').doc(userId).update(filteredData);
|
|
} else {
|
|
delete data.uid;
|
|
await db.collection('users').doc(userId).update(data);
|
|
}
|
|
|
|
res.status(200).json({ message: 'User updated successfully' });
|
|
} catch (error) {
|
|
logger.error("Error updating user:", error);
|
|
res.status(500).json({ error: error.message });
|
|
}
|
|
});
|
|
|
|
// ============================================================================
|
|
// EQUIPMENT STATUS - Batch Update
|
|
// ============================================================================
|
|
|
|
// Mettre à jour le statut de plusieurs équipements (pour préparation/retour)
|
|
exports.updateEquipmentStatus = onRequest({ cors: true }, async (req, res) => {
|
|
try {
|
|
const decodedToken = await auth.authenticateUser(req);
|
|
const { eventId, updates } = req.body.data;
|
|
|
|
if (!eventId || !updates || !Array.isArray(updates)) {
|
|
res.status(400).json({ error: 'Event ID and updates array are required' });
|
|
return;
|
|
}
|
|
|
|
// Vérifier que l'utilisateur est assigné à l'événement ou est admin
|
|
const isAssigned = await auth.isAssignedToEvent(decodedToken.uid, eventId);
|
|
const isAdminUser = await auth.isAdmin(decodedToken.uid);
|
|
|
|
if (!isAssigned && !isAdminUser) {
|
|
res.status(403).json({ error: 'Forbidden: Not assigned to this event' });
|
|
return;
|
|
}
|
|
|
|
// Batch update
|
|
const batch = db.batch();
|
|
|
|
for (const update of updates) {
|
|
const { equipmentId, status } = update;
|
|
if (equipmentId && status) {
|
|
const equipmentRef = db.collection('equipments').doc(equipmentId);
|
|
batch.update(equipmentRef, { status });
|
|
}
|
|
}
|
|
|
|
await batch.commit();
|
|
|
|
res.status(200).json({ message: 'Equipment statuses updated successfully' });
|
|
} catch (error) {
|
|
logger.error("Error updating equipment statuses:", error);
|
|
res.status(500).json({ error: error.message });
|
|
}
|
|
});
|