Skip to content

Commit 7cf8cd5

Browse files
committed
feat: Add detailed JSDoc comments to MigrationsModule and MigrationsService for improved documentation
1 parent e1b0613 commit 7cf8cd5

File tree

2 files changed

+140
-56
lines changed

2 files changed

+140
-56
lines changed

apps/api/src/migrations/migrations.module.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,25 @@
1-
import { DynamicModule, Module } from '@nestjs/common';
2-
import { MigrationsService } from './migrations.service';
1+
import { DynamicModule, Module } from '@nestjs/common'
2+
import { MigrationsService } from './migrations.service'
33

4+
/**
5+
* Module NestJS pour la gestion des migrations de base de données
6+
*
7+
* @module MigrationsModule
8+
* @description Ce module fournit les services nécessaires pour gérer les migrations
9+
* de la base de données de l'application. Il peut être enregistré dynamiquement
10+
* pour permettre une configuration flexible.
11+
*/
412
@Module({
513
providers: [
614
MigrationsService,
715
],
816
})
917
export class MigrationsModule {
18+
/**
19+
* Enregistre dynamiquement le module de migrations
20+
*
21+
* @returns {Promise<DynamicModule>} Une promesse qui résout en module dynamique configuré
22+
*/
1023
public static async register(): Promise<DynamicModule> {
1124
return {
1225
module: this,

apps/api/src/migrations/migrations.service.ts

Lines changed: 125 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -9,95 +9,144 @@ import { ConfigService } from '@nestjs/config'
99
import { Connection } from 'mongoose'
1010
import { 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()
1326
export 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(/.js$/, '') + '>')} does not have a timestamp in the filename !`));
95-
return;
143+
this.logger.warn(chalk.yellow(`Migration ${chalk.bold('<' + file.replace(/.js$/, '') + '>')} 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(/.js$/, '') + '>')} are already executed !`));
100-
return false;
148+
this.logger.debug(chalk.yellow(`Migration ${chalk.bold('<' + file.replace(/.js$/, '') + '>')} 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

Comments
 (0)