Skip to content

Commit 9a07c3a

Browse files
authored
Merge pull request #37 from Libertech-FR/function-disable-enable
Function disable enable
2 parents 79115a6 + 9a334ef commit 9a07c3a

21 files changed

+1079
-698
lines changed

src/core/backends/_enum/action-type.enum.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,6 @@ export enum ActionType {
77
IDENTITY_DELETE = 'IDENTITY_DELETE',
88
IDENTITY_PASSWORD_RESET = 'IDENTITY_PASSWORD_RESET',
99
IDENTITY_PASSWORD_CHANGE = 'IDENTITY_PASSWORD_CHANGE',
10+
IDENTITY_DISABLE = 'IDENTITY_DISABLE',
11+
IDENTITY_ENABLE = 'IDENTITY_ENABLE',
1012
}

src/core/backends/_interfaces/execute-job-options.interface.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,6 @@ export interface ExecuteJobOptions {
1111
switchToProcessing?: boolean;
1212
comment?: string;
1313
targetState?: any;
14+
dataState?: any;
1415
task?: Types.ObjectId;
1516
}

src/core/backends/backends.service.ts

Lines changed: 101 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import {
22
BadRequestException,
3-
HttpException,
43
HttpStatus,
4+
HttpException,
55
Injectable,
66
RequestTimeoutException,
77
UnprocessableEntityException,
@@ -11,24 +11,24 @@ import { Document, ModifyResult, Query, Types } from 'mongoose';
1111
import { AbstractQueueProcessor } from '~/_common/abstracts/abstract.queue.processor';
1212
import { IdentityState } from '~/management/identities/_enums/states.enum';
1313
import { Identities } from '~/management/identities/_schemas/identities.schema';
14-
import { IdentitiesService } from '~/management/identities/identities.service';
14+
import { IdentitiesCrudService } from '~/management/identities/identities-crud.service';
1515
import { JobState } from '../jobs/_enums/state.enum';
1616
import { Jobs } from '../jobs/_schemas/jobs.schema';
1717
import { JobsService } from '../jobs/jobs.service';
1818
import { Tasks } from '../tasks/_schemas/tasks.schema';
1919
import { TasksService } from '../tasks/tasks.service';
2020
import { ActionType } from './_enum/action-type.enum';
2121
import { ExecuteJobOptions } from './_interfaces/execute-job-options.interface';
22-
import { BackendResultInterface } from "~/core/backends/_interfaces/backend-result.interface";
23-
import { WorkerResultInterface } from "~/core/backends/_interfaces/worker-result.interface";
22+
import { WorkerResultInterface } from '~/core/backends/_interfaces/worker-result.interface';
23+
import { DataStatusEnum } from '~/management/identities/_enums/data-status';
2424

2525
const DEFAULT_SYNC_TIMEOUT = 30_000;
2626

2727
@Injectable()
2828
export class BackendsService extends AbstractQueueProcessor {
2929
public constructor(
3030
protected moduleRef: ModuleRef,
31-
protected identitiesService: IdentitiesService,
31+
protected identitiesService: IdentitiesCrudService,
3232
protected jobsService: JobsService,
3333
protected tasksService: TasksService,
3434
) {
@@ -265,6 +265,50 @@ export class BackendsService extends AbstractQueueProcessor {
265265
updateStatus: true,
266266
switchToProcessing: false,
267267
targetState: IdentityState.DONT_SYNC,
268+
dataState: DataStatusEnum.DELETED,
269+
task: task._id,
270+
});
271+
result[identity.identity._id] = executedJob;
272+
console.log(res);
273+
}
274+
return result;
275+
}
276+
277+
public async disableIdentities(payload: string[], options?: ExecuteJobOptions): Promise<any> {
278+
const identities: {
279+
action: ActionType;
280+
identity: Identities;
281+
}[] = [];
282+
283+
if (!payload.length) throw new BadRequestException('No identities to disable');
284+
285+
for (const key of payload) {
286+
const identity = await this.identitiesService.findById<Identities>(key);
287+
if (!identity.lastBackendSync) {
288+
throw new BadRequestException({
289+
status: HttpStatus.BAD_REQUEST,
290+
message: `Identity ${key} has never been synched`,
291+
identity,
292+
});
293+
}
294+
identities.push({
295+
action: ActionType.IDENTITY_DISABLE,
296+
identity,
297+
});
298+
}
299+
300+
const task: Tasks = await this.tasksService.create<Tasks>({
301+
jobs: identities.map((identity) => identity.identity._id),
302+
});
303+
304+
const result = {};
305+
for (const identity of identities) {
306+
const [executedJob, res] = await this.executeJob(identity.action, identity.identity._id, identity.identity, {
307+
...options,
308+
updateStatus: true,
309+
switchToProcessing: false,
310+
targetState: IdentityState.SYNCED,
311+
dataState: DataStatusEnum.INACTIVE,
268312
task: task._id,
269313
});
270314
result[identity.identity._id] = executedJob;
@@ -273,6 +317,57 @@ export class BackendsService extends AbstractQueueProcessor {
273317
return result;
274318
}
275319

320+
public async enableIdentities(payload: string[], options?: ExecuteJobOptions): Promise<any> {
321+
const identities: {
322+
action: ActionType;
323+
identity: Identities;
324+
}[] = [];
325+
326+
if (!payload.length) throw new BadRequestException('No identities to disable');
327+
328+
for (const key of payload) {
329+
const identity = await this.identitiesService.findById<Identities>(key);
330+
if (!identity.lastBackendSync) {
331+
throw new BadRequestException({
332+
status: HttpStatus.BAD_REQUEST,
333+
message: `Identity ${key} has never been synched`,
334+
identity,
335+
});
336+
}
337+
identities.push({
338+
action: ActionType.IDENTITY_ENABLE,
339+
identity,
340+
});
341+
}
342+
343+
const task: Tasks = await this.tasksService.create<Tasks>({
344+
jobs: identities.map((identity) => identity.identity._id),
345+
});
346+
347+
const result = {};
348+
for (const identity of identities) {
349+
const [executedJob, res] = await this.executeJob(identity.action, identity.identity._id, identity.identity, {
350+
...options,
351+
updateStatus: true,
352+
switchToProcessing: false,
353+
targetState: IdentityState.SYNCED,
354+
dataState: DataStatusEnum.ACTIVE,
355+
task: task._id,
356+
});
357+
result[identity.identity._id] = executedJob;
358+
console.log(res);
359+
}
360+
return result;
361+
}
362+
public async activationIdentity(payload: string, status: boolean, options?: ExecuteJobOptions) {
363+
let result = null;
364+
if (status === true) {
365+
result = await this.enableIdentities([payload], options );
366+
} else {
367+
result = await this.disableIdentities([payload], options);
368+
}
369+
return result[payload];
370+
}
276371
public async executeJob(
277372
actionType: ActionType,
278373
concernedTo?: Types.ObjectId,
@@ -289,7 +384,7 @@ export class BackendsService extends AbstractQueueProcessor {
289384
},
290385
{
291386
...options?.job,
292-
jobId: (new Types.ObjectId()).toHexString(),
387+
jobId: (new Types.ObjectId() ).toHexString(),
293388
attempts: 1,
294389
},
295390
);
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { ApiProperty } from '@nestjs/swagger';
2+
import { IsBoolean, IsString } from 'class-validator';
3+
4+
export class ActivationDto {
5+
@IsString()
6+
@ApiProperty({ example: '66d80ab41821baca9bf965b2', description: 'Id of identity', type: String })
7+
public id: string;
8+
9+
@IsBoolean()
10+
@ApiProperty({ example: 'true', description: 'true or false to enable or disable the identity', type: String })
11+
public status: boolean;
12+
}

src/management/identities/_dto/identities.dto.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { additionalFieldsPartDto } from './_parts/additionalFields.dto';
88
import { MetadataDto } from '~/_common/abstracts/dto/metadata.dto';
99
import { InitStatesEnum } from '~/management/identities/_enums/init-state.enum';
1010
import { CustomFieldsDto } from '~/_common/abstracts/dto/custom-fields.dto';
11+
import {DataStatusEnum} from "~/management/identities/_enums/data-status";
1112

1213
export class IdentitiesCreateDto extends IntersectionType(CustomFieldsDto, MetadataDto) {
1314
@IsNumber()
@@ -20,6 +21,11 @@ export class IdentitiesCreateDto extends IntersectionType(CustomFieldsDto, Metad
2021
@ApiProperty({ enum: InitStatesEnum })
2122
public initState: InitStatesEnum;
2223

24+
@IsNumber()
25+
@IsEnum(DataStatusEnum)
26+
@ApiProperty({ enum: DataStatusEnum })
27+
public dataStatus: DataStatusEnum;
28+
2329
@IsNumber()
2430
@IsOptional()
2531
@IsEnum(IdentityLifecycle)
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
//status de l enregistrement pour le soft delete
2+
//ACTIVE quand l'enregistrement est actif
3+
//INACTIVE quand l' identité doit etre disable sur les backend
4+
//DELETED : soft delete
5+
export enum DataStatusEnum {
6+
ACTIVE = 1,
7+
INACTIVE = 0,
8+
DELETED = -1,
9+
}

src/management/identities/_schemas/identities.schema.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { InitInfoPart, InitInfoPartSchema } from '~/management/identities/_schem
1010
import { MixedValue } from '~/_common/types/mixed-value.type';
1111
import { AutoIncrementPlugin } from '~/_common/plugins/mongoose/auto-increment.plugin';
1212
import { AutoIncrementPluginOptions } from '~/_common/plugins/mongoose/auto-increment.interface';
13+
import { DataStatusEnum } from '~/management/identities/_enums/data-status';
1314

1415
export type IdentitiesDocument = Identities & Document;
1516

@@ -21,6 +22,9 @@ export class Identities extends AbstractSchema {
2122
@Prop({ type: Number, enum: IdentityLifecycle, default: IdentityLifecycle.INACTIVE })
2223
public lifecycle: IdentityLifecycle;
2324

25+
@Prop({ type: Number, enum: DataStatusEnum, default: DataStatusEnum.ACTIVE })
26+
public dataStatus: DataStatusEnum;
27+
2428
@Prop({ type: inetOrgPersonSchema, required: true })
2529
public inetOrgPerson: inetOrgPerson;
2630

src/management/identities/_stubs/identities.dto.stub.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { IdentityState } from '~/management/identities/_enums/states.enum';
55
import { additionalFieldsPartDtoStub } from './_parts/addtionalFields.dto.stub';
66
import { inetOrgPersonDtoStub } from './_parts/inetOrgPerson.dto.stub';
77
import { InitStatesEnum } from '~/management/identities/_enums/init-state.enum';
8+
import { DataStatusEnum } from '~/management/identities/_enums/data-status';
89

910
export const IdentitiesDtoStub = (): IdentitiesDto => {
1011
return {
@@ -13,6 +14,7 @@ export const IdentitiesDtoStub = (): IdentitiesDto => {
1314
inetOrgPerson: inetOrgPersonDtoStub(),
1415
additionalFields: additionalFieldsPartDtoStub(),
1516
initState: InitStatesEnum.NOSENT,
17+
dataStatus: DataStatusEnum.ACTIVE,
1618
};
1719
};
1820

@@ -23,6 +25,7 @@ export const IdentitiesUpdateDtoStub = (): IdentitiesUpdateDto => {
2325
state: IdentityState.TO_COMPLETE,
2426
lifecycle: IdentityLifecycle.INACTIVE,
2527
initState: InitStatesEnum.NOSENT,
28+
dataStatus: DataStatusEnum.ACTIVE,
2629
inetOrgPerson,
2730
additionalFields: additionalFieldsPartDtoStub(),
2831
};
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
import { BadRequestException, forwardRef, HttpException, Inject, Injectable } from '@nestjs/common';
2+
import { InjectModel } from '@nestjs/mongoose';
3+
import { Document, Model, ModifyResult, Query, Types } from 'mongoose';
4+
import { AbstractServiceSchema } from '~/_common/abstracts/abstract.service.schema';
5+
import { AbstractSchema } from '~/_common/abstracts/schemas/abstract.schema';
6+
import { ValidationConfigException, ValidationSchemaException } from '~/_common/errors/ValidationException';
7+
import { IdentitiesUpsertDto } from './_dto/identities.dto';
8+
import { IdentityState } from './_enums/states.enum';
9+
import { Identities } from './_schemas/identities.schema';
10+
import { IdentitiesValidationService } from './validations/identities.validation.service';
11+
import { FactorydriveService } from '@the-software-compagny/nestjs_module_factorydrive';
12+
import { BackendsService } from '~/core/backends/backends.service';
13+
import { construct, omit } from 'radash';
14+
import { toPlainAndCrush } from '~/_common/functions/to-plain-and-crush';
15+
import { createHash } from 'node:crypto';
16+
17+
@Injectable()
18+
export abstract class AbstractIdentitiesService extends AbstractServiceSchema {
19+
public constructor(
20+
@InjectModel(Identities.name) protected _model: Model<Identities>,
21+
protected readonly _validation: IdentitiesValidationService,
22+
protected readonly storage: FactorydriveService,
23+
@Inject(forwardRef(() => BackendsService)) protected readonly backends: BackendsService,
24+
) {
25+
super();
26+
}
27+
28+
protected handleValidationError(
29+
error: Error | HttpException,
30+
identity: Identities | IdentitiesUpsertDto,
31+
logPrefix: string,
32+
): any {
33+
if (error instanceof ValidationConfigException) {
34+
this.logger.error(`${logPrefix} Validation config error. ${JSON.stringify(error.getValidations())}`);
35+
throw new ValidationConfigException(error.getPayload());
36+
}
37+
38+
if (error instanceof ValidationSchemaException) {
39+
this.logger.warn(`${logPrefix} Validation schema error. ${JSON.stringify(error.getValidations())}`);
40+
identity.additionalFields.validations = error.getValidations() as any;
41+
// console.log('identity.state', identity.state)
42+
if (identity.state === IdentityState.TO_CREATE) {
43+
this.logger.warn(`${logPrefix} State set to TO_COMPLETE.`);
44+
identity.state = IdentityState.TO_COMPLETE;
45+
return identity;
46+
} else {
47+
this.logger.error(`${logPrefix} Validation schema error. ${JSON.stringify(error.getValidations())}`);
48+
throw new ValidationSchemaException(error.getPayload());
49+
}
50+
} else {
51+
this.logger.error(`${logPrefix} Unhandled error: ${error.message}`);
52+
throw error; // Rethrow the original error if it's not one of the handled types.
53+
}
54+
}
55+
56+
public transformNullsToString(obj) {
57+
if (obj === null) {
58+
return '';
59+
}
60+
61+
if (Array.isArray(obj)) {
62+
return obj.map(this.transformNullsToString);
63+
}
64+
65+
if (typeof obj === 'object' && !(obj instanceof Types.ObjectId)) {
66+
for (const key in obj) {
67+
if (obj[key] === null) {
68+
obj[key] = '';
69+
} else if (typeof obj[key] === 'object') {
70+
// console.log('key', key);
71+
obj[key] = this.transformNullsToString(obj[key]);
72+
}
73+
}
74+
}
75+
76+
return obj;
77+
}
78+
79+
protected async checkInetOrgPersonJpegPhoto(data: any) {
80+
if (data?.inetOrgPerson?.jpegPhoto) {
81+
let reqStorage;
82+
const [disk, path] = data.inetOrgPerson.jpegPhoto.split(':');
83+
84+
try {
85+
reqStorage = await this.storage.getDisk(disk).exists(path);
86+
} catch (error) {
87+
throw new BadRequestException(`Error while checking photo in storage: ${error.message}`);
88+
}
89+
90+
if (!reqStorage.exists) {
91+
throw new BadRequestException(`Photo not found in storage: ${data.inetOrgPerson.jpegPhoto}`);
92+
}
93+
}
94+
}
95+
protected async generateFingerprint<T extends AbstractSchema | Document>(
96+
identity: Identities,
97+
fingerprint?: string,
98+
): Promise<ModifyResult<Query<T, T, any, T>>> {
99+
if (!fingerprint) {
100+
fingerprint = await this.previewFingerprint(identity.toJSON());
101+
}
102+
103+
const updated = await this.model.findOneAndUpdate(
104+
{ _id: identity._id, fingerprint: { $ne: fingerprint } },
105+
{ fingerprint },
106+
{
107+
new: true,
108+
},
109+
);
110+
111+
if (!updated) {
112+
this.logger.verbose(`Fingerprint already up to date for <${identity._id}>.`);
113+
return identity as unknown as ModifyResult<Query<T, T, any, T>>;
114+
}
115+
116+
this.logger.debug(`Fingerprint updated for <${identity._id}>: ${fingerprint}`);
117+
return updated as unknown as ModifyResult<Query<T, T, any, T>>;
118+
}
119+
protected async previewFingerprint(identity: any): Promise<string> {
120+
const additionalFields = omit(identity.additionalFields, ['validations']);
121+
const data = JSON.stringify(
122+
construct(
123+
omit(
124+
toPlainAndCrush({
125+
inetOrgPerson: identity.inetOrgPerson,
126+
additionalFields,
127+
}) as any,
128+
[
129+
//TODO: add configurable fields to exclude
130+
/* 'additionalFields.attributes.supannPerson.supannOIDCGenre' */
131+
],
132+
),
133+
),
134+
);
135+
136+
const hash = createHash('sha256');
137+
hash.update(data);
138+
return hash.digest('hex').toString();
139+
}
140+
}

0 commit comments

Comments
 (0)