1- import { HttpException , Injectable } from '@nestjs/common' ;
1+ import { HttpCode , HttpException , HttpStatus , Injectable } from '@nestjs/common' ;
22import { InjectModel } from '@nestjs/mongoose' ;
33import {
44 Document ,
@@ -20,6 +20,8 @@ import { IdentitiesUpsertDto } from './_dto/identities.dto';
2020import { IdentityState } from './_enums/states.enum' ;
2121import { Identities } from './_schemas/identities.schema' ;
2222import { IdentitiesValidationService } from './validations/identities.validation.service' ;
23+ import { createHash } from 'node:crypto' ;
24+ import { cp } from 'node:fs' ;
2325
2426@Injectable ( )
2527export class IdentitiesService extends AbstractServiceSchema {
@@ -46,22 +48,15 @@ export class IdentitiesService extends AbstractServiceSchema {
4648 ) : Promise < ModifyResult < Query < T , T , any , T > > > {
4749 this . logger . log ( `Upserting identity with filters ${ JSON . stringify ( filters ) } ` ) ;
4850
49- // const objectClasses = data.additionalFields.objectClasses;
50- // delete data.additionalFields.objectClasses;
5151 const crushedUpdate = toPlainAndCrush ( omit ( data || { } , [ '$setOnInsert' ] ) ) ;
5252 const crushedSetOnInsert = toPlainAndCrush ( data . $setOnInsert || { } ) ;
53- // crushedUpdate['additionalFields.objectClasses'] = objectClasses;
54-
55- // console.log('crushedUpdate', crushedUpdate);
56- // console.log('crushedSetOnInsert', crushedSetOnInsert);
57-
5853 data = construct ( {
5954 ...crushedUpdate ,
6055 ...crushedSetOnInsert ,
6156 } ) ;
6257 data . additionalFields . validations = { } ;
63- const logPrefix = `Validation [${ data . inetOrgPerson . cn } ]:` ;
6458
59+ const logPrefix = `Validation [${ data . inetOrgPerson . cn } ]:` ;
6560 try {
6661 this . logger . log ( `${ logPrefix } Starting additionalFields validation.` ) ;
6762 const validations = await this . _validation . validate ( data . additionalFields ) ;
@@ -74,68 +69,27 @@ export class IdentitiesService extends AbstractServiceSchema {
7469 crushedUpdate [ 'additionalFields.validations' ] = data . additionalFields . validations ;
7570 }
7671
77- return await super . upsert (
72+ const identity = await this . model . findOne ( filters ) . exec ( ) ;
73+ const fingerprint = await this . previewFingerprint (
74+ construct ( {
75+ ...toPlainAndCrush ( identity ?. toJSON ( ) ) ,
76+ ...crushedUpdate ,
77+ } ) ,
78+ ) ;
79+ await this . checkFingerprint ( filters , fingerprint ) ;
80+
81+ const upserted = await super . upsert (
7882 filters ,
7983 {
8084 $set : crushedUpdate ,
8185 $setOnInsert : crushedSetOnInsert ,
8286 } ,
8387 options ,
8488 ) ;
85- }
8689
87- // public async upsert<T extends AbstractSchema | Document>(
88- // data?: any,
89- // options?: QueryOptions<T>,
90- // ): Promise<ModifyResult<Query<T, T, any, T>>> {
91- // Logger.log(`Upserting identity: ${JSON.stringify(data)}`);
92- // const logPrefix = `Validation [${data.inetOrgPerson.cn}]:`;
93- // // console.log(options);
94- // const identity = await this._model.findOne({
95- // 'inetOrgPerson.employeeNumber': data.inetOrgPerson.employeeNumber,
96- // 'inetOrgPerson.employeeType': data.inetOrgPerson.employeeType,
97- // });
98- // // console.log(identity);
99- // if (!identity && options.errorOnNotFound) {
100- // this.logger.error(`${logPrefix} Identity not found.`);
101- // throw new HttpException('Identity not found.', 404);
102- // }
103- // data.additionalFields.validations = {};
104- // try {
105- // this.logger.log(`${logPrefix} Starting additionalFields validation.`);
106- // const validations = await this._validation.validate(data.additionalFields);
107- // this.logger.log(`${logPrefix} AdditionalFields validation successful.`);
108- // this.logger.log(`Validations : ${validations}`);
109- // data.state = IdentityState.TO_VALIDATE;
110- // } catch (error) {
111- // data = this.handleValidationError(error, data, logPrefix);
112- // }
113-
114- // //TODO: ameliorer la logique d'upsert
115- // if (identity) {
116- // this.logger.log(`${logPrefix} Identity already exists. Updating.`);
117- // data.inetOrgPerson = {
118- // ...identity.inetOrgPerson,
119- // ...data.inetOrgPerson,
120- // };
121- // data.additionalFields.objectClasses = [
122- // ...new Set([...identity.additionalFields.objectClasses, ...data.additionalFields.objectClasses]),
123- // ];
124- // data.additionalFields.attributes = {
125- // ...identity.additionalFields.attributes,
126- // ...data.additionalFields.attributes,
127- // };
128- // data.additionalFields.validations = {
129- // ...identity.additionalFields.validations,
130- // ...data.additionalFields.validations,
131- // };
132- // }
133-
134- // //TODO: rechercher par uid ou employeeNumber + employeeType ?
135- // const upsert = await super.upsert({ 'inetOrgPerson.uid': data.inetOrgPerson.uid }, data, options);
136- // return upsert;
137- // //TODO: add backends service logic here
138- // }
90+ const identityUpserted = await this . _model . findOne ( { _id : ( upserted as any ) . _id } ) . exec ( ) ;
91+ return await this . generateFingerprint ( identityUpserted as unknown as Identities , fingerprint ) ;
92+ }
13993
14094 public async update < T extends AbstractSchema | Document > (
14195 _id : Types . ObjectId | any ,
@@ -164,16 +118,18 @@ export class IdentitiesService extends AbstractServiceSchema {
164118 throw error ; // Rethrow the original error if it's not one of the handled types.
165119 }
166120 }
121+
167122 // if (update.state === IdentityState.TO_COMPLETE) {
168123 update = { ...update , state : IdentityState . TO_VALIDATE } ;
124+
169125 // }
170126 // if (update.state === IdentityState.SYNCED) {
171127 // update = { ...update, state: IdentityState.TO_VALIDATE };
172128 // }
173129 //update.state = IdentityState.TO_VALIDATE;
174130 const updated = await super . update ( _id , update , options ) ;
175131 //TODO: add backends service logic here (TO_SYNC)
176- return updated ;
132+ return await this . generateFingerprint ( updated as unknown as Identities ) ;
177133 }
178134
179135 public async updateState < T extends AbstractSchema | Document > (
@@ -219,6 +175,71 @@ export class IdentitiesService extends AbstractServiceSchema {
219175 return deleted ;
220176 }
221177
178+ public async checkFingerprint < T extends AbstractSchema | Document > (
179+ filters : FilterQuery < T > ,
180+ fingerprint : string ,
181+ ) : Promise < void > {
182+ const identity = await this . model
183+ . findOne (
184+ { ...filters , fingerprint } ,
185+ {
186+ _id : 1 ,
187+ } ,
188+ )
189+ . exec ( ) ;
190+ if ( identity ) {
191+ this . logger . debug ( `Fingerprint matched for <${ identity . _id } > (${ fingerprint } ).` ) ;
192+ throw new HttpException ( 'Fingerprint matched.' , HttpStatus . NOT_MODIFIED ) ;
193+ }
194+ }
195+
196+ private async generateFingerprint < T extends AbstractSchema | Document > (
197+ identity : Identities ,
198+ fingerprint ?: string ,
199+ ) : Promise < ModifyResult < Query < T , T , any , T > > > {
200+ if ( ! fingerprint ) {
201+ fingerprint = await this . previewFingerprint ( identity . toJSON ( ) ) ;
202+ }
203+
204+ const updated = await this . model . findOneAndUpdate (
205+ { _id : identity . _id , fingerprint : { $ne : fingerprint } } ,
206+ { fingerprint } ,
207+ {
208+ new : true ,
209+ } ,
210+ ) ;
211+
212+ if ( ! updated ) {
213+ this . logger . verbose ( `Fingerprint already up to date for <${ identity . _id } >.` ) ;
214+ return identity as unknown as ModifyResult < Query < T , T , any , T > > ;
215+ }
216+
217+ this . logger . debug ( `Fingerprint updated for <${ identity . _id } >: ${ fingerprint } ` ) ;
218+ return updated as unknown as ModifyResult < Query < T , T , any , T > > ;
219+ }
220+
221+ private async previewFingerprint ( identity : any ) : Promise < string > {
222+ const additionalFields = omit ( identity . additionalFields , [ 'validations' ] ) ;
223+ const data = JSON . stringify (
224+ construct (
225+ omit (
226+ toPlainAndCrush ( {
227+ inetOrgPerson : identity . inetOrgPerson ,
228+ additionalFields,
229+ } ) as any ,
230+ [
231+ //TODO: add configurable fields to exclude
232+ /* 'additionalFields.attributes.supannPerson.supannOIDCGenre' */
233+ ] ,
234+ ) ,
235+ ) ,
236+ ) ;
237+
238+ const hash = createHash ( 'sha256' ) ;
239+ hash . update ( data ) ;
240+ return hash . digest ( 'hex' ) . toString ( ) ;
241+ }
242+
222243 private handleValidationError (
223244 error : Error | HttpException ,
224245 identity : Identities | IdentitiesUpsertDto ,
0 commit comments