Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/server/src/config/mikro-orm-cli.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const mikroOrmCliConfig: MikroOrmModuleSyncOptions = {
migrations: {
tableName: 'migrations', // name of database table with log of executed transactions
path: migrationsPath, // path to the folder with migrations
pathTs: migrationsPath, // path to the folder with TS migrations (if used, we should put path to compiled files in `path`)
pathTs: migrationsPath.replace('/dist/apps/server/migrations/mikro-orm', '/apps/server/src/migrations/mikro-orm'), // path to the folder with TS migrations (if used, we should put path to compiled files in `path`)
Copy link
Copy Markdown
Contributor

@CeEv CeEv Feb 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bei pathTs von dist auf den source code umzulenken ist sinnvoll. Warum aber nicht diesen direkt eintragen?
Vielleicht übersetzte ich das falsch, aber der Kommentar der vorher schon drin war, impliziert für mich das wir es genau umgedreht machen sollten. Was falsch ist. Können wir den Kommentar noch anpassen, bzw. Link zur Doku von pathTs. Da war das ganz gut beschrieben.

glob: '!(*.d).{js,ts}', // how to match migration files (all .js and .ts files, but not .d.ts)
transactional: false, // wrap each migration in a transaction
disableForeignKeys: true, // wrap statements with `set foreign_key_checks = 0` or equivalent
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { MikroORM } from '@mikro-orm/core';
import { createMock, DeepMocked } from '@golevelup/ts-jest';
import { IMigrator, MikroORM } from '@mikro-orm/core';
import { EntityManager, ObjectId } from '@mikro-orm/mongodb';
import { Test, TestingModule } from '@nestjs/testing';
import { createCollections } from '@testing/create-collections';
Expand Down Expand Up @@ -168,4 +169,15 @@ describe('DatabaseManagementService', () => {
expect(spy).toHaveBeenCalled();
});
});

describe('When call migrationCreate()', () => {
it('should call migrator.createInitialMigration()', async () => {
const spyGetMigrator = jest.spyOn(orm, 'getMigrator');
const spyMigrator: DeepMocked<IMigrator> = createMock<IMigrator>();
spyGetMigrator.mockReturnValue(spyMigrator);

await service.migrationCreate();
expect(spyMigrator.createInitialMigration).toHaveBeenCalled();
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ export class DatabaseManagementService {
return connection;
}

getDatabaseCollection(collectionName: string): Collection {
public getDatabaseCollection(collectionName: string): Collection {
const collection = this.db.collection(collectionName);
return collection;
}

async importCollection(collectionName: string, jsonDocuments: unknown[]): Promise<number> {
public async importCollection(collectionName: string, jsonDocuments: unknown[]): Promise<number> {
if (jsonDocuments.length === 0) {
return 0;
}
Expand All @@ -31,64 +31,70 @@ export class DatabaseManagementService {
return insertedCount;
}

async findDocumentsOfCollection(collectionName: string): Promise<unknown[]> {
public async findDocumentsOfCollection(collectionName: string): Promise<unknown[]> {
const collection = this.getDatabaseCollection(collectionName);
const documents = (await collection.find({}).toArray()) as unknown[];
return documents;
}

async clearCollection(collectionName: string): Promise<number> {
public async clearCollection(collectionName: string): Promise<number> {
const collection = this.getDatabaseCollection(collectionName);
const { deletedCount } = await collection.deleteMany({});
return deletedCount || 0;
}

async getCollectionNames(): Promise<string[]> {
public async getCollectionNames(): Promise<string[]> {
const collections = (await this.db.listCollections(undefined, { nameOnly: true }).toArray()) as unknown[] as {
name: string;
}[];
const collectionNames = collections.map((collection) => collection.name);
return collectionNames;
}

async collectionExists(collectionName: string): Promise<boolean> {
public async collectionExists(collectionName: string): Promise<boolean> {
const collections = await this.getCollectionNames();
const result = collections.includes(collectionName);
return result;
}

async createCollection(collectionName: string): Promise<void> {
public async createCollection(collectionName: string): Promise<void> {
await this.db.createCollection(collectionName);
}

async dropCollection(collectionName: string): Promise<void> {
public async dropCollection(collectionName: string): Promise<void> {
await this.db.dropCollection(collectionName);
}

async syncIndexes(): Promise<void> {
return this.orm.getSchemaGenerator().ensureIndexes();
public async syncIndexes(): Promise<void> {
await this.orm.getSchemaGenerator().ensureIndexes();
}

async migrationUp(from?: string, to?: string, only?: string): Promise<void> {
public async migrationUp(from?: string, to?: string, only?: string): Promise<void> {
const migrator = this.orm.getMigrator();
const params = this.migrationParams(only, from, to);
await migrator.up(params);
}

async migrationDown(from?: string, to?: string, only?: string): Promise<void> {
public async migrationDown(from?: string, to?: string, only?: string): Promise<void> {
const migrator = this.orm.getMigrator();
const params = this.migrationParams(only, from, to);

await migrator.down(params);
}

async migrationPending(): Promise<UmzugMigration[]> {
public async migrationPending(): Promise<UmzugMigration[]> {
const migrator = this.orm.getMigrator();
const pendingMigrations = await migrator.getPendingMigrations();
return pendingMigrations;
}

private migrationParams(only?: string, from?: string, to?: string) {
public async migrationCreate(): Promise<void> {
const migrator = this.orm.getMigrator();

await migrator.createInitialMigration();
}

private migrationParams(only?: string, from?: string, to?: string): MigrateOptions {
const params: MigrateOptions = {};
if (only) {
params.migrations = [only];
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { createMock, DeepMocked } from '@golevelup/ts-jest';
import { Test, TestingModule } from '@nestjs/testing';
import { ConsoleWriterService } from '@infra/console';
import { Test, TestingModule } from '@nestjs/testing';
import { DatabaseManagementUc } from '../uc/database-management.uc';
import { DatabaseManagementConsole } from './database-management.console';

Expand Down Expand Up @@ -100,16 +100,25 @@ describe('DatabaseManagementConsole', () => {
expect(consoleInfoSpy).toHaveBeenCalledWith('migration up is completed');
expect(databaseManagementUc.migrationUp).toHaveBeenCalled();
});

it('should migrate down', async () => {
await service.migration({ down: true });
expect(consoleInfoSpy).toHaveBeenCalledWith('migration down is completed');
expect(databaseManagementUc.migrationDown).toHaveBeenCalled();
});

it('should check pending migrations', async () => {
await service.migration({ pending: true });
expect(consoleInfoSpy).toHaveBeenCalledWith(expect.stringContaining('Pending:'));
expect(databaseManagementUc.migrationPending).toHaveBeenCalled();
});

it('should check create migrations', async () => {
await service.migration({ create: true });
expect(consoleInfoSpy).toHaveBeenCalledWith('migration created');
expect(databaseManagementUc.migrationCreate).toHaveBeenCalled();
});

it('should no migrate if no param specified', async () => {
await service.migration({});
expect(consoleErrorSpy).toHaveBeenCalledWith('no migration option was given');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ interface MigrationOptions {
to?: string;
only?: string;
pending?: boolean;
create?: boolean;
}

@Console({ command: 'database', description: 'database setup console' })
Expand All @@ -37,7 +38,7 @@ export class DatabaseManagementConsole {
],
description: 'reset database collections with seed data from filesystem',
})
async seedCollections(options: Options): Promise<string[]> {
public async seedCollections(options: Options): Promise<string[]> {
const filter = options?.collection ? [options.collection] : undefined;

const collections = options.onlyfactories
Expand All @@ -64,7 +65,7 @@ export class DatabaseManagementConsole {
],
description: 'export database collections to filesystem',
})
async exportCollections(options: Options): Promise<string[]> {
public async exportCollections(options: Options): Promise<string[]> {
const filter = options?.collection ? [options.collection] : undefined;
const toSeedFolder = options?.override ? true : undefined;
const collections = await this.databaseManagementUc.exportCollectionsToFileSystem(filter, toSeedFolder);
Expand All @@ -78,7 +79,7 @@ export class DatabaseManagementConsole {
options: [],
description: 'sync indexes from nest and mikroorm',
})
async syncIndexes(): Promise<string> {
public async syncIndexes(): Promise<string> {
await this.databaseManagementUc.syncIndexes();
const report = 'sync of indexes is completed';
this.consoleWriter.info(report);
Expand Down Expand Up @@ -118,12 +119,17 @@ export class DatabaseManagementConsole {
required: false,
description: 'list pending migrations',
},
{
flags: '--create',
required: false,
description: 'create a new migration',
},
],
description: 'Execute MikroOrm migration up/down',
})
async migration(migrationOptions: MigrationOptions): Promise<string> {
public async migration(migrationOptions: MigrationOptions): Promise<string> {
let report = 'no migration option was given';
if (!migrationOptions.up && !migrationOptions.down && !migrationOptions.pending) {
if (!migrationOptions.up && !migrationOptions.down && !migrationOptions.pending && !migrationOptions.create) {
this.consoleWriter.error(report);
return report;
}
Expand All @@ -139,6 +145,10 @@ export class DatabaseManagementConsole {
const pendingMigrations = await this.databaseManagementUc.migrationPending();
report = `Pending: ${JSON.stringify(pendingMigrations.map((migration) => migration.name))}`;
}
if (migrationOptions.create) {
await this.databaseManagementUc.migrationCreate();
report = 'migration created';
}

this.consoleWriter.info(report);
return report;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -701,5 +701,10 @@ describe('DatabaseManagementService', () => {
await uc.migrationPending();
expect(dbService.migrationPending).toHaveBeenCalled();
});
it('should call migrationCreate', async () => {
jest.spyOn(dbService, 'migrationDown').mockImplementation();
await uc.migrationCreate();
expect(dbService.migrationCreate).toHaveBeenCalled();
});
});
});
38 changes: 22 additions & 16 deletions apps/server/src/modules/management/uc/database-management.uc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,15 @@ export class DatabaseManagementUc {
/**
* setup dir with json files
*/
private getSeedFolder() {
private getSeedFolder(): string {
return this.fileSystemAdapter.joinPath(this.baseDir, 'setup');
}

/**
* export folder name based on current date
* @returns
*/
private getTargetFolder(toSeedFolder?: boolean) {
private getTargetFolder(toSeedFolder?: boolean): string {
if (toSeedFolder === true) {
const targetFolder = this.getSeedFolder();
return targetFolder;
Expand Down Expand Up @@ -118,7 +118,7 @@ export class DatabaseManagementUc {
source: 'files' | 'database',
folder: string,
collectionNameFilter?: string[]
) {
): Promise<CollectionFilePath[]> {
let allCollectionsWithFilePaths: CollectionFilePath[] = [];

// load all available collections from source
Expand Down Expand Up @@ -151,7 +151,7 @@ export class DatabaseManagementUc {
return allCollectionsWithFilePaths;
}

private async dropCollectionIfExists(collectionName: string) {
private async dropCollectionIfExists(collectionName: string): Promise<void> {
const collectionExists = await this.databaseManagementService.collectionExists(collectionName);
if (collectionExists) {
// clear existing documents, if collection exists
Expand All @@ -162,7 +162,7 @@ export class DatabaseManagementUc {
}
}

async seedDatabaseCollectionsFromFactories(collections?: string[]): Promise<string[]> {
public async seedDatabaseCollectionsFromFactories(collections?: string[]): Promise<string[]> {
const promises = generateSeedData((s: string) => this.injectEnvVars(s))
.filter((data) => {
if (collections && collections.length > 0) {
Expand Down Expand Up @@ -266,7 +266,7 @@ export class DatabaseManagementUc {
* @param toSeedFolder optional override existing seed data files
* @returns the list of collection names exported
*/
async exportCollectionsToFileSystem(collections?: string[], toSeedFolder?: boolean): Promise<string[]> {
public async exportCollectionsToFileSystem(collections?: string[], toSeedFolder?: boolean): Promise<string[]> {
const targetFolder = this.getTargetFolder(toSeedFolder);
await this.fileSystemAdapter.createDir(targetFolder);
// detect collections to export based on database collections
Expand Down Expand Up @@ -301,7 +301,7 @@ export class DatabaseManagementUc {
/**
* Updates the indexes in the database based on definitions in entities
*/
async syncIndexes(): Promise<void> {
public async syncIndexes(): Promise<void> {
await this.createUserSearchIndex();
return this.databaseManagementService.syncIndexes();
}
Expand Down Expand Up @@ -357,7 +357,7 @@ export class DatabaseManagementUc {
return json;
}

private resolvePlaceholder(placeholder: string) {
private resolvePlaceholder(placeholder: string): string {
if (Configuration.has(placeholder)) {
return Configuration.get(placeholder) as string;
}
Expand All @@ -369,13 +369,13 @@ export class DatabaseManagementUc {
return '';
}

private encryptSecrets(collectionName: string, jsonDocuments: unknown[]) {
private encryptSecrets(collectionName: string, jsonDocuments: unknown[]): void {
if (collectionName === systemsCollectionName) {
this.encryptSecretsInSystems(jsonDocuments as SystemEntity[]);
}
}

private encryptSecretsInSystems(systems: SystemEntity[]) {
private encryptSecretsInSystems(systems: SystemEntity[]): SystemEntity[] {
systems.forEach((system) => {
if (system.oauthConfig) {
system.oauthConfig.clientSecret = this.defaultEncryptionService.encrypt(system.oauthConfig.clientSecret);
Expand All @@ -397,7 +397,7 @@ export class DatabaseManagementUc {
* Manual replacement with the intend placeholders or value is mandatory.
* Currently, this affects system and storageproviders collections.
*/
private removeSecrets(collectionName: string, jsonDocuments: unknown[]) {
private removeSecrets(collectionName: string, jsonDocuments: unknown[]): void {
if (collectionName === systemsCollectionName) {
this.removeSecretsFromSystems(jsonDocuments as SystemEntity[]);
}
Expand All @@ -406,14 +406,14 @@ export class DatabaseManagementUc {
}
}

private removeSecretsFromStorageproviders(storageProviders: StorageProviderEntity[]) {
private removeSecretsFromStorageproviders(storageProviders: StorageProviderEntity[]): void {
storageProviders.forEach((storageProvider) => {
storageProvider.accessKeyId = defaultSecretReplacementHintText;
storageProvider.secretAccessKey = defaultSecretReplacementHintText;
});
}

private removeSecretsFromSystems(systems: SystemEntity[]) {
private removeSecretsFromSystems(systems: SystemEntity[]): SystemEntity[] {
systems.forEach((system) => {
if (system.oauthConfig) {
system.oauthConfig.clientSecret = defaultSecretReplacementHintText;
Expand All @@ -428,16 +428,22 @@ export class DatabaseManagementUc {
return systems;
}

public async migrationCreate(): Promise<void> {
await this.databaseManagementService.migrationCreate();
}

public async migrationUp(from?: string, to?: string, only?: string): Promise<void> {
return this.databaseManagementService.migrationUp(from, to, only);
await this.databaseManagementService.migrationUp(from, to, only);
}

public async migrationDown(from?: string, to?: string, only?: string): Promise<void> {
return this.databaseManagementService.migrationDown(from, to, only);
await this.databaseManagementService.migrationDown(from, to, only);
}

public async migrationPending(): Promise<UmzugMigration[]> {
return this.databaseManagementService.migrationPending();
const result = await this.databaseManagementService.migrationPending();

return result;
}

public encryptPlainText(plainText: string): string {
Expand Down
Loading
Loading