Skip to content

Commit 8448152

Browse files
committed
Refactor code structure for improved readability and maintainability
1 parent f5007ab commit 8448152

File tree

8 files changed

+1400
-430
lines changed

8 files changed

+1400
-430
lines changed

apps/api/src/management/lifecycle/_dto/config-rules.dto.ts

Lines changed: 203 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,97 +1,116 @@
1-
import { ApiProperty } from '@nestjs/swagger';
2-
import { Type, Transform } from 'class-transformer';
3-
import { IsArray, IsNotEmpty, IsNumber, IsObject, IsOptional, ValidateNested, registerDecorator, ValidationOptions, ValidationArguments, isString, isNumber, IsString } from 'class-validator';
4-
import { IdentityLifecycleDefault } from '~/management/identities/_enums/lifecycle.enum';
1+
import { ApiProperty } from '@nestjs/swagger'
2+
import { Type, Transform } from 'class-transformer'
3+
import { IsArray, IsNotEmpty, IsNumber, IsObject, IsOptional, ValidateNested, registerDecorator, ValidationOptions, ValidationArguments, isString, isNumber, IsString } from 'class-validator'
4+
import { IdentityLifecycleDefault } from '~/management/identities/_enums/lifecycle.enum'
55

66
/**
7-
* Transform trigger values to seconds.
8-
* - Numbers are interpreted as days and converted to seconds
9-
* - Strings with 'd' suffix are interpreted as days and converted to seconds
10-
* - Strings with 'm' suffix are interpreted as minutes and converted to seconds
11-
* - Strings with 's' suffix are already in seconds
7+
* Convertit les valeurs de déclencheur temporel en secondes
128
*
13-
* @param value The trigger value to transform
14-
* @returns The value converted to seconds
9+
* @param {number | string} value - La valeur de déclencheur à transformer
10+
* @returns {number | undefined} La valeur convertie en secondes, ou undefined si invalide
11+
*
12+
* @description Effectue les conversions suivantes :
13+
* - Nombres : interprétés comme des jours et convertis en secondes
14+
* - Chaînes avec suffixe 'd' : interprétés comme des jours et convertis en secondes
15+
* - Chaînes avec suffixe 'm' : interprétés comme des minutes et convertis en secondes
16+
* - Chaînes avec suffixe 's' : déjà en secondes, valeur numérique extraite
17+
*
18+
* @throws {Error} Si la valeur ne correspond pas aux formats attendus
19+
*
20+
* @example
21+
* transformTriggerToSeconds(90) // retourne 7776000 (90 jours en secondes)
22+
* transformTriggerToSeconds('90d') // retourne 7776000
23+
* transformTriggerToSeconds('10m') // retourne 600
24+
* transformTriggerToSeconds('45s') // retourne 45
1525
*/
1626
function transformTriggerToSeconds(value: number | string): number | undefined {
17-
let isValid = false;
27+
let isValid = false
1828

1929
if (value === undefined || value === null) {
20-
return undefined;
30+
return undefined
2131
}
2232

2333
/**
24-
* Check if the value is a number.
25-
* If it's a number, we check if it's upper than 0.
26-
* If it's a string, we check if it matches the regex for time strings.
34+
* Validation de la valeur :
35+
* - Pour les nombres : doit être supérieur ou égal à 0
36+
* - Pour les chaînes : doit correspondre au format '\d+[dms]' (nombre suivi de d, m ou s)
2737
*/
2838
if (isNumber(value)) {
29-
isValid = value < 0;
39+
isValid = value < 0
3040
} else if (isString(value)) {
31-
const timeRegex = /^\d+[dms]$/;
41+
const timeRegex = /^\d+[dms]$/
3242
if (timeRegex.test(value)) {
33-
// Extract the number part and check if it's
34-
const numberPart = value.replace(/[dms]$/, '');
35-
const num = parseInt(numberPart, 10);
36-
isValid = num > 0;
43+
// Extraction de la partie numérique et vérification de sa validité
44+
const numberPart = value.replace(/[dms]$/, '')
45+
const num = parseInt(numberPart, 10)
46+
isValid = num > 0
3747
}
3848
}
3949

4050
if (!isValid) {
41-
throw new Error('Trigger must be a number (days) or a time string with units (e.g., "90d", "10m", "45s")');
51+
throw new Error('Le déclencheur doit être un nombre (jours) ou une chaîne temporelle avec unité (ex: "90d", "10m", "45s")')
4252
}
4353

4454
/**
45-
* If the value is a number, we assume it's in days and convert it to seconds.
46-
* We multiply by 24 (hours) * 60 (minutes) * 60 (seconds) to get the total seconds.
47-
* This conversion preserves the sign of the number,
48-
* so if the input is negative, the output will also be negative.
55+
* Conversion des nombres en secondes.
56+
* Les nombres sont interprétés comme des jours : jours * 24h * 60min * 60s
57+
* Le signe du nombre est préservé (négatif reste négatif)
4958
*/
5059
if (isNumber(value)) {
51-
return value * 24 * 60 * 60; // Convert days to seconds, preserving sign
60+
return value * 24 * 60 * 60 // Conversion jours → secondes avec préservation du signe
5261
}
5362

5463
/**
55-
* If the value is a string, we check if it matches the regex for negative time strings.
56-
* If it does, we extract the number and unit, then convert it to seconds.
57-
* - 'd' is converted to seconds by multiplying by 24 * 60 * 60
58-
* - 'm' is converted to seconds by multiplying by 60
59-
* - 's' is already in seconds
60-
* This conversion preserves the sign of the number,
61-
* so if the input is negative, the output will also be negative.
64+
* Conversion des chaînes temporelles en secondes selon l'unité :
65+
* - 'd' (jours) : × 24 × 60 × 60
66+
* - 'm' (minutes) : × 60
67+
* - 's' (secondes) : valeur inchangée
6268
*/
6369
if (isString(value)) {
64-
const match = value.match(/^(\d+)([dms])$/);
70+
const match = value.match(/^(\d+)([dms])$/)
6571
if (match) {
66-
const numValue = parseInt(match[1], 10);
67-
const unit = match[2];
72+
const numValue = parseInt(match[1], 10)
73+
const unit = match[2]
6874

6975
switch (unit) {
70-
case 'd': // days
71-
return numValue * 24 * 60 * 60;
76+
case 'd': // jours
77+
return numValue * 24 * 60 * 60
7278

7379
case 'm': // minutes
74-
return numValue * 60;
80+
return numValue * 60
7581

76-
case 's': // seconds
77-
return numValue;
82+
case 's': // secondes
83+
return numValue
7884

7985
default:
80-
throw new Error(`Unsupported time unit: ${unit}`);
86+
throw new Error(`Unité de temps non supportée : ${unit}`)
8187
}
8288
}
8389
}
8490

85-
// If we can't parse it, try to convert to number
86-
return Number(value) || undefined;
91+
// Tentative de conversion en nombre si aucun format ne correspond
92+
return Number(value) || undefined
8793
}
8894

8995
/**
90-
* Custom decorator to validate that at least one of the properties 'rules' or 'trigger' is defined and not empty.
91-
* This decorator can be applied to a class to enforce this validation rule.
96+
* Décorateur de validation personnalisé pour s'assurer qu'au moins une propriété 'rules' ou 'trigger' est définie
97+
*
98+
* @param {ValidationOptions} [validationOptions] - Options de validation class-validator
99+
* @returns {Function} Décorateur de classe
92100
*
93-
* @param validationOptions
94-
* @returns
101+
* @description Ce décorateur de classe valide qu'au moins l'une des deux propriétés suivantes est présente :
102+
* - `rules` : un objet contenant au moins une paire clé-valeur
103+
* - `trigger` : un nombre défini et non nul
104+
*
105+
* Cette validation garantit qu'une règle de transition de cycle de vie possède soit
106+
* des règles de filtrage, soit un déclencheur temporel, ou les deux.
107+
*
108+
* @example
109+
* @ValidateRulesOrTrigger({ message: 'Au moins rules ou trigger doit être fourni' })
110+
* class MaClasse {
111+
* rules?: object;
112+
* trigger?: number;
113+
* }
95114
*/
96115
function ValidateRulesOrTrigger(validationOptions?: ValidationOptions) {
97116
return function (constructor: Function) {
@@ -102,77 +121,189 @@ function ValidateRulesOrTrigger(validationOptions?: ValidationOptions) {
102121
options: validationOptions,
103122
validator: {
104123
validate(_: any, args: ValidationArguments) {
105-
const obj = args.object as ConfigRulesObjectIdentitiesDTO;
124+
const obj = args.object as ConfigRulesObjectIdentitiesDTO
106125

107126
/**
108-
* Check if either 'rules' or 'trigger' is defined and not empty.
109-
* 'rules' should be an object with at least one key-value pair,
110-
* and 'trigger' should be a number that is not null.
127+
* Vérification de la présence de 'rules' ou 'trigger' :
128+
* - 'rules' doit être un objet avec au moins une clé
129+
* - 'trigger' doit être un nombre défini et non nul
111130
*/
112-
const hasRules = obj.rules !== undefined && obj.rules !== null && (typeof obj.rules === 'object' && Object.keys(obj.rules).length > 0);
113-
const hasTrigger = obj.trigger !== undefined && obj.trigger !== null;
114-
return hasRules || hasTrigger;
131+
const hasRules = obj.rules !== undefined && obj.rules !== null && (typeof obj.rules === 'object' && Object.keys(obj.rules).length > 0)
132+
const hasTrigger = obj.trigger !== undefined && obj.trigger !== null
133+
return hasRules || hasTrigger
115134
},
116135
defaultMessage(_: ValidationArguments) {
117-
return 'Either rules or trigger must be provided';
136+
return 'Au moins rules ou trigger doit être fourni'
118137
}
119138
}
120-
});
121-
};
139+
})
140+
}
122141
}
123142

124-
@ValidateRulesOrTrigger({ message: 'Either rules or trigger must be provided' })
143+
/**
144+
* DTO de configuration des règles de transition du cycle de vie des identités
145+
*
146+
* @class ConfigRulesObjectIdentitiesDTO
147+
* @description Définit une règle de transition automatique entre états de cycle de vie.
148+
* Une règle spécifie :
149+
* - Les états source depuis lesquels la transition peut se faire
150+
* - Les conditions de déclenchement (temporelles et/ou basées sur des règles)
151+
* - L'état cible de la transition
152+
* - Les mutations à appliquer lors de la transition
153+
*
154+
* @example
155+
* {
156+
* sources: ['OFFICIAL'],
157+
* dateKey: 'lastLifecycleUpdate',
158+
* trigger: 90, // 90 jours
159+
* target: 'MANUAL',
160+
* mutation: { status: 'archived' }
161+
* }
162+
*/
163+
@ValidateRulesOrTrigger({ message: 'Au moins rules ou trigger doit être fourni' })
125164
export class ConfigRulesObjectIdentitiesDTO {
165+
/**
166+
* États source du cycle de vie depuis lesquels cette règle peut s'appliquer
167+
*
168+
* @type {IdentityLifecycleDefault[]}
169+
* @description Liste des états de cycle de vie qui déclenchent cette règle.
170+
* Une identité doit être dans l'un de ces états pour que la règle soit évaluée.
171+
*
172+
* @example ['OFFICIAL', 'MANUAL']
173+
*/
126174
@ApiProperty({
127175
type: String,
128176
enum: IdentityLifecycleDefault,
129-
description: 'Lifecycle state of the identity',
177+
description: 'États source du cycle de vie de l\'identité',
130178
example: IdentityLifecycleDefault.OFFICIAL,
131179
required: true,
132180
})
133181
@IsArray()
134182
@IsNotEmpty()
135183
@IsString({ each: true })
136-
public sources: IdentityLifecycleDefault[];
184+
public sources: IdentityLifecycleDefault[]
137185

186+
/**
187+
* Clé de date utilisée pour calculer le déclencheur temporel
188+
*
189+
* @type {string}
190+
* @default 'lastLifecycleUpdate'
191+
* @description Nom du champ de date dans le document d'identité à utiliser
192+
* comme référence pour calculer le délai du trigger.
193+
*
194+
* @example 'lastLifecycleUpdate', 'createdAt', 'lastModified'
195+
*/
138196
@IsOptional()
139197
@IsString()
140-
public dateKey: string = 'lastLifecycleUpdate';
198+
public dateKey: string = 'lastLifecycleUpdate'
141199

200+
/**
201+
* Règles de filtrage conditionnelles pour l'application de la transition
202+
*
203+
* @type {object}
204+
* @description Objet de règles permettant de filtrer les identités éligibles.
205+
* Fonctionne comme une requête MongoDB pour sélectionner les documents concernés.
206+
*
207+
* @example { department: 'IT', status: { $ne: 'disabled' } }
208+
*/
142209
@IsOptional()
143210
@IsObject()
144-
public rules: object;
211+
public rules: object
145212

213+
/**
214+
* Mutations à appliquer lors de la transition d'état
215+
*
216+
* @type {object}
217+
* @description Objet définissant les modifications à apporter aux champs de l'identité
218+
* lors de l'exécution de la transition. Permet de mettre à jour des attributs en plus
219+
* du changement d'état.
220+
*
221+
* @example { archived: true, archivedDate: new Date() }
222+
*/
146223
@IsOptional()
147224
@IsObject()
148-
public mutation: object;
225+
public mutation: object
149226

227+
/**
228+
* Déclencheur temporel de la transition en secondes
229+
*
230+
* @type {number}
231+
* @description Délai après lequel la transition doit s'exécuter, calculé depuis la date
232+
* spécifiée dans `dateKey`. Peut être fourni comme nombre (jours) ou chaîne avec unité.
233+
*
234+
* @example 90 (90 jours), '90d' (90 jours), '10m' (10 minutes), '45s' (45 secondes)
235+
*/
150236
@IsOptional()
151237
@Transform(({ value }) => transformTriggerToSeconds(value))
152238
@IsNumber()
153239
@ApiProperty({
154240
oneOf: [
155-
{ type: 'number', description: 'Number representing days' },
156-
{ type: 'string', description: 'Time string with units (d=days, m=minutes, s=seconds)' }
241+
{ type: 'number', description: 'Nombre représentant des jours' },
242+
{ type: 'string', description: 'Chaîne temporelle avec unité (d=jours, m=minutes, s=secondes)' }
157243
],
158244
required: false,
159-
description: 'Trigger time as number (days) or time string with units',
245+
description: 'Déclencheur temporel en nombre (jours) ou chaîne avec unité',
160246
examples: [90, '90d', '10m', '45s']
161247
})
162-
public trigger: number;
248+
public trigger: number
163249

250+
/**
251+
* État cible du cycle de vie après la transition
252+
*
253+
* @type {IdentityLifecycleDefault}
254+
* @description État de cycle de vie vers lequel l'identité sera transitionnée
255+
* lorsque les conditions de la règle sont satisfaites.
256+
*
257+
* @example IdentityLifecycleDefault.MANUAL
258+
*/
164259
@IsNotEmpty()
165260
@ApiProperty({
166261
type: String,
167262
enum: IdentityLifecycleDefault,
168-
description: 'Target lifecycle state for the identity',
263+
description: 'État cible du cycle de vie pour l\'identité',
169264
example: IdentityLifecycleDefault.MANUAL,
170265
required: true,
171266
})
172-
public target: IdentityLifecycleDefault;
267+
public target: IdentityLifecycleDefault
173268
}
174269

270+
/**
271+
* DTO de schéma de configuration des règles de cycle de vie
272+
*
273+
* @class ConfigRulesObjectSchemaDTO
274+
* @description Conteneur principal pour l'ensemble des règles de transition de cycle de vie.
275+
* Structure la configuration globale en regroupant toutes les règles applicables aux identités.
276+
*
277+
* @example
278+
* {
279+
* identities: [
280+
* {
281+
* sources: ['OFFICIAL'],
282+
* trigger: 90,
283+
* target: 'MANUAL'
284+
* },
285+
* {
286+
* sources: ['MANUAL'],
287+
* trigger: '30d',
288+
* target: 'ARCHIVED'
289+
* }
290+
* ]
291+
* }
292+
*/
175293
export class ConfigRulesObjectSchemaDTO {
294+
/**
295+
* Collection des règles de transition de cycle de vie pour les identités
296+
*
297+
* @type {ConfigRulesObjectIdentitiesDTO[]}
298+
* @description Tableau contenant l'ensemble des règles de transition automatique
299+
* appliquées aux identités. Chaque règle définit une transition possible entre états.
300+
*
301+
* @example
302+
* [
303+
* { sources: ['OFFICIAL'], trigger: 90, target: 'MANUAL' },
304+
* { sources: ['MANUAL'], trigger: 30, target: 'ARCHIVED' }
305+
* ]
306+
*/
176307
@IsOptional()
177308
@IsArray()
178309
@ApiProperty({

0 commit comments

Comments
 (0)