diff --git a/apps/server/src/apps/deletion-console.app.ts b/apps/server/src/apps/deletion-console.app.ts index d80448a4427..cd1cdeeb7f4 100644 --- a/apps/server/src/apps/deletion-console.app.ts +++ b/apps/server/src/apps/deletion-console.app.ts @@ -1,5 +1,5 @@ /* istanbul ignore file */ -import { DeletionConsoleModule } from '@src/modules/deletion-console'; +import { DeletionConsoleModule } from '@modules/deletion-console/deletion-console.app.module'; import { BootstrapConsole } from 'nestjs-console'; async function run() { diff --git a/apps/server/src/apps/idp-console.app.ts b/apps/server/src/apps/idp-console.app.ts index da523496526..e0f4d777b2f 100644 --- a/apps/server/src/apps/idp-console.app.ts +++ b/apps/server/src/apps/idp-console.app.ts @@ -1,5 +1,5 @@ /* istanbul ignore file */ -import { IdpConsoleModule } from '@modules/idp-console'; +import { IdpConsoleModule } from '@modules/idp-console/idp-console.app.module'; import { BootstrapConsole } from 'nestjs-console'; async function run() { diff --git a/apps/server/src/infra/console/console-writer.config.ts b/apps/server/src/infra/console/console-writer.config.ts new file mode 100644 index 00000000000..6c7a031b7f3 --- /dev/null +++ b/apps/server/src/infra/console/console-writer.config.ts @@ -0,0 +1,2 @@ +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface ConsoleWriterConfig {} diff --git a/apps/server/src/infra/console/index.ts b/apps/server/src/infra/console/index.ts index 4d8ddbe3234..064512d2782 100644 --- a/apps/server/src/infra/console/index.ts +++ b/apps/server/src/infra/console/index.ts @@ -1 +1,2 @@ -export * from './console-writer'; +export { ConsoleWriterModule, ConsoleWriterService } from './console-writer'; +export { ConsoleWriterConfig } from './console-writer.config'; diff --git a/apps/server/src/infra/rabbitmq/index.ts b/apps/server/src/infra/rabbitmq/index.ts index 53e075c9516..46581047feb 100644 --- a/apps/server/src/infra/rabbitmq/index.ts +++ b/apps/server/src/infra/rabbitmq/index.ts @@ -1,6 +1,13 @@ export * from './error.mapper'; export * from './exchange'; -export * from './rabbitmq.config'; +export { + RabbitMqConfig, + FilesPreviewExchange, + FilesStorageExchange, + MailSendExchange, + AntivirusExchange, + RabbitMqURI, +} from './rabbitmq.config'; export * from './rabbitmq.module'; export * from './rpc-message'; export * from './rpc-message-producer'; diff --git a/apps/server/src/infra/rabbitmq/rabbitmq.config.ts b/apps/server/src/infra/rabbitmq/rabbitmq.config.ts index f5eee733deb..e3491f8c976 100644 --- a/apps/server/src/infra/rabbitmq/rabbitmq.config.ts +++ b/apps/server/src/infra/rabbitmq/rabbitmq.config.ts @@ -1,7 +1,11 @@ import { Configuration } from '@hpi-schul-cloud/commons/lib'; +// should be move to config interface and added reading on places that use the module export const FilesPreviewExchange = Configuration.get('FILES_STORAGE__EXCHANGE') as string; export const FilesStorageExchange = Configuration.get('FILES_STORAGE__EXCHANGE') as string; export const MailSendExchange = Configuration.get('MAIL_SEND_EXCHANGE') as string; export const AntivirusExchange = Configuration.get('ANTIVIRUS_EXCHANGE') as string; export const RabbitMqURI = Configuration.get('RABBITMQ_URI') as string; + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface RabbitMqConfig {} diff --git a/apps/server/src/modules/board/board-collaboration.module.ts b/apps/server/src/modules/board/board-collaboration.module.ts index 4bdd0b0180e..84354cdfa19 100644 --- a/apps/server/src/modules/board/board-collaboration.module.ts +++ b/apps/server/src/modules/board/board-collaboration.module.ts @@ -1,12 +1,12 @@ import { AuthGuardModule, AuthGuardOptions } from '@infra/auth-guard'; import { MikroOrmModule } from '@mikro-orm/nestjs'; -import { defaultMikroOrmOptions } from '@modules/server'; import { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { ALL_ENTITIES } from '@shared/domain/entity'; import { createConfigModuleOptions, DB_PASSWORD, DB_URL, DB_USERNAME } from '@src/config'; import { CoreModule } from '@src/core'; import { RabbitMQWrapperModule } from '@src/infra/rabbitmq'; +import { defaultMikroOrmOptions } from '@shared/common'; import { AuthorizationModule } from '../authorization'; import { config } from './board-collaboration.config'; import { BoardWsApiModule } from './board-ws-api.module'; diff --git a/apps/server/src/modules/board/board-collaboration.testing.module.ts b/apps/server/src/modules/board/board-collaboration.testing.module.ts index 68f7c038104..4f0e78deed7 100644 --- a/apps/server/src/modules/board/board-collaboration.testing.module.ts +++ b/apps/server/src/modules/board/board-collaboration.testing.module.ts @@ -1,4 +1,4 @@ -import { defaultMikroOrmOptions, serverConfig } from '@modules/server'; +import { serverConfig } from '@modules/server'; import { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { ALL_ENTITIES } from '@shared/domain/entity'; @@ -7,6 +7,7 @@ import { CoreModule } from '@src/core'; import { AuthGuardModule, AuthGuardOptions } from '@src/infra/auth-guard'; import { MongoMemoryDatabaseModule } from '@src/infra/database'; import { RabbitMQWrapperTestModule } from '@src/infra/rabbitmq'; +import { defaultMikroOrmOptions } from '@shared/common'; import { AuthenticationApiModule } from '../authentication/authentication-api.module'; import { AuthorizationModule } from '../authorization'; import { config as boardCollaborationConfig } from './board-collaboration.config'; diff --git a/apps/server/src/modules/common-cartridge/common-cartridge.module.ts b/apps/server/src/modules/common-cartridge/common-cartridge.module.ts index babe99d04d3..82f59b03f1d 100644 --- a/apps/server/src/modules/common-cartridge/common-cartridge.module.ts +++ b/apps/server/src/modules/common-cartridge/common-cartridge.module.ts @@ -5,7 +5,7 @@ import { Module } from '@nestjs/common'; import { ALL_ENTITIES } from '@shared/domain/entity'; import { DB_PASSWORD, DB_URL, DB_USERNAME } from '@src/config'; import { RabbitMQWrapperModule } from '@src/infra/rabbitmq'; -import { defaultMikroOrmOptions } from '../server'; +import { defaultMikroOrmOptions } from '@shared/common'; import { BoardClientModule } from './common-cartridge-client/board-client'; import { CoursesClientModule } from './common-cartridge-client/course-client'; import { CommonCartridgeExportService } from './service/common-cartridge-export.service'; diff --git a/apps/server/src/modules/deletion-console/deletion-client/deletion-client.config.spec.ts b/apps/server/src/modules/deletion-console/deletion-client/deletion-client.config.spec.ts deleted file mode 100644 index a3cae21e425..00000000000 --- a/apps/server/src/modules/deletion-console/deletion-client/deletion-client.config.spec.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { IConfig } from '@hpi-schul-cloud/commons/lib/interfaces/IConfig'; -import { Configuration } from '@hpi-schul-cloud/commons/lib'; -import { DeletionClientConfig } from './interface'; -import { getDeletionClientConfig } from './deletion-client.config'; - -describe(getDeletionClientConfig.name, () => { - let configBefore: IConfig; - - beforeAll(() => { - configBefore = Configuration.toObject({ plainSecrets: true }); - }); - - afterEach(() => { - Configuration.reset(configBefore); - }); - - describe('when called', () => { - const setup = () => { - const baseUrl = 'http://api-admin:4030'; - const apiKey = '652559c2-93da-42ad-94e1-640e3afbaca0'; - - Configuration.set('ADMIN_API_CLIENT__BASE_URL', baseUrl); - Configuration.set('ADMIN_API_CLIENT__API_KEY', apiKey); - - const expectedConfig: DeletionClientConfig = { - ADMIN_API_CLIENT_BASE_URL: baseUrl, - ADMIN_API_CLIENT_API_KEY: apiKey, - }; - - return { expectedConfig }; - }; - - it('should return config with proper values', () => { - const { expectedConfig } = setup(); - - const config = getDeletionClientConfig(); - - expect(config).toEqual(expectedConfig); - }); - }); -}); diff --git a/apps/server/src/modules/deletion-console/deletion-client/deletion-client.config.ts b/apps/server/src/modules/deletion-console/deletion-client/deletion-client.config.ts deleted file mode 100644 index db5bf7ff226..00000000000 --- a/apps/server/src/modules/deletion-console/deletion-client/deletion-client.config.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Configuration } from '@hpi-schul-cloud/commons/lib'; -import { DeletionClientConfig } from './interface'; - -export const getDeletionClientConfig = (): DeletionClientConfig => { - return { - ADMIN_API_CLIENT_BASE_URL: Configuration.get('ADMIN_API_CLIENT__BASE_URL') as string, - ADMIN_API_CLIENT_API_KEY: Configuration.get('ADMIN_API_CLIENT__API_KEY') as string, - }; -}; diff --git a/apps/server/src/modules/deletion-console/deletion-client/deletion.client.spec.ts b/apps/server/src/modules/deletion-console/deletion-client/deletion.client.spec.ts index 478f23a2348..c63efcb4d9d 100644 --- a/apps/server/src/modules/deletion-console/deletion-client/deletion.client.spec.ts +++ b/apps/server/src/modules/deletion-console/deletion-client/deletion.client.spec.ts @@ -8,29 +8,22 @@ import { axiosResponseFactory } from '@shared/testing'; import { DeletionRequestInputBuilder, DeletionRequestOutputBuilder } from '.'; import { DeletionRequestOutput } from './interface'; import { DeletionClient } from './deletion.client'; +import { DeletionConsoleConfig } from '../deletion.config'; describe(DeletionClient.name, () => { let module: TestingModule; let client: DeletionClient; + let configService: DeepMocked; let httpService: DeepMocked; beforeEach(async () => { module = await Test.createTestingModule({ providers: [ - DeletionClient, { provide: ConfigService, - useValue: createMock({ - get: jest.fn((key: string) => { - if (key === 'ADMIN_API_CLIENT_BASE_URL') { - return 'http://localhost:4030'; - } - - // Default is for the Admin APIs API Key. - return '6b3df003-61e9-467c-9e6b-579634801896'; - }), - }), + useValue: createMock>(), }, + DeletionClient, { provide: HttpService, useValue: createMock(), @@ -39,6 +32,7 @@ describe(DeletionClient.name, () => { }).compile(); client = module.get(DeletionClient); + configService = module.get(ConfigService); httpService = module.get(HttpService); }); @@ -50,9 +44,17 @@ describe(DeletionClient.name, () => { await module.close(); }); + const setupConfig = () => { + // Please take a look that the order is correct if the code order is changed + configService.get + .mockReturnValueOnce('652559c2-93da-42ad-94e1-640e3afbaca0') + .mockReturnValueOnce('http://api-admin:4030'); + }; + describe('queueDeletionRequest', () => { describe('when sending the HTTP request failed', () => { const setup = () => { + setupConfig(); const input = DeletionRequestInputBuilder.build('user', '652f1625e9bc1a13bdaae48b'); const error = new Error('unknown error'); @@ -70,6 +72,7 @@ describe(DeletionClient.name, () => { describe('when received valid response with expected HTTP status code', () => { const setup = () => { + setupConfig(); const input = DeletionRequestInputBuilder.build('user', '652f1625e9bc1a13bdaae48b'); const output: DeletionRequestOutput = DeletionRequestOutputBuilder.build( @@ -98,6 +101,7 @@ describe(DeletionClient.name, () => { describe('when received invalid HTTP status code in a response', () => { const setup = () => { + setupConfig(); const input = DeletionRequestInputBuilder.build('user', '652f1625e9bc1a13bdaae48b'); const output: DeletionRequestOutput = DeletionRequestOutputBuilder.build('', new Date()); @@ -121,6 +125,7 @@ describe(DeletionClient.name, () => { describe('when received no requestId in a response', () => { const setup = () => { + setupConfig(); const input = DeletionRequestInputBuilder.build('user', '652f1625e9bc1a13bdaae48b'); const output: DeletionRequestOutput = DeletionRequestOutputBuilder.build( @@ -147,6 +152,7 @@ describe(DeletionClient.name, () => { describe('when received no deletionPlannedAt in a response', () => { const setup = () => { + setupConfig(); const input = DeletionRequestInputBuilder.build('user', '652f1625e9bc1a13bdaae48b'); const response: AxiosResponse = axiosResponseFactory.build({ @@ -172,6 +178,7 @@ describe(DeletionClient.name, () => { describe('executeDeletions', () => { describe('when sending the HTTP request failed', () => { const setup = () => { + setupConfig(); const error = new Error('unknown error'); httpService.post.mockReturnValueOnce(throwError(() => error)); }; @@ -185,6 +192,7 @@ describe(DeletionClient.name, () => { describe('when received valid response with expected HTTP status code', () => { const setup = () => { + setupConfig(); const limit = 10; const response: AxiosResponse = axiosResponseFactory.build({ @@ -203,8 +211,31 @@ describe(DeletionClient.name, () => { }); }); + describe('when pass invalid limit', () => { + const setup = () => { + setupConfig(); + const limit = true; + + const response: AxiosResponse = axiosResponseFactory.build({ + status: 204, + }); + + httpService.post.mockReturnValueOnce(of(response)); + + return { limit }; + }; + + it('should ignore limit and use default headers', async () => { + const { limit } = setup(); + + // @ts-expect-error test case + await expect(client.executeDeletions(limit)).resolves.not.toThrow(); + }); + }); + describe('when received invalid HTTP status code in a response', () => { const setup = () => { + setupConfig(); const response: AxiosResponse = axiosResponseFactory.build({ status: 200, }); diff --git a/apps/server/src/modules/deletion-console/deletion-client/deletion.client.ts b/apps/server/src/modules/deletion-console/deletion-client/deletion.client.ts index 598b6cf271f..374169050cc 100644 --- a/apps/server/src/modules/deletion-console/deletion-client/deletion.client.ts +++ b/apps/server/src/modules/deletion-console/deletion-client/deletion.client.ts @@ -3,101 +3,98 @@ import { Injectable, InternalServerErrorException } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { ErrorUtils } from '@src/core/error/utils'; import { firstValueFrom } from 'rxjs'; -import { DeletionClientConfig, DeletionRequestInput, DeletionRequestOutput } from './interface'; +import { AxiosResponse, HttpStatusCode } from 'axios'; +import { DeletionRequestInput, DeletionRequestOutput } from './interface'; +import { DeletionConsoleConfig } from '../deletion.config'; @Injectable() export class DeletionClient { - private readonly baseUrl: string; - - private readonly apiKey: string; - - private readonly postDeletionRequestsEndpoint: string; - - private readonly postDeletionExecutionsEndpoint: string; - constructor( private readonly httpService: HttpService, - private readonly configService: ConfigService - ) { - this.baseUrl = this.configService.get('ADMIN_API_CLIENT_BASE_URL'); - this.apiKey = this.configService.get('ADMIN_API_CLIENT_API_KEY'); - - // Prepare the POST /deletionRequests endpoint beforehand to not do it on every client call. - this.postDeletionRequestsEndpoint = new URL('/admin/api/v1/deletionRequests', this.baseUrl).toString(); - this.postDeletionExecutionsEndpoint = new URL('/admin/api/v1/deletionExecutions', this.baseUrl).toString(); + private readonly configService: ConfigService + ) {} + + public async queueDeletionRequest(input: DeletionRequestInput): Promise { + try { + const response = await this.postDeletionRequest(input); + this.checkResponseStatusCode(response, HttpStatusCode.Accepted); + this.checkDeletionRequestResponseData(response); + + return response.data; + } catch (err) { + throw this.createError(err, 'queueDeletionRequest'); + } } - async queueDeletionRequest(input: DeletionRequestInput): Promise { + public async executeDeletions(limit?: number): Promise { try { - const request = this.httpService.post( - this.postDeletionRequestsEndpoint, - input, - this.defaultHeaders() - ); + const response = await this.postDeletionExecutionRequest(limit); + this.checkResponseStatusCode(response, HttpStatusCode.NoContent); + } catch (err) { + throw this.createError(err, 'executeDeletions'); + } + } - const resp = await firstValueFrom(request); + private async postDeletionRequest(input: DeletionRequestInput): Promise> { + const headers = this.createDefaultHeaders(); + const baseUrl = this.configService.get('ADMIN_API_CLIENT_BASE_URL', { infer: true }); + const postDeletionRequestsEndpoint = new URL('/admin/api/v1/deletionRequests', baseUrl).toString(); - // Throw an error if any other status code (other than expected "202 Accepted" is returned). - if (resp.status !== 202) { - throw new Error(`invalid HTTP status code in a response from the server - ${resp.status} instead of 202`); - } + const request = this.httpService.post(postDeletionRequestsEndpoint, input, headers); + const response = await firstValueFrom(request); - // Throw an error if server didn't return a requestId in a response (and it is - // required as it gives client the reference to the created deletion request). - if (!resp.data.requestId) { - throw new Error('no valid requestId returned from the server'); - } + return response; + } - // Throw an error if server didn't return a deletionPlannedAt timestamp so the user - // will not be aware after which date the deletion request's execution will begin. - if (!resp.data.deletionPlannedAt) { - throw Error('no valid deletionPlannedAt returned from the server'); - } + private async postDeletionExecutionRequest(limit?: number): Promise> { + const defaultHeaders = this.createDefaultHeaders(); + const headers = this.isLimitGeaterZero(limit) ? { ...defaultHeaders, params: { limit } } : defaultHeaders; + const baseUrl = this.configService.get('ADMIN_API_CLIENT_BASE_URL', { infer: true }); + const postDeletionExecutionsEndpoint = new URL('/admin/api/v1/deletionExecutions', baseUrl).toString(); - return resp.data; - } catch (err) { - // Throw an error if sending deletion request has failed. - throw new InternalServerErrorException( - null, - ErrorUtils.createHttpExceptionOptions(err, 'DeletionClient:queueDeletionRequest') - ); - } + const request = this.httpService.post(postDeletionExecutionsEndpoint, null, headers); + const response = await firstValueFrom(request); + + return response; } - async executeDeletions(limit?: number): Promise { - let requestConfig = {}; + private createDefaultHeaders() { + const apiKey = this.configService.get('ADMIN_API_CLIENT_API_KEY', { infer: true }); - if (limit && limit > 0) { - requestConfig = { ...this.defaultHeaders(), params: { limit } }; - } else { - requestConfig = { ...this.defaultHeaders() }; - } + return { + headers: { 'X-Api-Key': apiKey }, + }; + } - try { - const request = this.httpService.post(this.postDeletionExecutionsEndpoint, null, requestConfig); + private checkDeletionRequestResponseData(response: AxiosResponse): void { + // It is required as it gives client the reference to the created deletion request. + if (!response.data.requestId) { + throw new Error('no valid requestId returned from the server'); + } - const resp = await firstValueFrom(request); + // Throw an error if server didn't return a deletionPlannedAt timestamp so the user + // will not be aware after which date the deletion request's execution will begin. + if (!response.data.deletionPlannedAt) { + throw Error('no valid deletionPlannedAt returned from the server'); + } + } - if (resp.status !== 204) { - // Throw an error if any other status code (other than expected "204 No Content" is returned). - throw new Error(`invalid HTTP status code in a response from the server - ${resp.status} instead of 204`); - } - } catch (err) { - // Throw an error if sending deletion request(s) execution trigger has failed. - throw new InternalServerErrorException( - null, - ErrorUtils.createHttpExceptionOptions(err, 'DeletionClient:executeDeletions') + private checkResponseStatusCode(response: AxiosResponse, expectedStatusCode: HttpStatusCode): void { + if (response.status !== expectedStatusCode) { + throw new Error( + `Invalid HTTP status code in a response from the server - ${response.status} instead of ${expectedStatusCode}.` ); } } - private apiKeyHeader() { - return { 'X-Api-Key': this.apiKey }; + private isLimitGeaterZero(limit?: number): boolean { + return typeof limit === 'number' && limit > 0; } - private defaultHeaders() { - return { - headers: this.apiKeyHeader(), - }; + private createError(err: unknown, scope: string): Error { + return new InternalServerErrorException( + null, + ErrorUtils.createHttpExceptionOptions(err, `DeletionClient:${scope}`) + ); } } diff --git a/apps/server/src/modules/deletion-console/deletion-client/interface/deletion-client-config.interface.ts b/apps/server/src/modules/deletion-console/deletion-client/interface/deletion-client-config.interface.ts deleted file mode 100644 index 8178515b6d4..00000000000 --- a/apps/server/src/modules/deletion-console/deletion-client/interface/deletion-client-config.interface.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface DeletionClientConfig { - ADMIN_API_CLIENT_BASE_URL: string; - ADMIN_API_CLIENT_API_KEY: string; -} diff --git a/apps/server/src/modules/deletion-console/deletion-client/interface/index.ts b/apps/server/src/modules/deletion-console/deletion-client/interface/index.ts index 38f0f639731..11d38082a0e 100644 --- a/apps/server/src/modules/deletion-console/deletion-client/interface/index.ts +++ b/apps/server/src/modules/deletion-console/deletion-client/interface/index.ts @@ -1,4 +1,3 @@ -export * from './deletion-client-config.interface'; export * from './deletion-request-target-ref-input.interface'; export * from './deletion-request-input.interface'; export * from './deletion-request-output.interface'; diff --git a/apps/server/src/modules/deletion-console/deletion-console.module.ts b/apps/server/src/modules/deletion-console/deletion-console.app.module.ts similarity index 87% rename from apps/server/src/modules/deletion-console/deletion-console.module.ts rename to apps/server/src/modules/deletion-console/deletion-console.app.module.ts index 8cab979dc6f..17ec04df8c4 100644 --- a/apps/server/src/modules/deletion-console/deletion-console.module.ts +++ b/apps/server/src/modules/deletion-console/deletion-console.app.module.ts @@ -7,22 +7,22 @@ import { ConsoleWriterModule } from '@infra/console'; import { UserModule } from '@modules/user'; import { ALL_ENTITIES } from '@shared/domain/entity'; import { DB_PASSWORD, DB_URL, DB_USERNAME, createConfigModuleOptions } from '@src/config'; -import { defaultMikroOrmOptions } from '@modules/server'; import { AccountModule } from '@modules/account'; -import { getDeletionClientConfig } from './deletion-client/deletion-client.config'; +import { defaultMikroOrmOptions } from '@shared/common'; import { FileEntity } from '../files/entity'; import { DeletionClient } from './deletion-client'; import { DeletionQueueConsole } from './deletion-queue.console'; import { BatchDeletionUc, DeletionExecutionUc } from './uc'; import { BatchDeletionService } from './services'; import { DeletionExecutionConsole } from './deletion-execution.console'; +import { deletionConsoleConfig } from './deletion.config'; @Module({ imports: [ ConsoleModule, ConsoleWriterModule, UserModule, - ConfigModule.forRoot(createConfigModuleOptions(getDeletionClientConfig)), + ConfigModule.forRoot(createConfigModuleOptions(deletionConsoleConfig)), MikroOrmModule.forRoot({ ...defaultMikroOrmOptions, type: 'mongo', diff --git a/apps/server/src/modules/deletion-console/deletion-execution.console.spec.ts b/apps/server/src/modules/deletion-console/deletion-execution.console.spec.ts index 0d4ec7b5743..e5374710405 100644 --- a/apps/server/src/modules/deletion-console/deletion-execution.console.spec.ts +++ b/apps/server/src/modules/deletion-console/deletion-execution.console.spec.ts @@ -1,9 +1,11 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { createMock, DeepMocked } from '@golevelup/ts-jest'; -import { ConsoleWriterService } from '@infra/console'; +import { DeepMocked } from '@golevelup/ts-jest'; +import { MongoMemoryDatabaseModule } from '@infra/database'; +import { defaultMikroOrmOptions } from '@shared/common'; import { DeletionExecutionConsole } from './deletion-execution.console'; import { DeletionExecutionTriggerResultBuilder, TriggerDeletionExecutionOptionsBuilder } from './builder'; import { DeletionExecutionUc } from './uc'; +import { DeletionConsoleModule } from './deletion-console.app.module'; describe(DeletionExecutionConsole.name, () => { let module: TestingModule; @@ -12,17 +14,7 @@ describe(DeletionExecutionConsole.name, () => { beforeAll(async () => { module = await Test.createTestingModule({ - providers: [ - DeletionExecutionConsole, - { - provide: ConsoleWriterService, - useValue: createMock(), - }, - { - provide: DeletionExecutionUc, - useValue: createMock(), - }, - ], + imports: [DeletionConsoleModule, MongoMemoryDatabaseModule.forRoot({ ...defaultMikroOrmOptions })], }).compile(); console = module.get(DeletionExecutionConsole); @@ -67,7 +59,6 @@ describe(DeletionExecutionConsole.name, () => { const options = TriggerDeletionExecutionOptionsBuilder.build(1000); deletionExecutionUc.triggerDeletionExecution.mockResolvedValueOnce(undefined); - const spy = jest.spyOn(DeletionExecutionTriggerResultBuilder, 'buildSuccess'); return { options, spy }; @@ -85,13 +76,9 @@ describe(DeletionExecutionConsole.name, () => { describe(`when ${DeletionExecutionUc.name}'s triggerDeletionExecution() method throws an exception`, () => { const setup = () => { const options = TriggerDeletionExecutionOptionsBuilder.build(1000); - const err = new Error('some error occurred...'); deletionExecutionUc.triggerDeletionExecution.mockRejectedValueOnce(err); - - // const spy = jest.spyOn(ErrorMapper, 'mapRpcErrorResponseToDomainError'); - const spy = jest.spyOn(DeletionExecutionTriggerResultBuilder, 'buildFailure'); return { options, err, spy }; diff --git a/apps/server/src/modules/deletion-console/deletion-queue.console.spec.ts b/apps/server/src/modules/deletion-console/deletion-queue.console.spec.ts index df3602a80d2..fe5789db379 100644 --- a/apps/server/src/modules/deletion-console/deletion-queue.console.spec.ts +++ b/apps/server/src/modules/deletion-console/deletion-queue.console.spec.ts @@ -1,11 +1,13 @@ import { ObjectId } from 'bson'; +import fs from 'fs'; import { Test, TestingModule } from '@nestjs/testing'; -import { ConsoleWriterService } from '@infra/console'; -import { createMock } from '@golevelup/ts-jest'; +import { MongoMemoryDatabaseModule } from '@infra/database'; +import { defaultMikroOrmOptions } from '@shared/common'; import { DeletionQueueConsole } from './deletion-queue.console'; import { PushDeleteRequestsOptionsBuilder } from './builder'; import { BatchDeletionUc } from './uc'; import { UnsyncedEntitiesOptionsBuilder } from './builder/unsynced-entities-options.builder'; +import { DeletionConsoleModule } from './deletion-console.app.module'; describe(DeletionQueueConsole.name, () => { let module: TestingModule; @@ -14,17 +16,7 @@ describe(DeletionQueueConsole.name, () => { beforeAll(async () => { module = await Test.createTestingModule({ - providers: [ - DeletionQueueConsole, - { - provide: ConsoleWriterService, - useValue: createMock(), - }, - { - provide: BatchDeletionUc, - useValue: createMock(), - }, - ], + imports: [DeletionConsoleModule, MongoMemoryDatabaseModule.forRoot({ ...defaultMikroOrmOptions })], }).compile(); console = module.get(DeletionQueueConsole); @@ -46,6 +38,7 @@ describe(DeletionQueueConsole.name, () => { describe('pushDeletionRequests', () => { describe('when called with valid options', () => { const setup = () => { + jest.spyOn(fs, 'readFileSync').mockReturnValueOnce(''); const refsFilePath = '/tmp/ids.txt'; const targetRefDomain = 'school'; const deleteInMinutes = 43200; diff --git a/apps/server/src/modules/deletion-console/deletion.config.ts b/apps/server/src/modules/deletion-console/deletion.config.ts new file mode 100644 index 00000000000..36e7349f80e --- /dev/null +++ b/apps/server/src/modules/deletion-console/deletion.config.ts @@ -0,0 +1,29 @@ +import { ConsoleWriterConfig } from '@infra/console'; +import { UserConfig } from '@modules/user'; +import { AccountConfig } from '@modules/account'; +import { Configuration } from '@hpi-schul-cloud/commons'; +import { LanguageType } from '@shared/domain/interface'; + +export interface DeletionConsoleConfig extends ConsoleWriterConfig, UserConfig, AccountConfig { + ADMIN_API_CLIENT_BASE_URL: string; + ADMIN_API_CLIENT_API_KEY: string; +} + +const config: DeletionConsoleConfig = { + ADMIN_API_CLIENT_BASE_URL: Configuration.get('ADMIN_API_CLIENT__BASE_URL') as string, + ADMIN_API_CLIENT_API_KEY: Configuration.get('ADMIN_API_CLIENT__API_KEY') as string, + AVAILABLE_LANGUAGES: (Configuration.get('I18N__AVAILABLE_LANGUAGES') as string).split(',') as LanguageType[], + TEACHER_VISIBILITY_FOR_EXTERNAL_TEAM_INVITATION: Configuration.get( + 'TEACHER_VISIBILITY_FOR_EXTERNAL_TEAM_INVITATION' + ) as string, + LOGIN_BLOCK_TIME: Configuration.get('LOGIN_BLOCK_TIME') as number, + TEACHER_STUDENT_VISIBILITY__IS_CONFIGURABLE: Configuration.get( + 'TEACHER_STUDENT_VISIBILITY__IS_CONFIGURABLE' + ) as boolean, + FEATURE_IDENTITY_MANAGEMENT_LOGIN_ENABLED: Configuration.get('FEATURE_IDENTITY_MANAGEMENT_LOGIN_ENABLED') as boolean, + FEATURE_IDENTITY_MANAGEMENT_STORE_ENABLED: Configuration.get('FEATURE_IDENTITY_MANAGEMENT_STORE_ENABLED') as boolean, + NEST_LOG_LEVEL: Configuration.get('NEST_LOG_LEVEL') as string, + EXIT_ON_ERROR: Configuration.get('EXIT_ON_ERROR') as boolean, +}; + +export const deletionConsoleConfig = () => config; diff --git a/apps/server/src/modules/deletion-console/index.ts b/apps/server/src/modules/deletion-console/index.ts index db47bd8c99f..6ace271ede5 100644 --- a/apps/server/src/modules/deletion-console/index.ts +++ b/apps/server/src/modules/deletion-console/index.ts @@ -1 +1,3 @@ -export * from './deletion-console.module'; +/** ************************************************* + * Do not re-export here! File should be empty! * + ************************************************** */ diff --git a/apps/server/src/modules/files-storage/files-storage-test.module.ts b/apps/server/src/modules/files-storage/files-storage-test.module.ts index 8fc04306de4..1c78abce3c0 100644 --- a/apps/server/src/modules/files-storage/files-storage-test.module.ts +++ b/apps/server/src/modules/files-storage/files-storage-test.module.ts @@ -1,10 +1,10 @@ import { DynamicModule, Module } from '@nestjs/common'; - import { MongoDatabaseModuleOptions, MongoMemoryDatabaseModule } from '@infra/database'; import { RabbitMQWrapperTestModule } from '@infra/rabbitmq'; import { ALL_ENTITIES } from '@shared/domain/entity'; import { CoreModule } from '@src/core'; import { LoggerModule } from '@src/core/logger'; +import { defaultMikroOrmOptions } from '@shared/common'; import { FileRecord } from './entity'; import { FilesStorageApiModule } from './files-storage-api.module'; @@ -26,7 +26,7 @@ export class FilesStorageTestModule { static forRoot(options?: MongoDatabaseModuleOptions): DynamicModule { return { module: FilesStorageTestModule, - imports: [...imports, MongoMemoryDatabaseModule.forRoot({ ...options })], + imports: [...imports, MongoMemoryDatabaseModule.forRoot({ ...defaultMikroOrmOptions, ...options })], controllers, providers, }; diff --git a/apps/server/src/modules/files-storage/files-storage.module.ts b/apps/server/src/modules/files-storage/files-storage.module.ts index f6d440130e0..a4590a91ce4 100644 --- a/apps/server/src/modules/files-storage/files-storage.module.ts +++ b/apps/server/src/modules/files-storage/files-storage.module.ts @@ -3,12 +3,12 @@ import { AntivirusModule } from '@infra/antivirus'; import { PreviewGeneratorProducerModule } from '@infra/preview-generator'; import { RabbitMQWrapperModule } from '@infra/rabbitmq'; import { S3ClientModule } from '@infra/s3-client'; -import { Dictionary, IPrimaryKey } from '@mikro-orm/core'; -import { MikroOrmModule, MikroOrmModuleSyncOptions } from '@mikro-orm/nestjs'; -import { Module, NotFoundException } from '@nestjs/common'; +import { MikroOrmModule } from '@mikro-orm/nestjs'; +import { Module } from '@nestjs/common'; import { ALL_ENTITIES } from '@shared/domain/entity'; import { DB_PASSWORD, DB_URL, DB_USERNAME } from '@src/config'; import { LoggerModule } from '@src/core/logger'; +import { defaultMikroOrmOptions } from '@shared/common'; import { FileRecord, FileRecordSecurityCheck } from './entity'; import { s3Config } from './files-storage.config'; import { FileRecordRepo } from './repo'; @@ -29,12 +29,6 @@ const imports = [ ]; const providers = [FilesStorageService, PreviewService, FileRecordRepo]; -const defaultMikroOrmOptions: MikroOrmModuleSyncOptions = { - findOneOrFailHandler: (entityName: string, where: Dictionary | IPrimaryKey) => - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - new NotFoundException(`The requested ${entityName}: ${where} has not been found.`), -}; - @Module({ imports: [ ...imports, diff --git a/apps/server/src/modules/fwu-learning-contents/fwu-learning-contents.module.ts b/apps/server/src/modules/fwu-learning-contents/fwu-learning-contents.module.ts index 01f90028efd..cf37ee653a9 100644 --- a/apps/server/src/modules/fwu-learning-contents/fwu-learning-contents.module.ts +++ b/apps/server/src/modules/fwu-learning-contents/fwu-learning-contents.module.ts @@ -1,28 +1,22 @@ import { AuthGuardModule, AuthGuardOptions } from '@infra/auth-guard'; import { RabbitMQWrapperModule } from '@infra/rabbitmq'; import { S3ClientModule } from '@infra/s3-client'; -import { Dictionary, IPrimaryKey } from '@mikro-orm/core'; -import { MikroOrmModule, MikroOrmModuleSyncOptions } from '@mikro-orm/nestjs'; +import { MikroOrmModule } from '@mikro-orm/nestjs'; import { AuthorizationModule } from '@modules/authorization'; import { SystemEntity } from '@modules/system/entity'; import { HttpModule } from '@nestjs/axios'; -import { Module, NotFoundException } from '@nestjs/common'; +import { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { Role, SchoolEntity, SchoolYearEntity, User } from '@shared/domain/entity'; import { createConfigModuleOptions, DB_PASSWORD, DB_URL, DB_USERNAME } from '@src/config'; import { CoreModule } from '@src/core'; import { LoggerModule } from '@src/core/logger'; import { AccountEntity } from '@src/modules/account/domain/entity/account.entity'; +import { defaultMikroOrmOptions } from '@shared/common'; import { FwuLearningContentsController } from './controller/fwu-learning-contents.controller'; import { config, s3Config } from './fwu-learning-contents.config'; import { FwuLearningContentsUc } from './uc/fwu-learning-contents.uc'; -const defaultMikroOrmOptions: MikroOrmModuleSyncOptions = { - findOneOrFailHandler: (entityName: string, where: Dictionary | IPrimaryKey) => - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - new NotFoundException(`The requested ${entityName}: ${where} has not been found.`), -}; - @Module({ imports: [ AuthorizationModule, diff --git a/apps/server/src/modules/h5p-editor/h5p-editor.module.ts b/apps/server/src/modules/h5p-editor/h5p-editor.module.ts index 4c2bcc32b00..92bb164504d 100644 --- a/apps/server/src/modules/h5p-editor/h5p-editor.module.ts +++ b/apps/server/src/modules/h5p-editor/h5p-editor.module.ts @@ -2,15 +2,15 @@ import { AuthGuardModule, AuthGuardOptions } from '@infra/auth-guard'; import { AuthorizationClientModule } from '@infra/authorization-client'; import { RabbitMQWrapperModule } from '@infra/rabbitmq'; import { S3ClientModule } from '@infra/s3-client'; -import { Dictionary, IPrimaryKey } from '@mikro-orm/core'; -import { MikroOrmModule, MikroOrmModuleSyncOptions } from '@mikro-orm/nestjs'; +import { MikroOrmModule } from '@mikro-orm/nestjs'; import { UserModule } from '@modules/user'; -import { Module, NotFoundException } from '@nestjs/common'; +import { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { ALL_ENTITIES } from '@shared/domain/entity'; import { createConfigModuleOptions, DB_PASSWORD, DB_URL, DB_USERNAME } from '@src/config'; import { CoreModule } from '@src/core'; import { Logger } from '@src/core/logger'; +import { defaultMikroOrmOptions } from '@shared/common'; import { H5PEditorController } from './controller/h5p-editor.controller'; import { H5PContent, InstalledLibrary } from './entity'; import { authorizationClientConfig, config, s3ConfigContent, s3ConfigLibraries } from './h5p-editor.config'; @@ -19,12 +19,6 @@ import { H5PContentRepo, LibraryRepo } from './repo'; import { ContentStorage, LibraryStorage, TemporaryFileStorage } from './service'; import { H5PEditorUc } from './uc/h5p.uc'; -const defaultMikroOrmOptions: MikroOrmModuleSyncOptions = { - findOneOrFailHandler: (entityName: string, where: Dictionary | IPrimaryKey) => - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - new NotFoundException(`The requested ${entityName}: ${where} has not been found.`), -}; - const imports = [ AuthorizationClientModule.register(authorizationClientConfig), CoreModule, diff --git a/apps/server/src/modules/idp-console/idp-sync-console.spec.ts b/apps/server/src/modules/idp-console/api/idp-sync-console.spec.ts similarity index 81% rename from apps/server/src/modules/idp-console/idp-sync-console.spec.ts rename to apps/server/src/modules/idp-console/api/idp-sync-console.spec.ts index b15186273cd..6a7bf1c6049 100644 --- a/apps/server/src/modules/idp-console/idp-sync-console.spec.ts +++ b/apps/server/src/modules/idp-console/api/idp-sync-console.spec.ts @@ -1,12 +1,14 @@ import { ObjectId } from 'bson'; import { Test, TestingModule } from '@nestjs/testing'; -import { ConsoleWriterService } from '@infra/console'; -import { createMock } from '@golevelup/ts-jest'; -import { SynchronizationUc } from './uc'; +import { MongoMemoryDatabaseModule } from '@infra/database'; +import { defaultMikroOrmOptions } from '@shared/common'; import { IdpSyncConsole } from './idp-sync-console'; import { SystemType } from './interface'; -import { UsersSyncOptionsBuilder } from './builder'; +import { UsersSyncOptionsBuilder } from '../testing'; +import { SynchronizationUc } from './synchronization.uc'; +import { IdpConsoleModule } from '../idp-console.app.module'; +// Sorry but in the end this test do test neraly nothing.. describe(IdpSyncConsole.name, () => { let module: TestingModule; let console: IdpSyncConsole; @@ -14,17 +16,7 @@ describe(IdpSyncConsole.name, () => { beforeAll(async () => { module = await Test.createTestingModule({ - providers: [ - IdpSyncConsole, - { - provide: ConsoleWriterService, - useValue: createMock(), - }, - { - provide: SynchronizationUc, - useValue: createMock(), - }, - ], + imports: [IdpConsoleModule, MongoMemoryDatabaseModule.forRoot({ ...defaultMikroOrmOptions })], }).compile(); console = module.get(IdpSyncConsole); diff --git a/apps/server/src/modules/idp-console/idp-sync-console.ts b/apps/server/src/modules/idp-console/api/idp-sync-console.ts similarity index 95% rename from apps/server/src/modules/idp-console/idp-sync-console.ts rename to apps/server/src/modules/idp-console/api/idp-sync-console.ts index dab943e8249..0c1acc730d7 100644 --- a/apps/server/src/modules/idp-console/idp-sync-console.ts +++ b/apps/server/src/modules/idp-console/api/idp-sync-console.ts @@ -1,6 +1,6 @@ import { Console, Command } from 'nestjs-console'; import { ConsoleWriterService } from '@infra/console'; -import { SynchronizationUc } from './uc'; +import { SynchronizationUc } from './synchronization.uc'; import { UsersSyncOptions, SystemType } from './interface'; @Console({ diff --git a/apps/server/src/modules/idp-console/uc/index.ts b/apps/server/src/modules/idp-console/api/index.ts similarity index 51% rename from apps/server/src/modules/idp-console/uc/index.ts rename to apps/server/src/modules/idp-console/api/index.ts index 01d3ae1087b..8dfcb690a66 100644 --- a/apps/server/src/modules/idp-console/uc/index.ts +++ b/apps/server/src/modules/idp-console/api/index.ts @@ -1 +1,2 @@ +export * from './idp-sync-console'; export * from './synchronization.uc'; diff --git a/apps/server/src/modules/idp-console/interface/index.ts b/apps/server/src/modules/idp-console/api/interface/index.ts similarity index 66% rename from apps/server/src/modules/idp-console/interface/index.ts rename to apps/server/src/modules/idp-console/api/interface/index.ts index 57dc47a1902..4809788082c 100644 --- a/apps/server/src/modules/idp-console/interface/index.ts +++ b/apps/server/src/modules/idp-console/api/interface/index.ts @@ -1,3 +1,2 @@ export * from './system-type.enum'; export * from './users-sync-options.interface'; -export * from './synchronization.config'; diff --git a/apps/server/src/modules/idp-console/interface/system-type.enum.ts b/apps/server/src/modules/idp-console/api/interface/system-type.enum.ts similarity index 100% rename from apps/server/src/modules/idp-console/interface/system-type.enum.ts rename to apps/server/src/modules/idp-console/api/interface/system-type.enum.ts diff --git a/apps/server/src/modules/idp-console/interface/users-sync-options.interface.ts b/apps/server/src/modules/idp-console/api/interface/users-sync-options.interface.ts similarity index 100% rename from apps/server/src/modules/idp-console/interface/users-sync-options.interface.ts rename to apps/server/src/modules/idp-console/api/interface/users-sync-options.interface.ts diff --git a/apps/server/src/modules/idp-console/uc/loggable-exception/failed-update-lastsyncedat.loggable-exception.spec.ts b/apps/server/src/modules/idp-console/api/loggable-exception/failed-update-lastsyncedat.loggable-exception.spec.ts similarity index 100% rename from apps/server/src/modules/idp-console/uc/loggable-exception/failed-update-lastsyncedat.loggable-exception.spec.ts rename to apps/server/src/modules/idp-console/api/loggable-exception/failed-update-lastsyncedat.loggable-exception.spec.ts diff --git a/apps/server/src/modules/idp-console/uc/loggable-exception/failed-update-lastsyncedat.loggable-exception.ts b/apps/server/src/modules/idp-console/api/loggable-exception/failed-update-lastsyncedat.loggable-exception.ts similarity index 100% rename from apps/server/src/modules/idp-console/uc/loggable-exception/failed-update-lastsyncedat.loggable-exception.ts rename to apps/server/src/modules/idp-console/api/loggable-exception/failed-update-lastsyncedat.loggable-exception.ts diff --git a/apps/server/src/modules/idp-console/uc/loggable-exception/index.ts b/apps/server/src/modules/idp-console/api/loggable-exception/index.ts similarity index 100% rename from apps/server/src/modules/idp-console/uc/loggable-exception/index.ts rename to apps/server/src/modules/idp-console/api/loggable-exception/index.ts diff --git a/apps/server/src/modules/idp-console/uc/loggable-exception/no-users-to-synchronization.loggable-exception.spec.ts b/apps/server/src/modules/idp-console/api/loggable-exception/no-users-to-synchronization.loggable-exception.spec.ts similarity index 100% rename from apps/server/src/modules/idp-console/uc/loggable-exception/no-users-to-synchronization.loggable-exception.spec.ts rename to apps/server/src/modules/idp-console/api/loggable-exception/no-users-to-synchronization.loggable-exception.spec.ts diff --git a/apps/server/src/modules/idp-console/uc/loggable-exception/no-users-to-synchronization.loggable-exception.ts b/apps/server/src/modules/idp-console/api/loggable-exception/no-users-to-synchronization.loggable-exception.ts similarity index 100% rename from apps/server/src/modules/idp-console/uc/loggable-exception/no-users-to-synchronization.loggable-exception.ts rename to apps/server/src/modules/idp-console/api/loggable-exception/no-users-to-synchronization.loggable-exception.ts diff --git a/apps/server/src/modules/idp-console/uc/loggable-exception/synchronization-unknown-error.loggable-exception.spec.ts b/apps/server/src/modules/idp-console/api/loggable-exception/synchronization-unknown-error.loggable-exception.spec.ts similarity index 100% rename from apps/server/src/modules/idp-console/uc/loggable-exception/synchronization-unknown-error.loggable-exception.spec.ts rename to apps/server/src/modules/idp-console/api/loggable-exception/synchronization-unknown-error.loggable-exception.spec.ts diff --git a/apps/server/src/modules/idp-console/uc/loggable-exception/synchronization-unknown-error.loggable-exception.ts b/apps/server/src/modules/idp-console/api/loggable-exception/synchronization-unknown-error.loggable-exception.ts similarity index 100% rename from apps/server/src/modules/idp-console/uc/loggable-exception/synchronization-unknown-error.loggable-exception.ts rename to apps/server/src/modules/idp-console/api/loggable-exception/synchronization-unknown-error.loggable-exception.ts diff --git a/apps/server/src/modules/idp-console/uc/loggable/index.ts b/apps/server/src/modules/idp-console/api/loggable/index.ts similarity index 100% rename from apps/server/src/modules/idp-console/uc/loggable/index.ts rename to apps/server/src/modules/idp-console/api/loggable/index.ts diff --git a/apps/server/src/modules/idp-console/uc/loggable/start-synchronization-loggable.spec.ts b/apps/server/src/modules/idp-console/api/loggable/start-synchronization-loggable.spec.ts similarity index 100% rename from apps/server/src/modules/idp-console/uc/loggable/start-synchronization-loggable.spec.ts rename to apps/server/src/modules/idp-console/api/loggable/start-synchronization-loggable.spec.ts diff --git a/apps/server/src/modules/idp-console/uc/loggable/start-synchronization-loggable.ts b/apps/server/src/modules/idp-console/api/loggable/start-synchronization-loggable.ts similarity index 100% rename from apps/server/src/modules/idp-console/uc/loggable/start-synchronization-loggable.ts rename to apps/server/src/modules/idp-console/api/loggable/start-synchronization-loggable.ts diff --git a/apps/server/src/modules/idp-console/uc/loggable/success-synchronization-loggable.spec.ts b/apps/server/src/modules/idp-console/api/loggable/success-synchronization-loggable.spec.ts similarity index 100% rename from apps/server/src/modules/idp-console/uc/loggable/success-synchronization-loggable.spec.ts rename to apps/server/src/modules/idp-console/api/loggable/success-synchronization-loggable.spec.ts diff --git a/apps/server/src/modules/idp-console/uc/loggable/sucess-synchronization-loggable.ts b/apps/server/src/modules/idp-console/api/loggable/sucess-synchronization-loggable.ts similarity index 100% rename from apps/server/src/modules/idp-console/uc/loggable/sucess-synchronization-loggable.ts rename to apps/server/src/modules/idp-console/api/loggable/sucess-synchronization-loggable.ts diff --git a/apps/server/src/modules/idp-console/uc/synchronization.uc.spec.ts b/apps/server/src/modules/idp-console/api/synchronization.uc.spec.ts similarity index 95% rename from apps/server/src/modules/idp-console/uc/synchronization.uc.spec.ts rename to apps/server/src/modules/idp-console/api/synchronization.uc.spec.ts index 767405c8fd5..2cce1678f53 100644 --- a/apps/server/src/modules/idp-console/uc/synchronization.uc.spec.ts +++ b/apps/server/src/modules/idp-console/api/synchronization.uc.spec.ts @@ -1,26 +1,21 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { SchulconnexResponse, SchulconnexRestClient } from '@infra/schulconnex-client'; import { schulconnexResponseFactory } from '@infra/schulconnex-client/testing'; -import { - Synchronization, - synchronizationFactory, - SynchronizationService, - SynchronizationStatusModel, -} from '@modules/synchronization'; +import { Synchronization, SynchronizationService, SynchronizationStatusModel } from '@modules/synchronization'; import { UserService } from '@modules/user'; -import { ConfigModule } from '@nestjs/config'; +import { ConfigService } from '@nestjs/config'; import { Test, TestingModule } from '@nestjs/testing'; import { setupEntities } from '@shared/testing'; -import { createConfigModuleOptions } from '@src/config'; import { Logger } from '@src/core/logger'; import { AccountService } from '@src/modules/account'; import { ObjectId } from 'bson'; +import { synchronizationFactory } from '@modules/synchronization/domain/testing'; import { FailedUpdateLastSyncedAtLoggableException, NoUsersToSynchronizationLoggableException, } from './loggable-exception'; import { SynchronizationUc } from './synchronization.uc'; -import { synchronizationTestConfig } from './testing'; +import { IdpConsoleConfig } from '../idp-console.config'; describe(SynchronizationUc.name, () => { let module: TestingModule; @@ -29,10 +24,10 @@ describe(SynchronizationUc.name, () => { let accountService: DeepMocked; let synchronizationService: DeepMocked; let schulconnexRestClient: DeepMocked; + let configService: DeepMocked; beforeAll(async () => { module = await Test.createTestingModule({ - imports: [ConfigModule.forRoot(createConfigModuleOptions(synchronizationTestConfig))], providers: [ SynchronizationUc, { @@ -55,6 +50,10 @@ describe(SynchronizationUc.name, () => { provide: SchulconnexRestClient, useValue: createMock(), }, + { + provide: ConfigService, + useValue: createMock>(), + }, ], }).compile(); @@ -63,6 +62,8 @@ describe(SynchronizationUc.name, () => { userService = module.get(UserService); accountService = module.get(AccountService); schulconnexRestClient = module.get(SchulconnexRestClient); + configService = module.get(ConfigService); + await setupEntities(); }); @@ -79,6 +80,7 @@ describe(SynchronizationUc.name, () => { const userSyncCount = 1; const status = SynchronizationStatusModel.SUCCESS; + configService.get.mockReturnValueOnce(1); // SYNCHRONIZATION_CHUNK=1 synchronizationService.createSynchronization.mockResolvedValueOnce(synchronizationId); jest.spyOn(uc, 'findUsersToSynchronize').mockResolvedValueOnce(usersToCheck); const spyUpdateLastSyncedAt = jest.spyOn(uc, 'updateLastSyncedAt'); @@ -184,6 +186,7 @@ describe(SynchronizationUc.name, () => { }), }; + configService.get.mockReturnValueOnce(1); // SYNCHRONIZATION_CHUNK=1 synchronizationService.createSynchronization.mockResolvedValueOnce(synchronizationId); jest.spyOn(uc, 'findUsersToSynchronize').mockResolvedValueOnce(usersToCheck); userService.updateLastSyncedAt.mockRejectedValueOnce(error); diff --git a/apps/server/src/modules/idp-console/uc/synchronization.uc.ts b/apps/server/src/modules/idp-console/api/synchronization.uc.ts similarity index 77% rename from apps/server/src/modules/idp-console/uc/synchronization.uc.ts rename to apps/server/src/modules/idp-console/api/synchronization.uc.ts index e254da66461..2cf733da3d5 100644 --- a/apps/server/src/modules/idp-console/uc/synchronization.uc.ts +++ b/apps/server/src/modules/idp-console/api/synchronization.uc.ts @@ -1,23 +1,23 @@ -import { SchulconnexResponse, SchulconnexRestClient } from '@infra/schulconnex-client'; +import { SchulconnexRestClient } from '@infra/schulconnex-client'; import { Synchronization, SynchronizationService, SynchronizationStatusModel } from '@modules/synchronization'; import { UserService } from '@modules/user'; import { Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { Logger } from '@src/core/logger'; import { ErrorLogMessage } from '@src/core/logger/types'; -import { AccountService } from '@src/modules/account'; -import { SynchronizationConfig } from '../interface'; +import { AccountService } from '@modules/account'; import { StartSynchronizationLoggable, SucessSynchronizationLoggable } from './loggable'; import { FailedUpdateLastSyncedAtLoggableException, NoUsersToSynchronizationLoggableException, SynchronizationUnknownErrorLoggableException, } from './loggable-exception'; +import { IdpConsoleConfig } from '../idp-console.config'; @Injectable() export class SynchronizationUc { constructor( - private readonly configService: ConfigService, + private readonly configService: ConfigService, private readonly schulconnexRestClient: SchulconnexRestClient, private readonly synchronizationService: SynchronizationService, private readonly userService: UserService, @@ -34,7 +34,7 @@ export class SynchronizationUc { try { const usersToCheck = await this.findUsersToSynchronize(systemId); - const chunkSize = this.configService.get('SYNCHRONIZATION_CHUNK'); + const chunkSize = this.configService.get('SYNCHRONIZATION_CHUNK', { infer: true }); const chunks = this.chunkArray(usersToCheck, chunkSize); const promises = chunks.map((chunk) => this.updateLastSyncedAt(chunk, systemId)); const results = await Promise.all(promises); @@ -58,14 +58,16 @@ export class SynchronizationUc { } } + // Should be privat! It is only used for testing. + // Every parts that need be tested and only avaible from intern, + // need to be passed from outside by constructor or public methods. public async findUsersToSynchronize(systemId: string): Promise { - let usersToCheck: string[] = []; - const usersDownloaded: SchulconnexResponse[] = await this.schulconnexRestClient.getPersonenInfo({}); + const usersDownloaded = await this.schulconnexRestClient.getPersonenInfo({}); if (usersDownloaded.length === 0) { throw new NoUsersToSynchronizationLoggableException(systemId); } - usersToCheck = usersDownloaded.map((user) => user.pid); + const usersToCheck = usersDownloaded.map((user) => user.pid); return usersToCheck; } @@ -73,7 +75,6 @@ export class SynchronizationUc { public async updateLastSyncedAt(usersToCheck: string[], systemId: string): Promise { try { const foundUsers = await this.userService.findMultipleByExternalIds(usersToCheck); - const verifiedUsers = await this.accountService.findByUserIdsAndSystemId(foundUsers, systemId); await this.userService.updateLastSyncedAt(verifiedUsers); @@ -84,6 +85,9 @@ export class SynchronizationUc { } } + // Should be privat! It is only used for testing. + // Every parts that need be tested and only avaible from intern, + // need to be passed from outside by constructor or public methods. public async updateSynchronization( synchronizationId: string, status: SynchronizationStatusModel, @@ -100,6 +104,9 @@ export class SynchronizationUc { await this.synchronizationService.update(newSynchronization); } + // Should be privat! It is only used for testing. + // Every parts that need be tested and only avaible from intern, + // need to be passed from outside by constructor or public methods. chunkArray(array: string[], chunkSize: number): string[][] { const chunkedArray: string[][] = []; let index = 0; diff --git a/apps/server/src/modules/idp-console/idp-console.module.ts b/apps/server/src/modules/idp-console/idp-console.app.module.ts similarity index 78% rename from apps/server/src/modules/idp-console/idp-console.module.ts rename to apps/server/src/modules/idp-console/idp-console.app.module.ts index 508d7b514dc..00926079db0 100644 --- a/apps/server/src/modules/idp-console/idp-console.module.ts +++ b/apps/server/src/modules/idp-console/idp-console.app.module.ts @@ -3,21 +3,21 @@ import { RabbitMQWrapperModule } from '@infra/rabbitmq'; import { SchulconnexClientModule } from '@infra/schulconnex-client/schulconnex-client.module'; import { MikroOrmModule } from '@mikro-orm/nestjs'; import { AccountModule } from '@modules/account'; -import { defaultMikroOrmOptions } from '@modules/server'; import { SynchronizationEntity, SynchronizationModule } from '@modules/synchronization'; import { UserModule } from '@modules/user'; import { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { ALL_ENTITIES } from '@shared/domain/entity'; -import { DB_PASSWORD, DB_URL, DB_USERNAME } from '@src/config'; +import { createConfigModuleOptions, DB_PASSWORD, DB_URL, DB_USERNAME } from '@src/config'; import { LoggerModule } from '@src/core/logger'; import { ConsoleModule } from 'nestjs-console'; -import { IdpSyncConsole } from './idp-sync-console'; -import { SynchronizationUc } from './uc'; +import { defaultMikroOrmOptions } from '@shared/common'; +import { idpConsoleConfigConfig } from './idp-console.config'; +import { IdpSyncConsole, SynchronizationUc } from './api'; @Module({ imports: [ - ConfigModule.forRoot({ isGlobal: true }), + ConfigModule.forRoot(createConfigModuleOptions(idpConsoleConfigConfig)), SchulconnexClientModule.registerAsync(), SynchronizationModule, MikroOrmModule.forRoot({ diff --git a/apps/server/src/modules/idp-console/idp-console.config.ts b/apps/server/src/modules/idp-console/idp-console.config.ts new file mode 100644 index 00000000000..08a1e9fe301 --- /dev/null +++ b/apps/server/src/modules/idp-console/idp-console.config.ts @@ -0,0 +1,44 @@ +import { ConsoleWriterConfig } from '@infra/console'; +import { LoggerConfig } from '@src/core/logger'; +import { AccountConfig } from '@modules/account'; +import { UserConfig } from '@modules/user'; +import { SynchronizationConfig } from '@modules/synchronization'; +import { SchulconnexClientConfig } from '@infra/schulconnex-client'; +import { Configuration } from '@hpi-schul-cloud/commons'; +import { LanguageType } from '@shared/domain/interface'; +import { RabbitMqConfig } from '@infra/rabbitmq'; + +export interface IdpConsoleConfig + extends ConsoleWriterConfig, + RabbitMqConfig, + LoggerConfig, + AccountConfig, + UserConfig, + SynchronizationConfig, + SchulconnexClientConfig { + SYNCHRONIZATION_CHUNK: number; +} + +const config: IdpConsoleConfig = { + SYNCHRONIZATION_CHUNK: Configuration.get('SYNCHRONIZATION_CHUNK') as number, + NEST_LOG_LEVEL: Configuration.get('NEST_LOG_LEVEL') as string, + EXIT_ON_ERROR: Configuration.get('EXIT_ON_ERROR') as boolean, + LOGIN_BLOCK_TIME: Configuration.get('LOGIN_BLOCK_TIME') as number, + TEACHER_STUDENT_VISIBILITY__IS_CONFIGURABLE: Configuration.get( + 'TEACHER_STUDENT_VISIBILITY__IS_CONFIGURABLE' + ) as boolean, + FEATURE_IDENTITY_MANAGEMENT_LOGIN_ENABLED: Configuration.get('FEATURE_IDENTITY_MANAGEMENT_LOGIN_ENABLED') as boolean, + FEATURE_IDENTITY_MANAGEMENT_STORE_ENABLED: Configuration.get('FEATURE_IDENTITY_MANAGEMENT_STORE_ENABLED') as boolean, + AVAILABLE_LANGUAGES: (Configuration.get('I18N__AVAILABLE_LANGUAGES') as string).split(',') as LanguageType[], + TEACHER_VISIBILITY_FOR_EXTERNAL_TEAM_INVITATION: Configuration.get( + 'TEACHER_VISIBILITY_FOR_EXTERNAL_TEAM_INVITATION' + ) as string, + SCHULCONNEX_CLIENT__PERSONEN_INFO_TIMEOUT_IN_MS: Configuration.get( + 'SCHULCONNEX_CLIENT__PERSONEN_INFO_TIMEOUT_IN_MS' + ) as number, + SCHULCONNEX_CLIENT__POLICIES_INFO_TIMEOUT_IN_MS: Configuration.get( + 'SCHULCONNEX_CLIENT__POLICIES_INFO_TIMEOUT_IN_MS' + ) as number, +}; + +export const idpConsoleConfigConfig = () => config; diff --git a/apps/server/src/modules/idp-console/index.ts b/apps/server/src/modules/idp-console/index.ts index c59204f5141..6ace271ede5 100644 --- a/apps/server/src/modules/idp-console/index.ts +++ b/apps/server/src/modules/idp-console/index.ts @@ -1,2 +1,3 @@ -export * from './idp-console.module'; -export * from './interface'; +/** ************************************************* + * Do not re-export here! File should be empty! * + ************************************************** */ diff --git a/apps/server/src/modules/idp-console/interface/synchronization.config.ts b/apps/server/src/modules/idp-console/interface/synchronization.config.ts deleted file mode 100644 index 9b0e05d6aa5..00000000000 --- a/apps/server/src/modules/idp-console/interface/synchronization.config.ts +++ /dev/null @@ -1,3 +0,0 @@ -export interface SynchronizationConfig { - SYNCHRONIZATION_CHUNK: number; -} diff --git a/apps/server/src/modules/idp-console/builder/index.ts b/apps/server/src/modules/idp-console/testing/index.ts similarity index 100% rename from apps/server/src/modules/idp-console/builder/index.ts rename to apps/server/src/modules/idp-console/testing/index.ts diff --git a/apps/server/src/modules/idp-console/builder/users-sync-options.builder.spec.ts b/apps/server/src/modules/idp-console/testing/users-sync-options.builder.spec.ts similarity index 92% rename from apps/server/src/modules/idp-console/builder/users-sync-options.builder.spec.ts rename to apps/server/src/modules/idp-console/testing/users-sync-options.builder.spec.ts index 3893011323b..fe5427bcc4d 100644 --- a/apps/server/src/modules/idp-console/builder/users-sync-options.builder.spec.ts +++ b/apps/server/src/modules/idp-console/testing/users-sync-options.builder.spec.ts @@ -1,5 +1,5 @@ import { ObjectId } from 'bson'; -import { SystemType, UsersSyncOptions } from '../interface'; +import { SystemType, UsersSyncOptions } from '../api/interface'; import { UsersSyncOptionsBuilder } from './users-sync-options.builder'; describe(UsersSyncOptionsBuilder.name, () => { diff --git a/apps/server/src/modules/idp-console/builder/users-sync-options.builder.ts b/apps/server/src/modules/idp-console/testing/users-sync-options.builder.ts similarity index 76% rename from apps/server/src/modules/idp-console/builder/users-sync-options.builder.ts rename to apps/server/src/modules/idp-console/testing/users-sync-options.builder.ts index aedb51030a6..69189a30cfe 100644 --- a/apps/server/src/modules/idp-console/builder/users-sync-options.builder.ts +++ b/apps/server/src/modules/idp-console/testing/users-sync-options.builder.ts @@ -1,5 +1,5 @@ import { EntityId } from '@shared/domain/types'; -import { SystemType, UsersSyncOptions } from '../interface'; +import { SystemType, UsersSyncOptions } from '../api/interface'; export class UsersSyncOptionsBuilder { static build(systemType: SystemType, systemId: EntityId): UsersSyncOptions { diff --git a/apps/server/src/modules/idp-console/uc/testing/index.ts b/apps/server/src/modules/idp-console/uc/testing/index.ts deleted file mode 100644 index d1626266255..00000000000 --- a/apps/server/src/modules/idp-console/uc/testing/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './test-config'; diff --git a/apps/server/src/modules/idp-console/uc/testing/test-config.ts b/apps/server/src/modules/idp-console/uc/testing/test-config.ts deleted file mode 100644 index c30472af82e..00000000000 --- a/apps/server/src/modules/idp-console/uc/testing/test-config.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Configuration } from '@hpi-schul-cloud/commons'; - -const synchronizationConfig = { - SYNCHRONIZATION_CHUNK: Configuration.get('SYNCHRONIZATION_CHUNK') as number, -}; - -const config = () => synchronizationConfig; - -export const synchronizationTestConfig = () => { - const conf = config(); - conf.SYNCHRONIZATION_CHUNK = 1; - return conf; -}; diff --git a/apps/server/src/modules/management/management-server.module.ts b/apps/server/src/modules/management/management-server.module.ts index ba2feba4dfe..366aef4af06 100644 --- a/apps/server/src/modules/management/management-server.module.ts +++ b/apps/server/src/modules/management/management-server.module.ts @@ -1,18 +1,12 @@ import { MongoMemoryDatabaseModule } from '@infra/database'; -import { MongoDatabaseModuleOptions } from '@infra/database/mongo-memory-database/types'; -import { Dictionary, IPrimaryKey } from '@mikro-orm/core'; -import { MikroOrmModule, MikroOrmModuleSyncOptions } from '@mikro-orm/nestjs'; -import { DynamicModule, Module, NotFoundException } from '@nestjs/common'; +import { MongoDatabaseModuleOptions } from '@infra/database/mongo-memory-database/types'; // Fix me!! +import { MikroOrmModule } from '@mikro-orm/nestjs'; +import { DynamicModule, Module } from '@nestjs/common'; import { ALL_ENTITIES } from '@shared/domain/entity'; import { DB_PASSWORD, DB_URL, DB_USERNAME } from '@src/config'; +import { defaultMikroOrmOptions } from '@shared/common'; import { ManagementModule } from './management.module'; -export const defaultMikroOrmOptions: MikroOrmModuleSyncOptions = { - findOneOrFailHandler: (entityName: string, where: Dictionary | IPrimaryKey) => - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - new NotFoundException(`The requested ${entityName}: ${where} has not been found.`), -}; - @Module({ imports: [ ManagementModule, diff --git a/apps/server/src/modules/server/admin-api.server.module.ts b/apps/server/src/modules/server/admin-api.server.module.ts index fa40347b5bb..10ef2ff0f1b 100644 --- a/apps/server/src/modules/server/admin-api.server.module.ts +++ b/apps/server/src/modules/server/admin-api.server.module.ts @@ -1,13 +1,12 @@ import { Configuration } from '@hpi-schul-cloud/commons'; import { AuthGuardModule, AuthGuardOptions } from '@infra/auth-guard'; -import { Dictionary, IPrimaryKey } from '@mikro-orm/core'; -import { MikroOrmModule, MikroOrmModuleSyncOptions } from '@mikro-orm/nestjs'; +import { MikroOrmModule } from '@mikro-orm/nestjs'; import { DeletionApiModule } from '@modules/deletion/deletion-api.module'; import { FileEntity } from '@modules/files/entity'; import { LegacySchoolAdminApiModule } from '@modules/legacy-school/legacy-school-admin.api-module'; import { ToolAdminApiModule } from '@modules/tool/tool-admin-api.module'; import { UserAdminApiModule } from '@modules/user/user-admin-api.module'; -import { DynamicModule, Module, NotFoundException } from '@nestjs/common'; +import { DynamicModule, Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { CqrsModule } from '@nestjs/cqrs'; import { ALL_ENTITIES } from '@shared/domain/entity'; @@ -16,6 +15,7 @@ import { LoggerModule } from '@src/core/logger'; import { MongoDatabaseModuleOptions, MongoMemoryDatabaseModule } from '@src/infra/database'; import { EtherpadClientModule } from '@src/infra/etherpad-client'; import { RabbitMQWrapperModule, RabbitMQWrapperTestModule } from '@src/infra/rabbitmq'; +import { defaultMikroOrmOptions } from '@shared/common'; import { AdminApiRegistrationPinModule } from '../registration-pin/admin-api-registration-pin.module'; import { adminApiServerConfig } from './admin-api-server.config'; @@ -33,12 +33,6 @@ const serverModules = [ AuthGuardModule.register([AuthGuardOptions.X_API_KEY]), ]; -const defaultMikroOrmOptions: MikroOrmModuleSyncOptions = { - findOneOrFailHandler: (entityName: string, where: Dictionary | IPrimaryKey) => - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - new NotFoundException(`The requested ${entityName}: ${where} has not been found.`), -}; - @Module({ imports: [ RabbitMQWrapperModule, diff --git a/apps/server/src/modules/server/server.config.ts b/apps/server/src/modules/server/server.config.ts index 81bc4e337a0..f4e5eb6ec5d 100644 --- a/apps/server/src/modules/server/server.config.ts +++ b/apps/server/src/modules/server/server.config.ts @@ -12,7 +12,6 @@ import type { AuthenticationConfig } from '@modules/authentication'; import type { BoardConfig, MediaBoardConfig } from '@modules/board'; import type { CollaborativeTextEditorConfig } from '@modules/collaborative-text-editor'; import type { FilesStorageClientConfig } from '@modules/files-storage-client'; -import { SynchronizationConfig } from '@modules/idp-console'; import type { LearnroomConfig } from '@modules/learnroom'; import type { LessonConfig } from '@modules/lesson'; import { OauthConfig } from '@modules/oauth'; @@ -65,7 +64,6 @@ export interface ServerConfig SharingConfig, UserImportConfig, SchulconnexClientConfig, - SynchronizationConfig, CollaborativeTextEditorConfig, ProvisioningConfig, RoomConfig, @@ -183,7 +181,6 @@ const config: ServerConfig = { JWT_TIMEOUT_SECONDS: Configuration.get('JWT_TIMEOUT_SECONDS') as number, JWT_LIFETIME_SUPPORT_SECONDS: Configuration.get('JWT_LIFETIME_SUPPORT_SECONDS') as number, JWT_EXTENDED_TIMEOUT_SECONDS: Configuration.get('JWT_EXTENDED_TIMEOUT_SECONDS') as number, - // Node's process.env escapes newlines. We need to reverse it for the keys to work. // See: https://stackoverflow.com/questions/30400341/environment-variables-containing-newlines-in-node JWT_PRIVATE_KEY: (Configuration.get('JWT_PRIVATE_KEY') as string).replace(/\\n/g, '\n'), @@ -217,7 +214,6 @@ const config: ServerConfig = { FEATURE_IDENTITY_MANAGEMENT_LOGIN_ENABLED: Configuration.get('FEATURE_IDENTITY_MANAGEMENT_LOGIN_ENABLED') as boolean, FEATURE_TSP_SYNC_ENABLED: Configuration.get('FEATURE_TSP_SYNC_ENABLED') as boolean, STUDENT_TEAM_CREATION: Configuration.get('STUDENT_TEAM_CREATION') as string, - SYNCHRONIZATION_CHUNK: Configuration.get('SYNCHRONIZATION_CHUNK') as number, // parse [:],[:]... and discard description BLOCKLIST_OF_EMAIL_DOMAINS: (Configuration.get('BLOCKLIST_OF_EMAIL_DOMAINS') as string) .split(',') diff --git a/apps/server/src/modules/server/server.module.ts b/apps/server/src/modules/server/server.module.ts index 46cafe9c2e7..e95d93f2103 100644 --- a/apps/server/src/modules/server/server.module.ts +++ b/apps/server/src/modules/server/server.module.ts @@ -4,8 +4,7 @@ import { MongoDatabaseModuleOptions, MongoMemoryDatabaseModule } from '@infra/da import { MailModule } from '@infra/mail'; import { RabbitMQWrapperModule, RabbitMQWrapperTestModule } from '@infra/rabbitmq'; import { SchulconnexClientModule } from '@infra/schulconnex-client/schulconnex-client.module'; -import { Dictionary, IPrimaryKey } from '@mikro-orm/core'; -import { MikroOrmModule, MikroOrmModuleSyncOptions } from '@mikro-orm/nestjs'; +import { MikroOrmModule } from '@mikro-orm/nestjs'; import { AccountApiModule } from '@modules/account/account-api.module'; import { AlertModule } from '@modules/alert/alert.module'; import { AuthenticationApiModule } from '@modules/authentication/authentication-api.module'; @@ -42,12 +41,13 @@ import { UserLoginMigrationApiModule } from '@modules/user-login-migration/user- import { UsersAdminApiModule } from '@modules/user/legacy/users-admin-api.module'; import { UserApiModule } from '@modules/user/user-api.module'; import { VideoConferenceApiModule } from '@modules/video-conference/video-conference-api.module'; -import { DynamicModule, Module, NotFoundException } from '@nestjs/common'; +import { DynamicModule, Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { ALL_ENTITIES } from '@shared/domain/entity'; import { createConfigModuleOptions, DB_PASSWORD, DB_URL, DB_USERNAME } from '@src/config'; import { CoreModule } from '@src/core'; import { LoggerModule } from '@src/core/logger'; +import { defaultMikroOrmOptions } from '@shared/common'; import { ServerConfigController, ServerController, ServerUc } from './api'; import { SERVER_CONFIG_TOKEN, serverConfig } from './server.config'; @@ -106,12 +106,6 @@ const serverModules = [ ShdApiModule, ]; -export const defaultMikroOrmOptions: MikroOrmModuleSyncOptions = { - findOneOrFailHandler: (entityName: string, where: Dictionary | IPrimaryKey) => - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - new NotFoundException(`The requested ${entityName}: ${where} has not been found.`), -}; - const providers = [ServerUc, { provide: SERVER_CONFIG_TOKEN, useValue: serverConfig() }]; const controllers = [ServerController, ServerConfigController]; diff --git a/apps/server/src/modules/synchronization/index.ts b/apps/server/src/modules/synchronization/index.ts index 9d4e28e58ca..d8c073f98e6 100644 --- a/apps/server/src/modules/synchronization/index.ts +++ b/apps/server/src/modules/synchronization/index.ts @@ -1,3 +1,4 @@ export { SynchronizationModule } from './synchronization.module'; -export * from './domain'; +export { Synchronization, SynchronizationService, SynchronizationStatusModel } from './domain'; export { SynchronizationEntity, SynchronizationRepo } from './repo'; +export { SynchronizationConfig } from './synchronization.config'; diff --git a/apps/server/src/modules/synchronization/synchronization.config.ts b/apps/server/src/modules/synchronization/synchronization.config.ts new file mode 100644 index 00000000000..afb9120b6c3 --- /dev/null +++ b/apps/server/src/modules/synchronization/synchronization.config.ts @@ -0,0 +1,2 @@ +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface SynchronizationConfig {} diff --git a/apps/server/src/modules/tldraw/tldraw-api.module.ts b/apps/server/src/modules/tldraw/tldraw-api.module.ts index e5d85599267..1e40e769924 100644 --- a/apps/server/src/modules/tldraw/tldraw-api.module.ts +++ b/apps/server/src/modules/tldraw/tldraw-api.module.ts @@ -1,23 +1,17 @@ import { AuthGuardModule, AuthGuardOptions } from '@infra/auth-guard'; -import { Dictionary, IPrimaryKey } from '@mikro-orm/core'; -import { MikroOrmModule, MikroOrmModuleSyncOptions } from '@mikro-orm/nestjs'; -import { Module, NotFoundException } from '@nestjs/common'; +import { MikroOrmModule } from '@mikro-orm/nestjs'; +import { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { createConfigModuleOptions, DB_PASSWORD, DB_USERNAME } from '@src/config'; import { CoreModule } from '@src/core'; import { LoggerModule } from '@src/core/logger'; +import { defaultMikroOrmOptions } from '@shared/common'; import { config, TLDRAW_DB_URL } from './config'; import { TldrawController } from './controller'; import { TldrawDrawing } from './entities'; import { TldrawBoardRepo, TldrawRepo, YMongodb } from './repo'; import { TldrawService } from './service'; -const defaultMikroOrmOptions: MikroOrmModuleSyncOptions = { - findOneOrFailHandler: (entityName: string, where: Dictionary | IPrimaryKey) => - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - new NotFoundException(`The requested ${entityName}: ${where} has not been found.`), -}; - @Module({ imports: [ LoggerModule, diff --git a/apps/server/src/modules/tldraw/tldraw-console.module.ts b/apps/server/src/modules/tldraw/tldraw-console.module.ts index cc25308ce27..2fc741de464 100644 --- a/apps/server/src/modules/tldraw/tldraw-console.module.ts +++ b/apps/server/src/modules/tldraw/tldraw-console.module.ts @@ -1,15 +1,15 @@ import { ConsoleWriterModule } from '@infra/console'; import { RabbitMQWrapperModule } from '@infra/rabbitmq'; import { S3ClientModule } from '@infra/s3-client'; -import { Dictionary, IPrimaryKey } from '@mikro-orm/core'; -import { MikroOrmModule, MikroOrmModuleSyncOptions } from '@mikro-orm/nestjs'; -import { Module, NotFoundException } from '@nestjs/common'; +import { MikroOrmModule } from '@mikro-orm/nestjs'; +import { Module } from '@nestjs/common'; import { ConfigModule, ConfigService } from '@nestjs/config'; import { initialisePerformanceObserver } from '@shared/common/measure-utils'; import { createConfigModuleOptions, DB_PASSWORD, DB_USERNAME } from '@src/config'; import { CoreModule } from '@src/core'; import { Logger, LoggerModule } from '@src/core/logger'; import { ConsoleModule } from 'nestjs-console'; +import { defaultMikroOrmOptions } from '@shared/common'; import { FilesStorageClientModule } from '../files-storage-client'; import { config, TLDRAW_DB_URL, TldrawConfig, tldrawS3Config } from './config'; import { TldrawDrawing } from './entities'; @@ -18,12 +18,6 @@ import { TldrawRepo, YMongodb } from './repo'; import { TldrawFilesStorageAdapterService } from './service'; import { TldrawDeleteFilesUc } from './uc'; -const defaultMikroOrmOptions: MikroOrmModuleSyncOptions = { - findOneOrFailHandler: (entityName: string, where: Dictionary | IPrimaryKey) => - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - new NotFoundException(`The requested ${entityName}: ${where} has not been found.`), -}; - @Module({ imports: [ S3ClientModule.register([tldrawS3Config]), diff --git a/apps/server/src/modules/tldraw/tldraw-ws.module.ts b/apps/server/src/modules/tldraw/tldraw-ws.module.ts index 7273c14814c..307048ab233 100644 --- a/apps/server/src/modules/tldraw/tldraw-ws.module.ts +++ b/apps/server/src/modules/tldraw/tldraw-ws.module.ts @@ -1,12 +1,12 @@ -import { Dictionary, IPrimaryKey } from '@mikro-orm/core'; -import { MikroOrmModule, MikroOrmModuleSyncOptions } from '@mikro-orm/nestjs'; +import { MikroOrmModule } from '@mikro-orm/nestjs'; import { HttpModule } from '@nestjs/axios'; -import { Module, NotFoundException } from '@nestjs/common'; +import { Module } from '@nestjs/common'; import { ConfigModule, ConfigService } from '@nestjs/config'; import { initialisePerformanceObserver } from '@shared/common/measure-utils'; import { createConfigModuleOptions, DB_PASSWORD, DB_USERNAME } from '@src/config'; import { CoreModule } from '@src/core'; import { Logger, LoggerModule } from '@src/core/logger'; +import { defaultMikroOrmOptions } from '@shared/common'; import { config, TLDRAW_DB_URL, TldrawConfig } from './config'; import { TldrawWs } from './controller'; import { TldrawDrawing } from './entities'; @@ -15,11 +15,6 @@ import { TldrawRedisFactory, TldrawRedisService } from './redis'; import { TldrawBoardRepo, TldrawRepo, YMongodb } from './repo'; import { TldrawWsService } from './service'; -const defaultMikroOrmOptions: MikroOrmModuleSyncOptions = { - findOneOrFailHandler: (entityName: string, where: Dictionary | IPrimaryKey) => - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - new NotFoundException(`The requested ${entityName}: ${where} has not been found.`), -}; @Module({ imports: [ HttpModule, diff --git a/apps/server/src/shared/common/defaultMikroOrmOptions.ts b/apps/server/src/shared/common/defaultMikroOrmOptions.ts new file mode 100644 index 00000000000..8c731e38c34 --- /dev/null +++ b/apps/server/src/shared/common/defaultMikroOrmOptions.ts @@ -0,0 +1,9 @@ +import { MikroOrmModuleSyncOptions } from '@mikro-orm/nestjs'; +import { NotFoundException } from '@nestjs/common'; +import { Dictionary, IPrimaryKey } from '@mikro-orm/core'; + +export const defaultMikroOrmOptions: MikroOrmModuleSyncOptions = { + findOneOrFailHandler: (entityName: string, where: Dictionary | IPrimaryKey) => + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + new NotFoundException(`The requested ${entityName}: ${where} has not been found.`), +}; diff --git a/apps/server/src/shared/common/index.ts b/apps/server/src/shared/common/index.ts index 95ae196d3a1..0497258c967 100644 --- a/apps/server/src/shared/common/index.ts +++ b/apps/server/src/shared/common/index.ts @@ -1,4 +1,5 @@ export { RequestTimeout } from './decorators'; +export { defaultMikroOrmOptions } from './defaultMikroOrmOptions'; export * from './error'; export * from './guards'; export * from './interceptor';