Files
EM2_ERP/em2rp/functions/index.js
T

518 lines
18 KiB
JavaScript

/**
* EM2RP Cloud Functions
* Architecture backend sécurisée et modulaire avec dynamic imports (lazy loading)
*/
const path = require("path");
require("dotenv").config({path: path.join(__dirname, ".env.local")});
require("dotenv").config({path: path.join(__dirname, ".env")});
const {onRequest, onCall} = require("firebase-functions/v2/https");
const {onSchedule} = require("firebase-functions/v2/scheduler");
const {onDocumentCreated, onDocumentUpdated} = require("firebase-functions/v2/firestore");
const logger = require("firebase-functions/logger");
const admin = require("firebase-admin");
// Initialisation sécurisée de Firebase Admin au démarrage
if (!admin.apps.length) {
admin.initializeApp();
}
const db = admin.firestore();
// Configuration commune pour toutes les fonctions HTTP
const httpOptions = {
cors: false,
invoker: "public",
region: "europe-west9",
};
// Options dédiées pour les traitements IA potentiellement longs.
const aiHttpOptions = {
...httpOptions,
timeoutSeconds: 300,
memory: "1GiB",
};
// Options HTTP spécifiques pour TTS avec CORS activé
const ttsHttpOptions = {
cors: true,
invoker: "public",
region: "europe-west9",
};
// CORS Middleware
const setCorsHeaders = (res, req) => {
const origin = req.headers.origin || "*";
res.set("Access-Control-Allow-Origin", origin);
if (origin !== "*") {
res.set("Access-Control-Allow-Credentials", "true");
}
res.set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
res.set("Access-Control-Allow-Headers", "Authorization, Content-Type, Accept, Origin, X-Requested-With");
res.set("Access-Control-Max-Age", "3600");
};
const withCors = (handler) => {
return async (req, res) => {
setCorsHeaders(res, req);
if (req.method === "OPTIONS") {
res.status(204).send("");
return;
}
try {
await handler(req, res);
} catch (error) {
logger.error("Unhandled error:", error);
if (!res.headersSent) {
res.status(500).json({error: error.message});
}
}
};
};
// ============================================================================
// STORAGE
// ============================================================================
exports.moveEventFileV2 = onRequest(httpOptions, withCors((req, res) => {
return require("./src/storage").moveEventFileV2(req, res);
}));
// ============================================================================
// EQUIPMENT
// ============================================================================
exports.createEquipment = onRequest(httpOptions, withCors((req, res) => {
return require("./src/equipments").createEquipment(req, res);
}));
exports.updateEquipment = onRequest(httpOptions, withCors((req, res) => {
return require("./src/equipments").updateEquipment(req, res);
}));
exports.deleteEquipment = onRequest(httpOptions, withCors((req, res) => {
return require("./src/equipments").deleteEquipment(req, res);
}));
exports.getEquipment = onRequest(httpOptions, withCors((req, res) => {
return require("./src/equipments").getEquipment(req, res);
}));
exports.getEquipmentsByIds = onRequest(httpOptions, withCors((req, res) => {
return require("./src/equipments").getEquipmentsByIds(req, res);
}));
exports.updateEquipmentStatusOnly = onRequest(httpOptions, withCors((req, res) => {
return require("./src/equipments").updateEquipmentStatusOnly(req, res);
}));
exports.updateEquipmentStatus = onRequest(httpOptions, withCors((req, res) => {
return require("./src/equipments").updateEquipmentStatus(req, res);
}));
exports.getEquipmentsPaginated = onRequest(httpOptions, withCors((req, res) => {
return require("./src/equipments").getEquipmentsPaginated(req, res);
}));
exports.quickSearch = onRequest(httpOptions, withCors((req, res) => {
return require("./src/equipments").quickSearch(req, res);
}));
// ============================================================================
// CONTAINERS
// ============================================================================
exports.createContainer = onRequest(httpOptions, withCors((req, res) => {
return require("./src/containers").createContainer(req, res);
}));
exports.updateContainer = onRequest(httpOptions, withCors((req, res) => {
return require("./src/containers").updateContainer(req, res);
}));
exports.deleteContainer = onRequest(httpOptions, withCors((req, res) => {
return require("./src/containers").deleteContainer(req, res);
}));
exports.getContainersByEquipment = onRequest(httpOptions, withCors((req, res) => {
return require("./src/containers").getContainersByEquipment(req, res);
}));
exports.getContainersByIds = onRequest(httpOptions, withCors((req, res) => {
return require("./src/containers").getContainersByIds(req, res);
}));
exports.addEquipmentToContainer = onRequest(httpOptions, withCors((req, res) => {
return require("./src/containers").addEquipmentToContainer(req, res);
}));
exports.removeEquipmentFromContainer = onRequest(httpOptions, withCors((req, res) => {
return require("./src/containers").removeEquipmentFromContainer(req, res);
}));
exports.getContainersPaginated = onRequest(httpOptions, withCors((req, res) => {
return require("./src/containers").getContainersPaginated(req, res);
}));
// ============================================================================
// EVENTS
// ============================================================================
exports.createEvent = onRequest(httpOptions, withCors((req, res) => {
return require("./src/events").createEvent(req, res);
}));
exports.updateEvent = onRequest(httpOptions, withCors((req, res) => {
return require("./src/events").updateEvent(req, res);
}));
exports.deleteEvent = onRequest(httpOptions, withCors((req, res) => {
return require("./src/events").deleteEvent(req, res);
}));
exports.updateEventEquipment = onRequest(httpOptions, withCors((req, res) => {
return require("./src/events").updateEventEquipment(req, res);
}));
exports.getEventsByEventType = onRequest(httpOptions, withCors((req, res) => {
return require("./src/events").getEventsByEventType(req, res);
}));
exports.getEvents = onRequest(httpOptions, withCors((req, res) => {
return require("./src/events").getEvents(req, res);
}));
exports.getEventsByMonth = onRequest(httpOptions, withCors((req, res) => {
return require("./src/events").getEventsByMonth(req, res);
}));
exports.searchEvents = onRequest(httpOptions, withCors((req, res) => {
return require("./src/events").searchEvents(req, res);
}));
exports.getEventWithDetails = onRequest(httpOptions, withCors((req, res) => {
return require("./src/events").getEventWithDetails(req, res);
}));
exports.validateEquipmentPreparation = onRequest(httpOptions, withCors((req, res) => {
return require("./src/events").validateEquipmentPreparation(req, res);
}));
exports.validateAllPreparation = onRequest(httpOptions, withCors((req, res) => {
return require("./src/events").validateAllPreparation(req, res);
}));
exports.validateEquipmentLoading = onRequest(httpOptions, withCors((req, res) => {
return require("./src/events").validateEquipmentLoading(req, res);
}));
exports.validateAllLoading = onRequest(httpOptions, withCors((req, res) => {
return require("./src/events").validateAllLoading(req, res);
}));
exports.validateEquipmentUnloading = onRequest(httpOptions, withCors((req, res) => {
return require("./src/events").validateEquipmentUnloading(req, res);
}));
exports.validateAllUnloading = onRequest(httpOptions, withCors((req, res) => {
return require("./src/events").validateAllUnloading(req, res);
}));
exports.validateEquipmentReturn = onRequest(httpOptions, withCors((req, res) => {
return require("./src/events").validateEquipmentReturn(req, res);
}));
exports.validateAllReturn = onRequest(httpOptions, withCors((req, res) => {
return require("./src/events").validateAllReturn(req, res);
}));
// ============================================================================
// MAINTENANCES
// ============================================================================
exports.createMaintenance = onRequest(httpOptions, withCors((req, res) => {
return require("./src/maintenances").createMaintenance(req, res);
}));
exports.updateMaintenance = onRequest(httpOptions, withCors((req, res) => {
return require("./src/maintenances").updateMaintenance(req, res);
}));
exports.getMaintenances = onRequest(httpOptions, withCors((req, res) => {
return require("./src/maintenances").getMaintenances(req, res);
}));
exports.deleteMaintenance = onRequest(httpOptions, withCors((req, res) => {
return require("./src/maintenances").deleteMaintenance(req, res);
}));
exports.checkUpcomingMaintenances = onRequest(httpOptions, withCors((req, res) => {
return require("./src/maintenances").checkUpcomingMaintenances(req, res);
}));
exports.completeMaintenance = onRequest(httpOptions, withCors((req, res) => {
return require("./src/maintenances").completeMaintenance(req, res);
}));
// ============================================================================
// OPTIONS & EVENT TYPES
// ============================================================================
exports.createOption = onRequest(httpOptions, withCors((req, res) => {
return require("./src/options").createOption(req, res);
}));
exports.updateOption = onRequest(httpOptions, withCors((req, res) => {
return require("./src/options").updateOption(req, res);
}));
exports.deleteOption = onRequest(httpOptions, withCors((req, res) => {
return require("./src/options").deleteOption(req, res);
}));
exports.getOptions = onRequest(httpOptions, withCors((req, res) => {
return require("./src/options").getOptions(req, res);
}));
exports.getEventTypes = onRequest(httpOptions, withCors((req, res) => {
return require("./src/options").getEventTypes(req, res);
}));
exports.createEventType = onRequest(httpOptions, withCors((req, res) => {
return require("./src/options").createEventType(req, res);
}));
exports.updateEventType = onRequest(httpOptions, withCors((req, res) => {
return require("./src/options").updateEventType(req, res);
}));
exports.deleteEventType = onRequest(httpOptions, withCors((req, res) => {
return require("./src/options").deleteEventType(req, res);
}));
// ============================================================================
// USERS
// ============================================================================
exports.createUser = onRequest(httpOptions, withCors((req, res) => {
return require("./src/users").createUser(req, res);
}));
exports.createUserWithInvite = onRequest(httpOptions, withCors((req, res) => {
return require("./src/users").createUserWithInvite(req, res);
}));
exports.updateUser = onRequest(httpOptions, withCors((req, res) => {
return require("./src/users").updateUser(req, res);
}));
exports.deleteUser = onRequest(httpOptions, withCors((req, res) => {
return require("./src/users").deleteUser(req, res);
}));
exports.getUsers = onRequest(httpOptions, withCors((req, res) => {
return require("./src/users").getUsers(req, res);
}));
exports.getUser = onRequest(httpOptions, withCors((req, res) => {
return require("./src/users").getUser(req, res);
}));
exports.getCurrentUser = onRequest(httpOptions, withCors((req, res) => {
return require("./src/users").getCurrentUser(req, res);
}));
exports.getRoles = onRequest(httpOptions, withCors((req, res) => {
return require("./src/users").getRoles(req, res);
}));
// ============================================================================
// ALERTS
// ============================================================================
exports.getAlerts = onRequest(httpOptions, withCors((req, res) => {
return require("./src/alerts").getAlerts(req, res);
}));
exports.markAlertAsRead = onRequest(httpOptions, withCors((req, res) => {
return require("./src/alerts").markAlertAsRead(req, res);
}));
exports.deleteAlert = onRequest(httpOptions, withCors((req, res) => {
return require("./src/alerts").deleteAlert(req, res);
}));
// ============================================================================
// AVAILABILITY
// ============================================================================
exports.checkEquipmentAvailability = onRequest(httpOptions, withCors((req, res) => {
return require("./src/availability").checkEquipmentAvailability(req, res);
}));
exports.checkContainerAvailability = onRequest(httpOptions, withCors((req, res) => {
return require("./src/availability").checkContainerAvailability(req, res);
}));
exports.getConflictingEquipmentIds = onRequest(httpOptions, withCors((req, res) => {
return require("./src/availability").getConflictingEquipmentIds(req, res);
}));
exports.findAlternativeEquipment = onRequest(httpOptions, withCors((req, res) => {
return require("./src/availability").findAlternativeEquipment(req, res);
}));
exports.calculateEquipmentStatuses = onRequest(httpOptions, withCors((req, res) => {
return require("./src/availability").calculateEquipmentStatuses(req, res);
}));
exports.getActiveEvents = onRequest(httpOptions, withCors((req, res) => {
return require("./src/availability").getActiveEvents(req, res);
}));
// ============================================================================
// TEXT-TO-SPEECH
// ============================================================================
exports.generateTTSV2 = onRequest(ttsHttpOptions, (req, res) => {
return require("./src/tts").generateTTSV2(req, res);
});
// ============================================================================
// AI
// ============================================================================
exports.aiEquipmentProposal = onRequest(aiHttpOptions, withCors((req, res) => {
return require("./aiEquipmentProposal").handleAiEquipmentProposal(req, res);
}));
// ============================================================================
// CALLABLE EMAIL & VALIDATION (LEGACY LAZY WRAPPERS)
// ============================================================================
exports.sendAlertEmail = onCall({region: "europe-west9", cors: true}, (request) => {
return require("./sendAlertEmail").sendAlertEmail(request);
});
exports.createAlert = onCall({region: "europe-west9", cors: true}, (request) => {
return require("./createAlert").createAlert(request);
});
exports.processEquipmentValidation = onCall({region: "europe-west9", cors: true}, (request) => {
return require("./processEquipmentValidation").processEquipmentValidation(request);
});
// ============================================================================
// SCHEDULED FUNCTIONS
// ============================================================================
exports.sendDailyDigest = onSchedule({
schedule: "0 8 * * *",
timeZone: "Europe/Paris",
region: "europe-west9",
retryCount: 2,
memory: "512MiB",
}, async (context) => {
logger.info("[Scheduler] Démarrage sendDailyDigest");
try {
const {sendDailyDigest} = require("./sendDailyDigest");
await sendDailyDigest();
logger.info("[Scheduler] sendDailyDigest terminé avec succès");
} catch (error) {
logger.error("[Scheduler] Erreur sendDailyDigest:", error);
throw error;
}
});
// ============================================================================
// FIRESTORE TRIGGERS
// ============================================================================
exports.onEventCreated = onDocumentCreated({
document: "events/{eventId}",
region: "europe-west9",
}, async (event) => {
logger.info(`[onEventCreated] Événement créé: ${event.params.eventId}`);
try {
const eventData = event.data.data();
const eventId = event.params.eventId;
await db.collection("alerts").add({
type: "EVENT_CREATED",
severity: "INFO",
message: `Nouvel événement créé : "${eventData.name}" le ${new Date(eventData.startDate?.toDate ? eventData.startDate.toDate() : eventData.startDate).toLocaleDateString("fr-FR")}`,
eventId: eventId,
eventName: eventData.name,
eventDate: eventData.startDate,
createdAt: admin.firestore.FieldValue.serverTimestamp(),
isRead: false,
metadata: {
eventId: eventId,
eventName: eventData.name,
eventDate: eventData.startDate,
},
assignedTo: [],
});
logger.info(`[onEventCreated] Alerte créée pour événement ${eventId}`);
} catch (error) {
logger.error("[onEventCreated] Erreur:", error);
}
});
exports.onEventUpdated = onDocumentUpdated({
document: "events/{eventId}",
region: "europe-west9",
}, async (event) => {
const before = event.data.before.data();
const after = event.data.after.data();
const eventId = event.params.eventId;
try {
const workforceBefore = before.workforce || [];
const workforceAfter = after.workforce || [];
const newMembers = workforceAfter.filter((afterMember) => {
return !workforceBefore.some((beforeMember) =>
beforeMember.userId === afterMember.userId,
);
});
if (newMembers.length > 0) {
logger.info(`[onEventUpdated] ${newMembers.length} nouveaux membres ajoutés à ${eventId}`);
for (const member of newMembers) {
await db.collection("alerts").add({
type: "WORKFORCE_ADDED",
severity: "INFO",
message: `Vous avez été ajouté(e) à l'événement "${after.name}" le ${new Date(after.startDate?.toDate ? after.startDate.toDate() : after.startDate).toLocaleDateString("fr-FR")}`,
eventId: eventId,
eventName: after.name,
eventDate: after.startDate,
createdAt: admin.firestore.FieldValue.serverTimestamp(),
isRead: false,
metadata: {
eventId: eventId,
eventName: after.name,
eventDate: after.startDate,
},
assignedTo: [member.userId],
});
logger.info(`[onEventUpdated] Alerte créée pour ${member.userId}`);
}
}
} catch (error) {
logger.error("[onEventUpdated] Erreur:", error);
}
});
exports.onAlertCreated = onDocumentCreated({
document: "alerts/{alertId}",
region: "europe-west9",
}, async (event) => {
const alertId = event.params.alertId;
const alertData = event.data.data();
logger.info(`[onAlertCreated] Nouvelle alerte: ${alertId} (${alertData.severity})`);
try {
if (alertData.severity === "CRITICAL" && !alertData.emailSent) {
const userIds = alertData.assignedTo || [];
if (userIds.length > 0) {
logger.info(`[onAlertCreated] Envoi email immédiat à ${userIds.length} utilisateurs`);
await db.collection("alerts").doc(alertId).update({
pendingEmailSend: true,
});
logger.info(`[onAlertCreated] Alerte marquée pour envoi email`);
}
}
} catch (error) {
logger.error("[onAlertCreated] Erreur:", error);
}
});