diff --git a/apiv2/Dockerfile b/apiv2/Dockerfile index 8311de9f17..4240cf482b 100644 --- a/apiv2/Dockerfile +++ b/apiv2/Dockerfile @@ -10,7 +10,7 @@ RUN apk update WORKDIR /app RUN npm install --global turbo@^2.0.0 COPY . . -RUN turbo prune $APP_NAME --docker +RUN turbo prune $APP_NAME --docker && cp -fr patches out/full/ # Add lockfile and package.json's of isolated subworkspace FROM base AS installer @@ -22,6 +22,7 @@ WORKDIR /app COPY .gitignore .gitignore COPY --from=builder /app/out/json/ . COPY --from=builder /app/out/full/packages/ ./packages/ +COPY --from=builder /app/out/full/patches/ ./patches/ RUN npm install # Build the project and its dependencies diff --git a/apiv2/package.json b/apiv2/package.json index 3ef2216475..f336e8b72c 100644 --- a/apiv2/package.json +++ b/apiv2/package.json @@ -45,7 +45,7 @@ "class-validator": "^0.14.1", "date-fns": "^3.6.0", "mongoose": "^8.9.1", - "mongoose-patch-history": "^2.0.0", + "mongoose-patch-history": "^2.0", "nestjs-cls": "^4.4.1", "nodemailer": "^6.9.16", "reflect-metadata": "^0.2.0", diff --git a/apiv2/src/admin/AdminJob.module.ts b/apiv2/src/admin/AdminJob.module.ts index 142ae3e6db..4df4ffe99e 100644 --- a/apiv2/src/admin/AdminJob.module.ts +++ b/apiv2/src/admin/AdminJob.module.ts @@ -28,6 +28,7 @@ import { planDeTransportMongoProviders } from "./infra/sejours/phase1/planDeTran import { DATABASE_CONNECTION } from "@infra/Database.provider"; import { ClsPluginTransactional } from "@nestjs-cls/transactional"; import { TransactionalAdapterMongoose } from "@infra/TransactionalAdatpterMongoose"; +import { historyProvider } from "./infra/history/historyProvider"; @Module({ imports: [ @@ -37,10 +38,6 @@ import { TransactionalAdapterMongoose } from "@infra/TransactionalAdatpterMongoo imports: [DatabaseModule], adapter: new TransactionalAdapterMongoose({ mongooseConnectionToken: DATABASE_CONNECTION, - defaultTxOptions: { - // https://www.mongodb.com/docs/manual/core/transactions-sharded-clusters/#time-limit - maxTimeMS: 1000 * 60 * 15, // 10 minutes - }, }), }), ], @@ -63,6 +60,7 @@ import { TransactionalAdapterMongoose } from "@infra/TransactionalAdatpterMongoo ...taskMongoProviders, ...phase1GatewayProviders, ...jeuneGatewayProviders, + ...historyProvider, { provide: FileGateway, useClass: FileProvider }, { provide: TaskGateway, useClass: AdminTaskRepository }, // add use case here diff --git a/apiv2/src/admin/core/history/History.gateway.ts b/apiv2/src/admin/core/history/History.gateway.ts index c5fc870155..28c50a637f 100644 --- a/apiv2/src/admin/core/history/History.gateway.ts +++ b/apiv2/src/admin/core/history/History.gateway.ts @@ -3,6 +3,7 @@ import { PatchType } from "snu-lib"; export interface HistoryGateway { findByReferenceId(history: HistoryType, referenceId: string): Promise; + bulkCreate(history: HistoryType, patches: PatchType[]): Promise; } export const HistoryGateway = Symbol("HistoryGateway"); diff --git a/apiv2/src/admin/core/sejours/jeune/Jeune.gateway.ts b/apiv2/src/admin/core/sejours/jeune/Jeune.gateway.ts index 2b61b96f52..86be1d962b 100644 --- a/apiv2/src/admin/core/sejours/jeune/Jeune.gateway.ts +++ b/apiv2/src/admin/core/sejours/jeune/Jeune.gateway.ts @@ -12,8 +12,9 @@ export interface JeuneGateway { ): Promise; findBySessionId(sessionId: string): Promise; update(jeune: JeuneModel, updateOriginName?: string): Promise; + bulkUpdate(jeunes: { original: JeuneModel; updated: JeuneModel }[], updateOriginName?: string): Promise; create(jeune: CreateJeuneModel): Promise; - countAffectedByLigneDeBus(ligneDeBusId: string): Promise; + countAffectedByLigneDeBus(ligneDeBusId: string); } export const JeuneGateway = Symbol("JeuneGateway"); diff --git a/apiv2/src/admin/core/sejours/jeune/Jeune.model.ts b/apiv2/src/admin/core/sejours/jeune/Jeune.model.ts index c06f08ed28..b0b443d3db 100644 --- a/apiv2/src/admin/core/sejours/jeune/Jeune.model.ts +++ b/apiv2/src/admin/core/sejours/jeune/Jeune.model.ts @@ -46,52 +46,7 @@ export type JeuneModel = { parent2Nom?: string; parent2Email?: string; parent2Telephone?: string; - // mandatory - cniFiles: any; - highSkilledActivityProofFiles: any; - parentConsentmentFiles: any; - autoTestPCRFiles: any; - imageRightFiles: any; - dataProcessingConsentmentFiles: any; - rulesFiles: any; - militaryPreparationFilesIdentity: any; - militaryPreparationFilesCensus: any; - militaryPreparationFilesAuthorization: any; - militaryPreparationFilesCertificate: any; - domains: any; - periodRanking: any; - mobilityTransport: any; - notes: any; - statusPhase2Contract: any; - historic: any; - phase2ApplicationStatus: any; - phase2ApplicationFilesType: any; - missionsInMail: any; - loginAttempts: any; - token2FA: any; - attempts2FA: any; - lastLoginAt: any; - forgotPasswordResetToken: any; - acceptCGU: any; - invitationToken: any; - phase: any; - statusPhase2: any; - statusPhase3: any; - lastStatusAt: any; - hasStartedReinscription: any; - cohesion2020Step: any; - tokenEmailValidation: any; - attemptsEmailValidation: any; - cohesionStayMedicalFileDownload: any; - convocationFileDownload: any; - source: any; - phase3Token: any; - parent1FromFranceConnect: any; - parent2FromFranceConnect: any; - imageRightFilesStatus: any; - autoTestPCRFilesStatus: any; - youngPhase1Agreement: any; - hasNotes: any; + youngPhase1Agreement: string; }; export type CreateJeuneModel = Omit; diff --git a/apiv2/src/admin/core/sejours/phase1/affectation/ValiderAffectationHTS.spec.ts b/apiv2/src/admin/core/sejours/phase1/affectation/ValiderAffectationHTS.spec.ts index fcdb94a947..7c66f55cb6 100644 --- a/apiv2/src/admin/core/sejours/phase1/affectation/ValiderAffectationHTS.spec.ts +++ b/apiv2/src/admin/core/sejours/phase1/affectation/ValiderAffectationHTS.spec.ts @@ -28,7 +28,7 @@ jest.mock("@nestjs-cls/transactional", () => ({ Transactional: () => jest.fn(), })); -describe("SimulationAffectationHTS", () => { +describe("ValiderAffectationHTS", () => { let validerAffectationHTS: ValiderAffectationHTS; beforeEach(async () => { @@ -66,14 +66,7 @@ describe("SimulationAffectationHTS", () => { sessionNom: "Avril 2024 - C", }, ]), - update: jest.fn().mockResolvedValue({ - id: "jeune1", - sessionNom: "Avril 2024 - C", - ligneDeBusId: "65f9c8bb735e0e12a4213c18", - sejourId: "6597e6acb86afb08146e8f86", - pointDeRassemblementId: "6398797d3bc18708cc3981f6", - hasPDR: "true", - }), + bulkUpdate: jest.fn().mockResolvedValue(1), countAffectedByLigneDeBus: jest.fn().mockResolvedValue(1), }, }, @@ -146,8 +139,6 @@ describe("SimulationAffectationHTS", () => { dateAffectation: new Date(), }); - console.log(result); - expect(result.analytics.errors).toEqual(0); expect(result.analytics.jeunesAffected).toEqual(1); expect(result.rapportData[0]).toEqual({ @@ -176,8 +167,8 @@ describe("SimulationAffectationHTS", () => { region: undefined, sessionId: "6597e6acb86afb08146e8f86", sessionNom: "Avril 2024 - C", - statut: undefined, - statutPhase1: undefined, + statut: "VALIDATED", + statutPhase1: "AFFECTED", telephone: undefined, }); }); diff --git a/apiv2/src/admin/core/sejours/phase1/affectation/ValiderAffectationHTS.ts b/apiv2/src/admin/core/sejours/phase1/affectation/ValiderAffectationHTS.ts index bf56dce966..603f996780 100644 --- a/apiv2/src/admin/core/sejours/phase1/affectation/ValiderAffectationHTS.ts +++ b/apiv2/src/admin/core/sejours/phase1/affectation/ValiderAffectationHTS.ts @@ -108,6 +108,7 @@ export class ValiderAffectationHTS implements UseCase> = []; // Traitement des jeunes @@ -167,18 +168,19 @@ export class ValiderAffectationHTS implements UseCase { + describe("toUpdateHistory", () => { + it("should return a PatchType object with user firstname", () => { + const original = { _id: "id", statusPhase1: "WAITING_AFFECTATION", otherField: 2 }; + const updated = { _id: "id", statusPhase1: "AFFECTED", otherField: 2 }; + const options = JEUNE_PATCHHISTORY_OPTIONS; + const user = { firstName: "firstName" }; + + const result = HistoryMapper.toUpdateHistory(original, updated, options, user); + + expect(result).toEqual({ + modelName: "young", + ops: [ + { + op: "replace", + originalValue: "WAITING_AFFECTATION", + path: "/statusPhase1", + value: "AFFECTED", + }, + ], + ref: "id", + user: { + firstName: "firstName", + }, + }); + }); + }); +}); diff --git a/apiv2/src/admin/infra/history/repository/HistoryMapper.ts b/apiv2/src/admin/infra/history/repository/HistoryMapper.ts new file mode 100644 index 0000000000..b60e226a0e --- /dev/null +++ b/apiv2/src/admin/infra/history/repository/HistoryMapper.ts @@ -0,0 +1,19 @@ +import { createPatch } from "mongoose-patch-history"; +import { PatchType } from "snu-lib"; + +export class HistoryMapper { + static toUpdateHistory(original: object & { _id: string }, updated: object, options: any, user: object): PatchType { + const doc = { + data: () => updated, + _original: original, + _user: user, + _id: original._id, + modelName: options.includes?.modelName?.default, + patches: { + create: (patch) => patch, + }, + }; + const history = createPatch(doc, options); + return history; + } +} diff --git a/apiv2/src/admin/infra/history/repository/mongo/HistoryMongo.provider.ts b/apiv2/src/admin/infra/history/repository/mongo/HistoryMongo.provider.ts index 38aff6a140..ef1041f840 100644 --- a/apiv2/src/admin/infra/history/repository/mongo/HistoryMongo.provider.ts +++ b/apiv2/src/admin/infra/history/repository/mongo/HistoryMongo.provider.ts @@ -1,6 +1,6 @@ import { DATABASE_CONNECTION } from "@infra/Database.provider"; import mongoose, { Connection, HydratedDocument } from "mongoose"; -import { PatchSchema, PatchType } from "snu-lib"; +import { PatchOperationSchema, PatchSchema, PatchType, PatchUserSchema } from "snu-lib"; import { ReferentName } from "../../../iam/provider/ReferentMongo.provider"; import { JeuneName } from "../../../sejours/jeune/provider/JeuneMongo.provider"; import { SessionName } from "../../../sejours/phase1/session/provider/SessionMongo.provider"; @@ -11,7 +11,17 @@ export type HistoryDocument = HydratedDocument; const PatchName = "patche"; const PATCH_MONGOOSE_ENTITY = "PATCH_MONGOOSE_ENTITY"; -const PatchSchemaRef = new mongoose.Schema(PatchSchema); +const PatchSchemaRef = new mongoose.Schema({ + ...PatchSchema, + user: { + ...PatchSchema.user, + type: new mongoose.Schema(PatchUserSchema), + }, + ops: { + ...PatchSchema.ops, + type: [new mongoose.Schema(PatchOperationSchema)], + }, +}); const collectionWithPatches = [JeuneName, ReferentName, ClasseName, SessionName]; diff --git a/apiv2/src/admin/infra/history/repository/mongo/HistoryMongo.repository.ts b/apiv2/src/admin/infra/history/repository/mongo/HistoryMongo.repository.ts index 560a0b3561..3dd9da05ad 100644 --- a/apiv2/src/admin/infra/history/repository/mongo/HistoryMongo.repository.ts +++ b/apiv2/src/admin/infra/history/repository/mongo/HistoryMongo.repository.ts @@ -31,4 +31,17 @@ export class HistoryRepository implements HistoryGateway { const instance = this.getInstance(history); return instance.find({ ref: referenceId }); } + + async bulkCreate(history: HistoryType, patches: PatchType[]): Promise { + const instance = this.getInstance(history); + + const updatePatches = await instance.bulkWrite( + patches.map((patch) => ({ + insertOne: { + document: patch, + }, + })), + ); + return updatePatches.insertedCount; + } } diff --git a/apiv2/src/admin/infra/sejours/jeune/provider/JeuneMongo.provider.ts b/apiv2/src/admin/infra/sejours/jeune/provider/JeuneMongo.provider.ts index 908033807c..1e82024c30 100644 --- a/apiv2/src/admin/infra/sejours/jeune/provider/JeuneMongo.provider.ts +++ b/apiv2/src/admin/infra/sejours/jeune/provider/JeuneMongo.provider.ts @@ -6,7 +6,7 @@ import { YoungSchema, YoungSchemaCorrectionRequest, YoungSchemaFile, YoungSchema import { DATABASE_CONNECTION } from "@infra/Database.provider"; export type JeuneDocument = HydratedDocument; -export const JeuneName = "youngs"; +export const JeuneName = "young"; export const JEUNE_MONGOOSE_ENTITY = "JEUNE_MONGOOSE_ENTITY"; const JeuneSchemaRef = new mongoose.Schema({ @@ -35,8 +35,7 @@ JeuneSchemaRef.pre("save", function (next, params) { next(); }); -JeuneSchemaRef.plugin(patchHistory, { - mongoose, +export const JEUNE_PATCHHISTORY_OPTIONS = { name: `${JeuneName}Patches`, trackOriginalValue: true, includes: { @@ -44,7 +43,9 @@ JeuneSchemaRef.plugin(patchHistory, { user: { type: Object, required: false, from: "_user" }, }, excludes: ["/updatedAt"], -}); +}; + +JeuneSchemaRef.plugin(patchHistory, { mongoose, ...JEUNE_PATCHHISTORY_OPTIONS }); export const jeuneMongoProviders = [ { diff --git a/apiv2/src/admin/infra/sejours/jeune/repository/Jeune.mapper.ts b/apiv2/src/admin/infra/sejours/jeune/repository/Jeune.mapper.ts index 66c82fd4ee..63da514f25 100644 --- a/apiv2/src/admin/infra/sejours/jeune/repository/Jeune.mapper.ts +++ b/apiv2/src/admin/infra/sejours/jeune/repository/Jeune.mapper.ts @@ -47,56 +47,62 @@ export class JeuneMapper { parent2Nom: jeuneDocument.parent2LastName, parent2Email: jeuneDocument.parent2Email, parent2Telephone: jeuneDocument.parent2Phone, - // mandatory - cniFiles: jeuneDocument.cniFiles, - highSkilledActivityProofFiles: jeuneDocument.highSkilledActivityProofFiles, - parentConsentmentFiles: jeuneDocument.parentConsentmentFiles, - autoTestPCRFiles: jeuneDocument.autoTestPCRFiles, - imageRightFiles: jeuneDocument.imageRightFiles, - dataProcessingConsentmentFiles: jeuneDocument.dataProcessingConsentmentFiles, - rulesFiles: jeuneDocument.rulesFiles, - militaryPreparationFilesIdentity: jeuneDocument.militaryPreparationFilesIdentity, - militaryPreparationFilesCensus: jeuneDocument.militaryPreparationFilesCensus, - militaryPreparationFilesAuthorization: jeuneDocument.militaryPreparationFilesAuthorization, - militaryPreparationFilesCertificate: jeuneDocument.militaryPreparationFilesCertificate, - domains: jeuneDocument.domains, - periodRanking: jeuneDocument.periodRanking, - mobilityTransport: jeuneDocument.mobilityTransport, - notes: jeuneDocument.notes, - statusPhase2Contract: jeuneDocument.statusPhase2Contract, - historic: jeuneDocument.historic, - phase2ApplicationStatus: jeuneDocument.phase2ApplicationStatus, - phase2ApplicationFilesType: jeuneDocument.phase2ApplicationFilesType, - missionsInMail: jeuneDocument.missionsInMail, - loginAttempts: jeuneDocument.loginAttempts, - token2FA: jeuneDocument.token2FA, - attempts2FA: jeuneDocument.attempts2FA, - lastLoginAt: jeuneDocument.lastLoginAt, - forgotPasswordResetToken: jeuneDocument.forgotPasswordResetToken, - acceptCGU: jeuneDocument.acceptCGU, - invitationToken: jeuneDocument.invitationToken, - phase: jeuneDocument.phase, - statusPhase2: jeuneDocument.statusPhase2, - statusPhase3: jeuneDocument.statusPhase3, - lastStatusAt: jeuneDocument.lastStatusAt, - hasStartedReinscription: jeuneDocument.hasStartedReinscription, - cohesion2020Step: jeuneDocument.cohesion2020Step, - tokenEmailValidation: jeuneDocument.tokenEmailValidation, - attemptsEmailValidation: jeuneDocument.attemptsEmailValidation, - cohesionStayMedicalFileDownload: jeuneDocument.cohesionStayMedicalFileDownload, - convocationFileDownload: jeuneDocument.convocationFileDownload, - source: jeuneDocument.source, - phase3Token: jeuneDocument.phase3Token, - parent1FromFranceConnect: jeuneDocument.parent1FromFranceConnect, - parent2FromFranceConnect: jeuneDocument.parent2FromFranceConnect, - imageRightFilesStatus: jeuneDocument.imageRightFilesStatus, - autoTestPCRFilesStatus: jeuneDocument.autoTestPCRFilesStatus, youngPhase1Agreement: jeuneDocument.youngPhase1Agreement, - hasNotes: jeuneDocument.hasNotes, }; } - static toEntity(jeuneModel: JeuneModel): Omit { + static toEntity( + jeuneModel: JeuneModel, + ): Omit< + YoungType, + | "metadata" + | "createdAt" + | "updatedAt" + | "cniFiles" + | "highSkilledActivityProofFiles" + | "parentConsentmentFiles" + | "autoTestPCRFiles" + | "imageRightFiles" + | "dataProcessingConsentmentFiles" + | "rulesFiles" + | "militaryPreparationFilesIdentity" + | "militaryPreparationFilesCensus" + | "militaryPreparationFilesAuthorization" + | "militaryPreparationFilesCertificate" + | "domains" + | "periodRanking" + | "mobilityTransport" + | "notes" + | "statusPhase2Contract" + | "historic" + | "phase2ApplicationStatus" + | "phase2ApplicationFilesType" + | "missionsInMail" + | "loginAttempts" + | "token2FA" + | "attempts2FA" + | "lastLoginAt" + | "forgotPasswordResetToken" + | "acceptCGU" + | "invitationToken" + | "phase" + | "statusPhase2" + | "statusPhase3" + | "lastStatusAt" + | "hasStartedReinscription" + | "cohesion2020Step" + | "tokenEmailValidation" + | "attemptsEmailValidation" + | "cohesionStayMedicalFileDownload" + | "convocationFileDownload" + | "source" + | "phase3Token" + | "parent1FromFranceConnect" + | "parent2FromFranceConnect" + | "imageRightFilesStatus" + | "autoTestPCRFilesStatus" + | "hasNotes" + > { return { _id: jeuneModel.id, status: jeuneModel.statut, @@ -135,52 +141,7 @@ export class JeuneMapper { parent2LastName: jeuneModel.parent2Nom, parent2Email: jeuneModel.parent2Email, parent2Phone: jeuneModel.parent2Telephone, - // mandatory - cniFiles: jeuneModel.cniFiles, - highSkilledActivityProofFiles: jeuneModel.highSkilledActivityProofFiles, - parentConsentmentFiles: jeuneModel.parentConsentmentFiles, - autoTestPCRFiles: jeuneModel.autoTestPCRFiles, - imageRightFiles: jeuneModel.imageRightFiles, - dataProcessingConsentmentFiles: jeuneModel.dataProcessingConsentmentFiles, - rulesFiles: jeuneModel.rulesFiles, - militaryPreparationFilesIdentity: jeuneModel.militaryPreparationFilesIdentity, - militaryPreparationFilesCensus: jeuneModel.militaryPreparationFilesCensus, - militaryPreparationFilesAuthorization: jeuneModel.militaryPreparationFilesAuthorization, - militaryPreparationFilesCertificate: jeuneModel.militaryPreparationFilesCertificate, - domains: jeuneModel.domains, - periodRanking: jeuneModel.periodRanking, - mobilityTransport: jeuneModel.mobilityTransport, - notes: jeuneModel.notes, - statusPhase2Contract: jeuneModel.statusPhase2Contract, - historic: jeuneModel.historic, - phase2ApplicationStatus: jeuneModel.phase2ApplicationStatus, - phase2ApplicationFilesType: jeuneModel.phase2ApplicationFilesType, - missionsInMail: jeuneModel.missionsInMail, - loginAttempts: jeuneModel.loginAttempts, - token2FA: jeuneModel.token2FA, - attempts2FA: jeuneModel.attempts2FA, - lastLoginAt: jeuneModel.lastLoginAt, - forgotPasswordResetToken: jeuneModel.forgotPasswordResetToken, - acceptCGU: jeuneModel.acceptCGU, - invitationToken: jeuneModel.invitationToken, - phase: jeuneModel.phase, - statusPhase2: jeuneModel.statusPhase2, - statusPhase3: jeuneModel.statusPhase3, - lastStatusAt: jeuneModel.lastStatusAt, - hasStartedReinscription: jeuneModel.hasStartedReinscription, - cohesion2020Step: jeuneModel.cohesion2020Step, - tokenEmailValidation: jeuneModel.tokenEmailValidation, - attemptsEmailValidation: jeuneModel.attemptsEmailValidation, - cohesionStayMedicalFileDownload: jeuneModel.cohesionStayMedicalFileDownload, - convocationFileDownload: jeuneModel.convocationFileDownload, - source: jeuneModel.source, - phase3Token: jeuneModel.phase3Token, - parent1FromFranceConnect: jeuneModel.parent1FromFranceConnect, - parent2FromFranceConnect: jeuneModel.parent2FromFranceConnect, - imageRightFilesStatus: jeuneModel.imageRightFilesStatus, - autoTestPCRFilesStatus: jeuneModel.autoTestPCRFilesStatus, youngPhase1Agreement: jeuneModel.youngPhase1Agreement, - hasNotes: jeuneModel.hasNotes, }; } } diff --git a/apiv2/src/admin/infra/sejours/jeune/repository/mongo/JeuneMongo.repository.ts b/apiv2/src/admin/infra/sejours/jeune/repository/mongo/JeuneMongo.repository.ts index 3ca3ff7d64..2fb95f686b 100644 --- a/apiv2/src/admin/infra/sejours/jeune/repository/mongo/JeuneMongo.repository.ts +++ b/apiv2/src/admin/infra/sejours/jeune/repository/mongo/JeuneMongo.repository.ts @@ -4,15 +4,18 @@ import { Model } from "mongoose"; import { ClsService } from "nestjs-cls"; import { JeuneGateway } from "../../../../../core/sejours/jeune/Jeune.gateway"; import { JeuneModel } from "../../../../../core/sejours/jeune/Jeune.model"; -import { JEUNE_MONGOOSE_ENTITY, JeuneDocument } from "../../provider/JeuneMongo.provider"; +import { JEUNE_MONGOOSE_ENTITY, JEUNE_PATCHHISTORY_OPTIONS, JeuneDocument } from "../../provider/JeuneMongo.provider"; import { JeuneMapper } from "../Jeune.mapper"; import { YOUNG_STATUS, YOUNG_STATUS_PHASE1 } from "snu-lib"; +import { HistoryType } from "@admin/core/history/History"; +import { HistoryMapper } from "@admin/infra/history/repository/HistoryMapper"; +import { HistoryGateway } from "@admin/core/history/History.gateway"; @Injectable() export class JeuneRepository implements JeuneGateway { constructor( @Inject(JEUNE_MONGOOSE_ENTITY) private jeuneMongooseEntity: Model, - + @Inject(HistoryGateway) private historyGateway: HistoryGateway, private readonly cls: ClsService, ) {} @@ -64,6 +67,37 @@ export class JeuneRepository implements JeuneGateway { return JeuneMapper.toModel(retrievedJeune); } + async bulkUpdate( + jeunes: { original: JeuneModel; updated: JeuneModel }[], + updateOriginName?: string, + ): Promise { + const jeunesEntity = jeunes.map((jeune) => ({ + original: JeuneMapper.toEntity(jeune.original), + updated: JeuneMapper.toEntity(jeune.updated), + })); + + const user = updateOriginName ? { firstName: updateOriginName } : this.cls.get("user"); + + const updateJeunes = await this.jeuneMongooseEntity.bulkWrite( + jeunesEntity.map((jeune) => ({ + updateOne: { + filter: { _id: jeune.updated._id }, + update: { $set: jeune.updated }, + upsert: false, + }, + })), + ); + + await this.historyGateway.bulkCreate( + HistoryType.JEUNE, + jeunesEntity.map((jeune) => + HistoryMapper.toUpdateHistory(jeune.original, jeune.updated, JEUNE_PATCHHISTORY_OPTIONS, user), + ), + ); + + return updateJeunes.modifiedCount; + } + async findAll(): Promise { const jeunes = await this.jeuneMongooseEntity.find(); return JeuneMapper.toModels(jeunes); diff --git a/apiv2/src/admin/infra/sejours/phase1/centre/provider/CentreMongo.provider.ts b/apiv2/src/admin/infra/sejours/phase1/centre/provider/CentreMongo.provider.ts index 35702cc138..b4c63a280e 100644 --- a/apiv2/src/admin/infra/sejours/phase1/centre/provider/CentreMongo.provider.ts +++ b/apiv2/src/admin/infra/sejours/phase1/centre/provider/CentreMongo.provider.ts @@ -6,7 +6,7 @@ import { CohesionCenterSchema, CohesionCenterType } from "snu-lib"; import { DATABASE_CONNECTION } from "@infra/Database.provider"; export type CentreDocument = HydratedDocument; -export const CentreName = "cohesioncenters"; +export const CentreName = "cohesioncenter"; export const CENTRE_MONGOOSE_ENTITY = "CENTRE_MONGOOSE_ENTITY"; const CentreSchemaRef = new mongoose.Schema(CohesionCenterSchema); diff --git a/apiv2/src/admin/infra/sejours/phase1/session/provider/SessionMongo.provider.ts b/apiv2/src/admin/infra/sejours/phase1/session/provider/SessionMongo.provider.ts index 8edf106306..8850b9a1f2 100644 --- a/apiv2/src/admin/infra/sejours/phase1/session/provider/SessionMongo.provider.ts +++ b/apiv2/src/admin/infra/sejours/phase1/session/provider/SessionMongo.provider.ts @@ -12,7 +12,7 @@ import { import { DATABASE_CONNECTION } from "@infra/Database.provider"; export type SessionDocument = HydratedDocument; -export const SessionName = "cohorts"; +export const SessionName = "cohort"; export const SESSION_MONGOOSE_ENTITY = "SESSION_MONGOOSE_ENTITY"; const SessionSchemaRef = new mongoose.Schema({ diff --git a/apiv2/test/admin/setUpAdminTest.ts b/apiv2/test/admin/setUpAdminTest.ts index ee878ea57a..2f7e23cd5d 100644 --- a/apiv2/test/admin/setUpAdminTest.ts +++ b/apiv2/test/admin/setUpAdminTest.ts @@ -45,6 +45,7 @@ import { serviceProvider } from "@admin/infra/iam/service/serviceProvider"; import { AffectationService } from "@admin/core/sejours/phase1/affectation/Affectation.service"; import { planDeTransportMongoProviders } from "@admin/infra/sejours/phase1/planDeTransport/provider/PlanDeTransportMongo.provider"; import { DATABASE_CONNECTION } from "@infra/Database.provider"; +import { historyProvider } from "@admin/infra/history/historyProvider"; export interface SetupOptions { newContainer: boolean; @@ -89,6 +90,7 @@ export const setupAdminTest = async (setupOptions: SetupOptions = { newContainer ...sejourMongoProviders, ...sessionMongoProviders, ...taskMongoProviders, + ...historyProvider, testDatabaseProviders(setupOptions.newContainer), Logger, ...guardProviders, diff --git a/package-lock.json b/package-lock.json index 5e9d10ca1c..589fdaf173 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "service-national-universel", - "lockfileVersion": 3, + "lockfileVersion": 4, "requires": true, "packages": { "": { @@ -1107,7 +1107,7 @@ "class-validator": "^0.14.1", "date-fns": "^3.6.0", "mongoose": "^8.9.1", - "mongoose-patch-history": "^2.0.0", + "mongoose-patch-history": "^2.0", "nestjs-cls": "^4.4.1", "nodemailer": "^6.9.16", "reflect-metadata": "^0.2.0", diff --git a/packages/lib/src/mongoSchema/patch.ts b/packages/lib/src/mongoSchema/patch.ts index 4a11948fd7..13661d758b 100644 --- a/packages/lib/src/mongoSchema/patch.ts +++ b/packages/lib/src/mongoSchema/patch.ts @@ -1,27 +1,36 @@ import mongoose, { InferSchemaType } from "mongoose"; -const userSchema = new mongoose.Schema({ - _id: mongoose.Schema.Types.ObjectId, - email: { type: String, required: true }, +export const PatchUserSchema = { + email: { type: String }, firstName: { type: String, required: true }, - lastName: { type: String, required: true }, -}); + lastName: { type: String }, +}; -const operationSchema = new mongoose.Schema({ +export const PatchOperationSchema = { op: { type: String, required: true }, path: { type: String, required: true }, - value: { type: String, required: true }, + value: { type: String }, originalValue: { type: String }, -}); +}; -export const PatchSchema = new mongoose.Schema({ - _id: mongoose.Schema.Types.ObjectId, - ops: { type: [operationSchema], required: true }, - ref: { type: mongoose.Schema.Types.ObjectId, required: true }, +export const PatchSchema = { + ops: { type: [PatchOperationSchema], required: true }, + ref: { type: mongoose.Schema.Types.ObjectId, required: true, index: true }, modelName: { type: String, required: true }, - user: { type: userSchema }, - date: { type: Date, required: true }, - __v: { type: Number, default: 0 }, + user: { type: PatchUserSchema }, + date: { type: Date, required: true, default: Date.now }, +}; + +const schema = new mongoose.Schema({ + ...PatchSchema, + user: { + ...PatchSchema.user, + type: new mongoose.Schema(PatchUserSchema), + }, + ops: { + ...PatchSchema.ops, + type: [new mongoose.Schema(PatchOperationSchema)], + }, }); -export type PatchType = InferSchemaType; +export type PatchType = InferSchemaType; diff --git a/patches/mongoose-patch-history+2.0.0.patch b/patches/mongoose-patch-history+2.0.0.patch index e0bed7aed2..92457bc8ad 100644 --- a/patches/mongoose-patch-history+2.0.0.patch +++ b/patches/mongoose-patch-history+2.0.0.patch @@ -1,5 +1,5 @@ diff --git a/node_modules/mongoose-patch-history/lib/index.js b/node_modules/mongoose-patch-history/lib/index.js -index 8587330..3bab347 100644 +index 8587330..fb60e6d 100644 --- a/node_modules/mongoose-patch-history/lib/index.js +++ b/node_modules/mongoose-patch-history/lib/index.js @@ -99,13 +99,7 @@ exports.default = function (schema, opts) { @@ -17,3 +17,12 @@ index 8587330..3bab347 100644 } schema.pre('remove', function (next) { +@@ -160,6 +154,8 @@ exports.default = function (schema, opts) { + return document.patches.create(data); + } + ++ exports.createPatch = createPatch; ++ + schema.pre('save', function (next) { + createPatch(this).then(function () { + return next();