@@ -9,95 +9,144 @@ import { ConfigService } from '@nestjs/config'
99import { Connection } from 'mongoose'
1010import { InjectConnection } from '@nestjs/mongoose'
1111
12+ /**
13+ * Service de gestion des migrations de base de données
14+ *
15+ * @class MigrationsService
16+ * @implements {OnModuleInit}
17+ * @description Ce service gère l'exécution automatique des migrations de base de données
18+ * au démarrage du module. Il maintient un fichier de verrouillage pour suivre l'état
19+ * des migrations et garantir qu'elles ne sont exécutées qu'une seule fois.
20+ *
21+ * Les migrations sont stockées dans le dossier `jobs/` avec un timestamp dans le nom du fichier.
22+ * Le service vérifie le fichier de verrouillage et la collection MongoDB pour déterminer
23+ * quelles migrations doivent être exécutées.
24+ */
1225@Injectable ( )
1326export class MigrationsService implements OnModuleInit {
1427 private readonly logger = new Logger ( `${ chalk . bold . red ( MigrationsService . name ) } \x1b[33m` )
1528
1629 protected lockLocation : string
1730 protected migrations = new Map < string , any > ( )
1831
32+ /**
33+ * Constructeur du service de migrations
34+ *
35+ * @param {Connection } mongo - Connexion MongoDB injectée
36+ * @param {ModuleRef } moduleRef - Référence au module NestJS pour créer des instances dynamiques
37+ * @param {ConfigService } config - Service de configuration pour récupérer les paramètres
38+ */
1939 public constructor (
2040 @InjectConnection ( ) private readonly mongo : Connection ,
2141 private readonly moduleRef : ModuleRef ,
2242 private readonly config : ConfigService ,
23-
2443 ) {
2544 this . lockLocation = posix . join ( this . config . get ( 'factorydrive.options.disks.local.config.root' , '/tmp' ) , 'migrations.lock' )
2645 }
2746
28- public async onModuleInit ( ) {
29- this . logger . debug ( chalk . yellow ( 'Migrations service initialized.' ) ) ;
30- this . logger . debug ( chalk . yellow ( 'Lock file location: ' + this . lockLocation ) ) ;
47+ /**
48+ * Hook d'initialisation du module
49+ *
50+ * @async
51+ * @description Appelé automatiquement au démarrage du module.
52+ * Initialise le service de migrations en:
53+ * 1. Vérifiant le fichier de verrouillage
54+ * 2. Chargeant les fichiers de migrations
55+ * 3. Exécutant les migrations nécessaires
56+ */
57+ public async onModuleInit ( ) : Promise < void > {
58+ this . logger . debug ( chalk . yellow ( 'Migrations service initialized.' ) )
59+ this . logger . debug ( chalk . yellow ( 'Lock file location: ' + this . lockLocation ) )
3160 const currentTimestamp = await this . _checkMigrationLockFile ( )
32- this . logger . debug ( chalk . yellow ( 'Checking migrations files...' ) ) ;
33- await this . _loadMigrationsFiles ( currentTimestamp ) ;
61+ this . logger . debug ( chalk . yellow ( 'Checking migrations files...' ) )
62+ await this . _loadMigrationsFiles ( currentTimestamp )
3463
35- const loader = startLoader ( 'Migration en cours...' ) ;
36- await this . _executeMigrations ( ) ;
37- stopLoader ( loader ) ;
64+ const loader = startLoader ( 'Migration en cours...' )
65+ await this . _executeMigrations ( )
66+ stopLoader ( loader )
3867 }
3968
40- private async _checkMigrationLockFile ( ) {
69+ /**
70+ * Vérifie et synchronise le fichier de verrouillage des migrations
71+ *
72+ * @async
73+ * @private
74+ * @returns {Promise<number> } Le timestamp de la dernière migration exécutée
75+ * @description Vérifie l'existence et la cohérence du fichier de verrouillage.
76+ * Si le fichier n'existe pas, il est créé avec le timestamp de la dernière migration
77+ * dans la base de données. Synchronise également la base de données si nécessaire.
78+ */
79+ private async _checkMigrationLockFile ( ) : Promise < number > {
4180 let currentTimestamp = 0
4281
4382 try {
44- const migration = await readFile ( this . lockLocation , 'utf-8' ) ;
45- currentTimestamp = parseInt ( migration , 10 ) ;
46- this . logger . log ( chalk . blue ( `Migration lock state is <${ currentTimestamp } > !` ) ) ;
83+ const migration = await readFile ( this . lockLocation , 'utf-8' )
84+ currentTimestamp = parseInt ( migration , 10 )
85+ this . logger . log ( chalk . blue ( `Migration lock state is <${ currentTimestamp } > !` ) )
4786 } catch ( error ) {
48- this . logger . warn ( chalk . red ( 'No migration lock file found.' ) ) ;
87+ this . logger . warn ( chalk . red ( 'No migration lock file found.' ) )
4988 }
5089
51- const dbMigration = await this . mongo . collection ( 'migrations' ) . findOne ( { } , { sort : { timestamp : - 1 } } ) ;
90+ const dbMigration = await this . mongo . collection ( 'migrations' ) . findOne ( { } , { sort : { timestamp : - 1 } } )
5291
5392 if ( currentTimestamp === 0 ) {
5493 if ( dbMigration ) {
5594 try {
56- this . logger . warn ( chalk . yellow ( 'No migration lock file found. Creating one with the last migration timestamp...' ) ) ;
57- await writeFile ( this . lockLocation , dbMigration . timestamp . toString ( ) ) ;
58- this . logger . log ( chalk . green ( 'Migration lock file created.' ) ) ;
95+ this . logger . warn ( chalk . yellow ( 'No migration lock file found. Creating one with the last migration timestamp...' ) )
96+ await writeFile ( this . lockLocation , dbMigration . timestamp . toString ( ) )
97+ this . logger . log ( chalk . green ( 'Migration lock file created.' ) )
5998 } catch ( error ) {
60- this . logger . error ( chalk . red ( 'Error while creating migration lock file !' ) ) ;
99+ this . logger . error ( chalk . red ( 'Error while creating migration lock file !' ) )
61100 }
62101 } else {
63102 try {
64- await writeFile ( this . lockLocation , currentTimestamp . toString ( ) ) ;
65- this . logger . log ( chalk . green ( 'Migration lock file created.' ) ) ;
103+ await writeFile ( this . lockLocation , currentTimestamp . toString ( ) )
104+ this . logger . log ( chalk . green ( 'Migration lock file created.' ) )
66105 } catch ( error ) {
67- this . logger . error ( chalk . red ( 'Error while creating migration lock file !' ) ) ;
106+ this . logger . error ( chalk . red ( 'Error while creating migration lock file !' ) )
68107 }
69108 }
70109 }
71110
72111 if ( ! dbMigration && currentTimestamp !== 0 ) {
73- this . logger . error ( chalk . red ( 'Database is not up to date with the migrations files !' ) ) ;
112+ this . logger . error ( chalk . red ( 'Database is not up to date with the migrations files !' ) )
74113 await this . mongo . collection ( 'migrations' ) . insertOne ( {
75114 timestamp : currentTimestamp ,
76115 comment : 'Synchronization with the migration lock file' ,
77- } ) ;
78- this . logger . log ( chalk . green ( 'Database updated with the current migration lock file !' ) ) ;
116+ } )
117+ this . logger . log ( chalk . green ( 'Database updated with the current migration lock file !' ) )
79118 }
80119
81120 return currentTimestamp
82121 }
83122
84- private async _loadMigrationsFiles ( currentTimestamp = 0 ) {
123+ /**
124+ * Charge les fichiers de migrations à exécuter
125+ *
126+ * @async
127+ * @private
128+ * @param {number } [currentTimestamp=0] - Timestamp de la dernière migration exécutée
129+ * @description Recherche et filtre les fichiers de migrations dans le dossier `jobs/`.
130+ * Ne charge que les migrations dont le timestamp est supérieur au timestamp actuel.
131+ * Les migrations sont triées par ordre chronologique avant d'être chargées.
132+ */
133+ private async _loadMigrationsFiles ( currentTimestamp = 0 ) : Promise < void > {
85134 let files = await glob ( `./jobs/*.js` , {
86135 cwd : __dirname ,
87136 root : __dirname ,
88- } ) ;
137+ } )
89138
90139 files = files . filter ( ( file ) => {
91140 const [ timestampMatch ] = file . match ( / \d { 10 , } / ) || [ ]
92141
93142 if ( ! timestampMatch ) {
94- this . logger . warn ( chalk . yellow ( `Migration ${ chalk . bold ( '<' + file . replace ( / .j s $ / , '' ) + '>' ) } does not have a timestamp in the filename !` ) ) ;
95- return ;
143+ this . logger . warn ( chalk . yellow ( `Migration ${ chalk . bold ( '<' + file . replace ( / .j s $ / , '' ) + '>' ) } does not have a timestamp in the filename !` ) )
144+ return
96145 }
97146
98147 if ( parseInt ( timestampMatch ) <= currentTimestamp ) {
99- this . logger . debug ( chalk . yellow ( `Migration ${ chalk . bold ( '<' + file . replace ( / .j s $ / , '' ) + '>' ) } are already executed !` ) ) ;
100- return false ;
148+ this . logger . debug ( chalk . yellow ( `Migration ${ chalk . bold ( '<' + file . replace ( / .j s $ / , '' ) + '>' ) } are already executed !` ) )
149+ return false
101150 }
102151
103152 return true
@@ -111,67 +160,89 @@ export class MigrationsService implements OnModuleInit {
111160 } )
112161
113162 for ( const file of files ) {
114- const migration = await import ( `${ __dirname } /${ file } ` ) ;
163+ const migration = await import ( `${ __dirname } /${ file } ` )
115164
116165 if ( ! migration . default ) {
117- this . logger . log ( chalk . yellow ( `Migration ${ chalk . bold ( '<' + file + '>' ) } does not have a default export !` ) ) ;
118- return ;
166+ this . logger . log ( chalk . yellow ( `Migration ${ chalk . bold ( '<' + file + '>' ) } does not have a default export !` ) )
167+ return
119168 }
120169
121170 this . migrations . set ( file , migration )
122171 }
123172 }
124173
125- private async _executeMigrations ( ) {
174+ /**
175+ * Exécute toutes les migrations en attente
176+ *
177+ * @async
178+ * @private
179+ * @description Parcourt et exécute séquentiellement toutes les migrations chargées.
180+ * Chaque migration doit avoir une méthode `up()` qui sera appelée.
181+ * En cas d'erreur, le processus s'arrête et l'erreur est loggée.
182+ * Après chaque migration réussie, le fichier de verrouillage est mis à jour.
183+ */
184+ private async _executeMigrations ( ) : Promise < void > {
126185 if ( this . migrations . size === 0 ) {
127- this . logger . log ( chalk . green ( 'No migrations to execute.' ) ) ;
186+ this . logger . log ( chalk . green ( 'No migrations to execute.' ) )
128187 return ;
129188 }
130189
131190 if ( ! this . migrations . size ) {
132- this . logger . log ( chalk . blue ( 'No migrations to execute.' ) ) ;
191+ this . logger . log ( chalk . blue ( 'No migrations to execute.' ) )
133192 return ;
134193 }
135194
136195 for ( const key of this . migrations . keys ( ) ) {
137196 const [ migrationTimestamp ] = key . match ( / \d { 10 , } / ) || [ ]
138197
139- const migration = this . migrations . get ( key ) ;
140- const instance = await this . moduleRef . create ( migration . default ) ;
198+ const migration = this . migrations . get ( key )
199+ const instance = await this . moduleRef . create ( migration . default )
141200
142201 if ( typeof instance . up !== 'function' ) {
143- this . logger . log ( chalk . yellow ( `Migration ${ chalk . bold ( '<' + key + '>' ) } does not have an up method !` ) ) ;
144- break ;
202+ this . logger . log ( chalk . yellow ( `Migration ${ chalk . bold ( '<' + key + '>' ) } does not have an up method !` ) )
203+ break
145204 }
146205
147206 try {
148- this . logger . log ( chalk . yellow ( `Running migration ${ chalk . bold ( '<' + key + '>' ) } ...` ) ) ;
149- await instance . up ( ) ;
207+ this . logger . log ( chalk . yellow ( `Running migration ${ chalk . bold ( '<' + key + '>' ) } ...` ) )
208+ await instance . up ( )
150209 } catch ( e ) {
151- this . logger . error ( chalk . red ( `Error while running migration ${ chalk . bold ( '<' + key + '>' ) } !` ) ) ;
152- this . logger . error ( e . message , e . stack ) ;
153- return ;
210+ this . logger . error ( chalk . red ( `Error while running migration ${ chalk . bold ( '<' + key + '>' ) } !` ) )
211+ this . logger . error ( e . message , e . stack )
212+ return
154213 }
155214
156- this . _writeMigrationLockFile ( key , migrationTimestamp ) ;
215+ this . _writeMigrationLockFile ( key , migrationTimestamp )
157216 }
158217
159- this . logger . log ( chalk . blue ( 'All migrations done.' ) ) ;
218+ this . logger . log ( chalk . blue ( 'All migrations done.' ) )
160219 }
161220
162- private async _writeMigrationLockFile ( migrationKey : string , migrationTimestamp : string ) {
221+ /**
222+ * Met à jour le fichier de verrouillage et la base de données après une migration
223+ *
224+ * @async
225+ * @private
226+ * @param {string } migrationKey - Nom du fichier de migration
227+ * @param {string } migrationTimestamp - Timestamp de la migration
228+ * @throws {Error } Si la mise à jour du fichier de verrouillage échoue
229+ * @description Écrit le nouveau timestamp dans le fichier de verrouillage
230+ * et insère un enregistrement dans la collection MongoDB `migrations`
231+ * pour garder une trace de l'exécution.
232+ */
233+ private async _writeMigrationLockFile ( migrationKey : string , migrationTimestamp : string ) : Promise < void > {
163234 try {
164- await writeFile ( this . lockLocation , migrationTimestamp ) ;
235+ await writeFile ( this . lockLocation , migrationTimestamp )
165236 await this . mongo . collection ( 'migrations' ) . insertOne ( {
166237 timestamp : parseInt ( migrationTimestamp ) ,
167238 comment : `Migration ${ migrationKey } executed` ,
168239 } )
169- this . logger . log ( chalk . blue ( `Migration ${ chalk . bold ( '<' + migrationKey + '>' ) } done.` ) ) ;
240+ this . logger . log ( chalk . blue ( `Migration ${ chalk . bold ( '<' + migrationKey + '>' ) } done.` ) )
170241 } catch ( e ) {
171- this . logger . error ( chalk . red ( `Error while updating migration lock file !` ) ) ;
172- this . logger . error ( e ) ;
242+ this . logger . error ( chalk . red ( `Error while updating migration lock file !` ) )
243+ this . logger . error ( e )
173244
174- throw new Error ( 'Error while updating migration lock file !' ) ;
245+ throw new Error ( 'Error while updating migration lock file !' )
175246 }
176247 }
177248}
0 commit comments