From faef50e6e9e768de57a58f596775614cd5d498bf Mon Sep 17 00:00:00 2001 From: Maximilian Kreuzkam Date: Mon, 11 Nov 2024 15:30:12 +0100 Subject: [PATCH 01/14] Add legacy migration. --- apps/server/src/infra/sync/sync.module.ts | 3 +- .../sync/tsp/tsp-legacy-migration.service.ts | 64 +++++++++++++++++++ .../src/infra/sync/tsp/tsp-sync.strategy.ts | 4 ++ 3 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 apps/server/src/infra/sync/tsp/tsp-legacy-migration.service.ts diff --git a/apps/server/src/infra/sync/sync.module.ts b/apps/server/src/infra/sync/sync.module.ts index 40e02c83966..02bff2032ab 100644 --- a/apps/server/src/infra/sync/sync.module.ts +++ b/apps/server/src/infra/sync/sync.module.ts @@ -10,6 +10,7 @@ import { LoggerModule } from '@src/core/logger'; import { ProvisioningModule } from '@src/modules/provisioning'; import { SyncConsole } from './console/sync.console'; import { SyncService } from './service/sync.service'; +import { TspLegacyMigrationService } from './tsp/tsp-legacy-migration.service'; import { TspOauthDataMapper } from './tsp/tsp-oauth-data.mapper'; import { TspSyncService } from './tsp/tsp-sync.service'; import { TspSyncStrategy } from './tsp/tsp-sync.strategy'; @@ -28,7 +29,7 @@ import { SyncUc } from './uc/sync.uc'; SyncUc, SyncService, ...((Configuration.get('FEATURE_TSP_SYNC_ENABLED') as boolean) - ? [TspSyncStrategy, TspSyncService, TspOauthDataMapper] + ? [TspSyncStrategy, TspSyncService, TspOauthDataMapper, TspLegacyMigrationService] : []), ], exports: [SyncConsole], diff --git a/apps/server/src/infra/sync/tsp/tsp-legacy-migration.service.ts b/apps/server/src/infra/sync/tsp/tsp-legacy-migration.service.ts new file mode 100644 index 00000000000..eab60f1c3e3 --- /dev/null +++ b/apps/server/src/infra/sync/tsp/tsp-legacy-migration.service.ts @@ -0,0 +1,64 @@ +import { EntityManager, ObjectId } from '@mikro-orm/mongodb'; +import { Injectable } from '@nestjs/common'; +import { SchoolProperties } from '@shared/domain/entity'; +import { EntityId } from '@shared/domain/types'; +import { Logger } from '@src/core/logger'; + +@Injectable() +export class TspLegacyMigrationService { + constructor(private readonly em: EntityManager, private readonly logger: Logger) { + logger.setContext(TspLegacyMigrationService.name); + } + + public async migrateLegacyData(newSystemId: EntityId): Promise { + console.log('starting legacy migration'); + const legacySystemId = await this.findLegacySystemId(); + + if (!legacySystemId) { + console.log('No legacy system found'); + return; + } + + const schools = await this.em.find< + SchoolProperties & { + sourceOptions: { + schoolIdentifier: number; + }; + } + >('schools', { + systems: [legacySystemId], + source: 'tsp', + }); + + const schoolIds = schools.map((school) => school.sourceOptions.schoolIdentifier); + + console.log('Number of schools', schoolIds); + + const promises = schoolIds.map((oldId) => + this.em.nativeUpdate( + 'schools', + { + systems: [legacySystemId], + source: 'tsp', + }, + { + $unset: { sourceOptions: '' }, + ldapSchoolIdentifier: oldId, + systems: [new ObjectId(newSystemId)], + } + ) + ); + + const res = await Promise.allSettled(promises); + const success = res.map((r) => r.status === 'fulfilled').length; + console.log(`Migrated ${schoolIds.length} legacy schools to new system. ${success} succeeded.`); + } + + private async findLegacySystemId() { + const tspLegacySystem = await this.em.getCollection('systems').findOne({ + type: 'tsp-school', + }); + + return tspLegacySystem?._id; + } +} diff --git a/apps/server/src/infra/sync/tsp/tsp-sync.strategy.ts b/apps/server/src/infra/sync/tsp/tsp-sync.strategy.ts index b7273d7cfdc..c889c69252c 100644 --- a/apps/server/src/infra/sync/tsp/tsp-sync.strategy.ts +++ b/apps/server/src/infra/sync/tsp/tsp-sync.strategy.ts @@ -16,6 +16,7 @@ import { TspSyncingUsersLoggable } from './loggable/tsp-syncing-users.loggable'; import { TspOauthDataMapper } from './tsp-oauth-data.mapper'; import { TspSyncConfig } from './tsp-sync.config'; import { TspSyncService } from './tsp-sync.service'; +import { TspLegacyMigrationService } from './tsp-legacy-migration.service'; @Injectable() export class TspSyncStrategy extends SyncStrategy { @@ -31,6 +32,7 @@ export class TspSyncStrategy extends SyncStrategy { private readonly logger: Logger, private readonly tspSyncService: TspSyncService, private readonly tspOauthDataMapper: TspOauthDataMapper, + private readonly tspLegacyMigrationService: TspLegacyMigrationService, configService: ConfigService, private readonly provisioningService: ProvisioningService ) { @@ -51,6 +53,8 @@ export class TspSyncStrategy extends SyncStrategy { public async sync(): Promise { const system = await this.tspSyncService.findTspSystemOrFail(); + await this.tspLegacyMigrationService.migrateLegacyData(system.id); + await this.syncSchools(system); const schools = await this.tspSyncService.findSchoolsForSystem(system); From de3967b9653847c2d9201053f05541c782beb262 Mon Sep 17 00:00:00 2001 From: Simone Radtke Date: Tue, 12 Nov 2024 15:50:50 +0100 Subject: [PATCH 02/14] EW-1052 Add fileStorageType to school --- apps/server/src/infra/sync/tsp/tsp-sync.service.spec.ts | 4 +++- apps/server/src/infra/sync/tsp/tsp-sync.service.ts | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/server/src/infra/sync/tsp/tsp-sync.service.spec.ts b/apps/server/src/infra/sync/tsp/tsp-sync.service.spec.ts index fe8bcc4c4a3..d0a54ed0a9a 100644 --- a/apps/server/src/infra/sync/tsp/tsp-sync.service.spec.ts +++ b/apps/server/src/infra/sync/tsp/tsp-sync.service.spec.ts @@ -14,7 +14,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import { SystemProvisioningStrategy } from '@shared/domain/interface/system-provisioning.strategy'; import { federalStateFactory, schoolYearFactory } from '@shared/testing'; import { FederalStateService, SchoolYearService } from '@src/modules/legacy-school'; -import { SchoolProps } from '@src/modules/school/domain'; +import { FileStorageType, SchoolProps } from '@src/modules/school/domain'; import { FederalStateEntityMapper, SchoolYearEntityMapper } from '@src/modules/school/repo/mikro-orm/mapper'; import { schoolFactory } from '@src/modules/school/testing'; import { systemFactory } from '@src/modules/system/testing'; @@ -451,6 +451,7 @@ describe(TspSyncService.name, () => { systemIds: [system.id], federalState, currentYear: schoolYear, + fileStorageType: FileStorageType.AWS_S3, }) as Partial, }); }); @@ -488,6 +489,7 @@ describe(TspSyncService.name, () => { systemIds: [system.id], federalState, currentYear: schoolYear, + fileStorageType: FileStorageType.AWS_S3, }) as Partial, }); expect(federalStateService.findFederalStateByName).not.toHaveBeenCalled(); diff --git a/apps/server/src/infra/sync/tsp/tsp-sync.service.ts b/apps/server/src/infra/sync/tsp/tsp-sync.service.ts index 084990d8f8e..ba2bb2b79a7 100644 --- a/apps/server/src/infra/sync/tsp/tsp-sync.service.ts +++ b/apps/server/src/infra/sync/tsp/tsp-sync.service.ts @@ -6,7 +6,7 @@ import { Injectable } from '@nestjs/common'; import { SystemProvisioningStrategy } from '@shared/domain/interface/system-provisioning.strategy'; import { SchoolFeature } from '@shared/domain/types'; import { FederalStateNames } from '@src/modules/legacy-school/types'; -import { FederalState } from '@src/modules/school/domain'; +import { FederalState, FileStorageType } from '@src/modules/school/domain'; import { SchoolFactory } from '@src/modules/school/domain/factory'; import { FederalStateEntityMapper, SchoolYearEntityMapper } from '@src/modules/school/repo/mikro-orm/mapper'; import { ObjectId } from 'bson'; @@ -126,6 +126,7 @@ export class TspSyncService { createdAt: new Date(), updatedAt: new Date(), id: new ObjectId().toHexString(), + fileStorageType: FileStorageType.AWS_S3, }); const savedSchool = await this.schoolService.save(school); From 938a179143a9d5e84dab327db3d8206c7c09e19c Mon Sep 17 00:00:00 2001 From: Maximilian Kreuzkam Date: Wed, 13 Nov 2024 16:40:23 +0100 Subject: [PATCH 03/14] More work on migration. --- .../sync/tsp/tsp-legacy-migration.service.ts | 83 ++++++++++------- .../src/infra/sync/tsp/tsp-sync.service.ts | 89 +++++++++++++------ 2 files changed, 114 insertions(+), 58 deletions(-) diff --git a/apps/server/src/infra/sync/tsp/tsp-legacy-migration.service.ts b/apps/server/src/infra/sync/tsp/tsp-legacy-migration.service.ts index eab60f1c3e3..e02d41ccb4a 100644 --- a/apps/server/src/infra/sync/tsp/tsp-legacy-migration.service.ts +++ b/apps/server/src/infra/sync/tsp/tsp-legacy-migration.service.ts @@ -1,9 +1,19 @@ import { EntityManager, ObjectId } from '@mikro-orm/mongodb'; import { Injectable } from '@nestjs/common'; -import { SchoolProperties } from '@shared/domain/entity'; -import { EntityId } from '@shared/domain/types'; +import { EntityId, SchoolFeature } from '@shared/domain/types'; import { Logger } from '@src/core/logger'; +type LegacyTspSchoolProperties = { + sourceOptions: { + schoolIdentifier: number; + }; +}; + +const TSP_LEGACY_SYSTEM_TYPE = 'tsp-school'; +const TSP_LEGACY_SOURCE_TYPE = 'tsp'; +const SCHOOLS_COLLECTION = 'schools'; +const SYSTEMS_COLLECTION = 'systems'; + @Injectable() export class TspLegacyMigrationService { constructor(private readonly em: EntityManager, private readonly logger: Logger) { @@ -19,46 +29,59 @@ export class TspLegacyMigrationService { return; } - const schools = await this.em.find< - SchoolProperties & { - sourceOptions: { - schoolIdentifier: number; - }; - } - >('schools', { - systems: [legacySystemId], - source: 'tsp', - }); + const schoolIds = await this.findIdsOfLegacyTspSchools(legacySystemId); - const schoolIds = schools.map((school) => school.sourceOptions.schoolIdentifier); + console.log('Number of schools', schoolIds.length); - console.log('Number of schools', schoolIds); + const promises = schoolIds.map(async (oldId): Promise => { + const filter = { + systems: [legacySystemId], + source: TSP_LEGACY_SOURCE_TYPE, + sourceOptions: { + schoolIdentifier: oldId, + }, + }; - const promises = schoolIds.map((oldId) => - this.em.nativeUpdate( - 'schools', - { - systems: [legacySystemId], - source: 'tsp', + const featureUpdateCount = await this.em.nativeUpdate(SCHOOLS_COLLECTION, filter, { + $addToSet: { + features: SchoolFeature.OAUTH_PROVISIONING_ENABLED, }, - { - $unset: { sourceOptions: '' }, - ldapSchoolIdentifier: oldId, - systems: [new ObjectId(newSystemId)], - } - ) - ); + }); + const idUpdateCount = await this.em.nativeUpdate(SCHOOLS_COLLECTION, filter, { + ldapSchoolIdentifier: oldId, + systems: [new ObjectId(newSystemId)], + }); + + return featureUpdateCount === 1 && idUpdateCount === 1 ? 1 : 0; + }); const res = await Promise.allSettled(promises); - const success = res.map((r) => r.status === 'fulfilled').length; + const success = res + .filter((r) => r.status === 'fulfilled') + .map((r) => r.value) + .reduce((acc, c) => acc + c, 0); console.log(`Migrated ${schoolIds.length} legacy schools to new system. ${success} succeeded.`); } private async findLegacySystemId() { - const tspLegacySystem = await this.em.getCollection('systems').findOne({ - type: 'tsp-school', + const tspLegacySystem = await this.em.getCollection(SYSTEMS_COLLECTION).findOne({ + type: TSP_LEGACY_SYSTEM_TYPE, }); return tspLegacySystem?._id; } + + private async findIdsOfLegacyTspSchools(legacySystemId: ObjectId) { + const schools = await this.em + .getCollection(SCHOOLS_COLLECTION) + .find({ + systems: [legacySystemId], + source: TSP_LEGACY_SOURCE_TYPE, + }) + .toArray(); + + const schoolIds = schools.map((school) => school.sourceOptions.schoolIdentifier); + + return schoolIds; + } } diff --git a/apps/server/src/infra/sync/tsp/tsp-sync.service.ts b/apps/server/src/infra/sync/tsp/tsp-sync.service.ts index ba2bb2b79a7..62588c461b2 100644 --- a/apps/server/src/infra/sync/tsp/tsp-sync.service.ts +++ b/apps/server/src/infra/sync/tsp/tsp-sync.service.ts @@ -9,6 +9,7 @@ import { FederalStateNames } from '@src/modules/legacy-school/types'; import { FederalState, FileStorageType } from '@src/modules/school/domain'; import { SchoolFactory } from '@src/modules/school/domain/factory'; import { FederalStateEntityMapper, SchoolYearEntityMapper } from '@src/modules/school/repo/mikro-orm/mapper'; +import { AxiosError } from 'axios'; import { ObjectId } from 'bson'; import moment from 'moment/moment'; import { TspSystemNotFoundLoggableException } from './loggable/tsp-system-not-found.loggable-exception'; @@ -40,43 +41,75 @@ export class TspSyncService { } public async fetchTspSchools(system: System, daysToFetch: number) { - const client = this.createClient(system); - - const lastChangeDate = this.formatChangeDate(daysToFetch); - const schoolsResponse = await client.exportSchuleList(lastChangeDate); - const schools = schoolsResponse.data; - - return schools; + try { + const client = this.createClient(system); + + const lastChangeDate = this.formatChangeDate(daysToFetch); + const schoolsResponse = await client.exportSchuleList(lastChangeDate); + const schools = schoolsResponse.data; + + return schools; + } catch (e) { + console.log('fetchTspSchools does not work'); + if (e instanceof AxiosError) { + console.log(e.response?.data); + } + } + return []; } public async fetchTspTeachers(system: System, daysToFetch: number) { - const client = this.createClient(system); - - const lastChangeDate = this.formatChangeDate(daysToFetch); - const teachersResponse = await client.exportLehrerList(lastChangeDate); - const teachers = teachersResponse.data; - - return teachers; + try { + const client = this.createClient(system); + + const lastChangeDate = this.formatChangeDate(daysToFetch); + const teachersResponse = await client.exportLehrerList(lastChangeDate); + const teachers = teachersResponse.data; + + return teachers; + } catch (e) { + console.log('fetchTspTeachers does not work'); + if (e instanceof AxiosError) { + console.log(e.response?.data); + } + } + return []; } public async fetchTspStudents(system: System, daysToFetch: number) { - const client = this.createClient(system); - - const lastChangeDate = this.formatChangeDate(daysToFetch); - const studentsResponse = await client.exportSchuelerList(lastChangeDate); - const students = studentsResponse.data; - - return students; + try { + const client = this.createClient(system); + + const lastChangeDate = this.formatChangeDate(daysToFetch); + const studentsResponse = await client.exportSchuelerList(lastChangeDate); + const students = studentsResponse.data; + + return students; + } catch (e) { + console.log('fetchTspStudents does not work'); + if (e instanceof AxiosError) { + console.log(e.response?.data); + } + } + return []; } public async fetchTspClasses(system: System, daysToFetch: number) { - const client = this.createClient(system); - - const lastChangeDate = this.formatChangeDate(daysToFetch); - const classesResponse = await client.exportKlasseList(lastChangeDate); - const classes = classesResponse.data; - - return classes; + try { + const client = this.createClient(system); + + const lastChangeDate = this.formatChangeDate(daysToFetch); + const classesResponse = await client.exportKlasseList(lastChangeDate); + const classes = classesResponse.data; + + return classes; + } catch (e) { + console.log('fetchTspClasses does not work'); + if (e instanceof AxiosError) { + console.log(e.response?.data); + } + } + return []; } public async findSchool(system: System, identifier: string): Promise { From 1bb6f40ed5f3d6cd88eea3d7b152558841d9c3a1 Mon Sep 17 00:00:00 2001 From: Maximilian Kreuzkam Date: Wed, 13 Nov 2024 17:48:26 +0100 Subject: [PATCH 04/14] Use loggables. --- .../tsp-legacy-migration-count.loggable.ts | 16 ++++++++++++++++ .../tsp-legacy-migration-start.loggable.ts | 11 +++++++++++ .../tsp-legacy-migration-success.loggable.ts | 17 +++++++++++++++++ ...-legacy-migration-system-missing.loggable.ts | 11 +++++++++++ .../sync/tsp/tsp-legacy-migration.service.ts | 14 ++++++++++---- 5 files changed, 65 insertions(+), 4 deletions(-) create mode 100644 apps/server/src/infra/sync/tsp/loggable/tsp-legacy-migration-count.loggable.ts create mode 100644 apps/server/src/infra/sync/tsp/loggable/tsp-legacy-migration-start.loggable.ts create mode 100644 apps/server/src/infra/sync/tsp/loggable/tsp-legacy-migration-success.loggable.ts create mode 100644 apps/server/src/infra/sync/tsp/loggable/tsp-legacy-migration-system-missing.loggable.ts diff --git a/apps/server/src/infra/sync/tsp/loggable/tsp-legacy-migration-count.loggable.ts b/apps/server/src/infra/sync/tsp/loggable/tsp-legacy-migration-count.loggable.ts new file mode 100644 index 00000000000..3e769b89165 --- /dev/null +++ b/apps/server/src/infra/sync/tsp/loggable/tsp-legacy-migration-count.loggable.ts @@ -0,0 +1,16 @@ +import { Loggable, LogMessage } from '@src/core/logger'; + +export class TspLegacyMigrationCountLoggable implements Loggable { + constructor(private readonly total: number) {} + + getLogMessage(): LogMessage { + const message: LogMessage = { + message: `Found ${this.total} legacy tsp schools to migrate`, + data: { + total: this.total, + }, + }; + + return message; + } +} diff --git a/apps/server/src/infra/sync/tsp/loggable/tsp-legacy-migration-start.loggable.ts b/apps/server/src/infra/sync/tsp/loggable/tsp-legacy-migration-start.loggable.ts new file mode 100644 index 00000000000..c3af20dc0c7 --- /dev/null +++ b/apps/server/src/infra/sync/tsp/loggable/tsp-legacy-migration-start.loggable.ts @@ -0,0 +1,11 @@ +import { Loggable, LogMessage } from '@src/core/logger'; + +export class TspLegacyMigrationStartLoggable implements Loggable { + getLogMessage(): LogMessage { + const message: LogMessage = { + message: 'Running migration of legacy tsp data.', + }; + + return message; + } +} diff --git a/apps/server/src/infra/sync/tsp/loggable/tsp-legacy-migration-success.loggable.ts b/apps/server/src/infra/sync/tsp/loggable/tsp-legacy-migration-success.loggable.ts new file mode 100644 index 00000000000..0469ffdd8ac --- /dev/null +++ b/apps/server/src/infra/sync/tsp/loggable/tsp-legacy-migration-success.loggable.ts @@ -0,0 +1,17 @@ +import { Loggable, LogMessage } from '@src/core/logger'; + +export class TspLegacyMigrationSuccessLoggable implements Loggable { + constructor(private readonly total: number, private readonly migrated: number) {} + + getLogMessage(): LogMessage { + const message: LogMessage = { + message: `Legacy tsp data migration finished. Total schools: ${this.total}, migrated schools: ${this.migrated}`, + data: { + total: this.total, + migrated: this.migrated, + }, + }; + + return message; + } +} diff --git a/apps/server/src/infra/sync/tsp/loggable/tsp-legacy-migration-system-missing.loggable.ts b/apps/server/src/infra/sync/tsp/loggable/tsp-legacy-migration-system-missing.loggable.ts new file mode 100644 index 00000000000..fcdf3b26d0a --- /dev/null +++ b/apps/server/src/infra/sync/tsp/loggable/tsp-legacy-migration-system-missing.loggable.ts @@ -0,0 +1,11 @@ +import { Loggable, LogMessage } from '@src/core/logger'; + +export class TspLegacyMigrationSystemMissingLoggable implements Loggable { + getLogMessage(): LogMessage { + const message: LogMessage = { + message: 'No legacy system found', + }; + + return message; + } +} diff --git a/apps/server/src/infra/sync/tsp/tsp-legacy-migration.service.ts b/apps/server/src/infra/sync/tsp/tsp-legacy-migration.service.ts index e02d41ccb4a..4cb73b520ec 100644 --- a/apps/server/src/infra/sync/tsp/tsp-legacy-migration.service.ts +++ b/apps/server/src/infra/sync/tsp/tsp-legacy-migration.service.ts @@ -2,6 +2,10 @@ import { EntityManager, ObjectId } from '@mikro-orm/mongodb'; import { Injectable } from '@nestjs/common'; import { EntityId, SchoolFeature } from '@shared/domain/types'; import { Logger } from '@src/core/logger'; +import { TspLegacyMigrationCountLoggable } from './loggable/tsp-legacy-migration-count.loggable'; +import { TspLegacyMigrationStartLoggable } from './loggable/tsp-legacy-migration-start.loggable'; +import { TspLegacyMigrationSuccessLoggable } from './loggable/tsp-legacy-migration-success.loggable'; +import { TspLegacyMigrationSystemMissingLoggable } from './loggable/tsp-legacy-migration-system-missing.loggable'; type LegacyTspSchoolProperties = { sourceOptions: { @@ -21,17 +25,18 @@ export class TspLegacyMigrationService { } public async migrateLegacyData(newSystemId: EntityId): Promise { - console.log('starting legacy migration'); + this.logger.info(new TspLegacyMigrationStartLoggable()); + const legacySystemId = await this.findLegacySystemId(); if (!legacySystemId) { - console.log('No legacy system found'); + this.logger.info(new TspLegacyMigrationSystemMissingLoggable()); return; } const schoolIds = await this.findIdsOfLegacyTspSchools(legacySystemId); - console.log('Number of schools', schoolIds.length); + this.logger.info(new TspLegacyMigrationCountLoggable(schoolIds.length)); const promises = schoolIds.map(async (oldId): Promise => { const filter = { @@ -60,7 +65,8 @@ export class TspLegacyMigrationService { .filter((r) => r.status === 'fulfilled') .map((r) => r.value) .reduce((acc, c) => acc + c, 0); - console.log(`Migrated ${schoolIds.length} legacy schools to new system. ${success} succeeded.`); + + this.logger.info(new TspLegacyMigrationSuccessLoggable(schoolIds.length, success)); } private async findLegacySystemId() { From 715ea09a631341d6cac608ab467fdb8388b05f66 Mon Sep 17 00:00:00 2001 From: Maximilian Kreuzkam Date: Thu, 14 Nov 2024 14:00:57 +0100 Subject: [PATCH 05/14] Write tests. --- ...sp-legacy-migration-count.loggable.spec.ts | 26 +++++ ...sp-legacy-migration-start.loggable.spec.ts | 23 ++++ ...-legacy-migration-success.loggable.spec.ts | 27 +++++ ...-migration-system-missing.loggable.spec.ts | 23 ++++ ...gacy-migration.service.integration.spec.ts | 106 ++++++++++++++++++ .../sync/tsp/tsp-legacy-migration.service.ts | 14 +-- .../infra/sync/tsp/tsp-sync.strategy.spec.ts | 23 ++++ 7 files changed, 235 insertions(+), 7 deletions(-) create mode 100644 apps/server/src/infra/sync/tsp/loggable/tsp-legacy-migration-count.loggable.spec.ts create mode 100644 apps/server/src/infra/sync/tsp/loggable/tsp-legacy-migration-start.loggable.spec.ts create mode 100644 apps/server/src/infra/sync/tsp/loggable/tsp-legacy-migration-success.loggable.spec.ts create mode 100644 apps/server/src/infra/sync/tsp/loggable/tsp-legacy-migration-system-missing.loggable.spec.ts create mode 100644 apps/server/src/infra/sync/tsp/tsp-legacy-migration.service.integration.spec.ts diff --git a/apps/server/src/infra/sync/tsp/loggable/tsp-legacy-migration-count.loggable.spec.ts b/apps/server/src/infra/sync/tsp/loggable/tsp-legacy-migration-count.loggable.spec.ts new file mode 100644 index 00000000000..6e25e37d14b --- /dev/null +++ b/apps/server/src/infra/sync/tsp/loggable/tsp-legacy-migration-count.loggable.spec.ts @@ -0,0 +1,26 @@ +import { TspLegacyMigrationCountLoggable } from './tsp-legacy-migration-count.loggable'; + +describe(TspLegacyMigrationCountLoggable.name, () => { + let loggable: TspLegacyMigrationCountLoggable; + + beforeAll(() => { + loggable = new TspLegacyMigrationCountLoggable(10); + }); + + describe('when loggable is initialized', () => { + it('should be defined', () => { + expect(loggable).toBeDefined(); + }); + }); + + describe('getLogMessage', () => { + it('should return a log message', () => { + expect(loggable.getLogMessage()).toEqual({ + message: `Found 10 legacy tsp schools to migrate`, + data: { + total: 10, + }, + }); + }); + }); +}); diff --git a/apps/server/src/infra/sync/tsp/loggable/tsp-legacy-migration-start.loggable.spec.ts b/apps/server/src/infra/sync/tsp/loggable/tsp-legacy-migration-start.loggable.spec.ts new file mode 100644 index 00000000000..ccba74ee0f8 --- /dev/null +++ b/apps/server/src/infra/sync/tsp/loggable/tsp-legacy-migration-start.loggable.spec.ts @@ -0,0 +1,23 @@ +import { TspLegacyMigrationStartLoggable } from './tsp-legacy-migration-start.loggable'; + +describe(TspLegacyMigrationStartLoggable.name, () => { + let loggable: TspLegacyMigrationStartLoggable; + + beforeAll(() => { + loggable = new TspLegacyMigrationStartLoggable(); + }); + + describe('when loggable is initialized', () => { + it('should be defined', () => { + expect(loggable).toBeDefined(); + }); + }); + + describe('getLogMessage', () => { + it('should return a log message', () => { + expect(loggable.getLogMessage()).toEqual({ + message: 'Running migration of legacy tsp data.', + }); + }); + }); +}); diff --git a/apps/server/src/infra/sync/tsp/loggable/tsp-legacy-migration-success.loggable.spec.ts b/apps/server/src/infra/sync/tsp/loggable/tsp-legacy-migration-success.loggable.spec.ts new file mode 100644 index 00000000000..2cd6056190f --- /dev/null +++ b/apps/server/src/infra/sync/tsp/loggable/tsp-legacy-migration-success.loggable.spec.ts @@ -0,0 +1,27 @@ +import { TspLegacyMigrationSuccessLoggable } from './tsp-legacy-migration-success.loggable'; + +describe(TspLegacyMigrationSuccessLoggable.name, () => { + let loggable: TspLegacyMigrationSuccessLoggable; + + beforeAll(() => { + loggable = new TspLegacyMigrationSuccessLoggable(10, 5); + }); + + describe('when loggable is initialized', () => { + it('should be defined', () => { + expect(loggable).toBeDefined(); + }); + }); + + describe('getLogMessage', () => { + it('should return a log message', () => { + expect(loggable.getLogMessage()).toEqual({ + message: `Legacy tsp data migration finished. Total schools: 10, migrated schools: 5`, + data: { + total: 10, + migrated: 5, + }, + }); + }); + }); +}); diff --git a/apps/server/src/infra/sync/tsp/loggable/tsp-legacy-migration-system-missing.loggable.spec.ts b/apps/server/src/infra/sync/tsp/loggable/tsp-legacy-migration-system-missing.loggable.spec.ts new file mode 100644 index 00000000000..fe7dc8eed89 --- /dev/null +++ b/apps/server/src/infra/sync/tsp/loggable/tsp-legacy-migration-system-missing.loggable.spec.ts @@ -0,0 +1,23 @@ +import { TspLegacyMigrationSystemMissingLoggable } from './tsp-legacy-migration-system-missing.loggable'; + +describe(TspLegacyMigrationSystemMissingLoggable.name, () => { + let loggable: TspLegacyMigrationSystemMissingLoggable; + + beforeAll(() => { + loggable = new TspLegacyMigrationSystemMissingLoggable(); + }); + + describe('when loggable is initialized', () => { + it('should be defined', () => { + expect(loggable).toBeDefined(); + }); + }); + + describe('getLogMessage', () => { + it('should return a log message', () => { + expect(loggable.getLogMessage()).toEqual({ + message: 'No legacy system found', + }); + }); + }); +}); diff --git a/apps/server/src/infra/sync/tsp/tsp-legacy-migration.service.integration.spec.ts b/apps/server/src/infra/sync/tsp/tsp-legacy-migration.service.integration.spec.ts new file mode 100644 index 00000000000..ee52feb75cf --- /dev/null +++ b/apps/server/src/infra/sync/tsp/tsp-legacy-migration.service.integration.spec.ts @@ -0,0 +1,106 @@ +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { EntityManager } from '@mikro-orm/mongodb'; +import { Test, TestingModule } from '@nestjs/testing'; +import { SchoolEntity } from '@shared/domain/entity'; +import { SystemProvisioningStrategy } from '@shared/domain/interface/system-provisioning.strategy'; +import { SchoolFeature } from '@shared/domain/types'; +import { cleanupCollections, schoolEntityFactory, systemEntityFactory } from '@shared/testing'; +import { Logger } from '@src/core/logger'; +import { MongoMemoryDatabaseModule } from '@src/infra/database'; +import { SystemType } from '@src/modules/system'; +import { TspLegacyMigrationSystemMissingLoggable } from './loggable/tsp-legacy-migration-system-missing.loggable'; +import { TspLegacyMigrationService } from './tsp-legacy-migration.service'; + +describe('account repo', () => { + let module: TestingModule; + let em: EntityManager; + let sut: TspLegacyMigrationService; + let logger: DeepMocked; + + beforeAll(async () => { + module = await Test.createTestingModule({ + imports: [MongoMemoryDatabaseModule.forRoot()], + providers: [ + TspLegacyMigrationService, + { + provide: Logger, + useValue: createMock(), + }, + ], + }).compile(); + sut = module.get(TspLegacyMigrationService); + em = module.get(EntityManager); + logger = module.get(Logger); + }); + + afterAll(async () => { + await module.close(); + }); + + afterEach(async () => { + jest.resetAllMocks(); + jest.clearAllMocks(); + jest.restoreAllMocks(); + await cleanupCollections(em); + }); + + describe('migrateLegacyData', () => { + describe('when legacy system is not found', () => { + it('should log TspLegacyMigrationSystemMissingLoggable', async () => { + await sut.migrateLegacyData(''); + + expect(logger.info).toHaveBeenCalledWith(new TspLegacyMigrationSystemMissingLoggable()); + }); + }); + + describe('when migrating legacy data', () => { + const setup = async () => { + const legacySystem = systemEntityFactory.buildWithId({ + type: 'tsp-school', + }); + const newSystem = systemEntityFactory.buildWithId({ + type: SystemType.OAUTH, + provisioningStrategy: SystemProvisioningStrategy.TSP, + }); + + const schoolIdentifier = '123'; + const legacySchool = schoolEntityFactory.buildWithId({ + systems: [legacySystem], + features: [], + }); + + await em.persistAndFlush([legacySystem, newSystem, legacySchool]); + em.clear(); + + await em.getCollection('schools').findOneAndUpdate( + { + systems: [legacySystem._id], + }, + { + $set: { + sourceOptions: { + schoolIdentifier, + }, + source: 'tsp', + }, + } + ); + + return { legacySystem, newSystem, legacySchool, schoolId: schoolIdentifier }; + }; + + it('should update the school to the new format', async () => { + const { newSystem, legacySchool, schoolId: schoolIdentifier } = await setup(); + + await sut.migrateLegacyData(newSystem.id); + + const migratedSchool = await em.findOne(SchoolEntity.name, { + id: legacySchool.id, + }); + expect(migratedSchool?.externalId).toBe(schoolIdentifier); + expect(migratedSchool?.systems[0].id).toBe(newSystem.id); + expect(migratedSchool?.features).toContain(SchoolFeature.OAUTH_PROVISIONING_ENABLED); + }); + }); + }); +}); diff --git a/apps/server/src/infra/sync/tsp/tsp-legacy-migration.service.ts b/apps/server/src/infra/sync/tsp/tsp-legacy-migration.service.ts index 4cb73b520ec..dce20a5d0ca 100644 --- a/apps/server/src/infra/sync/tsp/tsp-legacy-migration.service.ts +++ b/apps/server/src/infra/sync/tsp/tsp-legacy-migration.service.ts @@ -39,7 +39,7 @@ export class TspLegacyMigrationService { this.logger.info(new TspLegacyMigrationCountLoggable(schoolIds.length)); const promises = schoolIds.map(async (oldId): Promise => { - const filter = { + const legacySchoolFilter = { systems: [legacySystemId], source: TSP_LEGACY_SOURCE_TYPE, sourceOptions: { @@ -47,12 +47,12 @@ export class TspLegacyMigrationService { }, }; - const featureUpdateCount = await this.em.nativeUpdate(SCHOOLS_COLLECTION, filter, { + const featureUpdateCount = await this.em.nativeUpdate(SCHOOLS_COLLECTION, legacySchoolFilter, { $addToSet: { features: SchoolFeature.OAUTH_PROVISIONING_ENABLED, }, }); - const idUpdateCount = await this.em.nativeUpdate(SCHOOLS_COLLECTION, filter, { + const idUpdateCount = await this.em.nativeUpdate(SCHOOLS_COLLECTION, legacySchoolFilter, { ldapSchoolIdentifier: oldId, systems: [new ObjectId(newSystemId)], }); @@ -60,13 +60,13 @@ export class TspLegacyMigrationService { return featureUpdateCount === 1 && idUpdateCount === 1 ? 1 : 0; }); - const res = await Promise.allSettled(promises); - const success = res + const results = await Promise.allSettled(promises); + const successfulMigrations = results .filter((r) => r.status === 'fulfilled') .map((r) => r.value) - .reduce((acc, c) => acc + c, 0); + .reduce((previousValue, currentValue) => previousValue + currentValue, 0); - this.logger.info(new TspLegacyMigrationSuccessLoggable(schoolIds.length, success)); + this.logger.info(new TspLegacyMigrationSuccessLoggable(schoolIds.length, successfulMigrations)); } private async findLegacySystemId() { diff --git a/apps/server/src/infra/sync/tsp/tsp-sync.strategy.spec.ts b/apps/server/src/infra/sync/tsp/tsp-sync.strategy.spec.ts index c7877e9977c..cff0982d109 100644 --- a/apps/server/src/infra/sync/tsp/tsp-sync.strategy.spec.ts +++ b/apps/server/src/infra/sync/tsp/tsp-sync.strategy.spec.ts @@ -7,7 +7,9 @@ import { SystemProvisioningStrategy } from '@shared/domain/interface/system-prov import { Logger } from '@src/core/logger'; import { ExternalUserDto, OauthDataDto, ProvisioningService, ProvisioningSystemDto } from '@src/modules/provisioning'; import { schoolFactory } from '@src/modules/school/testing'; +import { systemFactory } from '@src/modules/system/testing'; import { SyncStrategyTarget } from '../sync-strategy.types'; +import { TspLegacyMigrationService } from './tsp-legacy-migration.service'; import { TspOauthDataMapper } from './tsp-oauth-data.mapper'; import { TspSyncConfig } from './tsp-sync.config'; import { TspSyncService } from './tsp-sync.service'; @@ -19,6 +21,7 @@ describe(TspSyncStrategy.name, () => { let tspSyncService: DeepMocked; let provisioningService: DeepMocked; let tspOauthDataMapper: DeepMocked; + let tspLegacyMigrationService: DeepMocked; beforeAll(async () => { module = await Test.createTestingModule({ @@ -59,6 +62,10 @@ describe(TspSyncStrategy.name, () => { provide: TspOauthDataMapper, useValue: createMock(), }, + { + provide: TspLegacyMigrationService, + useValue: createMock(), + }, ], }).compile(); @@ -66,6 +73,7 @@ describe(TspSyncStrategy.name, () => { tspSyncService = module.get(TspSyncService); provisioningService = module.get(ProvisioningService); tspOauthDataMapper = module.get(TspOauthDataMapper); + tspLegacyMigrationService = module.get(TspLegacyMigrationService); }); afterEach(() => { @@ -94,6 +102,7 @@ describe(TspSyncStrategy.name, () => { describe('sync', () => { describe('when sync is called', () => { const setup = () => { + tspSyncService.findTspSystemOrFail.mockResolvedValueOnce(systemFactory.build()); tspSyncService.fetchTspSchools.mockResolvedValueOnce([]); tspSyncService.fetchTspClasses.mockResolvedValueOnce([]); tspSyncService.fetchTspStudents.mockResolvedValueOnce([]); @@ -121,6 +130,14 @@ describe(TspSyncStrategy.name, () => { expect(tspSyncService.findTspSystemOrFail).toHaveBeenCalled(); }); + it('should migrate the legacy data', async () => { + setup(); + + await sut.sync(); + + expect(tspLegacyMigrationService.migrateLegacyData).toHaveBeenCalled(); + }); + it('should fetch the schools', async () => { setup(); @@ -166,6 +183,8 @@ describe(TspSyncStrategy.name, () => { describe('when school does not exist', () => { const setup = () => { + tspSyncService.findTspSystemOrFail.mockResolvedValueOnce(systemFactory.build()); + const tspSchool: RobjExportSchule = { schuleNummer: faker.string.alpha(), schuleName: faker.string.alpha(), @@ -193,6 +212,8 @@ describe(TspSyncStrategy.name, () => { describe('when school does exist', () => { const setup = () => { + tspSyncService.findTspSystemOrFail.mockResolvedValueOnce(systemFactory.build()); + const tspSchool: RobjExportSchule = { schuleNummer: faker.string.alpha(), schuleName: faker.string.alpha(), @@ -221,6 +242,8 @@ describe(TspSyncStrategy.name, () => { describe('when tsp school does not have a schulnummer', () => { const setup = () => { + tspSyncService.findTspSystemOrFail.mockResolvedValueOnce(systemFactory.build()); + const tspSchool: RobjExportSchule = { schuleNummer: undefined, schuleName: faker.string.alpha(), From 910abd73598cc8b90c4f0f6b02076b7f4e62689e Mon Sep 17 00:00:00 2001 From: Maximilian Kreuzkam Date: Thu, 14 Nov 2024 14:45:33 +0100 Subject: [PATCH 06/14] remove try catch. --- .../src/infra/sync/tsp/tsp-sync.service.ts | 89 ++++++------------- 1 file changed, 28 insertions(+), 61 deletions(-) diff --git a/apps/server/src/infra/sync/tsp/tsp-sync.service.ts b/apps/server/src/infra/sync/tsp/tsp-sync.service.ts index 62588c461b2..ba2bb2b79a7 100644 --- a/apps/server/src/infra/sync/tsp/tsp-sync.service.ts +++ b/apps/server/src/infra/sync/tsp/tsp-sync.service.ts @@ -9,7 +9,6 @@ import { FederalStateNames } from '@src/modules/legacy-school/types'; import { FederalState, FileStorageType } from '@src/modules/school/domain'; import { SchoolFactory } from '@src/modules/school/domain/factory'; import { FederalStateEntityMapper, SchoolYearEntityMapper } from '@src/modules/school/repo/mikro-orm/mapper'; -import { AxiosError } from 'axios'; import { ObjectId } from 'bson'; import moment from 'moment/moment'; import { TspSystemNotFoundLoggableException } from './loggable/tsp-system-not-found.loggable-exception'; @@ -41,75 +40,43 @@ export class TspSyncService { } public async fetchTspSchools(system: System, daysToFetch: number) { - try { - const client = this.createClient(system); - - const lastChangeDate = this.formatChangeDate(daysToFetch); - const schoolsResponse = await client.exportSchuleList(lastChangeDate); - const schools = schoolsResponse.data; - - return schools; - } catch (e) { - console.log('fetchTspSchools does not work'); - if (e instanceof AxiosError) { - console.log(e.response?.data); - } - } - return []; + const client = this.createClient(system); + + const lastChangeDate = this.formatChangeDate(daysToFetch); + const schoolsResponse = await client.exportSchuleList(lastChangeDate); + const schools = schoolsResponse.data; + + return schools; } public async fetchTspTeachers(system: System, daysToFetch: number) { - try { - const client = this.createClient(system); - - const lastChangeDate = this.formatChangeDate(daysToFetch); - const teachersResponse = await client.exportLehrerList(lastChangeDate); - const teachers = teachersResponse.data; - - return teachers; - } catch (e) { - console.log('fetchTspTeachers does not work'); - if (e instanceof AxiosError) { - console.log(e.response?.data); - } - } - return []; + const client = this.createClient(system); + + const lastChangeDate = this.formatChangeDate(daysToFetch); + const teachersResponse = await client.exportLehrerList(lastChangeDate); + const teachers = teachersResponse.data; + + return teachers; } public async fetchTspStudents(system: System, daysToFetch: number) { - try { - const client = this.createClient(system); - - const lastChangeDate = this.formatChangeDate(daysToFetch); - const studentsResponse = await client.exportSchuelerList(lastChangeDate); - const students = studentsResponse.data; - - return students; - } catch (e) { - console.log('fetchTspStudents does not work'); - if (e instanceof AxiosError) { - console.log(e.response?.data); - } - } - return []; + const client = this.createClient(system); + + const lastChangeDate = this.formatChangeDate(daysToFetch); + const studentsResponse = await client.exportSchuelerList(lastChangeDate); + const students = studentsResponse.data; + + return students; } public async fetchTspClasses(system: System, daysToFetch: number) { - try { - const client = this.createClient(system); - - const lastChangeDate = this.formatChangeDate(daysToFetch); - const classesResponse = await client.exportKlasseList(lastChangeDate); - const classes = classesResponse.data; - - return classes; - } catch (e) { - console.log('fetchTspClasses does not work'); - if (e instanceof AxiosError) { - console.log(e.response?.data); - } - } - return []; + const client = this.createClient(system); + + const lastChangeDate = this.formatChangeDate(daysToFetch); + const classesResponse = await client.exportKlasseList(lastChangeDate); + const classes = classesResponse.data; + + return classes; } public async findSchool(system: System, identifier: string): Promise { From 1bc065d2cf109bb3c0ca88a67b70ce5ac7a5f33d Mon Sep 17 00:00:00 2001 From: Simone Radtke Date: Fri, 15 Nov 2024 13:31:09 +0100 Subject: [PATCH 07/14] EW-1052 Add consent to new tsp user --- .../service/tsp-provisioning.service.ts | 33 +++++++++++++++++++ .../src/shared/domain/domainobject/consent.ts | 13 ++++++++ .../domain/domainobject/parent-consent.ts | 24 ++++++++++++++ .../domain/domainobject/user-consent.ts | 19 +++++++++++ .../src/shared/domain/domainobject/user.do.ts | 4 +++ 5 files changed, 93 insertions(+) create mode 100644 apps/server/src/shared/domain/domainobject/consent.ts create mode 100644 apps/server/src/shared/domain/domainobject/parent-consent.ts create mode 100644 apps/server/src/shared/domain/domainobject/user-consent.ts diff --git a/apps/server/src/modules/provisioning/service/tsp-provisioning.service.ts b/apps/server/src/modules/provisioning/service/tsp-provisioning.service.ts index 1efb774d9de..ba34a9cd2d0 100644 --- a/apps/server/src/modules/provisioning/service/tsp-provisioning.service.ts +++ b/apps/server/src/modules/provisioning/service/tsp-provisioning.service.ts @@ -4,9 +4,13 @@ import { RoleService } from '@modules/role'; import { Injectable } from '@nestjs/common'; import { NotFoundLoggableException } from '@shared/common/loggable-exception'; import { RoleReference, UserDO } from '@shared/domain/domainobject'; +import { Consent } from '@shared/domain/domainobject/consent'; +import { ParentConsent } from '@shared/domain/domainobject/parent-consent'; +import { UserConsent } from '@shared/domain/domainobject/user-consent'; import { RoleName } from '@shared/domain/interface'; import { School, SchoolService } from '@src/modules/school'; import { UserService } from '@src/modules/user'; +import { ObjectId } from 'bson'; import { ExternalClassDto, ExternalSchoolDto, ExternalUserDto, OauthDataDto, ProvisioningSystemDto } from '../dto'; import { BadDataLoggableException } from '../loggable'; @@ -128,6 +132,9 @@ export class TspProvisioningService { birthday: externalUser.birthday, externalId: externalUser.externalId, }); + + this.createTspConsent(newUser); + const savedUser = await this.userService.save(newUser); return savedUser; @@ -167,4 +174,30 @@ export class TspProvisioningService { return email.toLowerCase(); } + + private createTspConsent(user: UserDO) { + const userConsent = new UserConsent({ + form: 'digital', + privacyConsent: true, + termsOfUseConsent: true, + dateOfPrivacyConsent: new Date(), + dateOfTermsOfUseConsent: new Date(), + }); + + const parentConsent = new ParentConsent({ + id: new ObjectId().toString(), + form: 'digital', + privacyConsent: true, + termsOfUseConsent: true, + dateOfPrivacyConsent: new Date(), + dateOfTermsOfUseConsent: new Date(), + }); + + const consent = new Consent({ + userConsent, + parentConsent: [parentConsent], + }); + + user.consent = consent; + } } diff --git a/apps/server/src/shared/domain/domainobject/consent.ts b/apps/server/src/shared/domain/domainobject/consent.ts new file mode 100644 index 00000000000..5094bb9befb --- /dev/null +++ b/apps/server/src/shared/domain/domainobject/consent.ts @@ -0,0 +1,13 @@ +import { ParentConsent } from './parent-consent'; +import { UserConsent } from './user-consent'; + +export class Consent { + userConsent: UserConsent; + + parentConsent: ParentConsent[]; + + constructor(props: Consent) { + this.userConsent = props.userConsent; + this.parentConsent = props.parentConsent; + } +} diff --git a/apps/server/src/shared/domain/domainobject/parent-consent.ts b/apps/server/src/shared/domain/domainobject/parent-consent.ts new file mode 100644 index 00000000000..0c3a294dc36 --- /dev/null +++ b/apps/server/src/shared/domain/domainobject/parent-consent.ts @@ -0,0 +1,24 @@ +import { EntityId } from '../types'; + +export class ParentConsent { + id: EntityId; + + form: string; + + privacyConsent: boolean; + + termsOfUseConsent: boolean; + + dateOfPrivacyConsent: Date; + + dateOfTermsOfUseConsent: Date; + + constructor(props: ParentConsent) { + this.id = props.id; + this.form = props.form; + this.privacyConsent = props.privacyConsent; + this.termsOfUseConsent = props.termsOfUseConsent; + this.dateOfPrivacyConsent = props.dateOfPrivacyConsent; + this.dateOfTermsOfUseConsent = props.dateOfTermsOfUseConsent; + } +} diff --git a/apps/server/src/shared/domain/domainobject/user-consent.ts b/apps/server/src/shared/domain/domainobject/user-consent.ts new file mode 100644 index 00000000000..419e6d73327 --- /dev/null +++ b/apps/server/src/shared/domain/domainobject/user-consent.ts @@ -0,0 +1,19 @@ +export class UserConsent { + form: string; + + privacyConsent: boolean; + + termsOfUseConsent: boolean; + + dateOfPrivacyConsent: Date; + + dateOfTermsOfUseConsent: Date; + + constructor(props: UserConsent) { + this.form = props.form; + this.privacyConsent = props.privacyConsent; + this.termsOfUseConsent = props.termsOfUseConsent; + this.dateOfPrivacyConsent = props.dateOfPrivacyConsent; + this.dateOfTermsOfUseConsent = props.dateOfTermsOfUseConsent; + } +} diff --git a/apps/server/src/shared/domain/domainobject/user.do.ts b/apps/server/src/shared/domain/domainobject/user.do.ts index b98e9303210..a73adb14b6c 100644 --- a/apps/server/src/shared/domain/domainobject/user.do.ts +++ b/apps/server/src/shared/domain/domainobject/user.do.ts @@ -1,6 +1,7 @@ import { LanguageType } from '@shared/domain/interface'; import { EntityId } from '@shared/domain/types'; import { BaseDO } from './base.do'; +import { Consent } from './consent'; import { RoleReference } from './role-reference'; export class UserDO extends BaseDO { @@ -50,6 +51,8 @@ export class UserDO extends BaseDO { birthday?: Date; + consent?: Consent; + constructor(domainObject: UserDO) { super(domainObject.id); @@ -76,5 +79,6 @@ export class UserDO extends BaseDO { this.outdatedSince = domainObject.outdatedSince; this.previousExternalId = domainObject.previousExternalId; this.birthday = domainObject.birthday; + this.consent = domainObject.consent; } } From 31a07aaec3c655544bd4093ddc107793e933d8aa Mon Sep 17 00:00:00 2001 From: Simone Radtke Date: Fri, 15 Nov 2024 14:50:29 +0100 Subject: [PATCH 08/14] EW-1052 Add permissions to new tsp schools --- apps/server/src/infra/sync/tsp/tsp-sync.service.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/apps/server/src/infra/sync/tsp/tsp-sync.service.ts b/apps/server/src/infra/sync/tsp/tsp-sync.service.ts index e456d2c8c90..fbaf8bb5f3b 100644 --- a/apps/server/src/infra/sync/tsp/tsp-sync.service.ts +++ b/apps/server/src/infra/sync/tsp/tsp-sync.service.ts @@ -11,6 +11,7 @@ import { FederalStateNames } from '@src/modules/legacy-school/types'; import { OauthConfigMissingLoggableException } from '@src/modules/oauth/loggable'; import { FederalState, FileStorageType } from '@src/modules/school/domain'; import { SchoolFactory } from '@src/modules/school/domain/factory'; +import { SchoolPermissions } from '@src/modules/school/domain/type'; import { FederalStateEntityMapper, SchoolYearEntityMapper } from '@src/modules/school/repo/mikro-orm/mapper'; import { UserService } from '@src/modules/user'; import { ObjectId } from 'bson'; @@ -122,6 +123,12 @@ export class TspSyncService { const schoolYear = SchoolYearEntityMapper.mapToDo(schoolYearEntity); const federalState = await this.findFederalState(); + const permissions: SchoolPermissions = { + teacher: { + STUDENT_LIST: true, + }, + }; + const school = SchoolFactory.build({ externalId: identifier, name, @@ -133,6 +140,7 @@ export class TspSyncService { updatedAt: new Date(), id: new ObjectId().toHexString(), fileStorageType: FileStorageType.AWS_S3, + permissions, }); const savedSchool = await this.schoolService.save(school); From 25f0df59bc9974c8b7e8a4540d382eca59a840a0 Mon Sep 17 00:00:00 2001 From: Simone Radtke Date: Mon, 18 Nov 2024 13:26:04 +0100 Subject: [PATCH 09/14] EW-1052 Set source options during migration --- .../src/infra/sync/tsp/tsp-sync.service.ts | 2 + .../user-source-options.do.spec.ts | 43 +++++++++++++++++++ .../domainobject/user-source-options.do.ts | 15 +++++++ .../src/shared/domain/domainobject/user.do.ts | 4 ++ .../src/shared/repo/user/user-do.repo.ts | 2 + 5 files changed, 66 insertions(+) create mode 100644 apps/server/src/shared/domain/domainobject/user-source-options.do.spec.ts create mode 100644 apps/server/src/shared/domain/domainobject/user-source-options.do.ts diff --git a/apps/server/src/infra/sync/tsp/tsp-sync.service.ts b/apps/server/src/infra/sync/tsp/tsp-sync.service.ts index fbaf8bb5f3b..51d33a0ba29 100644 --- a/apps/server/src/infra/sync/tsp/tsp-sync.service.ts +++ b/apps/server/src/infra/sync/tsp/tsp-sync.service.ts @@ -4,6 +4,7 @@ import { School, SchoolService } from '@modules/school'; import { System, SystemService, SystemType } from '@modules/system'; import { Injectable } from '@nestjs/common'; import { UserDO } from '@shared/domain/domainobject'; +import { UserSourceOptions } from '@shared/domain/domainobject/user-source-options.do'; import { SystemProvisioningStrategy } from '@shared/domain/interface/system-provisioning.strategy'; import { SchoolFeature } from '@shared/domain/types'; import { Account, AccountService } from '@src/modules/account'; @@ -207,6 +208,7 @@ export class TspSyncService { user.email = email; user.externalId = externalId; user.previousExternalId = previousExternalId; + user.sourceOptions = new UserSourceOptions({ tspUid: user.externalId }); return this.userService.save(user); } diff --git a/apps/server/src/shared/domain/domainobject/user-source-options.do.spec.ts b/apps/server/src/shared/domain/domainobject/user-source-options.do.spec.ts new file mode 100644 index 00000000000..da26728ab83 --- /dev/null +++ b/apps/server/src/shared/domain/domainobject/user-source-options.do.spec.ts @@ -0,0 +1,43 @@ +import { UserSourceOptions } from './user-source-options.do'; + +describe(UserSourceOptions.name, () => { + describe('constructor', () => { + describe('When a constructor is called', () => { + const setup = () => { + const domainObject = new UserSourceOptions({ tspUid: '12345' }); + + return { domainObject }; + }; + + it('should create empty object', () => { + const domainObject = new UserSourceOptions({}); + + expect(domainObject).toEqual(expect.objectContaining({})); + }); + + it('should contain valid tspUid ', () => { + const { domainObject } = setup(); + + const userSourceOptionsDo: UserSourceOptions = new UserSourceOptions(domainObject); + + expect(userSourceOptionsDo.tspUid).toEqual(domainObject.tspUid); + }); + }); + }); + describe('getters', () => { + describe('When getters are used', () => { + it('getters should return proper value', () => { + const props = { + tspUid: '12345', + }; + + const userSourceOptionsDo = new UserSourceOptions(props); + const gettersValues = { + tspUid: userSourceOptionsDo.tspUid, + }; + + expect(gettersValues).toEqual(props); + }); + }); + }); +}); diff --git a/apps/server/src/shared/domain/domainobject/user-source-options.do.ts b/apps/server/src/shared/domain/domainobject/user-source-options.do.ts new file mode 100644 index 00000000000..49ab42d5071 --- /dev/null +++ b/apps/server/src/shared/domain/domainobject/user-source-options.do.ts @@ -0,0 +1,15 @@ +export interface UserSourceOptionsProps { + tspUid?: string; +} + +export class UserSourceOptions { + protected props: UserSourceOptionsProps; + + constructor(props: UserSourceOptionsProps) { + this.props = props; + } + + get tspUid(): string | undefined { + return this.props.tspUid; + } +} diff --git a/apps/server/src/shared/domain/domainobject/user.do.ts b/apps/server/src/shared/domain/domainobject/user.do.ts index a73adb14b6c..a100fc41149 100644 --- a/apps/server/src/shared/domain/domainobject/user.do.ts +++ b/apps/server/src/shared/domain/domainobject/user.do.ts @@ -3,6 +3,7 @@ import { EntityId } from '@shared/domain/types'; import { BaseDO } from './base.do'; import { Consent } from './consent'; import { RoleReference } from './role-reference'; +import { UserSourceOptions } from './user-source-options.do'; export class UserDO extends BaseDO { createdAt?: Date; @@ -53,6 +54,8 @@ export class UserDO extends BaseDO { consent?: Consent; + sourceOptions?: UserSourceOptions; + constructor(domainObject: UserDO) { super(domainObject.id); @@ -80,5 +83,6 @@ export class UserDO extends BaseDO { this.previousExternalId = domainObject.previousExternalId; this.birthday = domainObject.birthday; this.consent = domainObject.consent; + this.sourceOptions = domainObject.sourceOptions; } } diff --git a/apps/server/src/shared/repo/user/user-do.repo.ts b/apps/server/src/shared/repo/user/user-do.repo.ts index 265463f4335..208c5c498ba 100644 --- a/apps/server/src/shared/repo/user/user-do.repo.ts +++ b/apps/server/src/shared/repo/user/user-do.repo.ts @@ -4,6 +4,7 @@ import { UserQuery } from '@modules/user/service/user-query.type'; import { Injectable } from '@nestjs/common'; import { EntityNotFoundError } from '@shared/common'; import { Page, RoleReference } from '@shared/domain/domainobject'; +import { UserSourceOptions } from '@shared/domain/domainobject/user-source-options.do'; import { UserDO } from '@shared/domain/domainobject/user.do'; import { Role, SchoolEntity, User } from '@shared/domain/entity'; import { IFindOptions, Pagination, SortOrder, SortOrderMap } from '@shared/domain/interface'; @@ -151,6 +152,7 @@ export class UserDORepo extends BaseDORepo { outdatedSince: entity.outdatedSince, previousExternalId: entity.previousExternalId, birthday: entity.birthday, + sourceOptions: entity.sourceOptions ? new UserSourceOptions({ tspUid: entity.sourceOptions.tspUid }) : undefined, }); if (entity.roles.isInitialized()) { From 946dfdcaaa73aa0999d7616c7b8df4c026b455db Mon Sep 17 00:00:00 2001 From: Maximilian Kreuzkam Date: Mon, 18 Nov 2024 16:21:08 +0100 Subject: [PATCH 10/14] add mapping do to entity. --- apps/server/src/shared/repo/user/user-do.repo.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/server/src/shared/repo/user/user-do.repo.ts b/apps/server/src/shared/repo/user/user-do.repo.ts index 208c5c498ba..3251be09ff5 100644 --- a/apps/server/src/shared/repo/user/user-do.repo.ts +++ b/apps/server/src/shared/repo/user/user-do.repo.ts @@ -7,6 +7,7 @@ import { Page, RoleReference } from '@shared/domain/domainobject'; import { UserSourceOptions } from '@shared/domain/domainobject/user-source-options.do'; import { UserDO } from '@shared/domain/domainobject/user.do'; import { Role, SchoolEntity, User } from '@shared/domain/entity'; +import { UserSourceOptionsEntity } from '@shared/domain/entity/user-source-options-entity'; import { IFindOptions, Pagination, SortOrder, SortOrderMap } from '@shared/domain/interface'; import { EntityId } from '@shared/domain/types'; import { BaseDORepo, Scope } from '@shared/repo'; @@ -181,6 +182,9 @@ export class UserDORepo extends BaseDORepo { outdatedSince: entityDO.outdatedSince, previousExternalId: entityDO.previousExternalId, birthday: entityDO.birthday, + sourceOptions: entityDO.sourceOptions + ? new UserSourceOptionsEntity({ tspUid: entityDO.sourceOptions.tspUid }) + : undefined, }; } From fdb92bc1ccb4aea587ba5d9089498b5fd568e232 Mon Sep 17 00:00:00 2001 From: Maximilian Kreuzkam Date: Tue, 19 Nov 2024 08:34:41 +0100 Subject: [PATCH 11/14] Search for account by externalId of user. --- .../infra/sync/tsp/tsp-sync.service.spec.ts | 29 ++++++++++--------- .../src/infra/sync/tsp/tsp-sync.service.ts | 6 ++-- .../infra/sync/tsp/tsp-sync.strategy.spec.ts | 6 ++-- .../src/infra/sync/tsp/tsp-sync.strategy.ts | 2 +- 4 files changed, 23 insertions(+), 20 deletions(-) diff --git a/apps/server/src/infra/sync/tsp/tsp-sync.service.spec.ts b/apps/server/src/infra/sync/tsp/tsp-sync.service.spec.ts index 393d078d392..0ae641bdde0 100644 --- a/apps/server/src/infra/sync/tsp/tsp-sync.service.spec.ts +++ b/apps/server/src/infra/sync/tsp/tsp-sync.service.spec.ts @@ -652,26 +652,29 @@ describe(TspSyncService.name, () => { }); }); - describe('findAccountByTspUid', () => { + describe('findAccountByExternalId', () => { describe('when account is found', () => { const setup = () => { - const tspUid = faker.string.alpha(); + const externalId = faker.string.alpha(); + const systemId = faker.string.alpha(); + const user = userDoFactory.build(); const account = accountDoFactory.build(); - user.id = tspUid; + user.id = faker.string.alpha(); + user.externalId = externalId; account.userId = user.id; - userService.findUsers.mockResolvedValueOnce({ data: [user], total: 1 }); + userService.findByExternalId.mockResolvedValueOnce(user); accountService.findByUserId.mockResolvedValueOnce(account); - return { tspUid, account }; + return { externalId, systemId, account }; }; it('should return the account', async () => { - const { tspUid, account } = setup(); + const { externalId, systemId, account } = setup(); - const result = await sut.findAccountByTspUid(tspUid); + const result = await sut.findAccountByExternalId(externalId, systemId); expect(result).toBe(account); }); @@ -679,19 +682,19 @@ describe(TspSyncService.name, () => { describe('when account is not found', () => { const setup = () => { - const tspUid = faker.string.alpha(); - const user = userDoFactory.build(); + const externalId = faker.string.alpha(); + const systemId = faker.string.alpha(); - userService.findUsers.mockResolvedValueOnce({ data: [user], total: 0 }); + userService.findByExternalId.mockResolvedValueOnce(null); accountService.findByUserId.mockResolvedValueOnce(null); - return { tspUid }; + return { externalId, systemId }; }; it('should return null', async () => { - const { tspUid } = setup(); + const { externalId, systemId } = setup(); - const result = await sut.findAccountByTspUid(tspUid); + const result = await sut.findAccountByExternalId(externalId, systemId); expect(result).toBeNull(); }); diff --git a/apps/server/src/infra/sync/tsp/tsp-sync.service.ts b/apps/server/src/infra/sync/tsp/tsp-sync.service.ts index 51d33a0ba29..d6939d0bc5c 100644 --- a/apps/server/src/infra/sync/tsp/tsp-sync.service.ts +++ b/apps/server/src/infra/sync/tsp/tsp-sync.service.ts @@ -6,7 +6,7 @@ import { Injectable } from '@nestjs/common'; import { UserDO } from '@shared/domain/domainobject'; import { UserSourceOptions } from '@shared/domain/domainobject/user-source-options.do'; import { SystemProvisioningStrategy } from '@shared/domain/interface/system-provisioning.strategy'; -import { SchoolFeature } from '@shared/domain/types'; +import { EntityId, SchoolFeature } from '@shared/domain/types'; import { Account, AccountService } from '@src/modules/account'; import { FederalStateNames } from '@src/modules/legacy-school/types'; import { OauthConfigMissingLoggableException } from '@src/modules/oauth/loggable'; @@ -187,8 +187,8 @@ export class TspSyncService { return tspUser.data[0]; } - public async findAccountByTspUid(tspUid: string): Promise { - const user = await this.findUserByTspUid(tspUid); + public async findAccountByExternalId(externalId: string, systemId: EntityId): Promise { + const user = await this.userService.findByExternalId(externalId, systemId); if (!user || !user.id) { return null; diff --git a/apps/server/src/infra/sync/tsp/tsp-sync.strategy.spec.ts b/apps/server/src/infra/sync/tsp/tsp-sync.strategy.spec.ts index 71d820f729e..1ec6601a90b 100644 --- a/apps/server/src/infra/sync/tsp/tsp-sync.strategy.spec.ts +++ b/apps/server/src/infra/sync/tsp/tsp-sync.strategy.spec.ts @@ -141,7 +141,7 @@ describe(TspSyncStrategy.name, () => { tspSyncService.fetchTspStudentMigrations.mockResolvedValueOnce([tspStudent]); tspSyncService.findUserByTspUid.mockResolvedValueOnce(userDoFactory.build()); tspSyncService.updateUser.mockResolvedValueOnce(userDoFactory.build()); - tspSyncService.findAccountByTspUid.mockResolvedValueOnce(accountDoFactory.build()); + tspSyncService.findAccountByExternalId.mockResolvedValueOnce(accountDoFactory.build()); tspSyncService.updateAccount.mockResolvedValueOnce(accountDoFactory.build()); tspSyncService.findTspSystemOrFail.mockResolvedValueOnce(systemFactory.build()); @@ -243,7 +243,7 @@ describe(TspSyncStrategy.name, () => { await sut.sync(); - expect(tspSyncService.findAccountByTspUid).toHaveBeenCalled(); + expect(tspSyncService.findAccountByExternalId).toHaveBeenCalled(); }); it('should update account', async () => { @@ -421,7 +421,7 @@ describe(TspSyncStrategy.name, () => { }; tspSyncService.fetchTspTeacherMigrations.mockResolvedValueOnce([tspTeacher]); - tspSyncService.findAccountByTspUid.mockResolvedValueOnce(null); + tspSyncService.findAccountByExternalId.mockResolvedValueOnce(null); tspSyncService.fetchTspSchools.mockResolvedValueOnce([]); tspSyncService.fetchTspClasses.mockResolvedValueOnce([]); diff --git a/apps/server/src/infra/sync/tsp/tsp-sync.strategy.ts b/apps/server/src/infra/sync/tsp/tsp-sync.strategy.ts index 6897cd29d19..c643073a7a8 100644 --- a/apps/server/src/infra/sync/tsp/tsp-sync.strategy.ts +++ b/apps/server/src/infra/sync/tsp/tsp-sync.strategy.ts @@ -208,7 +208,7 @@ export class TspSyncStrategy extends SyncStrategy { const newEmail = newEmailAndUsername; const updatedUser = await this.tspSyncService.updateUser(user, newEmail, newUid, oldUid); - const account = await this.tspSyncService.findAccountByTspUid(oldUid); + const account = await this.tspSyncService.findAccountByExternalId(newUid, systemId); if (!account) { throw new NotFoundLoggableException(Account.name, { oldUid }); From e5dcec939c7800a12deb31b065e23891453eaeec Mon Sep 17 00:00:00 2001 From: Maximilian Kreuzkam Date: Tue, 19 Nov 2024 11:44:48 +0100 Subject: [PATCH 12/14] change restart policy --- .../templates/api-tsp-sync-init.yml.j2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ansible/roles/schulcloud-server-tspsync/templates/api-tsp-sync-init.yml.j2 b/ansible/roles/schulcloud-server-tspsync/templates/api-tsp-sync-init.yml.j2 index 3ec8144a7d8..6c126f8b981 100644 --- a/ansible/roles/schulcloud-server-tspsync/templates/api-tsp-sync-init.yml.j2 +++ b/ansible/roles/schulcloud-server-tspsync/templates/api-tsp-sync-init.yml.j2 @@ -50,5 +50,5 @@ spec: requests: cpu: {{ API_CPU_REQUESTS|default("100m", true) }} memory: {{ API_MEMORY_REQUESTS|default("150Mi", true) }} - restartPolicy: Never + restartPolicy: OnFailure backoffLimit: 5 From 8141a1a7224c8d139aaa25a1078ce8d623809350 Mon Sep 17 00:00:00 2001 From: Simone Radtke Date: Wed, 20 Nov 2024 11:33:52 +0100 Subject: [PATCH 13/14] EW-1052 Rename loggables --- ...=> tsp-legacy-school-migration-count.loggable.spec.ts} | 8 ++++---- ...e.ts => tsp-legacy-school-migration-count.loggable.ts} | 2 +- ... tsp-legacy-school-migration-success.loggable.spec.ts} | 8 ++++---- ...ts => tsp-legacy-school-migration-success.loggable.ts} | 2 +- .../src/infra/sync/tsp/tsp-legacy-migration.service.ts | 8 ++++---- 5 files changed, 14 insertions(+), 14 deletions(-) rename apps/server/src/infra/sync/tsp/loggable/{tsp-legacy-migration-count.loggable.spec.ts => tsp-legacy-school-migration-count.loggable.spec.ts} (58%) rename apps/server/src/infra/sync/tsp/loggable/{tsp-legacy-migration-count.loggable.ts => tsp-legacy-school-migration-count.loggable.ts} (80%) rename apps/server/src/infra/sync/tsp/loggable/{tsp-legacy-migration-success.loggable.spec.ts => tsp-legacy-school-migration-success.loggable.spec.ts} (60%) rename apps/server/src/infra/sync/tsp/loggable/{tsp-legacy-migration-success.loggable.ts => tsp-legacy-school-migration-success.loggable.ts} (84%) diff --git a/apps/server/src/infra/sync/tsp/loggable/tsp-legacy-migration-count.loggable.spec.ts b/apps/server/src/infra/sync/tsp/loggable/tsp-legacy-school-migration-count.loggable.spec.ts similarity index 58% rename from apps/server/src/infra/sync/tsp/loggable/tsp-legacy-migration-count.loggable.spec.ts rename to apps/server/src/infra/sync/tsp/loggable/tsp-legacy-school-migration-count.loggable.spec.ts index 6e25e37d14b..8d5bad881a2 100644 --- a/apps/server/src/infra/sync/tsp/loggable/tsp-legacy-migration-count.loggable.spec.ts +++ b/apps/server/src/infra/sync/tsp/loggable/tsp-legacy-school-migration-count.loggable.spec.ts @@ -1,10 +1,10 @@ -import { TspLegacyMigrationCountLoggable } from './tsp-legacy-migration-count.loggable'; +import { TspLegacySchoolMigrationCountLoggable } from './tsp-legacy-school-migration-count.loggable'; -describe(TspLegacyMigrationCountLoggable.name, () => { - let loggable: TspLegacyMigrationCountLoggable; +describe(TspLegacySchoolMigrationCountLoggable.name, () => { + let loggable: TspLegacySchoolMigrationCountLoggable; beforeAll(() => { - loggable = new TspLegacyMigrationCountLoggable(10); + loggable = new TspLegacySchoolMigrationCountLoggable(10); }); describe('when loggable is initialized', () => { diff --git a/apps/server/src/infra/sync/tsp/loggable/tsp-legacy-migration-count.loggable.ts b/apps/server/src/infra/sync/tsp/loggable/tsp-legacy-school-migration-count.loggable.ts similarity index 80% rename from apps/server/src/infra/sync/tsp/loggable/tsp-legacy-migration-count.loggable.ts rename to apps/server/src/infra/sync/tsp/loggable/tsp-legacy-school-migration-count.loggable.ts index 3e769b89165..c04fc6b5a53 100644 --- a/apps/server/src/infra/sync/tsp/loggable/tsp-legacy-migration-count.loggable.ts +++ b/apps/server/src/infra/sync/tsp/loggable/tsp-legacy-school-migration-count.loggable.ts @@ -1,6 +1,6 @@ import { Loggable, LogMessage } from '@src/core/logger'; -export class TspLegacyMigrationCountLoggable implements Loggable { +export class TspLegacySchoolMigrationCountLoggable implements Loggable { constructor(private readonly total: number) {} getLogMessage(): LogMessage { diff --git a/apps/server/src/infra/sync/tsp/loggable/tsp-legacy-migration-success.loggable.spec.ts b/apps/server/src/infra/sync/tsp/loggable/tsp-legacy-school-migration-success.loggable.spec.ts similarity index 60% rename from apps/server/src/infra/sync/tsp/loggable/tsp-legacy-migration-success.loggable.spec.ts rename to apps/server/src/infra/sync/tsp/loggable/tsp-legacy-school-migration-success.loggable.spec.ts index 2cd6056190f..d8bd6bee2ac 100644 --- a/apps/server/src/infra/sync/tsp/loggable/tsp-legacy-migration-success.loggable.spec.ts +++ b/apps/server/src/infra/sync/tsp/loggable/tsp-legacy-school-migration-success.loggable.spec.ts @@ -1,10 +1,10 @@ -import { TspLegacyMigrationSuccessLoggable } from './tsp-legacy-migration-success.loggable'; +import { TspLegacySchoolMigrationSuccessLoggable } from './tsp-legacy-school-migration-success.loggable'; -describe(TspLegacyMigrationSuccessLoggable.name, () => { - let loggable: TspLegacyMigrationSuccessLoggable; +describe(TspLegacySchoolMigrationSuccessLoggable.name, () => { + let loggable: TspLegacySchoolMigrationSuccessLoggable; beforeAll(() => { - loggable = new TspLegacyMigrationSuccessLoggable(10, 5); + loggable = new TspLegacySchoolMigrationSuccessLoggable(10, 5); }); describe('when loggable is initialized', () => { diff --git a/apps/server/src/infra/sync/tsp/loggable/tsp-legacy-migration-success.loggable.ts b/apps/server/src/infra/sync/tsp/loggable/tsp-legacy-school-migration-success.loggable.ts similarity index 84% rename from apps/server/src/infra/sync/tsp/loggable/tsp-legacy-migration-success.loggable.ts rename to apps/server/src/infra/sync/tsp/loggable/tsp-legacy-school-migration-success.loggable.ts index 0469ffdd8ac..b6ac7b247e2 100644 --- a/apps/server/src/infra/sync/tsp/loggable/tsp-legacy-migration-success.loggable.ts +++ b/apps/server/src/infra/sync/tsp/loggable/tsp-legacy-school-migration-success.loggable.ts @@ -1,6 +1,6 @@ import { Loggable, LogMessage } from '@src/core/logger'; -export class TspLegacyMigrationSuccessLoggable implements Loggable { +export class TspLegacySchoolMigrationSuccessLoggable implements Loggable { constructor(private readonly total: number, private readonly migrated: number) {} getLogMessage(): LogMessage { diff --git a/apps/server/src/infra/sync/tsp/tsp-legacy-migration.service.ts b/apps/server/src/infra/sync/tsp/tsp-legacy-migration.service.ts index dce20a5d0ca..1bff95ef2b5 100644 --- a/apps/server/src/infra/sync/tsp/tsp-legacy-migration.service.ts +++ b/apps/server/src/infra/sync/tsp/tsp-legacy-migration.service.ts @@ -2,10 +2,10 @@ import { EntityManager, ObjectId } from '@mikro-orm/mongodb'; import { Injectable } from '@nestjs/common'; import { EntityId, SchoolFeature } from '@shared/domain/types'; import { Logger } from '@src/core/logger'; -import { TspLegacyMigrationCountLoggable } from './loggable/tsp-legacy-migration-count.loggable'; import { TspLegacyMigrationStartLoggable } from './loggable/tsp-legacy-migration-start.loggable'; -import { TspLegacyMigrationSuccessLoggable } from './loggable/tsp-legacy-migration-success.loggable'; import { TspLegacyMigrationSystemMissingLoggable } from './loggable/tsp-legacy-migration-system-missing.loggable'; +import { TspLegacySchoolMigrationCountLoggable } from './loggable/tsp-legacy-school-migration-count.loggable'; +import { TspLegacySchoolMigrationSuccessLoggable } from './loggable/tsp-legacy-school-migration-success.loggable'; type LegacyTspSchoolProperties = { sourceOptions: { @@ -36,7 +36,7 @@ export class TspLegacyMigrationService { const schoolIds = await this.findIdsOfLegacyTspSchools(legacySystemId); - this.logger.info(new TspLegacyMigrationCountLoggable(schoolIds.length)); + this.logger.info(new TspLegacySchoolMigrationCountLoggable(schoolIds.length)); const promises = schoolIds.map(async (oldId): Promise => { const legacySchoolFilter = { @@ -66,7 +66,7 @@ export class TspLegacyMigrationService { .map((r) => r.value) .reduce((previousValue, currentValue) => previousValue + currentValue, 0); - this.logger.info(new TspLegacyMigrationSuccessLoggable(schoolIds.length, successfulMigrations)); + this.logger.info(new TspLegacySchoolMigrationSuccessLoggable(schoolIds.length, successfulMigrations)); } private async findLegacySystemId() { From 9c1a188975c152a8b539baf99292bfcedfdba55a Mon Sep 17 00:00:00 2001 From: Maximilian Kreuzkam Date: Wed, 20 Nov 2024 16:04:50 +0100 Subject: [PATCH 14/14] code review --- apps/server/src/infra/sync/tsp/tsp-sync.strategy.spec.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/apps/server/src/infra/sync/tsp/tsp-sync.strategy.spec.ts b/apps/server/src/infra/sync/tsp/tsp-sync.strategy.spec.ts index 5e8c48b5e15..b4d8a3b8a52 100644 --- a/apps/server/src/infra/sync/tsp/tsp-sync.strategy.spec.ts +++ b/apps/server/src/infra/sync/tsp/tsp-sync.strategy.spec.ts @@ -301,8 +301,6 @@ describe(TspSyncStrategy.name, () => { describe('when school does not exist', () => { const setup = () => { - tspSyncService.findTspSystemOrFail.mockResolvedValueOnce(systemFactory.build()); - const tspSchool: RobjExportSchule = { schuleNummer: faker.string.alpha(), schuleName: faker.string.alpha(), @@ -325,8 +323,6 @@ describe(TspSyncStrategy.name, () => { describe('when school does exist', () => { const setup = () => { - tspSyncService.findTspSystemOrFail.mockResolvedValueOnce(systemFactory.build()); - const tspSchool: RobjExportSchule = { schuleNummer: faker.string.alpha(), schuleName: faker.string.alpha(), @@ -351,8 +347,6 @@ describe(TspSyncStrategy.name, () => { describe('when tsp school does not have a schulnummer', () => { const setup = () => { - tspSyncService.findTspSystemOrFail.mockResolvedValueOnce(systemFactory.build()); - const tspSchool: RobjExportSchule = { schuleNummer: undefined, schuleName: faker.string.alpha(),