feat: implement equipment and container loading rollback functionality with corresponding backend cloud functions

This commit is contained in:
ElPoyo
2026-05-27 22:04:46 +02:00
parent 64a9fe382a
commit faff06e4df
15 changed files with 660 additions and 514 deletions
+20 -51
View File
@@ -1,60 +1,22 @@
const {onRequest} = require("firebase-functions/v2/https");
const {onCall} = require("firebase-functions/v2/https");
const admin = require("firebase-admin");
const nodemailer = require("nodemailer");
const logger = require("firebase-functions/logger");
const {getSmtpConfig, EMAIL_CONFIG} = require("./utils/emailConfig");
const {renderTemplate, getEmailSubject, getAlertTitle, prepareTemplateData, checkAlertPreference} = require("./utils/emailTemplates");
const auth = require("./utils/auth");
// Configuration CORS
const setCorsHeaders = (res, req) => {
// Utiliser l'origin de la requête pour permettre les credentials
const origin = req.headers.origin || "*";
res.set("Access-Control-Allow-Origin", origin);
// N'autoriser les credentials que si on a un origin spécifique (pas '*')
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);
// Gérer les requêtes preflight OPTIONS immédiatement
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});
}
}
};
};
/**
* Crée une alerte et envoie les notifications
* Gère tout le processus côté backend de A à Z
*/
exports.createAlert = onRequest({
cors: false,
invoker: "public",
region: "europe-west9",
}, withCors(async (req, res) => {
const handler = async (request) => {
try {
const {auth, data} = request;
// Vérifier l'authentification
const decodedToken = await auth.authenticateUser(req);
const data = req.body.data || req.body;
if (!auth) {
throw new Error("L'utilisateur doit être authentifié");
}
const {
@@ -96,7 +58,7 @@ exports.createAlert = onRequest({
metadata: metadata || {},
assignedTo: userIds,
createdAt: admin.firestore.FieldValue.serverTimestamp(),
createdBy: decodedToken.uid,
createdBy: auth.uid,
isRead: false,
emailSent: false,
status: "ACTIVE",
@@ -117,17 +79,17 @@ exports.createAlert = onRequest({
});
}
res.status(200).json({
return {
success: true,
alertId: alertRef.id,
usersNotified: userIds.length,
emailsSent: Object.values(emailResults).filter((v) => v).length,
});
};
} catch (error) {
logger.error("[createAlert] Erreur:", error);
res.status(500).json({error: `Erreur lors de la création de l'alerte: ${error.message}`});
throw error;
}
}));
};
/**
* Détermine les utilisateurs à notifier selon le type d'alerte
@@ -189,7 +151,7 @@ async function determineTargetUsers(alertType, severity, eventId) {
*/
async function sendAlertEmails(alertId, alertData, userIds) {
const results = {};
const transporter = nodemailer.createTransporter(getSmtpConfig());
const transporter = nodemailer.createTransport(getSmtpConfig());
// Envoyer les emails en parallèle (batch de 5)
const batches = [];
@@ -269,3 +231,10 @@ async function sendSingleEmail(transporter, alertId, alertData, userId) {
}
}
exports.createAlert = onCall({
cors: true,
region: "europe-west9",
}, handler);
exports.handler = handler;