betagouv/service-national-universel

View on GitHub
api/src/controllers/application.js

Summary

Maintainability
D
2 days
Test Coverage
const express = require("express");
const router = express.Router();
const passport = require("passport");
const Joi = require("joi");
const { ObjectId } = require("mongoose").Types;
const fs = require("fs");
const fileUpload = require("express-fileupload");
const mime = require("mime-types");

const { capture, captureMessage } = require("../sentry");
const { YoungModel, CohortModel, ReferentModel, ApplicationModel, ContractModel, MissionModel, StructureModel } = require("../models");
const { decrypt, encrypt } = require("../cryptoUtils");

const { sendTemplate } = require("../brevo");
const { validateUpdateApplication, validateNewApplication, validateId } = require("../utils/validator");
const config = require("config");
const {
  ROLES,
  SENDINBLUE_TEMPLATES,
  canCreateYoungApplication,
  canViewYoungApplications,
  canApplyToPhase2,
  canViewContract,
  translateAddFilePhase2,
  translateAddFilesPhase2,
  APPLICATION_STATUS,
} = require("snu-lib");
const { serializeApplication, serializeYoung, serializeContract } = require("../utils/serializer");
const {
  uploadFile,
  ERRORS,
  isYoung,
  isReferent,
  getCcOfYoung,
  updateYoungPhase2Hours,
  updateStatusPhase2,
  getFile,
  updateYoungStatusPhase2Contract,
  getReferentManagerPhase2,
  updateYoungApplicationFilesType,
} = require("../utils");
const patches = require("./patches");
const scanFile = require("../utils/virusScanner");
const { getAuthorizationToApply } = require("../services/application");
const { apiEngagement } = require("../services/gouv.fr/api-engagement");
const { getMimeFromBuffer, getMimeFromFile } = require("../utils/file");

const canUpdateApplication = async (user, application, young, structures) => {
  // - admin can update all applications
  // - referent can update applications of their department/region
  // - responsible and supervisor can update applications of their structures
  if (user.role === ROLES.ADMIN) return true;
  if (isYoung(user) && application.youngId.toString() !== user._id.toString()) return false;
  if (isReferent(user)) {
    if (!canCreateYoungApplication(user, young)) return false;
    if (user.role === ROLES.RESPONSIBLE && (!user.structureId || application.structureId.toString() !== user.structureId.toString())) return false;
    if (user.role === ROLES.SUPERVISOR) {
      if (!user.structureId) return false;
      if (!structures.map((e) => e._id.toString()).includes(application.structureId.toString())) return false;
    }
  }
  return true;
};

async function updateMission(app, fromUser) {
  try {
    const mission = await MissionModel.findById(app.missionId);

    // Get all applications for the mission
    const placesTaken = await ApplicationModel.countDocuments({ missionId: mission._id, status: { $in: ["VALIDATED", "IN_PROGRESS", "DONE"] } });
    const placesLeft = Math.max(0, mission.placesTotal - placesTaken);
    if (mission.placesLeft !== placesLeft) {
      mission.set({ placesLeft });
    }

    if (placesLeft === 0) {
      mission.set({ placesStatus: "FULL" });
    } else if (placesLeft === mission.placesTotal) {
      mission.set({ placesStatus: "EMPTY" });
    } else {
      mission.set({ placesStatus: "ONE_OR_MORE" });
    }

    // On met à jour le nb de candidatures en attente.
    const pendingApplications = await ApplicationModel.countDocuments({
      missionId: mission._id,
      status: { $in: ["WAITING_VERIFICATION", "WAITING_VALIDATION"] },
    });

    if (mission.pendingApplications !== pendingApplications) {
      mission.set({ pendingApplications });
    }

    const allApplications = await ApplicationModel.find({ missionId: mission._id });
    mission.set({ applicationStatus: allApplications.map((e) => e.status) });

    await mission.save({ fromUser });
  } catch (e) {
    capture(e);
  }
}

router.post("/:id/change-classement/:rank", passport.authenticate(["young"], { session: false, failWithError: true }), async (req, res) => {
  try {
    const JoiId = validateId(req.params.id);
    if (JoiId.error) return res.status(400).send({ ok: false, code: ERRORS.INVALID_PARAMS });
    const JoiRank = Joi.string().required().validate(req.params.rank);
    if (JoiRank.error) return res.status(400).send({ ok: false, code: ERRORS.INVALID_PARAMS });

    const application = await ApplicationModel.findById(JoiId.value);
    if (!application) return res.status(404).send({ ok: false, code: ERRORS.NOT_FOUND });

    const young = await YoungModel.findById(application.youngId);
    if (!young) return res.status(404).send({ ok: false, code: ERRORS.NOT_FOUND });

    // A young can only update his own application.
    if (isYoung(req.user) && application.youngId.toString() !== req.user._id.toString()) {
      return res.status(403).send({ ok: false, code: ERRORS.OPERATION_UNAUTHORIZED });
    }

    const allApplications = await ApplicationModel.find({ youngId: young._id.toString() });
    const allApplicationsSorted = allApplications.sort((a, b) => Number(a.priority) - Number(b.priority));
    const currentIndex = allApplicationsSorted.findIndex((app) => app._id.toString() === application._id.toString());

    // on l'enlève de sa position initiale
    allApplicationsSorted.splice(currentIndex, 1);
    // et on l'insère au nouveau rang
    allApplicationsSorted.splice(JoiRank.value, 0, application);

    for (const i in allApplicationsSorted) {
      const applicationTemp = allApplicationsSorted[i];
      applicationTemp.set({ priority: Number(i) + 1 });
      await applicationTemp.save({ fromUser: req.user });
    }
    return res.status(200).send({ ok: true, data: allApplicationsSorted.map(serializeApplication) });
  } catch (error) {
    capture(error);
    res.status(500).send({ ok: false, code: ERRORS.SERVER_ERROR });
  }
});

router.post("/", passport.authenticate(["young", "referent"], { session: false, failWithError: true }), async (req, res) => {
  try {
    const { value, error } = validateNewApplication(req.body, req.user);
    if (error) {
      capture(error);
      return res.status(400).send({ ok: false, code: ERRORS.INVALID_PARAMS });
    }

    const { clickId } = Joi.object({ clickId: Joi.string().optional() }).validate(req.query, { stripUnknown: true }).value;

    if (!("priority" in value)) {
      const applications = await ApplicationModel.find({ youngId: value.youngId });
      value.priority = applications.length + 1;
    }
    const mission = await MissionModel.findById(value.missionId);
    if (!mission) return res.status(404).send({ ok: false, code: ERRORS.NOT_FOUND });

    // On vérifie si les candidatures sont ouvertes.
    if (mission.visibility === "HIDDEN") {
      return res.status(403).send({ ok: false, code: ERRORS.OPERATION_NOT_ALLOWED });
    }

    value.isJvaMission = mission.isJvaMission;

    const young = await YoungModel.findById(value.youngId);
    if (!young) return res.status(404).send({ ok: false, code: ERRORS.NOT_FOUND });

    if (isYoung(req.user)) {
      const { canApply, message } = await getAuthorizationToApply(mission, young);
      if (!canApply) {
        return res.status(403).send({ ok: false, code: ERRORS.OPERATION_UNAUTHORIZED, message });
      }
    }

    // A young can only create their own applications.
    if (isYoung(req.user) && young._id.toString() !== req.user._id.toString()) {
      return res.status(403).send({ ok: false, code: ERRORS.OPERATION_NOT_ALLOWED });
    }
    // - admin can create all applications
    // - referent can create applications of their department/region
    // - responsible and supervisor can create applications of their structures
    if (isReferent(req.user)) {
      if (!canCreateYoungApplication(req.user, young)) {
        return res.status(403).send({ ok: false, code: ERRORS.OPERATION_NOT_ALLOWED });
      }
      if (req.user.role === ROLES.RESPONSIBLE) {
        if (!req.user.structureId) return res.status(404).send({ ok: false, code: ERRORS.NOT_FOUND });
        if (value.structureId.toString() !== req.user.structureId.toString()) {
          return res.status(403).send({ ok: false, code: ERRORS.OPERATION_NOT_ALLOWED });
        }
      }
      if (req.user.role === ROLES.SUPERVISOR) {
        if (!req.user.structureId) return res.status(404).send({ ok: false, code: ERRORS.NOT_FOUND });
        const structures = await StructureModel.find({ $or: [{ networkId: String(req.user.structureId) }, { _id: String(req.user.structureId) }] });
        if (!structures.map((e) => e._id.toString()).includes(value.structureId.toString())) {
          return res.status(403).send({ ok: false, code: ERRORS.OPERATION_NOT_ALLOWED });
        }
      }
      const cohort = await CohortModel.findById(young.cohortId);
      if (!canApplyToPhase2(young, cohort)) {
        return res.status(403).send({ ok: false, code: ERRORS.OPERATION_UNAUTHORIZED });
      }
    }

    // On vérifie que la candidature n'existe pas déjà en base de donnée.
    const doublon = await ApplicationModel.findOne({ youngId: value.youngId, missionId: value.missionId });
    if (doublon) {
      return res.status(403).send({ ok: false, code: ERRORS.OPERATION_NOT_ALLOWED });
    }

    // Send tracking data to API Engagement
    if (mission.apiEngagementId) {
      const data = await apiEngagement.create(value, mission.apiEngagementId, clickId);
      value.apiEngagementId = data?._id;
    }

    value.contractStatus = "DRAFT";
    const data = await ApplicationModel.create(value);
    await updateYoungPhase2Hours(young, req.user);
    await updateStatusPhase2(young, req.user);
    await updateMission(data, req.user);
    await updateYoungStatusPhase2Contract(young, req.user);

    return res.status(200).send({ ok: true, data: serializeApplication(data) });
  } catch (error) {
    capture(error);
    res.status(500).send({ ok: false, code: ERRORS.SERVER_ERROR });
  }
});
router.post("/multiaction/change-status/:key", passport.authenticate("referent", { session: false, failWithError: true }), async (req, res) => {
  try {
    const allowedKeys = ["WAITING_VALIDATION", "WAITING_ACCEPTATION", "VALIDATED", "REFUSED", "CANCEL", "IN_PROGRESS", "DONE", "ABANDON", "WAITING_VERIFICATION"];
    const { error, value } = Joi.object({
      ids: Joi.array().items(Joi.string().required()).required(),
    })
      .unknown()
      .validate(req.body, { stripUnknown: true });
    if (error) {
      capture(error);
      return res.status(400).send({ ok: false, code: ERRORS.INVALID_BODY });
    }

    const { errorKey, value: valueKey } = Joi.object({
      key: Joi.string()
        .trim()
        .required()
        .valid(...allowedKeys),
    })
      .unknown()
      .validate(req.params, { stripUnknown: true });
    if (errorKey) {
      capture(errorKey);
      return res.status(400).send({ ok: false, code: ERRORS.INVALID_PARAMS });
    }

    // Transform ids to ObjectId
    value.ids = value.ids.map((id) => ObjectId(id));

    const pipeline = [
      { $match: { _id: { $in: value.ids } } },
      {
        $addFields: {
          youngObjectId: {
            $toObjectId: "$youngId",
          },
        },
      },
      {
        $lookup: {
          from: "youngs",
          localField: "youngObjectId",
          foreignField: "_id",
          as: "young",
        },
      },
      { $unwind: "$young" },
    ];

    const applications = await ApplicationModel.aggregate(pipeline).exec();
    if (!applications || applications?.length !== value.ids?.length) return res.status(404).send({ ok: false, code: ERRORS.NOT_FOUND });
    //check toutes les perms pour chaque application

    // if supervisor store structures --> avoid multiple mongoDb calls
    let structures = null;
    if (req.user.role === ROLES.SUPERVISOR) {
      if (!req.user.structureId) return res.status(404).send({ ok: false, code: ERRORS.NOT_FOUND });
      structures = await StructureModel.find({ $or: [{ networkId: String(req.user.structureId) }, { _id: String(req.user.structureId) }] });
    }

    for (const application of applications) {
      const young = application.young;
      if (!young) return res.status(404).send({ ok: false, code: ERRORS.NOT_FOUND });

      // A young can only update his own application.
      if (!canUpdateApplication(req.user, application, young, structures)) {
        return res.status(403).send({ ok: false, code: ERRORS.OPERATION_NOT_ALLOWED });
      }
    }

    value.ids.map(async (id) => {
      const application = await ApplicationModel.findById(id);
      const young = await YoungModel.findById(application.youngId);

      application.set({ status: valueKey.key });
      await application.save({ fromUser: req.user });

      if (application.apiEngagementId) {
        await apiEngagement.update(application);
      }

      await updateYoungPhase2Hours(young, req.user);
      await updateStatusPhase2(young, req.user);
      await updateYoungStatusPhase2Contract(young, req.user);
      await updateMission(application, req.user);
    });
    res.status(200).send({ ok: true });
  } catch (error) {
    capture(error);
    res.status(500).send({ ok: false, code: ERRORS.SERVER_ERROR });
  }
});
router.put("/", passport.authenticate(["referent", "young"], { session: false, failWithError: true }), async (req, res) => {
  try {
    const { value, error } = validateUpdateApplication(req.body, req.user);
    if (error) {
      capture(error);
      return res.status(400).send({ ok: false, code: ERRORS.INVALID_PARAMS });
    }

    const application = await ApplicationModel.findById(value._id);
    if (!application) return res.status(404).send({ ok: false, code: ERRORS.NOT_FOUND });

    const young = await YoungModel.findById(application.youngId);
    if (!young) return res.status(404).send({ ok: false, code: ERRORS.NOT_FOUND });

    // A young can only update his own application.
    if (isYoung(req.user) && application.youngId.toString() !== req.user._id.toString()) {
      return res.status(403).send({ ok: false, code: ERRORS.OPERATION_UNAUTHORIZED });
    }

    // - admin can update all applications
    // - referent can update applications of their department/region
    // - responsible and supervisor can update applications of their structures
    if (isReferent(req.user)) {
      if (!canCreateYoungApplication(req.user, young)) {
        return res.status(403).send({ ok: false, code: ERRORS.OPERATION_NOT_ALLOWED });
      }
      if (req.user.role === ROLES.RESPONSIBLE) {
        if (!req.user.structureId) return res.status(404).send({ ok: false, code: ERRORS.NOT_FOUND });
        if (application.structureId.toString() !== req.user.structureId.toString()) {
          return res.status(403).send({ ok: false, code: ERRORS.OPERATION_NOT_ALLOWED });
        }
      }
      if (req.user.role === ROLES.SUPERVISOR) {
        if (!req.user.structureId) return res.status(404).send({ ok: false, code: ERRORS.NOT_FOUND });
        const structures = await StructureModel.find({ $or: [{ networkId: String(req.user.structureId) }, { _id: String(req.user.structureId) }] });
        if (!structures.map((e) => e._id.toString()).includes(application.structureId.toString())) {
          return res.status(403).send({ ok: false, code: ERRORS.OPERATION_NOT_ALLOWED });
        }
      }
    }

    const originalStatus = application.status;

    application.set(value);

    if (application.isJvaMission === "true") {
      // When a young accepts a mission proposed by a ref, it counts as an application creation in API Engagement
      if (originalStatus === APPLICATION_STATUS.WAITING_ACCEPTATION && application.status === APPLICATION_STATUS.WAITING_VALIDATION) {
        const mission = await MissionModel.findById(application.missionId);
        const data = await apiEngagement.create(application, mission.apiEngagementId, null);
        application.set({ apiEngagementId: data._id });
      } else {
        await apiEngagement.update(application);
      }
    }

    await application.save({ fromUser: req.user });

    await updateYoungPhase2Hours(young, req.user);
    await updateStatusPhase2(young, req.user);
    await updateYoungStatusPhase2Contract(young, req.user);
    await updateMission(application, req.user);

    res.status(200).send({ ok: true, data: serializeApplication(application) });
  } catch (error) {
    capture(error);
    res.status(500).send({ ok: false, code: ERRORS.SERVER_ERROR });
  }
});

router.put("/:id/visibilite", passport.authenticate(["young"], { session: false, failWithError: true }), async (req, res) => {
  try {
    const joiId = validateId(req.params.id);
    if (joiId.error) return res.status(400).send({ ok: false, code: ERRORS.INVALID_PARAMS });

    // const joiBody = validateUpdateApplication(req.body, req.user);
    const joiBody = Joi.object()
      .keys({ hidden: Joi.string().allow(null, "") })
      .validate(req.body, { stripUnknown: true });
    if (joiBody.error) return res.status(400).send({ ok: false, code: ERRORS.INVALID_PARAMS });

    const application = await ApplicationModel.findById(joiId.value);
    if (!application) return res.status(404).send({ ok: false, code: ERRORS.NOT_FOUND });

    const young = await YoungModel.findById(application.youngId);
    if (!young) return res.status(404).send({ ok: false, code: ERRORS.NOT_FOUND });

    // A young can only update his own application.
    if (isYoung(req.user) && application.youngId.toString() !== req.user._id.toString()) {
      return res.status(403).send({ ok: false, code: ERRORS.OPERATION_UNAUTHORIZED });
    }

    application.set({ hidden: joiBody.value.hidden });
    await application.save({ fromUser: req.user });

    res.status(200).send({ ok: true, data: serializeApplication(application) });
  } catch (error) {
    capture(error);
    res.status(500).send({ ok: false, code: ERRORS.SERVER_ERROR });
  }
});

router.get("/:id/contract", passport.authenticate("referent", { session: false, failWithError: true }), async (req, res) => {
  try {
    const { error, value: id } = validateId(req.params.id);
    if (error) return res.status(400).send({ ok: false, code: ERRORS.INVALID_PARAMS });

    const application = await ApplicationModel.findById(id);
    if (!application) return res.status(404).send({ ok: false, code: ERRORS.NOT_FOUND });

    const contract = await ContractModel.findById(application.contractId);
    if (!contract) return res.status(404).send({ ok: false, code: ERRORS.NOT_FOUND });

    const young = await YoungModel.findById(application.youngId);
    if (!young) return res.status(404).send({ ok: false, code: ERRORS.NOT_FOUND });

    if (isYoung(req.user) && application.youngId.toString() !== req.user._id.toString()) {
      return res.status(403).send({ ok: false, code: ERRORS.OPERATION_NOT_ALLOWED });
    }

    if (isReferent(req.user) && !canViewContract(req.user, contract)) {
      return res.status(403).send({ ok: false, code: ERRORS.OPERATION_NOT_ALLOWED });
    }

    return res.status(200).send({ ok: true, data: serializeContract(contract, req.user) });
  } catch (error) {
    capture(error);
    res.status(500).send({ ok: false, code: ERRORS.SERVER_ERROR });
  }
});

router.get("/:id", passport.authenticate("referent", { session: false, failWithError: true }), async (req, res) => {
  try {
    const { error, value: id } = validateId(req.params.id);
    if (error) {
      capture(error);
      return res.status(400).send({ ok: false, code: ERRORS.INVALID_PARAMS });
    }

    const data = await ApplicationModel.findById(id);
    if (!data) return res.status(404).send({ ok: false, code: ERRORS.NOT_FOUND });

    const young = await YoungModel.findById(data.youngId);
    if (!young) return res.status(404).send({ ok: false, code: ERRORS.NOT_FOUND });

    if (!canViewYoungApplications(req.user, young)) {
      return res.status(403).send({ ok: false, code: ERRORS.OPERATION_NOT_ALLOWED });
    }

    return res.status(200).send({ ok: true, data: serializeApplication(data) });
  } catch (error) {
    capture(error);
    res.status(500).send({ ok: false, code: ERRORS.SERVER_ERROR });
  }
});

router.post("/notify/docs-military-preparation/:template", passport.authenticate("young", { session: false, failWithError: true }), async (req, res) => {
  try {
    const { error, value: template } = Joi.string().required().validate(req.params.template);
    if (error) {
      capture(error);
      return res.status(400).send({ ok: false, code: ERRORS.INVALID_PARAMS });
    }

    const toReferents = await getReferentManagerPhase2(req.user.department);
    if (!toReferents) return res.status(404).send({ ok: false, code: ERRORS.NOT_FOUND });

    if (SENDINBLUE_TEMPLATES.referent.MILITARY_PREPARATION_DOCS_SUBMITTED !== template) {
      return res.status(400).send({ ok: false, code: ERRORS.INVALID_PARAMS });
    }

    const mail = await sendTemplate(parseInt(template), {
      emailTo: toReferents.map((referent) => ({
        name: `${referent.firstName} ${referent.lastName}`,
        email: referent.email,
      })),
      params: { cta: `${config.ADMIN_URL}/volontaire/${req.user._id}/phase2`, youngFirstName: req.user.firstName, youngLastName: req.user.lastName },
    });
    return res.status(200).send({ ok: true, data: mail });
  } catch (error) {
    capture(error);
    res.status(500).send({ ok: false, code: ERRORS.SERVER_ERROR });
  }
});

router.post("/:id/notify/:template", passport.authenticate(["referent", "young"], { session: false, failWithError: true }), async (req, res) => {
  try {
    const { error, value } = Joi.object({
      id: Joi.string().required(),
      template: Joi.string().required(),
      message: Joi.string().optional(),
      type: Joi.string().optional(),
      multipleDocument: Joi.string().optional(),
    })
      .unknown()
      .validate({ ...req.params, ...req.body }, { stripUnknown: true });
    if (error) {
      capture(error);
      return res.status(400).send({ ok: false, code: ERRORS.INVALID_PARAMS });
    }

    const { id, template: defaultTemplate, message, type, multipleDocument } = value;

    const application = await ApplicationModel.findById(id);
    if (!application) return res.status(404).send({ ok: false, code: ERRORS.NOT_FOUND });
    const mission = await MissionModel.findById(application.missionId);
    if (!mission) return res.status(404).send({ ok: false, code: ERRORS.NOT_FOUND });
    const referent = await ReferentModel.findById(mission.tutorId);
    if (!referent) return res.status(404).send({ ok: false, code: ERRORS.NOT_FOUND });
    const young = await YoungModel.findById(application.youngId);
    if (!young) return res.status(404).send({ ok: false, code: ERRORS.NOT_FOUND });

    if (isYoung(req.user) && req.user._id.toString() !== application.youngId) {
      return res.status(403).send({ ok: false, code: ERRORS.OPERATION_UNAUTHORIZED });
    }

    // - admin can notify for all applications
    // - referent can notify for applications of their department/region
    // - responsible and supervisor can notify for applications of their structures
    if (isReferent(req.user)) {
      if (!canCreateYoungApplication(req.user, young)) {
        return res.status(403).send({ ok: false, code: ERRORS.OPERATION_NOT_ALLOWED });
      }
      if (req.user.role === ROLES.RESPONSIBLE) {
        if (!req.user.structureId) return res.status(404).send({ ok: false, code: ERRORS.NOT_FOUND });
        if (application.structureId.toString() !== req.user.structureId.toString()) {
          return res.status(403).send({ ok: false, code: ERRORS.OPERATION_NOT_ALLOWED });
        }
      }
      if (req.user.role === ROLES.SUPERVISOR) {
        if (!req.user.structureId) return res.status(404).send({ ok: false, code: ERRORS.NOT_FOUND });
        const structures = await StructureModel.find({ $or: [{ networkId: String(req.user.structureId) }, { _id: String(req.user.structureId) }] });
        if (!structures.map((e) => e._id.toString()).includes(application.structureId.toString())) {
          return res.status(403).send({ ok: false, code: ERRORS.OPERATION_NOT_ALLOWED });
        }
      }
    }

    let template = defaultTemplate;
    let emailTo;
    // build default values for params
    // => young name, and mission name
    let params = { youngFirstName: application.youngFirstName, youngLastName: application.youngLastName, missionName: mission.name };

    if (template === SENDINBLUE_TEMPLATES.referent.YOUNG_VALIDATED) {
      emailTo = [{ name: `${referent.firstName} ${referent.lastName}`, email: referent.email }];
      params = { ...params, cta: `${config.ADMIN_URL}/volontaire/${application.youngId}/phase2/application/${application._id}/contrat` };
    } else if (template === SENDINBLUE_TEMPLATES.young.VALIDATE_APPLICATION) {
      emailTo = [{ name: `${application.youngFirstName} ${application.youngLastName}`, email: application.youngEmail }];
      params = { ...params, cta: `${config.APP_URL}/candidature?utm_campaign=transactionel+mig+candidature+approuvee&utm_source=notifauto&utm_medium=mail+151+faire` };
    } else if (template === SENDINBLUE_TEMPLATES.referent.VALIDATE_APPLICATION_TUTOR) {
      emailTo = [{ name: `${referent.firstName} ${referent.lastName}`, email: referent.email }];
      params = { ...params, cta: `${config.ADMIN_URL}/volontaire/${application.youngId}` };
    } else if (template === SENDINBLUE_TEMPLATES.referent.CANCEL_APPLICATION) {
      emailTo = [{ name: `${referent.firstName} ${referent.lastName}`, email: referent.email }];
    } else if (template === SENDINBLUE_TEMPLATES.young.CANCEL_APPLICATION) {
      emailTo = [{ name: `${application.youngFirstName} ${application.youngLastName}`, email: application.youngEmail }];
    } else if (template === SENDINBLUE_TEMPLATES.referent.ABANDON_APPLICATION) {
      emailTo = [{ name: `${referent.firstName} ${referent.lastName}`, email: referent.email }];
    } else if (template === SENDINBLUE_TEMPLATES.young.REFUSE_APPLICATION) {
      emailTo = [{ name: `${application.youngFirstName} ${application.youngLastName}`, email: application.youngEmail }];
      params = { ...params, message, cta: `${config.APP_URL}/mission?utm_campaign=transactionnel+mig+candidature+nonretenue&utm_source=notifauto&utm_medium=mail+152+candidater` };
    } else if (template === SENDINBLUE_TEMPLATES.referent.NEW_APPLICATION) {
      // when it is a new application, there are 2 possibilities
      if (mission.isMilitaryPreparation === "true") {
        if (young.statusMilitaryPreparationFiles === "VALIDATED") {
          emailTo = [{ name: `${referent.firstName} ${referent.lastName}`, email: referent.email }];
          template = SENDINBLUE_TEMPLATES.referent.MILITARY_PREPARATION_DOCS_VALIDATED;
          params = { ...params, cta: `${config.ADMIN_URL}/volontaire/${application.youngId}/phase2` };
        } else {
          const referentManagerPhase2 = await getReferentManagerPhase2(application.youngDepartment);
          emailTo = referentManagerPhase2.map((referent) => ({
            name: `${referent.firstName} ${referent.lastName}`,
            email: referent.email,
          }));
          template = SENDINBLUE_TEMPLATES.referent.MILITARY_PREPARATION_DOCS_SUBMITTED;
          params = { ...params, cta: `${config.ADMIN_URL}/volontaire/${application.youngId}/phase2` };
        }
      } else {
        emailTo = [{ name: `${referent.firstName} ${referent.lastName}`, email: referent.email }];
        template = SENDINBLUE_TEMPLATES.referent.NEW_APPLICATION_MIG;
        params = { ...params, cta: `${config.ADMIN_URL}/volontaire${application.youngId}/phase2` };
      }
    } else if (template === SENDINBLUE_TEMPLATES.referent.RELANCE_APPLICATION) {
      // when it is a new application, there are 2 possibilities
      if (mission.isMilitaryPreparation === "true") {
        emailTo = [{ name: `${referent.firstName} ${referent.lastName}`, email: referent.email }];
        template = SENDINBLUE_TEMPLATES.referent.MILITARY_PREPARATION_DOCS_VALIDATED;
        params = { ...params, cta: `${config.ADMIN_URL}/volontaire/${application.youngId}/phase2` };
      } else {
        emailTo = [{ name: `${referent.firstName} ${referent.lastName}`, email: referent.email }];
        template = SENDINBLUE_TEMPLATES.referent.NEW_APPLICATION_MIG;
        params = { ...params, cta: `${config.ADMIN_URL}/volontaire${application.youngId}/phase2` };
      }
    } else if (template === SENDINBLUE_TEMPLATES.ATTACHEMENT_PHASE_2_APPLICATION) {
      // get CC of young
      let cc = [];
      if (young.parent1Email && young.parent1FirstName && young.parent1LastName) cc.push({ name: `${young.parent1FirstName} ${young.parent1LastName}`, email: young.parent1Email });
      if (young.parent2Email && young.parent2FirstName && young.parent2LastName) cc.push({ name: `${young.parent2FirstName} ${young.parent2LastName}`, email: young.parent2Email });
      params = {
        firstName: req.user.firstName,
        lastName: req.user.lastName,
        type_document: `${multipleDocument === "true" ? translateAddFilesPhase2(type) : translateAddFilePhase2(type)}`,
      };
      const sendYoungRPMail = async () => {
        // prevenir jeune / RP
        emailTo = [{ name: `${young.firstName} ${young.lastName}`, email: young.email }];
        params.cta = `${config.APP_URL}/mission/${application.missionId}`;
        const mail = await sendTemplate(template, {
          emailTo,
          params,
          cc,
        });
        return res.status(200).send({ ok: true, data: mail });
      };
      if (isYoung(req.user)) {
        //second email
        const referentManagerPhase2 = await getReferentManagerPhase2(application.youngDepartment);
        emailTo = referentManagerPhase2.map((referent) => ({
          name: `${referent.firstName} ${referent.lastName}`,
          email: referent.email,
        }));
        emailTo.push({ name: `${referent.firstName} ${referent.lastName}`, email: referent.email });

        const mail = await sendTemplate(template, {
          emailTo,
          params,
        });
        return res.status(200).send({ ok: true, data: mail });
      } else {
        // envoyer le mail au jeune / RP
        if (req.user.role === ROLES.REFERENT_DEPARTMENT) {
          // prevenir tuteur mission
          emailTo = [{ name: `${referent.firstName} ${referent.lastName}`, email: referent.email }];
          params.cta = `${config.ADMIN_URL}/volontaire/${application.youngId}/phase2`;

          await sendTemplate(template, {
            emailTo,
            params,
          });

          return sendYoungRPMail();
        } else if (req.user.role === ROLES.RESPONSIBLE) {
          // prevenir referent departement pahse 2
          const referentManagerPhase2 = await getReferentManagerPhase2(application.youngDepartment);
          emailTo = referentManagerPhase2.map((referent) => ({
            name: `${referent.firstName} ${referent.lastName}`,
            email: referent.email,
          }));
          params.cta = `${config.ADMIN_URL}/volontaire/${application.youngId}/phase2`;

          await sendTemplate(template, {
            emailTo,
            params,
          });
          return sendYoungRPMail();
        } else if (req.user.role === ROLES.ADMIN || req.user.role === ROLES.REFERENT_REGION) {
          // prevenir tutor
          emailTo = [{ name: `${referent.firstName} ${referent.lastName}`, email: referent.email }];
          params.cta = `${config.ADMIN_URL}/volontaire/${application.youngId}/phase2`;
          await sendTemplate(template, {
            emailTo,
            params,
          });
          return sendYoungRPMail();
        }
      }
    } else {
      return res.status(404).send({ ok: false, code: ERRORS.NOT_FOUND });
    }
    let cc = isYoung(req.user) ? getCcOfYoung({ template, young }) : [];
    const mail = await sendTemplate(template, {
      emailTo,
      params,
      cc,
    });
    return res.status(200).send({ ok: true, data: mail });
  } catch (error) {
    capture(error);
    res.status(500).send({ ok: false, code: ERRORS.SERVER_ERROR });
  }
});

router.post(
  "/:id/file/:key",
  passport.authenticate(["referent", "young"], { session: false, failWithError: true }),
  fileUpload({ limits: { fileSize: 10 * 1024 * 1024 }, useTempFiles: true, tempFileDir: "/tmp/" }),
  async (req, res) => {
    try {
      const application = await ApplicationModel.findById(req.params.id);
      const rootKeys = ["contractAvenantFiles", "justificatifsFiles", "feedBackExperienceFiles", "othersFiles"];
      const { error: keyError, value: key } = Joi.string()
        .required()
        .valid(...rootKeys)
        .validate(req.params.key, { stripUnknown: true });
      if (keyError) return res.status(400).send({ ok: false, code: ERRORS.INVALID_PARAMS });

      const { error: bodyError, value: body } = Joi.string().required().validate(req.body.body);
      if (bodyError) return res.status(400).send({ ok: false, code: ERRORS.INVALID_PARAMS });

      const {
        error: namesError,
        value: { names },
      } = Joi.object({ names: Joi.array().items(Joi.string()).required() }).validate(JSON.parse(body), { stripUnknown: true });
      if (namesError) return res.status(400).send({ ok: false, code: ERRORS.INVALID_PARAMS });
      const user = await YoungModel.findById(application.youngId);
      if (!user) return res.status(404).send({ ok: false, code: ERRORS.USER_NOT_FOUND });

      // Validate files with Joi
      const { error: filesError, value: files } = Joi.array()
        .items(
          Joi.alternatives().try(
            Joi.object({
              name: Joi.string().required(),
              data: Joi.binary().required(),
              tempFilePath: Joi.string().allow("").optional(),
            }).unknown(),
            Joi.array().items(
              Joi.object({
                name: Joi.string().required(),
                data: Joi.binary().required(),
                tempFilePath: Joi.string().allow("").optional(),
              }).unknown(),
            ),
          ),
        )
        .validate(
          Object.keys(req.files || {}).map((e) => req.files[e]),
          { stripUnknown: true },
        );
      if (filesError) return res.status(400).send({ ok: false, code: ERRORS.INVALID_PARAMS });
      //const application = await ApplicationModel.find({ youngId: req.user._id });
      if (!application) return res.status(404).send({ ok: false, code: ERRORS.APPLICATION_NOT_FOUND });

      for (let i = 0; i < files.length; i++) {
        let currentFile = files[i];
        // If multiple file with same names are provided, currentFile is an array. We just take the latest.
        if (Array.isArray(currentFile)) {
          currentFile = currentFile[currentFile.length - 1];
        }
        const { name, tempFilePath, mimetype } = currentFile;
        const mimeFromMagicNumbers = await getMimeFromFile(tempFilePath);
        const validTypes = ["image/jpeg", "image/png", "application/pdf"];
        if (!(validTypes.includes(mimetype) && validTypes.includes(mimeFromMagicNumbers))) {
          fs.unlinkSync(tempFilePath);
          captureMessage("Wrong filetype", { extra: { tempFilePath, mimetype } });
          return res.status(500).send({ ok: false, code: "UNSUPPORTED_TYPE" });
        }

        const scanResult = await scanFile(tempFilePath, name, user._id);
        if (scanResult.infected) {
          return res.status(403).send({ ok: false, code: ERRORS.FILE_INFECTED });
        }

        const data = fs.readFileSync(tempFilePath);
        const encryptedBuffer = encrypt(data);
        const resultingFile = { mimetype: "image/png", encoding: "7bit", data: encryptedBuffer };
        await uploadFile(`app/young/${user._id}/application/${key}/${name}`, resultingFile);
        //get application et get j eunes
        fs.unlinkSync(tempFilePath);
      }
      application.set({ [key]: names });
      await application.save({ fromUser: req.user });

      await updateYoungApplicationFilesType(application, req.user);

      return res.status(200).send({ young: serializeYoung(user, user), data: names, ok: true });
    } catch (error) {
      capture(error);
      if (error === "FILE_CORRUPTED") return res.status(500).send({ ok: false, code: ERRORS.FILE_CORRUPTED });
      return res.status(500).send({ ok: false, code: ERRORS.SERVER_ERROR });
    }
  },
);

router.get("/:id/file/:key/:name", passport.authenticate(["referent", "young"], { session: false, failWithError: true }), async (req, res) => {
  try {
    const { error, value } = Joi.object({
      id: Joi.string().required(),
      key: Joi.string().required(),
      name: Joi.string().required(),
    })
      .unknown()
      .validate({ ...req.params }, { stripUnknown: true });
    if (error) {
      capture(error);
      return res.status(400).send({ ok: false, code: ERRORS.INVALID_PARAMS });
    }

    const { id, key, name } = value;

    const application = await ApplicationModel.findById(id);
    if (!application) return res.status(404).send({ ok: false, code: ERRORS.NOT_FOUND });
    const young = await YoungModel.findById(application.youngId);
    if (!young) return res.status(404).send({ ok: false, code: ERRORS.NOT_FOUND });

    if (isYoung(req.user) && req.user._id.toString() !== young?._id.toString()) return res.status(403).send({ ok: false, code: ERRORS.OPERATION_UNAUTHORIZED });

    const downloaded = await getFile(`app/young/${young._id}/application/${key}/${name}`);
    const decryptedBuffer = decrypt(downloaded.Body);

    let mimeFromFile = null;
    try {
      mimeFromFile = await getMimeFromBuffer(decryptedBuffer);
    } catch (e) {
      //
    }

    return res.status(200).send({
      data: Buffer.from(decryptedBuffer, "base64"),
      mimeType: mimeFromFile ? mimeFromFile : mime.lookup(name),
      fileName: name,
      ok: true,
    });
  } catch (error) {
    capture(error);
    return res.status(500).send({ ok: false, code: ERRORS.SERVER_ERROR });
  }
});

router.get("/:id/patches", passport.authenticate("referent", { session: false, failWithError: true }), async (req, res) => await patches.get(req, res, ApplicationModel));

module.exports = router;