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 */
1626function 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 + [ d m s ] $ / ;
41+ const timeRegex = / ^ \d + [ d m s ] $ /
3242 if ( timeRegex . test ( value ) ) {
33- // Extract the number part and check if it's
34- const numberPart = value . replace ( / [ d m s ] $ / , '' ) ;
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 ( / [ d m s ] $ / , '' )
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 + ) ( [ d m s ] ) $ / ) ;
70+ const match = value . match ( / ^ ( \d + ) ( [ d m s ] ) $ / )
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 */
96115function 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' } )
125164export 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+ */
175293export 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