betagouv/service-national-universel

View on GitHub
api/src/models/young.ts

Summary

Maintainability
A
0 mins
Test Coverage
import mongoose, { Schema } from "mongoose";
import bcrypt from "bcryptjs";
import mongooseElastic from "@selego/mongoose-elastic";
import patchHistory from "mongoose-patch-history";
import { YOUNG_SOURCE, YOUNG_STATUS, YoungSchema, YoungSchemaCorrectionRequest, YoungSchemaFile, YoungSchemaNote, YoungType } from "snu-lib";
import esClient from "../es";
import * as brevo from "../brevo";
import config from "config";
import anonymize from "../anonymization/young";
import { DocumentExtended, CustomSaveParams, UserExtension, UserSaved } from "./types";

const MODELNAME = "young";

const ClasseStateManager = require("../cle/classe/stateManager").default;

const schema = new Schema({
  ...YoungSchema,
  files: {
    ...Object.keys(YoungSchema.files).reduce((acc, key) => {
      acc[key] = [new Schema(YoungSchemaFile)];
      return acc;
    }, {}),
  },
  correctionRequests: {
    ...YoungSchema.correctionRequests,
    type: [new Schema(YoungSchemaCorrectionRequest)],
  },
  notes: {
    ...YoungSchema.notes,
    type: [new Schema(YoungSchemaNote)],
  },
});

schema.virtual("fromUser").set<SchemaExtended>(function (fromUser: UserSaved) {
  if (fromUser) {
    const { _id, role, department, region, email, firstName, lastName, model } = fromUser;
    this._user = { _id, role, department, region, email, firstName, lastName, model };
  }
});

schema.pre<SchemaExtended>("save", function (next) {
  if (this.isModified("password") || this.isNew) {
    bcrypt.hash(this.password, 10, (e, hash) => {
      this.password = hash;
      return next();
    });
  } else {
    return next();
  }
});

schema.methods.comparePassword = async function (p) {
  const user = await YoungModel.findById(this._id).select("password");
  return bcrypt.compare(p, user?.password || "");
};

schema.methods.anonymise = function () {
  return anonymize(this);
};

//Sync with brevo
schema.post<SchemaExtended>("save", async function (doc) {
  //TODO ajouter la transaction
  if (doc.source === YOUNG_SOURCE.CLE && doc.status === YOUNG_STATUS.VALIDATED) {
    await ClasseStateManager.compute(doc.classeId, doc._user, { YoungModel });
  }

  if (config.ENVIRONMENT === "test") return;
  brevo.sync(doc, MODELNAME);
});
schema.post("findOneAndUpdate", function (doc) {
  brevo.sync(doc, MODELNAME);
});
schema.post("remove", function (doc) {
  brevo.unsync(doc);
});

schema.pre<SchemaExtended>("save", function (next, params: CustomSaveParams) {
  this.fromUser = params?.fromUser;
  this.updatedAt = new Date();
  this.previousStatus = this.status; // Used to compute classe if a young CLE has a change in status (see post save hook)
  next();
});

schema.plugin(patchHistory, {
  mongoose,
  name: `${MODELNAME}Patches`,
  trackOriginalValue: true,
  includes: {
    modelName: { type: String, required: true, default: MODELNAME },
    user: { type: Object, required: false, from: "_user" },
  },
  excludes: [
    "/password",
    "/lastLoginAt",
    "/lastActivityAt",
    "/lastLogoutAt",
    "/passwordChangedAt",
    "/nextLoginAttemptIn",
    "/forgotPasswordResetToken",
    "/forgotPasswordResetExpires",
    "/invitationToken",
    "/invitationExpires",
    "/phase3Token",
    "/loginAttempts",
    "/updatedAt",
    "/statusPhase2UpdatedAt",
    "/statusPhase3UpdatedAt",
    "/statusPhase2ValidatedAt",
    "/statusPhase3ValidatedAt",
    "/userIps",
    "/token2FA",
    "/token2FAExpires",
  ],
});

schema.plugin(
  mongooseElastic(esClient, {
    selectiveIndexing: true,
    ignore: [
      "historic",
      "missionsInMail",
      "password",
      "lastLogoutAt",
      "passwordChangedAt",
      "nextLoginAttemptIn",
      "forgotPasswordResetToken",
      "forgotPasswordResetExpires",
      "invitationExpires",
      "phase3Token",
      "loginAttempts",
      "parent1Inscription2023Token",
      "parent2Inscription2023Token",
      "updatedAt",
      "lastActivityAt",
      "userIps",
      "token2FA",
      "token2FAExpires",
    ],
  }),
  MODELNAME,
);

schema.index({ ligneId: 1 });
schema.index({ sessionPhase1Id: 1 });
schema.index({ sessionPhase1Id: 1, status: 1 });
schema.index({ classeId: -1 });

export type YoungDocument<T = {}> = DocumentExtended<YoungType & T>;
type SchemaExtended = YoungDocument & UserExtension & { previousStatus?: YoungType["status"] };

export const YoungModel = mongoose.model<YoungDocument>(MODELNAME, schema);