From b3b4cee3b13c783c3a67619db5cab92d1b743e81 Mon Sep 17 00:00:00 2001 From: agnisa-cap Date: Tue, 14 Nov 2023 16:16:57 +0100 Subject: [PATCH 01/10] N21-1447 user-login-migration refactorings (#4556) --- .../src/modules/authentication/index.ts | 1 + .../modules/authentication/services/index.ts | 2 + .../strategy/oauth2.strategy.spec.ts | 3 +- .../strategy/oauth2.strategy.ts | 3 +- apps/server/src/modules/oauth/index.ts | 1 + .../server/src/modules/oauth/service/index.ts | 3 + .../modules/oauth/service/oauth.service.ts | 3 +- .../modules/oauth/uc/hydra-oauth.uc.spec.ts | 3 +- apps/server/src/modules/provisioning/index.ts | 2 +- ...ation-already-closed.loggable-exception.ts | 2 +- ...login-migration-mandatory.loggable.spec.ts | 33 ++++++ ...user-login-migration-mandatory.loggable.ts | 2 +- ...ser-login-migration-start.loggable.spec.ts | 32 ++++++ .../user-login-migration-start.loggable.ts | 2 +- .../user-login-migration/service/dto/index.ts | 1 - .../service/dto/school-migration-flags.ts | 4 - .../service/migration-check.service.spec.ts | 3 +- .../service/migration-check.service.ts | 45 +++++--- .../service/school-migration.service.spec.ts | 8 +- .../service/school-migration.service.ts | 16 ++- .../user-login-migration.service.spec.ts | 96 ++++++++-------- .../service/user-login-migration.service.ts | 14 ++- .../uc/restart-user-login-migration.uc.ts | 2 +- .../uc/start-user-login-migration.uc.ts | 13 +-- .../uc/toggle-user-login-migration.uc.spec.ts | 103 +++++------------- .../uc/toggle-user-login-migration.uc.ts | 31 ++---- .../uc/user-login-migration.uc.spec.ts | 35 +++--- .../uc/user-login-migration.uc.ts | 8 +- 28 files changed, 248 insertions(+), 223 deletions(-) create mode 100644 apps/server/src/modules/authentication/services/index.ts create mode 100644 apps/server/src/modules/oauth/service/index.ts create mode 100644 apps/server/src/modules/user-login-migration/loggable/user-login-migration-mandatory.loggable.spec.ts create mode 100644 apps/server/src/modules/user-login-migration/loggable/user-login-migration-start.loggable.spec.ts delete mode 100644 apps/server/src/modules/user-login-migration/service/dto/school-migration-flags.ts diff --git a/apps/server/src/modules/authentication/index.ts b/apps/server/src/modules/authentication/index.ts index 59e749c7abc..3a3ea0c2755 100644 --- a/apps/server/src/modules/authentication/index.ts +++ b/apps/server/src/modules/authentication/index.ts @@ -1,3 +1,4 @@ export { ICurrentUser } from './interface'; export { JWT, CurrentUser, Authenticate } from './decorator'; export { AuthenticationModule } from './authentication.module'; +export { AuthenticationService } from './services'; diff --git a/apps/server/src/modules/authentication/services/index.ts b/apps/server/src/modules/authentication/services/index.ts new file mode 100644 index 00000000000..45277bbf1c5 --- /dev/null +++ b/apps/server/src/modules/authentication/services/index.ts @@ -0,0 +1,2 @@ +export * from './ldap.service'; +export * from './authentication.service'; diff --git a/apps/server/src/modules/authentication/strategy/oauth2.strategy.spec.ts b/apps/server/src/modules/authentication/strategy/oauth2.strategy.spec.ts index e6bf4ef2fa6..428f3c5e048 100644 --- a/apps/server/src/modules/authentication/strategy/oauth2.strategy.spec.ts +++ b/apps/server/src/modules/authentication/strategy/oauth2.strategy.spec.ts @@ -1,8 +1,7 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { AccountService } from '@modules/account/services/account.service'; import { AccountDto } from '@modules/account/services/dto'; -import { OAuthTokenDto } from '@modules/oauth'; -import { OAuthService } from '@modules/oauth/service/oauth.service'; +import { OAuthTokenDto, OAuthService } from '@modules/oauth'; import { UnauthorizedException } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { EntityId, RoleName } from '@shared/domain'; diff --git a/apps/server/src/modules/authentication/strategy/oauth2.strategy.ts b/apps/server/src/modules/authentication/strategy/oauth2.strategy.ts index e83e9174abc..e5bc6f942f8 100644 --- a/apps/server/src/modules/authentication/strategy/oauth2.strategy.ts +++ b/apps/server/src/modules/authentication/strategy/oauth2.strategy.ts @@ -1,7 +1,6 @@ import { AccountService } from '@modules/account/services/account.service'; import { AccountDto } from '@modules/account/services/dto'; -import { OAuthTokenDto } from '@modules/oauth'; -import { OAuthService } from '@modules/oauth/service/oauth.service'; +import { OAuthTokenDto, OAuthService } from '@modules/oauth'; import { Injectable, UnauthorizedException } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { UserDO } from '@shared/domain/domainobject/user.do'; diff --git a/apps/server/src/modules/oauth/index.ts b/apps/server/src/modules/oauth/index.ts index 40724ec25c2..67f5f98b440 100644 --- a/apps/server/src/modules/oauth/index.ts +++ b/apps/server/src/modules/oauth/index.ts @@ -1,2 +1,3 @@ export * from './oauth.module'; export * from './interface'; +export * from './service'; diff --git a/apps/server/src/modules/oauth/service/index.ts b/apps/server/src/modules/oauth/service/index.ts new file mode 100644 index 00000000000..d9123c8e5bd --- /dev/null +++ b/apps/server/src/modules/oauth/service/index.ts @@ -0,0 +1,3 @@ +export * from './hydra.service'; +export * from './oauth.service'; +export * from './oauth-adapter.service'; diff --git a/apps/server/src/modules/oauth/service/oauth.service.ts b/apps/server/src/modules/oauth/service/oauth.service.ts index 9a484a52d47..5e787d79082 100644 --- a/apps/server/src/modules/oauth/service/oauth.service.ts +++ b/apps/server/src/modules/oauth/service/oauth.service.ts @@ -1,7 +1,6 @@ import { DefaultEncryptionService, IEncryptionService } from '@infra/encryption'; import { LegacySchoolService } from '@modules/legacy-school'; -import { ProvisioningService } from '@modules/provisioning'; -import { OauthDataDto } from '@modules/provisioning/dto'; +import { ProvisioningService, OauthDataDto } from '@modules/provisioning'; import { SystemService } from '@modules/system'; import { SystemDto } from '@modules/system/service'; import { UserService } from '@modules/user'; diff --git a/apps/server/src/modules/oauth/uc/hydra-oauth.uc.spec.ts b/apps/server/src/modules/oauth/uc/hydra-oauth.uc.spec.ts index 339bc6c09d9..b889995b9e5 100644 --- a/apps/server/src/modules/oauth/uc/hydra-oauth.uc.spec.ts +++ b/apps/server/src/modules/oauth/uc/hydra-oauth.uc.spec.ts @@ -7,8 +7,7 @@ import { OauthConfig } from '@shared/domain'; import { axiosResponseFactory } from '@shared/testing'; import { LegacyLogger } from '@src/core/logger'; import { HydraRedirectDto } from '@modules/oauth/service/dto/hydra.redirect.dto'; -import { HydraSsoService } from '@modules/oauth/service/hydra.service'; -import { OAuthService } from '@modules/oauth/service/oauth.service'; +import { OAuthService, HydraSsoService } from '@modules/oauth'; import { AxiosResponse } from 'axios'; import { HydraOauthUc } from '.'; import { AuthorizationParams } from '../controller/dto'; diff --git a/apps/server/src/modules/provisioning/index.ts b/apps/server/src/modules/provisioning/index.ts index 0e0bc64d04a..caf7d1f3483 100644 --- a/apps/server/src/modules/provisioning/index.ts +++ b/apps/server/src/modules/provisioning/index.ts @@ -1,4 +1,4 @@ export * from './provisioning.module'; -export * from './dto/provisioning.dto'; +export * from './dto'; export * from './service/provisioning.service'; export * from './strategy'; diff --git a/apps/server/src/modules/user-login-migration/loggable/user-login-migration-already-closed.loggable-exception.ts b/apps/server/src/modules/user-login-migration/loggable/user-login-migration-already-closed.loggable-exception.ts index 321eba624f9..4e5fec1c31b 100644 --- a/apps/server/src/modules/user-login-migration/loggable/user-login-migration-already-closed.loggable-exception.ts +++ b/apps/server/src/modules/user-login-migration/loggable/user-login-migration-already-closed.loggable-exception.ts @@ -3,7 +3,7 @@ import { EntityId } from '@shared/domain'; import { ErrorLogMessage, Loggable, LogMessage, ValidationErrorLogMessage } from '@src/core/logger'; export class UserLoginMigrationAlreadyClosedLoggableException extends UnprocessableEntityException implements Loggable { - constructor(private readonly userLoginMigrationId: EntityId, private readonly closedAt: Date) { + constructor(private readonly closedAt: Date, private readonly userLoginMigrationId?: EntityId) { super(); } diff --git a/apps/server/src/modules/user-login-migration/loggable/user-login-migration-mandatory.loggable.spec.ts b/apps/server/src/modules/user-login-migration/loggable/user-login-migration-mandatory.loggable.spec.ts new file mode 100644 index 00000000000..a2b1cde606b --- /dev/null +++ b/apps/server/src/modules/user-login-migration/loggable/user-login-migration-mandatory.loggable.spec.ts @@ -0,0 +1,33 @@ +import { ObjectId } from 'bson'; +import { UserLoginMigrationMandatoryLoggable } from './user-login-migration-mandatory.loggable'; + +describe(UserLoginMigrationMandatoryLoggable.name, () => { + describe('getLogMessage', () => { + const setup = () => { + const userId = new ObjectId().toHexString(); + const userLoginMigrationId = new ObjectId().toHexString(); + const exception = new UserLoginMigrationMandatoryLoggable(userId, userLoginMigrationId, true); + + return { + exception, + userId, + userLoginMigrationId, + }; + }; + + it('should return the correct log message', () => { + const { exception, userId, userLoginMigrationId } = setup(); + + const message = exception.getLogMessage(); + + expect(message).toEqual({ + message: 'The school administrator changed the requirement status of the user login migration for his school.', + data: { + userId, + userLoginMigrationId, + mandatory: true, + }, + }); + }); + }); +}); diff --git a/apps/server/src/modules/user-login-migration/loggable/user-login-migration-mandatory.loggable.ts b/apps/server/src/modules/user-login-migration/loggable/user-login-migration-mandatory.loggable.ts index 93312a60402..b3bf5724aaf 100644 --- a/apps/server/src/modules/user-login-migration/loggable/user-login-migration-mandatory.loggable.ts +++ b/apps/server/src/modules/user-login-migration/loggable/user-login-migration-mandatory.loggable.ts @@ -4,7 +4,7 @@ import { ErrorLogMessage, Loggable, LogMessage, ValidationErrorLogMessage } from export class UserLoginMigrationMandatoryLoggable implements Loggable { constructor( private readonly userId: EntityId, - private readonly userLoginMigrationId: EntityId, + private readonly userLoginMigrationId: EntityId | undefined, private readonly mandatory: boolean ) {} diff --git a/apps/server/src/modules/user-login-migration/loggable/user-login-migration-start.loggable.spec.ts b/apps/server/src/modules/user-login-migration/loggable/user-login-migration-start.loggable.spec.ts new file mode 100644 index 00000000000..f15ac763e5c --- /dev/null +++ b/apps/server/src/modules/user-login-migration/loggable/user-login-migration-start.loggable.spec.ts @@ -0,0 +1,32 @@ +import { ObjectId } from 'bson'; +import { UserLoginMigrationStartLoggable } from './user-login-migration-start.loggable'; + +describe(UserLoginMigrationStartLoggable.name, () => { + describe('getLogMessage', () => { + const setup = () => { + const userId = new ObjectId().toHexString(); + const userLoginMigrationId = new ObjectId().toHexString(); + const exception = new UserLoginMigrationStartLoggable(userId, userLoginMigrationId); + + return { + exception, + userId, + userLoginMigrationId, + }; + }; + + it('should return the correct log message', () => { + const { exception, userId, userLoginMigrationId } = setup(); + + const message = exception.getLogMessage(); + + expect(message).toEqual({ + message: 'The school administrator started the migration for his school.', + data: { + userId, + userLoginMigrationId, + }, + }); + }); + }); +}); diff --git a/apps/server/src/modules/user-login-migration/loggable/user-login-migration-start.loggable.ts b/apps/server/src/modules/user-login-migration/loggable/user-login-migration-start.loggable.ts index f1ccb50d4d3..150ce3117b1 100644 --- a/apps/server/src/modules/user-login-migration/loggable/user-login-migration-start.loggable.ts +++ b/apps/server/src/modules/user-login-migration/loggable/user-login-migration-start.loggable.ts @@ -2,7 +2,7 @@ import { EntityId } from '@shared/domain'; import { ErrorLogMessage, Loggable, LogMessage, ValidationErrorLogMessage } from '@src/core/logger'; export class UserLoginMigrationStartLoggable implements Loggable { - constructor(private readonly userId: EntityId, private readonly userLoginMigrationId: EntityId) {} + constructor(private readonly userId: EntityId, private readonly userLoginMigrationId: EntityId | undefined) {} getLogMessage(): LogMessage | ErrorLogMessage | ValidationErrorLogMessage { return { diff --git a/apps/server/src/modules/user-login-migration/service/dto/index.ts b/apps/server/src/modules/user-login-migration/service/dto/index.ts index 7a92999ded8..bbc5344f4dd 100644 --- a/apps/server/src/modules/user-login-migration/service/dto/index.ts +++ b/apps/server/src/modules/user-login-migration/service/dto/index.ts @@ -1,3 +1,2 @@ export * from './migration.dto'; export * from './page-content.dto'; -export * from './school-migration-flags'; diff --git a/apps/server/src/modules/user-login-migration/service/dto/school-migration-flags.ts b/apps/server/src/modules/user-login-migration/service/dto/school-migration-flags.ts deleted file mode 100644 index b2b0f8b6e52..00000000000 --- a/apps/server/src/modules/user-login-migration/service/dto/school-migration-flags.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface SchoolMigrationFlags { - isPossible: boolean; - isMandatory: boolean; -} diff --git a/apps/server/src/modules/user-login-migration/service/migration-check.service.spec.ts b/apps/server/src/modules/user-login-migration/service/migration-check.service.spec.ts index 7afc017cf98..c3e2d1eac91 100644 --- a/apps/server/src/modules/user-login-migration/service/migration-check.service.spec.ts +++ b/apps/server/src/modules/user-login-migration/service/migration-check.service.spec.ts @@ -44,7 +44,7 @@ describe('MigrationCheckService', () => { await module.close(); }); - describe('shouldUserMigrate is called', () => { + describe('shouldUserMigrate', () => { describe('when no school with the official school number was found', () => { const setup = () => { schoolService.getSchoolBySchoolNumber.mockResolvedValue(null); @@ -87,6 +87,7 @@ describe('MigrationCheckService', () => { targetSystemId: 'targetSystemId', startedAt: new Date('2023-03-03'), }); + schoolService.getSchoolBySchoolNumber.mockResolvedValue(school); userService.findByExternalId.mockResolvedValue(null); userLoginMigrationRepo.findBySchoolId.mockResolvedValue(userLoginMigration); diff --git a/apps/server/src/modules/user-login-migration/service/migration-check.service.ts b/apps/server/src/modules/user-login-migration/service/migration-check.service.ts index 70d0ab94066..4c30cc2f8c0 100644 --- a/apps/server/src/modules/user-login-migration/service/migration-check.service.ts +++ b/apps/server/src/modules/user-login-migration/service/migration-check.service.ts @@ -1,8 +1,8 @@ +import { LegacySchoolService } from '@modules/legacy-school'; +import { UserService } from '@modules/user'; import { Injectable } from '@nestjs/common'; import { EntityId, LegacySchoolDo, UserDO, UserLoginMigrationDO } from '@shared/domain'; import { UserLoginMigrationRepo } from '@shared/repo'; -import { LegacySchoolService } from '@modules/legacy-school'; -import { UserService } from '@modules/user'; @Injectable() export class MigrationCheckService { @@ -12,22 +12,39 @@ export class MigrationCheckService { private readonly userLoginMigrationRepo: UserLoginMigrationRepo ) {} - async shouldUserMigrate(externalUserId: string, systemId: EntityId, officialSchoolNumber: string): Promise { + public async shouldUserMigrate( + externalUserId: string, + systemId: EntityId, + officialSchoolNumber: string + ): Promise { const school: LegacySchoolDo | null = await this.schoolService.getSchoolBySchoolNumber(officialSchoolNumber); - if (school && school.id) { - const userLoginMigration: UserLoginMigrationDO | null = await this.userLoginMigrationRepo.findBySchoolId( - school.id - ); + if (!school?.id) { + return false; + } - const user: UserDO | null = await this.userService.findByExternalId(externalUserId, systemId); + const userLoginMigration: UserLoginMigrationDO | null = await this.userLoginMigrationRepo.findBySchoolId(school.id); - if (user?.lastLoginSystemChange && userLoginMigration && !userLoginMigration.closedAt) { - const hasMigrated: boolean = user.lastLoginSystemChange > userLoginMigration.startedAt; - return !hasMigrated; - } - return !!userLoginMigration && !userLoginMigration.closedAt; + if (!userLoginMigration || !this.isMigrationActive(userLoginMigration)) { + return false; } - return false; + + const user: UserDO | null = await this.userService.findByExternalId(externalUserId, systemId); + + if (this.isUserMigrated(user, userLoginMigration)) { + return false; + } + + return true; + } + + private isUserMigrated(user: UserDO | null, userLoginMigration: UserLoginMigrationDO): boolean { + return ( + !!user && user.lastLoginSystemChange !== undefined && user.lastLoginSystemChange > userLoginMigration.startedAt + ); + } + + private isMigrationActive(userLoginMigration: UserLoginMigrationDO): boolean { + return !userLoginMigration.closedAt; } } diff --git a/apps/server/src/modules/user-login-migration/service/school-migration.service.spec.ts b/apps/server/src/modules/user-login-migration/service/school-migration.service.spec.ts index 988e6de9b01..c84339c9fbf 100644 --- a/apps/server/src/modules/user-login-migration/service/school-migration.service.spec.ts +++ b/apps/server/src/modules/user-login-migration/service/school-migration.service.spec.ts @@ -303,8 +303,8 @@ describe(SchoolMigrationService.name, () => { const closedAt: Date = new Date('2023-05-01'); const userLoginMigration: UserLoginMigrationDO = userLoginMigrationDOFactory.buildWithId({ - schoolId: 'schoolId', - targetSystemId: 'targetSystemId', + schoolId: new ObjectId().toHexString(), + targetSystemId: new ObjectId().toHexString(), startedAt: new Date('2023-05-01'), closedAt, finishedAt: new Date('2023-05-01'), @@ -379,7 +379,7 @@ describe(SchoolMigrationService.name, () => { }; }; - it('should call userLoginMigrationRepo.findBySchoolId', async () => { + it('should find user login migration by school id', async () => { setup(); await service.hasSchoolMigratedUser('schoolId'); @@ -399,7 +399,7 @@ describe(SchoolMigrationService.name, () => { }; }; - it('should call userService.findUsers', async () => { + it('should call user service to find users', async () => { const { userLoginMigration } = setup(); await service.hasSchoolMigratedUser('schoolId'); diff --git a/apps/server/src/modules/user-login-migration/service/school-migration.service.ts b/apps/server/src/modules/user-login-migration/service/school-migration.service.ts index aa5173dcff6..41cabc13d03 100644 --- a/apps/server/src/modules/user-login-migration/service/school-migration.service.ts +++ b/apps/server/src/modules/user-login-migration/service/school-migration.service.ts @@ -20,7 +20,11 @@ export class SchoolMigrationService { private readonly userLoginMigrationRepo: UserLoginMigrationRepo ) {} - async migrateSchool(existingSchool: LegacySchoolDo, externalId: string, targetSystemId: string): Promise { + public async migrateSchool( + existingSchool: LegacySchoolDo, + externalId: string, + targetSystemId: string + ): Promise { const schoolDOCopy: LegacySchoolDo = new LegacySchoolDo({ ...existingSchool }); try { @@ -45,7 +49,7 @@ export class SchoolMigrationService { await this.schoolService.save(school); } - private async tryRollbackMigration(originalSchoolDO: LegacySchoolDo) { + private async tryRollbackMigration(originalSchoolDO: LegacySchoolDo): Promise { try { await this.schoolService.save(originalSchoolDO); } catch (error: unknown) { @@ -55,7 +59,7 @@ export class SchoolMigrationService { } } - async getSchoolForMigration( + public async getSchoolForMigration( userId: string, externalId: string, officialSchoolNumber: string @@ -89,7 +93,7 @@ export class SchoolMigrationService { return isExternalIdEquivalent; } - async markUnmigratedUsersAsOutdated(userLoginMigration: UserLoginMigrationDO): Promise { + public async markUnmigratedUsersAsOutdated(userLoginMigration: UserLoginMigrationDO): Promise { const startTime: number = performance.now(); const notMigratedUsers: Page = await this.userService.findUsers({ @@ -112,7 +116,7 @@ export class SchoolMigrationService { ); } - async unmarkOutdatedUsers(userLoginMigration: UserLoginMigrationDO): Promise { + public async unmarkOutdatedUsers(userLoginMigration: UserLoginMigrationDO): Promise { const startTime: number = performance.now(); const migratedUsers: Page = await this.userService.findUsers({ @@ -132,7 +136,7 @@ export class SchoolMigrationService { ); } - async hasSchoolMigratedUser(schoolId: string): Promise { + public async hasSchoolMigratedUser(schoolId: string): Promise { const userLoginMigration: UserLoginMigrationDO | null = await this.userLoginMigrationRepo.findBySchoolId(schoolId); if (!userLoginMigration) { diff --git a/apps/server/src/modules/user-login-migration/service/user-login-migration.service.spec.ts b/apps/server/src/modules/user-login-migration/service/user-login-migration.service.spec.ts index ac7f3bc4740..0edaf9d1a38 100644 --- a/apps/server/src/modules/user-login-migration/service/user-login-migration.service.spec.ts +++ b/apps/server/src/modules/user-login-migration/service/user-login-migration.service.spec.ts @@ -11,8 +11,8 @@ import { EntityId, LegacySchoolDo, SchoolFeatures, UserDO, UserLoginMigrationDO import { UserLoginMigrationRepo } from '@shared/repo'; import { legacySchoolDoFactory, userDoFactory, userLoginMigrationDOFactory } from '@shared/testing'; import { + UserLoginMigrationAlreadyClosedLoggableException, UserLoginMigrationGracePeriodExpiredLoggableException, - UserLoginMigrationNotFoundLoggableException, } from '../loggable'; import { UserLoginMigrationService } from './user-login-migration.service'; @@ -523,34 +523,25 @@ describe(UserLoginMigrationService.name, () => { describe('setMigrationMandatory', () => { describe('when migration is set to mandatory', () => { const setup = () => { - const schoolId: EntityId = new ObjectId().toHexString(); - - const targetSystemId: EntityId = new ObjectId().toHexString(); - - const userLoginMigrationDO: UserLoginMigrationDO = userLoginMigrationDOFactory.buildWithId({ - targetSystemId, - schoolId, + const userLoginMigration: UserLoginMigrationDO = userLoginMigrationDOFactory.buildWithId({ startedAt: mockedDate, mandatorySince: undefined, }); - userLoginMigrationRepo.findBySchoolId.mockResolvedValue(userLoginMigrationDO); - userLoginMigrationRepo.save.mockResolvedValue(userLoginMigrationDO); + userLoginMigrationRepo.save.mockResolvedValue(userLoginMigration); return { - schoolId, - targetSystemId, - userLoginMigrationDO, + userLoginMigration, }; }; it('should call save the user login migration', async () => { - const { schoolId, userLoginMigrationDO } = setup(); + const { userLoginMigration } = setup(); - await service.setMigrationMandatory(schoolId, true); + await service.setMigrationMandatory(userLoginMigration, true); expect(userLoginMigrationRepo.save).toHaveBeenCalledWith({ - ...userLoginMigrationDO, + ...userLoginMigration, mandatorySince: mockedDate, }); }); @@ -558,65 +549,76 @@ describe(UserLoginMigrationService.name, () => { describe('when migration is set to optional', () => { const setup = () => { - const schoolId: EntityId = new ObjectId().toHexString(); - - const targetSystemId: EntityId = new ObjectId().toHexString(); - - const userLoginMigrationDO: UserLoginMigrationDO = userLoginMigrationDOFactory.buildWithId({ - targetSystemId, - schoolId, + const userLoginMigration: UserLoginMigrationDO = userLoginMigrationDOFactory.buildWithId({ startedAt: mockedDate, mandatorySince: mockedDate, }); - userLoginMigrationRepo.findBySchoolId.mockResolvedValue(userLoginMigrationDO); - userLoginMigrationRepo.save.mockResolvedValue(userLoginMigrationDO); - return { - schoolId, - targetSystemId, - userLoginMigrationDO, + userLoginMigration, }; }; it('should call save the user login migration', async () => { - const { schoolId, userLoginMigrationDO } = setup(); + const { userLoginMigration } = setup(); - await service.setMigrationMandatory(schoolId, false); + await service.setMigrationMandatory(userLoginMigration, false); expect(userLoginMigrationRepo.save).toHaveBeenCalledWith({ - ...userLoginMigrationDO, + ...userLoginMigration, mandatorySince: undefined, }); }); }); - describe('when migration could not be found', () => { + describe('when the grace period for the user login migration is expired', () => { const setup = () => { - const schoolId: EntityId = new ObjectId().toHexString(); + const dateInThePast: Date = new Date(mockedDate.getTime() - 100); + const userLoginMigration = userLoginMigrationDOFactory.buildWithId({ + closedAt: dateInThePast, + finishedAt: dateInThePast, + }); - const targetSystemId: EntityId = new ObjectId().toHexString(); + return { + userLoginMigration, + dateInThePast, + }; + }; - const userLoginMigrationDO: UserLoginMigrationDO = userLoginMigrationDOFactory.buildWithId({ - targetSystemId, - schoolId, - startedAt: mockedDate, + it('should not save the user login migration again', async () => { + const { userLoginMigration } = setup(); + + await expect(service.setMigrationMandatory({ ...userLoginMigration }, true)).rejects.toThrow(); + + expect(userLoginMigrationRepo.save).not.toHaveBeenCalled(); + }); + + it('should return throw an error', async () => { + const { userLoginMigration, dateInThePast } = setup(); + + await expect(service.setMigrationMandatory({ ...userLoginMigration }, true)).rejects.toThrow( + new UserLoginMigrationGracePeriodExpiredLoggableException(userLoginMigration.id as string, dateInThePast) + ); + }); + }); + + describe('when migration is closed', () => { + const setup = () => { + const userLoginMigration: UserLoginMigrationDO = userLoginMigrationDOFactory.buildWithId({ + closedAt: new Date(2023, 5), }); - userLoginMigrationRepo.findBySchoolId.mockResolvedValue(null); return { - schoolId, - targetSystemId, - userLoginMigrationDO, + userLoginMigration, }; }; - it('should throw UserLoginMigrationLoggableException ', async () => { - const { schoolId } = setup(); + it('should throw a UserLoginMigrationAlreadyClosedLoggableException', async () => { + const { userLoginMigration } = setup(); - const func = async () => service.setMigrationMandatory(schoolId, true); + const func = async () => service.setMigrationMandatory(userLoginMigration, true); - await expect(func).rejects.toThrow(UserLoginMigrationNotFoundLoggableException); + await expect(func).rejects.toThrow(UserLoginMigrationAlreadyClosedLoggableException); }); }); }); diff --git a/apps/server/src/modules/user-login-migration/service/user-login-migration.service.ts b/apps/server/src/modules/user-login-migration/service/user-login-migration.service.ts index 04f74f8408c..459b163f119 100644 --- a/apps/server/src/modules/user-login-migration/service/user-login-migration.service.ts +++ b/apps/server/src/modules/user-login-migration/service/user-login-migration.service.ts @@ -6,8 +6,8 @@ import { Injectable, InternalServerErrorException } from '@nestjs/common'; import { EntityId, LegacySchoolDo, SchoolFeatures, SystemTypeEnum, UserDO, UserLoginMigrationDO } from '@shared/domain'; import { UserLoginMigrationRepo } from '@shared/repo'; import { + UserLoginMigrationAlreadyClosedLoggableException, UserLoginMigrationGracePeriodExpiredLoggableException, - UserLoginMigrationNotFoundLoggableException, } from '../loggable'; @Injectable() @@ -47,12 +47,14 @@ export class UserLoginMigrationService { return updatedUserLoginMigration; } - public async setMigrationMandatory(schoolId: string, mandatory: boolean): Promise { - // this.checkGracePeriod(userLoginMigration); - let userLoginMigration: UserLoginMigrationDO | null = await this.userLoginMigrationRepo.findBySchoolId(schoolId); + public async setMigrationMandatory( + userLoginMigration: UserLoginMigrationDO, + mandatory: boolean + ): Promise { + this.checkGracePeriod(userLoginMigration); - if (!userLoginMigration) { - throw new UserLoginMigrationNotFoundLoggableException(schoolId); + if (userLoginMigration.closedAt) { + throw new UserLoginMigrationAlreadyClosedLoggableException(userLoginMigration.closedAt, userLoginMigration.id); } if (mandatory) { diff --git a/apps/server/src/modules/user-login-migration/uc/restart-user-login-migration.uc.ts b/apps/server/src/modules/user-login-migration/uc/restart-user-login-migration.uc.ts index 3fcecce5196..60be2ca956b 100644 --- a/apps/server/src/modules/user-login-migration/uc/restart-user-login-migration.uc.ts +++ b/apps/server/src/modules/user-login-migration/uc/restart-user-login-migration.uc.ts @@ -38,7 +38,7 @@ export class RestartUserLoginMigrationUc { await this.schoolMigrationService.unmarkOutdatedUsers(updatedUserLoginMigration); - this.logger.info(new UserLoginMigrationStartLoggable(userId, updatedUserLoginMigration.id as string)); + this.logger.info(new UserLoginMigrationStartLoggable(userId, updatedUserLoginMigration.id)); return updatedUserLoginMigration; } diff --git a/apps/server/src/modules/user-login-migration/uc/start-user-login-migration.uc.ts b/apps/server/src/modules/user-login-migration/uc/start-user-login-migration.uc.ts index 0f7c615bfbd..1bf47635d83 100644 --- a/apps/server/src/modules/user-login-migration/uc/start-user-login-migration.uc.ts +++ b/apps/server/src/modules/user-login-migration/uc/start-user-login-migration.uc.ts @@ -1,7 +1,7 @@ import { AuthorizationContext, AuthorizationContextBuilder, AuthorizationService } from '@modules/authorization'; import { LegacySchoolService } from '@modules/legacy-school'; import { Injectable } from '@nestjs/common/decorators/core/injectable.decorator'; -import { LegacySchoolDo, Permission, User, UserLoginMigrationDO } from '@shared/domain'; +import { EntityId, LegacySchoolDo, Permission, User, UserLoginMigrationDO } from '@shared/domain'; import { Logger } from '@src/core/logger'; import { SchoolNumberMissingLoggableException, @@ -21,7 +21,7 @@ export class StartUserLoginMigrationUc { this.logger.setContext(StartUserLoginMigrationUc.name); } - async startMigration(userId: string, schoolId: string): Promise { + async startMigration(userId: EntityId, schoolId: EntityId): Promise { await this.checkPreconditions(userId, schoolId); let userLoginMigration: UserLoginMigrationDO | null = await this.userLoginMigrationService.findMigrationBySchool( @@ -31,18 +31,15 @@ export class StartUserLoginMigrationUc { if (!userLoginMigration) { userLoginMigration = await this.userLoginMigrationService.startMigration(schoolId); - this.logger.info(new UserLoginMigrationStartLoggable(userId, userLoginMigration.id as string)); + this.logger.info(new UserLoginMigrationStartLoggable(userId, userLoginMigration.id)); } else if (userLoginMigration.closedAt) { - throw new UserLoginMigrationAlreadyClosedLoggableException( - userLoginMigration.id as string, - userLoginMigration.closedAt - ); + throw new UserLoginMigrationAlreadyClosedLoggableException(userLoginMigration.closedAt, userLoginMigration.id); } return userLoginMigration; } - async checkPreconditions(userId: string, schoolId: string): Promise { + private async checkPreconditions(userId: string, schoolId: string): Promise { const user: User = await this.authorizationService.getUserWithPermissions(userId); const school: LegacySchoolDo = await this.schoolService.getSchoolById(schoolId); diff --git a/apps/server/src/modules/user-login-migration/uc/toggle-user-login-migration.uc.spec.ts b/apps/server/src/modules/user-login-migration/uc/toggle-user-login-migration.uc.spec.ts index 75fffd4a3c2..3416250a67e 100644 --- a/apps/server/src/modules/user-login-migration/uc/toggle-user-login-migration.uc.spec.ts +++ b/apps/server/src/modules/user-login-migration/uc/toggle-user-login-migration.uc.spec.ts @@ -6,11 +6,8 @@ import { Test, TestingModule } from '@nestjs/testing'; import { LegacySchoolDo, Permission, User, UserLoginMigrationDO } from '@shared/domain'; import { legacySchoolDoFactory, setupEntities, userFactory, userLoginMigrationDOFactory } from '@shared/testing'; import { Logger } from '@src/core/logger'; -import { - UserLoginMigrationAlreadyClosedLoggableException, - UserLoginMigrationGracePeriodExpiredLoggableException, - UserLoginMigrationNotFoundLoggableException, -} from '../loggable'; +import { ObjectId } from 'bson'; +import { UserLoginMigrationNotFoundLoggableException } from '../loggable'; import { UserLoginMigrationService } from '../service'; import { ToggleUserLoginMigrationUc } from './toggle-user-login-migration.uc'; @@ -78,13 +75,13 @@ describe(ToggleUserLoginMigrationUc.name, () => { userLoginMigrationService.findMigrationBySchool.mockResolvedValueOnce(migrationBeforeMandatory); userLoginMigrationService.setMigrationMandatory.mockResolvedValueOnce(migrationAfterMandatory); - return { user, school, migrationAfterMandatory }; + return { user, school, migrationAfterMandatory, migrationBeforeMandatory }; }; it('should check permission', async () => { const { user, school } = setup(); - await uc.setMigrationMandatory('userId', 'schoolId', true); + await uc.setMigrationMandatory(new ObjectId().toHexString(), new ObjectId().toHexString(), true); expect(authorizationService.checkPermission).toHaveBeenCalledWith( user, @@ -94,17 +91,21 @@ describe(ToggleUserLoginMigrationUc.name, () => { }); it('should call the service to set a migration mandatory', async () => { - setup(); + const { migrationBeforeMandatory } = setup(); - await uc.setMigrationMandatory('userId', 'schoolId', true); + await uc.setMigrationMandatory(new ObjectId().toHexString(), new ObjectId().toHexString(), true); - expect(userLoginMigrationService.setMigrationMandatory).toHaveBeenCalledWith('schoolId', true); + expect(userLoginMigrationService.setMigrationMandatory).toHaveBeenCalledWith(migrationBeforeMandatory, true); }); it('should return a UserLoginMigration', async () => { const { migrationAfterMandatory } = setup(); - const result: UserLoginMigrationDO = await uc.setMigrationMandatory('userId', 'schoolId', true); + const result: UserLoginMigrationDO = await uc.setMigrationMandatory( + new ObjectId().toHexString(), + new ObjectId().toHexString(), + true + ); expect(result).toEqual(migrationAfterMandatory); }); @@ -126,13 +127,13 @@ describe(ToggleUserLoginMigrationUc.name, () => { userLoginMigrationService.findMigrationBySchool.mockResolvedValueOnce(migrationBeforeOptional); userLoginMigrationService.setMigrationMandatory.mockResolvedValueOnce(migrationAfterOptional); - return { user, school, migrationAfterOptional }; + return { user, school, migrationAfterOptional, migrationBeforeOptional }; }; it('should check permission', async () => { const { user, school } = setup(); - await uc.setMigrationMandatory('userId', 'schoolId', false); + await uc.setMigrationMandatory(new ObjectId().toHexString(), new ObjectId().toHexString(), false); expect(authorizationService.checkPermission).toHaveBeenCalledWith( user, @@ -142,17 +143,21 @@ describe(ToggleUserLoginMigrationUc.name, () => { }); it('should call the service to set a migration optional', async () => { - setup(); + const { migrationBeforeOptional } = setup(); - await uc.setMigrationMandatory('userId', 'schoolId', false); + await uc.setMigrationMandatory(new ObjectId().toHexString(), new ObjectId().toHexString(), false); - expect(userLoginMigrationService.setMigrationMandatory).toHaveBeenCalledWith('schoolId', false); + expect(userLoginMigrationService.setMigrationMandatory).toHaveBeenCalledWith(migrationBeforeOptional, false); }); it('should return a UserLoginMigration', async () => { const { migrationAfterOptional } = setup(); - const result: UserLoginMigrationDO = await uc.setMigrationMandatory('userId', 'schoolId', false); + const result: UserLoginMigrationDO = await uc.setMigrationMandatory( + new ObjectId().toHexString(), + new ObjectId().toHexString(), + false + ); expect(result).toEqual(migrationAfterOptional); }); @@ -174,9 +179,9 @@ describe(ToggleUserLoginMigrationUc.name, () => { it('should throw an exception', async () => { setup(); - const func = async () => uc.setMigrationMandatory('userId', 'schoolId', true); - - await expect(func).rejects.toThrow(ForbiddenException); + await expect( + uc.setMigrationMandatory(new ObjectId().toHexString(), new ObjectId().toHexString(), true) + ).rejects.toThrow(ForbiddenException); }); }); @@ -194,61 +199,9 @@ describe(ToggleUserLoginMigrationUc.name, () => { it('should throw a UserLoginMigrationNotFoundLoggableException', async () => { setup(); - const func = async () => uc.setMigrationMandatory('userId', 'schoolId', true); - - await expect(func).rejects.toThrow(UserLoginMigrationNotFoundLoggableException); - }); - }); - - describe('when the grace period for restarting a migration has expired', () => { - const setup = () => { - jest.useFakeTimers(); - jest.setSystemTime(new Date(2023, 6)); - - const migration: UserLoginMigrationDO = userLoginMigrationDOFactory.buildWithId({ - closedAt: new Date(2023, 5), - finishedAt: new Date(2023, 5), - }); - - const user: User = userFactory.buildWithId(); - - const school: LegacySchoolDo = legacySchoolDoFactory.buildWithId(); - - authorizationService.getUserWithPermissions.mockResolvedValueOnce(user); - schoolService.getSchoolById.mockResolvedValueOnce(school); - userLoginMigrationService.findMigrationBySchool.mockResolvedValueOnce(migration); - }; - - it('should throw a UserLoginMigrationGracePeriodExpiredLoggableException', async () => { - setup(); - - const func = async () => uc.setMigrationMandatory('userId', 'schoolId', true); - - await expect(func).rejects.toThrow(UserLoginMigrationGracePeriodExpiredLoggableException); - }); - }); - - describe('when migration is closed', () => { - const setup = () => { - const migration: UserLoginMigrationDO = userLoginMigrationDOFactory.buildWithId({ - closedAt: new Date(2023, 5), - }); - - const user: User = userFactory.buildWithId(); - - const school: LegacySchoolDo = legacySchoolDoFactory.buildWithId(); - - authorizationService.getUserWithPermissions.mockResolvedValueOnce(user); - schoolService.getSchoolById.mockResolvedValueOnce(school); - userLoginMigrationService.findMigrationBySchool.mockResolvedValueOnce(migration); - }; - - it('should throw a UserLoginMigrationAlreadyClosedLoggableException', async () => { - setup(); - - const func = async () => uc.setMigrationMandatory('userId', 'schoolId', true); - - await expect(func).rejects.toThrow(UserLoginMigrationAlreadyClosedLoggableException); + await expect( + uc.setMigrationMandatory(new ObjectId().toHexString(), new ObjectId().toHexString(), true) + ).rejects.toThrow(UserLoginMigrationNotFoundLoggableException); }); }); }); diff --git a/apps/server/src/modules/user-login-migration/uc/toggle-user-login-migration.uc.ts b/apps/server/src/modules/user-login-migration/uc/toggle-user-login-migration.uc.ts index bb020379b9c..cb964ff7fa6 100644 --- a/apps/server/src/modules/user-login-migration/uc/toggle-user-login-migration.uc.ts +++ b/apps/server/src/modules/user-login-migration/uc/toggle-user-login-migration.uc.ts @@ -1,14 +1,9 @@ import { AuthorizationContext, AuthorizationContextBuilder, AuthorizationService } from '@modules/authorization'; import { LegacySchoolService } from '@modules/legacy-school'; import { Injectable } from '@nestjs/common'; -import { LegacySchoolDo, Permission, User, UserLoginMigrationDO } from '@shared/domain'; +import { EntityId, LegacySchoolDo, Permission, User, UserLoginMigrationDO } from '@shared/domain'; import { Logger } from '@src/core/logger'; -import { - UserLoginMigrationAlreadyClosedLoggableException, - UserLoginMigrationGracePeriodExpiredLoggableException, - UserLoginMigrationMandatoryLoggable, - UserLoginMigrationNotFoundLoggableException, -} from '../loggable'; +import { UserLoginMigrationMandatoryLoggable, UserLoginMigrationNotFoundLoggableException } from '../loggable'; import { UserLoginMigrationService } from '../service'; @Injectable() @@ -20,7 +15,7 @@ export class ToggleUserLoginMigrationUc { private readonly logger: Logger ) {} - async setMigrationMandatory(userId: string, schoolId: string, mandatory: boolean): Promise { + async setMigrationMandatory(userId: EntityId, schoolId: EntityId, mandatory: boolean): Promise { await this.checkPermission(userId, schoolId); let userLoginMigration: UserLoginMigrationDO | null = await this.userLoginMigrationService.findMigrationBySchool( @@ -29,26 +24,16 @@ export class ToggleUserLoginMigrationUc { if (!userLoginMigration) { throw new UserLoginMigrationNotFoundLoggableException(schoolId); - } else if (userLoginMigration.finishedAt && Date.now() >= userLoginMigration.finishedAt.getTime()) { - throw new UserLoginMigrationGracePeriodExpiredLoggableException( - userLoginMigration.id as string, - userLoginMigration.finishedAt - ); - } else if (userLoginMigration.closedAt) { - throw new UserLoginMigrationAlreadyClosedLoggableException( - userLoginMigration.id as string, - userLoginMigration.closedAt - ); - } else { - userLoginMigration = await this.userLoginMigrationService.setMigrationMandatory(schoolId, mandatory); - - this.logger.debug(new UserLoginMigrationMandatoryLoggable(userId, userLoginMigration.id as string, mandatory)); } + userLoginMigration = await this.userLoginMigrationService.setMigrationMandatory(userLoginMigration, mandatory); + + this.logger.debug(new UserLoginMigrationMandatoryLoggable(userId, userLoginMigration.id, mandatory)); + return userLoginMigration; } - async checkPermission(userId: string, schoolId: string): Promise { + private async checkPermission(userId: string, schoolId: string): Promise { const user: User = await this.authorizationService.getUserWithPermissions(userId); const school: LegacySchoolDo = await this.schoolService.getSchoolById(schoolId); diff --git a/apps/server/src/modules/user-login-migration/uc/user-login-migration.uc.spec.ts b/apps/server/src/modules/user-login-migration/uc/user-login-migration.uc.spec.ts index bebd6b7115a..4179505d009 100644 --- a/apps/server/src/modules/user-login-migration/uc/user-login-migration.uc.spec.ts +++ b/apps/server/src/modules/user-login-migration/uc/user-login-migration.uc.spec.ts @@ -1,12 +1,16 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { ObjectId } from '@mikro-orm/mongodb'; -import { AuthenticationService } from '@modules/authentication/services/authentication.service'; +import { AuthenticationService } from '@modules/authentication'; import { Action, AuthorizationService } from '@modules/authorization'; import { LegacySchoolService } from '@modules/legacy-school'; -import { OAuthTokenDto } from '@modules/oauth'; -import { OAuthService } from '@modules/oauth/service/oauth.service'; -import { ProvisioningService } from '@modules/provisioning'; -import { ExternalSchoolDto, ExternalUserDto, OauthDataDto, ProvisioningSystemDto } from '@modules/provisioning/dto'; +import { OAuthTokenDto, OAuthService } from '@modules/oauth'; +import { + ProvisioningService, + ExternalSchoolDto, + ExternalUserDto, + OauthDataDto, + ProvisioningSystemDto, +} from '@modules/provisioning'; import { ForbiddenException } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { NotFoundLoggableException } from '@shared/common/loggable-exception'; @@ -20,8 +24,7 @@ import { userLoginMigrationDOFactory, } from '@shared/testing'; import { Logger } from '@src/core/logger'; -import { ExternalSchoolNumberMissingLoggableException } from '../loggable'; -import { InvalidUserLoginMigrationLoggableException } from '../loggable/invalid-user-login-migration.loggable-exception'; +import { ExternalSchoolNumberMissingLoggableException, InvalidUserLoginMigrationLoggableException } from '../loggable'; import { SchoolMigrationService, UserLoginMigrationService, UserMigrationService } from '../service'; import { UserLoginMigrationUc } from './user-login-migration.uc'; @@ -104,7 +107,7 @@ describe(UserLoginMigrationUc.name, () => { describe('getMigrations', () => { describe('when searching for a users migration', () => { const setup = () => { - const userId = 'userId'; + const userId = new ObjectId().toHexString(); const migrations: UserLoginMigrationDO = userLoginMigrationDOFactory.buildWithId({ schoolId: 'schoolId', @@ -117,7 +120,7 @@ describe(UserLoginMigrationUc.name, () => { return { userId, migrations }; }; - it('should return a response', async () => { + it('should return a page response with data', async () => { const { userId, migrations } = setup(); const result: Page = await uc.getMigrations(userId, { userId }); @@ -131,14 +134,14 @@ describe(UserLoginMigrationUc.name, () => { describe('when a user has no migration available', () => { const setup = () => { - const userId = 'userId'; + const userId = new ObjectId().toHexString(); userLoginMigrationService.findMigrationByUser.mockResolvedValueOnce(null); return { userId }; }; - it('should return a response', async () => { + it('should return a page response without data', async () => { const { userId } = setup(); const result: Page = await uc.getMigrations(userId, { userId }); @@ -152,12 +155,12 @@ describe(UserLoginMigrationUc.name, () => { describe('when searching for other users migrations', () => { const setup = () => { - const userId = 'userId'; + const userId = new ObjectId().toHexString(); return { userId }; }; - it('should return a response', async () => { + it('should throw a forbidden exception', async () => { const { userId } = setup(); const func = async () => uc.getMigrations(userId, { userId: 'otherUserId' }); @@ -172,7 +175,7 @@ describe(UserLoginMigrationUc.name, () => { describe('findUserLoginMigrationBySchool', () => { describe('when searching for an existing user login migration', () => { const setup = () => { - const schoolId = 'schoolId'; + const schoolId = new ObjectId().toHexString(); const migration: UserLoginMigrationDO = userLoginMigrationDOFactory.buildWithId({ schoolId, @@ -209,7 +212,7 @@ describe(UserLoginMigrationUc.name, () => { describe('when a user login migration does not exist', () => { const setup = () => { - const schoolId = 'schoolId'; + const schoolId = new ObjectId().toHexString(); const user: User = userFactory.buildWithId(); @@ -230,7 +233,7 @@ describe(UserLoginMigrationUc.name, () => { describe('when the authorization fails', () => { const setup = () => { - const schoolId = 'schoolId'; + const schoolId = new ObjectId().toHexString(); const user: User = userFactory.buildWithId(); diff --git a/apps/server/src/modules/user-login-migration/uc/user-login-migration.uc.ts b/apps/server/src/modules/user-login-migration/uc/user-login-migration.uc.ts index 24442147e5e..8314cbfd455 100644 --- a/apps/server/src/modules/user-login-migration/uc/user-login-migration.uc.ts +++ b/apps/server/src/modules/user-login-migration/uc/user-login-migration.uc.ts @@ -1,9 +1,7 @@ -import { AuthenticationService } from '@modules/authentication/services/authentication.service'; +import { AuthenticationService } from '@modules/authentication'; import { Action, AuthorizationService } from '@modules/authorization'; -import { OAuthTokenDto } from '@modules/oauth'; -import { OAuthService } from '@modules/oauth/service/oauth.service'; -import { ProvisioningService } from '@modules/provisioning'; -import { OauthDataDto } from '@modules/provisioning/dto'; +import { OAuthTokenDto, OAuthService } from '@modules/oauth'; +import { ProvisioningService, OauthDataDto } from '@modules/provisioning'; import { ForbiddenException, Injectable } from '@nestjs/common'; import { NotFoundLoggableException } from '@shared/common/loggable-exception'; import { EntityId, LegacySchoolDo, Page, Permission, User, UserLoginMigrationDO } from '@shared/domain'; From 8f1aea39fd4bed99cb4bed845b05df2228ae1909 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20=C3=96hlerking?= <103562092+MarvinOehlerkingCap@users.noreply.github.com> Date: Wed, 15 Nov 2023 16:11:46 +0100 Subject: [PATCH 02/10] N21-1488 Fix optional external tool parameter with regex (#4558) --- .../common-tool-validation.service.spec.ts | 178 +++++++++--------- .../service/common-tool-validation.service.ts | 46 +++-- .../api-test/tool-context.api.spec.ts | 55 ++++-- .../context-external-tool-request.mapper.ts | 2 +- ...ontext-external-tool-validation.service.ts | 4 +- .../api-test/tool-school.api.spec.ts | 69 ++++--- .../school-external-tool-request.mapper.ts | 4 +- ...l-external-tool-validation.service.spec.ts | 10 +- ...school-external-tool-validation.service.ts | 5 +- .../factory/external-tool-entity.factory.ts | 2 - 10 files changed, 212 insertions(+), 163 deletions(-) diff --git a/apps/server/src/modules/tool/common/service/common-tool-validation.service.spec.ts b/apps/server/src/modules/tool/common/service/common-tool-validation.service.spec.ts index d3ca547a854..1f76ec01a14 100644 --- a/apps/server/src/modules/tool/common/service/common-tool-validation.service.spec.ts +++ b/apps/server/src/modules/tool/common/service/common-tool-validation.service.spec.ts @@ -109,104 +109,93 @@ describe('CommonToolValidationService', () => { }); }); - describe('checkForDuplicateParameters', () => { - describe('when given parameters has a case sensitive duplicate', () => { - const setup = () => { - const schoolExternalTool: SchoolExternalTool = schoolExternalToolFactory.build({ - parameters: [ - { name: 'nameDuplicate', value: 'value' }, - { name: 'nameDuplicate', value: 'value' }, - ], - }); + describe('checkCustomParameterEntries', () => { + const createTools = ( + externalToolMock?: Partial, + schoolExternalToolMock?: Partial, + contextExternalToolMock?: Partial + ) => { + const externalTool: ExternalTool = new ExternalTool({ + ...externalToolFactory.buildWithId(), + ...externalToolMock, + }); + const schoolExternalTool: SchoolExternalTool = schoolExternalToolFactory.build({ + ...schoolExternalToolFactory.buildWithId(), + ...schoolExternalToolMock, + }); + const schoolExternalToolId = schoolExternalTool.id as string; + const contextExternalTool: ContextExternalTool = contextExternalToolFactory.build({ + ...contextExternalToolFactory.buildWithId(), + ...contextExternalToolMock, + }); - return { - schoolExternalTool, - }; + return { + externalTool, + schoolExternalTool, + schoolExternalToolId, + contextExternalTool, }; + }; - it('should throw error', () => { - const { schoolExternalTool } = setup(); - - const func = () => service.checkForDuplicateParameters(schoolExternalTool); - - expect(func).toThrow('tool_param_duplicate'); - }); - }); - - describe('when given parameters has case insensitive duplicate', () => { + describe('when a parameter is a duplicate', () => { const setup = () => { - const schoolExternalTool: SchoolExternalTool = schoolExternalToolFactory.build({ + const externalTool: ExternalTool = externalToolFactory.buildWithId({ parameters: [ - { name: 'nameDuplicate', value: 'value' }, - { name: 'nameduplicate', value: 'value' }, + customParameterFactory.build({ + name: 'duplicate', + scope: CustomParameterScope.SCHOOL, + type: CustomParameterType.STRING, + isOptional: true, + }), + ], + }); + const schoolExternalTool: SchoolExternalTool = schoolExternalToolFactory.buildWithId({ + toolId: externalTool.id, + parameters: [ + { name: 'duplicate', value: undefined }, + { name: 'duplicate', value: undefined }, ], }); return { + externalTool, schoolExternalTool, }; }; - it('should throw error when given parameters has case insensitive duplicate', () => { - const { schoolExternalTool } = setup(); + it('should throw error', () => { + const { externalTool, schoolExternalTool } = setup(); - const func = () => service.checkForDuplicateParameters(schoolExternalTool); + const func = () => service.checkCustomParameterEntries(externalTool, schoolExternalTool); expect(func).toThrowError('tool_param_duplicate'); }); }); - describe('when given parameters has no duplicates', () => { + describe('when a parameter is unknown', () => { const setup = () => { - const schoolExternalTool: SchoolExternalTool = schoolExternalToolFactory.build({ - parameters: [ - { name: 'nameNoDuplicate1', value: 'value' }, - { name: 'nameNoDuplicate2', value: 'value' }, - ], + const externalTool: ExternalTool = externalToolFactory.buildWithId({ + parameters: [], + }); + const schoolExternalTool: SchoolExternalTool = schoolExternalToolFactory.buildWithId({ + toolId: externalTool.id, + parameters: [{ name: 'unknownParameter', value: undefined }], }); return { + externalTool, schoolExternalTool, }; }; - it('when given parameters has no duplicates should return without error', () => { - const { schoolExternalTool } = setup(); + it('should throw error', () => { + const { externalTool, schoolExternalTool } = setup(); - const func = () => service.checkForDuplicateParameters(schoolExternalTool); + const func = () => service.checkCustomParameterEntries(externalTool, schoolExternalTool); - expect(func).not.toThrowError('tool_param_duplicate'); + expect(func).toThrowError('tool_param_unknown'); }); }); - }); - - describe('checkCustomParameterEntries', () => { - const createTools = ( - externalToolMock?: Partial, - schoolExternalToolMock?: Partial, - contextExternalToolMock?: Partial - ) => { - const externalTool: ExternalTool = new ExternalTool({ - ...externalToolFactory.buildWithId(), - ...externalToolMock, - }); - const schoolExternalTool: SchoolExternalTool = schoolExternalToolFactory.build({ - ...schoolExternalToolFactory.buildWithId(), - ...schoolExternalToolMock, - }); - const schoolExternalToolId = schoolExternalTool.id as string; - const contextExternalTool: ContextExternalTool = contextExternalToolFactory.build({ - ...contextExternalToolFactory.buildWithId(), - ...contextExternalToolMock, - }); - - return { - externalTool, - schoolExternalTool, - schoolExternalToolId, - contextExternalTool, - }; - }; describe('when checking parameter is required', () => { describe('and given parameter is not optional and parameter value is empty', () => { @@ -280,16 +269,23 @@ describe('CommonToolValidationService', () => { describe('when parameter is not school or context', () => { const setup = () => { - const notSchoolParam: CustomParameter = customParameterFactory.build({ - name: 'notSchoolParam', - scope: CustomParameterScope.GLOBAL, - type: CustomParameterType.BOOLEAN, - }); - const { externalTool, schoolExternalTool } = createTools( - { parameters: [notSchoolParam] }, { - parameters: [{ name: 'name', value: 'true' }], + parameters: [ + customParameterFactory.build({ + name: 'notSchoolParam', + scope: CustomParameterScope.GLOBAL, + type: CustomParameterType.BOOLEAN, + }), + customParameterFactory.build({ + name: 'schoolParam', + scope: CustomParameterScope.SCHOOL, + type: CustomParameterType.BOOLEAN, + }), + ], + }, + { + parameters: [{ name: 'schoolParam', value: 'true' }], } ); @@ -320,7 +316,7 @@ describe('CommonToolValidationService', () => { const { externalTool, schoolExternalTool } = createTools( { parameters: [missingParam] }, { - parameters: [{ name: 'anotherParam', value: 'value' }], + parameters: [], } ); @@ -339,18 +335,26 @@ describe('CommonToolValidationService', () => { }); }); - describe('when parameter is optional but is missing on params', () => { + describe('when parameter is optional and was not defined', () => { const setup = () => { - const param: CustomParameter = customParameterFactory.build({ - name: 'notChecked', - scope: CustomParameterScope.SCHOOL, - isOptional: true, - }); - const { externalTool, schoolExternalTool } = createTools( - { parameters: [param] }, { - parameters: [{ name: 'anotherParam', value: 'value' }], + parameters: [ + customParameterFactory.build({ + name: 'optionalParameter', + scope: CustomParameterScope.SCHOOL, + isOptional: true, + }), + customParameterFactory.build({ + name: 'requiredParameter', + scope: CustomParameterScope.SCHOOL, + type: CustomParameterType.STRING, + isOptional: false, + }), + ], + }, + { + parameters: [{ name: 'requiredParameter', value: 'value' }], } ); @@ -360,7 +364,7 @@ describe('CommonToolValidationService', () => { }; }; - it('should return without error ', () => { + it('should return without error', () => { const { externalTool, schoolExternalTool } = setup(); const func = () => service.checkCustomParameterEntries(externalTool, schoolExternalTool); @@ -385,7 +389,7 @@ describe('CommonToolValidationService', () => { }, undefined, { - parameters: [{ name: 'anotherParam', value: 'value' }], + parameters: [], } ); diff --git a/apps/server/src/modules/tool/common/service/common-tool-validation.service.ts b/apps/server/src/modules/tool/common/service/common-tool-validation.service.ts index 3ee9ee7d465..e6c3a31b288 100644 --- a/apps/server/src/modules/tool/common/service/common-tool-validation.service.ts +++ b/apps/server/src/modules/tool/common/service/common-tool-validation.service.ts @@ -29,12 +29,25 @@ export class CommonToolValidationService { return isValid; } - public checkForDuplicateParameters(validatableTool: ValidatableTool): void { - const caseInsensitiveNames: string[] = validatableTool.parameters.map(({ name }: CustomParameterEntry) => - name.toLowerCase() + public checkCustomParameterEntries(loadedExternalTool: ExternalTool, validatableTool: ValidatableTool): void { + this.checkForDuplicateParameters(validatableTool); + + const parametersForScope: CustomParameter[] = (loadedExternalTool.parameters ?? []).filter( + (param: CustomParameter) => + (validatableTool instanceof SchoolExternalTool && param.scope === CustomParameterScope.SCHOOL) || + (validatableTool instanceof ContextExternalTool && param.scope === CustomParameterScope.CONTEXT) ); + this.checkForUnknownParameters(validatableTool, parametersForScope); + + this.checkValidityOfParameters(validatableTool, parametersForScope); + } + + private checkForDuplicateParameters(validatableTool: ValidatableTool): void { + const caseInsensitiveNames: string[] = validatableTool.parameters.map(({ name }: CustomParameterEntry) => name); + const uniqueNames: Set = new Set(caseInsensitiveNames); + if (uniqueNames.size !== validatableTool.parameters.length) { throw new ValidationError( `tool_param_duplicate: The tool ${validatableTool.id ?? ''} contains multiple of the same custom parameters.` @@ -42,28 +55,33 @@ export class CommonToolValidationService { } } - public checkCustomParameterEntries(loadedExternalTool: ExternalTool, validatableTool: ValidatableTool): void { - if (loadedExternalTool.parameters) { - for (const param of loadedExternalTool.parameters) { - this.checkScopeAndValidateParameter(validatableTool, param); + private checkForUnknownParameters(validatableTool: ValidatableTool, parametersForScope: CustomParameter[]): void { + for (const entry of validatableTool.parameters) { + const foundParameter: CustomParameter | undefined = parametersForScope.find( + ({ name }: CustomParameter): boolean => name === entry.name + ); + + if (!foundParameter) { + throw new ValidationError( + `tool_param_unknown: The parameter with name ${entry.name} is not part of this tool.` + ); } } } - private checkScopeAndValidateParameter(validatableTool: ValidatableTool, param: CustomParameter): void { - const foundEntry: CustomParameterEntry | undefined = validatableTool.parameters.find( - ({ name }: CustomParameterEntry): boolean => name.toLowerCase() === param.name.toLowerCase() - ); + private checkValidityOfParameters(validatableTool: ValidatableTool, parametersForScope: CustomParameter[]): void { + for (const param of parametersForScope) { + const foundEntry: CustomParameterEntry | undefined = validatableTool.parameters.find( + ({ name }: CustomParameterEntry): boolean => name === param.name + ); - if (param.scope === CustomParameterScope.SCHOOL && validatableTool instanceof SchoolExternalTool) { - this.validateParameter(param, foundEntry); - } else if (param.scope === CustomParameterScope.CONTEXT && validatableTool instanceof ContextExternalTool) { this.validateParameter(param, foundEntry); } } private validateParameter(param: CustomParameter, foundEntry: CustomParameterEntry | undefined): void { this.checkOptionalParameter(param, foundEntry); + if (foundEntry) { this.checkParameterType(foundEntry, param); this.checkParameterRegex(foundEntry, param); diff --git a/apps/server/src/modules/tool/context-external-tool/controller/api-test/tool-context.api.spec.ts b/apps/server/src/modules/tool/context-external-tool/controller/api-test/tool-context.api.spec.ts index eb570130c4e..af14eb379f3 100644 --- a/apps/server/src/modules/tool/context-external-tool/controller/api-test/tool-context.api.spec.ts +++ b/apps/server/src/modules/tool/context-external-tool/controller/api-test/tool-context.api.spec.ts @@ -17,9 +17,8 @@ import { userFactory, } from '@shared/testing'; import { ObjectId } from 'bson'; -import { CustomParameterScope, ToolContextType } from '../../../common/enum'; +import { CustomParameterScope, CustomParameterType, ToolContextType } from '../../../common/enum'; import { ExternalToolEntity } from '../../../external-tool/entity'; -import { CustomParameterEntryResponse } from '../../../school-external-tool/controller/dto'; import { SchoolExternalToolEntity } from '../../../school-external-tool/entity'; import { ContextExternalToolEntity, ContextExternalToolType } from '../../entity'; import { @@ -66,10 +65,27 @@ describe('ToolContextController (API)', () => { const course: Course = courseFactory.buildWithId({ teachers: [teacherUser], school }); - const paramEntry: CustomParameterEntryResponse = { name: 'name', value: 'value' }; + const externalToolEntity: ExternalToolEntity = externalToolEntityFactory.buildWithId({ + parameters: [ + customParameterEntityFactory.build({ + name: 'param1', + scope: CustomParameterScope.CONTEXT, + type: CustomParameterType.STRING, + isOptional: false, + }), + customParameterEntityFactory.build({ + name: 'param2', + scope: CustomParameterScope.CONTEXT, + type: CustomParameterType.BOOLEAN, + isOptional: true, + }), + ], + version: 1, + }); const schoolExternalToolEntity: SchoolExternalToolEntity = schoolExternalToolEntityFactory.buildWithId({ + tool: externalToolEntity, school, - schoolParameters: [paramEntry], + schoolParameters: [], toolVersion: 1, }); @@ -78,7 +94,10 @@ describe('ToolContextController (API)', () => { contextId: course.id, displayName: course.name, contextType: ToolContextType.COURSE, - parameters: [paramEntry], + parameters: [ + { name: 'param1', value: 'value' }, + { name: 'param2', value: '' }, + ], toolVersion: 1, }; @@ -87,30 +106,30 @@ describe('ToolContextController (API)', () => { const loggedInClient: TestApiClient = await testApiClient.login(teacherAccount); - const expected: ContextExternalToolResponse = { - id: expect.any(String), - schoolToolId: postParams.schoolToolId, - contextId: postParams.contextId, - displayName: postParams.displayName, - contextType: postParams.contextType, - parameters: [paramEntry], - toolVersion: postParams.toolVersion, - }; - return { loggedInClient, postParams, - expected, }; }; it('should create a contextExternalTool', async () => { - const { postParams, loggedInClient, expected } = await setup(); + const { postParams, loggedInClient } = await setup(); const response = await loggedInClient.post().send(postParams); expect(response.statusCode).toEqual(HttpStatus.CREATED); - expect(response.body).toEqual(expect.objectContaining(expected)); + expect(response.body).toEqual({ + id: expect.any(String), + schoolToolId: postParams.schoolToolId, + contextId: postParams.contextId, + displayName: postParams.displayName, + contextType: postParams.contextType, + parameters: [ + { name: 'param1', value: 'value' }, + { name: 'param2', value: undefined }, + ], + toolVersion: postParams.toolVersion, + }); }); }); diff --git a/apps/server/src/modules/tool/context-external-tool/mapper/context-external-tool-request.mapper.ts b/apps/server/src/modules/tool/context-external-tool/mapper/context-external-tool-request.mapper.ts index 951559afbcc..8e72f5f540b 100644 --- a/apps/server/src/modules/tool/context-external-tool/mapper/context-external-tool-request.mapper.ts +++ b/apps/server/src/modules/tool/context-external-tool/mapper/context-external-tool-request.mapper.ts @@ -25,7 +25,7 @@ export class ContextExternalToolRequestMapper { return customParameterParams.map((customParameterParam: CustomParameterEntryParam) => { return { name: customParameterParam.name, - value: customParameterParam.value, + value: customParameterParam.value || undefined, }; }); } diff --git a/apps/server/src/modules/tool/context-external-tool/service/context-external-tool-validation.service.ts b/apps/server/src/modules/tool/context-external-tool/service/context-external-tool-validation.service.ts index 2cce83a08b2..b12193eadd5 100644 --- a/apps/server/src/modules/tool/context-external-tool/service/context-external-tool-validation.service.ts +++ b/apps/server/src/modules/tool/context-external-tool/service/context-external-tool-validation.service.ts @@ -18,7 +18,7 @@ export class ContextExternalToolValidationService { ) {} async validate(contextExternalTool: ContextExternalTool): Promise { - await this.checkDuplicateInContext(contextExternalTool); + await this.checkDuplicateUsesInContext(contextExternalTool); const loadedSchoolExternalTool: SchoolExternalTool = await this.schoolExternalToolService.findById( contextExternalTool.schoolToolRef.schoolToolId @@ -29,7 +29,7 @@ export class ContextExternalToolValidationService { this.commonToolValidationService.checkCustomParameterEntries(loadedExternalTool, contextExternalTool); } - private async checkDuplicateInContext(contextExternalTool: ContextExternalTool) { + private async checkDuplicateUsesInContext(contextExternalTool: ContextExternalTool) { let duplicate: ContextExternalTool[] = await this.contextExternalToolService.findContextExternalTools({ schoolToolRef: contextExternalTool.schoolToolRef, context: contextExternalTool.contextRef, diff --git a/apps/server/src/modules/tool/school-external-tool/controller/api-test/tool-school.api.spec.ts b/apps/server/src/modules/tool/school-external-tool/controller/api-test/tool-school.api.spec.ts index 3512e66038e..2cca9819b3a 100644 --- a/apps/server/src/modules/tool/school-external-tool/controller/api-test/tool-school.api.spec.ts +++ b/apps/server/src/modules/tool/school-external-tool/controller/api-test/tool-school.api.spec.ts @@ -1,9 +1,11 @@ import { EntityManager, MikroORM } from '@mikro-orm/core'; +import { ServerTestModule } from '@modules/server'; import { HttpStatus, INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { Account, Permission, SchoolEntity, User } from '@shared/domain'; import { accountFactory, + customParameterEntityFactory, externalToolEntityFactory, schoolExternalToolEntityFactory, schoolFactory, @@ -11,9 +13,8 @@ import { UserAndAccountTestFactory, userFactory, } from '@shared/testing'; -import { ServerTestModule } from '@modules/server'; import { ToolConfigurationStatusResponse } from '../../../context-external-tool/controller/dto/tool-configuration-status.response'; -import { ExternalToolEntity } from '../../../external-tool/entity'; +import { CustomParameterScope, CustomParameterType, ExternalToolEntity } from '../../../external-tool/entity'; import { SchoolExternalToolEntity } from '../../entity'; import { CustomParameterEntryParam, @@ -66,31 +67,31 @@ describe('ToolSchoolController (API)', () => { const externalToolEntity: ExternalToolEntity = externalToolEntityFactory.buildWithId({ version: 1, - parameters: [], + parameters: [ + customParameterEntityFactory.build({ + name: 'param1', + scope: CustomParameterScope.SCHOOL, + type: CustomParameterType.STRING, + isOptional: false, + }), + customParameterEntityFactory.build({ + name: 'param2', + scope: CustomParameterScope.SCHOOL, + type: CustomParameterType.BOOLEAN, + isOptional: true, + }), + ], }); - const paramEntry: CustomParameterEntryParam = { name: 'name', value: 'value' }; const postParams: SchoolExternalToolPostParams = { toolId: externalToolEntity.id, schoolId: school.id, version: 1, - parameters: [paramEntry], - }; - - const schoolExternalToolResponse: SchoolExternalToolResponse = new SchoolExternalToolResponse({ - id: expect.any(String), - name: externalToolEntity.name, - schoolId: postParams.schoolId, - toolId: postParams.toolId, - status: ToolConfigurationStatusResponse.LATEST, - toolVersion: postParams.version, parameters: [ - { - name: paramEntry.name, - value: paramEntry.value, - }, + { name: 'param1', value: 'value' }, + { name: 'param2', value: '' }, ], - }); + }; em.persist([ school, @@ -112,7 +113,7 @@ describe('ToolSchoolController (API)', () => { loggedInClientWithMissingPermission, loggedInClient, postParams, - schoolExternalToolResponse, + externalToolEntity, }; }; @@ -125,12 +126,23 @@ describe('ToolSchoolController (API)', () => { }); it('should create an school external tool', async () => { - const { loggedInClient, postParams, schoolExternalToolResponse } = await setup(); + const { loggedInClient, postParams, externalToolEntity } = await setup(); const response = await loggedInClient.post().send(postParams); expect(response.statusCode).toEqual(HttpStatus.CREATED); - expect(response.body).toEqual(schoolExternalToolResponse); + expect(response.body).toEqual({ + id: expect.any(String), + name: externalToolEntity.name, + schoolId: postParams.schoolId, + toolId: postParams.toolId, + status: ToolConfigurationStatusResponse.LATEST, + toolVersion: postParams.version, + parameters: [ + { name: 'param1', value: 'value' }, + { name: 'param2', value: undefined }, + ], + }); const createdSchoolExternalTool: SchoolExternalToolEntity | null = await em.findOne(SchoolExternalToolEntity, { school: postParams.schoolId, @@ -391,7 +403,14 @@ describe('ToolSchoolController (API)', () => { const externalToolEntity: ExternalToolEntity = externalToolEntityFactory.buildWithId({ version: 1, - parameters: [], + parameters: [ + customParameterEntityFactory.build({ + name: 'param1', + scope: CustomParameterScope.SCHOOL, + type: CustomParameterType.STRING, + isOptional: false, + }), + ], }); const externalToolEntity2: ExternalToolEntity = externalToolEntityFactory.buildWithId({ version: 1, @@ -422,7 +441,7 @@ describe('ToolSchoolController (API)', () => { accountWithMissingPermission ); - const paramEntry: CustomParameterEntryParam = { name: 'name', value: 'value' }; + const paramEntry: CustomParameterEntryParam = { name: 'param1', value: 'value' }; const postParams: SchoolExternalToolPostParams = { toolId: externalToolEntity.id, schoolId: school.id, @@ -430,7 +449,7 @@ describe('ToolSchoolController (API)', () => { parameters: [paramEntry], }; - const updatedParamEntry: CustomParameterEntryParam = { name: 'name', value: 'updatedValue' }; + const updatedParamEntry: CustomParameterEntryParam = { name: 'param1', value: 'updatedValue' }; const postParamsUpdate: SchoolExternalToolPostParams = { toolId: externalToolEntity.id, schoolId: school.id, diff --git a/apps/server/src/modules/tool/school-external-tool/mapper/school-external-tool-request.mapper.ts b/apps/server/src/modules/tool/school-external-tool/mapper/school-external-tool-request.mapper.ts index 6617e4ec7fd..eff05c092cb 100644 --- a/apps/server/src/modules/tool/school-external-tool/mapper/school-external-tool-request.mapper.ts +++ b/apps/server/src/modules/tool/school-external-tool/mapper/school-external-tool-request.mapper.ts @@ -1,7 +1,7 @@ import { Injectable } from '@nestjs/common'; +import { CustomParameterEntry } from '../../common/domain'; import { CustomParameterEntryParam, SchoolExternalToolPostParams } from '../controller/dto'; import { SchoolExternalToolDto } from '../uc/dto/school-external-tool.types'; -import { CustomParameterEntry } from '../../common/domain'; @Injectable() export class SchoolExternalToolRequestMapper { @@ -20,7 +20,7 @@ export class SchoolExternalToolRequestMapper { return customParameterParams.map((customParameterParam: CustomParameterEntryParam) => { return { name: customParameterParam.name, - value: customParameterParam.value, + value: customParameterParam.value || undefined, }; }); } diff --git a/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-validation.service.spec.ts b/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-validation.service.spec.ts index 6b4f69d6686..1f2ba7f5eb9 100644 --- a/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-validation.service.spec.ts +++ b/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-validation.service.spec.ts @@ -4,9 +4,9 @@ import { externalToolFactory, schoolExternalToolFactory } from '@shared/testing/ import { CommonToolValidationService } from '../../common/service'; import { ExternalTool } from '../../external-tool/domain'; import { ExternalToolService } from '../../external-tool/service'; +import { IToolFeatures, ToolFeatures } from '../../tool-config'; import { SchoolExternalTool } from '../domain'; import { SchoolExternalToolValidationService } from './school-external-tool-validation.service'; -import { IToolFeatures, ToolFeatures } from '../../tool-config'; describe('SchoolExternalToolValidationService', () => { let module: TestingModule; @@ -82,14 +82,6 @@ describe('SchoolExternalToolValidationService', () => { expect(externalToolService.findById).toHaveBeenCalledWith(schoolExternalTool.toolId); }); - it('should call commonToolValidationService.checkForDuplicateParameters', async () => { - const { schoolExternalTool } = setup(); - - await service.validate(schoolExternalTool); - - expect(commonToolValidationService.checkForDuplicateParameters).toHaveBeenCalledWith(schoolExternalTool); - }); - it('should call commonToolValidationService.checkCustomParameterEntries', async () => { const { schoolExternalTool } = setup(); diff --git a/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-validation.service.ts b/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-validation.service.ts index 3474aa90f5e..899055e321f 100644 --- a/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-validation.service.ts +++ b/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-validation.service.ts @@ -3,8 +3,8 @@ import { ValidationError } from '@shared/common'; import { CommonToolValidationService } from '../../common/service'; import { ExternalTool } from '../../external-tool/domain'; import { ExternalToolService } from '../../external-tool/service'; -import { SchoolExternalTool } from '../domain'; import { IToolFeatures, ToolFeatures } from '../../tool-config'; +import { SchoolExternalTool } from '../domain'; @Injectable() export class SchoolExternalToolValidationService { @@ -15,13 +15,12 @@ export class SchoolExternalToolValidationService { ) {} async validate(schoolExternalTool: SchoolExternalTool): Promise { - this.commonToolValidationService.checkForDuplicateParameters(schoolExternalTool); - const loadedExternalTool: ExternalTool = await this.externalToolService.findById(schoolExternalTool.toolId); if (!this.toolFeatures.toolStatusWithoutVersions) { this.checkVersionMatch(schoolExternalTool.toolVersion, loadedExternalTool.version); } + this.commonToolValidationService.checkCustomParameterEntries(loadedExternalTool, schoolExternalTool); } diff --git a/apps/server/src/shared/testing/factory/external-tool-entity.factory.ts b/apps/server/src/shared/testing/factory/external-tool-entity.factory.ts index 32c077e2529..39ff2662cf7 100644 --- a/apps/server/src/shared/testing/factory/external-tool-entity.factory.ts +++ b/apps/server/src/shared/testing/factory/external-tool-entity.factory.ts @@ -82,8 +82,6 @@ export const customParameterEntityFactory = BaseFactory.define Date: Wed, 15 Nov 2023 20:31:58 +0100 Subject: [PATCH 03/10] BC-5629 batch deletion mechanism (#4521) * first commit * add some tests * add test cases and services * add new (almost empty for now) batch deletion app * refactor config vars * add optional env var for specifying delay between the API calls * add usecases and test cases * fix importing * add type in uc * fix import * add references service that'll load all the references to the data we want to delete * fix most of issue form review * add deletion API client with just a single method for now that allows for sending a deletion request * refactor the env vars for configurting the Admin API * add exporting DeletionClientConfig * move references service to the deletion module * delete unused code * add batch deletion service that makes it possible ot queue deletion for many references at once * move some parts of the interface to the interface subdir * add an interface for the batch deletion summary * move some interfaces to a separate subdir * refactor the batch deletion summary interface * add uc for the batch deletion * remove unused annotation * refactor deletion client implementation * add batch deletion service implementation * add UC for the batch deletion * add a console app for the deletion module and a console command to manage deletion requests queue * remove no longer used app, add param to make it possible to define delay between the client calls for the case one would like to queue many thousands of deletion requests at once * remove no longer used separate batch-deletion module (it became a part of the main deletion module) * fix invalid key * remove no longer used config vars * remove no longer used commands * remove no longer used Nest cli config * remove no longer used code * change name of the method that prepares default headers * add builders for most of the interfaces * add builders for the remaining interfaces * add type in catch clause * do some adjustments, move PushDeletionRequestsOptions interface to a separate file * remove unused import * rollback * remove unnecessary indent * remove unnecessary indents * remove empty line * remove repeated imports * refactor some imports to omit calling Configuration.get() on every subpackage import * add builder for the DeletionRequestOutput class * add unit tests for the batch deletion service * add unit tests for the BatchDeletionUc * modify env keys for the Admin API client configuration, refactor the way the deletion module's console is bootstrapped * fix invalid import, remove unused undefined arg * add comment to ignore console.ts file for coverage * move deletion client config interface to a separate file, refactor function that prepares current config, add unit tests for it * fix invalid import * add more test cases to the deletion client unit tests * change invalid import Co-authored-by: WojciechGrancow <116577704+WojciechGrancow@users.noreply.github.com> * fix invalid import * add builder for the PushDeletionRequestsOptions class, add unit tests for the DeletionQueueConsole * rename the file containing the deletion module console to deletion.console.ts, add coverage exclusion for it for the Sonar coverage analysis * remove deletion.console.ts from the sonar.coverage.exclusions param as it doesn't seem to work anyway * add deletion.console.ts file to the coverage exclusions (another try with different path) * change name of the file containing the deletion console app * fix some imports * move default value for the ADMIN_API_CLIENT object to default.schema.json * move default for the BASE_URL * move Deletion module console app to the apps/ dir * add separate functino to log error and set exit code * add handling of the case that only CR chars are used as a line separators * add use of the BatchDeletionSummaryBuilder in place of an anonymous object creation * fix some imports/exports * refactor console app flow --------- Co-authored-by: WojciechGrancow Co-authored-by: WojciechGrancow <116577704+WojciechGrancow@users.noreply.github.com> --- apps/server/src/apps/deletion-console.app.ts | 31 +++ .../deletion-request-input.builder.spec.ts | 33 +++ .../builder/deletion-request-input.builder.ts | 11 + .../deletion-request-output.builder.spec.ts | 29 +++ .../deletion-request-output.builder.ts | 10 + ...n-request-target-ref-input.builder.spec.ts | 26 +++ ...letion-request-target-ref-input.builder.ts | 7 + .../modules/deletion/client/builder/index.ts | 3 + .../client/deletion-client.config.spec.ts | 41 ++++ .../deletion/client/deletion-client.config.ts | 9 + .../deletion/client/deletion.client.spec.ts | 154 ++++++++++++++ .../deletion/client/deletion.client.ts | 66 ++++++ .../src/modules/deletion/client/index.ts | 3 + .../deletion-client-config.interface.ts | 4 + .../deletion-request-input.interface.ts | 6 + .../deletion-request-output.interface.ts | 4 + ...tion-request-target-ref-input.interface.ts | 4 + .../deletion/client/interface/index.ts | 4 + .../modules/deletion/console/builder/index.ts | 1 + ...sh-delete-requests-options.builder.spec.ts | 43 ++++ .../push-delete-requests-options.builder.ts | 17 ++ .../console/deletion-console.module.ts | 22 ++ .../console/deletion-queue.console.spec.ts | 79 +++++++ .../console/deletion-queue.console.ts | 46 +++++ .../src/modules/deletion/console/index.ts | 1 + .../deletion/console/interface/index.ts | 1 + .../push-delete-requests-options.interface.ts | 6 + apps/server/src/modules/deletion/index.ts | 2 + .../services/batch-deletion.service.spec.ts | 97 +++++++++ .../services/batch-deletion.service.ts | 55 +++++ .../deletion/services/builder/index.ts | 2 + ...eue-deletion-request-input.builder.spec.ts | 27 +++ .../queue-deletion-request-input.builder.ts | 7 + ...ue-deletion-request-output.builder.spec.ts | 46 +++++ .../queue-deletion-request-output.builder.ts | 29 +++ .../src/modules/deletion/services/index.ts | 4 + .../deletion/services/interface/index.ts | 2 + .../queue-deletion-request-input.interface.ts | 5 + ...queue-deletion-request-output.interface.ts | 5 + .../services/references.service.spec.ts | 74 +++++++ .../deletion/services/references.service.ts | 27 +++ .../deletion/uc/batch-deletion.uc.spec.ts | 195 ++++++++++++++++++ .../modules/deletion/uc/batch-deletion.uc.ts | 71 +++++++ ...ch-deletion-summary-detail.builder.spec.ts | 69 +++++++ .../batch-deletion-summary-detail.builder.ts | 8 + .../batch-deletion-summary.builder.spec.ts | 30 +++ .../builder/batch-deletion-summary.builder.ts | 13 ++ .../src/modules/deletion/uc/builder/index.ts | 2 + apps/server/src/modules/deletion/uc/index.ts | 2 + ...batch-deletion-summary-detail.interface.ts | 6 + ...ch-deletion-summary-overall-status.enum.ts | 4 + .../batch-deletion-summary.interface.ts | 9 + .../modules/deletion/uc/interface/index.ts | 3 + config/default.schema.json | 17 ++ nest-cli.json | 9 + package.json | 3 + 56 files changed, 1484 insertions(+) create mode 100644 apps/server/src/apps/deletion-console.app.ts create mode 100644 apps/server/src/modules/deletion/client/builder/deletion-request-input.builder.spec.ts create mode 100644 apps/server/src/modules/deletion/client/builder/deletion-request-input.builder.ts create mode 100644 apps/server/src/modules/deletion/client/builder/deletion-request-output.builder.spec.ts create mode 100644 apps/server/src/modules/deletion/client/builder/deletion-request-output.builder.ts create mode 100644 apps/server/src/modules/deletion/client/builder/deletion-request-target-ref-input.builder.spec.ts create mode 100644 apps/server/src/modules/deletion/client/builder/deletion-request-target-ref-input.builder.ts create mode 100644 apps/server/src/modules/deletion/client/builder/index.ts create mode 100644 apps/server/src/modules/deletion/client/deletion-client.config.spec.ts create mode 100644 apps/server/src/modules/deletion/client/deletion-client.config.ts create mode 100644 apps/server/src/modules/deletion/client/deletion.client.spec.ts create mode 100644 apps/server/src/modules/deletion/client/deletion.client.ts create mode 100644 apps/server/src/modules/deletion/client/index.ts create mode 100644 apps/server/src/modules/deletion/client/interface/deletion-client-config.interface.ts create mode 100644 apps/server/src/modules/deletion/client/interface/deletion-request-input.interface.ts create mode 100644 apps/server/src/modules/deletion/client/interface/deletion-request-output.interface.ts create mode 100644 apps/server/src/modules/deletion/client/interface/deletion-request-target-ref-input.interface.ts create mode 100644 apps/server/src/modules/deletion/client/interface/index.ts create mode 100644 apps/server/src/modules/deletion/console/builder/index.ts create mode 100644 apps/server/src/modules/deletion/console/builder/push-delete-requests-options.builder.spec.ts create mode 100644 apps/server/src/modules/deletion/console/builder/push-delete-requests-options.builder.ts create mode 100644 apps/server/src/modules/deletion/console/deletion-console.module.ts create mode 100644 apps/server/src/modules/deletion/console/deletion-queue.console.spec.ts create mode 100644 apps/server/src/modules/deletion/console/deletion-queue.console.ts create mode 100644 apps/server/src/modules/deletion/console/index.ts create mode 100644 apps/server/src/modules/deletion/console/interface/index.ts create mode 100644 apps/server/src/modules/deletion/console/interface/push-delete-requests-options.interface.ts create mode 100644 apps/server/src/modules/deletion/services/batch-deletion.service.spec.ts create mode 100644 apps/server/src/modules/deletion/services/batch-deletion.service.ts create mode 100644 apps/server/src/modules/deletion/services/builder/index.ts create mode 100644 apps/server/src/modules/deletion/services/builder/queue-deletion-request-input.builder.spec.ts create mode 100644 apps/server/src/modules/deletion/services/builder/queue-deletion-request-input.builder.ts create mode 100644 apps/server/src/modules/deletion/services/builder/queue-deletion-request-output.builder.spec.ts create mode 100644 apps/server/src/modules/deletion/services/builder/queue-deletion-request-output.builder.ts create mode 100644 apps/server/src/modules/deletion/services/interface/index.ts create mode 100644 apps/server/src/modules/deletion/services/interface/queue-deletion-request-input.interface.ts create mode 100644 apps/server/src/modules/deletion/services/interface/queue-deletion-request-output.interface.ts create mode 100644 apps/server/src/modules/deletion/services/references.service.spec.ts create mode 100644 apps/server/src/modules/deletion/services/references.service.ts create mode 100644 apps/server/src/modules/deletion/uc/batch-deletion.uc.spec.ts create mode 100644 apps/server/src/modules/deletion/uc/batch-deletion.uc.ts create mode 100644 apps/server/src/modules/deletion/uc/builder/batch-deletion-summary-detail.builder.spec.ts create mode 100644 apps/server/src/modules/deletion/uc/builder/batch-deletion-summary-detail.builder.ts create mode 100644 apps/server/src/modules/deletion/uc/builder/batch-deletion-summary.builder.spec.ts create mode 100644 apps/server/src/modules/deletion/uc/builder/batch-deletion-summary.builder.ts create mode 100644 apps/server/src/modules/deletion/uc/builder/index.ts create mode 100644 apps/server/src/modules/deletion/uc/index.ts create mode 100644 apps/server/src/modules/deletion/uc/interface/batch-deletion-summary-detail.interface.ts create mode 100644 apps/server/src/modules/deletion/uc/interface/batch-deletion-summary-overall-status.enum.ts create mode 100644 apps/server/src/modules/deletion/uc/interface/batch-deletion-summary.interface.ts diff --git a/apps/server/src/apps/deletion-console.app.ts b/apps/server/src/apps/deletion-console.app.ts new file mode 100644 index 00000000000..cafb137e160 --- /dev/null +++ b/apps/server/src/apps/deletion-console.app.ts @@ -0,0 +1,31 @@ +/* istanbul ignore file */ +import { BootstrapConsole } from 'nestjs-console'; +import { DeletionConsoleModule } from '@modules/deletion'; + +async function run() { + const bootstrap = new BootstrapConsole({ + module: DeletionConsoleModule, + useDecorators: true, + }); + + const app = await bootstrap.init(); + + try { + await app.init(); + + // Execute console application with provided arguments. + await bootstrap.boot(); + } catch (err) { + // eslint-disable-next-line no-console, @typescript-eslint/no-unsafe-call + console.error(err); + + // Set the exit code to 1 to indicate a console app failure. + process.exitCode = 1; + } + + // Always close the app, even if some exception + // has been thrown from the console app. + await app.close(); +} + +void run(); diff --git a/apps/server/src/modules/deletion/client/builder/deletion-request-input.builder.spec.ts b/apps/server/src/modules/deletion/client/builder/deletion-request-input.builder.spec.ts new file mode 100644 index 00000000000..bd49bb841e6 --- /dev/null +++ b/apps/server/src/modules/deletion/client/builder/deletion-request-input.builder.spec.ts @@ -0,0 +1,33 @@ +import { ObjectId } from 'bson'; +import { DeletionRequestInput } from '../interface'; +import { DeletionRequestInputBuilder } from './deletion-request-input.builder'; + +describe(DeletionRequestInputBuilder.name, () => { + describe(DeletionRequestInputBuilder.build.name, () => { + describe('when called with proper arguments', () => { + const setup = () => { + const targetRefDomain = 'school'; + const targetRefId = new ObjectId().toHexString(); + const deleteInMinutes = 43200; + + const expectedOutput: DeletionRequestInput = { + targetRef: { + domain: targetRefDomain, + id: targetRefId, + }, + deleteInMinutes, + }; + + return { targetRefDomain, targetRefId, deleteInMinutes, expectedOutput }; + }; + + it('should return valid object with expected values', () => { + const { targetRefDomain, targetRefId, deleteInMinutes, expectedOutput } = setup(); + + const output = DeletionRequestInputBuilder.build(targetRefDomain, targetRefId, deleteInMinutes); + + expect(output).toStrictEqual(expectedOutput); + }); + }); + }); +}); diff --git a/apps/server/src/modules/deletion/client/builder/deletion-request-input.builder.ts b/apps/server/src/modules/deletion/client/builder/deletion-request-input.builder.ts new file mode 100644 index 00000000000..28091418065 --- /dev/null +++ b/apps/server/src/modules/deletion/client/builder/deletion-request-input.builder.ts @@ -0,0 +1,11 @@ +import { DeletionRequestInput } from '../interface'; +import { DeletionRequestTargetRefInputBuilder } from './deletion-request-target-ref-input.builder'; + +export class DeletionRequestInputBuilder { + static build(targetRefDomain: string, targetRefId: string, deleteInMinutes?: number): DeletionRequestInput { + return { + targetRef: DeletionRequestTargetRefInputBuilder.build(targetRefDomain, targetRefId), + deleteInMinutes, + }; + } +} diff --git a/apps/server/src/modules/deletion/client/builder/deletion-request-output.builder.spec.ts b/apps/server/src/modules/deletion/client/builder/deletion-request-output.builder.spec.ts new file mode 100644 index 00000000000..399821f33ff --- /dev/null +++ b/apps/server/src/modules/deletion/client/builder/deletion-request-output.builder.spec.ts @@ -0,0 +1,29 @@ +import { ObjectId } from 'bson'; +import { DeletionRequestOutput } from '../interface'; +import { DeletionRequestOutputBuilder } from './deletion-request-output.builder'; + +describe(DeletionRequestOutputBuilder.name, () => { + describe(DeletionRequestOutputBuilder.build.name, () => { + describe('when called with proper arguments', () => { + const setup = () => { + const requestId = new ObjectId().toHexString(); + const deletionPlannedAt = new Date(); + + const expectedOutput: DeletionRequestOutput = { + requestId, + deletionPlannedAt, + }; + + return { requestId, deletionPlannedAt, expectedOutput }; + }; + + it('should return valid object with expected values', () => { + const { requestId, deletionPlannedAt, expectedOutput } = setup(); + + const output = DeletionRequestOutputBuilder.build(requestId, deletionPlannedAt); + + expect(output).toStrictEqual(expectedOutput); + }); + }); + }); +}); diff --git a/apps/server/src/modules/deletion/client/builder/deletion-request-output.builder.ts b/apps/server/src/modules/deletion/client/builder/deletion-request-output.builder.ts new file mode 100644 index 00000000000..9192c1a47ce --- /dev/null +++ b/apps/server/src/modules/deletion/client/builder/deletion-request-output.builder.ts @@ -0,0 +1,10 @@ +import { DeletionRequestOutput } from '../interface'; + +export class DeletionRequestOutputBuilder { + static build(requestId: string, deletionPlannedAt: Date): DeletionRequestOutput { + return { + requestId, + deletionPlannedAt, + }; + } +} diff --git a/apps/server/src/modules/deletion/client/builder/deletion-request-target-ref-input.builder.spec.ts b/apps/server/src/modules/deletion/client/builder/deletion-request-target-ref-input.builder.spec.ts new file mode 100644 index 00000000000..74b0631e49d --- /dev/null +++ b/apps/server/src/modules/deletion/client/builder/deletion-request-target-ref-input.builder.spec.ts @@ -0,0 +1,26 @@ +import { ObjectId } from 'bson'; +import { DeletionRequestTargetRefInput } from '../interface'; +import { DeletionRequestTargetRefInputBuilder } from './deletion-request-target-ref-input.builder'; + +describe(DeletionRequestTargetRefInputBuilder.name, () => { + describe(DeletionRequestTargetRefInputBuilder.build.name, () => { + describe('when called with proper arguments', () => { + const setup = () => { + const domain = 'user'; + const id = new ObjectId().toHexString(); + + const expectedOutput: DeletionRequestTargetRefInput = { domain, id }; + + return { domain, id, expectedOutput }; + }; + + it('should return valid object with expected values', () => { + const { domain, id, expectedOutput } = setup(); + + const output = DeletionRequestTargetRefInputBuilder.build(domain, id); + + expect(output).toStrictEqual(expectedOutput); + }); + }); + }); +}); diff --git a/apps/server/src/modules/deletion/client/builder/deletion-request-target-ref-input.builder.ts b/apps/server/src/modules/deletion/client/builder/deletion-request-target-ref-input.builder.ts new file mode 100644 index 00000000000..ed3c0219993 --- /dev/null +++ b/apps/server/src/modules/deletion/client/builder/deletion-request-target-ref-input.builder.ts @@ -0,0 +1,7 @@ +import { DeletionRequestTargetRefInput } from '../interface'; + +export class DeletionRequestTargetRefInputBuilder { + static build(domain: string, id: string): DeletionRequestTargetRefInput { + return { domain, id }; + } +} diff --git a/apps/server/src/modules/deletion/client/builder/index.ts b/apps/server/src/modules/deletion/client/builder/index.ts new file mode 100644 index 00000000000..85644a6b2ee --- /dev/null +++ b/apps/server/src/modules/deletion/client/builder/index.ts @@ -0,0 +1,3 @@ +export * from './deletion-request-target-ref-input.builder'; +export * from './deletion-request-input.builder'; +export * from './deletion-request-output.builder'; diff --git a/apps/server/src/modules/deletion/client/deletion-client.config.spec.ts b/apps/server/src/modules/deletion/client/deletion-client.config.spec.ts new file mode 100644 index 00000000000..a3cae21e425 --- /dev/null +++ b/apps/server/src/modules/deletion/client/deletion-client.config.spec.ts @@ -0,0 +1,41 @@ +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/client/deletion-client.config.ts b/apps/server/src/modules/deletion/client/deletion-client.config.ts new file mode 100644 index 00000000000..db5bf7ff226 --- /dev/null +++ b/apps/server/src/modules/deletion/client/deletion-client.config.ts @@ -0,0 +1,9 @@ +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/client/deletion.client.spec.ts b/apps/server/src/modules/deletion/client/deletion.client.spec.ts new file mode 100644 index 00000000000..096b1f9b082 --- /dev/null +++ b/apps/server/src/modules/deletion/client/deletion.client.spec.ts @@ -0,0 +1,154 @@ +import { of } from 'rxjs'; +import { AxiosResponse } from 'axios'; +import { HttpService } from '@nestjs/axios'; +import { ConfigService } from '@nestjs/config'; +import { Test, TestingModule } from '@nestjs/testing'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { axiosResponseFactory } from '@shared/testing'; +import { DeletionRequestInputBuilder, DeletionRequestOutputBuilder } from '.'; +import { DeletionRequestOutput } from './interface'; +import { DeletionClient } from './deletion.client'; + +describe(DeletionClient.name, () => { + let module: TestingModule; + let client: DeletionClient; + 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'; + }), + }), + }, + { + provide: HttpService, + useValue: createMock(), + }, + ], + }).compile(); + + client = module.get(DeletionClient); + httpService = module.get(HttpService); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + afterAll(async () => { + await module.close(); + }); + + describe('queueDeletionRequest', () => { + describe('when received valid response with expected HTTP status code', () => { + const setup = () => { + const input = DeletionRequestInputBuilder.build('user', '652f1625e9bc1a13bdaae48b'); + + const output: DeletionRequestOutput = DeletionRequestOutputBuilder.build( + '6536ce29b595d7c8e5faf200', + new Date('2024-10-15T12:42:50.521Z') + ); + + const response: AxiosResponse = axiosResponseFactory.build({ + data: output, + status: 202, + }); + + httpService.post.mockReturnValueOnce(of(response)); + + return { input, output }; + }; + + it('should return proper output', async () => { + const { input, output } = setup(); + + const result = await client.queueDeletionRequest(input); + + expect(result).toEqual(output); + }); + }); + + describe('when received invalid HTTP status code in a response', () => { + const setup = () => { + const input = DeletionRequestInputBuilder.build('user', '652f1625e9bc1a13bdaae48b'); + + const output: DeletionRequestOutput = DeletionRequestOutputBuilder.build('', new Date()); + + const response: AxiosResponse = axiosResponseFactory.build({ + data: output, + status: 200, + }); + + httpService.post.mockReturnValueOnce(of(response)); + + return { input }; + }; + + it('should throw an exception', async () => { + const { input } = setup(); + + await expect(client.queueDeletionRequest(input)).rejects.toThrow(Error); + }); + }); + + describe('when received no requestId in a response', () => { + const setup = () => { + const input = DeletionRequestInputBuilder.build('user', '652f1625e9bc1a13bdaae48b'); + + const output: DeletionRequestOutput = DeletionRequestOutputBuilder.build( + '', + new Date('2024-10-15T12:42:50.521Z') + ); + + const response: AxiosResponse = axiosResponseFactory.build({ + data: output, + status: 202, + }); + + httpService.post.mockReturnValueOnce(of(response)); + + return { input }; + }; + + it('should throw an exception', async () => { + const { input } = setup(); + + await expect(client.queueDeletionRequest(input)).rejects.toThrow(Error); + }); + }); + + describe('when received no deletionPlannedAt in a response', () => { + const setup = () => { + const input = DeletionRequestInputBuilder.build('user', '652f1625e9bc1a13bdaae48b'); + + const response: AxiosResponse = axiosResponseFactory.build({ + data: { + requestId: '6536ce29b595d7c8e5faf200', + }, + status: 202, + }); + + httpService.post.mockReturnValueOnce(of(response)); + + return { input }; + }; + + it('should throw an exception', async () => { + const { input } = setup(); + + await expect(client.queueDeletionRequest(input)).rejects.toThrow(Error); + }); + }); + }); +}); diff --git a/apps/server/src/modules/deletion/client/deletion.client.ts b/apps/server/src/modules/deletion/client/deletion.client.ts new file mode 100644 index 00000000000..66bb267d070 --- /dev/null +++ b/apps/server/src/modules/deletion/client/deletion.client.ts @@ -0,0 +1,66 @@ +import { firstValueFrom } from 'rxjs'; +import { AxiosResponse } from 'axios'; +import { Injectable } from '@nestjs/common'; +import { HttpService } from '@nestjs/axios'; +import { ConfigService } from '@nestjs/config'; +import { DeletionRequestInput, DeletionRequestOutput, DeletionClientConfig } from './interface'; + +@Injectable() +export class DeletionClient { + private readonly baseUrl: string; + + private readonly apiKey: string; + + private readonly postDeletionRequestsEndpoint: 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(); + } + + async queueDeletionRequest(input: DeletionRequestInput): Promise { + const request = this.httpService.post(this.postDeletionRequestsEndpoint, input, this.defaultHeaders()); + + return firstValueFrom(request) + .then((resp: AxiosResponse) => { + // 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`); + } + + // 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'); + } + + // 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 new Error('no valid deletionPlannedAt returned from the server'); + } + + return resp.data; + }) + .catch((err: Error) => { + // Throw an error if sending/processing deletion request by the client failed in any way. + throw new Error(`failed to send/process a deletion request: ${err.toString()}`); + }); + } + + private apiKeyHeader() { + return { 'X-Api-Key': this.apiKey }; + } + + private defaultHeaders() { + return { + headers: this.apiKeyHeader(), + }; + } +} diff --git a/apps/server/src/modules/deletion/client/index.ts b/apps/server/src/modules/deletion/client/index.ts new file mode 100644 index 00000000000..fde3db98f3b --- /dev/null +++ b/apps/server/src/modules/deletion/client/index.ts @@ -0,0 +1,3 @@ +export * from './interface'; +export * from './builder'; +export * from './deletion.client'; diff --git a/apps/server/src/modules/deletion/client/interface/deletion-client-config.interface.ts b/apps/server/src/modules/deletion/client/interface/deletion-client-config.interface.ts new file mode 100644 index 00000000000..8178515b6d4 --- /dev/null +++ b/apps/server/src/modules/deletion/client/interface/deletion-client-config.interface.ts @@ -0,0 +1,4 @@ +export interface DeletionClientConfig { + ADMIN_API_CLIENT_BASE_URL: string; + ADMIN_API_CLIENT_API_KEY: string; +} diff --git a/apps/server/src/modules/deletion/client/interface/deletion-request-input.interface.ts b/apps/server/src/modules/deletion/client/interface/deletion-request-input.interface.ts new file mode 100644 index 00000000000..4879ce4d972 --- /dev/null +++ b/apps/server/src/modules/deletion/client/interface/deletion-request-input.interface.ts @@ -0,0 +1,6 @@ +import { DeletionRequestTargetRefInput } from './deletion-request-target-ref-input.interface'; + +export interface DeletionRequestInput { + targetRef: DeletionRequestTargetRefInput; + deleteInMinutes?: number; +} diff --git a/apps/server/src/modules/deletion/client/interface/deletion-request-output.interface.ts b/apps/server/src/modules/deletion/client/interface/deletion-request-output.interface.ts new file mode 100644 index 00000000000..b61a372d5a5 --- /dev/null +++ b/apps/server/src/modules/deletion/client/interface/deletion-request-output.interface.ts @@ -0,0 +1,4 @@ +export interface DeletionRequestOutput { + requestId: string; + deletionPlannedAt: Date; +} diff --git a/apps/server/src/modules/deletion/client/interface/deletion-request-target-ref-input.interface.ts b/apps/server/src/modules/deletion/client/interface/deletion-request-target-ref-input.interface.ts new file mode 100644 index 00000000000..603bb0c13ec --- /dev/null +++ b/apps/server/src/modules/deletion/client/interface/deletion-request-target-ref-input.interface.ts @@ -0,0 +1,4 @@ +export interface DeletionRequestTargetRefInput { + domain: string; + id: string; +} diff --git a/apps/server/src/modules/deletion/client/interface/index.ts b/apps/server/src/modules/deletion/client/interface/index.ts new file mode 100644 index 00000000000..38f0f639731 --- /dev/null +++ b/apps/server/src/modules/deletion/client/interface/index.ts @@ -0,0 +1,4 @@ +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/builder/index.ts b/apps/server/src/modules/deletion/console/builder/index.ts new file mode 100644 index 00000000000..12fd0997ebe --- /dev/null +++ b/apps/server/src/modules/deletion/console/builder/index.ts @@ -0,0 +1 @@ +export * from './push-delete-requests-options.builder'; diff --git a/apps/server/src/modules/deletion/console/builder/push-delete-requests-options.builder.spec.ts b/apps/server/src/modules/deletion/console/builder/push-delete-requests-options.builder.spec.ts new file mode 100644 index 00000000000..5c83defdd1e --- /dev/null +++ b/apps/server/src/modules/deletion/console/builder/push-delete-requests-options.builder.spec.ts @@ -0,0 +1,43 @@ +import { PushDeletionRequestsOptions } from '../interface'; +import { PushDeleteRequestsOptionsBuilder } from './push-delete-requests-options.builder'; + +describe(PushDeleteRequestsOptionsBuilder.name, () => { + describe(PushDeleteRequestsOptionsBuilder.build.name, () => { + describe('when called with proper arguments', () => { + const setup = () => { + const refsFilePath = '/tmp/ids.txt'; + const targetRefDomain = 'school'; + const deleteInMinutes = 43200; + const callsDelayMs = 100; + + const expectedOutput: PushDeletionRequestsOptions = { + refsFilePath, + targetRefDomain, + deleteInMinutes, + callsDelayMs, + }; + + return { + refsFilePath, + targetRefDomain, + deleteInMinutes, + callsDelayMs, + expectedOutput, + }; + }; + + it('should return valid object with expected values', () => { + const { refsFilePath, targetRefDomain, deleteInMinutes, callsDelayMs, expectedOutput } = setup(); + + const output = PushDeleteRequestsOptionsBuilder.build( + refsFilePath, + targetRefDomain, + deleteInMinutes, + callsDelayMs + ); + + expect(output).toStrictEqual(expectedOutput); + }); + }); + }); +}); diff --git a/apps/server/src/modules/deletion/console/builder/push-delete-requests-options.builder.ts b/apps/server/src/modules/deletion/console/builder/push-delete-requests-options.builder.ts new file mode 100644 index 00000000000..f8ceae9263d --- /dev/null +++ b/apps/server/src/modules/deletion/console/builder/push-delete-requests-options.builder.ts @@ -0,0 +1,17 @@ +import { PushDeletionRequestsOptions } from '../interface'; + +export class PushDeleteRequestsOptionsBuilder { + static build( + refsFilePath: string, + targetRefDomain: string, + deleteInMinutes: number, + callsDelayMs: number + ): PushDeletionRequestsOptions { + return { + refsFilePath, + targetRefDomain, + deleteInMinutes, + callsDelayMs, + }; + } +} diff --git a/apps/server/src/modules/deletion/console/deletion-console.module.ts b/apps/server/src/modules/deletion/console/deletion-console.module.ts new file mode 100644 index 00000000000..0585b3631da --- /dev/null +++ b/apps/server/src/modules/deletion/console/deletion-console.module.ts @@ -0,0 +1,22 @@ +import { Module } from '@nestjs/common'; +import { HttpModule } from '@nestjs/axios'; +import { ConfigModule } from '@nestjs/config'; +import { ConsoleModule } from 'nestjs-console'; +import { ConsoleWriterModule } from '@infra/console'; +import { createConfigModuleOptions } from '@src/config'; +import { DeletionClient } from '../client'; +import { getDeletionClientConfig } from '../client/deletion-client.config'; +import { BatchDeletionService } from '../services'; +import { BatchDeletionUc } from '../uc'; +import { DeletionQueueConsole } from './deletion-queue.console'; + +@Module({ + imports: [ + ConsoleModule, + ConsoleWriterModule, + HttpModule, + ConfigModule.forRoot(createConfigModuleOptions(getDeletionClientConfig)), + ], + providers: [DeletionClient, BatchDeletionService, BatchDeletionUc, DeletionQueueConsole], +}) +export class DeletionConsoleModule {} 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 new file mode 100644 index 00000000000..61f9cf0ff53 --- /dev/null +++ b/apps/server/src/modules/deletion/console/deletion-queue.console.spec.ts @@ -0,0 +1,79 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { ConsoleWriterService } from '@infra/console'; +import { createMock } from '@golevelup/ts-jest'; +import { BatchDeletionUc } from '../uc'; +import { DeletionQueueConsole } from './deletion-queue.console'; +import { PushDeleteRequestsOptionsBuilder } from './builder'; + +describe(DeletionQueueConsole.name, () => { + let module: TestingModule; + let console: DeletionQueueConsole; + let batchDeletionUc: BatchDeletionUc; + + beforeAll(async () => { + module = await Test.createTestingModule({ + providers: [ + DeletionQueueConsole, + { + provide: ConsoleWriterService, + useValue: createMock(), + }, + { + provide: BatchDeletionUc, + useValue: createMock(), + }, + ], + }).compile(); + + console = module.get(DeletionQueueConsole); + batchDeletionUc = module.get(BatchDeletionUc); + }); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + afterAll(async () => { + await module.close(); + }); + + it('console should be defined', () => { + expect(console).toBeDefined(); + }); + + describe('pushDeletionRequests', () => { + describe('when called with valid options', () => { + const setup = () => { + const refsFilePath = '/tmp/ids.txt'; + const targetRefDomain = 'school'; + const deleteInMinutes = 43200; + const callsDelayMs = 100; + + const options = PushDeleteRequestsOptionsBuilder.build( + refsFilePath, + targetRefDomain, + deleteInMinutes, + callsDelayMs + ); + + return { + refsFilePath, + targetRefDomain, + deleteInMinutes, + callsDelayMs, + options, + }; + }; + + it(`should call ${BatchDeletionUc.name} with proper arguments`, async () => { + const { refsFilePath, targetRefDomain, deleteInMinutes, callsDelayMs, options } = setup(); + + const spy = jest.spyOn(batchDeletionUc, 'deleteRefsFromTxtFile'); + + await console.pushDeletionRequests(options); + + expect(spy).toBeCalledWith(refsFilePath, targetRefDomain, deleteInMinutes, callsDelayMs); + }); + }); + }); +}); diff --git a/apps/server/src/modules/deletion/console/deletion-queue.console.ts b/apps/server/src/modules/deletion/console/deletion-queue.console.ts new file mode 100644 index 00000000000..c8b133dba84 --- /dev/null +++ b/apps/server/src/modules/deletion/console/deletion-queue.console.ts @@ -0,0 +1,46 @@ +import { Console, Command } from 'nestjs-console'; +import { ConsoleWriterService } from '@infra/console'; +import { BatchDeletionUc } from '../uc'; +import { PushDeletionRequestsOptions } from './interface'; + +@Console({ command: 'queue', description: 'Console providing an access to the deletion queue.' }) +export class DeletionQueueConsole { + constructor(private consoleWriter: ConsoleWriterService, private batchDeletionUc: BatchDeletionUc) {} + + @Command({ + command: 'push', + description: 'Push new deletion requests to the deletion queue.', + options: [ + { + flags: '-rfp, --refsFilePath ', + description: 'Path of the file containing all the references to the data that should be deleted.', + required: true, + }, + { + flags: '-trd, --targetRefDomain ', + description: 'Name of the target ref domain.', + required: false, + }, + { + flags: '-dim, --deleteInMinutes ', + description: 'Number of minutes after which the data deletion process should begin.', + required: false, + }, + { + flags: '-cdm, --callsDelayMs ', + description: 'Delay between all the performed client calls, in milliseconds.', + required: false, + }, + ], + }) + async pushDeletionRequests(options: PushDeletionRequestsOptions): Promise { + const summary = await this.batchDeletionUc.deleteRefsFromTxtFile( + options.refsFilePath, + options.targetRefDomain, + options.deleteInMinutes ? Number(options.deleteInMinutes) : undefined, + options.callsDelayMs ? Number(options.callsDelayMs) : undefined + ); + + this.consoleWriter.info(JSON.stringify(summary)); + } +} diff --git a/apps/server/src/modules/deletion/console/index.ts b/apps/server/src/modules/deletion/console/index.ts new file mode 100644 index 00000000000..db47bd8c99f --- /dev/null +++ b/apps/server/src/modules/deletion/console/index.ts @@ -0,0 +1 @@ +export * from './deletion-console.module'; diff --git a/apps/server/src/modules/deletion/console/interface/index.ts b/apps/server/src/modules/deletion/console/interface/index.ts new file mode 100644 index 00000000000..2fcb281430f --- /dev/null +++ b/apps/server/src/modules/deletion/console/interface/index.ts @@ -0,0 +1 @@ +export * from './push-delete-requests-options.interface'; diff --git a/apps/server/src/modules/deletion/console/interface/push-delete-requests-options.interface.ts b/apps/server/src/modules/deletion/console/interface/push-delete-requests-options.interface.ts new file mode 100644 index 00000000000..a54f652cf94 --- /dev/null +++ b/apps/server/src/modules/deletion/console/interface/push-delete-requests-options.interface.ts @@ -0,0 +1,6 @@ +export interface PushDeletionRequestsOptions { + refsFilePath: string; + targetRefDomain: string; + deleteInMinutes: number; + callsDelayMs: number; +} diff --git a/apps/server/src/modules/deletion/index.ts b/apps/server/src/modules/deletion/index.ts index bd89c1e8d84..793f306e7a0 100644 --- a/apps/server/src/modules/deletion/index.ts +++ b/apps/server/src/modules/deletion/index.ts @@ -1,2 +1,4 @@ export * from './deletion.module'; export * from './services'; +export * from './client'; +export * from './console'; diff --git a/apps/server/src/modules/deletion/services/batch-deletion.service.spec.ts b/apps/server/src/modules/deletion/services/batch-deletion.service.spec.ts new file mode 100644 index 00000000000..640e290af1a --- /dev/null +++ b/apps/server/src/modules/deletion/services/batch-deletion.service.spec.ts @@ -0,0 +1,97 @@ +import { ObjectId } from 'bson'; +import { Test, TestingModule } from '@nestjs/testing'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { DeletionClient, DeletionRequestOutput, DeletionRequestOutputBuilder } from '../client'; +import { QueueDeletionRequestInputBuilder, QueueDeletionRequestOutputBuilder } from './builder'; +import { BatchDeletionService } from './batch-deletion.service'; + +describe(BatchDeletionService.name, () => { + let module: TestingModule; + let service: BatchDeletionService; + let deletionClient: DeepMocked; + + beforeAll(async () => { + module = await Test.createTestingModule({ + providers: [ + BatchDeletionService, + { + provide: DeletionClient, + useValue: createMock(), + }, + ], + }).compile(); + + service = module.get(BatchDeletionService); + deletionClient = module.get(DeletionClient); + }); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + afterAll(async () => { + await module.close(); + }); + + it('service should be defined', () => { + expect(service).toBeDefined(); + }); + + describe('queueDeletionRequests', () => { + describe('when called with valid inputs array and a requested delay between the client calls', () => { + describe("when client doesn't throw any error", () => { + const setup = () => { + const inputs = [QueueDeletionRequestInputBuilder.build('user', new ObjectId().toHexString(), 60)]; + + const requestId = new ObjectId().toHexString(); + const deletionPlannedAt = new Date(); + + const queueDeletionRequestOutput: DeletionRequestOutput = DeletionRequestOutputBuilder.build( + requestId, + deletionPlannedAt + ); + + deletionClient.queueDeletionRequest.mockResolvedValueOnce(queueDeletionRequestOutput); + + const expectedOutput = QueueDeletionRequestOutputBuilder.buildSuccess(requestId, deletionPlannedAt); + + const expectedOutputs = [expectedOutput]; + + return { inputs, expectedOutputs }; + }; + + it('should return an output object with successful status info', async () => { + const { inputs, expectedOutputs } = setup(); + + const outputs = await service.queueDeletionRequests(inputs, 1); + + expect(outputs).toStrictEqual(expectedOutputs); + }); + }); + + describe('when client throws an error', () => { + const setup = () => { + const inputs = [QueueDeletionRequestInputBuilder.build('user', new ObjectId().toHexString(), 60)]; + + const error = new Error('connection error'); + + deletionClient.queueDeletionRequest.mockRejectedValueOnce(error); + + const expectedOutput = QueueDeletionRequestOutputBuilder.buildError(error); + + const expectedOutputs = [expectedOutput]; + + return { inputs, expectedOutputs }; + }; + + it('should return an output object with failure status info', async () => { + const { inputs, expectedOutputs } = setup(); + + const outputs = await service.queueDeletionRequests(inputs); + + expect(outputs).toStrictEqual(expectedOutputs); + }); + }); + }); + }); +}); diff --git a/apps/server/src/modules/deletion/services/batch-deletion.service.ts b/apps/server/src/modules/deletion/services/batch-deletion.service.ts new file mode 100644 index 00000000000..d8c14f315a0 --- /dev/null +++ b/apps/server/src/modules/deletion/services/batch-deletion.service.ts @@ -0,0 +1,55 @@ +import { Injectable } from '@nestjs/common'; +import { QueueDeletionRequestOutputBuilder } from './builder'; +import { DeletionClient, DeletionRequestInputBuilder } from '../client'; +import { QueueDeletionRequestInput, QueueDeletionRequestOutput } from './interface'; + +@Injectable() +export class BatchDeletionService { + constructor(private readonly deletionClient: DeletionClient) {} + + async queueDeletionRequests( + inputs: QueueDeletionRequestInput[], + callsDelayMilliseconds?: number + ): Promise { + const outputs: QueueDeletionRequestOutput[] = []; + + // For every provided deletion request input, try to queue it via deletion client. + // In any case, add the result of the trial to the outputs - it will be either a valid + // response in a form of a requestId + deletionPlannedAt values pair or some error + // returned from the client. In any case, every input should be processed. + for (const input of inputs) { + const deletionRequestInput = DeletionRequestInputBuilder.build( + input.targetRefDomain, + input.targetRefId, + input.deleteInMinutes + ); + + try { + // eslint-disable-next-line no-await-in-loop + const deletionRequestOutput = await this.deletionClient.queueDeletionRequest(deletionRequestInput); + + // In case of a successful client response, add the + // requestId + deletionPlannedAt values pair to the outputs. + outputs.push( + QueueDeletionRequestOutputBuilder.buildSuccess( + deletionRequestOutput.requestId, + deletionRequestOutput.deletionPlannedAt + ) + ); + } catch (err) { + // In case of a failure client response, add the full error message to the outputs. + outputs.push(QueueDeletionRequestOutputBuilder.buildError(err as Error)); + } + + // If any delay between the client calls has been requested, "sleep" for the specified amount of time. + if (callsDelayMilliseconds && callsDelayMilliseconds > 0) { + // eslint-disable-next-line no-await-in-loop + await new Promise((resolve) => { + setTimeout(resolve, callsDelayMilliseconds); + }); + } + } + + return outputs; + } +} diff --git a/apps/server/src/modules/deletion/services/builder/index.ts b/apps/server/src/modules/deletion/services/builder/index.ts new file mode 100644 index 00000000000..acd85a37989 --- /dev/null +++ b/apps/server/src/modules/deletion/services/builder/index.ts @@ -0,0 +1,2 @@ +export * from './queue-deletion-request-input.builder'; +export * from './queue-deletion-request-output.builder'; diff --git a/apps/server/src/modules/deletion/services/builder/queue-deletion-request-input.builder.spec.ts b/apps/server/src/modules/deletion/services/builder/queue-deletion-request-input.builder.spec.ts new file mode 100644 index 00000000000..e5d87858156 --- /dev/null +++ b/apps/server/src/modules/deletion/services/builder/queue-deletion-request-input.builder.spec.ts @@ -0,0 +1,27 @@ +import { ObjectId } from 'bson'; +import { QueueDeletionRequestInput } from '../interface'; +import { QueueDeletionRequestInputBuilder } from './queue-deletion-request-input.builder'; + +describe(QueueDeletionRequestInputBuilder.name, () => { + describe(QueueDeletionRequestInputBuilder.build.name, () => { + describe('when called with proper arguments', () => { + const setup = () => { + const targetRefDomain = 'user'; + const targetRefId = new ObjectId().toHexString(); + const deleteInMinutes = 60; + + const expectedOutput: QueueDeletionRequestInput = { targetRefDomain, targetRefId, deleteInMinutes }; + + return { targetRefDomain, targetRefId, deleteInMinutes, expectedOutput }; + }; + + it('should return valid object with expected values', () => { + const { targetRefDomain, targetRefId, deleteInMinutes, expectedOutput } = setup(); + + const output = QueueDeletionRequestInputBuilder.build(targetRefDomain, targetRefId, deleteInMinutes); + + expect(output).toStrictEqual(expectedOutput); + }); + }); + }); +}); diff --git a/apps/server/src/modules/deletion/services/builder/queue-deletion-request-input.builder.ts b/apps/server/src/modules/deletion/services/builder/queue-deletion-request-input.builder.ts new file mode 100644 index 00000000000..a7fff2152b9 --- /dev/null +++ b/apps/server/src/modules/deletion/services/builder/queue-deletion-request-input.builder.ts @@ -0,0 +1,7 @@ +import { QueueDeletionRequestInput } from '../interface'; + +export class QueueDeletionRequestInputBuilder { + static build(targetRefDomain: string, targetRefId: string, deleteInMinutes: number): QueueDeletionRequestInput { + return { targetRefDomain, targetRefId, deleteInMinutes }; + } +} diff --git a/apps/server/src/modules/deletion/services/builder/queue-deletion-request-output.builder.spec.ts b/apps/server/src/modules/deletion/services/builder/queue-deletion-request-output.builder.spec.ts new file mode 100644 index 00000000000..cd835a9cf4a --- /dev/null +++ b/apps/server/src/modules/deletion/services/builder/queue-deletion-request-output.builder.spec.ts @@ -0,0 +1,46 @@ +import { ObjectId } from 'bson'; +import { QueueDeletionRequestOutput } from '../interface'; +import { QueueDeletionRequestOutputBuilder } from './queue-deletion-request-output.builder'; + +describe(QueueDeletionRequestOutputBuilder.name, () => { + describe(QueueDeletionRequestOutputBuilder.buildSuccess.name, () => { + describe('when called with proper arguments', () => { + const setup = () => { + const requestId = new ObjectId().toHexString(); + const deletionPlannedAt = new Date(); + + const expectedOutput: QueueDeletionRequestOutput = { requestId, deletionPlannedAt }; + + return { requestId, deletionPlannedAt, expectedOutput }; + }; + + it('should return valid object with expected values', () => { + const { requestId, deletionPlannedAt, expectedOutput } = setup(); + + const output = QueueDeletionRequestOutputBuilder.buildSuccess(requestId, deletionPlannedAt); + + expect(output).toStrictEqual(expectedOutput); + }); + }); + }); + + describe(QueueDeletionRequestOutputBuilder.buildError.name, () => { + describe('when called with proper arguments', () => { + const setup = () => { + const error = new Error('test error message'); + + const expectedOutput: QueueDeletionRequestOutput = { error: error.toString() }; + + return { error, expectedOutput }; + }; + + it('should return valid object with expected values', () => { + const { error, expectedOutput } = setup(); + + const output = QueueDeletionRequestOutputBuilder.buildError(error); + + expect(output).toStrictEqual(expectedOutput); + }); + }); + }); +}); diff --git a/apps/server/src/modules/deletion/services/builder/queue-deletion-request-output.builder.ts b/apps/server/src/modules/deletion/services/builder/queue-deletion-request-output.builder.ts new file mode 100644 index 00000000000..c44e503cbaf --- /dev/null +++ b/apps/server/src/modules/deletion/services/builder/queue-deletion-request-output.builder.ts @@ -0,0 +1,29 @@ +import { QueueDeletionRequestOutput } from '../interface'; + +export class QueueDeletionRequestOutputBuilder { + private static build(requestId?: string, deletionPlannedAt?: Date, error?: string): QueueDeletionRequestOutput { + const output: QueueDeletionRequestOutput = {}; + + if (requestId) { + output.requestId = requestId; + } + + if (deletionPlannedAt) { + output.deletionPlannedAt = deletionPlannedAt; + } + + if (error) { + output.error = error.toString(); + } + + return output; + } + + static buildSuccess(requestId: string, deletionPlannedAt: Date): QueueDeletionRequestOutput { + return this.build(requestId, deletionPlannedAt); + } + + static buildError(err: Error): QueueDeletionRequestOutput { + return this.build(undefined, undefined, err.toString()); + } +} diff --git a/apps/server/src/modules/deletion/services/index.ts b/apps/server/src/modules/deletion/services/index.ts index 9661354718c..52e0b6ba22d 100644 --- a/apps/server/src/modules/deletion/services/index.ts +++ b/apps/server/src/modules/deletion/services/index.ts @@ -1 +1,5 @@ +export * from './interface'; +export * from './builder'; +export * from './references.service'; +export * from './batch-deletion.service'; export * from './deletion-request.service'; diff --git a/apps/server/src/modules/deletion/services/interface/index.ts b/apps/server/src/modules/deletion/services/interface/index.ts new file mode 100644 index 00000000000..8a455440798 --- /dev/null +++ b/apps/server/src/modules/deletion/services/interface/index.ts @@ -0,0 +1,2 @@ +export * from './queue-deletion-request-input.interface'; +export * from './queue-deletion-request-output.interface'; diff --git a/apps/server/src/modules/deletion/services/interface/queue-deletion-request-input.interface.ts b/apps/server/src/modules/deletion/services/interface/queue-deletion-request-input.interface.ts new file mode 100644 index 00000000000..b421943bce9 --- /dev/null +++ b/apps/server/src/modules/deletion/services/interface/queue-deletion-request-input.interface.ts @@ -0,0 +1,5 @@ +export interface QueueDeletionRequestInput { + targetRefDomain: string; + targetRefId: string; + deleteInMinutes: number; +} diff --git a/apps/server/src/modules/deletion/services/interface/queue-deletion-request-output.interface.ts b/apps/server/src/modules/deletion/services/interface/queue-deletion-request-output.interface.ts new file mode 100644 index 00000000000..375ff811857 --- /dev/null +++ b/apps/server/src/modules/deletion/services/interface/queue-deletion-request-output.interface.ts @@ -0,0 +1,5 @@ +export interface QueueDeletionRequestOutput { + requestId?: string; + deletionPlannedAt?: Date; + error?: string; +} diff --git a/apps/server/src/modules/deletion/services/references.service.spec.ts b/apps/server/src/modules/deletion/services/references.service.spec.ts new file mode 100644 index 00000000000..26eb6e0c8d7 --- /dev/null +++ b/apps/server/src/modules/deletion/services/references.service.spec.ts @@ -0,0 +1,74 @@ +import fs from 'fs'; +import { ReferencesService } from './references.service'; + +describe(ReferencesService.name, () => { + describe(ReferencesService.loadFromTxtFile.name, () => { + const setup = (mockedFileContent: string) => { + jest.spyOn(fs, 'readFileSync').mockReturnValueOnce(mockedFileContent); + }; + + describe('when passed a completely empty file (without any content)', () => { + it('should return an empty references array', () => { + setup(''); + + const references = ReferencesService.loadFromTxtFile('references.txt'); + + expect(references).toEqual([]); + }); + }); + + describe('when passed a file without any references (just some empty lines)', () => { + it('should return an empty references array', () => { + setup('\n\n \n \n\n\n \n\n\n'); + + const references = ReferencesService.loadFromTxtFile('references.txt'); + + expect(references).toEqual([]); + }); + }); + + describe('when passed a file with 3 references on a few separate lines', () => { + describe('split with CRs', () => { + it('should return an array with all the references present in a file', () => { + setup('653fd3b784ca851b17e98579\r653fd3b784ca851b17e9857a\r653fd3b784ca851b17e9857b\n\n\n'); + + const references = ReferencesService.loadFromTxtFile('references.txt'); + + expect(references).toEqual([ + '653fd3b784ca851b17e98579', + '653fd3b784ca851b17e9857a', + '653fd3b784ca851b17e9857b', + ]); + }); + }); + + describe('split with LFs', () => { + it('should return an array with all the references present in a file', () => { + setup('653fd3b784ca851b17e98579\n653fd3b784ca851b17e9857a\n653fd3b784ca851b17e9857b\n\n\n'); + + const references = ReferencesService.loadFromTxtFile('references.txt'); + + expect(references).toEqual([ + '653fd3b784ca851b17e98579', + '653fd3b784ca851b17e9857a', + '653fd3b784ca851b17e9857b', + ]); + }); + }); + + describe('split with CRLFs', () => { + it('should return an array with all the references present in a file', () => { + setup('653fd3b784ca851b17e98579\r\n653fd3b784ca851b17e9857a\r\n653fd3b784ca851b17e9857b\r\n\r\n\r\n'); + + const references = ReferencesService.loadFromTxtFile('references.txt'); + + expect(references).toEqual([ + '653fd3b784ca851b17e98579', + '653fd3b784ca851b17e9857a', + '653fd3b784ca851b17e9857b', + ]); + }); + }); + }); + }); +}); diff --git a/apps/server/src/modules/deletion/services/references.service.ts b/apps/server/src/modules/deletion/services/references.service.ts new file mode 100644 index 00000000000..991c36c02af --- /dev/null +++ b/apps/server/src/modules/deletion/services/references.service.ts @@ -0,0 +1,27 @@ +import fs from 'fs'; + +export class ReferencesService { + static loadFromTxtFile(filePath: string): string[] { + let fileContent = fs.readFileSync(filePath).toString(); + + // Replace all the CRLF occurrences with just a LF. + fileContent = fileContent.replace(/\r\n?/g, '\n'); + + // Split the whole file content by a line feed (LF) char (\n). + const fileLines = fileContent.split('\n'); + + const references: string[] = []; + + // Iterate over all the file lines and if it contains a valid id (which is + // basically any non-empty string), add it to the loaded references array. + fileLines.forEach((fileLine) => { + const reference = fileLine.trim(); + + if (reference && reference.length > 0) { + references.push(reference); + } + }); + + return references; + } +} diff --git a/apps/server/src/modules/deletion/uc/batch-deletion.uc.spec.ts b/apps/server/src/modules/deletion/uc/batch-deletion.uc.spec.ts new file mode 100644 index 00000000000..7292f36efb0 --- /dev/null +++ b/apps/server/src/modules/deletion/uc/batch-deletion.uc.spec.ts @@ -0,0 +1,195 @@ +import { ObjectId } from 'bson'; +import { Test, TestingModule } from '@nestjs/testing'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { + BatchDeletionService, + QueueDeletionRequestInputBuilder, + QueueDeletionRequestOutput, + QueueDeletionRequestOutputBuilder, + ReferencesService, +} from '../services'; +import { BatchDeletionSummaryDetail, BatchDeletionSummaryOverallStatus } from './interface'; +import { BatchDeletionSummaryDetailBuilder } from './builder'; +import { BatchDeletionUc } from './batch-deletion.uc'; + +describe(BatchDeletionUc.name, () => { + let module: TestingModule; + let uc: BatchDeletionUc; + let batchDeletionService: DeepMocked; + + beforeAll(async () => { + module = await Test.createTestingModule({ + providers: [ + BatchDeletionUc, + { + provide: BatchDeletionService, + useValue: createMock(), + }, + ], + }).compile(); + + uc = module.get(BatchDeletionUc); + batchDeletionService = module.get(BatchDeletionService); + }); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + afterAll(async () => { + await module.close(); + }); + + it('uc should be defined', () => { + expect(uc).toBeDefined(); + }); + + describe('deleteRefsFromTxtFile', () => { + describe('when called with valid arguments', () => { + describe('when batch deletion service returns an expected amount of outputs', () => { + describe('when only successful executions took place', () => { + const setup = () => { + const targetRefsCount = 3; + + const targetRefIds: string[] = []; + const outputs: QueueDeletionRequestOutput[] = []; + + for (let i = 0; i < targetRefsCount; i += 1) { + targetRefIds.push(new ObjectId().toHexString()); + outputs.push(QueueDeletionRequestOutputBuilder.buildSuccess(new ObjectId().toHexString(), new Date())); + } + + ReferencesService.loadFromTxtFile = jest.fn().mockReturnValueOnce(targetRefIds); + + batchDeletionService.queueDeletionRequests.mockResolvedValueOnce([outputs[0], outputs[1], outputs[2]]); + + const targetRefDomain = 'school'; + const deleteInMinutes = 60; + + const expectedSummaryFieldsDetails: BatchDeletionSummaryDetail[] = []; + + for (let i = 0; i < targetRefIds.length; i += 1) { + expectedSummaryFieldsDetails.push( + BatchDeletionSummaryDetailBuilder.build( + QueueDeletionRequestInputBuilder.build(targetRefDomain, targetRefIds[i], deleteInMinutes), + outputs[i] + ) + ); + } + + const expectedSummaryFields = { + overallStatus: BatchDeletionSummaryOverallStatus.SUCCESS, + successCount: 3, + failureCount: 0, + details: expectedSummaryFieldsDetails, + }; + + const refsFilePath = '/tmp/ids.txt'; + + return { refsFilePath, targetRefDomain, deleteInMinutes, expectedSummaryFields }; + }; + + it('should return proper summary with all the successes and a successful overall status', async () => { + const { refsFilePath, targetRefDomain, deleteInMinutes, expectedSummaryFields } = setup(); + + const summary = await uc.deleteRefsFromTxtFile(refsFilePath, targetRefDomain, deleteInMinutes); + + expect(summary.executionTimeMilliseconds).toBeGreaterThan(0); + expect(summary).toMatchObject(expectedSummaryFields); + }); + }); + + describe('when both successful and failed executions took place', () => { + const setup = () => { + const targetRefsCount = 3; + + const targetRefIds: string[] = []; + + for (let i = 0; i < targetRefsCount; i += 1) { + targetRefIds.push(new ObjectId().toHexString()); + } + + const targetRefDomain = 'school'; + const deleteInMinutes = 60; + + ReferencesService.loadFromTxtFile = jest.fn().mockReturnValueOnce(targetRefIds); + + const outputs = [ + QueueDeletionRequestOutputBuilder.buildSuccess(new ObjectId().toHexString(), new Date()), + QueueDeletionRequestOutputBuilder.buildError(new Error('some error occurred...')), + QueueDeletionRequestOutputBuilder.buildSuccess(new ObjectId().toHexString(), new Date()), + ]; + + batchDeletionService.queueDeletionRequests.mockResolvedValueOnce([outputs[0], outputs[1], outputs[2]]); + + const expectedSummaryFieldsDetails: BatchDeletionSummaryDetail[] = []; + + for (let i = 0; i < targetRefIds.length; i += 1) { + expectedSummaryFieldsDetails.push( + BatchDeletionSummaryDetailBuilder.build( + QueueDeletionRequestInputBuilder.build(targetRefDomain, targetRefIds[i], deleteInMinutes), + outputs[i] + ) + ); + } + + const expectedSummaryFields = { + overallStatus: BatchDeletionSummaryOverallStatus.FAILURE, + successCount: 2, + failureCount: 1, + details: expectedSummaryFieldsDetails, + }; + + const refsFilePath = '/tmp/ids.txt'; + + return { refsFilePath, targetRefDomain, deleteInMinutes, expectedSummaryFields }; + }; + + it('should return proper summary with all the successes and failures', async () => { + const { refsFilePath, targetRefDomain, deleteInMinutes, expectedSummaryFields } = setup(); + + const summary = await uc.deleteRefsFromTxtFile(refsFilePath, targetRefDomain, deleteInMinutes); + + expect(summary.executionTimeMilliseconds).toBeGreaterThan(0); + expect(summary).toMatchObject(expectedSummaryFields); + }); + }); + }); + + describe('when batch deletion service returns an invalid amount of outputs', () => { + const setup = () => { + const targetRefsCount = 3; + + const targetRefIds: string[] = []; + + for (let i = 0; i < targetRefsCount; i += 1) { + targetRefIds.push(new ObjectId().toHexString()); + } + + ReferencesService.loadFromTxtFile = jest.fn().mockReturnValueOnce(targetRefIds); + + const outputs: QueueDeletionRequestOutput[] = []; + + for (let i = 0; i < targetRefsCount - 1; i += 1) { + targetRefIds.push(new ObjectId().toHexString()); + outputs.push(QueueDeletionRequestOutputBuilder.buildSuccess(new ObjectId().toHexString(), new Date())); + } + + batchDeletionService.queueDeletionRequests.mockResolvedValueOnce(outputs); + + const refsFilePath = '/tmp/ids.txt'; + + return { refsFilePath }; + }; + + it('should throw an error', async () => { + const { refsFilePath } = setup(); + + const func = () => uc.deleteRefsFromTxtFile(refsFilePath); + + await expect(func()).rejects.toThrow(); + }); + }); + }); + }); +}); diff --git a/apps/server/src/modules/deletion/uc/batch-deletion.uc.ts b/apps/server/src/modules/deletion/uc/batch-deletion.uc.ts new file mode 100644 index 00000000000..258b1b53f65 --- /dev/null +++ b/apps/server/src/modules/deletion/uc/batch-deletion.uc.ts @@ -0,0 +1,71 @@ +import { Injectable } from '@nestjs/common'; +import { BatchDeletionSummaryBuilder, BatchDeletionSummaryDetailBuilder } from './builder'; +import { + ReferencesService, + BatchDeletionService, + QueueDeletionRequestInput, + QueueDeletionRequestInputBuilder, +} from '../services'; +import { BatchDeletionSummary, BatchDeletionSummaryOverallStatus } from './interface'; + +@Injectable() +export class BatchDeletionUc { + constructor(private readonly batchDeletionService: BatchDeletionService) {} + + async deleteRefsFromTxtFile( + refsFilePath: string, + targetRefDomain = 'user', + deleteInMinutes = 43200, // 43200 minutes = 720 hours = 30 days + callsDelayMilliseconds?: number + ): Promise { + // First, load all the references from the provided text file (with given path). + const refsFromTxtFile = ReferencesService.loadFromTxtFile(refsFilePath); + + const inputs: QueueDeletionRequestInput[] = []; + + // For each reference found in a given file, add it to the inputs + // array (with added targetRefDomain and deleteInMinutes fields). + refsFromTxtFile.forEach((ref) => + inputs.push(QueueDeletionRequestInputBuilder.build(targetRefDomain, ref, deleteInMinutes)) + ); + + // Measure the overall queueing execution time by setting the start... + const startTime = performance.now(); + + const outputs = await this.batchDeletionService.queueDeletionRequests(inputs, callsDelayMilliseconds); + + // ...and end timestamps before and after the batch deletion service method execution. + const endTime = performance.now(); + + // Throw an error if the returned outputs number doesn't match the returned inputs number. + if (outputs.length !== inputs.length) { + throw new Error( + 'invalid result from the batch deletion service - expected to ' + + 'receive the same amount of outputs as the provided inputs, ' + + `instead received ${outputs.length} outputs for ${inputs.length} inputs` + ); + } + + const summary: BatchDeletionSummary = BatchDeletionSummaryBuilder.build(endTime - startTime); + + // Go through every received output and, in case of an error presence increase + // a failure count or, in case of no error, increase a success count. + for (let i = 0; i < outputs.length; i += 1) { + if (outputs[i].error) { + summary.failureCount += 1; + } else { + summary.successCount += 1; + } + + // Also add all the processed inputs and outputs details to the overall summary. + summary.details.push(BatchDeletionSummaryDetailBuilder.build(inputs[i], outputs[i])); + } + + // If no failure has been spotted, assume an overall success. + if (summary.failureCount === 0) { + summary.overallStatus = BatchDeletionSummaryOverallStatus.SUCCESS; + } + + return summary; + } +} diff --git a/apps/server/src/modules/deletion/uc/builder/batch-deletion-summary-detail.builder.spec.ts b/apps/server/src/modules/deletion/uc/builder/batch-deletion-summary-detail.builder.spec.ts new file mode 100644 index 00000000000..43ea82e86d5 --- /dev/null +++ b/apps/server/src/modules/deletion/uc/builder/batch-deletion-summary-detail.builder.spec.ts @@ -0,0 +1,69 @@ +import { ObjectId } from '@mikro-orm/mongodb'; +import { BatchDeletionSummaryDetail } from '..'; +import { QueueDeletionRequestInput, QueueDeletionRequestOutput } from '../../services'; +import { BatchDeletionSummaryDetailBuilder } from './batch-deletion-summary-detail.builder'; + +describe(BatchDeletionSummaryDetailBuilder.name, () => { + describe(BatchDeletionSummaryDetailBuilder.build.name, () => { + describe('when called with proper arguments for', () => { + describe('a successful output case', () => { + const setup = () => { + const deletionRequestInput: QueueDeletionRequestInput = { + targetRefDomain: 'user', + targetRefId: new ObjectId().toHexString(), + deleteInMinutes: 1440, + }; + + const deletionRequestOutput: QueueDeletionRequestOutput = { + requestId: new ObjectId().toHexString(), + deletionPlannedAt: new Date(), + }; + + const expectedOutput: BatchDeletionSummaryDetail = { + input: deletionRequestInput, + output: deletionRequestOutput, + }; + + return { deletionRequestInput, deletionRequestOutput, expectedOutput }; + }; + + it('should return valid object with expected values', () => { + const { deletionRequestInput, deletionRequestOutput, expectedOutput } = setup(); + + const output = BatchDeletionSummaryDetailBuilder.build(deletionRequestInput, deletionRequestOutput); + + expect(output).toStrictEqual(expectedOutput); + }); + }); + + describe('an error output case', () => { + const setup = () => { + const deletionRequestInput: QueueDeletionRequestInput = { + targetRefDomain: 'user', + targetRefId: new ObjectId().toHexString(), + deleteInMinutes: 1440, + }; + + const deletionRequestOutput: QueueDeletionRequestOutput = { + error: 'some error occurred...', + }; + + const expectedOutput: BatchDeletionSummaryDetail = { + input: deletionRequestInput, + output: deletionRequestOutput, + }; + + return { deletionRequestInput, deletionRequestOutput, expectedOutput }; + }; + + it('should return valid object with expected values', () => { + const { deletionRequestInput, deletionRequestOutput, expectedOutput } = setup(); + + const output = BatchDeletionSummaryDetailBuilder.build(deletionRequestInput, deletionRequestOutput); + + expect(output).toStrictEqual(expectedOutput); + }); + }); + }); + }); +}); diff --git a/apps/server/src/modules/deletion/uc/builder/batch-deletion-summary-detail.builder.ts b/apps/server/src/modules/deletion/uc/builder/batch-deletion-summary-detail.builder.ts new file mode 100644 index 00000000000..9ebbce66171 --- /dev/null +++ b/apps/server/src/modules/deletion/uc/builder/batch-deletion-summary-detail.builder.ts @@ -0,0 +1,8 @@ +import { QueueDeletionRequestInput, QueueDeletionRequestOutput } from '../../services'; +import { BatchDeletionSummaryDetail } from '../interface'; + +export class BatchDeletionSummaryDetailBuilder { + static build(input: QueueDeletionRequestInput, output: QueueDeletionRequestOutput): BatchDeletionSummaryDetail { + return { input, output }; + } +} diff --git a/apps/server/src/modules/deletion/uc/builder/batch-deletion-summary.builder.spec.ts b/apps/server/src/modules/deletion/uc/builder/batch-deletion-summary.builder.spec.ts new file mode 100644 index 00000000000..a2b534602d4 --- /dev/null +++ b/apps/server/src/modules/deletion/uc/builder/batch-deletion-summary.builder.spec.ts @@ -0,0 +1,30 @@ +import { BatchDeletionSummary, BatchDeletionSummaryOverallStatus } from '../interface'; +import { BatchDeletionSummaryBuilder } from './batch-deletion-summary.builder'; + +describe(BatchDeletionSummaryBuilder.name, () => { + describe(BatchDeletionSummaryBuilder.build.name, () => { + describe('when called with proper arguments', () => { + const setup = () => { + const executionTimeMilliseconds = 142; + + const expectedOutput: BatchDeletionSummary = { + executionTimeMilliseconds: 142, + overallStatus: BatchDeletionSummaryOverallStatus.FAILURE, + successCount: 0, + failureCount: 0, + details: [], + }; + + return { executionTimeMilliseconds, expectedOutput }; + }; + + it('should return valid object with expected values', () => { + const { executionTimeMilliseconds, expectedOutput } = setup(); + + const output = BatchDeletionSummaryBuilder.build(executionTimeMilliseconds); + + expect(output).toStrictEqual(expectedOutput); + }); + }); + }); +}); diff --git a/apps/server/src/modules/deletion/uc/builder/batch-deletion-summary.builder.ts b/apps/server/src/modules/deletion/uc/builder/batch-deletion-summary.builder.ts new file mode 100644 index 00000000000..57fa2bcccd9 --- /dev/null +++ b/apps/server/src/modules/deletion/uc/builder/batch-deletion-summary.builder.ts @@ -0,0 +1,13 @@ +import { BatchDeletionSummary, BatchDeletionSummaryOverallStatus } from '../interface'; + +export class BatchDeletionSummaryBuilder { + static build(executionTimeMilliseconds: number): BatchDeletionSummary { + return { + executionTimeMilliseconds, + overallStatus: BatchDeletionSummaryOverallStatus.FAILURE, + successCount: 0, + failureCount: 0, + details: [], + }; + } +} diff --git a/apps/server/src/modules/deletion/uc/builder/index.ts b/apps/server/src/modules/deletion/uc/builder/index.ts new file mode 100644 index 00000000000..46733980f94 --- /dev/null +++ b/apps/server/src/modules/deletion/uc/builder/index.ts @@ -0,0 +1,2 @@ +export * from './batch-deletion-summary-detail.builder'; +export * from './batch-deletion-summary.builder'; diff --git a/apps/server/src/modules/deletion/uc/index.ts b/apps/server/src/modules/deletion/uc/index.ts new file mode 100644 index 00000000000..cf74de969e5 --- /dev/null +++ b/apps/server/src/modules/deletion/uc/index.ts @@ -0,0 +1,2 @@ +export * from './interface'; +export * from './batch-deletion.uc'; diff --git a/apps/server/src/modules/deletion/uc/interface/batch-deletion-summary-detail.interface.ts b/apps/server/src/modules/deletion/uc/interface/batch-deletion-summary-detail.interface.ts new file mode 100644 index 00000000000..4fe99c13fad --- /dev/null +++ b/apps/server/src/modules/deletion/uc/interface/batch-deletion-summary-detail.interface.ts @@ -0,0 +1,6 @@ +import { QueueDeletionRequestInput, QueueDeletionRequestOutput } from '../../services'; + +export interface BatchDeletionSummaryDetail { + input: QueueDeletionRequestInput; + output: QueueDeletionRequestOutput; +} diff --git a/apps/server/src/modules/deletion/uc/interface/batch-deletion-summary-overall-status.enum.ts b/apps/server/src/modules/deletion/uc/interface/batch-deletion-summary-overall-status.enum.ts new file mode 100644 index 00000000000..4ae91bdf70e --- /dev/null +++ b/apps/server/src/modules/deletion/uc/interface/batch-deletion-summary-overall-status.enum.ts @@ -0,0 +1,4 @@ +export const enum BatchDeletionSummaryOverallStatus { + SUCCESS = 'success', + FAILURE = 'failure', +} diff --git a/apps/server/src/modules/deletion/uc/interface/batch-deletion-summary.interface.ts b/apps/server/src/modules/deletion/uc/interface/batch-deletion-summary.interface.ts new file mode 100644 index 00000000000..ce633e164f1 --- /dev/null +++ b/apps/server/src/modules/deletion/uc/interface/batch-deletion-summary.interface.ts @@ -0,0 +1,9 @@ +import { BatchDeletionSummaryDetail } from './batch-deletion-summary-detail.interface'; + +export interface BatchDeletionSummary { + executionTimeMilliseconds: number; + overallStatus: string; + successCount: number; + failureCount: number; + details: BatchDeletionSummaryDetail[]; +} diff --git a/apps/server/src/modules/deletion/uc/interface/index.ts b/apps/server/src/modules/deletion/uc/interface/index.ts index 95786098275..9dcf644f410 100644 --- a/apps/server/src/modules/deletion/uc/interface/index.ts +++ b/apps/server/src/modules/deletion/uc/interface/index.ts @@ -1 +1,4 @@ +export * from './batch-deletion-summary-overall-status.enum'; +export * from './batch-deletion-summary-detail.interface'; +export * from './batch-deletion-summary.interface'; export * from './interfaces'; diff --git a/config/default.schema.json b/config/default.schema.json index b971968a4ae..89d0a328a59 100644 --- a/config/default.schema.json +++ b/config/default.schema.json @@ -1343,6 +1343,23 @@ "description": "Number of simultaneously synchronized students, teachers and classes" } } + }, + "ADMIN_API_CLIENT": { + "type": "object", + "description": "Configuration of the schulcloud-server's admin API client.", + "properties": { + "BASE_URL": { + "type": "string", + "description": "Base URL of the Admin API." + }, + "API_KEY": { + "type": "string", + "description": "API key for accessing the Admin API." + } + }, + "default": { + "BASE_URL": "http://localhost:4030" + } } }, "required": [], diff --git a/nest-cli.json b/nest-cli.json index 73dea03c093..8ce5461bb6f 100644 --- a/nest-cli.json +++ b/nest-cli.json @@ -45,6 +45,15 @@ "tsConfigPath": "apps/server/tsconfig.app.json" } }, + "deletion-console": { + "type": "application", + "root": "apps/server", + "entryFile": "apps/deletion-console.app", + "sourceRoot": "apps/server/src", + "compilerOptions": { + "tsConfigPath": "apps/server/tsconfig.app.json" + } + }, "files-storage": { "type": "application", "root": "apps/server", diff --git a/package.json b/package.json index 53a0752ee10..4153b7636e7 100644 --- a/package.json +++ b/package.json @@ -76,6 +76,9 @@ "nest:start:console": "nest start console --", "nest:start:console:dev": "nest start console --watch --", "nest:start:console:debug": "nest start console --debug --watch --", + "nest:start:deletion-console": "nest start deletion-console --", + "nest:start:deletion-console:dev": "nest start deletion-console --watch --", + "nest:start:deletion-console:debug": "nest start deletion-console --debug --watch --", "nest:test": "npm run nest:test:cov && npm run nest:lint", "nest:test:all": "jest", "nest:test:unit": "jest \"^((?!\\.api\\.spec\\.ts).)*\\.spec\\.ts$\"", From 7cf730c05447f9e243499c7174ad68ab7afe6ed8 Mon Sep 17 00:00:00 2001 From: Martin Schuhmacher <55735359+MartinSchuhmacher@users.noreply.github.com> Date: Thu, 16 Nov 2023 12:09:00 +0100 Subject: [PATCH 04/10] BC-5405 - Metadata on k8s ressources (#4517) * add specific labels to deployments, cronjobs and pods --- .../templates/amqp-files-deployment.yml.j2 | 14 +++++++++++++ .../api-delete-s3-files-cronjob.yml.j2 | 21 ++++++++++++++++--- .../templates/api-files-deployment.yml.j2 | 14 +++++++++++++ .../templates/api-fwu-deployment.yml.j2 | 14 +++++++++++++ .../templates/deployment.yml.j2 | 14 +++++++++++++ .../preview-generator-deployment.yml.j2 | 14 +++++++++++++ .../preview-generator-scaled-object.yml.j2 | 7 +++++++ .../templates/api-h5p-deployment.yml.j2 | 14 +++++++++++++ .../templates/management-deployment.yml.j2 | 14 +++++++++++++ .../api-ldap-sync-full-cronjob.yml.j2 | 17 +++++++++++++++ .../api-ldap-worker-deployment.yml.j2 | 14 +++++++++++++ .../api-tsp-sync-base-cronjob.yml.j2 | 20 +++++++++++++++--- .../templates/api-tsp-sync-deployment.yml.j2 | 14 +++++++++++++ .../api-tsp-sync-school-cronjob.yml.j2 | 20 +++++++++++++++--- 14 files changed, 202 insertions(+), 9 deletions(-) diff --git a/ansible/roles/schulcloud-server-core/templates/amqp-files-deployment.yml.j2 b/ansible/roles/schulcloud-server-core/templates/amqp-files-deployment.yml.j2 index 17384845b71..e3843effc48 100644 --- a/ansible/roles/schulcloud-server-core/templates/amqp-files-deployment.yml.j2 +++ b/ansible/roles/schulcloud-server-core/templates/amqp-files-deployment.yml.j2 @@ -5,6 +5,13 @@ metadata: namespace: {{ NAMESPACE }} labels: app: amqp-files + app.kubernetes.io/part-of: schulcloud-verbund + app.kubernetes.io/version: {{ SCHULCLOUD_SERVER_IMAGE_TAG }} + app.kubernetes.io/name: amqp-files + app.kubernetes.io/component: files + app.kubernetes.io/managed-by: ansible + git.branch: {{ SCHULCLOUD_SERVER_BRANCH_NAME }} + git.repo: {{ SCHULCLOUD_SERVER_REPO_NAME }} spec: replicas: {{ AMQP_FILE_STORAGE_REPLICAS|default("1", true) }} strategy: @@ -21,6 +28,13 @@ spec: metadata: labels: app: amqp-files + app.kubernetes.io/part-of: schulcloud-verbund + app.kubernetes.io/version: {{ SCHULCLOUD_SERVER_IMAGE_TAG }} + app.kubernetes.io/name: amqp-files + app.kubernetes.io/component: files + app.kubernetes.io/managed-by: ansible + git.branch: {{ SCHULCLOUD_SERVER_BRANCH_NAME }} + git.repo: {{ SCHULCLOUD_SERVER_REPO_NAME }} spec: securityContext: runAsUser: 1000 diff --git a/ansible/roles/schulcloud-server-core/templates/api-delete-s3-files-cronjob.yml.j2 b/ansible/roles/schulcloud-server-core/templates/api-delete-s3-files-cronjob.yml.j2 index 317d37dcda9..2eb0fdd4094 100644 --- a/ansible/roles/schulcloud-server-core/templates/api-delete-s3-files-cronjob.yml.j2 +++ b/ansible/roles/schulcloud-server-core/templates/api-delete-s3-files-cronjob.yml.j2 @@ -5,14 +5,18 @@ metadata: labels: app: api cronjob: delete-s3-files + app.kubernetes.io/part-of: schulcloud-verbund + app.kubernetes.io/version: {{ SCHULCLOUD_SERVER_IMAGE_TAG }} + app.kubernetes.io/name: delete-s3-files + app.kubernetes.io/component: files + app.kubernetes.io/managed-by: ansible + git.branch: {{ SCHULCLOUD_SERVER_BRANCH_NAME }} + git.repo: {{ SCHULCLOUD_SERVER_REPO_NAME }} name: api-delete-s3-files-cronjob spec: concurrencyPolicy: Forbid schedule: "{{ SERVER_FILE_DELETION_CRONJOB_SCHEDULE|default("@hourly", true) }}" jobTemplate: - labels: - app: api - cronjob: delete-s3-files spec: template: spec: @@ -34,3 +38,14 @@ spec: cpu: {{ API_CPU_REQUESTS|default("100m", true) }} memory: {{ API_MEMORY_REQUESTS|default("150Mi", true) }} restartPolicy: OnFailure + metadata: + labels: + app: api + cronjob: delete-s3-files + app.kubernetes.io/part-of: schulcloud-verbund + app.kubernetes.io/version: {{ SCHULCLOUD_SERVER_IMAGE_TAG }} + app.kubernetes.io/name: delete-s3-files + app.kubernetes.io/component: files + app.kubernetes.io/managed-by: ansible + git.branch: {{ SCHULCLOUD_SERVER_BRANCH_NAME }} + git.repo: {{ SCHULCLOUD_SERVER_REPO_NAME }} diff --git a/ansible/roles/schulcloud-server-core/templates/api-files-deployment.yml.j2 b/ansible/roles/schulcloud-server-core/templates/api-files-deployment.yml.j2 index 727b5a9f4f0..7866e23dcc3 100644 --- a/ansible/roles/schulcloud-server-core/templates/api-files-deployment.yml.j2 +++ b/ansible/roles/schulcloud-server-core/templates/api-files-deployment.yml.j2 @@ -5,6 +5,13 @@ metadata: namespace: {{ NAMESPACE }} labels: app: api-files + app.kubernetes.io/part-of: schulcloud-verbund + app.kubernetes.io/version: {{ SCHULCLOUD_SERVER_IMAGE_TAG }} + app.kubernetes.io/name: api-files + app.kubernetes.io/component: files + app.kubernetes.io/managed-by: ansible + git.branch: {{ SCHULCLOUD_SERVER_BRANCH_NAME }} + git.repo: {{ SCHULCLOUD_SERVER_REPO_NAME }} spec: replicas: {{ API_FILE_STORAGE_REPLICAS|default("1", true) }} strategy: @@ -21,6 +28,13 @@ spec: metadata: labels: app: api-files + app.kubernetes.io/part-of: schulcloud-verbund + app.kubernetes.io/version: {{ SCHULCLOUD_SERVER_IMAGE_TAG }} + app.kubernetes.io/name: api-files + app.kubernetes.io/component: files + app.kubernetes.io/managed-by: ansible + git.branch: {{ SCHULCLOUD_SERVER_BRANCH_NAME }} + git.repo: {{ SCHULCLOUD_SERVER_REPO_NAME }} spec: securityContext: runAsUser: 1000 diff --git a/ansible/roles/schulcloud-server-core/templates/api-fwu-deployment.yml.j2 b/ansible/roles/schulcloud-server-core/templates/api-fwu-deployment.yml.j2 index c8d7e6f894c..5fe3f528bde 100644 --- a/ansible/roles/schulcloud-server-core/templates/api-fwu-deployment.yml.j2 +++ b/ansible/roles/schulcloud-server-core/templates/api-fwu-deployment.yml.j2 @@ -5,6 +5,13 @@ metadata: namespace: {{ NAMESPACE }} labels: app: api-fwu + app.kubernetes.io/part-of: schulcloud-verbund + app.kubernetes.io/version: {{ SCHULCLOUD_SERVER_IMAGE_TAG }} + app.kubernetes.io/name: api-fwu + app.kubernetes.io/component: fwu + app.kubernetes.io/managed-by: ansible + git.branch: {{ SCHULCLOUD_SERVER_BRANCH_NAME }} + git.repo: {{ SCHULCLOUD_SERVER_REPO_NAME }} spec: replicas: {{ API_FWU_LEARNING_CONTENTS_REPLICAS|default("1", true) }} strategy: @@ -21,6 +28,13 @@ spec: metadata: labels: app: api-fwu + app.kubernetes.io/part-of: schulcloud-verbund + app.kubernetes.io/version: {{ SCHULCLOUD_SERVER_IMAGE_TAG }} + app.kubernetes.io/name: api-fwu + app.kubernetes.io/component: fwu + app.kubernetes.io/managed-by: ansible + git.branch: {{ SCHULCLOUD_SERVER_BRANCH_NAME }} + git.repo: {{ SCHULCLOUD_SERVER_REPO_NAME }} spec: securityContext: runAsUser: 1000 diff --git a/ansible/roles/schulcloud-server-core/templates/deployment.yml.j2 b/ansible/roles/schulcloud-server-core/templates/deployment.yml.j2 index 0750bf91495..a18102bb55d 100644 --- a/ansible/roles/schulcloud-server-core/templates/deployment.yml.j2 +++ b/ansible/roles/schulcloud-server-core/templates/deployment.yml.j2 @@ -5,6 +5,13 @@ metadata: namespace: {{ NAMESPACE }} labels: app: api + app.kubernetes.io/part-of: schulcloud-verbund + app.kubernetes.io/version: {{ SCHULCLOUD_SERVER_IMAGE_TAG }} + app.kubernetes.io/name: api + app.kubernetes.io/component: server + app.kubernetes.io/managed-by: ansible + git.branch: {{ SCHULCLOUD_SERVER_BRANCH_NAME }} + git.repo: {{ SCHULCLOUD_SERVER_REPO_NAME }} spec: replicas: {{ API_REPLICAS|default("1", true) }} strategy: @@ -21,6 +28,13 @@ spec: metadata: labels: app: api + app.kubernetes.io/part-of: schulcloud-verbund + app.kubernetes.io/version: {{ SCHULCLOUD_SERVER_IMAGE_TAG }} + app.kubernetes.io/name: api + app.kubernetes.io/component: server + app.kubernetes.io/managed-by: ansible + git.branch: {{ SCHULCLOUD_SERVER_BRANCH_NAME }} + git.repo: {{ SCHULCLOUD_SERVER_REPO_NAME }} spec: securityContext: runAsUser: 1000 diff --git a/ansible/roles/schulcloud-server-core/templates/preview-generator-deployment.yml.j2 b/ansible/roles/schulcloud-server-core/templates/preview-generator-deployment.yml.j2 index 51d87b88755..cffa1dc02b8 100644 --- a/ansible/roles/schulcloud-server-core/templates/preview-generator-deployment.yml.j2 +++ b/ansible/roles/schulcloud-server-core/templates/preview-generator-deployment.yml.j2 @@ -5,6 +5,13 @@ metadata: namespace: {{ NAMESPACE }} labels: app: preview-generator + app.kubernetes.io/part-of: schulcloud-verbund + app.kubernetes.io/version: {{ SCHULCLOUD_SERVER_IMAGE_TAG }} + app.kubernetes.io/name: preview-generator + app.kubernetes.io/component: files + app.kubernetes.io/managed-by: ansible + git.branch: {{ SCHULCLOUD_SERVER_BRANCH_NAME }} + git.repo: {{ SCHULCLOUD_SERVER_REPO_NAME }} spec: replicas: {{ AMQP_FILE_PREVIEW_REPLICAS|default("1", true) }} strategy: @@ -21,6 +28,13 @@ spec: metadata: labels: app: preview-generator + app.kubernetes.io/part-of: schulcloud-verbund + app.kubernetes.io/version: {{ SCHULCLOUD_SERVER_IMAGE_TAG }} + app.kubernetes.io/name: preview-generator + app.kubernetes.io/component: files + app.kubernetes.io/managed-by: ansible + git.branch: {{ SCHULCLOUD_SERVER_BRANCH_NAME }} + git.repo: {{ SCHULCLOUD_SERVER_REPO_NAME }} spec: securityContext: runAsUser: 1000 diff --git a/ansible/roles/schulcloud-server-core/templates/preview-generator-scaled-object.yml.j2 b/ansible/roles/schulcloud-server-core/templates/preview-generator-scaled-object.yml.j2 index 2f8f9091b8e..6058b1b0ccb 100644 --- a/ansible/roles/schulcloud-server-core/templates/preview-generator-scaled-object.yml.j2 +++ b/ansible/roles/schulcloud-server-core/templates/preview-generator-scaled-object.yml.j2 @@ -6,6 +6,13 @@ metadata: namespace: {{ NAMESPACE }} labels: app: preview-generator + app.kubernetes.io/part-of: schulcloud-verbund + app.kubernetes.io/version: {{ SCHULCLOUD_SERVER_IMAGE_TAG }} + app.kubernetes.io/name: preview-generator + app.kubernetes.io/component: files + app.kubernetes.io/managed-by: ansible + git.branch: {{ SCHULCLOUD_SERVER_BRANCH_NAME }} + git.repo: {{ SCHULCLOUD_SERVER_REPO_NAME }} spec: scaleTargetRef: name: preview-generator-deployment diff --git a/ansible/roles/schulcloud-server-h5p/templates/api-h5p-deployment.yml.j2 b/ansible/roles/schulcloud-server-h5p/templates/api-h5p-deployment.yml.j2 index 4e41f454ed9..e5923a2e1b9 100644 --- a/ansible/roles/schulcloud-server-h5p/templates/api-h5p-deployment.yml.j2 +++ b/ansible/roles/schulcloud-server-h5p/templates/api-h5p-deployment.yml.j2 @@ -5,6 +5,13 @@ metadata: namespace: {{ NAMESPACE }} labels: app: api-h5p + app.kubernetes.io/part-of: schulcloud-verbund + app.kubernetes.io/version: {{ SCHULCLOUD_SERVER_IMAGE_TAG }} + app.kubernetes.io/name: api-h5p + app.kubernetes.io/component: h5p + app.kubernetes.io/managed-by: ansible + git.branch: {{ SCHULCLOUD_SERVER_BRANCH_NAME }} + git.repo: {{ SCHULCLOUD_SERVER_REPO_NAME }} spec: replicas: {{ API_H5P_EDITOR_REPLICAS|default("1", true) }} strategy: @@ -21,6 +28,13 @@ spec: metadata: labels: app: api-h5p + app.kubernetes.io/part-of: schulcloud-verbund + app.kubernetes.io/version: {{ SCHULCLOUD_SERVER_IMAGE_TAG }} + app.kubernetes.io/name: api-h5p + app.kubernetes.io/component: h5p + app.kubernetes.io/managed-by: ansible + git.branch: {{ SCHULCLOUD_SERVER_BRANCH_NAME }} + git.repo: {{ SCHULCLOUD_SERVER_REPO_NAME }} spec: securityContext: runAsUser: 1000 diff --git a/ansible/roles/schulcloud-server-init/templates/management-deployment.yml.j2 b/ansible/roles/schulcloud-server-init/templates/management-deployment.yml.j2 index 9d99c672be8..93378069cc9 100644 --- a/ansible/roles/schulcloud-server-init/templates/management-deployment.yml.j2 +++ b/ansible/roles/schulcloud-server-init/templates/management-deployment.yml.j2 @@ -5,6 +5,13 @@ metadata: namespace: {{ NAMESPACE }} labels: app: management-deployment + app.kubernetes.io/part-of: schulcloud-verbund + app.kubernetes.io/version: {{ SCHULCLOUD_SERVER_IMAGE_TAG }} + app.kubernetes.io/name: management-deployment + app.kubernetes.io/component: management + app.kubernetes.io/managed-by: ansible + git.branch: {{ SCHULCLOUD_SERVER_BRANCH_NAME }} + git.repo: {{ SCHULCLOUD_SERVER_REPO_NAME }} spec: replicas: {{ API_MANAGEMENT_REPLICAS|default("1", true) }} strategy: @@ -20,6 +27,13 @@ spec: metadata: labels: app: management-deployment + app.kubernetes.io/part-of: schulcloud-verbund + app.kubernetes.io/version: {{ SCHULCLOUD_SERVER_IMAGE_TAG }} + app.kubernetes.io/name: management-deployment + app.kubernetes.io/component: management + app.kubernetes.io/managed-by: ansible + git.branch: {{ SCHULCLOUD_SERVER_BRANCH_NAME }} + git.repo: {{ SCHULCLOUD_SERVER_REPO_NAME }} spec: securityContext: runAsUser: 1000 diff --git a/ansible/roles/schulcloud-server-ldapsync/templates/api-ldap-sync-full-cronjob.yml.j2 b/ansible/roles/schulcloud-server-ldapsync/templates/api-ldap-sync-full-cronjob.yml.j2 index 74cc37d75b6..db852a7081f 100644 --- a/ansible/roles/schulcloud-server-ldapsync/templates/api-ldap-sync-full-cronjob.yml.j2 +++ b/ansible/roles/schulcloud-server-ldapsync/templates/api-ldap-sync-full-cronjob.yml.j2 @@ -4,6 +4,13 @@ metadata: namespace: {{ NAMESPACE }} labels: app: api-ldapsync-cronjob + app.kubernetes.io/part-of: schulcloud-verbund + app.kubernetes.io/version: {{ SCHULCLOUD_SERVER_IMAGE_TAG }} + app.kubernetes.io/name: api-ldapsync-cronjob + app.kubernetes.io/component: sync + app.kubernetes.io/managed-by: ansible + git.branch: {{ SCHULCLOUD_SERVER_BRANCH_NAME }} + git.repo: {{ SCHULCLOUD_SERVER_REPO_NAME }} name: api-ldapsync-full-cronjob spec: schedule: "{{ SERVER_LDAP_SYNC_FULL_CRONJOB|default("0 3 * * 3,6", true) }}" @@ -30,3 +37,13 @@ spec: cpu: {{ API_CPU_REQUESTS|default("100m", true) }} memory: {{ API_MEMORY_REQUESTS|default("150Mi", true) }} restartPolicy: OnFailure + metadata: + labels: + app: api-ldapsync-cronjob + app.kubernetes.io/part-of: schulcloud-verbund + app.kubernetes.io/version: {{ SCHULCLOUD_SERVER_IMAGE_TAG }} + app.kubernetes.io/name: api-ldapsync-cronjob + app.kubernetes.io/component: sync + app.kubernetes.io/managed-by: ansible + git.branch: {{ SCHULCLOUD_SERVER_BRANCH_NAME }} + git.repo: {{ SCHULCLOUD_SERVER_REPO_NAME }} diff --git a/ansible/roles/schulcloud-server-ldapsync/templates/api-ldap-worker-deployment.yml.j2 b/ansible/roles/schulcloud-server-ldapsync/templates/api-ldap-worker-deployment.yml.j2 index 838009883d4..9f03dbee883 100644 --- a/ansible/roles/schulcloud-server-ldapsync/templates/api-ldap-worker-deployment.yml.j2 +++ b/ansible/roles/schulcloud-server-ldapsync/templates/api-ldap-worker-deployment.yml.j2 @@ -5,6 +5,13 @@ metadata: namespace: {{ NAMESPACE }} labels: app: api-worker + app.kubernetes.io/part-of: schulcloud-verbund + app.kubernetes.io/version: {{ SCHULCLOUD_SERVER_IMAGE_TAG }} + app.kubernetes.io/name: api-worker + app.kubernetes.io/component: sync + app.kubernetes.io/managed-by: ansible + git.branch: {{ SCHULCLOUD_SERVER_BRANCH_NAME }} + git.repo: {{ SCHULCLOUD_SERVER_REPO_NAME }} spec: replicas: {{ API_WORKER_REPLICAS|default("2", true) }} strategy: @@ -21,6 +28,13 @@ spec: metadata: labels: app: api-worker + app.kubernetes.io/part-of: schulcloud-verbund + app.kubernetes.io/version: {{ SCHULCLOUD_SERVER_IMAGE_TAG }} + app.kubernetes.io/name: api-worker + app.kubernetes.io/component: sync + app.kubernetes.io/managed-by: ansible + git.branch: {{ SCHULCLOUD_SERVER_BRANCH_NAME }} + git.repo: {{ SCHULCLOUD_SERVER_REPO_NAME }} spec: securityContext: runAsUser: 1000 diff --git a/ansible/roles/schulcloud-server-tspsync/templates/api-tsp-sync-base-cronjob.yml.j2 b/ansible/roles/schulcloud-server-tspsync/templates/api-tsp-sync-base-cronjob.yml.j2 index f5a3d0751f4..e46dac14330 100644 --- a/ansible/roles/schulcloud-server-tspsync/templates/api-tsp-sync-base-cronjob.yml.j2 +++ b/ansible/roles/schulcloud-server-tspsync/templates/api-tsp-sync-base-cronjob.yml.j2 @@ -4,15 +4,19 @@ metadata: namespace: {{ NAMESPACE }} labels: app: api-tsp-sync-cronjob + app.kubernetes.io/part-of: schulcloud-verbund + app.kubernetes.io/version: {{ SCHULCLOUD_SERVER_IMAGE_TAG }} + app.kubernetes.io/name: api-tsp-sync-cronjob + app.kubernetes.io/component: sync + app.kubernetes.io/managed-by: ansible + git.branch: {{ SCHULCLOUD_SERVER_BRANCH_NAME }} + git.repo: {{ SCHULCLOUD_SERVER_REPO_NAME }} name: api-tsp-sync-base-cronjob spec: schedule: "{{ SERVER_TSP_SYNC_BASE_CRONJOB|default("9 3 * * *", true) }}" jobTemplate: spec: template: - metadata: - labels: - app: api-tsp-sync-cronjob spec: containers: - name: api-tsp-sync-base-cronjob @@ -23,3 +27,13 @@ spec: command: ['/bin/sh','-c'] args: ['curl -H "X-API-Key: $SYNC_API_KEY" "http://{{ API_TSP_SYNC_SVC|default("api-tsp-sync-svc", true) }}:3030/api/v1/sync?target=tsp-base" | python3 -m json.tool'] restartPolicy: OnFailure + metadata: + labels: + app: api-tsp-sync-cronjob + app.kubernetes.io/part-of: schulcloud-verbund + app.kubernetes.io/version: {{ SCHULCLOUD_SERVER_IMAGE_TAG }} + app.kubernetes.io/name: api-tsp-sync-cronjob + app.kubernetes.io/component: sync + app.kubernetes.io/managed-by: ansible + git.branch: {{ SCHULCLOUD_SERVER_BRANCH_NAME }} + git.repo: {{ SCHULCLOUD_SERVER_REPO_NAME }} diff --git a/ansible/roles/schulcloud-server-tspsync/templates/api-tsp-sync-deployment.yml.j2 b/ansible/roles/schulcloud-server-tspsync/templates/api-tsp-sync-deployment.yml.j2 index 54595985dd4..614db8cf1f9 100644 --- a/ansible/roles/schulcloud-server-tspsync/templates/api-tsp-sync-deployment.yml.j2 +++ b/ansible/roles/schulcloud-server-tspsync/templates/api-tsp-sync-deployment.yml.j2 @@ -5,6 +5,13 @@ metadata: namespace: {{ NAMESPACE }} labels: app: api-tsp-sync + app.kubernetes.io/part-of: schulcloud-verbund + app.kubernetes.io/version: {{ SCHULCLOUD_SERVER_IMAGE_TAG }} + app.kubernetes.io/name: api-tsp-sync + app.kubernetes.io/component: sync + app.kubernetes.io/managed-by: ansible + git.branch: {{ SCHULCLOUD_SERVER_BRANCH_NAME }} + git.repo: {{ SCHULCLOUD_SERVER_REPO_NAME }} spec: replicas: {{ API_TSP_REPLICAS|default("1", true) }} strategy: @@ -21,6 +28,13 @@ spec: metadata: labels: app: api-tsp-sync + app.kubernetes.io/part-of: schulcloud-verbund + app.kubernetes.io/version: {{ SCHULCLOUD_SERVER_IMAGE_TAG }} + app.kubernetes.io/name: api-tsp-sync + app.kubernetes.io/component: sync + app.kubernetes.io/managed-by: ansible + git.branch: {{ SCHULCLOUD_SERVER_BRANCH_NAME }} + git.repo: {{ SCHULCLOUD_SERVER_REPO_NAME }} spec: securityContext: runAsUser: 1000 diff --git a/ansible/roles/schulcloud-server-tspsync/templates/api-tsp-sync-school-cronjob.yml.j2 b/ansible/roles/schulcloud-server-tspsync/templates/api-tsp-sync-school-cronjob.yml.j2 index a92bd92560e..e47ae85fab1 100644 --- a/ansible/roles/schulcloud-server-tspsync/templates/api-tsp-sync-school-cronjob.yml.j2 +++ b/ansible/roles/schulcloud-server-tspsync/templates/api-tsp-sync-school-cronjob.yml.j2 @@ -4,15 +4,19 @@ metadata: namespace: {{ NAMESPACE }} labels: app: api-tsp-sync-cronjob + app.kubernetes.io/part-of: schulcloud-verbund + app.kubernetes.io/version: {{ SCHULCLOUD_SERVER_IMAGE_TAG }} + app.kubernetes.io/name: api-tsp-sync-cronjob + app.kubernetes.io/component: sync + app.kubernetes.io/managed-by: ansible + git.branch: {{ SCHULCLOUD_SERVER_BRANCH_NAME }} + git.repo: {{ SCHULCLOUD_SERVER_REPO_NAME }} name: api-tsp-sync-school-cronjob spec: schedule: "{{ SERVER_TSP_SYNC_SCHOOL_CRONJOB|default("39 3 * * *", true) }}" jobTemplate: spec: template: - metadata: - labels: - app: api-tsp-sync-cronjob spec: containers: - name: api-tsp-sync-school-cronjob @@ -23,3 +27,13 @@ spec: command: ['/bin/sh','-c'] args: ['curl -H "X-API-Key: $SYNC_API_KEY" "http://{{ API_TSP_SYNC_SVC|default("api-tsp-sync-svc", true) }}:3030/api/v1/sync?target=tsp-school" | python3 -m json.tool'] restartPolicy: OnFailure + metadata: + labels: + app: api-tsp-sync-cronjob + app.kubernetes.io/part-of: schulcloud-verbund + app.kubernetes.io/version: {{ SCHULCLOUD_SERVER_IMAGE_TAG }} + app.kubernetes.io/name: api-tsp-sync-cronjob + app.kubernetes.io/component: sync + app.kubernetes.io/managed-by: ansible + git.branch: {{ SCHULCLOUD_SERVER_BRANCH_NAME }} + git.repo: {{ SCHULCLOUD_SERVER_REPO_NAME }} From 598809adfd4e2877d6ed75631f2cd77ef74f2e4a Mon Sep 17 00:00:00 2001 From: Sergej Hoffmann <97111299+SevenWaysDP@users.noreply.github.com> Date: Fri, 17 Nov 2023 14:36:24 +0100 Subject: [PATCH 05/10] BC-5755 - Column board does not show any preview images (#4554) * BC-5755 - add coreModule for correct error handling --- Dockerfile | 1 + .../preview-generator-configmap.yml.j2 | 1 + .../preview-generator.consumer.ts | 6 +++-- .../preview-generator.service.ts | 6 ++--- .../preview-generator/preview.producer.ts | 7 ++--- .../src/infra/s3-client/s3-client.adapter.ts | 26 +++++++++---------- .../files-preview-amqp.module.ts | 8 ++++-- 7 files changed, 32 insertions(+), 23 deletions(-) diff --git a/Dockerfile b/Dockerfile index 8663c9b8d85..77ed4a1088b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,4 +24,5 @@ COPY scripts/ldapSync.sh /schulcloud-server/scripts/ RUN npm run build ENV NODE_ENV=production +ENV NO_COLOR="true" CMD npm run start diff --git a/ansible/roles/schulcloud-server-core/templates/preview-generator-configmap.yml.j2 b/ansible/roles/schulcloud-server-core/templates/preview-generator-configmap.yml.j2 index 457a5e47364..8d3a4b1f4c2 100644 --- a/ansible/roles/schulcloud-server-core/templates/preview-generator-configmap.yml.j2 +++ b/ansible/roles/schulcloud-server-core/templates/preview-generator-configmap.yml.j2 @@ -6,3 +6,4 @@ metadata: labels: app: preview-generator data: + NEST_LOG_LEVEL: "info" diff --git a/apps/server/src/infra/preview-generator/preview-generator.consumer.ts b/apps/server/src/infra/preview-generator/preview-generator.consumer.ts index d34fc8bc37c..7e1a5c523ae 100644 --- a/apps/server/src/infra/preview-generator/preview-generator.consumer.ts +++ b/apps/server/src/infra/preview-generator/preview-generator.consumer.ts @@ -1,7 +1,7 @@ import { RabbitPayload, RabbitRPC } from '@golevelup/nestjs-rabbitmq'; +import { FilesPreviewEvents, FilesPreviewExchange } from '@infra/rabbitmq'; import { Injectable } from '@nestjs/common'; import { Logger } from '@src/core/logger'; -import { FilesPreviewEvents, FilesPreviewExchange } from '@infra/rabbitmq'; import { PreviewFileOptions } from './interface'; import { PreviewActionsLoggable } from './loggable/preview-actions.loggable'; import { PreviewGeneratorService } from './preview-generator.service'; @@ -18,10 +18,12 @@ export class PreviewGeneratorConsumer { queue: FilesPreviewEvents.GENERATE_PREVIEW, }) public async generatePreview(@RabbitPayload() payload: PreviewFileOptions) { - this.logger.debug(new PreviewActionsLoggable('PreviewGeneratorConsumer.generatePreview', payload)); + this.logger.info(new PreviewActionsLoggable('PreviewGeneratorConsumer.generatePreview:start', payload)); const response = await this.previewGeneratorService.generatePreview(payload); + this.logger.info(new PreviewActionsLoggable('PreviewGeneratorConsumer.generatePreview:end', payload)); + return { message: response }; } } diff --git a/apps/server/src/infra/preview-generator/preview-generator.service.ts b/apps/server/src/infra/preview-generator/preview-generator.service.ts index 83dca461a2f..35b52d5e174 100644 --- a/apps/server/src/infra/preview-generator/preview-generator.service.ts +++ b/apps/server/src/infra/preview-generator/preview-generator.service.ts @@ -1,5 +1,5 @@ -import { Injectable } from '@nestjs/common'; import { GetFile, S3ClientAdapter } from '@infra/s3-client'; +import { Injectable } from '@nestjs/common'; import { Logger } from '@src/core/logger'; import { subClass } from 'gm'; import { PassThrough } from 'stream'; @@ -16,7 +16,7 @@ export class PreviewGeneratorService { } public async generatePreview(params: PreviewFileOptions): Promise { - this.logger.debug(new PreviewActionsLoggable('PreviewGeneratorService.generatePreview:start', params)); + this.logger.info(new PreviewActionsLoggable('PreviewGeneratorService.generatePreview:start', params)); const { originFilePath, previewFilePath, previewOptions } = params; const original = await this.downloadOriginFile(originFilePath); @@ -26,7 +26,7 @@ export class PreviewGeneratorService { await this.storageClient.create(previewFilePath, file); - this.logger.debug(new PreviewActionsLoggable('PreviewGeneratorService.generatePreview:end', params)); + this.logger.info(new PreviewActionsLoggable('PreviewGeneratorService.generatePreview:end', params)); return { previewFilePath, diff --git a/apps/server/src/infra/preview-generator/preview.producer.ts b/apps/server/src/infra/preview-generator/preview.producer.ts index 28cf6930830..8c84e93b295 100644 --- a/apps/server/src/infra/preview-generator/preview.producer.ts +++ b/apps/server/src/infra/preview-generator/preview.producer.ts @@ -1,7 +1,7 @@ import { AmqpConnection } from '@golevelup/nestjs-rabbitmq'; +import { FilesPreviewEvents, FilesPreviewExchange, RpcMessageProducer } from '@infra/rabbitmq'; import { Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; -import { FilesPreviewEvents, FilesPreviewExchange, RpcMessageProducer } from '@infra/rabbitmq'; import { Logger } from '@src/core/logger'; import { PreviewFileOptions, PreviewResponseMessage } from './interface'; import { PreviewModuleConfig } from './interface/preview-consumer-config'; @@ -21,10 +21,11 @@ export class PreviewProducer extends RpcMessageProducer { } async generate(payload: PreviewFileOptions): Promise { - this.logger.debug(new PreviewActionsLoggable('PreviewProducer.generate:started', payload)); + this.logger.info(new PreviewActionsLoggable('PreviewProducer.generate:started', payload)); + const response = await this.request(FilesPreviewEvents.GENERATE_PREVIEW, payload); - this.logger.debug(new PreviewActionsLoggable('PreviewProducer.generate:finished', payload)); + this.logger.info(new PreviewActionsLoggable('PreviewProducer.generate:finished', payload)); return response; } diff --git a/apps/server/src/infra/s3-client/s3-client.adapter.ts b/apps/server/src/infra/s3-client/s3-client.adapter.ts index 3c83fce7413..1ed693a4d52 100644 --- a/apps/server/src/infra/s3-client/s3-client.adapter.ts +++ b/apps/server/src/infra/s3-client/s3-client.adapter.ts @@ -33,7 +33,7 @@ export class S3ClientAdapter { // is public but only used internally public async createBucket() { try { - this.logger.log({ action: 'create bucket', params: { bucket: this.config.bucket } }); + this.logger.debug({ action: 'create bucket', params: { bucket: this.config.bucket } }); const req = new CreateBucketCommand({ Bucket: this.config.bucket }); await this.client.send(req); @@ -50,7 +50,7 @@ export class S3ClientAdapter { public async get(path: string, bytesRange?: string): Promise { try { - this.logger.log({ action: 'get', params: { path, bucket: this.config.bucket } }); + this.logger.debug({ action: 'get', params: { path, bucket: this.config.bucket } }); const req = new GetObjectCommand({ Bucket: this.config.bucket, @@ -73,8 +73,8 @@ export class S3ClientAdapter { } catch (err) { // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access if (err?.Code === 'NoSuchKey') { - this.logger.log(`could not find one of the files for deletion with id ${path}`); - throw new NotFoundException('NoSuchKey'); + this.logger.warn(`could not find one of the files for deletion with id ${path}`); + throw new NotFoundException('NoSuchKey', ErrorUtils.createHttpExceptionOptions(err)); } else { throw new InternalServerErrorException('S3ClientAdapter:get', ErrorUtils.createHttpExceptionOptions(err)); } @@ -83,7 +83,7 @@ export class S3ClientAdapter { public async create(path: string, file: File): Promise { try { - this.logger.log({ action: 'create', params: { path, bucket: this.config.bucket } }); + this.logger.debug({ action: 'create', params: { path, bucket: this.config.bucket } }); const req = { Body: file.data, @@ -126,7 +126,7 @@ export class S3ClientAdapter { } catch (err) { // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access if (err?.cause?.name === 'NoSuchKey') { - this.logger.log(`could not find one of the files for deletion with ids ${paths.join(',')}`); + this.logger.warn(`could not find one of the files for deletion with ids ${paths.join(',')}`); return []; } throw new InternalServerErrorException('S3ClientAdapter:delete', ErrorUtils.createHttpExceptionOptions(err)); @@ -135,7 +135,7 @@ export class S3ClientAdapter { public async restore(paths: string[]): Promise { try { - this.logger.log({ action: 'restore', params: { paths, bucket: this.config.bucket } }); + this.logger.debug({ action: 'restore', params: { paths, bucket: this.config.bucket } }); const copyPaths = paths.map((path) => { return { sourcePath: `${this.deletedFolderName}/${path}`, targetPath: path }; @@ -156,7 +156,7 @@ export class S3ClientAdapter { public async copy(paths: CopyFiles[]) { try { - this.logger.log({ action: 'copy', params: { paths, bucket: this.config.bucket } }); + this.logger.debug({ action: 'copy', params: { paths, bucket: this.config.bucket } }); const copyRequests = paths.map(async (path) => { const req = new CopyObjectCommand({ @@ -180,7 +180,7 @@ export class S3ClientAdapter { public async delete(paths: string[]) { try { - this.logger.log({ action: 'delete', params: { paths, bucket: this.config.bucket } }); + this.logger.debug({ action: 'delete', params: { paths, bucket: this.config.bucket } }); const pathObjects = paths.map((p) => { return { Key: p }; @@ -200,7 +200,7 @@ export class S3ClientAdapter { public async list(params: ListFiles): Promise { try { - this.logger.log({ action: 'list', params }); + this.logger.debug({ action: 'list', params }); const result = await this.listObjectKeysRecursive(params); @@ -242,7 +242,7 @@ export class S3ClientAdapter { public async head(path: string): Promise { try { - this.logger.log({ action: 'head', params: { path, bucket: this.config.bucket } }); + this.logger.debug({ action: 'head', params: { path, bucket: this.config.bucket } }); const req = new HeadObjectCommand({ Bucket: this.config.bucket, @@ -255,7 +255,7 @@ export class S3ClientAdapter { } catch (err) { // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access if (err.message && err.message === 'NoSuchKey') { - this.logger.log(`could not find the file for head with id ${path}`); + this.logger.warn(`could not find the file for head with id ${path}`); throw new NotFoundException(null, ErrorUtils.createHttpExceptionOptions(err, 'NoSuchKey')); } throw new InternalServerErrorException(null, ErrorUtils.createHttpExceptionOptions(err, 'S3ClientAdapter:head')); @@ -264,7 +264,7 @@ export class S3ClientAdapter { public async deleteDirectory(path: string) { try { - this.logger.log({ action: 'deleteDirectory', params: { path, bucket: this.config.bucket } }); + this.logger.debug({ action: 'deleteDirectory', params: { path, bucket: this.config.bucket } }); const req = new ListObjectsV2Command({ Bucket: this.config.bucket, diff --git a/apps/server/src/modules/files-storage/files-preview-amqp.module.ts b/apps/server/src/modules/files-storage/files-preview-amqp.module.ts index 78a1aec0129..c59dc720729 100644 --- a/apps/server/src/modules/files-storage/files-preview-amqp.module.ts +++ b/apps/server/src/modules/files-storage/files-preview-amqp.module.ts @@ -1,8 +1,12 @@ -import { Module } from '@nestjs/common'; import { PreviewGeneratorConsumerModule } from '@infra/preview-generator'; +import { Module } from '@nestjs/common'; +import { CoreModule } from '@src/core'; import { defaultConfig, s3Config } from './files-storage.config'; @Module({ - imports: [PreviewGeneratorConsumerModule.register({ storageConfig: s3Config, serverConfig: defaultConfig })], + imports: [ + PreviewGeneratorConsumerModule.register({ storageConfig: s3Config, serverConfig: defaultConfig }), + CoreModule, + ], }) export class PreviewGeneratorAMQPModule {} From 912f489cd3a7f00dcb157434ad5d4087c252d455 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marvin=20=C3=96hlerking?= <103562092+MarvinOehlerkingCap@users.noreply.github.com> Date: Mon, 20 Nov 2023 09:53:05 +0100 Subject: [PATCH 06/10] N21-1491 Provisioning of birthday from SchulConneX (#4564) --- .../src/modules/provisioning/dto/external-user.dto.ts | 3 +++ .../oidc/service/oidc-provisioning.service.spec.ts | 4 ++++ .../oidc/service/oidc-provisioning.service.ts | 2 ++ .../strategy/sanis/response/sanis-geburt-response.ts | 3 +++ .../strategy/sanis/response/sanis-name-response.ts | 4 ++-- .../strategy/sanis/response/sanis-person-response.ts | 11 +++++++---- .../strategy/sanis/sanis-response.mapper.spec.ts | 5 +++++ .../strategy/sanis/sanis-response.mapper.ts | 5 +++-- apps/server/src/shared/domain/domainobject/user.do.ts | 3 +++ apps/server/src/shared/domain/entity/user.entity.ts | 5 +++++ .../shared/repo/user/user-do.repo.integration.spec.ts | 8 ++++++-- apps/server/src/shared/repo/user/user-do.repo.ts | 4 +++- .../shared/repo/user/user.repo.integration.spec.ts | 5 ++++- 13 files changed, 50 insertions(+), 12 deletions(-) create mode 100644 apps/server/src/modules/provisioning/strategy/sanis/response/sanis-geburt-response.ts diff --git a/apps/server/src/modules/provisioning/dto/external-user.dto.ts b/apps/server/src/modules/provisioning/dto/external-user.dto.ts index eb973cf0fbe..d7291504b57 100644 --- a/apps/server/src/modules/provisioning/dto/external-user.dto.ts +++ b/apps/server/src/modules/provisioning/dto/external-user.dto.ts @@ -11,11 +11,14 @@ export class ExternalUserDto { roles?: RoleName[]; + birthday?: Date; + constructor(props: ExternalUserDto) { this.externalId = props.externalId; this.firstName = props.firstName; this.lastName = props.lastName; this.email = props.email; this.roles = props.roles; + this.birthday = props.birthday; } } diff --git a/apps/server/src/modules/provisioning/strategy/oidc/service/oidc-provisioning.service.spec.ts b/apps/server/src/modules/provisioning/strategy/oidc/service/oidc-provisioning.service.spec.ts index 5fcd5fc37a5..c313be9973d 100644 --- a/apps/server/src/modules/provisioning/strategy/oidc/service/oidc-provisioning.service.spec.ts +++ b/apps/server/src/modules/provisioning/strategy/oidc/service/oidc-provisioning.service.spec.ts @@ -518,6 +518,7 @@ describe('OidcProvisioningService', () => { const setupUser = () => { const systemId = 'systemId'; const schoolId = 'schoolId'; + const birthday = new Date('2023-11-17'); const existingUser: UserDO = userDoFactory.withRoles([{ id: 'existingRoleId', name: RoleName.USER }]).buildWithId( { firstName: 'existingFirstName', @@ -525,6 +526,7 @@ describe('OidcProvisioningService', () => { email: 'existingEmail', schoolId: 'existingSchoolId', externalId: 'externalUserId', + birthday: new Date('2023-11-16'), }, 'userId' ); @@ -535,6 +537,7 @@ describe('OidcProvisioningService', () => { email: 'email', schoolId, externalId: 'externalUserId', + birthday, }, 'userId' ); @@ -544,6 +547,7 @@ describe('OidcProvisioningService', () => { lastName: 'lastName', email: 'email', roles: [RoleName.USER], + birthday, }); const userRole: RoleDto = new RoleDto({ id: 'roleId', diff --git a/apps/server/src/modules/provisioning/strategy/oidc/service/oidc-provisioning.service.ts b/apps/server/src/modules/provisioning/strategy/oidc/service/oidc-provisioning.service.ts index 6d13537439b..52b0e7472e2 100644 --- a/apps/server/src/modules/provisioning/strategy/oidc/service/oidc-provisioning.service.ts +++ b/apps/server/src/modules/provisioning/strategy/oidc/service/oidc-provisioning.service.ts @@ -92,6 +92,7 @@ export class OidcProvisioningService { user.email = externalUser.email ?? existingUser.email; user.roles = roleRefs ?? existingUser.roles; user.schoolId = schoolId ?? existingUser.schoolId; + user.birthday = externalUser.birthday ?? existingUser.birthday; } else { createNewAccount = true; @@ -108,6 +109,7 @@ export class OidcProvisioningService { email: externalUser.email ?? '', roles: roleRefs ?? [], schoolId, + birthday: externalUser.birthday, }); } diff --git a/apps/server/src/modules/provisioning/strategy/sanis/response/sanis-geburt-response.ts b/apps/server/src/modules/provisioning/strategy/sanis/response/sanis-geburt-response.ts new file mode 100644 index 00000000000..618be9dd9e6 --- /dev/null +++ b/apps/server/src/modules/provisioning/strategy/sanis/response/sanis-geburt-response.ts @@ -0,0 +1,3 @@ +export interface SanisGeburtResponse { + datum?: string; +} diff --git a/apps/server/src/modules/provisioning/strategy/sanis/response/sanis-name-response.ts b/apps/server/src/modules/provisioning/strategy/sanis/response/sanis-name-response.ts index fad5c23319a..1da6a4ad8f9 100644 --- a/apps/server/src/modules/provisioning/strategy/sanis/response/sanis-name-response.ts +++ b/apps/server/src/modules/provisioning/strategy/sanis/response/sanis-name-response.ts @@ -1,5 +1,5 @@ export interface SanisNameResponse { - familienname: string; + familienname?: string; - vorname: string; + vorname?: string; } diff --git a/apps/server/src/modules/provisioning/strategy/sanis/response/sanis-person-response.ts b/apps/server/src/modules/provisioning/strategy/sanis/response/sanis-person-response.ts index e34d324b2e7..6b225b58032 100644 --- a/apps/server/src/modules/provisioning/strategy/sanis/response/sanis-person-response.ts +++ b/apps/server/src/modules/provisioning/strategy/sanis/response/sanis-person-response.ts @@ -1,11 +1,14 @@ +import { SanisGeburtResponse } from './sanis-geburt-response'; import { SanisNameResponse } from './sanis-name-response'; export interface SanisPersonResponse { - name: SanisNameResponse; + name?: SanisNameResponse; - geschlecht: string; + geburt?: SanisGeburtResponse; - lokalisierung: string; + geschlecht?: string; - vertrauensstufe: string; + lokalisierung?: string; + + vertrauensstufe?: string; } diff --git a/apps/server/src/modules/provisioning/strategy/sanis/sanis-response.mapper.spec.ts b/apps/server/src/modules/provisioning/strategy/sanis/sanis-response.mapper.spec.ts index 9829e3cb930..c560d4f7c3a 100644 --- a/apps/server/src/modules/provisioning/strategy/sanis/sanis-response.mapper.spec.ts +++ b/apps/server/src/modules/provisioning/strategy/sanis/sanis-response.mapper.spec.ts @@ -36,6 +36,7 @@ describe('SanisResponseMapper', () => { const setupSanisResponse = () => { const externalUserId = 'aef1f4fd-c323-466e-962b-a84354c0e713'; const externalSchoolId = 'df66c8e6-cfac-40f7-b35b-0da5d8ee680e'; + const sanisResponse: SanisResponse = { pid: externalUserId, person: { @@ -43,6 +44,9 @@ describe('SanisResponseMapper', () => { vorname: 'firstName', familienname: 'lastName', }, + geburt: { + datum: '2023-11-17', + }, geschlecht: 'x', lokalisierung: 'de-de', vertrauensstufe: '', @@ -124,6 +128,7 @@ describe('SanisResponseMapper', () => { firstName: 'firstName', lastName: 'lastName', roles: [RoleName.STUDENT], + birthday: new Date('2023-11-17'), }); }); }); diff --git a/apps/server/src/modules/provisioning/strategy/sanis/sanis-response.mapper.ts b/apps/server/src/modules/provisioning/strategy/sanis/sanis-response.mapper.ts index ee912dd67b5..3ca0d06806e 100644 --- a/apps/server/src/modules/provisioning/strategy/sanis/sanis-response.mapper.ts +++ b/apps/server/src/modules/provisioning/strategy/sanis/sanis-response.mapper.ts @@ -53,10 +53,11 @@ export class SanisResponseMapper { mapToExternalUserDto(source: SanisResponse): ExternalUserDto { const mapped = new ExternalUserDto({ - firstName: source.person.name.vorname, - lastName: source.person.name.familienname, + firstName: source.person.name?.vorname, + lastName: source.person.name?.familienname, roles: [this.mapSanisRoleToRoleName(source)], externalId: source.pid, + birthday: source.person.geburt?.datum ? new Date(source.person.geburt?.datum) : undefined, }); return mapped; diff --git a/apps/server/src/shared/domain/domainobject/user.do.ts b/apps/server/src/shared/domain/domainobject/user.do.ts index fa8e4f77b25..5c0deab549b 100644 --- a/apps/server/src/shared/domain/domainobject/user.do.ts +++ b/apps/server/src/shared/domain/domainobject/user.do.ts @@ -42,6 +42,8 @@ export class UserDO extends BaseDO { previousExternalId?: string; + birthday?: Date; + constructor(domainObject: UserDO) { super(domainObject.id); @@ -64,5 +66,6 @@ export class UserDO extends BaseDO { this.lastLoginSystemChange = domainObject.lastLoginSystemChange; this.outdatedSince = domainObject.outdatedSince; this.previousExternalId = domainObject.previousExternalId; + this.birthday = domainObject.birthday; } } diff --git a/apps/server/src/shared/domain/entity/user.entity.ts b/apps/server/src/shared/domain/entity/user.entity.ts index 2d1918e1d61..3a648dacf2b 100644 --- a/apps/server/src/shared/domain/entity/user.entity.ts +++ b/apps/server/src/shared/domain/entity/user.entity.ts @@ -26,6 +26,7 @@ export interface IUserProperties { lastLoginSystemChange?: Date; outdatedSince?: Date; previousExternalId?: string; + birthday?: Date; } @Entity({ tableName: 'users' }) @@ -96,6 +97,9 @@ export class User extends BaseEntityWithTimestamps implements IEntityWithSchool @Property({ nullable: true }) outdatedSince?: Date; + @Property({ nullable: true }) + birthday?: Date; + constructor(props: IUserProperties) { super(); this.firstName = props.firstName; @@ -112,6 +116,7 @@ export class User extends BaseEntityWithTimestamps implements IEntityWithSchool this.lastLoginSystemChange = props.lastLoginSystemChange; this.outdatedSince = props.outdatedSince; this.previousExternalId = props.previousExternalId; + this.birthday = props.birthday; } public resolvePermissions(): string[] { diff --git a/apps/server/src/shared/repo/user/user-do.repo.integration.spec.ts b/apps/server/src/shared/repo/user/user-do.repo.integration.spec.ts index 43d46a95383..327c1728ccb 100644 --- a/apps/server/src/shared/repo/user/user-do.repo.integration.spec.ts +++ b/apps/server/src/shared/repo/user/user-do.repo.integration.spec.ts @@ -1,6 +1,8 @@ import { createMock } from '@golevelup/ts-jest'; +import { MongoMemoryDatabaseModule } from '@infra/database'; import { FindOptions, NotFoundError, QueryOrderMap } from '@mikro-orm/core'; import { EntityManager, ObjectId } from '@mikro-orm/mongodb'; +import { UserQuery } from '@modules/user/service/user-query.type'; import { Test, TestingModule } from '@nestjs/testing'; import { EntityNotFoundError } from '@shared/common'; import { @@ -16,7 +18,6 @@ import { } from '@shared/domain'; import { Page } from '@shared/domain/domainobject/page'; import { UserDO } from '@shared/domain/domainobject/user.do'; -import { MongoMemoryDatabaseModule } from '@infra/database'; import { UserDORepo } from '@shared/repo/user/user-do.repo'; import { cleanupCollections, @@ -27,7 +28,6 @@ import { userFactory, } from '@shared/testing'; import { LegacyLogger } from '@src/core/logger'; -import { UserQuery } from '@modules/user/service/user-query.type'; describe('UserRepo', () => { let module: TestingModule; @@ -234,6 +234,7 @@ describe('UserRepo', () => { language: LanguageType.DE, forcePasswordChange: false, preferences: { firstLogin: true }, + birthday: new Date(), }, id.toHexString() ); @@ -276,6 +277,7 @@ describe('UserRepo', () => { outdatedSince: testEntity.outdatedSince, lastLoginSystemChange: testEntity.lastLoginSystemChange, previousExternalId: testEntity.previousExternalId, + birthday: testEntity.birthday, }) ); }); @@ -299,6 +301,7 @@ describe('UserRepo', () => { outdatedSince: new Date(), lastLoginSystemChange: new Date(), previousExternalId: 'someId', + birthday: new Date(), }, 'testId' ); @@ -321,6 +324,7 @@ describe('UserRepo', () => { outdatedSince: testDO.outdatedSince, lastLoginSystemChange: testDO.lastLoginSystemChange, previousExternalId: testDO.previousExternalId, + birthday: testDO.birthday, }); }); }); 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 e9eae128a1f..7d536596b77 100644 --- a/apps/server/src/shared/repo/user/user-do.repo.ts +++ b/apps/server/src/shared/repo/user/user-do.repo.ts @@ -1,4 +1,5 @@ import { EntityName, FilterQuery, QueryOrderMap } from '@mikro-orm/core'; +import { UserQuery } from '@modules/user/service/user-query.type'; import { Injectable } from '@nestjs/common'; import { EntityNotFoundError } from '@shared/common'; import { @@ -17,7 +18,6 @@ import { RoleReference } from '@shared/domain/domainobject'; import { Page } from '@shared/domain/domainobject/page'; import { UserDO } from '@shared/domain/domainobject/user.do'; import { BaseDORepo, Scope } from '@shared/repo'; -import { UserQuery } from '@modules/user/service/user-query.type'; import { UserScope } from './user.scope'; @Injectable() @@ -109,6 +109,7 @@ export class UserDORepo extends BaseDORepo { lastLoginSystemChange: entity.lastLoginSystemChange, outdatedSince: entity.outdatedSince, previousExternalId: entity.previousExternalId, + birthday: entity.birthday, }); if (entity.roles.isInitialized()) { @@ -135,6 +136,7 @@ export class UserDORepo extends BaseDORepo { lastLoginSystemChange: entityDO.lastLoginSystemChange, outdatedSince: entityDO.outdatedSince, previousExternalId: entityDO.previousExternalId, + birthday: entityDO.birthday, }; } diff --git a/apps/server/src/shared/repo/user/user.repo.integration.spec.ts b/apps/server/src/shared/repo/user/user.repo.integration.spec.ts index ea10e6e5b3e..3d44d0edfb8 100644 --- a/apps/server/src/shared/repo/user/user.repo.integration.spec.ts +++ b/apps/server/src/shared/repo/user/user.repo.integration.spec.ts @@ -1,8 +1,8 @@ +import { MongoMemoryDatabaseModule } from '@infra/database'; import { NotFoundError } from '@mikro-orm/core'; import { EntityManager, ObjectId } from '@mikro-orm/mongodb'; import { Test, TestingModule } from '@nestjs/testing'; import { MatchCreator, SortOrder, SystemEntity, User } from '@shared/domain'; -import { MongoMemoryDatabaseModule } from '@infra/database'; import { cleanupCollections, importUserFactory, roleFactory, schoolFactory, userFactory } from '@shared/testing'; import { systemFactory } from '@shared/testing/factory/system.factory'; import { UserRepo } from './user.repo'; @@ -70,6 +70,7 @@ describe('user repo', () => { 'lastLoginSystemChange', 'outdatedSince', 'previousExternalId', + 'birthday', ].sort() ); }); @@ -133,6 +134,7 @@ describe('user repo', () => { await em.persistAndFlush([userA, userB]); em.clear(); }); + it('should return right keys', async () => { const result = await repo.findByExternalIdOrFail(userA.externalId as string, sys.id); expect(Object.keys(result).sort()).toEqual( @@ -158,6 +160,7 @@ describe('user repo', () => { 'lastLoginSystemChange', 'outdatedSince', 'previousExternalId', + 'birthday', ].sort() ); }); From d7ae60f0ac171115b4cf1731daf847864a9b079c Mon Sep 17 00:00:00 2001 From: Thomas Feldtkeller Date: Mon, 20 Nov 2023 13:07:41 +0100 Subject: [PATCH 07/10] BC-5803 lesson refactorings (#4562) * isolate lesson repo * fix missing module imports * move lesson repo in lesson module --- .../authorization-reference.module.ts | 19 +++++--- .../authorization/authorization.module.ts | 10 ++--- .../domain/service/reference.loader.spec.ts | 20 ++++----- .../domain/service/reference.loader.ts | 16 +++---- .../server/src/modules/authorization/index.ts | 14 +++--- .../src/modules/files-storage-client/index.ts | 2 +- apps/server/src/modules/learnroom/index.ts | 3 +- .../modules/learnroom/learnroom-api.module.ts | 5 +-- .../src/modules/learnroom/learnroom.module.ts | 7 ++- .../learnroom/service/rooms.service.spec.ts | 19 ++++---- .../learnroom/service/rooms.service.ts | 11 ++--- .../modules/learnroom/uc/course.uc.spec.ts | 6 +-- .../learnroom/uc/lesson-copy.uc.spec.ts | 36 +++++++-------- .../modules/learnroom/uc/lesson-copy.uc.ts | 15 +++---- .../src/modules/learnroom/uc/rooms.uc.spec.ts | 10 +---- apps/server/src/modules/lesson/index.ts | 2 + .../src/modules/lesson/lesson.module.ts | 6 +-- .../lesson/repository}/index.ts | 0 .../lesson/repository}/lesson-scope.ts | 2 +- .../lesson.repo.integration.spec.ts | 0 .../lesson/repository}/lesson.repo.ts | 4 +- .../service/lesson-copy.service.spec.ts | 8 ++-- .../lesson/service/lesson-copy.service.ts | 9 ++-- .../lesson/service/lesson.service.spec.ts | 24 +++++++--- .../modules/lesson/service/lesson.service.ts | 11 ++--- .../modules/sharing/uc/share-token.uc.spec.ts | 25 +++++------ apps/server/src/modules/task/index.ts | 3 ++ .../src/modules/task/task-api.module.ts | 9 ++-- apps/server/src/modules/task/task.module.ts | 4 +- .../src/modules/task/uc/task-copy.uc.spec.ts | 27 +++++------ .../src/modules/task/uc/task-copy.uc.ts | 11 ++--- .../src/modules/task/uc/task.uc.spec.ts | 45 ++++++++++--------- apps/server/src/modules/task/uc/task.uc.ts | 11 ++--- apps/server/src/modules/tool/index.ts | 5 ++- apps/server/src/shared/repo/index.ts | 1 - 35 files changed, 202 insertions(+), 198 deletions(-) rename apps/server/src/{shared/repo/lesson => modules/lesson/repository}/index.ts (100%) rename apps/server/src/{shared/repo/lesson => modules/lesson/repository}/lesson-scope.ts (90%) rename apps/server/src/{shared/repo/lesson => modules/lesson/repository}/lesson.repo.integration.spec.ts (100%) rename apps/server/src/{shared/repo/lesson => modules/lesson/repository}/lesson.repo.ts (97%) diff --git a/apps/server/src/modules/authorization/authorization-reference.module.ts b/apps/server/src/modules/authorization/authorization-reference.module.ts index e253587af7b..a115d0433c3 100644 --- a/apps/server/src/modules/authorization/authorization-reference.module.ts +++ b/apps/server/src/modules/authorization/authorization-reference.module.ts @@ -1,20 +1,20 @@ +import { BoardModule } from '@modules/board'; +import { ToolModule } from '@modules/tool'; import { forwardRef, Module } from '@nestjs/common'; import { CourseGroupRepo, CourseRepo, - LessonRepo, - SchoolExternalToolRepo, LegacySchoolRepo, + SchoolExternalToolRepo, SubmissionRepo, TaskRepo, TeamsRepo, UserRepo, } from '@shared/repo'; -import { ToolModule } from '@modules/tool'; import { LoggerModule } from '@src/core/logger'; -import { BoardModule } from '@modules/board'; -import { ReferenceLoader, AuthorizationReferenceService, AuthorizationHelper } from './domain'; +import { LessonModule } from '../lesson'; import { AuthorizationModule } from './authorization.module'; +import { AuthorizationHelper, AuthorizationReferenceService, ReferenceLoader } from './domain'; /** * This module is part of an intermediate state. In the future it should be replaced by an AuthorizationApiModule. @@ -23,7 +23,13 @@ import { AuthorizationModule } from './authorization.module'; */ @Module({ // TODO: remove forwardRef to TooModule N21-1055 - imports: [AuthorizationModule, forwardRef(() => ToolModule), forwardRef(() => BoardModule), LoggerModule], + imports: [ + AuthorizationModule, + LessonModule, + forwardRef(() => ToolModule), + forwardRef(() => BoardModule), + LoggerModule, + ], providers: [ AuthorizationHelper, ReferenceLoader, @@ -32,7 +38,6 @@ import { AuthorizationModule } from './authorization.module'; CourseGroupRepo, TaskRepo, LegacySchoolRepo, - LessonRepo, TeamsRepo, SubmissionRepo, SchoolExternalToolRepo, diff --git a/apps/server/src/modules/authorization/authorization.module.ts b/apps/server/src/modules/authorization/authorization.module.ts index d01cd9363f4..f734a72ed8a 100644 --- a/apps/server/src/modules/authorization/authorization.module.ts +++ b/apps/server/src/modules/authorization/authorization.module.ts @@ -1,23 +1,23 @@ +import { FeathersModule } from '@infra/feathers'; import { Module } from '@nestjs/common'; import { UserRepo } from '@shared/repo'; import { LoggerModule } from '@src/core/logger'; -import { FeathersModule } from '@infra/feathers'; +import { AuthorizationHelper, AuthorizationService, RuleManager } from './domain'; import { BoardDoRule, ContextExternalToolRule, CourseGroupRule, CourseRule, + GroupRule, + LegacySchoolRule, LessonRule, SchoolExternalToolRule, SubmissionRule, TaskRule, TeamRule, - UserRule, UserLoginMigrationRule, - LegacySchoolRule, - GroupRule, + UserRule, } from './domain/rules'; -import { AuthorizationHelper, AuthorizationService, RuleManager } from './domain'; import { FeathersAuthorizationService, FeathersAuthProvider } from './feathers'; @Module({ diff --git a/apps/server/src/modules/authorization/domain/service/reference.loader.spec.ts b/apps/server/src/modules/authorization/domain/service/reference.loader.spec.ts index e2e77212cab..11f553423b6 100644 --- a/apps/server/src/modules/authorization/domain/service/reference.loader.spec.ts +++ b/apps/server/src/modules/authorization/domain/service/reference.loader.spec.ts @@ -1,24 +1,24 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { ObjectId } from '@mikro-orm/mongodb'; +import { BoardDoAuthorizableService } from '@modules/board'; +import { ContextExternalToolAuthorizableService } from '@modules/tool'; import { NotImplementedException } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { EntityId } from '@shared/domain'; import { CourseGroupRepo, CourseRepo, - LessonRepo, - SchoolExternalToolRepo, LegacySchoolRepo, + SchoolExternalToolRepo, SubmissionRepo, TaskRepo, TeamsRepo, UserRepo, } from '@shared/repo'; import { setupEntities, userFactory } from '@shared/testing'; -import { BoardDoAuthorizableService } from '@modules/board'; -import { ContextExternalToolAuthorizableService } from '@modules/tool/context-external-tool/service/context-external-tool-authorizable.service'; -import { ReferenceLoader } from './reference.loader'; +import { LessonService } from '@modules/lesson'; import { AuthorizableReferenceType } from '../type'; +import { ReferenceLoader } from './reference.loader'; describe('reference.loader', () => { let service: ReferenceLoader; @@ -27,7 +27,7 @@ describe('reference.loader', () => { let courseGroupRepo: DeepMocked; let taskRepo: DeepMocked; let schoolRepo: DeepMocked; - let lessonRepo: DeepMocked; + let lessonService: DeepMocked; let teamsRepo: DeepMocked; let submissionRepo: DeepMocked; let schoolExternalToolRepo: DeepMocked; @@ -62,8 +62,8 @@ describe('reference.loader', () => { useValue: createMock(), }, { - provide: LessonRepo, - useValue: createMock(), + provide: LessonService, + useValue: createMock(), }, { provide: TeamsRepo, @@ -94,7 +94,7 @@ describe('reference.loader', () => { courseGroupRepo = await module.get(CourseGroupRepo); taskRepo = await module.get(TaskRepo); schoolRepo = await module.get(LegacySchoolRepo); - lessonRepo = await module.get(LessonRepo); + lessonService = await module.get(LessonService); teamsRepo = await module.get(TeamsRepo); submissionRepo = await module.get(SubmissionRepo); schoolExternalToolRepo = await module.get(SchoolExternalToolRepo); @@ -144,7 +144,7 @@ describe('reference.loader', () => { it('should call lessonRepo.findById', async () => { await service.loadAuthorizableObject(AuthorizableReferenceType.Lesson, entityId); - expect(lessonRepo.findById).toBeCalledWith(entityId); + expect(lessonService.findById).toBeCalledWith(entityId); }); it('should call teamsRepo.findById', async () => { diff --git a/apps/server/src/modules/authorization/domain/service/reference.loader.ts b/apps/server/src/modules/authorization/domain/service/reference.loader.ts index d584561be9e..c0ea22212a0 100644 --- a/apps/server/src/modules/authorization/domain/service/reference.loader.ts +++ b/apps/server/src/modules/authorization/domain/service/reference.loader.ts @@ -1,19 +1,19 @@ +import { BoardDoAuthorizableService } from '@modules/board'; +import { ContextExternalToolAuthorizableService } from '@modules/tool'; import { Injectable, NotImplementedException } from '@nestjs/common'; import { BaseDO, EntityId } from '@shared/domain'; import { AuthorizableObject } from '@shared/domain/domain-object'; import { CourseGroupRepo, CourseRepo, - LessonRepo, - SchoolExternalToolRepo, LegacySchoolRepo, + SchoolExternalToolRepo, SubmissionRepo, TaskRepo, TeamsRepo, UserRepo, } from '@shared/repo'; -import { BoardDoAuthorizableService } from '@modules/board'; -import { ContextExternalToolAuthorizableService } from '@modules/tool/context-external-tool/service'; +import { LessonService } from '@modules/lesson'; import { AuthorizableReferenceType } from '../type'; type RepoType = @@ -21,13 +21,13 @@ type RepoType = | CourseRepo | UserRepo | LegacySchoolRepo - | LessonRepo | TeamsRepo | CourseGroupRepo | SubmissionRepo | SchoolExternalToolRepo | BoardDoAuthorizableService - | ContextExternalToolAuthorizableService; + | ContextExternalToolAuthorizableService + | LessonService; interface IRepoLoader { repo: RepoType; @@ -44,7 +44,7 @@ export class ReferenceLoader { private readonly courseGroupRepo: CourseGroupRepo, private readonly taskRepo: TaskRepo, private readonly schoolRepo: LegacySchoolRepo, - private readonly lessonRepo: LessonRepo, + private readonly lessonService: LessonService, private readonly teamsRepo: TeamsRepo, private readonly submissionRepo: SubmissionRepo, private readonly schoolExternalToolRepo: SchoolExternalToolRepo, @@ -56,7 +56,7 @@ export class ReferenceLoader { this.repos.set(AuthorizableReferenceType.CourseGroup, { repo: this.courseGroupRepo }); this.repos.set(AuthorizableReferenceType.User, { repo: this.userRepo }); this.repos.set(AuthorizableReferenceType.School, { repo: this.schoolRepo }); - this.repos.set(AuthorizableReferenceType.Lesson, { repo: this.lessonRepo }); + this.repos.set(AuthorizableReferenceType.Lesson, { repo: this.lessonService }); this.repos.set(AuthorizableReferenceType.Team, { repo: this.teamsRepo, populate: true }); this.repos.set(AuthorizableReferenceType.Submission, { repo: this.submissionRepo }); this.repos.set(AuthorizableReferenceType.SchoolExternalToolEntity, { repo: this.schoolExternalToolRepo }); diff --git a/apps/server/src/modules/authorization/index.ts b/apps/server/src/modules/authorization/index.ts index e129df2cd11..13ae209f13d 100644 --- a/apps/server/src/modules/authorization/index.ts +++ b/apps/server/src/modules/authorization/index.ts @@ -1,15 +1,17 @@ export { AuthorizationModule } from './authorization.module'; export { - AuthorizationService, - AuthorizationHelper, - AuthorizationContextBuilder, - ForbiddenLoggableException, - Rule, - AuthorizationContext, // Action should not be exported, but hard to solve for now. The AuthorizationContextBuilder is the prefared way Action, + AuthorizableReferenceType, + AuthorizationContext, + AuthorizationContextBuilder, + AuthorizationHelper, AuthorizationLoaderService, AuthorizationLoaderServiceGeneric, + AuthorizationReferenceService, + AuthorizationService, + ForbiddenLoggableException, + Rule, } from './domain'; // Should not used anymore export { FeathersAuthorizationService } from './feathers'; diff --git a/apps/server/src/modules/files-storage-client/index.ts b/apps/server/src/modules/files-storage-client/index.ts index 7b242946d8f..19248d129de 100644 --- a/apps/server/src/modules/files-storage-client/index.ts +++ b/apps/server/src/modules/files-storage-client/index.ts @@ -2,5 +2,5 @@ export { FileDto } from './dto'; export * from './files-storage-client.module'; export { IFilesStorageClientConfig } from './interfaces'; export { FileParamBuilder } from './mapper/files-storage-param.builder'; -export { CopyFilesService } from './service/copy-files.service'; +export * from './service/copy-files.service'; export { FilesStorageClientAdapterService } from './service/files-storage-client.service'; diff --git a/apps/server/src/modules/learnroom/index.ts b/apps/server/src/modules/learnroom/index.ts index e4d907784d5..9fe9c100886 100644 --- a/apps/server/src/modules/learnroom/index.ts +++ b/apps/server/src/modules/learnroom/index.ts @@ -1,3 +1,2 @@ export * from './learnroom.module'; -export * from './service/course-copy.service'; -export { CourseService } from './service'; +export { CommonCartridgeExportService, CourseCopyService, CourseService, RoomsService } from './service'; diff --git a/apps/server/src/modules/learnroom/learnroom-api.module.ts b/apps/server/src/modules/learnroom/learnroom-api.module.ts index 5cfaada65b8..a2a407daf21 100644 --- a/apps/server/src/modules/learnroom/learnroom-api.module.ts +++ b/apps/server/src/modules/learnroom/learnroom-api.module.ts @@ -1,9 +1,9 @@ -import { Module } from '@nestjs/common'; -import { BoardRepo, CourseRepo, DashboardModelMapper, DashboardRepo, LessonRepo, UserRepo } from '@shared/repo'; import { AuthorizationModule } from '@modules/authorization'; import { AuthorizationReferenceModule } from '@modules/authorization/authorization-reference.module'; import { CopyHelperModule } from '@modules/copy-helper'; import { LessonModule } from '@modules/lesson'; +import { Module } from '@nestjs/common'; +import { BoardRepo, CourseRepo, DashboardModelMapper, DashboardRepo, UserRepo } from '@shared/repo'; import { CourseController } from './controller/course.controller'; import { DashboardController } from './controller/dashboard.controller'; import { RoomsController } from './controller/rooms.controller'; @@ -42,7 +42,6 @@ import { CourseRepo, UserRepo, BoardRepo, - LessonRepo, ], }) export class LearnroomApiModule {} diff --git a/apps/server/src/modules/learnroom/learnroom.module.ts b/apps/server/src/modules/learnroom/learnroom.module.ts index c84310ba05e..02071369766 100644 --- a/apps/server/src/modules/learnroom/learnroom.module.ts +++ b/apps/server/src/modules/learnroom/learnroom.module.ts @@ -1,10 +1,10 @@ -import { Module } from '@nestjs/common'; -import { BoardRepo, CourseRepo, DashboardModelMapper, DashboardRepo, LessonRepo, UserRepo } from '@shared/repo'; -import { LoggerModule } from '@src/core/logger'; import { BoardModule } from '@modules/board'; import { CopyHelperModule } from '@modules/copy-helper'; import { LessonModule } from '@modules/lesson'; import { TaskModule } from '@modules/task'; +import { Module } from '@nestjs/common'; +import { BoardRepo, CourseRepo, DashboardModelMapper, DashboardRepo, UserRepo } from '@shared/repo'; +import { LoggerModule } from '@src/core/logger'; import { BoardCopyService, ColumnBoardTargetService, @@ -23,7 +23,6 @@ import { }, DashboardModelMapper, CourseRepo, - LessonRepo, BoardRepo, UserRepo, BoardCopyService, diff --git a/apps/server/src/modules/learnroom/service/rooms.service.spec.ts b/apps/server/src/modules/learnroom/service/rooms.service.spec.ts index 2358e2b2067..ef161ce179a 100644 --- a/apps/server/src/modules/learnroom/service/rooms.service.spec.ts +++ b/apps/server/src/modules/learnroom/service/rooms.service.spec.ts @@ -2,19 +2,20 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { Configuration } from '@hpi-schul-cloud/commons/lib'; import { IConfig } from '@hpi-schul-cloud/commons/lib/interfaces/IConfig'; import { ObjectId } from '@mikro-orm/mongodb'; +import { CardService, ColumnBoardService, ColumnService, ContentElementService } from '@modules/board'; +import { LessonService } from '@modules/lesson'; +import { TaskService } from '@modules/task'; import { Test, TestingModule } from '@nestjs/testing'; import { BoardExternalReference, BoardExternalReferenceType, EntityId } from '@shared/domain'; -import { BoardRepo, LessonRepo } from '@shared/repo'; +import { BoardRepo } from '@shared/repo'; import { boardFactory, courseFactory, lessonFactory, setupEntities, taskFactory, userFactory } from '@shared/testing'; -import { CardService, ColumnBoardService, ColumnService, ContentElementService } from '@modules/board'; -import { TaskService } from '@modules/task/service'; import { ColumnBoardTargetService } from './column-board-target.service'; import { RoomsService } from './rooms.service'; describe('rooms service', () => { let module: TestingModule; let roomsService: RoomsService; - let lessonRepo: DeepMocked; + let lessonService: DeepMocked; let taskService: DeepMocked; let boardRepo: DeepMocked; let columnBoardService: DeepMocked; @@ -32,8 +33,8 @@ describe('rooms service', () => { providers: [ RoomsService, { - provide: LessonRepo, - useValue: createMock(), + provide: LessonService, + useValue: createMock(), }, { provide: TaskService, @@ -66,7 +67,7 @@ describe('rooms service', () => { ], }).compile(); roomsService = module.get(RoomsService); - lessonRepo = module.get(LessonRepo); + lessonService = module.get(LessonService); taskService = module.get(TaskService); boardRepo = module.get(BoardRepo); columnBoardService = module.get(ColumnBoardService); @@ -90,7 +91,7 @@ describe('rooms service', () => { board.syncBoardElementReferences([...tasks, ...lessons]); const tasksSpy = taskService.findBySingleParent.mockResolvedValue([tasks, 3]); - const lessonsSpy = lessonRepo.findAllByCourseIds.mockResolvedValue([lessons, 3]); + const lessonsSpy = lessonService.findByCourseIds.mockResolvedValue([lessons, 3]); const syncBoardElementReferencesSpy = jest.spyOn(board, 'syncBoardElementReferences'); const saveSpy = boardRepo.save.mockResolvedValue(); @@ -134,7 +135,7 @@ describe('rooms service', () => { describe('for column boards', () => { const setup = () => { - lessonRepo.findAllByCourseIds.mockResolvedValueOnce([[], 0]); + lessonService.findByCourseIds.mockResolvedValueOnce([[], 0]); taskService.findBySingleParent.mockResolvedValueOnce([[], 0]); const user = userFactory.buildWithId(); diff --git a/apps/server/src/modules/learnroom/service/rooms.service.ts b/apps/server/src/modules/learnroom/service/rooms.service.ts index cc8b95e09b0..19ddfd57be8 100644 --- a/apps/server/src/modules/learnroom/service/rooms.service.ts +++ b/apps/server/src/modules/learnroom/service/rooms.service.ts @@ -1,23 +1,24 @@ import { Configuration } from '@hpi-schul-cloud/commons/lib'; +import { ColumnBoardService } from '@modules/board'; +import { LessonService } from '@modules/lesson'; +import { TaskService } from '@modules/task'; import { Injectable } from '@nestjs/common'; import { Board, BoardExternalReferenceType, ColumnBoardTarget, EntityId } from '@shared/domain'; -import { BoardRepo, LessonRepo } from '@shared/repo'; -import { ColumnBoardService } from '@modules/board'; -import { TaskService } from '@modules/task/service'; +import { BoardRepo } from '@shared/repo'; import { ColumnBoardTargetService } from './column-board-target.service'; @Injectable() export class RoomsService { constructor( private readonly taskService: TaskService, - private readonly lessonRepo: LessonRepo, + private readonly lessonService: LessonService, private readonly boardRepo: BoardRepo, private readonly columnBoardService: ColumnBoardService, private readonly columnBoardTargetService: ColumnBoardTargetService ) {} async updateBoard(board: Board, roomId: EntityId, userId: EntityId): Promise { - const [courseLessons] = await this.lessonRepo.findAllByCourseIds([roomId]); + const [courseLessons] = await this.lessonService.findByCourseIds([roomId]); const [courseTasks] = await this.taskService.findBySingleParent(userId, roomId); const courseColumnBoardTargets = await this.handleColumnBoardIntegration(roomId); diff --git a/apps/server/src/modules/learnroom/uc/course.uc.spec.ts b/apps/server/src/modules/learnroom/uc/course.uc.spec.ts index 750c9b1a287..3ab86b82c65 100644 --- a/apps/server/src/modules/learnroom/uc/course.uc.spec.ts +++ b/apps/server/src/modules/learnroom/uc/course.uc.spec.ts @@ -1,7 +1,7 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { Test, TestingModule } from '@nestjs/testing'; import { SortOrder } from '@shared/domain'; -import { CourseRepo, LessonRepo } from '@shared/repo'; +import { CourseRepo } from '@shared/repo'; import { courseFactory, setupEntities } from '@shared/testing'; import { CourseUc } from './course.uc'; @@ -19,10 +19,6 @@ describe('CourseUc', () => { provide: CourseRepo, useValue: createMock(), }, - { - provide: LessonRepo, - useValue: createMock(), - }, ], }).compile(); diff --git a/apps/server/src/modules/learnroom/uc/lesson-copy.uc.spec.ts b/apps/server/src/modules/learnroom/uc/lesson-copy.uc.spec.ts index 49c7c53435c..6f148abc94f 100644 --- a/apps/server/src/modules/learnroom/uc/lesson-copy.uc.spec.ts +++ b/apps/server/src/modules/learnroom/uc/lesson-copy.uc.spec.ts @@ -1,21 +1,21 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { Configuration } from '@hpi-schul-cloud/commons'; import { ObjectId } from '@mikro-orm/mongodb'; +import { AuthorizationContextBuilder, AuthorizationService } from '@modules/authorization'; +import { CopyElementType, CopyHelperService, CopyStatusEnum } from '@modules/copy-helper'; +import { LessonCopyService, LessonService } from '@modules/lesson'; import { ForbiddenException, InternalServerErrorException } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { Permission } from '@shared/domain'; -import { CourseRepo, LessonRepo, UserRepo } from '@shared/repo'; +import { CourseRepo, UserRepo } from '@shared/repo'; import { courseFactory, lessonFactory, setupEntities, userFactory } from '@shared/testing'; -import { AuthorizationContextBuilder, AuthorizationService } from '@modules/authorization'; -import { CopyElementType, CopyHelperService, CopyStatusEnum } from '@modules/copy-helper'; -import { EtherpadService, LessonCopyService } from '@modules/lesson/service'; import { LessonCopyUC } from './lesson-copy.uc'; describe('lesson copy uc', () => { let module: TestingModule; let uc: LessonCopyUC; let userRepo: DeepMocked; - let lessonRepo: DeepMocked; + let lessonService: DeepMocked; let courseRepo: DeepMocked; let authorisation: DeepMocked; let lessonCopyService: DeepMocked; @@ -35,8 +35,8 @@ describe('lesson copy uc', () => { useValue: createMock(), }, { - provide: LessonRepo, - useValue: createMock(), + provide: LessonService, + useValue: createMock(), }, { provide: CourseRepo, @@ -54,16 +54,12 @@ describe('lesson copy uc', () => { provide: CopyHelperService, useValue: createMock(), }, - { - provide: EtherpadService, - useValue: createMock(), - }, ], }).compile(); uc = module.get(LessonCopyUC); userRepo = module.get(UserRepo); - lessonRepo = module.get(LessonRepo); + lessonService = module.get(LessonService); authorisation = module.get(AuthorizationService); courseRepo = module.get(CourseRepo); lessonCopyService = module.get(LessonCopyService); @@ -128,8 +124,8 @@ describe('lesson copy uc', () => { authorisation.getUserWithPermissions.mockResolvedValueOnce(user); authorisation.hasPermission.mockReturnValue(true); - lessonRepo.findById.mockResolvedValueOnce(lesson); - lessonRepo.findAllByCourseIds.mockResolvedValueOnce([allLessons, allLessons.length]); + lessonService.findById.mockResolvedValueOnce(lesson); + lessonService.findByCourseIds.mockResolvedValueOnce([allLessons, allLessons.length]); courseRepo.findById.mockResolvedValueOnce(course); lessonCopyService.copyLesson.mockResolvedValueOnce(status); @@ -185,8 +181,8 @@ describe('lesson copy uc', () => { authorisation.getUserWithPermissions.mockResolvedValueOnce(user); authorisation.hasPermission.mockReturnValue(true); - lessonRepo.findById.mockResolvedValueOnce(lesson); - lessonRepo.findAllByCourseIds.mockResolvedValueOnce([allLessons, allLessons.length]); + lessonService.findById.mockResolvedValueOnce(lesson); + lessonService.findByCourseIds.mockResolvedValueOnce([allLessons, allLessons.length]); courseRepo.findById.mockResolvedValueOnce(course); lessonCopyService.copyLesson.mockResolvedValueOnce(status); @@ -221,7 +217,7 @@ describe('lesson copy uc', () => { await uc.copyLesson(userId, lessonId, parentParams); - expect(lessonRepo.findById).toBeCalledWith(lessonId); + expect(lessonService.findById).toBeCalledWith(lessonId); }); it('should fetch destination course', async () => { @@ -285,7 +281,7 @@ describe('lesson copy uc', () => { await uc.copyLesson(userId, lessonId, parentParams); - expect(lessonRepo.findAllByCourseIds).toHaveBeenCalledWith([courseId]); + expect(lessonService.findByCourseIds).toHaveBeenCalledWith([courseId]); }); }); @@ -300,7 +296,7 @@ describe('lesson copy uc', () => { const parentParams = { courseId: course.id, userId: new ObjectId().toHexString() }; userRepo.findById.mockResolvedValueOnce(user); - lessonRepo.findById.mockResolvedValueOnce(lesson); + lessonService.findById.mockResolvedValueOnce(lesson); courseRepo.findById.mockResolvedValueOnce(course); authorisation.hasPermission.mockReturnValueOnce(false); @@ -331,7 +327,7 @@ describe('lesson copy uc', () => { const parentParams = { courseId: course.id, userId: new ObjectId().toHexString() }; userRepo.findById.mockResolvedValueOnce(user); - lessonRepo.findById.mockResolvedValueOnce(lesson); + lessonService.findById.mockResolvedValueOnce(lesson); courseRepo.findById.mockResolvedValueOnce(course); authorisation.checkPermission.mockImplementationOnce(() => { throw new ForbiddenException(); diff --git a/apps/server/src/modules/learnroom/uc/lesson-copy.uc.ts b/apps/server/src/modules/learnroom/uc/lesson-copy.uc.ts index ae41fb6881b..c5c75a6e011 100644 --- a/apps/server/src/modules/learnroom/uc/lesson-copy.uc.ts +++ b/apps/server/src/modules/learnroom/uc/lesson-copy.uc.ts @@ -1,19 +1,18 @@ import { Configuration } from '@hpi-schul-cloud/commons'; +import { AuthorizationContextBuilder, AuthorizationService } from '@modules/authorization'; +import { CopyHelperService, CopyStatus } from '@modules/copy-helper'; +import { LessonCopyParentParams, LessonCopyService, LessonService } from '@modules/lesson'; import { ForbiddenException, Injectable, InternalServerErrorException } from '@nestjs/common'; import { Course, EntityId, LessonEntity, User } from '@shared/domain'; import { Permission } from '@shared/domain/interface/permission.enum'; -import { CourseRepo, LessonRepo } from '@shared/repo'; -import { AuthorizationContextBuilder, AuthorizationService } from '@modules/authorization'; -import { CopyHelperService, CopyStatus } from '@modules/copy-helper'; -import { LessonCopyParentParams } from '@modules/lesson'; -import { LessonCopyService } from '@modules/lesson/service'; +import { CourseRepo } from '@shared/repo'; @Injectable() export class LessonCopyUC { constructor( private readonly authorisation: AuthorizationService, private readonly lessonCopyService: LessonCopyService, - private readonly lessonRepo: LessonRepo, + private readonly lessonService: LessonService, private readonly courseRepo: CourseRepo, private readonly copyHelperService: CopyHelperService ) {} @@ -23,7 +22,7 @@ export class LessonCopyUC { const [user, originalLesson]: [User, LessonEntity] = await Promise.all([ this.authorisation.getUserWithPermissions(userId), - this.lessonRepo.findById(lessonId), + this.lessonService.findById(lessonId), ]); this.checkOriginalLessonAuthorization(user, originalLesson); @@ -37,7 +36,7 @@ export class LessonCopyUC { this.checkDestinationCourseAuthorization(user, destinationCourse); // should be a seperate private method - const [existingLessons] = await this.lessonRepo.findAllByCourseIds([originalLesson.course.id]); + const [existingLessons] = await this.lessonService.findByCourseIds([originalLesson.course.id]); const existingNames = existingLessons.map((l) => l.name); const copyName = this.copyHelperService.deriveCopyName(originalLesson.name, existingNames); diff --git a/apps/server/src/modules/learnroom/uc/rooms.uc.spec.ts b/apps/server/src/modules/learnroom/uc/rooms.uc.spec.ts index 7c6b82aad84..69faa5c8fd7 100644 --- a/apps/server/src/modules/learnroom/uc/rooms.uc.spec.ts +++ b/apps/server/src/modules/learnroom/uc/rooms.uc.spec.ts @@ -1,7 +1,7 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { ForbiddenException } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; -import { BoardRepo, CourseRepo, LessonRepo, TaskRepo, UserRepo } from '@shared/repo'; +import { BoardRepo, CourseRepo, TaskRepo, UserRepo } from '@shared/repo'; import { boardFactory, courseFactory, lessonFactory, setupEntities, taskFactory, userFactory } from '@shared/testing'; import { RoomsService } from '../service/rooms.service'; import { RoomBoardDTOFactory } from './room-board-dto.factory'; @@ -11,7 +11,6 @@ import { RoomsUc } from './rooms.uc'; describe('rooms usecase', () => { let uc: RoomsUc; let courseRepo: DeepMocked; - let lessonRepo: DeepMocked; let taskRepo: DeepMocked; let userRepo: DeepMocked; let boardRepo: DeepMocked; @@ -33,10 +32,6 @@ describe('rooms usecase', () => { provide: CourseRepo, useValue: createMock(), }, - { - provide: LessonRepo, - useValue: createMock(), - }, { provide: TaskRepo, useValue: createMock(), @@ -66,7 +61,6 @@ describe('rooms usecase', () => { uc = module.get(RoomsUc); courseRepo = module.get(CourseRepo); - lessonRepo = module.get(LessonRepo); taskRepo = module.get(TaskRepo); userRepo = module.get(UserRepo); boardRepo = module.get(BoardRepo); @@ -97,7 +91,6 @@ describe('rooms usecase', () => { const roomSpy = courseRepo.findOne.mockResolvedValue(room); const boardSpy = boardRepo.findByCourseId.mockResolvedValue(board); const tasksSpy = taskRepo.findBySingleParent.mockResolvedValue([tasks, 3]); - const lessonsSpy = lessonRepo.findAllByCourseIds.mockResolvedValue([lessons, 3]); const syncBoardElementReferencesSpy = jest.spyOn(board, 'syncBoardElementReferences'); const mapperSpy = factory.createDTO.mockReturnValue(roomBoardDTO); const saveSpy = boardRepo.save.mockResolvedValue(); @@ -114,7 +107,6 @@ describe('rooms usecase', () => { roomSpy, boardSpy, tasksSpy, - lessonsSpy, syncBoardElementReferencesSpy, mapperSpy, saveSpy, diff --git a/apps/server/src/modules/lesson/index.ts b/apps/server/src/modules/lesson/index.ts index 61e512d84b7..b552bf9c988 100644 --- a/apps/server/src/modules/lesson/index.ts +++ b/apps/server/src/modules/lesson/index.ts @@ -1,3 +1,5 @@ export * from './lesson.module'; +export * from './service/lesson-copy.service'; +export * from './service/lesson.service'; export * from './types/lesson-copy-parent.params'; export * from './types/lesson-copy.params'; diff --git a/apps/server/src/modules/lesson/lesson.module.ts b/apps/server/src/modules/lesson/lesson.module.ts index dde1eb157ec..3a009550010 100644 --- a/apps/server/src/modules/lesson/lesson.module.ts +++ b/apps/server/src/modules/lesson/lesson.module.ts @@ -1,10 +1,10 @@ -import { Module } from '@nestjs/common'; import { FeathersServiceProvider } from '@infra/feathers'; -import { LessonRepo } from '@shared/repo'; -import { LoggerModule } from '@src/core/logger'; import { CopyHelperModule } from '@modules/copy-helper'; import { FilesStorageClientModule } from '@modules/files-storage-client'; import { TaskModule } from '@modules/task'; +import { Module } from '@nestjs/common'; +import { LoggerModule } from '@src/core/logger'; +import { LessonRepo } from './repository'; import { EtherpadService, LessonCopyService, LessonService, NexboardService } from './service'; @Module({ diff --git a/apps/server/src/shared/repo/lesson/index.ts b/apps/server/src/modules/lesson/repository/index.ts similarity index 100% rename from apps/server/src/shared/repo/lesson/index.ts rename to apps/server/src/modules/lesson/repository/index.ts diff --git a/apps/server/src/shared/repo/lesson/lesson-scope.ts b/apps/server/src/modules/lesson/repository/lesson-scope.ts similarity index 90% rename from apps/server/src/shared/repo/lesson/lesson-scope.ts rename to apps/server/src/modules/lesson/repository/lesson-scope.ts index ef99b7624c1..81529b03f88 100644 --- a/apps/server/src/shared/repo/lesson/lesson-scope.ts +++ b/apps/server/src/modules/lesson/repository/lesson-scope.ts @@ -1,5 +1,5 @@ import { EntityId, LessonEntity } from '@shared/domain'; -import { Scope } from '../scope'; +import { Scope } from '@shared/repo'; export class LessonScope extends Scope { byCourseIds(courseIds: EntityId[]): LessonScope { diff --git a/apps/server/src/shared/repo/lesson/lesson.repo.integration.spec.ts b/apps/server/src/modules/lesson/repository/lesson.repo.integration.spec.ts similarity index 100% rename from apps/server/src/shared/repo/lesson/lesson.repo.integration.spec.ts rename to apps/server/src/modules/lesson/repository/lesson.repo.integration.spec.ts diff --git a/apps/server/src/shared/repo/lesson/lesson.repo.ts b/apps/server/src/modules/lesson/repository/lesson.repo.ts similarity index 97% rename from apps/server/src/shared/repo/lesson/lesson.repo.ts rename to apps/server/src/modules/lesson/repository/lesson.repo.ts index 26f66e9587d..c2fc2f0269e 100644 --- a/apps/server/src/shared/repo/lesson/lesson.repo.ts +++ b/apps/server/src/modules/lesson/repository/lesson.repo.ts @@ -1,7 +1,7 @@ +import { EntityDictionary } from '@mikro-orm/core'; import { Injectable } from '@nestjs/common'; import { Counted, EntityId, LessonEntity, SortOrder } from '@shared/domain'; -import { EntityDictionary } from '@mikro-orm/core'; -import { BaseRepo } from '../base.repo'; +import { BaseRepo } from '@shared/repo'; import { LessonScope } from './lesson-scope'; @Injectable() diff --git a/apps/server/src/modules/lesson/service/lesson-copy.service.spec.ts b/apps/server/src/modules/lesson/service/lesson-copy.service.spec.ts index 34392c91c0c..5e7c1aa3e59 100644 --- a/apps/server/src/modules/lesson/service/lesson-copy.service.spec.ts +++ b/apps/server/src/modules/lesson/service/lesson-copy.service.spec.ts @@ -1,5 +1,8 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { Configuration } from '@hpi-schul-cloud/commons'; +import { CopyElementType, CopyHelperService, CopyStatus, CopyStatusEnum } from '@modules/copy-helper'; +import { CopyFilesService } from '@modules/files-storage-client'; +import { TaskCopyService } from '@modules/task'; import { Test, TestingModule } from '@nestjs/testing'; import { BaseEntity, @@ -15,7 +18,6 @@ import { Material, } from '@shared/domain'; import { AuthorizableObject } from '@shared/domain/domain-object'; -import { LessonRepo } from '@shared/repo'; import { courseFactory, lessonFactory, @@ -24,9 +26,7 @@ import { taskFactory, userFactory, } from '@shared/testing'; -import { CopyElementType, CopyHelperService, CopyStatus, CopyStatusEnum } from '@modules/copy-helper'; -import { CopyFilesService } from '@modules/files-storage-client'; -import { TaskCopyService } from '@modules/task/service'; +import { LessonRepo } from '../repository'; import { EtherpadService } from './etherpad.service'; import { LessonCopyService } from './lesson-copy.service'; import { NexboardService } from './nexboard.service'; diff --git a/apps/server/src/modules/lesson/service/lesson-copy.service.ts b/apps/server/src/modules/lesson/service/lesson-copy.service.ts index b6d7e7849c7..0ea5110b424 100644 --- a/apps/server/src/modules/lesson/service/lesson-copy.service.ts +++ b/apps/server/src/modules/lesson/service/lesson-copy.service.ts @@ -1,4 +1,7 @@ import { Configuration } from '@hpi-schul-cloud/commons'; +import { CopyDictionary, CopyElementType, CopyHelperService, CopyStatus, CopyStatusEnum } from '@modules/copy-helper'; +import { CopyFilesService, FileUrlReplacement } from '@modules/files-storage-client'; +import { TaskCopyService } from '@modules/task'; import { Injectable } from '@nestjs/common'; import { ComponentType, @@ -11,12 +14,8 @@ import { LessonEntity, Material, } from '@shared/domain'; -import { LessonRepo } from '@shared/repo'; -import { CopyDictionary, CopyElementType, CopyHelperService, CopyStatus, CopyStatusEnum } from '@modules/copy-helper'; -import { CopyFilesService } from '@modules/files-storage-client'; -import { FileUrlReplacement } from '@modules/files-storage-client/service/copy-files.service'; -import { TaskCopyService } from '@modules/task/service/task-copy.service'; import { randomBytes } from 'crypto'; +import { LessonRepo } from '../repository'; import { LessonCopyParams } from '../types'; import { EtherpadService } from './etherpad.service'; import { NexboardService } from './nexboard.service'; diff --git a/apps/server/src/modules/lesson/service/lesson.service.spec.ts b/apps/server/src/modules/lesson/service/lesson.service.spec.ts index a94ecfe9c8b..b82875d7c1f 100644 --- a/apps/server/src/modules/lesson/service/lesson.service.spec.ts +++ b/apps/server/src/modules/lesson/service/lesson.service.spec.ts @@ -1,10 +1,10 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; -import { Test, TestingModule } from '@nestjs/testing'; -import { LessonRepo } from '@shared/repo'; -import { lessonFactory, setupEntities } from '@shared/testing'; -import { FilesStorageClientAdapterService } from '@modules/files-storage-client'; import { ObjectId } from '@mikro-orm/mongodb'; +import { FilesStorageClientAdapterService } from '@modules/files-storage-client'; +import { Test, TestingModule } from '@nestjs/testing'; import { ComponentType, IComponentProperties } from '@shared/domain'; +import { lessonFactory, setupEntities } from '@shared/testing'; +import { LessonRepo } from '../repository'; import { LessonService } from './lesson.service'; describe('LessonService', () => { @@ -71,9 +71,19 @@ describe('LessonService', () => { const courseIds = ['course-1', 'course-2']; lessonRepo.findAllByCourseIds.mockResolvedValueOnce([[], 0]); - await expect(lessonService.findByCourseIds(courseIds)).resolves.not.toThrow(); - expect(lessonRepo.findAllByCourseIds).toBeCalledTimes(1); - expect(lessonRepo.findAllByCourseIds).toBeCalledWith(courseIds); + await lessonService.findByCourseIds(courseIds); + + expect(lessonRepo.findAllByCourseIds).toBeCalledWith(courseIds, undefined); + }); + + it('should pass filters', async () => { + const courseIds = ['course-1', 'course-2']; + lessonRepo.findAllByCourseIds.mockResolvedValueOnce([[], 0]); + const filters = { hidden: false }; + + await lessonService.findByCourseIds(courseIds, filters); + + expect(lessonRepo.findAllByCourseIds).toBeCalledWith(courseIds, filters); }); }); diff --git a/apps/server/src/modules/lesson/service/lesson.service.ts b/apps/server/src/modules/lesson/service/lesson.service.ts index 2dee6f05563..c98e633469e 100644 --- a/apps/server/src/modules/lesson/service/lesson.service.ts +++ b/apps/server/src/modules/lesson/service/lesson.service.ts @@ -1,10 +1,11 @@ +import { FilesStorageClientAdapterService } from '@modules/files-storage-client'; import { Injectable } from '@nestjs/common'; import { Counted, EntityId, IComponentProperties, LessonEntity } from '@shared/domain'; -import { LessonRepo } from '@shared/repo'; -import { FilesStorageClientAdapterService } from '@modules/files-storage-client'; +import { AuthorizationLoaderService } from '@src/modules/authorization'; +import { LessonRepo } from '../repository'; @Injectable() -export class LessonService { +export class LessonService implements AuthorizationLoaderService { constructor( private readonly lessonRepo: LessonRepo, private readonly filesStorageClientAdapterService: FilesStorageClientAdapterService @@ -20,8 +21,8 @@ export class LessonService { return this.lessonRepo.findById(lessonId); } - async findByCourseIds(courseIds: EntityId[]): Promise> { - return this.lessonRepo.findAllByCourseIds(courseIds); + async findByCourseIds(courseIds: EntityId[], filters?: { hidden?: boolean }): Promise> { + return this.lessonRepo.findAllByCourseIds(courseIds, filters); } async findAllLessonsByUserId(userId: EntityId): Promise { diff --git a/apps/server/src/modules/sharing/uc/share-token.uc.spec.ts b/apps/server/src/modules/sharing/uc/share-token.uc.spec.ts index 72d2a824327..9ad71462008 100644 --- a/apps/server/src/modules/sharing/uc/share-token.uc.spec.ts +++ b/apps/server/src/modules/sharing/uc/share-token.uc.spec.ts @@ -3,8 +3,17 @@ import { Configuration } from '@hpi-schul-cloud/commons/lib'; import { BadRequestException, InternalServerErrorException, NotImplementedException } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { Permission } from '@shared/domain'; -import { LessonRepo } from '@shared/repo'; +import { + Action, + AuthorizableReferenceType, + AuthorizationReferenceService, + AuthorizationService, +} from '@modules/authorization'; +import { CopyElementType, CopyStatus, CopyStatusEnum } from '@modules/copy-helper'; +import { CourseCopyService, CourseService } from '@modules/learnroom'; +import { LessonCopyService } from '@modules/lesson'; +import { TaskCopyService } from '@modules/task'; import { courseFactory, lessonFactory, @@ -15,13 +24,6 @@ import { userFactory, } from '@shared/testing'; import { LegacyLogger } from '@src/core/logger'; -import { Action, AuthorizationService } from '@modules/authorization'; -import { AuthorizableReferenceType, AuthorizationReferenceService } from '@modules/authorization/domain'; -import { CopyElementType, CopyStatus, CopyStatusEnum } from '@modules/copy-helper'; -import { CourseCopyService } from '@modules/learnroom'; -import { CourseService } from '@modules/learnroom/service'; -import { LessonCopyService } from '@modules/lesson/service'; -import { TaskCopyService } from '@modules/task/service'; import { ShareTokenContextType, ShareTokenParentType, ShareTokenPayload } from '../domainobject/share-token.do'; import { ShareTokenService } from '../service'; import { ShareTokenUC } from './share-token.uc'; @@ -36,7 +38,6 @@ describe('ShareTokenUC', () => { let authorization: DeepMocked; let authorizationReferenceService: DeepMocked; let courseService: DeepMocked; - let lessonRepo: DeepMocked; beforeAll(async () => { module = await Test.createTestingModule({ @@ -62,10 +63,6 @@ describe('ShareTokenUC', () => { provide: LessonCopyService, useValue: createMock(), }, - { - provide: LessonRepo, - useValue: createMock(), - }, { provide: CourseService, useValue: createMock(), @@ -89,7 +86,6 @@ describe('ShareTokenUC', () => { authorization = module.get(AuthorizationService); authorizationReferenceService = module.get(AuthorizationReferenceService); courseService = module.get(CourseService); - lessonRepo = module.get(LessonRepo); await setupEntities(); }); @@ -729,7 +725,6 @@ describe('ShareTokenUC', () => { const course = courseFactory.buildWithId(); courseService.findById.mockResolvedValue(course); const lesson = lessonFactory.buildWithId({ course }); - lessonRepo.findById.mockResolvedValue(lesson); const status: CopyStatus = { type: CopyElementType.LESSON, diff --git a/apps/server/src/modules/task/index.ts b/apps/server/src/modules/task/index.ts index 62ab8a69e94..8734b0eb623 100644 --- a/apps/server/src/modules/task/index.ts +++ b/apps/server/src/modules/task/index.ts @@ -1 +1,4 @@ +export * from './service/submission.service'; +export * from './service/task-copy.service'; +export * from './service/task.service'; export * from './task.module'; diff --git a/apps/server/src/modules/task/task-api.module.ts b/apps/server/src/modules/task/task-api.module.ts index cdb998eab4a..6085e426f6c 100644 --- a/apps/server/src/modules/task/task-api.module.ts +++ b/apps/server/src/modules/task/task-api.module.ts @@ -1,14 +1,15 @@ -import { Module } from '@nestjs/common'; -import { CourseRepo, LessonRepo, TaskRepo } from '@shared/repo'; import { AuthorizationModule } from '@modules/authorization'; import { CopyHelperModule } from '@modules/copy-helper/copy-helper.module'; +import { Module } from '@nestjs/common'; +import { CourseRepo, TaskRepo } from '@shared/repo'; +import { LessonModule } from '@modules/lesson'; import { SubmissionController, TaskController } from './controller'; import { TaskModule } from './task.module'; import { SubmissionUc, TaskCopyUC, TaskUC } from './uc'; @Module({ - imports: [AuthorizationModule, CopyHelperModule, TaskModule], + imports: [AuthorizationModule, CopyHelperModule, TaskModule, LessonModule], controllers: [TaskController, SubmissionController], - providers: [TaskUC, TaskRepo, LessonRepo, CourseRepo, TaskCopyUC, SubmissionUc], + providers: [TaskUC, TaskRepo, CourseRepo, TaskCopyUC, SubmissionUc], }) export class TaskApiModule {} diff --git a/apps/server/src/modules/task/task.module.ts b/apps/server/src/modules/task/task.module.ts index 45a0fdb720a..87ecf144798 100644 --- a/apps/server/src/modules/task/task.module.ts +++ b/apps/server/src/modules/task/task.module.ts @@ -1,12 +1,12 @@ import { CopyHelperModule } from '@modules/copy-helper'; import { FilesStorageClientModule } from '@modules/files-storage-client'; import { Module } from '@nestjs/common'; -import { CourseRepo, LessonRepo, SubmissionRepo, TaskRepo } from '@shared/repo'; +import { CourseRepo, SubmissionRepo, TaskRepo } from '@shared/repo'; import { SubmissionService, TaskCopyService, TaskService } from './service'; @Module({ imports: [FilesStorageClientModule, CopyHelperModule], - providers: [TaskService, TaskCopyService, SubmissionService, TaskRepo, LessonRepo, CourseRepo, SubmissionRepo], + providers: [TaskService, TaskCopyService, SubmissionService, TaskRepo, CourseRepo, SubmissionRepo], exports: [TaskService, TaskCopyService, SubmissionService], }) export class TaskModule {} diff --git a/apps/server/src/modules/task/uc/task-copy.uc.spec.ts b/apps/server/src/modules/task/uc/task-copy.uc.spec.ts index dc381cda22e..182294cfd91 100644 --- a/apps/server/src/modules/task/uc/task-copy.uc.spec.ts +++ b/apps/server/src/modules/task/uc/task-copy.uc.spec.ts @@ -1,23 +1,24 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { Configuration } from '@hpi-schul-cloud/commons'; import { ObjectId } from '@mikro-orm/mongodb'; -import { ForbiddenException, InternalServerErrorException, NotFoundException } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; -import { CourseRepo, LessonRepo, TaskRepo, UserRepo } from '@shared/repo'; -import { courseFactory, lessonFactory, setupEntities, taskFactory, userFactory } from '@shared/testing'; import { Action, AuthorizationContextBuilder, AuthorizationService } from '@modules/authorization'; import { CopyElementType, CopyHelperService, CopyStatusEnum } from '@modules/copy-helper'; import { FilesStorageClientAdapterService } from '@modules/files-storage-client'; +import { LessonService } from '@modules/lesson'; +import { ForbiddenException, InternalServerErrorException, NotFoundException } from '@nestjs/common'; +import { Test, TestingModule } from '@nestjs/testing'; +import { CourseRepo, TaskRepo, UserRepo } from '@shared/repo'; +import { courseFactory, lessonFactory, setupEntities, taskFactory, userFactory } from '@shared/testing'; import { TaskCopyService } from '../service'; -import { TaskCopyUC } from './task-copy.uc'; import { TaskCopyParentParams } from '../types'; +import { TaskCopyUC } from './task-copy.uc'; describe('task copy uc', () => { let uc: TaskCopyUC; let userRepo: DeepMocked; let taskRepo: DeepMocked; let courseRepo: DeepMocked; - let lessonRepo: DeepMocked; + let lessonService: DeepMocked; let authorisation: DeepMocked; let taskCopyService: DeepMocked; let copyHelperService: DeepMocked; @@ -42,8 +43,8 @@ describe('task copy uc', () => { useValue: createMock(), }, { - provide: LessonRepo, - useValue: createMock(), + provide: LessonService, + useValue: createMock(), }, { provide: AuthorizationService, @@ -69,7 +70,7 @@ describe('task copy uc', () => { taskRepo = module.get(TaskRepo); authorisation = module.get(AuthorizationService); courseRepo = module.get(CourseRepo); - lessonRepo = module.get(LessonRepo); + lessonService = module.get(LessonService); taskCopyService = module.get(TaskCopyService); copyHelperService = module.get(CopyHelperService); }); @@ -104,7 +105,7 @@ describe('task copy uc', () => { authorisation.getUserWithPermissions.mockResolvedValueOnce(user); taskRepo.findById.mockResolvedValueOnce(task); - lessonRepo.findById.mockResolvedValueOnce(lesson); + lessonService.findById.mockResolvedValueOnce(lesson); taskRepo.findBySingleParent.mockResolvedValueOnce([allTasks, allTasks.length]); courseRepo.findById.mockResolvedValueOnce(course); authorisation.hasPermission.mockReturnValueOnce(true).mockReturnValueOnce(true); @@ -187,7 +188,7 @@ describe('task copy uc', () => { await uc.copyTask(user.id, task.id, { lessonId: lesson.id, userId }); - expect(lessonRepo.findById).toBeCalledWith(lesson.id); + expect(lessonService.findById).toBeCalledWith(lesson.id); }); it('should pass without destination lesson', async () => { @@ -195,7 +196,7 @@ describe('task copy uc', () => { await uc.copyTask(user.id, task.id, { userId }); - expect(lessonRepo.findById).not.toHaveBeenCalled(); + expect(lessonService.findById).not.toHaveBeenCalled(); }); }); @@ -365,7 +366,7 @@ describe('task copy uc', () => { userRepo.findById.mockResolvedValueOnce(user); taskRepo.findById.mockResolvedValueOnce(task); courseRepo.findById.mockResolvedValueOnce(course); - lessonRepo.findById.mockResolvedValueOnce(lesson); + lessonService.findById.mockResolvedValueOnce(lesson); // first canReadTask > second canWriteLesson authorisation.hasPermission.mockReturnValueOnce(true).mockReturnValueOnce(false); diff --git a/apps/server/src/modules/task/uc/task-copy.uc.ts b/apps/server/src/modules/task/uc/task-copy.uc.ts index 69fd99e224f..58316aa1972 100644 --- a/apps/server/src/modules/task/uc/task-copy.uc.ts +++ b/apps/server/src/modules/task/uc/task-copy.uc.ts @@ -1,9 +1,10 @@ import { Configuration } from '@hpi-schul-cloud/commons'; -import { ForbiddenException, Injectable, InternalServerErrorException, NotFoundException } from '@nestjs/common'; -import { Course, EntityId, Task, LessonEntity, User } from '@shared/domain'; -import { CourseRepo, LessonRepo, TaskRepo } from '@shared/repo'; import { AuthorizationContextBuilder, AuthorizationService } from '@modules/authorization'; import { CopyHelperService, CopyStatus } from '@modules/copy-helper'; +import { ForbiddenException, Injectable, InternalServerErrorException, NotFoundException } from '@nestjs/common'; +import { Course, EntityId, LessonEntity, Task, User } from '@shared/domain'; +import { CourseRepo, TaskRepo } from '@shared/repo'; +import { LessonService } from '@modules/lesson'; import { TaskCopyService } from '../service'; import { TaskCopyParentParams } from '../types'; @@ -11,7 +12,7 @@ import { TaskCopyParentParams } from '../types'; export class TaskCopyUC { constructor( private readonly courseRepo: CourseRepo, - private readonly lessonRepo: LessonRepo, + private readonly lessonService: LessonService, private readonly authorisation: AuthorizationService, private readonly taskCopyService: TaskCopyService, private readonly taskRepo: TaskRepo, @@ -104,7 +105,7 @@ export class TaskCopyUC { return undefined; } - const destinationLesson = await this.lessonRepo.findById(lessonId); + const destinationLesson = await this.lessonService.findById(lessonId); return destinationLesson; } diff --git a/apps/server/src/modules/task/uc/task.uc.spec.ts b/apps/server/src/modules/task/uc/task.uc.spec.ts index 90bb29db444..e836f907f26 100644 --- a/apps/server/src/modules/task/uc/task.uc.spec.ts +++ b/apps/server/src/modules/task/uc/task.uc.spec.ts @@ -1,9 +1,10 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { Action, AuthorizationService } from '@modules/authorization'; import { ForbiddenException, UnauthorizedException } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { PaginationParams } from '@shared/controller'; import { ITaskStatus, Permission, SortOrder } from '@shared/domain'; -import { CourseRepo, LessonRepo, TaskRepo } from '@shared/repo'; +import { CourseRepo, TaskRepo } from '@shared/repo'; import { courseFactory, lessonFactory, @@ -13,7 +14,7 @@ import { taskFactory, userFactory, } from '@shared/testing'; -import { Action, AuthorizationService } from '@modules/authorization'; +import { LessonService } from '@modules/lesson'; import { TaskService } from '../service'; import { TaskUC } from './task.uc'; @@ -22,7 +23,7 @@ describe('TaskUC', () => { let service: TaskUC; let taskRepo: DeepMocked; let courseRepo: DeepMocked; - let lessonRepo: DeepMocked; + let lessonService: DeepMocked; let authorizationService: DeepMocked; let taskService: DeepMocked; @@ -48,8 +49,8 @@ describe('TaskUC', () => { useValue: createMock(), }, { - provide: LessonRepo, - useValue: createMock(), + provide: LessonService, + useValue: createMock(), }, { provide: AuthorizationService, @@ -69,7 +70,7 @@ describe('TaskUC', () => { service = module.get(TaskUC); taskRepo = module.get(TaskRepo); courseRepo = module.get(CourseRepo); - lessonRepo = module.get(LessonRepo); + lessonService = module.get(LessonService); authorizationService = module.get(AuthorizationService); taskService = module.get(TaskService); }); @@ -90,8 +91,8 @@ describe('TaskUC', () => { const finishedTask = taskFactory.finished(user).build(); authorizationService.getUserWithPermissions.mockResolvedValueOnce(user); courseRepo.findAllByUserId.mockResolvedValueOnce([[], 0]); - lessonRepo.findAllByCourseIds.mockResolvedValueOnce([[], 0]); - lessonRepo.findAllByCourseIds.mockResolvedValueOnce([[], 0]); + lessonService.findByCourseIds.mockResolvedValueOnce([[], 0]); + lessonService.findByCourseIds.mockResolvedValueOnce([[], 0]); authorizationService.hasPermission.mockReturnValueOnce(false); taskRepo.findAllFinishedByParentIds.mockResolvedValueOnce([[finishedTask], 1]); @@ -204,8 +205,8 @@ describe('TaskUC', () => { authorizationService.getUserWithPermissions.mockResolvedValue(user); courseRepo.findAllByUserId.mockResolvedValue([[], 0]); - lessonRepo.findAllByCourseIds.mockResolvedValueOnce([[lesson], 1]); - lessonRepo.findAllByCourseIds.mockResolvedValueOnce([[], 0]); + lessonService.findByCourseIds.mockResolvedValueOnce([[lesson], 1]); + lessonService.findByCourseIds.mockResolvedValueOnce([[], 0]); authorizationService.hasPermission.mockReturnValueOnce(false); taskRepo.findAllFinishedByParentIds.mockResolvedValue([[task], 1]); @@ -240,8 +241,8 @@ describe('TaskUC', () => { authorizationService.getUserWithPermissions.mockResolvedValueOnce(user); courseRepo.findAllByUserId.mockResolvedValueOnce([[course], 1]); - lessonRepo.findAllByCourseIds.mockResolvedValueOnce([[], 0]); - lessonRepo.findAllByCourseIds.mockResolvedValueOnce([[], 0]); + lessonService.findByCourseIds.mockResolvedValueOnce([[], 0]); + lessonService.findByCourseIds.mockResolvedValueOnce([[], 0]); authorizationService.hasPermission.mockReturnValueOnce(false); taskRepo.findAllFinishedByParentIds.mockResolvedValueOnce([[task], 1]); @@ -276,8 +277,8 @@ describe('TaskUC', () => { authorizationService.getUserWithPermissions.mockResolvedValueOnce(user); courseRepo.findAllByUserId.mockResolvedValueOnce([[], 0]); - lessonRepo.findAllByCourseIds.mockResolvedValueOnce([[], 0]); - lessonRepo.findAllByCourseIds.mockResolvedValueOnce([[], 0]); + lessonService.findByCourseIds.mockResolvedValueOnce([[], 0]); + lessonService.findByCourseIds.mockResolvedValueOnce([[], 0]); authorizationService.hasPermission.mockReturnValueOnce(true); taskRepo.findAllFinishedByParentIds.mockResolvedValueOnce([[task], 1]); @@ -303,8 +304,8 @@ describe('TaskUC', () => { authorizationService.getUserWithPermissions.mockResolvedValueOnce(user); courseRepo.findAllByUserId.mockResolvedValueOnce([[course], 1]); - lessonRepo.findAllByCourseIds.mockResolvedValueOnce([[], 0]); - lessonRepo.findAllByCourseIds.mockResolvedValueOnce([[], 0]); + lessonService.findByCourseIds.mockResolvedValueOnce([[], 0]); + lessonService.findByCourseIds.mockResolvedValueOnce([[], 0]); authorizationService.hasPermission.mockReturnValueOnce(true); authorizationService.hasPermission.mockReturnValueOnce(true); taskRepo.findAllFinishedByParentIds.mockResolvedValueOnce([[task], 1]); @@ -389,8 +390,8 @@ describe('TaskUC', () => { authorizationService.hasAllPermissions.mockReturnValueOnce(true); courseRepo.findAllByUserId.mockResolvedValueOnce([[course], 1]); authorizationService.hasPermission.mockReturnValueOnce(false); - lessonRepo.findAllByCourseIds.mockResolvedValueOnce([[], 0]); - lessonRepo.findAllByCourseIds.mockResolvedValueOnce([[lesson], 1]); + lessonService.findByCourseIds.mockResolvedValueOnce([[], 0]); + lessonService.findByCourseIds.mockResolvedValueOnce([[lesson], 1]); taskRepo.findAllByParentIds.mockResolvedValueOnce([[task1, task2, task3], 3]); return { user, course, lesson, task1, paginationParams }; @@ -503,8 +504,8 @@ describe('TaskUC', () => { authorizationService.hasAllPermissions.mockReturnValueOnce(true); courseRepo.findAllForTeacherOrSubstituteTeacher.mockResolvedValueOnce([[course], 1]); authorizationService.hasPermission.mockReturnValueOnce(true); - lessonRepo.findAllByCourseIds.mockResolvedValueOnce([[lesson], 1]); - lessonRepo.findAllByCourseIds.mockResolvedValueOnce([[], 0]); + lessonService.findByCourseIds.mockResolvedValueOnce([[lesson], 1]); + lessonService.findByCourseIds.mockResolvedValueOnce([[], 0]); taskRepo.findAllByParentIds.mockResolvedValueOnce([[task], 1]); return { user, paginationParams }; @@ -538,8 +539,8 @@ describe('TaskUC', () => { authorizationService.hasAllPermissions.mockReturnValueOnce(true); courseRepo.findAllForTeacherOrSubstituteTeacher.mockResolvedValueOnce([[course], 1]); authorizationService.hasPermission.mockReturnValueOnce(true); - lessonRepo.findAllByCourseIds.mockResolvedValueOnce([[lesson], 1]); - lessonRepo.findAllByCourseIds.mockResolvedValueOnce([[], 0]); + lessonService.findByCourseIds.mockResolvedValueOnce([[lesson], 1]); + lessonService.findByCourseIds.mockResolvedValueOnce([[], 0]); taskRepo.findAllByParentIds.mockResolvedValueOnce([[task1, task2, task3], 3]); return { user, course, lesson, task1, paginationParams }; diff --git a/apps/server/src/modules/task/uc/task.uc.ts b/apps/server/src/modules/task/uc/task.uc.ts index a6e40dd3b6d..69ca7b32c8f 100644 --- a/apps/server/src/modules/task/uc/task.uc.ts +++ b/apps/server/src/modules/task/uc/task.uc.ts @@ -1,3 +1,4 @@ +import { Action, AuthorizationContextBuilder, AuthorizationService } from '@modules/authorization'; import { Injectable, UnauthorizedException } from '@nestjs/common'; import { Counted, @@ -11,8 +12,8 @@ import { TaskWithStatusVo, User, } from '@shared/domain'; -import { CourseRepo, LessonRepo, TaskRepo } from '@shared/repo'; -import { Action, AuthorizationContextBuilder, AuthorizationService } from '@modules/authorization'; +import { CourseRepo, TaskRepo } from '@shared/repo'; +import { LessonService } from '@modules/lesson'; import { TaskService } from '../service'; @Injectable() @@ -21,7 +22,7 @@ export class TaskUC { private readonly taskRepo: TaskRepo, private readonly authorizationService: AuthorizationService, private readonly courseRepo: CourseRepo, - private readonly lessonRepo: LessonRepo, + private readonly lessonService: LessonService, private readonly taskService: TaskService ) {} @@ -206,8 +207,8 @@ export class TaskUC { // idea as combined query: // [{courseIds: onlyWriteCoursesIds}, { courseIds: onlyReadCourses, filter: { hidden: false }}] const [[writeLessons], [readLessons]] = await Promise.all([ - this.lessonRepo.findAllByCourseIds(writeCourseIds), - this.lessonRepo.findAllByCourseIds(readCourseIds, { hidden: false }), + this.lessonService.findByCourseIds(writeCourseIds), + this.lessonService.findByCourseIds(readCourseIds, { hidden: false }), ]); const permittedLessons = [...writeLessons, ...readLessons]; diff --git a/apps/server/src/modules/tool/index.ts b/apps/server/src/modules/tool/index.ts index 5af54483f5b..a7a6c23ddab 100644 --- a/apps/server/src/modules/tool/index.ts +++ b/apps/server/src/modules/tool/index.ts @@ -1,6 +1,7 @@ -export * from './school-external-tool/entity/school-external-tool.entity'; export * from './common/entity/custom-parameter-entry.entity'; +export * from './common/interface'; export * from './context-external-tool/entity'; +export * from './context-external-tool/service/context-external-tool-authorizable.service'; export * from './external-tool'; +export * from './school-external-tool/entity/school-external-tool.entity'; export * from './tool.module'; -export * from './common/interface'; diff --git a/apps/server/src/shared/repo/index.ts b/apps/server/src/shared/repo/index.ts index 715a78ee420..ce9304ec7ef 100644 --- a/apps/server/src/shared/repo/index.ts +++ b/apps/server/src/shared/repo/index.ts @@ -12,7 +12,6 @@ export * from './coursegroup'; export * from './dashboard'; export * from './federalstate'; export * from './importuser'; -export * from './lesson'; export * from './ltitool'; export * from './materials'; export * from './mongo.patterns'; From 84c267debe5b1f8e00cbdf69098ea8c30fadd545 Mon Sep 17 00:00:00 2001 From: hoeppner-dataport <106819770+hoeppner-dataport@users.noreply.github.com> Date: Mon, 20 Nov 2023 13:43:45 +0100 Subject: [PATCH 08/10] BC-5642 - internal links in LinkElements on boards (#4549) The collection of meta tags for internal pages of the project can not be done via scraping. Therefore a set of rules for types of urls and implementations to gather the needed data was implemented. --- .../update-element-content.body.params.ts | 4 +- .../meta-tag-extractor-get-data.api.spec.ts | 8 +- .../dto/meta-tag-extractor.response.spec.ts | 16 ++- .../dto/meta-tag-extractor.response.ts | 18 ++- .../meta-tag-extractor.controller.ts | 7 +- .../controller/post-link-url.body.params.ts | 4 +- .../interface/url-handler.ts | 6 + .../meta-tag-extractor-api.module.ts | 2 +- .../meta-tag-extractor.module.ts | 19 ++- .../meta-tag-extractor.service.spec.ts | 79 ++++++---- .../service/meta-tag-extractor.service.ts | 31 ++-- .../meta-tag-internal-url.service.spec.ts | 136 ++++++++++++++++++ .../service/meta-tag-internal-url.service.ts | 51 +++++++ .../url-handler/abstract-url-handler.spec.ts | 59 ++++++++ .../url-handler/abstract-url-handler.ts | 35 +++++ .../url-handler/board-url-handler.spec.ts | 70 +++++++++ .../service/url-handler/board-url-handler.ts | 37 +++++ .../url-handler/course-url-handler.spec.ts | 62 ++++++++ .../service/url-handler/course-url-handler.ts | 29 ++++ .../service/url-handler/index.ts | 4 + .../url-handler/lesson-url-handler.spec.ts | 62 ++++++++ .../service/url-handler/lesson-url-handler.ts | 29 ++++ .../url-handler/task-url-handler.spec.ts | 62 ++++++++ .../service/url-handler/task-url-handler.ts | 29 ++++ .../modules/meta-tag-extractor/types/index.ts | 1 + .../types/meta-data.type.ts | 13 ++ .../uc/meta-tag-extractor.uc.spec.ts | 12 +- .../uc/meta-tag-extractor.uc.ts | 9 +- 28 files changed, 825 insertions(+), 69 deletions(-) create mode 100644 apps/server/src/modules/meta-tag-extractor/interface/url-handler.ts create mode 100644 apps/server/src/modules/meta-tag-extractor/service/meta-tag-internal-url.service.spec.ts create mode 100644 apps/server/src/modules/meta-tag-extractor/service/meta-tag-internal-url.service.ts create mode 100644 apps/server/src/modules/meta-tag-extractor/service/url-handler/abstract-url-handler.spec.ts create mode 100644 apps/server/src/modules/meta-tag-extractor/service/url-handler/abstract-url-handler.ts create mode 100644 apps/server/src/modules/meta-tag-extractor/service/url-handler/board-url-handler.spec.ts create mode 100644 apps/server/src/modules/meta-tag-extractor/service/url-handler/board-url-handler.ts create mode 100644 apps/server/src/modules/meta-tag-extractor/service/url-handler/course-url-handler.spec.ts create mode 100644 apps/server/src/modules/meta-tag-extractor/service/url-handler/course-url-handler.ts create mode 100644 apps/server/src/modules/meta-tag-extractor/service/url-handler/index.ts create mode 100644 apps/server/src/modules/meta-tag-extractor/service/url-handler/lesson-url-handler.spec.ts create mode 100644 apps/server/src/modules/meta-tag-extractor/service/url-handler/lesson-url-handler.ts create mode 100644 apps/server/src/modules/meta-tag-extractor/service/url-handler/task-url-handler.spec.ts create mode 100644 apps/server/src/modules/meta-tag-extractor/service/url-handler/task-url-handler.ts create mode 100644 apps/server/src/modules/meta-tag-extractor/types/index.ts create mode 100644 apps/server/src/modules/meta-tag-extractor/types/meta-data.type.ts diff --git a/apps/server/src/modules/board/controller/dto/element/update-element-content.body.params.ts b/apps/server/src/modules/board/controller/dto/element/update-element-content.body.params.ts index 23ce88b904c..7d0314208c6 100644 --- a/apps/server/src/modules/board/controller/dto/element/update-element-content.body.params.ts +++ b/apps/server/src/modules/board/controller/dto/element/update-element-content.body.params.ts @@ -2,7 +2,7 @@ import { ApiProperty, ApiPropertyOptional, getSchemaPath } from '@nestjs/swagger import { ContentElementType } from '@shared/domain'; import { InputFormat } from '@shared/domain/types'; import { Type } from 'class-transformer'; -import { IsDate, IsEnum, IsMongoId, IsOptional, IsString, IsUrl, ValidateNested } from 'class-validator'; +import { IsDate, IsEnum, IsMongoId, IsOptional, IsString, ValidateNested } from 'class-validator'; export abstract class ElementContentBody { @IsEnum(ContentElementType) @@ -34,7 +34,7 @@ export class FileElementContentBody extends ElementContentBody { } export class LinkContentBody { - @IsUrl() + @IsString() @ApiProperty({}) url!: string; diff --git a/apps/server/src/modules/meta-tag-extractor/controller/api-test/meta-tag-extractor-get-data.api.spec.ts b/apps/server/src/modules/meta-tag-extractor/controller/api-test/meta-tag-extractor-get-data.api.spec.ts index c80d47df66d..2a6c2a93d90 100644 --- a/apps/server/src/modules/meta-tag-extractor/controller/api-test/meta-tag-extractor-get-data.api.spec.ts +++ b/apps/server/src/modules/meta-tag-extractor/controller/api-test/meta-tag-extractor-get-data.api.spec.ts @@ -5,7 +5,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import { TestApiClient, UserAndAccountTestFactory } from '@shared/testing'; import { MetaTagExtractorService } from '../../service'; -const URL = 'https://test.de'; +const URL = 'https://best-example.de/my-article'; const mockedResponse = { url: URL, @@ -13,7 +13,7 @@ const mockedResponse = { description: 'with great description', }; -describe(`get data (api)`, () => { +describe(`get meta tags (api)`, () => { let app: INestApplication; let em: EntityManager; let testApiClient: TestApiClient; @@ -24,7 +24,7 @@ describe(`get data (api)`, () => { }) .overrideProvider(MetaTagExtractorService) .useValue({ - fetchMetaData: () => mockedResponse, + getMetaData: () => mockedResponse, }) .compile(); @@ -63,7 +63,7 @@ describe(`get data (api)`, () => { const response = await loggedInClient.post(undefined, { url: URL }); - expect(response?.body).toEqual(mockedResponse); + expect(response?.body).toEqual(expect.objectContaining(mockedResponse)); }); }); diff --git a/apps/server/src/modules/meta-tag-extractor/controller/dto/meta-tag-extractor.response.spec.ts b/apps/server/src/modules/meta-tag-extractor/controller/dto/meta-tag-extractor.response.spec.ts index 29dfbd94c72..0f527d1139d 100644 --- a/apps/server/src/modules/meta-tag-extractor/controller/dto/meta-tag-extractor.response.spec.ts +++ b/apps/server/src/modules/meta-tag-extractor/controller/dto/meta-tag-extractor.response.spec.ts @@ -8,13 +8,19 @@ describe(MetaTagExtractorResponse.name, () => { title: 'Testbild', description: 'Here we describe what this page is about.', imageUrl: 'https://www.abc.de/test.png', + type: 'unknown', + parentTitle: 'Math', + parentType: 'course', }; - const errorResponse = new MetaTagExtractorResponse(properties); - expect(errorResponse.url).toEqual(properties.url); - expect(errorResponse.title).toEqual(properties.title); - expect(errorResponse.description).toEqual(properties.description); - expect(errorResponse.imageUrl).toEqual(properties.imageUrl); + const response = new MetaTagExtractorResponse(properties); + expect(response.url).toEqual(properties.url); + expect(response.title).toEqual(properties.title); + expect(response.description).toEqual(properties.description); + expect(response.imageUrl).toEqual(properties.imageUrl); + expect(response.type).toEqual(properties.type); + expect(response.parentTitle).toEqual(properties.parentTitle); + expect(response.parentType).toEqual(properties.parentType); }); }); }); diff --git a/apps/server/src/modules/meta-tag-extractor/controller/dto/meta-tag-extractor.response.ts b/apps/server/src/modules/meta-tag-extractor/controller/dto/meta-tag-extractor.response.ts index a2f5acd8465..16863f0e16a 100644 --- a/apps/server/src/modules/meta-tag-extractor/controller/dto/meta-tag-extractor.response.ts +++ b/apps/server/src/modules/meta-tag-extractor/controller/dto/meta-tag-extractor.response.ts @@ -1,13 +1,17 @@ import { ApiProperty } from '@nestjs/swagger'; import { DecodeHtmlEntities } from '@shared/controller'; import { IsString, IsUrl } from 'class-validator'; +import { MetaDataEntityType } from '../../types'; export class MetaTagExtractorResponse { - constructor({ url, title, description, imageUrl }: MetaTagExtractorResponse) { + constructor({ url, title, description, imageUrl, type, parentTitle, parentType }: MetaTagExtractorResponse) { this.url = url; this.title = title; this.description = description; this.imageUrl = imageUrl; + this.type = type; + this.parentTitle = parentTitle; + this.parentType = parentType; } @ApiProperty() @@ -25,4 +29,16 @@ export class MetaTagExtractorResponse { @ApiProperty() @IsString() imageUrl?: string; + + @ApiProperty() + @IsString() + type: MetaDataEntityType; + + @ApiProperty() + @DecodeHtmlEntities() + parentTitle?: string; + + @ApiProperty() + @IsString() + parentType?: MetaDataEntityType; } diff --git a/apps/server/src/modules/meta-tag-extractor/controller/meta-tag-extractor.controller.ts b/apps/server/src/modules/meta-tag-extractor/controller/meta-tag-extractor.controller.ts index 8133c4c0b83..79f798c9e29 100644 --- a/apps/server/src/modules/meta-tag-extractor/controller/meta-tag-extractor.controller.ts +++ b/apps/server/src/modules/meta-tag-extractor/controller/meta-tag-extractor.controller.ts @@ -1,7 +1,6 @@ +import { Authenticate, CurrentUser, ICurrentUser } from '@modules/authentication'; import { Body, Controller, InternalServerErrorException, Post, UnauthorizedException } from '@nestjs/common'; import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; -import { ICurrentUser } from '@src/modules/authentication'; -import { Authenticate, CurrentUser } from '@src/modules/authentication/decorator/auth.decorator'; import { MetaTagExtractorUc } from '../uc'; import { MetaTagExtractorResponse } from './dto'; import { GetMetaTagDataBody } from './post-link-url.body.params'; @@ -17,11 +16,11 @@ export class MetaTagExtractorController { @ApiResponse({ status: 401, type: UnauthorizedException }) @ApiResponse({ status: 500, type: InternalServerErrorException }) @Post('') - async getData( + async getMetaTags( @CurrentUser() currentUser: ICurrentUser, @Body() bodyParams: GetMetaTagDataBody ): Promise { - const result = await this.metaTagExtractorUc.fetchMetaData(currentUser.userId, bodyParams.url); + const result = await this.metaTagExtractorUc.getMetaData(currentUser.userId, bodyParams.url); const imageUrl = result.image?.url; const response = new MetaTagExtractorResponse({ ...result, imageUrl }); return response; diff --git a/apps/server/src/modules/meta-tag-extractor/controller/post-link-url.body.params.ts b/apps/server/src/modules/meta-tag-extractor/controller/post-link-url.body.params.ts index 1e9cd1f7f34..ac6baeebbe8 100644 --- a/apps/server/src/modules/meta-tag-extractor/controller/post-link-url.body.params.ts +++ b/apps/server/src/modules/meta-tag-extractor/controller/post-link-url.body.params.ts @@ -1,8 +1,8 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsUrl } from 'class-validator'; +import { IsString } from 'class-validator'; export class GetMetaTagDataBody { - @IsUrl() + @IsString() @ApiProperty({ required: true, nullable: false, diff --git a/apps/server/src/modules/meta-tag-extractor/interface/url-handler.ts b/apps/server/src/modules/meta-tag-extractor/interface/url-handler.ts new file mode 100644 index 00000000000..fc09d2cd40e --- /dev/null +++ b/apps/server/src/modules/meta-tag-extractor/interface/url-handler.ts @@ -0,0 +1,6 @@ +import { MetaData } from '../types'; + +export interface UrlHandler { + doesUrlMatch(url: string): boolean; + getMetaData(url: string): Promise; +} diff --git a/apps/server/src/modules/meta-tag-extractor/meta-tag-extractor-api.module.ts b/apps/server/src/modules/meta-tag-extractor/meta-tag-extractor-api.module.ts index d9095315e87..acc5eb31776 100644 --- a/apps/server/src/modules/meta-tag-extractor/meta-tag-extractor-api.module.ts +++ b/apps/server/src/modules/meta-tag-extractor/meta-tag-extractor-api.module.ts @@ -1,6 +1,6 @@ +import { AuthorizationModule } from '@modules/authorization'; import { forwardRef, Module } from '@nestjs/common'; import { LoggerModule } from '@src/core/logger'; -import { AuthorizationModule } from '@src/modules/authorization'; import { MetaTagExtractorController } from './controller'; import { MetaTagExtractorModule } from './meta-tag-extractor.module'; import { MetaTagExtractorUc } from './uc'; diff --git a/apps/server/src/modules/meta-tag-extractor/meta-tag-extractor.module.ts b/apps/server/src/modules/meta-tag-extractor/meta-tag-extractor.module.ts index a85e71c526f..d7a1e0faeff 100644 --- a/apps/server/src/modules/meta-tag-extractor/meta-tag-extractor.module.ts +++ b/apps/server/src/modules/meta-tag-extractor/meta-tag-extractor.module.ts @@ -5,20 +5,37 @@ import { ConsoleWriterModule } from '@infra/console'; import { createConfigModuleOptions } from '@src/config'; import { LoggerModule } from '@src/core/logger'; import { AuthenticationModule } from '../authentication/authentication.module'; +import { BoardModule } from '../board'; +import { LearnroomModule } from '../learnroom'; +import { LessonModule } from '../lesson'; +import { TaskModule } from '../task'; import { UserModule } from '../user'; import metaTagExtractorConfig from './meta-tag-extractor.config'; import { MetaTagExtractorService } from './service'; +import { MetaTagInternalUrlService } from './service/meta-tag-internal-url.service'; +import { BoardUrlHandler, CourseUrlHandler, LessonUrlHandler, TaskUrlHandler } from './service/url-handler'; @Module({ imports: [ AuthenticationModule, + BoardModule, ConsoleWriterModule, HttpModule, + LearnroomModule, + LessonModule, LoggerModule, + TaskModule, UserModule, ConfigModule.forRoot(createConfigModuleOptions(metaTagExtractorConfig)), ], - providers: [MetaTagExtractorService], + providers: [ + MetaTagExtractorService, + MetaTagInternalUrlService, + TaskUrlHandler, + LessonUrlHandler, + CourseUrlHandler, + BoardUrlHandler, + ], exports: [MetaTagExtractorService], }) export class MetaTagExtractorModule {} diff --git a/apps/server/src/modules/meta-tag-extractor/service/meta-tag-extractor.service.spec.ts b/apps/server/src/modules/meta-tag-extractor/service/meta-tag-extractor.service.spec.ts index af1a256d121..06fa3b09170 100644 --- a/apps/server/src/modules/meta-tag-extractor/service/meta-tag-extractor.service.spec.ts +++ b/apps/server/src/modules/meta-tag-extractor/service/meta-tag-extractor.service.spec.ts @@ -1,35 +1,52 @@ +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { Configuration } from '@hpi-schul-cloud/commons/lib'; import { Test, TestingModule } from '@nestjs/testing'; import { setupEntities } from '@shared/testing'; +import ogs from 'open-graph-scraper'; import { ImageObject } from 'open-graph-scraper/dist/lib/types'; import { MetaTagExtractorService } from './meta-tag-extractor.service'; +import { MetaTagInternalUrlService } from './meta-tag-internal-url.service'; -let ogsResponseMock = {}; -let ogsRejectMock: Error | undefined; - -jest.mock('open-graph-scraper', () => () => { - if (ogsRejectMock) { - return Promise.reject(ogsRejectMock); - } +jest.mock('open-graph-scraper', () => { + return { + __esModule: true, + default: jest.fn(), + }; +}); - return Promise.resolve({ +const mockOgsResolve = (result: Record) => { + const mockedOgs = ogs as jest.Mock; + mockedOgs.mockResolvedValueOnce({ error: false, html: '', response: {}, - result: ogsResponseMock, + result, }); -}); +}; + +const mockOgsReject = (error: Error) => { + const mockedOgs = ogs as jest.Mock; + mockedOgs.mockRejectedValueOnce(error); +}; describe(MetaTagExtractorService.name, () => { let module: TestingModule; + let metaTagInternalUrlService: DeepMocked; let service: MetaTagExtractorService; beforeAll(async () => { module = await Test.createTestingModule({ - providers: [MetaTagExtractorService], + providers: [ + MetaTagExtractorService, + { + provide: MetaTagInternalUrlService, + useValue: createMock(), + }, + ], }).compile(); + metaTagInternalUrlService = module.get(MetaTagInternalUrlService); service = module.get(MetaTagExtractorService); - await setupEntities(); }); @@ -38,8 +55,8 @@ describe(MetaTagExtractorService.name, () => { }); beforeEach(() => { - ogsResponseMock = {}; - ogsRejectMock = undefined; + Configuration.set('SC_DOMAIN', 'localhost'); + metaTagInternalUrlService.tryInternalLinkMetaTags.mockResolvedValue(undefined); }); afterEach(() => { @@ -48,26 +65,28 @@ describe(MetaTagExtractorService.name, () => { describe('create', () => { describe('when url points to webpage', () => { + it('should thrown an error if url is an empty string', async () => { + const url = ''; + + await expect(service.getMetaData(url)).rejects.toThrow(); + }); + it('should return also the original url', async () => { + const ogTitle = 'My Title'; const url = 'https://de.wikipedia.org'; + mockOgsResolve({ url, ogTitle }); - const result = await service.fetchMetaData(url); + const result = await service.getMetaData(url); expect(result).toEqual(expect.objectContaining({ url })); }); - it('should thrown an error if url is an empty string', async () => { - const url = ''; - - await expect(service.fetchMetaData(url)).rejects.toThrow(); - }); - it('should return ogTitle as title', async () => { const ogTitle = 'My Title'; const url = 'https://de.wikipedia.org'; - ogsResponseMock = { ogTitle }; + mockOgsResolve({ ogTitle }); - const result = await service.fetchMetaData(url); + const result = await service.getMetaData(url); expect(result).toEqual(expect.objectContaining({ title: ogTitle })); }); @@ -91,9 +110,9 @@ describe(MetaTagExtractorService.name, () => { }, ]; const url = 'https://de.wikipedia.org'; - ogsResponseMock = { ogImage }; + mockOgsResolve({ url, ogImage }); - const result = await service.fetchMetaData(url); + const result = await service.getMetaData(url); expect(result).toEqual(expect.objectContaining({ image: ogImage[1] })); }); @@ -102,9 +121,10 @@ describe(MetaTagExtractorService.name, () => { describe('when url points to a file', () => { it('should return filename as title', async () => { const url = 'https://de.wikipedia.org/abc.jpg'; - ogsRejectMock = new Error('no open graph data included... probably not a webpage'); - const result = await service.fetchMetaData(url); + mockOgsReject(new Error('no open graph data included... probably not a webpage')); + + const result = await service.getMetaData(url); expect(result).toEqual(expect.objectContaining({ title: 'abc.jpg' })); }); }); @@ -112,9 +132,10 @@ describe(MetaTagExtractorService.name, () => { describe('when url is invalid', () => { it('should return url as it is', async () => { const url = 'not-a-real-domain'; - ogsRejectMock = new Error('no open graph data included... probably not a webpage'); - const result = await service.fetchMetaData(url); + mockOgsReject(new Error('no open graph data included... probably not a webpage')); + + const result = await service.getMetaData(url); expect(result).toEqual(expect.objectContaining({ url, title: '', description: '' })); }); }); diff --git a/apps/server/src/modules/meta-tag-extractor/service/meta-tag-extractor.service.ts b/apps/server/src/modules/meta-tag-extractor/service/meta-tag-extractor.service.ts index 46c30c17702..d64c3a42a58 100644 --- a/apps/server/src/modules/meta-tag-extractor/service/meta-tag-extractor.service.ts +++ b/apps/server/src/modules/meta-tag-extractor/service/meta-tag-extractor.service.ts @@ -2,24 +2,29 @@ import { Injectable } from '@nestjs/common'; import ogs from 'open-graph-scraper'; import { ImageObject } from 'open-graph-scraper/dist/lib/types'; import { basename } from 'path'; - -export type MetaData = { - title: string; - description: string; - url: string; - image?: ImageObject; -}; +import type { MetaData } from '../types'; +import { MetaTagInternalUrlService } from './meta-tag-internal-url.service'; @Injectable() export class MetaTagExtractorService { - async fetchMetaData(url: string): Promise { + constructor(private readonly internalLinkMataTagService: MetaTagInternalUrlService) {} + + async getMetaData(url: string): Promise { if (url.length === 0) { throw new Error(`MetaTagExtractorService requires a valid URL. Given URL: ${url}`); } - const metaData = (await this.tryExtractMetaTags(url)) ?? this.tryFilenameAsFallback(url); + const metaData = + (await this.tryInternalLinkMetaTags(url)) ?? + (await this.tryExtractMetaTags(url)) ?? + this.tryFilenameAsFallback(url) ?? + this.getDefaultMetaData(url); + + return metaData; + } - return metaData ?? { url, title: '', description: '' }; + private async tryInternalLinkMetaTags(url: string): Promise { + return this.internalLinkMataTagService.tryInternalLinkMetaTags(url); } private async tryExtractMetaTags(url: string): Promise { @@ -35,6 +40,7 @@ export class MetaTagExtractorService { description, image, url, + type: 'external', }; } catch (error) { return undefined; @@ -49,12 +55,17 @@ export class MetaTagExtractorService { title, description: '', url, + type: 'unknown', }; } catch (error) { return undefined; } } + private getDefaultMetaData(url: string): MetaData { + return { url, title: '', description: '', type: 'unknown' }; + } + private pickImage(images: ImageObject[], minWidth = 400): ImageObject | undefined { const sortedImages = [...images]; sortedImages.sort((a, b) => (a.width && b.width ? Number(a.width) - Number(b.width) : 0)); diff --git a/apps/server/src/modules/meta-tag-extractor/service/meta-tag-internal-url.service.spec.ts b/apps/server/src/modules/meta-tag-extractor/service/meta-tag-internal-url.service.spec.ts new file mode 100644 index 00000000000..04d2f8b0c77 --- /dev/null +++ b/apps/server/src/modules/meta-tag-extractor/service/meta-tag-internal-url.service.spec.ts @@ -0,0 +1,136 @@ +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { Configuration } from '@hpi-schul-cloud/commons/lib'; +import { Test, TestingModule } from '@nestjs/testing'; +import { setupEntities } from '@shared/testing'; +import { MetaData } from '../types'; +import { MetaTagExtractorService } from './meta-tag-extractor.service'; +import { MetaTagInternalUrlService } from './meta-tag-internal-url.service'; +import { BoardUrlHandler, CourseUrlHandler, LessonUrlHandler, TaskUrlHandler } from './url-handler'; + +const INTERNAL_DOMAIN = 'my-school-cloud.org'; +const INTERNAL_URL = `https://${INTERNAL_DOMAIN}/my-article`; +const UNKNOWN_INTERNAL_URL = `https://${INTERNAL_DOMAIN}/playground/23hafe23234`; +const EXTERNAL_URL = 'https://de.wikipedia.org/example-article'; + +describe(MetaTagExtractorService.name, () => { + let module: TestingModule; + let taskUrlHandler: DeepMocked; + let lessonUrlHandler: DeepMocked; + let courseUrlHandler: DeepMocked; + let boardUrlHandler: DeepMocked; + let service: MetaTagInternalUrlService; + + beforeAll(async () => { + module = await Test.createTestingModule({ + providers: [ + MetaTagInternalUrlService, + { + provide: TaskUrlHandler, + useValue: createMock(), + }, + { + provide: LessonUrlHandler, + useValue: createMock(), + }, + { + provide: CourseUrlHandler, + useValue: createMock(), + }, + { + provide: BoardUrlHandler, + useValue: createMock(), + }, + ], + }).compile(); + + taskUrlHandler = module.get(TaskUrlHandler); + lessonUrlHandler = module.get(LessonUrlHandler); + courseUrlHandler = module.get(CourseUrlHandler); + boardUrlHandler = module.get(BoardUrlHandler); + service = module.get(MetaTagInternalUrlService); + await setupEntities(); + }); + + afterAll(async () => { + await module.close(); + }); + + beforeEach(() => {}); + + afterEach(() => { + jest.resetAllMocks(); + }); + + describe('isInternalUrl', () => { + const setup = () => { + Configuration.set('SC_DOMAIN', INTERNAL_DOMAIN); + taskUrlHandler.doesUrlMatch.mockReturnValueOnce(false); + lessonUrlHandler.doesUrlMatch.mockReturnValueOnce(false); + courseUrlHandler.doesUrlMatch.mockReturnValueOnce(false); + boardUrlHandler.doesUrlMatch.mockReturnValueOnce(false); + }; + + it('should return true for internal urls', () => { + setup(); + + expect(service.isInternalUrl(INTERNAL_URL)).toBe(true); + }); + + it('should return false for external urls', () => { + setup(); + + expect(service.isInternalUrl(EXTERNAL_URL)).toBe(false); + }); + }); + + describe('tryInternalLinkMetaTags', () => { + const setup = () => { + Configuration.set('SC_DOMAIN', INTERNAL_DOMAIN); + taskUrlHandler.doesUrlMatch.mockReturnValueOnce(false); + lessonUrlHandler.doesUrlMatch.mockReturnValueOnce(false); + boardUrlHandler.doesUrlMatch.mockReturnValueOnce(false); + const mockedMetaTags: MetaData = { + title: 'My Title', + url: INTERNAL_URL, + description: '', + type: 'course', + }; + + return { mockedMetaTags }; + }; + + describe('when url matches to a handler', () => { + it('should return the handlers meta tags', async () => { + const { mockedMetaTags } = setup(); + courseUrlHandler.doesUrlMatch.mockReturnValueOnce(true); + courseUrlHandler.getMetaData.mockResolvedValueOnce(mockedMetaTags); + + const result = await service.tryInternalLinkMetaTags(INTERNAL_URL); + + expect(result).toEqual(mockedMetaTags); + }); + }); + + describe('when url matches to none of the handlers', () => { + it('should return default meta tags', async () => { + setup(); + courseUrlHandler.doesUrlMatch.mockReturnValueOnce(false); + + const result = await service.tryInternalLinkMetaTags(UNKNOWN_INTERNAL_URL); + + expect(result).toEqual(expect.objectContaining({ type: 'unknown' })); + }); + }); + + describe('when url is external', () => { + it('should return undefined', async () => { + setup(); + courseUrlHandler.doesUrlMatch.mockReturnValueOnce(false); + + const result = await service.tryInternalLinkMetaTags(EXTERNAL_URL); + + expect(result).toBeUndefined(); + }); + }); + }); +}); diff --git a/apps/server/src/modules/meta-tag-extractor/service/meta-tag-internal-url.service.ts b/apps/server/src/modules/meta-tag-extractor/service/meta-tag-internal-url.service.ts new file mode 100644 index 00000000000..5c0d5efca5c --- /dev/null +++ b/apps/server/src/modules/meta-tag-extractor/service/meta-tag-internal-url.service.ts @@ -0,0 +1,51 @@ +import { Configuration } from '@hpi-schul-cloud/commons/lib'; +import { Injectable } from '@nestjs/common'; +import type { UrlHandler } from '../interface/url-handler'; +import { MetaData } from '../types'; +import { BoardUrlHandler, CourseUrlHandler, LessonUrlHandler, TaskUrlHandler } from './url-handler'; + +@Injectable() +export class MetaTagInternalUrlService { + private handlers: UrlHandler[] = []; + + constructor( + private readonly taskUrlHandler: TaskUrlHandler, + private readonly lessonUrlHandler: LessonUrlHandler, + private readonly courseUrlHandler: CourseUrlHandler, + private readonly boardUrlHandler: BoardUrlHandler + ) { + this.handlers = [this.taskUrlHandler, this.lessonUrlHandler, this.courseUrlHandler, this.boardUrlHandler]; + } + + async tryInternalLinkMetaTags(url: string): Promise { + if (this.isInternalUrl(url)) { + return this.composeMetaTags(url); + } + return Promise.resolve(undefined); + } + + isInternalUrl(url: string) { + let domain = Configuration.get('SC_DOMAIN') as string; + domain = domain === '' ? 'nothing-configured-for-internal-url.de' : domain; + const isInternal = url.toLowerCase().includes(domain.toLowerCase()); + return isInternal; + } + + private async composeMetaTags(url: string): Promise { + const urlObject = new URL(url); + + const handler = this.handlers.find((h) => h.doesUrlMatch(url)); + if (handler) { + const result = await handler.getMetaData(url); + return result; + } + + const title = urlObject.pathname; + return Promise.resolve({ + title, + description: '', + url, + type: 'unknown', + }); + } +} diff --git a/apps/server/src/modules/meta-tag-extractor/service/url-handler/abstract-url-handler.spec.ts b/apps/server/src/modules/meta-tag-extractor/service/url-handler/abstract-url-handler.spec.ts new file mode 100644 index 00000000000..b6900cfd492 --- /dev/null +++ b/apps/server/src/modules/meta-tag-extractor/service/url-handler/abstract-url-handler.spec.ts @@ -0,0 +1,59 @@ +import { AbstractUrlHandler } from './abstract-url-handler'; + +class DummyHandler extends AbstractUrlHandler { + patterns: RegExp[] = [/\/dummy\/([0-9a-z]+)$/i]; + + extractId(url: string): string | undefined { + return super.extractId(url); + } +} + +describe(AbstractUrlHandler.name, () => { + const setup = () => { + const id = 'af322312feae'; + const url = `https://localhost/dummy/${id}`; + const invalidUrl = `https://localhost/wrong/${id}`; + const handler = new DummyHandler(); + return { id, url, invalidUrl, handler }; + }; + + describe('extractId', () => { + describe('when no id was extracted', () => { + it('should return undefined', () => { + const { invalidUrl, handler } = setup(); + + const result = handler.extractId(invalidUrl); + + expect(result).toBeUndefined(); + }); + }); + }); + + describe('doesUrlMatch', () => { + it('should be true for valid urls', () => { + const { url, handler } = setup(); + + const result = handler.doesUrlMatch(url); + + expect(result).toBe(true); + }); + + it('should be false for invalid urls', () => { + const { invalidUrl, handler } = setup(); + + const result = handler.doesUrlMatch(invalidUrl); + + expect(result).toBe(false); + }); + }); + + describe('getDefaultMetaData', () => { + it('should return meta data of type unknown', () => { + const { url, handler } = setup(); + + const result = handler.getDefaultMetaData(url); + + expect(result).toEqual(expect.objectContaining({ type: 'unknown', url })); + }); + }); +}); diff --git a/apps/server/src/modules/meta-tag-extractor/service/url-handler/abstract-url-handler.ts b/apps/server/src/modules/meta-tag-extractor/service/url-handler/abstract-url-handler.ts new file mode 100644 index 00000000000..fb618c3bf36 --- /dev/null +++ b/apps/server/src/modules/meta-tag-extractor/service/url-handler/abstract-url-handler.ts @@ -0,0 +1,35 @@ +import { basename } from 'node:path'; +import { MetaData } from '../../types'; + +export abstract class AbstractUrlHandler { + protected abstract patterns: RegExp[]; + + protected extractId(url: string): string | undefined { + const results: RegExpMatchArray = this.patterns + .map((pattern: RegExp) => pattern.exec(url)) + .filter((result) => result !== null) + .find((result) => (result?.length ?? 0) >= 2) as RegExpMatchArray; + + if (results && results[1]) { + return results[1]; + } + return undefined; + } + + doesUrlMatch(url: string): boolean { + const doesMatch = this.patterns.some((pattern) => pattern.test(url)); + return doesMatch; + } + + getDefaultMetaData(url: string, partial: Partial = {}): MetaData { + const urlObject = new URL(url); + const title = basename(urlObject.pathname); + return { + title, + description: '', + url, + type: 'unknown', + ...partial, + }; + } +} diff --git a/apps/server/src/modules/meta-tag-extractor/service/url-handler/board-url-handler.spec.ts b/apps/server/src/modules/meta-tag-extractor/service/url-handler/board-url-handler.spec.ts new file mode 100644 index 00000000000..f7775b58f1f --- /dev/null +++ b/apps/server/src/modules/meta-tag-extractor/service/url-handler/board-url-handler.spec.ts @@ -0,0 +1,70 @@ +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { ColumnBoardService } from '@modules/board'; +import { CourseService } from '@modules/learnroom'; +import { Test, TestingModule } from '@nestjs/testing'; +import { ColumnBoard } from '@shared/domain'; +import { setupEntities } from '@shared/testing'; +import { BoardUrlHandler } from './board-url-handler'; + +describe(BoardUrlHandler.name, () => { + let module: TestingModule; + let columnBoardService: DeepMocked; + let boardUrlHandler: BoardUrlHandler; + + beforeAll(async () => { + module = await Test.createTestingModule({ + providers: [ + BoardUrlHandler, + { + provide: ColumnBoardService, + useValue: createMock(), + }, + { + provide: CourseService, + useValue: createMock(), + }, + ], + }).compile(); + + columnBoardService = module.get(ColumnBoardService); + boardUrlHandler = module.get(BoardUrlHandler); + await setupEntities(); + }); + + describe('getMetaData', () => { + describe('when url fits', () => { + it('should call courseService with the correct id', async () => { + const id = 'af322312feae'; + const url = `https://localhost/rooms/${id}/board`; + + await boardUrlHandler.getMetaData(url); + + expect(columnBoardService.findById).toHaveBeenCalledWith(id); + }); + + it('should take the title from the board name', async () => { + const id = 'af322312feae'; + const url = `https://localhost/rooms/${id}/board`; + const boardName = 'My Board'; + columnBoardService.findById.mockResolvedValue({ + title: boardName, + context: { type: 'course', id: 'a-board-id' }, + } as ColumnBoard); + + const result = await boardUrlHandler.getMetaData(url); + + expect(result).toEqual(expect.objectContaining({ title: boardName, type: 'board' })); + }); + }); + + describe('when url does not fit', () => { + it('should return undefined', async () => { + const url = `https://localhost/invalid/ef2345abe4e3b`; + + const result = await boardUrlHandler.getMetaData(url); + + expect(result).toBeUndefined(); + }); + }); + }); +}); diff --git a/apps/server/src/modules/meta-tag-extractor/service/url-handler/board-url-handler.ts b/apps/server/src/modules/meta-tag-extractor/service/url-handler/board-url-handler.ts new file mode 100644 index 00000000000..013631244dd --- /dev/null +++ b/apps/server/src/modules/meta-tag-extractor/service/url-handler/board-url-handler.ts @@ -0,0 +1,37 @@ +import { ColumnBoardService } from '@modules/board'; +import { CourseService } from '@modules/learnroom'; +import { Injectable } from '@nestjs/common'; +import { BoardExternalReferenceType } from '@shared/domain'; +import type { UrlHandler } from '../../interface/url-handler'; +import { MetaData } from '../../types'; +import { AbstractUrlHandler } from './abstract-url-handler'; + +@Injectable() +export class BoardUrlHandler extends AbstractUrlHandler implements UrlHandler { + patterns: RegExp[] = [/\/rooms\/(.*?)\/board\/?$/i]; + + constructor(private readonly columnBoardService: ColumnBoardService, private readonly courseService: CourseService) { + super(); + } + + async getMetaData(url: string): Promise { + const id = this.extractId(url); + if (id === undefined) { + return undefined; + } + + const metaData = this.getDefaultMetaData(url, { type: 'board' }); + + const columnBoard = await this.columnBoardService.findById(id); + if (columnBoard) { + metaData.title = columnBoard.title; + if (columnBoard.context.type === BoardExternalReferenceType.Course) { + const course = await this.courseService.findById(columnBoard.context.id); + metaData.parentType = 'course'; + metaData.parentTitle = course.name; + } + } + + return metaData; + } +} diff --git a/apps/server/src/modules/meta-tag-extractor/service/url-handler/course-url-handler.spec.ts b/apps/server/src/modules/meta-tag-extractor/service/url-handler/course-url-handler.spec.ts new file mode 100644 index 00000000000..75a43876de4 --- /dev/null +++ b/apps/server/src/modules/meta-tag-extractor/service/url-handler/course-url-handler.spec.ts @@ -0,0 +1,62 @@ +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { CourseService } from '@modules/learnroom'; +import { Test, TestingModule } from '@nestjs/testing'; +import { Course } from '@shared/domain'; +import { setupEntities } from '@shared/testing'; +import { CourseUrlHandler } from './course-url-handler'; + +describe(CourseUrlHandler.name, () => { + let module: TestingModule; + let courseService: DeepMocked; + let courseUrlHandler: CourseUrlHandler; + + beforeAll(async () => { + module = await Test.createTestingModule({ + providers: [ + CourseUrlHandler, + { + provide: CourseService, + useValue: createMock(), + }, + ], + }).compile(); + + courseService = module.get(CourseService); + courseUrlHandler = module.get(CourseUrlHandler); + await setupEntities(); + }); + + describe('getMetaData', () => { + describe('when url fits', () => { + it('should call courseService with the correct id', async () => { + const id = 'af322312feae'; + const url = `https://localhost/rooms/${id}`; + + await courseUrlHandler.getMetaData(url); + + expect(courseService.findById).toHaveBeenCalledWith(id); + }); + + it('should take the title from the course name', async () => { + const id = 'af322312feae'; + const url = `https://localhost/rooms/${id}`; + const courseName = 'My Course'; + courseService.findById.mockResolvedValue({ name: courseName } as Course); + + const result = await courseUrlHandler.getMetaData(url); + + expect(result).toEqual(expect.objectContaining({ title: courseName, type: 'course' })); + }); + }); + + describe('when url does not fit', () => { + it('should return undefined', async () => { + const url = `https://localhost/invalid/ef2345abe4e3b`; + + const result = await courseUrlHandler.getMetaData(url); + + expect(result).toBeUndefined(); + }); + }); + }); +}); diff --git a/apps/server/src/modules/meta-tag-extractor/service/url-handler/course-url-handler.ts b/apps/server/src/modules/meta-tag-extractor/service/url-handler/course-url-handler.ts new file mode 100644 index 00000000000..def041886f1 --- /dev/null +++ b/apps/server/src/modules/meta-tag-extractor/service/url-handler/course-url-handler.ts @@ -0,0 +1,29 @@ +import { CourseService } from '@modules/learnroom'; +import { Injectable } from '@nestjs/common'; +import type { UrlHandler } from '../../interface/url-handler'; +import { MetaData } from '../../types'; +import { AbstractUrlHandler } from './abstract-url-handler'; + +@Injectable() +export class CourseUrlHandler extends AbstractUrlHandler implements UrlHandler { + patterns: RegExp[] = [/\/rooms\/([0-9a-z]+)$/i]; + + constructor(private readonly courseService: CourseService) { + super(); + } + + async getMetaData(url: string): Promise { + const id = this.extractId(url); + if (id === undefined) { + return undefined; + } + + const metaData = this.getDefaultMetaData(url, { type: 'course' }); + const course = await this.courseService.findById(id); + if (course) { + metaData.title = course.name; + } + + return metaData; + } +} diff --git a/apps/server/src/modules/meta-tag-extractor/service/url-handler/index.ts b/apps/server/src/modules/meta-tag-extractor/service/url-handler/index.ts new file mode 100644 index 00000000000..a29b8401da2 --- /dev/null +++ b/apps/server/src/modules/meta-tag-extractor/service/url-handler/index.ts @@ -0,0 +1,4 @@ +export * from './board-url-handler'; +export * from './course-url-handler'; +export * from './lesson-url-handler'; +export * from './task-url-handler'; diff --git a/apps/server/src/modules/meta-tag-extractor/service/url-handler/lesson-url-handler.spec.ts b/apps/server/src/modules/meta-tag-extractor/service/url-handler/lesson-url-handler.spec.ts new file mode 100644 index 00000000000..53b59d86ab7 --- /dev/null +++ b/apps/server/src/modules/meta-tag-extractor/service/url-handler/lesson-url-handler.spec.ts @@ -0,0 +1,62 @@ +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { LessonService } from '@modules/lesson'; +import { Test, TestingModule } from '@nestjs/testing'; +import { LessonEntity } from '@shared/domain'; +import { setupEntities } from '@shared/testing'; +import { LessonUrlHandler } from './lesson-url-handler'; + +describe(LessonUrlHandler.name, () => { + let module: TestingModule; + let lessonService: DeepMocked; + let lessonUrlHandler: LessonUrlHandler; + + beforeAll(async () => { + module = await Test.createTestingModule({ + providers: [ + LessonUrlHandler, + { + provide: LessonService, + useValue: createMock(), + }, + ], + }).compile(); + + lessonService = module.get(LessonService); + lessonUrlHandler = module.get(LessonUrlHandler); + await setupEntities(); + }); + + describe('getMetaData', () => { + describe('when url fits', () => { + it('should call lessonService with the correct id', async () => { + const id = 'af322312feae'; + const url = `https://localhost/topics/${id}`; + + await lessonUrlHandler.getMetaData(url); + + expect(lessonService.findById).toHaveBeenCalledWith(id); + }); + + it('should take the title from the lessons name', async () => { + const id = 'af322312feae'; + const url = `https://localhost/topics/${id}`; + const lessonName = 'My lesson'; + lessonService.findById.mockResolvedValue({ name: lessonName } as LessonEntity); + + const result = await lessonUrlHandler.getMetaData(url); + + expect(result).toEqual(expect.objectContaining({ title: lessonName, type: 'lesson' })); + }); + }); + + describe('when url does not fit', () => { + it('should return undefined', async () => { + const url = `https://localhost/invalid/ef2345abe4e3b`; + + const result = await lessonUrlHandler.getMetaData(url); + + expect(result).toBeUndefined(); + }); + }); + }); +}); diff --git a/apps/server/src/modules/meta-tag-extractor/service/url-handler/lesson-url-handler.ts b/apps/server/src/modules/meta-tag-extractor/service/url-handler/lesson-url-handler.ts new file mode 100644 index 00000000000..c5264020a50 --- /dev/null +++ b/apps/server/src/modules/meta-tag-extractor/service/url-handler/lesson-url-handler.ts @@ -0,0 +1,29 @@ +import { LessonService } from '@modules/lesson'; +import { Injectable } from '@nestjs/common'; +import type { UrlHandler } from '../../interface/url-handler'; +import { MetaData } from '../../types'; +import { AbstractUrlHandler } from './abstract-url-handler'; + +@Injectable() +export class LessonUrlHandler extends AbstractUrlHandler implements UrlHandler { + patterns: RegExp[] = [/\/topics\/([0-9a-z]+)$/i]; + + constructor(private readonly lessonService: LessonService) { + super(); + } + + async getMetaData(url: string): Promise { + const id = this.extractId(url); + if (id === undefined) { + return undefined; + } + + const metaData = this.getDefaultMetaData(url, { type: 'lesson' }); + const lesson = await this.lessonService.findById(id); + if (lesson) { + metaData.title = lesson.name; + } + + return metaData; + } +} diff --git a/apps/server/src/modules/meta-tag-extractor/service/url-handler/task-url-handler.spec.ts b/apps/server/src/modules/meta-tag-extractor/service/url-handler/task-url-handler.spec.ts new file mode 100644 index 00000000000..0423382f2a8 --- /dev/null +++ b/apps/server/src/modules/meta-tag-extractor/service/url-handler/task-url-handler.spec.ts @@ -0,0 +1,62 @@ +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { TaskService } from '@modules/task'; +import { Test, TestingModule } from '@nestjs/testing'; +import { Task } from '@shared/domain'; +import { setupEntities } from '@shared/testing'; +import { TaskUrlHandler } from './task-url-handler'; + +describe(TaskUrlHandler.name, () => { + let module: TestingModule; + let taskService: DeepMocked; + let taskUrlHandler: TaskUrlHandler; + + beforeAll(async () => { + module = await Test.createTestingModule({ + providers: [ + TaskUrlHandler, + { + provide: TaskService, + useValue: createMock(), + }, + ], + }).compile(); + + taskService = module.get(TaskService); + taskUrlHandler = module.get(TaskUrlHandler); + await setupEntities(); + }); + + describe('getMetaData', () => { + describe('when url fits', () => { + it('should call taskService with the correct id', async () => { + const id = 'af322312feae'; + const url = `https://localhost/homework/${id}`; + + await taskUrlHandler.getMetaData(url); + + expect(taskService.findById).toHaveBeenCalledWith(id); + }); + + it('should take the title from the tasks name', async () => { + const id = 'af322312feae'; + const url = `https://localhost/homework/${id}`; + const taskName = 'My Task'; + taskService.findById.mockResolvedValue({ name: taskName } as Task); + + const result = await taskUrlHandler.getMetaData(url); + + expect(result).toEqual(expect.objectContaining({ title: taskName, type: 'task' })); + }); + }); + + describe('when url does not fit', () => { + it('should return undefined', async () => { + const url = `https://localhost/invalid/ef2345abe4e3b`; + + const result = await taskUrlHandler.getMetaData(url); + + expect(result).toBeUndefined(); + }); + }); + }); +}); diff --git a/apps/server/src/modules/meta-tag-extractor/service/url-handler/task-url-handler.ts b/apps/server/src/modules/meta-tag-extractor/service/url-handler/task-url-handler.ts new file mode 100644 index 00000000000..cb1cec86048 --- /dev/null +++ b/apps/server/src/modules/meta-tag-extractor/service/url-handler/task-url-handler.ts @@ -0,0 +1,29 @@ +import { TaskService } from '@modules/task'; +import { Injectable } from '@nestjs/common'; +import type { UrlHandler } from '../../interface/url-handler'; +import { MetaData } from '../../types'; +import { AbstractUrlHandler } from './abstract-url-handler'; + +@Injectable() +export class TaskUrlHandler extends AbstractUrlHandler implements UrlHandler { + patterns: RegExp[] = [/\/homework\/([0-9a-z]+)$/i]; + + constructor(private readonly taskService: TaskService) { + super(); + } + + async getMetaData(url: string): Promise { + const id = this.extractId(url); + if (id === undefined) { + return undefined; + } + + const metaData = this.getDefaultMetaData(url, { type: 'task' }); + const task = await this.taskService.findById(id); + if (task) { + metaData.title = task.name; + } + + return metaData; + } +} diff --git a/apps/server/src/modules/meta-tag-extractor/types/index.ts b/apps/server/src/modules/meta-tag-extractor/types/index.ts new file mode 100644 index 00000000000..776e417867e --- /dev/null +++ b/apps/server/src/modules/meta-tag-extractor/types/index.ts @@ -0,0 +1 @@ +export * from './meta-data.type'; diff --git a/apps/server/src/modules/meta-tag-extractor/types/meta-data.type.ts b/apps/server/src/modules/meta-tag-extractor/types/meta-data.type.ts new file mode 100644 index 00000000000..b4da460d6e9 --- /dev/null +++ b/apps/server/src/modules/meta-tag-extractor/types/meta-data.type.ts @@ -0,0 +1,13 @@ +import { ImageObject } from 'open-graph-scraper/dist/lib/types'; + +export type MetaDataEntityType = 'external' | 'course' | 'board' | 'task' | 'lesson' | 'unknown'; + +export type MetaData = { + title: string; + description: string; + url: string; + image?: ImageObject; + type: MetaDataEntityType; + parentTitle?: string; + parentType?: MetaDataEntityType; +}; diff --git a/apps/server/src/modules/meta-tag-extractor/uc/meta-tag-extractor.uc.spec.ts b/apps/server/src/modules/meta-tag-extractor/uc/meta-tag-extractor.uc.spec.ts index 118b7d82633..f5aa0c6cd72 100644 --- a/apps/server/src/modules/meta-tag-extractor/uc/meta-tag-extractor.uc.spec.ts +++ b/apps/server/src/modules/meta-tag-extractor/uc/meta-tag-extractor.uc.spec.ts @@ -1,8 +1,8 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { AuthorizationService } from '@modules/authorization'; import { UnauthorizedException } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { setupEntities, userFactory } from '@shared/testing'; -import { AuthorizationService } from '@src/modules/authorization'; import { MetaTagExtractorService } from '../service'; import { MetaTagExtractorUc } from './meta-tag-extractor.uc'; @@ -42,7 +42,7 @@ describe(MetaTagExtractorUc.name, () => { jest.resetAllMocks(); }); - describe('fetchMetaData', () => { + describe('getMetaData', () => { describe('when user exists', () => { const setup = () => { const user = userFactory.build(); @@ -57,7 +57,7 @@ describe(MetaTagExtractorUc.name, () => { authorizationService.getUserWithPermissions.mockResolvedValueOnce(user); const url = 'https://www.example.com/great-example'; - await uc.fetchMetaData(user.id, url); + await uc.getMetaData(user.id, url); expect(authorizationService.getUserWithPermissions).toHaveBeenCalledWith(user.id); }); @@ -66,9 +66,9 @@ describe(MetaTagExtractorUc.name, () => { const { user } = setup(); const url = 'https://www.example.com/great-example'; - await uc.fetchMetaData(user.id, url); + await uc.getMetaData(user.id, url); - expect(metaTagExtractorService.fetchMetaData).toHaveBeenCalledWith(url); + expect(metaTagExtractorService.getMetaData).toHaveBeenCalledWith(url); }); }); @@ -84,7 +84,7 @@ describe(MetaTagExtractorUc.name, () => { const { user } = setup(); const url = 'https://www.example.com/great-example'; - await expect(uc.fetchMetaData(user.id, url)).rejects.toThrow(UnauthorizedException); + await expect(uc.getMetaData(user.id, url)).rejects.toThrow(UnauthorizedException); }); }); }); diff --git a/apps/server/src/modules/meta-tag-extractor/uc/meta-tag-extractor.uc.ts b/apps/server/src/modules/meta-tag-extractor/uc/meta-tag-extractor.uc.ts index 5daca6c962d..47ac53d88b0 100644 --- a/apps/server/src/modules/meta-tag-extractor/uc/meta-tag-extractor.uc.ts +++ b/apps/server/src/modules/meta-tag-extractor/uc/meta-tag-extractor.uc.ts @@ -1,7 +1,8 @@ +import { AuthorizationService } from '@modules/authorization'; import { Injectable, UnauthorizedException } from '@nestjs/common'; import { EntityId } from '@shared/domain'; -import { AuthorizationService } from '@src/modules/authorization'; -import { MetaData, MetaTagExtractorService } from '../service'; +import { MetaTagExtractorService } from '../service'; +import { MetaData } from '../types'; @Injectable() export class MetaTagExtractorUc { @@ -10,14 +11,14 @@ export class MetaTagExtractorUc { private readonly metaTagExtractorService: MetaTagExtractorService ) {} - async fetchMetaData(userId: EntityId, url: string): Promise { + async getMetaData(userId: EntityId, url: string): Promise { try { await this.authorizationService.getUserWithPermissions(userId); } catch (error) { throw new UnauthorizedException(); } - const result = await this.metaTagExtractorService.fetchMetaData(url); + const result = await this.metaTagExtractorService.getMetaData(url); return result; } } From caf63ead12afd27c6a76bff90efe3f61927d0abc Mon Sep 17 00:00:00 2001 From: mrikallab <93978883+mrikallab@users.noreply.github.com> Date: Mon, 20 Nov 2023 16:39:49 +0100 Subject: [PATCH 09/10] N21-1319 ctl showing tool usage (#4543) * N21-1319 externaltool metadata * N21-1319 schoolexternaltool metadata * N21-1319 add ExternalToolMetadataService * N21-1319 add SchoolExternalToolMetadataService * N21-1319 loggable --- .../tool/common/mapper/tool-context.mapper.ts | 9 ++ .../controller/api-test/tool.api.spec.ts | 95 +++++++++++- .../external-tool-metadata.response.ts | 20 +++ .../controller/dto/response/index.ts | 1 + .../controller/tool.controller.ts | 28 +++- .../domain/external-tool-metadata.ts | 10 ++ .../tool/external-tool/domain/index.ts | 1 + .../external-tool/external-tool.module.ts | 7 + .../mapper/external-tool-metadata.mapper.ts | 13 ++ .../tool/external-tool/mapper/index.ts | 1 + .../external-tool-metadata.service.spec.ts | 145 ++++++++++++++++++ .../service/external-tool-metadata.service.ts | 50 ++++++ .../tool/external-tool/service/index.ts | 1 + .../external-tool/uc/external-tool.uc.spec.ts | 127 ++++++++++++++- .../tool/external-tool/uc/external-tool.uc.ts | 21 ++- .../api-test/tool-school.api.spec.ts | 83 +++++++++- .../controller/dto/index.ts | 1 + .../school-external-tool-metadata.response.ts | 16 ++ .../controller/tool-school.controller.ts | 29 +++- .../tool/school-external-tool/domain/index.ts | 1 + .../domain/school-external-tool-metadata.ts | 7 + .../tool/school-external-tool/mapper/index.ts | 1 + .../school-external-tool-metadata.mapper.ts | 14 ++ .../school-external-tool.module.ts | 10 +- .../school-external-tool/service/index.ts | 1 + ...ool-external-tool-metadata.service.spec.ts | 93 +++++++++++ .../school-external-tool-metadata.service.ts | 37 +++++ .../uc/school-external-tool.uc.spec.ts | 64 +++++++- .../uc/school-external-tool.uc.ts | 25 ++- ...ext-external-tool.repo.integration.spec.ts | 96 +++++++++++- .../context-external-tool.repo.ts | 8 + .../school-external-tool.repo.ts | 1 + backup/setup/school-external-tools.json | 23 +++ 33 files changed, 1010 insertions(+), 29 deletions(-) create mode 100644 apps/server/src/modules/tool/common/mapper/tool-context.mapper.ts create mode 100644 apps/server/src/modules/tool/external-tool/controller/dto/response/external-tool-metadata.response.ts create mode 100644 apps/server/src/modules/tool/external-tool/domain/external-tool-metadata.ts create mode 100644 apps/server/src/modules/tool/external-tool/mapper/external-tool-metadata.mapper.ts create mode 100644 apps/server/src/modules/tool/external-tool/service/external-tool-metadata.service.spec.ts create mode 100644 apps/server/src/modules/tool/external-tool/service/external-tool-metadata.service.ts create mode 100644 apps/server/src/modules/tool/school-external-tool/controller/dto/school-external-tool-metadata.response.ts create mode 100644 apps/server/src/modules/tool/school-external-tool/domain/school-external-tool-metadata.ts create mode 100644 apps/server/src/modules/tool/school-external-tool/mapper/school-external-tool-metadata.mapper.ts create mode 100644 apps/server/src/modules/tool/school-external-tool/service/school-external-tool-metadata.service.spec.ts create mode 100644 apps/server/src/modules/tool/school-external-tool/service/school-external-tool-metadata.service.ts diff --git a/apps/server/src/modules/tool/common/mapper/tool-context.mapper.ts b/apps/server/src/modules/tool/common/mapper/tool-context.mapper.ts new file mode 100644 index 00000000000..183da9cdbbd --- /dev/null +++ b/apps/server/src/modules/tool/common/mapper/tool-context.mapper.ts @@ -0,0 +1,9 @@ +import { ToolContextType } from '../enum'; +import { ContextExternalToolType } from '../../context-external-tool/entity'; + +export class ToolContextMapper { + static contextMapping: Record = { + [ToolContextType.COURSE]: ContextExternalToolType.COURSE, + [ToolContextType.BOARD_ELEMENT]: ContextExternalToolType.BOARD_ELEMENT, + }; +} diff --git a/apps/server/src/modules/tool/external-tool/controller/api-test/tool.api.spec.ts b/apps/server/src/modules/tool/external-tool/controller/api-test/tool.api.spec.ts index ebff659529d..75dc1229231 100644 --- a/apps/server/src/modules/tool/external-tool/controller/api-test/tool.api.spec.ts +++ b/apps/server/src/modules/tool/external-tool/controller/api-test/tool.api.spec.ts @@ -1,16 +1,19 @@ import { Loaded } from '@mikro-orm/core'; import { EntityManager, ObjectId } from '@mikro-orm/mongodb'; +import { ServerTestModule } from '@modules/server'; import { HttpStatus, INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; -import { Permission } from '@shared/domain'; +import { Permission, SchoolEntity } from '@shared/domain'; import { cleanupCollections, + contextExternalToolEntityFactory, externalToolEntityFactory, externalToolFactory, + schoolExternalToolEntityFactory, + schoolFactory, TestApiClient, UserAndAccountTestFactory, } from '@shared/testing'; -import { ServerTestModule } from '@modules/server'; import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; import { Response } from 'supertest'; @@ -20,8 +23,16 @@ import { CustomParameterTypeParams, ToolConfigType, } from '../../../common/enum'; +import { ContextExternalToolEntity, ContextExternalToolType } from '../../../context-external-tool/entity'; +import { SchoolExternalToolEntity } from '../../../school-external-tool/entity'; +import { ExternalToolMetadata } from '../../domain'; import { ExternalToolEntity } from '../../entity'; -import { ExternalToolCreateParams, ExternalToolResponse, ExternalToolSearchListResponse } from '../dto'; +import { + ExternalToolCreateParams, + ExternalToolResponse, + ExternalToolSearchListResponse, + ExternalToolMetadataResponse, +} from '../dto'; describe('ToolController (API)', () => { let app: INestApplication; @@ -617,4 +628,82 @@ describe('ToolController (API)', () => { }); }); }); + + describe('[GET] tools/external-tools/:externalToolId/metadata', () => { + describe('when user is not authenticated', () => { + const setup = () => { + const toolId: string = new ObjectId().toHexString(); + + return { toolId }; + }; + + it('should return unauthorized', async () => { + const { toolId } = setup(); + + const response: Response = await testApiClient.get(`${toolId}/metadata`); + + expect(response.statusCode).toEqual(HttpStatus.UNAUTHORIZED); + }); + }); + + describe('when externalToolId is given ', () => { + const setup = async () => { + const toolId: string = new ObjectId().toHexString(); + const externalToolEntity: ExternalToolEntity = externalToolEntityFactory.buildWithId(undefined, toolId); + + const school: SchoolEntity = schoolFactory.buildWithId(); + const schoolExternalToolEntitys: SchoolExternalToolEntity[] = schoolExternalToolEntityFactory.buildList(2, { + tool: externalToolEntity, + school, + }); + + const courseTools: ContextExternalToolEntity[] = contextExternalToolEntityFactory.buildList(3, { + schoolTool: schoolExternalToolEntitys[0], + contextType: ContextExternalToolType.COURSE, + }); + + const boardTools: ContextExternalToolEntity[] = contextExternalToolEntityFactory.buildList(2, { + schoolTool: schoolExternalToolEntitys[1], + contextType: ContextExternalToolType.BOARD_ELEMENT, + contextId: new ObjectId().toHexString(), + }); + + const externalToolMetadata: ExternalToolMetadata = new ExternalToolMetadata({ + schoolExternalToolCount: 2, + contextExternalToolCountPerContext: { course: 3, boardElement: 2 }, + }); + + const { adminUser, adminAccount } = UserAndAccountTestFactory.buildAdmin({}, [Permission.TOOL_ADMIN]); + await em.persistAndFlush([ + adminAccount, + adminUser, + school, + externalToolEntity, + ...schoolExternalToolEntitys, + ...courseTools, + ...boardTools, + ]); + em.clear(); + + const loggedInClient: TestApiClient = await testApiClient.login(adminAccount); + + return { loggedInClient, toolId, externalToolEntity, externalToolMetadata }; + }; + + it('should return the metadata of externalTool', async () => { + const { loggedInClient, externalToolEntity } = await setup(); + + const response: Response = await loggedInClient.get(`${externalToolEntity.id}/metadata`); + + expect(response.statusCode).toEqual(HttpStatus.OK); + expect(response.body).toEqual({ + schoolExternalToolCount: 2, + contextExternalToolCountPerContext: { + course: 3, + boardElement: 2, + }, + }); + }); + }); + }); }); diff --git a/apps/server/src/modules/tool/external-tool/controller/dto/response/external-tool-metadata.response.ts b/apps/server/src/modules/tool/external-tool/controller/dto/response/external-tool-metadata.response.ts new file mode 100644 index 00000000000..d38b48cc503 --- /dev/null +++ b/apps/server/src/modules/tool/external-tool/controller/dto/response/external-tool-metadata.response.ts @@ -0,0 +1,20 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { ContextExternalToolType } from '../../../../context-external-tool/entity'; + +export class ExternalToolMetadataResponse { + @ApiProperty() + schoolExternalToolCount: number; + + @ApiProperty({ + type: 'object', + properties: Object.fromEntries( + Object.values(ContextExternalToolType).map((key: ContextExternalToolType) => [key, { type: 'number' }]) + ), + }) + contextExternalToolCountPerContext: Record; + + constructor(externalToolMetadataResponse: ExternalToolMetadataResponse) { + this.schoolExternalToolCount = externalToolMetadataResponse.schoolExternalToolCount; + this.contextExternalToolCountPerContext = externalToolMetadataResponse.contextExternalToolCountPerContext; + } +} diff --git a/apps/server/src/modules/tool/external-tool/controller/dto/response/index.ts b/apps/server/src/modules/tool/external-tool/controller/dto/response/index.ts index e9e5fafa376..4f532238863 100644 --- a/apps/server/src/modules/tool/external-tool/controller/dto/response/index.ts +++ b/apps/server/src/modules/tool/external-tool/controller/dto/response/index.ts @@ -6,3 +6,4 @@ export * from './context-external-tool-configuration-template.response'; export * from './context-external-tool-configuration-template-list.response'; export * from './school-external-tool-configuration-template.response'; export * from './school-external-tool-configuration-template-list.response'; +export * from './external-tool-metadata.response'; diff --git a/apps/server/src/modules/tool/external-tool/controller/tool.controller.ts b/apps/server/src/modules/tool/external-tool/controller/tool.controller.ts index 80139a586cb..448be593367 100644 --- a/apps/server/src/modules/tool/external-tool/controller/tool.controller.ts +++ b/apps/server/src/modules/tool/external-tool/controller/tool.controller.ts @@ -17,9 +17,10 @@ import { LegacyLogger } from '@src/core/logger'; import { Authenticate, CurrentUser, ICurrentUser } from '@modules/authentication'; import { Response } from 'express'; import { ExternalToolSearchQuery } from '../../common/interface'; -import { ExternalTool } from '../domain'; +import { ExternalTool, ExternalToolMetadata } from '../domain'; import { ExternalToolLogo } from '../domain/external-tool-logo'; -import { ExternalToolRequestMapper, ExternalToolResponseMapper } from '../mapper'; + +import { ExternalToolRequestMapper, ExternalToolResponseMapper, ExternalToolMetadataMapper } from '../mapper'; import { ExternalToolLogoService } from '../service'; import { ExternalToolCreate, ExternalToolUc, ExternalToolUpdate } from '../uc'; import { @@ -30,6 +31,7 @@ import { ExternalToolSearchParams, ExternalToolUpdateParams, SortExternalToolParams, + ExternalToolMetadataResponse, } from './dto'; @ApiTags('Tool') @@ -165,4 +167,26 @@ export class ToolController { res.setHeader('Cache-Control', 'must-revalidate'); res.send(externalToolLogo.logo); } + + @Get('/:externalToolId/metadata') + @ApiOperation({ summary: 'Gets the metadata of an external tool.' }) + @ApiOkResponse({ + description: 'Metadata of external tool fetched successfully.', + type: ExternalToolMetadataResponse, + }) + @ApiUnauthorizedResponse({ description: 'User is not logged in.' }) + async getMetaDataForExternalTool( + @CurrentUser() currentUser: ICurrentUser, + @Param() params: ExternalToolIdParams + ): Promise { + const externalToolMetadata: ExternalToolMetadata = await this.externalToolUc.getMetadataForExternalTool( + currentUser.userId, + params.externalToolId + ); + + const mapped: ExternalToolMetadataResponse = + ExternalToolMetadataMapper.mapToExternalToolMetadataResponse(externalToolMetadata); + + return mapped; + } } diff --git a/apps/server/src/modules/tool/external-tool/domain/external-tool-metadata.ts b/apps/server/src/modules/tool/external-tool/domain/external-tool-metadata.ts new file mode 100644 index 00000000000..04492680ff7 --- /dev/null +++ b/apps/server/src/modules/tool/external-tool/domain/external-tool-metadata.ts @@ -0,0 +1,10 @@ +export class ExternalToolMetadata { + schoolExternalToolCount: number; + + contextExternalToolCountPerContext: Record; + + constructor(externalToolMetadata: ExternalToolMetadata) { + this.schoolExternalToolCount = externalToolMetadata.schoolExternalToolCount; + this.contextExternalToolCountPerContext = externalToolMetadata.contextExternalToolCountPerContext; + } +} diff --git a/apps/server/src/modules/tool/external-tool/domain/index.ts b/apps/server/src/modules/tool/external-tool/domain/index.ts index e5a1dab735d..61fca0d2bfe 100644 --- a/apps/server/src/modules/tool/external-tool/domain/index.ts +++ b/apps/server/src/modules/tool/external-tool/domain/index.ts @@ -1,2 +1,3 @@ export * from './external-tool.do'; export * from './config'; +export * from './external-tool-metadata'; diff --git a/apps/server/src/modules/tool/external-tool/external-tool.module.ts b/apps/server/src/modules/tool/external-tool/external-tool.module.ts index 9aa0a11e448..a84734fecb2 100644 --- a/apps/server/src/modules/tool/external-tool/external-tool.module.ts +++ b/apps/server/src/modules/tool/external-tool/external-tool.module.ts @@ -5,6 +5,8 @@ import { OauthProviderServiceModule } from '@infra/oauth-provider'; import { EncryptionModule } from '@infra/encryption'; import { ExternalToolRepo } from '@shared/repo'; import { ToolConfigModule } from '../tool-config.module'; +import { ExternalToolMetadataMapper } from './mapper'; +import { ToolContextMapper } from '../common/mapper/tool-context.mapper'; import { ExternalToolConfigurationService, ExternalToolLogoService, @@ -13,6 +15,7 @@ import { ExternalToolServiceMapper, ExternalToolValidationService, ExternalToolVersionIncrementService, + ExternalToolMetadataService, } from './service'; import { CommonToolModule } from '../common'; @@ -27,6 +30,9 @@ import { CommonToolModule } from '../common'; ExternalToolConfigurationService, ExternalToolLogoService, ExternalToolRepo, + ExternalToolMetadataService, + ExternalToolMetadataMapper, + ToolContextMapper, ], exports: [ ExternalToolService, @@ -34,6 +40,7 @@ import { CommonToolModule } from '../common'; ExternalToolVersionIncrementService, ExternalToolConfigurationService, ExternalToolLogoService, + ExternalToolMetadataService, ], }) export class ExternalToolModule {} diff --git a/apps/server/src/modules/tool/external-tool/mapper/external-tool-metadata.mapper.ts b/apps/server/src/modules/tool/external-tool/mapper/external-tool-metadata.mapper.ts new file mode 100644 index 00000000000..b3d6555f898 --- /dev/null +++ b/apps/server/src/modules/tool/external-tool/mapper/external-tool-metadata.mapper.ts @@ -0,0 +1,13 @@ +import { ExternalToolMetadataResponse } from '../controller/dto'; +import { ExternalToolMetadata } from '../domain'; + +export class ExternalToolMetadataMapper { + static mapToExternalToolMetadataResponse(externalToolMetadata: ExternalToolMetadata): ExternalToolMetadataResponse { + const externalToolMetadataResponse: ExternalToolMetadataResponse = new ExternalToolMetadataResponse({ + schoolExternalToolCount: externalToolMetadata.schoolExternalToolCount, + contextExternalToolCountPerContext: externalToolMetadata.contextExternalToolCountPerContext, + }); + + return externalToolMetadataResponse; + } +} diff --git a/apps/server/src/modules/tool/external-tool/mapper/index.ts b/apps/server/src/modules/tool/external-tool/mapper/index.ts index 4149a17a519..73fdb05cf5a 100644 --- a/apps/server/src/modules/tool/external-tool/mapper/index.ts +++ b/apps/server/src/modules/tool/external-tool/mapper/index.ts @@ -1,2 +1,3 @@ export * from './external-tool-request.mapper'; export * from './external-tool-response.mapper'; +export * from './external-tool-metadata.mapper'; diff --git a/apps/server/src/modules/tool/external-tool/service/external-tool-metadata.service.spec.ts b/apps/server/src/modules/tool/external-tool/service/external-tool-metadata.service.spec.ts new file mode 100644 index 00000000000..4753cc805f4 --- /dev/null +++ b/apps/server/src/modules/tool/external-tool/service/external-tool-metadata.service.spec.ts @@ -0,0 +1,145 @@ +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { ObjectId } from '@mikro-orm/mongodb'; +import { Test, TestingModule } from '@nestjs/testing'; +import { ContextExternalToolRepo, SchoolExternalToolRepo } from '@shared/repo'; +import { externalToolFactory, legacySchoolDoFactory, schoolExternalToolFactory } from '@shared/testing'; +import { ContextExternalToolType } from '../../context-external-tool/entity'; +import { SchoolExternalTool } from '../../school-external-tool/domain'; +import { ExternalTool, ExternalToolMetadata } from '../domain'; +import { ExternalToolMetadataService } from './external-tool-metadata.service'; + +describe('ExternalToolMetadataService', () => { + let module: TestingModule; + let service: ExternalToolMetadataService; + + let schoolExternalToolRepo: DeepMocked; + let contextExternalToolRepo: DeepMocked; + + beforeAll(async () => { + module = await Test.createTestingModule({ + providers: [ + ExternalToolMetadataService, + { + provide: SchoolExternalToolRepo, + useValue: createMock(), + }, + { + provide: ContextExternalToolRepo, + useValue: createMock(), + }, + ], + }).compile(); + + service = module.get(ExternalToolMetadataService); + schoolExternalToolRepo = module.get(SchoolExternalToolRepo); + contextExternalToolRepo = module.get(ContextExternalToolRepo); + }); + + afterAll(async () => { + await module.close(); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('getMetadata', () => { + describe('when externalToolId is given', () => { + const setup = () => { + const toolId: string = new ObjectId().toHexString(); + + const school = legacySchoolDoFactory.buildWithId(); + const school1 = legacySchoolDoFactory.buildWithId(); + + const schoolToolId: string = new ObjectId().toHexString(); + const schoolToolId1: string = new ObjectId().toHexString(); + + const schoolExternalTool: SchoolExternalTool = schoolExternalToolFactory.buildWithId({ + toolId, + schoolId: school.id, + id: schoolToolId, + }); + const schoolExternalTool1: SchoolExternalTool = schoolExternalToolFactory.buildWithId( + { toolId, schoolId: school1.id, id: schoolToolId1 }, + schoolToolId1 + ); + + const externalToolMetadata: ExternalToolMetadata = new ExternalToolMetadata({ + schoolExternalToolCount: 2, + contextExternalToolCountPerContext: { course: 3, boardElement: 3 }, + }); + + schoolExternalToolRepo.findByExternalToolId.mockResolvedValue([schoolExternalTool, schoolExternalTool1]); + contextExternalToolRepo.countBySchoolToolIdsAndContextType.mockResolvedValue(3); + + return { + toolId, + externalToolMetadata, + schoolExternalTool, + schoolExternalTool1, + }; + }; + + it('should call the repo to get schoolExternalTools by externalToolId', async () => { + const { toolId } = setup(); + + await service.getMetadata(toolId); + + expect(schoolExternalToolRepo.findByExternalToolId).toHaveBeenCalledWith(toolId); + }); + + it('should call the repo to count contextExternalTools by schoolExternalToolId and context', async () => { + const { toolId, schoolExternalTool, schoolExternalTool1 } = setup(); + + await service.getMetadata(toolId); + + expect(contextExternalToolRepo.countBySchoolToolIdsAndContextType).toHaveBeenCalledWith( + ContextExternalToolType.COURSE, + [schoolExternalTool.id, schoolExternalTool1.id] + ); + expect(contextExternalToolRepo.countBySchoolToolIdsAndContextType).toHaveBeenCalledWith( + ContextExternalToolType.BOARD_ELEMENT, + [schoolExternalTool.id, schoolExternalTool1.id] + ); + expect(contextExternalToolRepo.countBySchoolToolIdsAndContextType).toHaveBeenCalledTimes(2); + }); + + it('should return externalToolMetadata', async () => { + const { toolId, externalToolMetadata } = setup(); + + const result: ExternalToolMetadata = await service.getMetadata(toolId); + + expect(result).toEqual(externalToolMetadata); + }); + }); + + describe('when no related school external tool was found', () => { + const setup = () => { + const toolId: string = new ObjectId().toHexString(); + const externalToolEntity: ExternalTool = externalToolFactory.buildWithId(undefined, toolId); + + const externalToolMetadata: ExternalToolMetadata = new ExternalToolMetadata({ + schoolExternalToolCount: 0, + contextExternalToolCountPerContext: { course: 0, boardElement: 0 }, + }); + + schoolExternalToolRepo.findByExternalToolId.mockResolvedValue([]); + contextExternalToolRepo.countBySchoolToolIdsAndContextType.mockResolvedValue(0); + + return { + toolId, + externalToolEntity, + externalToolMetadata, + }; + }; + + it('should return empty externalToolMetadata', async () => { + const { toolId, externalToolMetadata } = setup(); + + const result: ExternalToolMetadata = await service.getMetadata(toolId); + + expect(result).toEqual(externalToolMetadata); + }); + }); + }); +}); diff --git a/apps/server/src/modules/tool/external-tool/service/external-tool-metadata.service.ts b/apps/server/src/modules/tool/external-tool/service/external-tool-metadata.service.ts new file mode 100644 index 00000000000..9476e738a87 --- /dev/null +++ b/apps/server/src/modules/tool/external-tool/service/external-tool-metadata.service.ts @@ -0,0 +1,50 @@ +import { Injectable } from '@nestjs/common'; +import { EntityId } from '@shared/domain'; +import { ContextExternalToolRepo, SchoolExternalToolRepo } from '@shared/repo'; +import { ToolContextType } from '../../common/enum'; +import { ContextExternalToolType } from '../../context-external-tool/entity'; +import { SchoolExternalTool } from '../../school-external-tool/domain'; +import { ExternalToolMetadata } from '../domain'; +import { ToolContextMapper } from '../../common/mapper/tool-context.mapper'; + +@Injectable() +export class ExternalToolMetadataService { + constructor( + private readonly schoolToolRepo: SchoolExternalToolRepo, + private readonly contextToolRepo: ContextExternalToolRepo + ) {} + + async getMetadata(toolId: EntityId): Promise { + const schoolExternalTools: SchoolExternalTool[] = await this.schoolToolRepo.findByExternalToolId(toolId); + + const schoolExternalToolIds: string[] = schoolExternalTools.map( + (schoolExternalTool: SchoolExternalTool): string => + // We can be sure that the repo returns the id + schoolExternalTool.id as string + ); + const contextExternalToolCount: Record = { + [ContextExternalToolType.BOARD_ELEMENT]: 0, + [ContextExternalToolType.COURSE]: 0, + }; + if (schoolExternalTools.length >= 1) { + await Promise.all( + Object.values(ToolContextType).map(async (contextType: ToolContextType): Promise => { + const type: ContextExternalToolType = ToolContextMapper.contextMapping[contextType]; + + const countPerContext: number = await this.contextToolRepo.countBySchoolToolIdsAndContextType( + type, + schoolExternalToolIds + ); + contextExternalToolCount[type] = countPerContext; + }) + ); + } + + const externaltoolMetadata: ExternalToolMetadata = new ExternalToolMetadata({ + schoolExternalToolCount: schoolExternalTools.length, + contextExternalToolCountPerContext: contextExternalToolCount, + }); + + return externaltoolMetadata; + } +} diff --git a/apps/server/src/modules/tool/external-tool/service/index.ts b/apps/server/src/modules/tool/external-tool/service/index.ts index f2290ca8969..e2a936d158b 100644 --- a/apps/server/src/modules/tool/external-tool/service/index.ts +++ b/apps/server/src/modules/tool/external-tool/service/index.ts @@ -5,3 +5,4 @@ export * from './external-tool-validation.service'; export * from './external-tool-parameter-validation.service'; export * from './external-tool-configuration.service'; export * from './external-tool-logo.service'; +export * from './external-tool-metadata.service'; diff --git a/apps/server/src/modules/tool/external-tool/uc/external-tool.uc.spec.ts b/apps/server/src/modules/tool/external-tool/uc/external-tool.uc.spec.ts index 2d371f47c9e..501eef61de9 100644 --- a/apps/server/src/modules/tool/external-tool/uc/external-tool.uc.spec.ts +++ b/apps/server/src/modules/tool/external-tool/uc/external-tool.uc.spec.ts @@ -1,18 +1,24 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { ObjectId } from '@mikro-orm/mongodb'; import { UnauthorizedException, UnprocessableEntityException } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; -import { IFindOptions, Permission, SortOrder, User } from '@shared/domain'; +import { IFindOptions, Permission, Role, SortOrder, User } from '@shared/domain'; import { Page } from '@shared/domain/domainobject/page'; -import { setupEntities, userFactory } from '@shared/testing'; +import { roleFactory, setupEntities, userFactory } from '@shared/testing'; import { externalToolFactory, oauth2ToolConfigFactory, } from '@shared/testing/factory/domainobject/tool/external-tool.factory'; import { ICurrentUser } from '@modules/authentication'; -import { AuthorizationService } from '@modules/authorization'; +import { Action, AuthorizationService } from '@modules/authorization'; import { ExternalToolSearchQuery } from '../../common/interface'; -import { ExternalTool, Oauth2ToolConfig } from '../domain'; -import { ExternalToolLogoService, ExternalToolService, ExternalToolValidationService } from '../service'; +import { ExternalTool, ExternalToolMetadata, Oauth2ToolConfig } from '../domain'; +import { + ExternalToolLogoService, + ExternalToolMetadataService, + ExternalToolService, + ExternalToolValidationService, +} from '../service'; import { ExternalToolUpdate } from './dto'; import { ExternalToolUc } from './external-tool.uc'; @@ -25,6 +31,7 @@ describe('ExternalToolUc', () => { let authorizationService: DeepMocked; let toolValidationService: DeepMocked; let logoService: DeepMocked; + let externalToolMetadataService: DeepMocked; beforeAll(async () => { await setupEntities(); @@ -48,6 +55,10 @@ describe('ExternalToolUc', () => { provide: ExternalToolLogoService, useValue: createMock(), }, + { + provide: ExternalToolMetadataService, + useValue: createMock(), + }, ], }).compile(); @@ -56,6 +67,7 @@ describe('ExternalToolUc', () => { authorizationService = module.get(AuthorizationService); toolValidationService = module.get(ExternalToolValidationService); logoService = module.get(ExternalToolLogoService); + externalToolMetadataService = module.get(ExternalToolMetadataService); }); afterAll(async () => { @@ -468,4 +480,109 @@ describe('ExternalToolUc', () => { expect(externalToolService.deleteExternalTool).toHaveBeenCalledWith(toolId); }); }); + + describe('getMetadataForExternalTool', () => { + describe('when authorize user', () => { + const setupMetadata = () => { + const toolId: string = new ObjectId().toHexString(); + const tool: ExternalTool = externalToolFactory.buildWithId({ id: toolId }, toolId); + + const role: Role = roleFactory.buildWithId({ permissions: [Permission.TOOL_ADMIN] }); + const user: User = userFactory.buildWithId({ roles: [role] }); + const currentUser: ICurrentUser = { userId: user.id } as ICurrentUser; + const context = { action: Action.read, requiredPermissions: [Permission.TOOL_ADMIN] }; + + externalToolService.findById.mockResolvedValue(tool); + authorizationService.getUserWithPermissions.mockResolvedValue(user); + + return { + user, + currentUser, + toolId, + tool, + context, + }; + }; + + it('get user with permissions', async () => { + const { toolId, user } = setupMetadata(); + + await uc.getMetadataForExternalTool(user.id, toolId); + + expect(authorizationService.getUserWithPermissions).toHaveBeenCalledWith(user.id); + }); + + it('should check that the user has TOOL_ADMIN permission', async () => { + const { user, tool } = setupMetadata(); + + await uc.getMetadataForExternalTool(user.id, tool.id!); + + expect(authorizationService.checkAllPermissions).toHaveBeenCalledWith(user, [Permission.TOOL_ADMIN]); + }); + }); + + describe('when user has insufficient permission to get an metadata for external tool ', () => { + const setupMetadata = () => { + const toolId: string = new ObjectId().toHexString(); + + const user: User = userFactory.buildWithId(); + + authorizationService.getUserWithPermissions.mockRejectedValue(new UnauthorizedException()); + + return { + user, + toolId, + }; + }; + + it('should throw UnauthorizedException ', async () => { + const { toolId, user } = setupMetadata(); + + const result: Promise = uc.getMetadataForExternalTool(user.id, toolId); + + await expect(result).rejects.toThrow(UnauthorizedException); + }); + }); + + describe('when externalToolId is given', () => { + const setupMetadata = () => { + const toolId: string = new ObjectId().toHexString(); + + const externalToolMetadata: ExternalToolMetadata = new ExternalToolMetadata({ + schoolExternalToolCount: 2, + contextExternalToolCountPerContext: { course: 3, 'board-element': 3 }, + }); + + externalToolMetadataService.getMetadata.mockResolvedValue(externalToolMetadata); + + const user: User = userFactory.buildWithId(); + const currentUser: ICurrentUser = { userId: user.id } as ICurrentUser; + + authorizationService.getUserWithPermissions.mockResolvedValue(user); + + return { + user, + currentUser, + toolId, + externalToolMetadata, + }; + }; + + it('get metadata for external tool', async () => { + const { toolId, currentUser } = setupMetadata(); + + await uc.getMetadataForExternalTool(currentUser.userId, toolId); + + expect(externalToolMetadataService.getMetadata).toHaveBeenCalledWith(toolId); + }); + + it('return metadata of external tool', async () => { + const { toolId, currentUser, externalToolMetadata } = setupMetadata(); + + const result = await uc.getMetadataForExternalTool(currentUser.userId, toolId); + + expect(result).toEqual(externalToolMetadata); + }); + }); + }); }); diff --git a/apps/server/src/modules/tool/external-tool/uc/external-tool.uc.ts b/apps/server/src/modules/tool/external-tool/uc/external-tool.uc.ts index 2cf49867103..9880994272a 100644 --- a/apps/server/src/modules/tool/external-tool/uc/external-tool.uc.ts +++ b/apps/server/src/modules/tool/external-tool/uc/external-tool.uc.ts @@ -2,8 +2,13 @@ import { Injectable } from '@nestjs/common'; import { EntityId, IFindOptions, Page, Permission, User } from '@shared/domain'; import { AuthorizationService } from '@modules/authorization'; import { ExternalToolSearchQuery } from '../../common/interface'; -import { ExternalTool, ExternalToolConfig } from '../domain'; -import { ExternalToolLogoService, ExternalToolService, ExternalToolValidationService } from '../service'; +import { ExternalTool, ExternalToolConfig, ExternalToolMetadata } from '../domain'; +import { + ExternalToolLogoService, + ExternalToolService, + ExternalToolValidationService, + ExternalToolMetadataService, +} from '../service'; import { ExternalToolCreate, ExternalToolUpdate } from './dto'; @Injectable() @@ -12,7 +17,8 @@ export class ExternalToolUc { private readonly externalToolService: ExternalToolService, private readonly authorizationService: AuthorizationService, private readonly toolValidationService: ExternalToolValidationService, - private readonly externalToolLogoService: ExternalToolLogoService + private readonly externalToolLogoService: ExternalToolLogoService, + private readonly externalToolMetadataService: ExternalToolMetadataService ) {} async createExternalTool(userId: EntityId, externalToolCreate: ExternalToolCreate): Promise { @@ -74,6 +80,15 @@ export class ExternalToolUc { return promise; } + async getMetadataForExternalTool(userId: EntityId, toolId: EntityId): Promise { + // TODO N21-1496: Change External Tools to use authorizationService.checkPermission + await this.ensurePermission(userId, Permission.TOOL_ADMIN); + + const metadata: ExternalToolMetadata = await this.externalToolMetadataService.getMetadata(toolId); + + return metadata; + } + private async ensurePermission(userId: EntityId, permission: Permission) { const user: User = await this.authorizationService.getUserWithPermissions(userId); this.authorizationService.checkAllPermissions(user, [permission]); diff --git a/apps/server/src/modules/tool/school-external-tool/controller/api-test/tool-school.api.spec.ts b/apps/server/src/modules/tool/school-external-tool/controller/api-test/tool-school.api.spec.ts index 2cca9819b3a..274bd289edf 100644 --- a/apps/server/src/modules/tool/school-external-tool/controller/api-test/tool-school.api.spec.ts +++ b/apps/server/src/modules/tool/school-external-tool/controller/api-test/tool-school.api.spec.ts @@ -1,10 +1,12 @@ import { EntityManager, MikroORM } from '@mikro-orm/core'; +import { ObjectId } from '@mikro-orm/mongodb'; import { ServerTestModule } from '@modules/server'; import { HttpStatus, INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { Account, Permission, SchoolEntity, User } from '@shared/domain'; import { accountFactory, + contextExternalToolEntityFactory, customParameterEntityFactory, externalToolEntityFactory, schoolExternalToolEntityFactory, @@ -13,7 +15,8 @@ import { UserAndAccountTestFactory, userFactory, } from '@shared/testing'; -import { ToolConfigurationStatusResponse } from '../../../context-external-tool/controller/dto/tool-configuration-status.response'; +import { ToolConfigurationStatusResponse } from '../../../context-external-tool/controller/dto'; +import { ContextExternalToolEntity, ContextExternalToolType } from '../../../context-external-tool/entity'; import { CustomParameterScope, CustomParameterType, ExternalToolEntity } from '../../../external-tool/entity'; import { SchoolExternalToolEntity } from '../../entity'; import { @@ -22,6 +25,7 @@ import { SchoolExternalToolResponse, SchoolExternalToolSearchListResponse, SchoolExternalToolSearchParams, + SchoolExternalToolMetadataResponse, } from '../dto'; describe('ToolSchoolController (API)', () => { @@ -506,4 +510,81 @@ describe('ToolSchoolController (API)', () => { expect(updatedSchoolExternalTool).toBeDefined(); }); }); + + describe('[GET] tools/school-external-tools/:schoolExternalToolId/metadata', () => { + describe('when user is not authenticated', () => { + const setup = () => { + const toolId: string = new ObjectId().toHexString(); + const externalToolEntity: ExternalToolEntity = externalToolEntityFactory.buildWithId(undefined, toolId); + + return { toolId, externalToolEntity }; + }; + + it('should return unauthorized', async () => { + const { externalToolEntity } = setup(); + + const response = await testApiClient.get(`${externalToolEntity.id}/metadata`); + + expect(response.statusCode).toEqual(HttpStatus.UNAUTHORIZED); + }); + }); + + describe('when schoolExternalToolId is given ', () => { + const setup = async () => { + const school = schoolFactory.buildWithId(); + const schoolToolId: string = new ObjectId().toHexString(); + const schoolExternalToolEntity: SchoolExternalToolEntity = schoolExternalToolEntityFactory.buildWithId( + { school }, + schoolToolId + ); + + const courseExternalToolEntitys: ContextExternalToolEntity[] = contextExternalToolEntityFactory.buildList(3, { + schoolTool: schoolExternalToolEntity, + contextType: ContextExternalToolType.COURSE, + contextId: new ObjectId().toHexString(), + }); + + const boardExternalToolEntitys: ContextExternalToolEntity[] = contextExternalToolEntityFactory.buildList(2, { + schoolTool: schoolExternalToolEntity, + contextType: ContextExternalToolType.BOARD_ELEMENT, + contextId: new ObjectId().toHexString(), + }); + + const schoolExternalToolMetadata: SchoolExternalToolMetadataResponse = new SchoolExternalToolMetadataResponse({ + contextExternalToolCountPerContext: { course: 3, boardElement: 2 }, + }); + + const { adminUser, adminAccount } = UserAndAccountTestFactory.buildAdmin({ school }, [ + Permission.SCHOOL_TOOL_ADMIN, + ]); + + await em.persistAndFlush([ + adminAccount, + adminUser, + schoolExternalToolEntity, + ...courseExternalToolEntitys, + ...boardExternalToolEntitys, + ]); + em.clear(); + + const loggedInClient: TestApiClient = await testApiClient.login(adminAccount); + + return { loggedInClient, schoolExternalToolEntity, schoolExternalToolMetadata }; + }; + + it('should return the metadata of schoolExternalTool', async () => { + const { loggedInClient, schoolExternalToolEntity } = await setup(); + + const response = await loggedInClient.get(`${schoolExternalToolEntity.id}/metadata`); + + expect(response.statusCode).toEqual(HttpStatus.OK); + expect(response.body).toEqual({ + contextExternalToolCountPerContext: { + course: 3, + boardElement: 2, + }, + }); + }); + }); + }); }); diff --git a/apps/server/src/modules/tool/school-external-tool/controller/dto/index.ts b/apps/server/src/modules/tool/school-external-tool/controller/dto/index.ts index 5c3075e1cd2..595b16d7f9c 100644 --- a/apps/server/src/modules/tool/school-external-tool/controller/dto/index.ts +++ b/apps/server/src/modules/tool/school-external-tool/controller/dto/index.ts @@ -5,3 +5,4 @@ export * from './custom-parameter-entry.response'; export * from './school-external-tool-post.params'; export * from './school-external-tool-search.params'; export * from './school-external-tool-search-list.response'; +export * from './school-external-tool-metadata.response'; diff --git a/apps/server/src/modules/tool/school-external-tool/controller/dto/school-external-tool-metadata.response.ts b/apps/server/src/modules/tool/school-external-tool/controller/dto/school-external-tool-metadata.response.ts new file mode 100644 index 00000000000..db4806d23ec --- /dev/null +++ b/apps/server/src/modules/tool/school-external-tool/controller/dto/school-external-tool-metadata.response.ts @@ -0,0 +1,16 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { ContextExternalToolType } from '../../../context-external-tool/entity'; + +export class SchoolExternalToolMetadataResponse { + @ApiProperty({ + type: 'object', + properties: Object.fromEntries( + Object.values(ContextExternalToolType).map((key: ContextExternalToolType) => [key, { type: 'number' }]) + ), + }) + contextExternalToolCountPerContext: Record; + + constructor(schoolExternalToolMetadataResponse: SchoolExternalToolMetadataResponse) { + this.contextExternalToolCountPerContext = schoolExternalToolMetadataResponse.contextExternalToolCountPerContext; + } +} diff --git a/apps/server/src/modules/tool/school-external-tool/controller/tool-school.controller.ts b/apps/server/src/modules/tool/school-external-tool/controller/tool-school.controller.ts index 79d6f789443..ccfef902bee 100644 --- a/apps/server/src/modules/tool/school-external-tool/controller/tool-school.controller.ts +++ b/apps/server/src/modules/tool/school-external-tool/controller/tool-school.controller.ts @@ -14,7 +14,11 @@ import { Body, Controller, Delete, Get, Param, Post, Query, Put, HttpCode, HttpS import { ValidationError } from '@shared/common'; import { LegacyLogger } from '@src/core/logger'; import { Authenticate, CurrentUser, ICurrentUser } from '@modules/authentication'; -import { SchoolExternalToolRequestMapper, SchoolExternalToolResponseMapper } from '../mapper'; +import { + SchoolExternalToolRequestMapper, + SchoolExternalToolResponseMapper, + SchoolExternalToolMetadataMapper, +} from '../mapper'; import { ExternalToolSearchListResponse } from '../../external-tool/controller/dto'; import { SchoolExternalToolIdParams, @@ -22,10 +26,11 @@ import { SchoolExternalToolResponse, SchoolExternalToolSearchListResponse, SchoolExternalToolSearchParams, + SchoolExternalToolMetadataResponse, } from './dto'; import { SchoolExternalToolDto } from '../uc/dto/school-external-tool.types'; import { SchoolExternalToolUc } from '../uc'; -import { SchoolExternalTool } from '../domain'; +import { SchoolExternalTool, SchoolExternalToolMetadata } from '../domain'; @ApiTags('Tool') @Authenticate('jwt') @@ -136,4 +141,24 @@ export class ToolSchoolController { return response; } + + @Get('/:schoolExternalToolId/metadata') + @ApiOperation({ summary: 'Gets the metadata of an school external tool.' }) + @ApiOkResponse({ + description: 'Metadata of school external tool fetched successfully.', + type: SchoolExternalToolMetadataResponse, + }) + @ApiUnauthorizedResponse({ description: 'User is not logged in.' }) + async getMetaDataForExternalTool( + @CurrentUser() currentUser: ICurrentUser, + @Param() params: SchoolExternalToolIdParams + ): Promise { + const schoolExternalToolMetadata: SchoolExternalToolMetadata = + await this.schoolExternalToolUc.getMetadataForSchoolExternalTool(currentUser.userId, params.schoolExternalToolId); + + const mapped: SchoolExternalToolMetadataResponse = + SchoolExternalToolMetadataMapper.mapToSchoolExternalToolMetadataResponse(schoolExternalToolMetadata); + + return mapped; + } } diff --git a/apps/server/src/modules/tool/school-external-tool/domain/index.ts b/apps/server/src/modules/tool/school-external-tool/domain/index.ts index 1d734ed8376..d089fb2e908 100644 --- a/apps/server/src/modules/tool/school-external-tool/domain/index.ts +++ b/apps/server/src/modules/tool/school-external-tool/domain/index.ts @@ -1,2 +1,3 @@ export * from './school-external-tool.do'; export * from './school-external-tool-ref.do'; +export * from './school-external-tool-metadata'; diff --git a/apps/server/src/modules/tool/school-external-tool/domain/school-external-tool-metadata.ts b/apps/server/src/modules/tool/school-external-tool/domain/school-external-tool-metadata.ts new file mode 100644 index 00000000000..4cccdfe11a1 --- /dev/null +++ b/apps/server/src/modules/tool/school-external-tool/domain/school-external-tool-metadata.ts @@ -0,0 +1,7 @@ +export class SchoolExternalToolMetadata { + contextExternalToolCountPerContext: Record; + + constructor(schoolExternalToolMetadata: SchoolExternalToolMetadata) { + this.contextExternalToolCountPerContext = schoolExternalToolMetadata.contextExternalToolCountPerContext; + } +} diff --git a/apps/server/src/modules/tool/school-external-tool/mapper/index.ts b/apps/server/src/modules/tool/school-external-tool/mapper/index.ts index 961644805f1..48cd0b13a20 100644 --- a/apps/server/src/modules/tool/school-external-tool/mapper/index.ts +++ b/apps/server/src/modules/tool/school-external-tool/mapper/index.ts @@ -1,2 +1,3 @@ export * from './school-external-tool-request.mapper'; export * from './school-external-tool-response.mapper'; +export * from './school-external-tool-metadata.mapper'; diff --git a/apps/server/src/modules/tool/school-external-tool/mapper/school-external-tool-metadata.mapper.ts b/apps/server/src/modules/tool/school-external-tool/mapper/school-external-tool-metadata.mapper.ts new file mode 100644 index 00000000000..1e42f22ebf2 --- /dev/null +++ b/apps/server/src/modules/tool/school-external-tool/mapper/school-external-tool-metadata.mapper.ts @@ -0,0 +1,14 @@ +import { SchoolExternalToolMetadataResponse } from '../controller/dto'; +import { SchoolExternalToolMetadata } from '../domain'; + +export class SchoolExternalToolMetadataMapper { + static mapToSchoolExternalToolMetadataResponse( + schoolExternalToolMetadata: SchoolExternalToolMetadata + ): SchoolExternalToolMetadataResponse { + const externalToolMetadataResponse: SchoolExternalToolMetadataResponse = new SchoolExternalToolMetadataResponse({ + contextExternalToolCountPerContext: schoolExternalToolMetadata.contextExternalToolCountPerContext, + }); + + return externalToolMetadataResponse; + } +} diff --git a/apps/server/src/modules/tool/school-external-tool/school-external-tool.module.ts b/apps/server/src/modules/tool/school-external-tool/school-external-tool.module.ts index 898e159348a..93d4c4f6705 100644 --- a/apps/server/src/modules/tool/school-external-tool/school-external-tool.module.ts +++ b/apps/server/src/modules/tool/school-external-tool/school-external-tool.module.ts @@ -1,12 +1,16 @@ import { Module } from '@nestjs/common'; import { CommonToolModule } from '../common'; -import { SchoolExternalToolService, SchoolExternalToolValidationService } from './service'; +import { + SchoolExternalToolService, + SchoolExternalToolValidationService, + SchoolExternalToolMetadataService, +} from './service'; import { ExternalToolModule } from '../external-tool'; import { ToolConfigModule } from '../tool-config.module'; @Module({ imports: [CommonToolModule, ExternalToolModule, ToolConfigModule], - providers: [SchoolExternalToolService, SchoolExternalToolValidationService], - exports: [SchoolExternalToolService, SchoolExternalToolValidationService], + providers: [SchoolExternalToolService, SchoolExternalToolValidationService, SchoolExternalToolMetadataService], + exports: [SchoolExternalToolService, SchoolExternalToolValidationService, SchoolExternalToolMetadataService], }) export class SchoolExternalToolModule {} diff --git a/apps/server/src/modules/tool/school-external-tool/service/index.ts b/apps/server/src/modules/tool/school-external-tool/service/index.ts index 1ceab5f3da5..ea949d8b70a 100644 --- a/apps/server/src/modules/tool/school-external-tool/service/index.ts +++ b/apps/server/src/modules/tool/school-external-tool/service/index.ts @@ -1,2 +1,3 @@ export * from './school-external-tool.service'; export * from './school-external-tool-validation.service'; +export * from './school-external-tool-metadata.service'; diff --git a/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-metadata.service.spec.ts b/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-metadata.service.spec.ts new file mode 100644 index 00000000000..8aa29737550 --- /dev/null +++ b/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-metadata.service.spec.ts @@ -0,0 +1,93 @@ +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { ObjectId } from '@mikro-orm/mongodb'; +import { Test, TestingModule } from '@nestjs/testing'; +import { ContextExternalToolRepo } from '@shared/repo'; +import { Logger } from '@src/core/logger'; +import { SchoolExternalToolMetadata } from '../domain'; +import { SchoolExternalToolMetadataService } from './school-external-tool-metadata.service'; + +describe('SchoolExternalToolMetadataService', () => { + let module: TestingModule; + let service: SchoolExternalToolMetadataService; + + let contextExternalToolRepo: DeepMocked; + + beforeAll(async () => { + module = await Test.createTestingModule({ + providers: [ + SchoolExternalToolMetadataService, + { + provide: ContextExternalToolRepo, + useValue: createMock(), + }, + { + provide: Logger, + useValue: createMock(), + }, + ], + }).compile(); + + service = module.get(SchoolExternalToolMetadataService); + contextExternalToolRepo = module.get(ContextExternalToolRepo); + }); + + afterAll(async () => { + await module.close(); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('getMetadata', () => { + describe('when schoolExternalToolId is given', () => { + const setup = () => { + const schoolToolId: string = new ObjectId().toHexString(); + + const schoolExternalToolMetadata: SchoolExternalToolMetadata = new SchoolExternalToolMetadata({ + contextExternalToolCountPerContext: { course: 3, boardElement: 3 }, + }); + + contextExternalToolRepo.countBySchoolToolIdsAndContextType.mockResolvedValue(3); + + return { + schoolToolId, + schoolExternalToolMetadata, + }; + }; + + it('should return externalToolMetadata', async () => { + const { schoolToolId, schoolExternalToolMetadata } = setup(); + + const result: SchoolExternalToolMetadata = await service.getMetadata(schoolToolId); + + expect(result).toEqual(schoolExternalToolMetadata); + }); + }); + + describe('when no related context external tool was found', () => { + const setup = () => { + const schoolToolId: string = new ObjectId().toHexString(); + + const schoolExternalToolMetadata: SchoolExternalToolMetadata = new SchoolExternalToolMetadata({ + contextExternalToolCountPerContext: { course: 0, boardElement: 0 }, + }); + + contextExternalToolRepo.countBySchoolToolIdsAndContextType.mockResolvedValue(0); + + return { + schoolToolId, + schoolExternalToolMetadata, + }; + }; + + it('should return empty schoolExternalToolMetadata', async () => { + const { schoolToolId, schoolExternalToolMetadata } = setup(); + + const result: SchoolExternalToolMetadata = await service.getMetadata(schoolToolId); + + expect(result).toEqual(schoolExternalToolMetadata); + }); + }); + }); +}); diff --git a/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-metadata.service.ts b/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-metadata.service.ts new file mode 100644 index 00000000000..fa8fd4fc926 --- /dev/null +++ b/apps/server/src/modules/tool/school-external-tool/service/school-external-tool-metadata.service.ts @@ -0,0 +1,37 @@ +import { Injectable } from '@nestjs/common'; +import { EntityId } from '@shared/domain'; +import { ContextExternalToolRepo } from '@shared/repo'; +import { ToolContextType } from '../../common/enum'; +import { ToolContextMapper } from '../../common/mapper/tool-context.mapper'; +import { ContextExternalToolType } from '../../context-external-tool/entity'; +import { SchoolExternalToolMetadata } from '../domain'; + +@Injectable() +export class SchoolExternalToolMetadataService { + constructor(private readonly contextToolRepo: ContextExternalToolRepo) {} + + async getMetadata(schoolExternalToolId: EntityId) { + const contextExternalToolCount: Record = { + [ContextExternalToolType.BOARD_ELEMENT]: 0, + [ContextExternalToolType.COURSE]: 0, + }; + + await Promise.all( + Object.values(ToolContextType).map(async (contextType: ToolContextType): Promise => { + const type: ContextExternalToolType = ToolContextMapper.contextMapping[contextType]; + + const countPerContext: number = await this.contextToolRepo.countBySchoolToolIdsAndContextType(type, [ + schoolExternalToolId, + ]); + + contextExternalToolCount[type] = countPerContext; + }) + ); + + const schoolExternaltoolMetadata: SchoolExternalToolMetadata = new SchoolExternalToolMetadata({ + contextExternalToolCountPerContext: contextExternalToolCount, + }); + + return schoolExternaltoolMetadata; + } +} diff --git a/apps/server/src/modules/tool/school-external-tool/uc/school-external-tool.uc.spec.ts b/apps/server/src/modules/tool/school-external-tool/uc/school-external-tool.uc.spec.ts index 377a5d24a38..6f3512f6a0d 100644 --- a/apps/server/src/modules/tool/school-external-tool/uc/school-external-tool.uc.spec.ts +++ b/apps/server/src/modules/tool/school-external-tool/uc/school-external-tool.uc.spec.ts @@ -1,4 +1,5 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { ObjectId } from '@mikro-orm/mongodb'; import { Test, TestingModule } from '@nestjs/testing'; import { EntityId, Permission, User } from '@shared/domain'; import { schoolExternalToolFactory, setupEntities, userFactory } from '@shared/testing'; @@ -6,7 +7,11 @@ import { AuthorizationContextBuilder } from '@modules/authorization'; import { ToolPermissionHelper } from '../../common/uc/tool-permission-helper'; import { ContextExternalToolService } from '../../context-external-tool/service'; import { SchoolExternalTool } from '../domain'; -import { SchoolExternalToolService, SchoolExternalToolValidationService } from '../service'; +import { + SchoolExternalToolMetadataService, + SchoolExternalToolService, + SchoolExternalToolValidationService, +} from '../service'; import { SchoolExternalToolQueryInput } from './dto/school-external-tool.types'; import { SchoolExternalToolUc } from './school-external-tool.uc'; @@ -18,6 +23,7 @@ describe('SchoolExternalToolUc', () => { let contextExternalToolService: DeepMocked; let schoolExternalToolValidationService: DeepMocked; let toolPermissionHelper: DeepMocked; + let schoolExternalToolMetadataService: DeepMocked; beforeAll(async () => { await setupEntities(); @@ -40,6 +46,10 @@ describe('SchoolExternalToolUc', () => { provide: ToolPermissionHelper, useValue: createMock(), }, + { + provide: SchoolExternalToolMetadataService, + useValue: createMock(), + }, ], }).compile(); @@ -48,6 +58,7 @@ describe('SchoolExternalToolUc', () => { contextExternalToolService = module.get(ContextExternalToolService); schoolExternalToolValidationService = module.get(SchoolExternalToolValidationService); toolPermissionHelper = module.get(ToolPermissionHelper); + schoolExternalToolMetadataService = module.get(SchoolExternalToolMetadataService); }); afterAll(async () => { @@ -358,4 +369,55 @@ describe('SchoolExternalToolUc', () => { expect(result).toEqual(updatedTool); }); }); + + describe('getMetadataForSchoolExternalTool', () => { + describe('when authorize user', () => { + const setupMetadata = () => { + const toolId = new ObjectId().toHexString(); + const tool: SchoolExternalTool = schoolExternalToolFactory.buildWithId({ id: toolId }, toolId); + const userId: string = new ObjectId().toHexString(); + const user: User = userFactory.buildWithId({}, userId); + + schoolExternalToolService.findById.mockResolvedValue(tool); + + return { + user, + tool, + }; + }; + + it('should check the permissions of the user', async () => { + const { user, tool } = setupMetadata(); + + await uc.getMetadataForSchoolExternalTool(user.id, tool.id!); + + expect(toolPermissionHelper.ensureSchoolPermissions).toHaveBeenCalledWith( + user.id, + tool, + AuthorizationContextBuilder.read([Permission.SCHOOL_TOOL_ADMIN]) + ); + }); + }); + + describe('when externalToolId is given', () => { + const setupMetadata = () => { + const user: User = userFactory.buildWithId(); + + const toolId: string = new ObjectId().toHexString(); + + return { + toolId, + user, + }; + }; + + it('should call the service to get metadata', async () => { + const { toolId, user } = setupMetadata(); + + await uc.getMetadataForSchoolExternalTool(user.id, toolId); + + expect(schoolExternalToolMetadataService.getMetadata).toHaveBeenCalledWith(toolId); + }); + }); + }); }); diff --git a/apps/server/src/modules/tool/school-external-tool/uc/school-external-tool.uc.ts b/apps/server/src/modules/tool/school-external-tool/uc/school-external-tool.uc.ts index d7adf3f4937..0d098c8c657 100644 --- a/apps/server/src/modules/tool/school-external-tool/uc/school-external-tool.uc.ts +++ b/apps/server/src/modules/tool/school-external-tool/uc/school-external-tool.uc.ts @@ -3,8 +3,12 @@ import { EntityId, Permission } from '@shared/domain'; import { AuthorizationContext, AuthorizationContextBuilder } from '@modules/authorization'; import { ToolPermissionHelper } from '../../common/uc/tool-permission-helper'; import { ContextExternalToolService } from '../../context-external-tool/service'; -import { SchoolExternalTool } from '../domain'; -import { SchoolExternalToolService, SchoolExternalToolValidationService } from '../service'; +import { SchoolExternalTool, SchoolExternalToolMetadata } from '../domain'; +import { + SchoolExternalToolService, + SchoolExternalToolValidationService, + SchoolExternalToolMetadataService, +} from '../service'; import { SchoolExternalToolDto, SchoolExternalToolQueryInput } from './dto/school-external-tool.types'; @Injectable() @@ -13,6 +17,7 @@ export class SchoolExternalToolUc { private readonly schoolExternalToolService: SchoolExternalToolService, private readonly contextExternalToolService: ContextExternalToolService, private readonly schoolExternalToolValidationService: SchoolExternalToolValidationService, + private readonly schoolExternalToolMetadataService: SchoolExternalToolMetadataService, private readonly toolPermissionHelper: ToolPermissionHelper ) {} @@ -95,4 +100,20 @@ export class SchoolExternalToolUc { const saved = await this.schoolExternalToolService.saveSchoolExternalTool(updated); return saved; } + + async getMetadataForSchoolExternalTool( + userId: EntityId, + schoolExternalToolId: EntityId + ): Promise { + const schoolExternalTool: SchoolExternalTool = await this.schoolExternalToolService.findById(schoolExternalToolId); + + const context: AuthorizationContext = AuthorizationContextBuilder.read([Permission.SCHOOL_TOOL_ADMIN]); + await this.toolPermissionHelper.ensureSchoolPermissions(userId, schoolExternalTool, context); + + const metadata: SchoolExternalToolMetadata = await this.schoolExternalToolMetadataService.getMetadata( + schoolExternalToolId + ); + + return metadata; + } } diff --git a/apps/server/src/shared/repo/contextexternaltool/context-external-tool.repo.integration.spec.ts b/apps/server/src/shared/repo/contextexternaltool/context-external-tool.repo.integration.spec.ts index 854c3958135..c6a684a377d 100644 --- a/apps/server/src/shared/repo/contextexternaltool/context-external-tool.repo.integration.spec.ts +++ b/apps/server/src/shared/repo/contextexternaltool/context-external-tool.repo.integration.spec.ts @@ -1,8 +1,14 @@ import { createMock } from '@golevelup/ts-jest'; +import { MongoMemoryDatabaseModule } from '@infra/database'; import { EntityManager, ObjectId } from '@mikro-orm/mongodb'; +import { CustomParameterEntry } from '@modules/tool/common/domain'; +import { ToolContextType } from '@modules/tool/common/enum'; +import { ContextExternalTool, ContextExternalToolProps } from '@modules/tool/context-external-tool/domain'; +import { ContextExternalToolEntity, ContextExternalToolType } from '@modules/tool/context-external-tool/entity'; +import { ContextExternalToolQuery } from '@modules/tool/context-external-tool/uc/dto/context-external-tool.types'; +import { SchoolExternalToolEntity } from '@modules/tool/school-external-tool/entity'; import { Test, TestingModule } from '@nestjs/testing'; import { SchoolEntity } from '@shared/domain'; -import { MongoMemoryDatabaseModule } from '@infra/database'; import { ExternalToolRepoMapper } from '@shared/repo/externaltool/external-tool.repo.mapper'; import { cleanupCollections, @@ -12,12 +18,6 @@ import { schoolFactory, } from '@shared/testing'; import { LegacyLogger } from '@src/core/logger'; -import { CustomParameterEntry } from '@modules/tool/common/domain'; -import { ToolContextType } from '@modules/tool/common/enum'; -import { ContextExternalTool, ContextExternalToolProps } from '@modules/tool/context-external-tool/domain'; -import { ContextExternalToolEntity, ContextExternalToolType } from '@modules/tool/context-external-tool/entity'; -import { ContextExternalToolQuery } from '@modules/tool/context-external-tool/uc/dto/context-external-tool.types'; -import { SchoolExternalToolEntity } from '@modules/tool/school-external-tool/entity'; import { ContextExternalToolRepo } from './context-external-tool.repo'; describe('ContextExternalToolRepo', () => { @@ -394,4 +394,86 @@ describe('ContextExternalToolRepo', () => { }); }); }); + + describe('countBySchoolToolIdsAndContextType', () => { + describe('when a ContextExternalTool is found for course context', () => { + const setup = async () => { + const schoolExternalTool = schoolExternalToolEntityFactory.buildWithId(); + const schoolExternalTool1 = schoolExternalToolEntityFactory.buildWithId(); + + const contextExternalTool = contextExternalToolEntityFactory.buildList(4, { + contextType: ContextExternalToolType.COURSE, + schoolTool: schoolExternalTool, + }); + + const contextExternalTool3 = contextExternalToolEntityFactory.buildList(2, { + contextType: ContextExternalToolType.COURSE, + schoolTool: schoolExternalTool1, + }); + + await em.persistAndFlush([ + schoolExternalTool, + schoolExternalTool1, + ...contextExternalTool, + ...contextExternalTool3, + ]); + + return { + schoolExternalTool, + schoolExternalTool1, + }; + }; + + it('should return correct results', async () => { + const { schoolExternalTool, schoolExternalTool1 } = await setup(); + + const result = await repo.countBySchoolToolIdsAndContextType(ContextExternalToolType.COURSE, [ + schoolExternalTool.id, + schoolExternalTool1.id, + ]); + + expect(result).toEqual(6); + }); + }); + + describe('when a ContextExternalTool is found for board context', () => { + const setup = async () => { + const schoolExternalTool = schoolExternalToolEntityFactory.buildWithId(); + const schoolExternalTool1 = schoolExternalToolEntityFactory.buildWithId(); + + const contextExternalTool1 = contextExternalToolEntityFactory.buildList(3, { + contextType: ContextExternalToolType.BOARD_ELEMENT, + schoolTool: schoolExternalTool, + }); + + const contextExternalTool2 = contextExternalToolEntityFactory.buildList(2, { + contextType: ContextExternalToolType.BOARD_ELEMENT, + schoolTool: schoolExternalTool1, + }); + + await em.persistAndFlush([ + schoolExternalTool, + schoolExternalTool1, + ...contextExternalTool1, + ...contextExternalTool2, + ]); + + return { + schoolExternalTool, + schoolExternalTool1, + }; + }; + + it('should return correct results', async () => { + const { schoolExternalTool, schoolExternalTool1 } = await setup(); + + const result = await repo.countBySchoolToolIdsAndContextType(ContextExternalToolType.BOARD_ELEMENT, [ + schoolExternalTool.id, + schoolExternalTool1.id, + ]); + + expect(result).toEqual(5); + }); + }); + }); }); diff --git a/apps/server/src/shared/repo/contextexternaltool/context-external-tool.repo.ts b/apps/server/src/shared/repo/contextexternaltool/context-external-tool.repo.ts index 5ad1629f0c2..31053cd48b5 100644 --- a/apps/server/src/shared/repo/contextexternaltool/context-external-tool.repo.ts +++ b/apps/server/src/shared/repo/contextexternaltool/context-external-tool.repo.ts @@ -53,6 +53,14 @@ export class ContextExternalToolRepo extends BaseDORepo< return dos; } + async countBySchoolToolIdsAndContextType(contextType: ContextExternalToolType, schoolExternalToolIds: string[]) { + const contextExternalToolCount = await this._em.count(this.entityName, { + $and: [{ schoolTool: { $in: schoolExternalToolIds }, contextType }], + }); + + return contextExternalToolCount; + } + public override async findById(id: EntityId): Promise { const entity: ContextExternalToolEntity = await this._em.findOneOrFail( this.entityName, diff --git a/apps/server/src/shared/repo/schoolexternaltool/school-external-tool.repo.ts b/apps/server/src/shared/repo/schoolexternaltool/school-external-tool.repo.ts index 64f55c715e8..c17e4d2ac17 100644 --- a/apps/server/src/shared/repo/schoolexternaltool/school-external-tool.repo.ts +++ b/apps/server/src/shared/repo/schoolexternaltool/school-external-tool.repo.ts @@ -44,6 +44,7 @@ export class SchoolExternalToolRepo extends BaseDORepo< const domainObject: SchoolExternalTool = this.mapEntityToDO(entity); return domainObject; }); + return domainObjects; } diff --git a/backup/setup/school-external-tools.json b/backup/setup/school-external-tools.json index 884c8ffe373..59befc464af 100644 --- a/backup/setup/school-external-tools.json +++ b/backup/setup/school-external-tools.json @@ -45,5 +45,28 @@ }, "schoolParameters": [], "toolVersion": 2 + }, + { + "_id": { + "$oid": "647de374cf6a427b9d39e5bb" + }, + "createdAt": { + "$date": { + "$numberLong": "1685971828284" + } + }, + "updatedAt": { + "$date": { + "$numberLong": "1685971828284" + } + }, + "tool": { + "$oid": "647de247cf6a427b9d39e5b9" + }, + "school": { + "$oid": "5fa2c5ccb229544f2c69666c" + }, + "schoolParameters": [], + "toolVersion": 2 } ] From bbd70757912355809e340da75be7d1c638ab0802 Mon Sep 17 00:00:00 2001 From: wolfganggreschus Date: Mon, 20 Nov 2023 20:16:21 +0100 Subject: [PATCH 10/10] BC-3513 Remove I-Prefix (#4514) * IErrorType -> ErrorType * ICoreModuleConfig -> CoreModuleConfig * IInterceptorConfig -> InterceptorConfig * ILoggerConfig -> LoggerConfig * IAccountConfig -> AccountConfig * ClassEntityProps, RepoLoader * FileStorageConfig * FileRecordSecurityCheckProperties, ParentInfo, FileRecordProperties * CopyFileDomainObjectProps, FileDomainObjectProps, CopyFilesRequestInfo, FileRequestInfo * FilesStorageClientConfig, FilesStorageClientConfig, CopyFileDomainObjectProps, FileDomainObjectProps, CommonCartridgeElement, CommonCartridgeElement tests adjusted * solved some merge conflicts * ILgecyLogger named temp LegacyLoggerInterface * ICalendarEvent -> CalendarEvent * ICollaborativeStorageStrategy -> CollaborativeStorageStrategy * IEncryptionService -> EncryptionService * CollaborativeStorageStrategy - ModuleName changed * IIdentityManagementConfig -> IdentityManagementConfig * IKeycloakSettings -> KeycloakSettingsInterface (temp) * renamed RetryOptions, MigrationOptions, CleanOptions * IJsonAccount -> JsonAccount * IJsonUser -> JsonUser * IKeycloakConfigurationInputFiles -> KeycloakConfigurationInputFilesInterface (temp) * ICopyFilesOfParentParams > CopyFilesOfParentParams IMailConfig -> MailConfig IError -> ErrorInterface (temp) * IMailConfig -> MailConfig * ICopyFileDO -> CopyFileDO * IFileDO -> FileDO * ICurrentUserInterface -> CurrentUserInterface * ICurrentUser -> CurrentUserInterface (temp) * ITemporaryFileProperties -> TemporaryFileProperties * ICommonCartridgeFileBuilder -> CommonCartridgeFileBuilderInterface ICommonCartridgeOrganizationBuilder -> CommonCartridgeOrganizationBuilderInterface ICommonCartridgeFileBuilder -> CommonCartridgeFileBuilderInterface all temp * ICommonCartridgeConfig -> CommonCartridgeConfig * ExternalToolPseudonymEntityProps * IServerConfig -> ServerConfig * IShareTokenProperties -> ShareTokenProperties * IToolFeatures -> ToolFeaturesInterface * IContextExternalToolProperties -> ContextExternalToolProperties * ISchoolExternalToolProperties -> SchoolExternalToolProperties * IToolLaunchParams -> ToolLaunchParams * IToolLaunchStrategy -> ToolLaunchStrategy * IUserConfig -> UserConfig * IBbbSettings -> BbbSettingsInterface * IVideoConferenceSettings -> VideoConferenceSettingsInterface * IScopeInfo -> ScopeInfo * ICourseProperties -> CourseProperties * ICourseGroupProperties -> CourseGroupProperties * IImportUserProperties -> ImportUserProperties * ILessonProperties -> LessonProperties * IComponentTextProperties -> ComponentTextProperties * IComponentTextProperties -> ComponentTextProperties * IComponentLernstoreProperties -> ComponentLernstoreProperties * IComponentEtherpadProperties -> ComponentEtherpadProperties * IComponentInternalProperties -> ComponentInternalProperties * IComponentProperties -> ComponentProperties * ILessonParent -> LessonParent * ILearnroomElement -> LearnroomElement * ITargetGroupProperties -> TargetGroupProperties * IRelatedResourceProperties -> RelatedResourceProperties * IMaterialProperties -> MaterialProperties * NewsProperties -> INewsProperties * IRoleProperties -> RoleProperties * SchoolProperties -> SchoolProperties * ISchoolYearProperties -> SchoolYearProperties * IStorageProviderProperties -> StorageProviderProperties * ISubmissionProperties -> SubmissionProperties * ISystemProperties -> SystemProperties * ITaskParent -> TaskParent * ITeamProperties -> TeamProperties * ITeamUserProperties -> TeamUserProperties * IUserProperties -> IUserProperties * IPagination -> Pagination * IFindOptions -> FindOptions * ILearnroom -> Learnroom * IImportUserScope -> ImportUserScopeInterface * INameMatch -> NameMatch * ICreateNews -> CreateNews * INewsScope -> NewsScopeInterface * ITaskUpdate -> TaskUpdate * ITaskCreate -> TaskCreate * ITaskProperties -> TaskProperties * ITaskStatus -> TaskStatus * ITestEntityProperties -> TestEntityProperties * IDashboardRepo -> DashboardRepoInterface * IToolLaunchParams -> ToolLaunchParams * IEntityWithSchool -> EntityWithSchool * IEntity -> EntityInterface * test adjusted * tests adjusted * test adjusted * Revert "IFindOptions -> FindOptions" This reverts commit 3ad7af309c44198c3cdedc4362c84fc6bf78f7a2. * solved more conflicts caused by revert * merged more conflicts caused by main-merge * adjusted tests * Adjusted more tests * specs adjusted * CurrentUserInterface -> ICurrentUser * LegacyLoggerInterface -> ILegacyLogger * KeycloakSettingsInterface -> IKeycloakSettings * KeycloakConfigurationInputFilesInterface -> IKeycloakConfigurationInputFiles * ErrorInterface -> IError * ICommonCartridgeOrganizationBuilderInterface -> ICommonCartridgeOrganizationBuilder * CommonCartridgeFileBuilderInterface -> ICommonCartridgeFileBuilder * ToolFeaturesInterface -> IToolFeatures * BbbSettingsInterface -> IBbbSettings * VideoConferenceSettingsInterface -> IVideoConferenceSettings * GridElementInterface -> IGridElement * EntityInterface -> IEntity * ImportUserScopeInterface -> IImportUserScope * TaskInterface -> ITask * NewsScopeInterface -> INewsScope * DashboardRepoInterface -> IDashboardRepo --- TODO.md | 2 +- .../core/error/filter/global-error.filter.ts | 2 +- .../error/interface/error-type.interface.ts | 2 +- .../src/core/error/server-error-types.ts | 10 +-- .../core/interceptor/interceptor.module.ts | 6 +- .../src/core/interfaces/core-module-config.ts | 6 +- .../core/logger/interfaces/logger-config.ts | 2 +- apps/server/src/core/logger/logger.module.ts | 4 +- .../interface/calendar-event.interface.ts | 2 +- .../calendar/mapper/calendar.mapper.spec.ts | 4 +- .../infra/calendar/mapper/calendar.mapper.ts | 4 +- .../calendar/service/calendar.service.spec.ts | 10 +-- .../calendar/service/calendar.service.ts | 10 +-- .../collaborative-storage-adapter.module.ts | 14 ++-- .../collaborative-storage.adapter.spec.ts | 12 ++-- .../collaborative-storage.adapter.ts | 10 +-- .../strategy/base.interface.strategy.ts | 2 +- .../strategy/nextcloud/nextcloud.strategy.ts | 16 ++--- .../infra/encryption/encryption.interface.ts | 2 +- .../encryption/encryption.module.spec.ts | 6 +- .../infra/encryption/encryption.service.ts | 4 +- .../identity-management.config.ts | 2 +- .../console/keycloak-configuration.console.ts | 14 ++-- .../interface/json-account.interface.ts | 2 +- .../interface/json-user.interface.ts | 2 +- .../mapper/identity-provider.mapper.ts | 6 +- .../keycloak-configuration.service.spec.ts | 6 +- .../service/keycloak-configuration.service.ts | 8 +-- .../service/keycloak-seed.service.spec.ts | 14 ++-- .../service/keycloak-seed.service.ts | 16 ++--- ...-identity-management-oauth.service.spec.ts | 4 +- ...cloak-identity-management-oauth.service.ts | 6 +- .../src/infra/mail/interfaces/mail-config.ts | 2 +- apps/server/src/infra/mail/mail.module.ts | 6 +- .../src/infra/mail/mail.service.spec.ts | 6 +- apps/server/src/infra/mail/mail.service.ts | 4 +- .../src/infra/rabbitmq/error.mapper.spec.ts | 2 +- .../server/src/infra/rabbitmq/error.mapper.ts | 2 +- .../infra/rabbitmq/exchange/files-storage.ts | 12 ++-- .../src/modules/account/account-config.ts | 2 +- .../src/modules/account/account.module.ts | 12 ++-- .../account/controller/account.controller.ts | 2 +- .../controller/api-test/account.api.spec.ts | 2 +- .../services/account-db.service.spec.ts | 12 ++-- .../services/account-lookup.service.ts | 6 +- .../account/services/account.service.spec.ts | 4 +- .../account/services/account.service.ts | 4 +- .../src/modules/account/uc/account.uc.spec.ts | 8 +-- .../src/modules/account/uc/account.uc.ts | 10 +-- .../decorator/auth.decorator.spec.ts | 6 +- .../decorator/auth.decorator.ts | 4 +- .../src/modules/authentication/index.ts | 4 +- .../services/authentication.service.spec.ts | 6 +- .../services/authentication.service.ts | 8 +-- .../strategy/ldap.strategy.spec.ts | 2 +- .../strategy/local.strategy.spec.ts | 10 +-- .../authentication/strategy/local.strategy.ts | 16 ++--- .../strategy/oauth2.strategy.spec.ts | 5 +- .../strategy/oauth2.strategy.ts | 2 +- .../domain/service/reference.loader.ts | 11 +-- .../api-test/board-delete.api.spec.ts | 6 +- .../api-test/board-lookup.api.spec.ts | 6 +- .../api-test/card-create.api.spec.ts | 6 +- .../api-test/card-delete.api.spec.ts | 6 +- .../api-test/card-lookup.api.spec.ts | 6 +- .../controller/api-test/card-move.api.spec.ts | 6 +- .../api-test/column-create.api.spec.ts | 6 +- .../api-test/column-delete.api.spec.ts | 6 +- .../api-test/column-move.api.spec.ts | 6 +- .../content-element-delete.api.spec.ts | 6 +- .../api-test/content-element-move.api.spec.ts | 6 +- .../controller/board-submission.controller.ts | 4 +- .../board/controller/board.controller.ts | 2 +- .../board/controller/card.controller.ts | 2 +- .../board/controller/column.controller.ts | 4 +- .../board/controller/element.controller.ts | 2 +- .../src/modules/class/entity/class.entity.ts | 8 +-- .../testing/factory/class.entity.factory.ts | 8 +-- .../collaborative-storage.controller.spec.ts | 6 +- .../collaborative-storage.controller.ts | 4 +- .../files-storage-client/dto/copy-file.dto.ts | 4 +- .../files-storage-client/dto/file.dto.ts | 6 +- .../src/modules/files-storage-client/index.ts | 2 +- .../copy-file-domain-object-props.ts | 2 +- .../interfaces/copy-file-request-info.ts | 8 +-- .../interfaces/file-domain-object-props.ts | 2 +- .../interfaces/file-request-info.ts | 2 +- .../interfaces/files-storage-client-config.ts | 2 +- .../copy-files-of-parent-param.builder.ts | 6 +- .../files-storage-client.mapper.spec.ts | 8 +-- .../mapper/files-storage-client.mapper.ts | 12 ++-- .../mapper/files-storage-param.builder.ts | 4 +- .../service/copy-files.service.spec.ts | 10 +-- .../service/files-storage-client.service.ts | 8 +-- .../service/files-storage.producer.ts | 32 ++++----- .../files-storage-copy-files.api.spec.ts | 4 +- .../files-storage-delete-files.api.spec.ts | 4 +- .../files-storage-download-upload.api.spec.ts | 4 +- .../files-storage-restore-files.api.spec.ts | 4 +- .../controller/files-storage.consumer.ts | 10 +-- .../entity/filerecord.entity.spec.ts | 4 +- .../files-storage/entity/filerecord.entity.ts | 16 ++--- .../files-storage/files-storage.config.ts | 6 +- .../service/files-storage.service.ts | 4 +- .../group/controller/group.controller.ts | 2 +- .../api-test/h5p-editor-delete.api.spec.ts | 4 +- .../api-test/h5p-editor-files.api.spec.ts | 6 +- .../h5p-editor-get-editor.api.spec.ts | 4 +- .../h5p-editor-get-player.api.spec.ts | 4 +- .../controller/h5p-editor.controller.ts | 6 +- .../entity/h5p-content.entity.spec.ts | 16 ++--- .../h5p-editor/entity/h5p-content.entity.ts | 4 +- .../entity/h5p-editor-tempfile.entity.ts | 6 +- .../service/contentStorage.service.spec.ts | 8 +-- .../h5p-editor/uc/h5p-delete.uc.spec.ts | 4 +- .../h5p-editor/uc/h5p-files.uc.spec.ts | 4 +- .../h5p-editor/uc/h5p-get-editor.uc.spec.ts | 4 +- .../h5p-editor/uc/h5p-get-player.uc.spec.ts | 6 +- .../h5p-editor/uc/h5p-save-create.uc.spec.ts | 12 ++-- .../common-cartridge-element.interface.ts | 2 +- .../common-cartridge-file-builder.spec.ts | 4 +- .../common-cartridge-file-builder.ts | 18 ++--- .../common-cartridge-file.interface.ts | 2 +- .../common-cartridge-lti-resource.ts | 6 +- .../common-cartridge-manifest-element.ts | 10 +-- .../common-cartridge-metadata-element.ts | 4 +- ...mon-cartridge-organization-item-element.ts | 4 +- ...ridge-organization-wrapper-element.spec.ts | 4 +- ...-cartridge-organization-wrapper-element.ts | 6 +- .../common-cartridge-resource-item-element.ts | 10 +-- ...cartridge-resource-wrapper-element.spec.ts | 4 +- ...mmon-cartridge-resource-wrapper-element.ts | 6 +- .../common-cartridge-web-content-resource.ts | 6 +- .../common-cartridge-web-link-resource.ts | 6 +- .../common-cartridge.config.ts | 2 +- .../controller/api-test/dashboard.api.spec.ts | 10 +-- .../api-test/rooms-copy-timeout.api.spec.ts | 8 +-- .../controller/api-test/rooms.api.spec.ts | 12 ++-- .../learnroom/controller/course.controller.ts | 10 +-- .../controller/dashboard.controller.spec.ts | 2 +- .../controller/dashboard.controller.ts | 2 +- .../controller/rooms.controller.spec.ts | 4 +- .../learnroom/controller/rooms.controller.ts | 6 +- .../mapper/board-taskStatus.mapper.ts | 4 +- .../common-cartridge-export.service.spec.ts | 24 +++---- .../common-cartridge-export.service.ts | 10 +-- .../src/modules/learnroom/uc/dashboard.uc.ts | 6 +- .../learnroom/uc/room-board-dto.factory.ts | 8 +-- .../lesson/controller/lesson.controller.ts | 2 +- .../lesson.repo.integration.spec.ts | 6 +- .../service/lesson-copy.service.spec.ts | 68 +++++++++--------- .../lesson/service/lesson-copy.service.ts | 71 +++++++++---------- .../lesson/service/lesson.service.spec.ts | 6 +- .../modules/lesson/service/lesson.service.ts | 4 +- .../management/seed-data/federalstates.ts | 6 +- .../src/modules/management/seed-data/roles.ts | 6 +- .../modules/management/seed-data/schools.ts | 6 +- .../management/seed-data/schoolyears.ts | 6 +- .../modules/management/seed-data/systems.ts | 6 +- .../management/uc/database-management.uc.ts | 24 +++---- .../news/controller/news.controller.ts | 2 +- .../news/controller/team-news.controller.ts | 2 +- .../modules/news/mapper/news.mapper.spec.ts | 24 +++---- .../src/modules/news/mapper/news.mapper.ts | 4 +- .../src/modules/news/uc/news.uc.spec.ts | 8 +-- apps/server/src/modules/news/uc/news.uc.ts | 6 +- .../oauth-provider.controller.spec.ts | 22 +++--- .../controller/oauth-provider.controller.ts | 14 ++-- .../oauth-provider.login-flow.service.spec.ts | 8 +-- .../oauth-provider.login-flow.service.ts | 8 +-- .../uc/oauth-provider.client-crud.uc.spec.ts | 8 +-- .../uc/oauth-provider.client-crud.uc.ts | 8 +-- .../uc/oauth-provider.consent-flow.uc.spec.ts | 12 ++-- .../uc/oauth-provider.consent-flow.uc.ts | 8 +-- .../modules/oauth/service/hydra.service.ts | 12 ++-- .../oauth/service/oauth.service.spec.ts | 4 +- .../modules/oauth/service/oauth.service.ts | 6 +- .../controller/pseudonym.controller.ts | 2 +- .../entity/external-tool-pseudonym.entity.ts | 6 +- .../repo/external-tool-pseudonym.repo.ts | 10 +-- .../src/modules/server/server.config.ts | 32 ++++----- .../api-test/sharing-create-token.api.spec.ts | 12 ++-- .../api-test/sharing-import-token.api.spec.ts | 8 +-- .../controller/share-token.controller.spec.ts | 4 +- .../controller/share-token.controller.ts | 4 +- .../sharing/entity/share-token.entity.ts | 4 +- .../modules/sharing/repo/share-token.repo.ts | 10 +-- .../controller/api-test/system.api.spec.ts | 6 +- .../api-test/submission.api.spec.ts | 10 +-- .../api-test/task-finished.api.spec.ts | 8 +-- .../task/controller/dto/task-create.params.ts | 6 +- .../task/controller/dto/task-update.params.ts | 6 +- .../task/controller/submission.controller.ts | 2 +- .../task/controller/task.controller.spec.ts | 2 +- .../task/controller/task.controller.ts | 4 +- .../modules/task/mapper/task-status.mapper.ts | 4 +- .../modules/task/mapper/task.mapper.spec.ts | 12 ++-- .../src/modules/task/mapper/task.mapper.ts | 10 +-- .../src/modules/task/uc/task.uc.spec.ts | 6 +- apps/server/src/modules/task/uc/task.uc.ts | 16 ++--- .../controller/tool-reference.controller.ts | 2 +- .../entity/context-external-tool.entity.ts | 4 +- .../service/tool-version-service.spec.ts | 10 +-- .../service/tool-version-service.ts | 12 ++-- .../tool-configuration.controller.ts | 2 +- .../controller/tool.controller.ts | 2 +- .../external-tool-configuration.service.ts | 8 +-- .../service/external-tool.service.spec.ts | 10 +-- .../service/external-tool.service.ts | 8 +-- .../external-tool/uc/external-tool.uc.spec.ts | 4 +- .../controller/tool-school.controller.ts | 22 +++--- .../entity/school-external-tool.entity.ts | 4 +- .../school-external-tool.service.spec.ts | 6 +- .../service/school-external-tool.service.ts | 2 +- .../controller/tool-launch.controller.ts | 6 +- .../abstract-launch.strategy.spec.ts | 4 +- .../abstract-launch.strategy.ts | 12 ++-- .../basic-tool-launch.strategy.spec.ts | 4 +- .../basic-tool-launch.strategy.ts | 4 +- .../lti11-tool-launch.strategy.spec.ts | 14 ++-- .../lti11-tool-launch.strategy.ts | 4 +- .../oauth2-tool-launch.strategy.spec.ts | 4 +- .../oauth2-tool-launch.strategy.ts | 4 +- .../tool-launch-params.interface.ts | 2 +- .../tool-launch-strategy.interface.ts | 6 +- .../service/tool-launch.service.spec.ts | 8 +-- .../service/tool-launch.service.ts | 8 +-- .../api-test/import-user.api.spec.ts | 38 +++++----- .../controller/import-user.controller.ts | 2 +- .../user-import/mapper/import-user.mapper.ts | 4 +- .../user-import/mapper/user-match.mapper.ts | 8 +-- .../modules/user-import/uc/user-import.uc.ts | 12 ++-- .../api-test/user-language.api.spec.ts | 6 +- .../controller/api-test/user-me.api.spec.ts | 6 +- .../user/controller/user.controller.ts | 2 +- .../modules/user/interfaces/user-config.ts | 2 +- .../src/modules/user/service/user.service.ts | 6 +- apps/server/src/modules/user/uc/user.uc.ts | 4 +- .../video-conference/bbb/bbb.service.ts | 2 +- ...o-conference-deprecated.controller.spec.ts | 2 +- .../video-conference-deprecated.controller.ts | 2 +- .../controller/video-conference.controller.ts | 2 +- .../service/video-conference.service.spec.ts | 28 ++++---- .../service/video-conference.service.ts | 16 ++--- .../uc/dto/scope-info.interface.ts | 2 +- .../uc/video-conference-create.uc.spec.ts | 18 ++--- .../uc/video-conference-create.uc.ts | 8 +-- .../uc/video-conference-deprecated.uc.spec.ts | 26 +++---- .../uc/video-conference-deprecated.uc.ts | 28 ++++---- .../uc/video-conference-end.uc.spec.ts | 16 ++--- .../uc/video-conference-end.uc.ts | 10 +-- .../uc/video-conference-info.uc.spec.ts | 26 +++---- .../uc/video-conference-info.uc.ts | 12 ++-- .../src/shared/common/error/business.error.ts | 4 +- .../interfaces/interceptor-config.ts | 2 +- .../request-logging.interceptor.ts | 2 +- .../src/shared/domain/entity/course.entity.ts | 17 ++--- .../domain/entity/coursegroup.entity.ts | 12 ++-- .../domain/entity/dashboard.entity.spec.ts | 6 +- .../shared/domain/entity/dashboard.entity.ts | 42 +++++------ .../domain/entity/dashboard.model.entity.ts | 4 +- .../domain/entity/federal-state.entity.ts | 4 +- .../domain/entity/import-user.entity.ts | 8 +-- .../entity/legacy-board/board.entity.ts | 4 +- .../column-board-target.entity.ts | 4 +- .../domain/entity/lesson.entity.spec.ts | 4 +- .../src/shared/domain/entity/lesson.entity.ts | 46 ++++++------ .../shared/domain/entity/materials.entity.ts | 16 ++--- .../src/shared/domain/entity/news.entity.ts | 16 ++--- .../src/shared/domain/entity/role.entity.ts | 4 +- .../src/shared/domain/entity/school.entity.ts | 6 +- .../shared/domain/entity/schoolyear.entity.ts | 6 +- .../domain/entity/storageprovider.entity.ts | 4 +- .../shared/domain/entity/submission.entity.ts | 4 +- .../src/shared/domain/entity/system.entity.ts | 4 +- .../src/shared/domain/entity/task.entity.ts | 22 +++--- .../src/shared/domain/entity/team.entity.ts | 8 +-- .../src/shared/domain/entity/user.entity.ts | 8 +-- .../src/shared/domain/interface/entity.ts | 2 +- .../shared/domain/interface/find-options.ts | 4 +- .../src/shared/domain/interface/learnroom.ts | 4 +- .../shared/domain/types/importuser.types.ts | 2 +- .../src/shared/domain/types/news.types.ts | 6 +- .../src/shared/domain/types/task.types.ts | 8 +-- .../repo/base.do.repo.integration.spec.ts | 20 +++--- .../context-external-tool.repo.ts | 14 ++-- .../repo/dashboard/dashboard.model.mapper.ts | 12 ++-- .../shared/repo/dashboard/dashboard.repo.ts | 4 +- .../repo/externaltool/external-tool.repo.ts | 10 +-- .../legacy-school.repo.integration.spec.ts | 8 +-- .../shared/repo/school/legacy-school.repo.ts | 8 +-- .../school-external-tool.repo.ts | 16 ++--- apps/server/src/shared/repo/scope.spec.ts | 4 +- .../user/user-do.repo.integration.spec.ts | 8 +-- .../src/shared/repo/user/user-do.repo.ts | 12 ++-- apps/server/src/shared/repo/user/user.repo.ts | 4 +- .../testing/factory/base.factory.spec.ts | 28 ++++---- .../context-external-tool-entity.factory.ts | 6 +- .../shared/testing/factory/course.factory.ts | 14 ++-- .../testing/factory/coursegroup.factory.ts | 8 +-- .../external-tool-pseudonym.factory.ts | 6 +- .../testing/factory/federal-state.factory.ts | 4 +- .../testing/factory/filerecord.factory.ts | 6 +- .../testing/factory/h5p-content.factory.ts | 4 +- .../factory/h5p-temporary-file.factory.ts | 6 +- .../testing/factory/import-user.factory.ts | 6 +- .../shared/testing/factory/lesson.factory.ts | 8 +-- .../testing/factory/material.factory.ts | 6 +- .../shared/testing/factory/news.factory.ts | 14 ++-- .../shared/testing/factory/role.factory.ts | 4 +- .../school-external-tool-entity.factory.ts | 4 +- .../shared/testing/factory/school.factory.ts | 6 +- .../testing/factory/schoolyear.factory.ts | 4 +- .../factory/storageprovider.factory.ts | 4 +- .../testing/factory/submission.factory.ts | 12 ++-- .../shared/testing/factory/system.factory.ts | 8 +-- .../shared/testing/factory/task.factory.ts | 12 ++-- .../shared/testing/factory/team.factory.ts | 12 ++-- .../shared/testing/factory/user.factory.ts | 14 ++-- .../testing/map-user-to-current-user.ts | 2 +- config/README.md | 8 +-- 321 files changed, 1279 insertions(+), 1285 deletions(-) diff --git a/TODO.md b/TODO.md index 90f2bcfd358..7fa92508759 100644 --- a/TODO.md +++ b/TODO.md @@ -88,4 +88,4 @@ - naming of dtos and dto-files: api vs domain, we leave out "dto" suffix for simplicity (we know that they are dtos) and instead append a specific suffix: e.g. api: , , - domain: , + domain: , diff --git a/apps/server/src/core/error/filter/global-error.filter.ts b/apps/server/src/core/error/filter/global-error.filter.ts index 314c8247d18..068e7bcca8a 100644 --- a/apps/server/src/core/error/filter/global-error.filter.ts +++ b/apps/server/src/core/error/filter/global-error.filter.ts @@ -1,6 +1,6 @@ +import { IError, RpcMessage } from '@infra/rabbitmq/rpc-message'; import { ArgumentsHost, Catch, ExceptionFilter, HttpException, InternalServerErrorException } from '@nestjs/common'; import { ApiValidationError, BusinessError } from '@shared/common'; -import { IError, RpcMessage } from '@infra/rabbitmq/rpc-message'; import { ErrorLogger, Loggable } from '@src/core/logger'; import { LoggingUtils } from '@src/core/logger/logging.utils'; import { Response } from 'express'; diff --git a/apps/server/src/core/error/interface/error-type.interface.ts b/apps/server/src/core/error/interface/error-type.interface.ts index 9ea4939da89..006fa18c793 100644 --- a/apps/server/src/core/error/interface/error-type.interface.ts +++ b/apps/server/src/core/error/interface/error-type.interface.ts @@ -1,4 +1,4 @@ -export interface IErrorType { +export interface ErrorType { readonly type: string; readonly title: string; readonly defaultMessage: string; diff --git a/apps/server/src/core/error/server-error-types.ts b/apps/server/src/core/error/server-error-types.ts index 3224aec5016..91538d75cc3 100644 --- a/apps/server/src/core/error/server-error-types.ts +++ b/apps/server/src/core/error/server-error-types.ts @@ -1,13 +1,13 @@ /** * all errors defined in the application require a definition, - * implementing the @IErrorType where their type is unambigious within of the application. + * implementing the @ErrorType where their type is unambigious within of the application. * */ import legacyErrorTypes = require('../../../../../src/errors/commonErrorTypes'); -import { IErrorType } from './interface/error-type.interface'; +import { ErrorType } from './interface/error-type.interface'; -// check legacy error typing is matching IErrorType -const serverErrorTypes: { [index: string]: IErrorType } = legacyErrorTypes; +// check legacy error typing is matching ErrorType +const serverErrorTypes: { [index: string]: ErrorType } = legacyErrorTypes; // re-use legacy error types export const { INTERNAL_SERVER_ERROR_TYPE, ASSERTION_ERROR_TYPE, API_VALIDATION_ERROR_TYPE, FORBIDDEN_ERROR_TYPE } = @@ -15,7 +15,7 @@ export const { INTERNAL_SERVER_ERROR_TYPE, ASSERTION_ERROR_TYPE, API_VALIDATION_ // further error types -export const NOT_FOUND_ERROR_TYPE: IErrorType = { +export const NOT_FOUND_ERROR_TYPE: ErrorType = { type: 'NOT_FOUND_ERROR', title: 'Not Found', defaultMessage: 'The requested ressource has not been found.', diff --git a/apps/server/src/core/interceptor/interceptor.module.ts b/apps/server/src/core/interceptor/interceptor.module.ts index 6815e44a867..12f12306335 100644 --- a/apps/server/src/core/interceptor/interceptor.module.ts +++ b/apps/server/src/core/interceptor/interceptor.module.ts @@ -1,7 +1,7 @@ import { ClassSerializerInterceptor, Module } from '@nestjs/common'; -import { APP_INTERCEPTOR } from '@nestjs/core'; -import { IInterceptorConfig, TimeoutInterceptor } from '@shared/common'; import { ConfigService } from '@nestjs/config'; +import { APP_INTERCEPTOR } from '@nestjs/core'; +import { InterceptorConfig, TimeoutInterceptor } from '@shared/common'; /** ********************************************* * Global Interceptor setup @@ -18,7 +18,7 @@ import { ConfigService } from '@nestjs/config'; }, { provide: APP_INTERCEPTOR, // TODO remove (for testing) - useFactory: (configService: ConfigService) => { + useFactory: (configService: ConfigService) => { const timeout = configService.get('INCOMING_REQUEST_TIMEOUT'); return new TimeoutInterceptor(timeout); }, diff --git a/apps/server/src/core/interfaces/core-module-config.ts b/apps/server/src/core/interfaces/core-module-config.ts index 21f4cd51b7d..a51eb7bd08d 100644 --- a/apps/server/src/core/interfaces/core-module-config.ts +++ b/apps/server/src/core/interfaces/core-module-config.ts @@ -1,4 +1,4 @@ -import { IInterceptorConfig } from '@shared/common'; -import { ILoggerConfig } from '../logger'; +import { InterceptorConfig } from '@shared/common'; +import { LoggerConfig } from '../logger'; -export interface ICoreModuleConfig extends IInterceptorConfig, ILoggerConfig {} +export interface CoreModuleConfig extends InterceptorConfig, LoggerConfig {} diff --git a/apps/server/src/core/logger/interfaces/logger-config.ts b/apps/server/src/core/logger/interfaces/logger-config.ts index d9fe1e86538..0d9c651ec70 100644 --- a/apps/server/src/core/logger/interfaces/logger-config.ts +++ b/apps/server/src/core/logger/interfaces/logger-config.ts @@ -1,3 +1,3 @@ -export interface ILoggerConfig { +export interface LoggerConfig { NEST_LOG_LEVEL: string; } diff --git a/apps/server/src/core/logger/logger.module.ts b/apps/server/src/core/logger/logger.module.ts index 7751a48b8aa..477037b7cfa 100644 --- a/apps/server/src/core/logger/logger.module.ts +++ b/apps/server/src/core/logger/logger.module.ts @@ -3,14 +3,14 @@ import { ConfigService } from '@nestjs/config'; import { utilities, WinstonModule } from 'nest-winston'; import winston from 'winston'; import { ErrorLogger } from './error-logger'; -import { ILoggerConfig } from './interfaces'; +import { LoggerConfig } from './interfaces'; import { LegacyLogger } from './legacy-logger.service'; import { Logger } from './logger'; @Module({ imports: [ WinstonModule.forRootAsync({ - useFactory: (configService: ConfigService) => { + useFactory: (configService: ConfigService) => { return { levels: winston.config.syslog.levels, level: configService.get('NEST_LOG_LEVEL'), diff --git a/apps/server/src/infra/calendar/interface/calendar-event.interface.ts b/apps/server/src/infra/calendar/interface/calendar-event.interface.ts index b9c6a1e6293..1ce70acdf87 100644 --- a/apps/server/src/infra/calendar/interface/calendar-event.interface.ts +++ b/apps/server/src/infra/calendar/interface/calendar-event.interface.ts @@ -1,4 +1,4 @@ -export interface ICalendarEvent { +export interface CalendarEvent { data: { attributes: { summary: string; diff --git a/apps/server/src/infra/calendar/mapper/calendar.mapper.spec.ts b/apps/server/src/infra/calendar/mapper/calendar.mapper.spec.ts index 9679ce0a4fa..f84427791b9 100644 --- a/apps/server/src/infra/calendar/mapper/calendar.mapper.spec.ts +++ b/apps/server/src/infra/calendar/mapper/calendar.mapper.spec.ts @@ -1,4 +1,4 @@ -import { ICalendarEvent } from '@infra/calendar/interface/calendar-event.interface'; +import { CalendarEvent } from '@infra/calendar/interface/calendar-event.interface'; import { Test, TestingModule } from '@nestjs/testing'; import { CalendarMapper } from './calendar.mapper'; @@ -6,7 +6,7 @@ describe('CalendarMapper', () => { let module: TestingModule; let mapper: CalendarMapper; - const event: ICalendarEvent = { + const event: CalendarEvent = { data: [ { attributes: { diff --git a/apps/server/src/infra/calendar/mapper/calendar.mapper.ts b/apps/server/src/infra/calendar/mapper/calendar.mapper.ts index a75ff01eae6..c229df3c278 100644 --- a/apps/server/src/infra/calendar/mapper/calendar.mapper.ts +++ b/apps/server/src/infra/calendar/mapper/calendar.mapper.ts @@ -1,10 +1,10 @@ -import { ICalendarEvent } from '@infra/calendar/interface/calendar-event.interface'; +import { CalendarEvent } from '@infra/calendar/interface/calendar-event.interface'; import { Injectable } from '@nestjs/common'; import { CalendarEventDto } from '../dto/calendar-event.dto'; @Injectable() export class CalendarMapper { - mapToDto(event: ICalendarEvent): CalendarEventDto { + mapToDto(event: CalendarEvent): CalendarEventDto { const { attributes } = event.data[0]; return new CalendarEventDto({ teamId: attributes['x-sc-teamid'], diff --git a/apps/server/src/infra/calendar/service/calendar.service.spec.ts b/apps/server/src/infra/calendar/service/calendar.service.spec.ts index ed6bb4620bc..95d1fa95e22 100644 --- a/apps/server/src/infra/calendar/service/calendar.service.spec.ts +++ b/apps/server/src/infra/calendar/service/calendar.service.spec.ts @@ -1,14 +1,14 @@ -import { DeepMocked, createMock } from '@golevelup/ts-jest'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { Configuration } from '@hpi-schul-cloud/commons/lib'; +import { CalendarEventDto, CalendarService } from '@infra/calendar'; import { HttpService } from '@nestjs/axios'; import { InternalServerErrorException } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; -import { CalendarEventDto, CalendarService } from '@infra/calendar'; import { axiosResponseFactory } from '@shared/testing'; import { AxiosResponse } from 'axios'; import { of, throwError } from 'rxjs'; +import { CalendarEvent } from '../interface/calendar-event.interface'; import { CalendarMapper } from '../mapper/calendar.mapper'; -import { ICalendarEvent } from '../interface/calendar-event.interface'; describe('CalendarServiceSpec', () => { let module: TestingModule; @@ -56,7 +56,7 @@ describe('CalendarServiceSpec', () => { const title = 'eventTitle'; const teamId = 'teamId'; - const event: ICalendarEvent = { + const event: CalendarEvent = { data: [ { attributes: { @@ -66,7 +66,7 @@ describe('CalendarServiceSpec', () => { }, ], }; - const axiosResponse: AxiosResponse = axiosResponseFactory.build({ + const axiosResponse: AxiosResponse = axiosResponseFactory.build({ data: event, }); httpService.get.mockReturnValue(of(axiosResponse)); diff --git a/apps/server/src/infra/calendar/service/calendar.service.ts b/apps/server/src/infra/calendar/service/calendar.service.ts index 3bf2a6576be..7f39c21ae58 100644 --- a/apps/server/src/infra/calendar/service/calendar.service.ts +++ b/apps/server/src/infra/calendar/service/calendar.service.ts @@ -4,11 +4,11 @@ import { Injectable, InternalServerErrorException } from '@nestjs/common'; import { EntityId } from '@shared/domain'; import { ErrorUtils } from '@src/core/error/utils'; import { AxiosRequestConfig, AxiosResponse } from 'axios'; -import { Observable, firstValueFrom } from 'rxjs'; +import { firstValueFrom, Observable } from 'rxjs'; import { URL, URLSearchParams } from 'url'; -import { CalendarMapper } from '../mapper/calendar.mapper'; import { CalendarEventDto } from '../dto/calendar-event.dto'; -import { ICalendarEvent } from '../interface/calendar-event.interface'; +import { CalendarEvent } from '../interface/calendar-event.interface'; +import { CalendarMapper } from '../mapper/calendar.mapper'; @Injectable() export class CalendarService { @@ -34,7 +34,7 @@ export class CalendarService { timeout: this.timeoutMs, }) ) - .then((resp: AxiosResponse) => this.calendarMapper.mapToDto(resp.data)) + .then((resp: AxiosResponse) => this.calendarMapper.mapToDto(resp.data)) .catch((error) => { throw new InternalServerErrorException( null, @@ -47,7 +47,7 @@ export class CalendarService { path: string, queryParams: URLSearchParams, config: AxiosRequestConfig - ): Observable> { + ): Observable> { const url: URL = new URL(this.baseURL); url.pathname = path; url.search = queryParams.toString(); diff --git a/apps/server/src/infra/collaborative-storage/collaborative-storage-adapter.module.ts b/apps/server/src/infra/collaborative-storage/collaborative-storage-adapter.module.ts index f60ff664654..3415d0dbdc3 100644 --- a/apps/server/src/infra/collaborative-storage/collaborative-storage-adapter.module.ts +++ b/apps/server/src/infra/collaborative-storage/collaborative-storage-adapter.module.ts @@ -1,18 +1,18 @@ import { Configuration } from '@hpi-schul-cloud/commons/lib'; +import { PseudonymModule } from '@modules/pseudonym'; +import { ToolModule } from '@modules/tool'; +import { UserModule } from '@modules/user'; import { HttpModule } from '@nestjs/axios'; import { Module, Provider } from '@nestjs/common'; import { LtiToolRepo } from '@shared/repo/ltitool/'; import { LoggerModule } from '@src/core/logger'; -import { ToolModule } from '@modules/tool'; -import { PseudonymModule } from '@modules/pseudonym'; -import { UserModule } from '@modules/user'; -import { NextcloudStrategy } from './strategy/nextcloud/nextcloud.strategy'; -import { NextcloudClient } from './strategy/nextcloud/nextcloud.client'; -import { CollaborativeStorageAdapterMapper } from './mapper'; import { CollaborativeStorageAdapter } from './collaborative-storage.adapter'; +import { CollaborativeStorageAdapterMapper } from './mapper'; +import { NextcloudClient } from './strategy/nextcloud/nextcloud.client'; +import { NextcloudStrategy } from './strategy/nextcloud/nextcloud.strategy'; const storageStrategy: Provider = { - provide: 'ICollaborativeStorageStrategy', + provide: 'CollaborativeStorageStrategy', useExisting: NextcloudStrategy, }; diff --git a/apps/server/src/infra/collaborative-storage/collaborative-storage.adapter.spec.ts b/apps/server/src/infra/collaborative-storage/collaborative-storage.adapter.spec.ts index 3b9ba2175e6..3742c3ff408 100644 --- a/apps/server/src/infra/collaborative-storage/collaborative-storage.adapter.spec.ts +++ b/apps/server/src/infra/collaborative-storage/collaborative-storage.adapter.spec.ts @@ -1,14 +1,14 @@ import { createMock } from '@golevelup/ts-jest'; import { ObjectId } from '@mikro-orm/mongodb'; +import { TeamDto } from '@modules/collaborative-storage/services/dto/team.dto'; // invalid import please fix import { Test, TestingModule } from '@nestjs/testing'; import { RoleName } from '@shared/domain'; import { LegacyLogger } from '@src/core/logger'; -import { TeamDto } from '@modules/collaborative-storage/services/dto/team.dto'; // invalid import please fix import { CollaborativeStorageAdapter } from './collaborative-storage.adapter'; import { CollaborativeStorageAdapterMapper } from './mapper/collaborative-storage-adapter.mapper'; -import { ICollaborativeStorageStrategy } from './strategy/base.interface.strategy'; +import { CollaborativeStorageStrategy } from './strategy/base.interface.strategy'; -class TestStrategy implements ICollaborativeStorageStrategy { +class TestStrategy implements CollaborativeStorageStrategy { baseURL: string; constructor() { @@ -38,7 +38,7 @@ class TestStrategy implements ICollaborativeStorageStrategy { describe('CollaborativeStorage Adapter', () => { let module: TestingModule; let adapter: CollaborativeStorageAdapter; - let strategy: ICollaborativeStorageStrategy; + let strategy: CollaborativeStorageStrategy; beforeAll(async () => { module = await Test.createTestingModule({ @@ -50,8 +50,8 @@ describe('CollaborativeStorage Adapter', () => { useValue: createMock(), }, { - provide: 'ICollaborativeStorageStrategy', - useValue: createMock(), + provide: 'CollaborativeStorageStrategy', + useValue: createMock(), }, ], }).compile(); diff --git a/apps/server/src/infra/collaborative-storage/collaborative-storage.adapter.ts b/apps/server/src/infra/collaborative-storage/collaborative-storage.adapter.ts index b50657f393e..27cfdd1b201 100644 --- a/apps/server/src/infra/collaborative-storage/collaborative-storage.adapter.ts +++ b/apps/server/src/infra/collaborative-storage/collaborative-storage.adapter.ts @@ -1,10 +1,10 @@ import { TeamPermissionsDto } from '@modules/collaborative-storage/services/dto/team-permissions.dto'; import { TeamDto } from '@modules/collaborative-storage/services/dto/team.dto'; +import { RoleDto } from '@modules/role/service/dto/role.dto'; import { Inject, Injectable } from '@nestjs/common'; import { LegacyLogger } from '@src/core/logger'; -import { RoleDto } from '@modules/role/service/dto/role.dto'; import { CollaborativeStorageAdapterMapper } from './mapper/collaborative-storage-adapter.mapper'; -import { ICollaborativeStorageStrategy } from './strategy/base.interface.strategy'; +import { CollaborativeStorageStrategy } from './strategy/base.interface.strategy'; /** * Provides an Adapter to an external collaborative storage. @@ -12,10 +12,10 @@ import { ICollaborativeStorageStrategy } from './strategy/base.interface.strateg */ @Injectable() export class CollaborativeStorageAdapter { - strategy: ICollaborativeStorageStrategy; + strategy: CollaborativeStorageStrategy; constructor( - @Inject('ICollaborativeStorageStrategy') strategy: ICollaborativeStorageStrategy, + @Inject('CollaborativeStorageStrategy') strategy: CollaborativeStorageStrategy, private mapper: CollaborativeStorageAdapterMapper, private logger: LegacyLogger ) { @@ -27,7 +27,7 @@ export class CollaborativeStorageAdapter { * Set the strategy that should be used by the adapter * @param strategy The strategy */ - setStrategy(strategy: ICollaborativeStorageStrategy) { + setStrategy(strategy: CollaborativeStorageStrategy) { this.strategy = strategy; } diff --git a/apps/server/src/infra/collaborative-storage/strategy/base.interface.strategy.ts b/apps/server/src/infra/collaborative-storage/strategy/base.interface.strategy.ts index f960452df5f..3021d53dcb7 100644 --- a/apps/server/src/infra/collaborative-storage/strategy/base.interface.strategy.ts +++ b/apps/server/src/infra/collaborative-storage/strategy/base.interface.strategy.ts @@ -4,7 +4,7 @@ import { TeamRolePermissionsDto } from '../dto/team-role-permissions.dto'; /** * base interface for all CollaborativeStorage Strategies */ -export interface ICollaborativeStorageStrategy { +export interface CollaborativeStorageStrategy { /** * Updates The Permissions for the given Role in the given Team * @param dto The DTO to be processed diff --git a/apps/server/src/infra/collaborative-storage/strategy/nextcloud/nextcloud.strategy.ts b/apps/server/src/infra/collaborative-storage/strategy/nextcloud/nextcloud.strategy.ts index 6b75d6ec76f..b7312f9158e 100644 --- a/apps/server/src/infra/collaborative-storage/strategy/nextcloud/nextcloud.strategy.ts +++ b/apps/server/src/infra/collaborative-storage/strategy/nextcloud/nextcloud.strategy.ts @@ -1,24 +1,24 @@ +import { TeamDto, TeamUserDto } from '@modules/collaborative-storage'; +import { PseudonymService } from '@modules/pseudonym'; +import { ExternalTool } from '@modules/tool/external-tool/domain'; +import { ExternalToolService } from '@modules/tool/external-tool/service'; +import { UserService } from '@modules/user'; import { Injectable, UnprocessableEntityException } from '@nestjs/common'; import { Pseudonym, UserDO } from '@shared/domain/'; import { LtiToolDO } from '@shared/domain/domainobject/ltitool.do'; import { LtiToolRepo } from '@shared/repo/ltitool/'; import { LegacyLogger } from '@src/core/logger'; -import { TeamDto, TeamUserDto } from '@modules/collaborative-storage'; -import { PseudonymService } from '@modules/pseudonym'; -import { UserService } from '@modules/user'; -import { ExternalToolService } from '@modules/tool/external-tool/service'; -import { ExternalTool } from '@modules/tool/external-tool/domain'; import { TeamRolePermissionsDto } from '../../dto/team-role-permissions.dto'; -import { ICollaborativeStorageStrategy } from '../base.interface.strategy'; +import { CollaborativeStorageStrategy } from '../base.interface.strategy'; import { NextcloudClient } from './nextcloud.client'; /** * Nextcloud Strategy Implementation for Collaborative Storage * - * @implements {ICollaborativeStorageStrategy} + * @implements {CollaborativeStorageStrategy} */ @Injectable() -export class NextcloudStrategy implements ICollaborativeStorageStrategy { +export class NextcloudStrategy implements CollaborativeStorageStrategy { constructor( private readonly logger: LegacyLogger, private readonly client: NextcloudClient, diff --git a/apps/server/src/infra/encryption/encryption.interface.ts b/apps/server/src/infra/encryption/encryption.interface.ts index b8a26d7d145..062e7184d0e 100644 --- a/apps/server/src/infra/encryption/encryption.interface.ts +++ b/apps/server/src/infra/encryption/encryption.interface.ts @@ -1,7 +1,7 @@ export const DefaultEncryptionService = Symbol('DefaultEncryptionService'); export const LdapEncryptionService = Symbol('LdapEncryptionService'); -export interface IEncryptionService { +export interface EncryptionService { encrypt(data: string): string; decrypt(data: string): string; } diff --git a/apps/server/src/infra/encryption/encryption.module.spec.ts b/apps/server/src/infra/encryption/encryption.module.spec.ts index 2625f37a321..c65896efeaf 100644 --- a/apps/server/src/infra/encryption/encryption.module.spec.ts +++ b/apps/server/src/infra/encryption/encryption.module.spec.ts @@ -1,11 +1,11 @@ import { ConfigModule } from '@nestjs/config'; import { Test, TestingModule } from '@nestjs/testing'; -import { DefaultEncryptionService, EncryptionModule, IEncryptionService, LdapEncryptionService } from '.'; +import { DefaultEncryptionService, EncryptionModule, EncryptionService, LdapEncryptionService } from '.'; describe('EncryptionModule', () => { let module: TestingModule; - let defaultService: IEncryptionService; - let ldapService: IEncryptionService; + let defaultService: EncryptionService; + let ldapService: EncryptionService; beforeAll(async () => { module = await Test.createTestingModule({ diff --git a/apps/server/src/infra/encryption/encryption.service.ts b/apps/server/src/infra/encryption/encryption.service.ts index a443503e9ef..d23a7395675 100644 --- a/apps/server/src/infra/encryption/encryption.service.ts +++ b/apps/server/src/infra/encryption/encryption.service.ts @@ -2,10 +2,10 @@ import CryptoJs from 'crypto-js'; import { Injectable } from '@nestjs/common'; import { LegacyLogger } from '@src/core/logger'; -import { IEncryptionService } from './encryption.interface'; +import { EncryptionService } from './encryption.interface'; @Injectable() -export class SymetricKeyEncryptionService implements IEncryptionService { +export class SymetricKeyEncryptionService implements EncryptionService { constructor(private logger: LegacyLogger, private key?: string) { if (!this.key) { this.logger.warn('No AES key defined. Encryption will no work'); diff --git a/apps/server/src/infra/identity-management/identity-management.config.ts b/apps/server/src/infra/identity-management/identity-management.config.ts index 39ff137fb2b..5c4d8953813 100644 --- a/apps/server/src/infra/identity-management/identity-management.config.ts +++ b/apps/server/src/infra/identity-management/identity-management.config.ts @@ -1,4 +1,4 @@ -export interface IIdentityManagementConfig { +export interface IdentityManagementConfig { FEATURE_IDENTITY_MANAGEMENT_ENABLED: boolean; FEATURE_IDENTITY_MANAGEMENT_STORE_ENABLED: boolean; FEATURE_IDENTITY_MANAGEMENT_LOGIN_ENABLED: boolean; diff --git a/apps/server/src/infra/identity-management/keycloak-configuration/console/keycloak-configuration.console.ts b/apps/server/src/infra/identity-management/keycloak-configuration/console/keycloak-configuration.console.ts index 85d3f7a5a3c..10daef11317 100644 --- a/apps/server/src/infra/identity-management/keycloak-configuration/console/keycloak-configuration.console.ts +++ b/apps/server/src/infra/identity-management/keycloak-configuration/console/keycloak-configuration.console.ts @@ -5,18 +5,18 @@ import { KeycloakConfigurationUc } from '../uc/keycloak-configuration.uc'; const defaultError = new Error('IDM is not reachable or authentication failed.'); -interface IRetryOptions { +interface RetryOptions { retryCount?: number; retryDelay?: number; } -interface IMigrationOptions { +interface MigrationOptions { skip?: number; query?: string; verbose?: boolean; } -interface ICleanOptions { +interface CleanOptions { pageSize?: number; } @Console({ command: 'idm', description: 'Prefixes all Identity Management (IDM) related console commands.' }) @@ -74,7 +74,7 @@ export class KeycloakConsole { }, ], }) - async clean(options: IRetryOptions & ICleanOptions): Promise { + async clean(options: RetryOptions & CleanOptions): Promise { await this.repeatCommand( 'clean', async () => { @@ -96,7 +96,7 @@ export class KeycloakConsole { description: 'Add all seed users to the IDM.', options: KeycloakConsole.retryFlags, }) - async seed(options: IRetryOptions): Promise { + async seed(options: RetryOptions): Promise { await this.repeatCommand( 'seed', async () => { @@ -118,7 +118,7 @@ export class KeycloakConsole { description: 'Configures Keycloak identity providers.', options: [...KeycloakConsole.retryFlags], }) - async configure(options: IRetryOptions): Promise { + async configure(options: RetryOptions): Promise { await this.repeatCommand( 'configure', async () => { @@ -153,7 +153,7 @@ export class KeycloakConsole { }, ], }) - async migrate(options: IRetryOptions & IMigrationOptions): Promise { + async migrate(options: RetryOptions & MigrationOptions): Promise { await this.repeatCommand( 'migrate', async () => { diff --git a/apps/server/src/infra/identity-management/keycloak-configuration/interface/json-account.interface.ts b/apps/server/src/infra/identity-management/keycloak-configuration/interface/json-account.interface.ts index 7a284b70a6b..2b8d74a1006 100644 --- a/apps/server/src/infra/identity-management/keycloak-configuration/interface/json-account.interface.ts +++ b/apps/server/src/infra/identity-management/keycloak-configuration/interface/json-account.interface.ts @@ -1,4 +1,4 @@ -export interface IJsonAccount { +export interface JsonAccount { _id: { $oid: string; }; diff --git a/apps/server/src/infra/identity-management/keycloak-configuration/interface/json-user.interface.ts b/apps/server/src/infra/identity-management/keycloak-configuration/interface/json-user.interface.ts index 79ff1d11708..2e4f3f89c9d 100644 --- a/apps/server/src/infra/identity-management/keycloak-configuration/interface/json-user.interface.ts +++ b/apps/server/src/infra/identity-management/keycloak-configuration/interface/json-user.interface.ts @@ -1,4 +1,4 @@ -export interface IJsonUser { +export interface JsonUser { _id: { $oid: string; }; diff --git a/apps/server/src/infra/identity-management/keycloak-configuration/mapper/identity-provider.mapper.ts b/apps/server/src/infra/identity-management/keycloak-configuration/mapper/identity-provider.mapper.ts index 6573ed35a5b..a7f9e360074 100644 --- a/apps/server/src/infra/identity-management/keycloak-configuration/mapper/identity-provider.mapper.ts +++ b/apps/server/src/infra/identity-management/keycloak-configuration/mapper/identity-provider.mapper.ts @@ -1,10 +1,10 @@ +import { DefaultEncryptionService, EncryptionService } from '@infra/encryption'; import IdentityProviderRepresentation from '@keycloak/keycloak-admin-client/lib/defs/identityProviderRepresentation'; -import { Inject } from '@nestjs/common'; -import { DefaultEncryptionService, IEncryptionService } from '@infra/encryption'; import { OidcConfigDto } from '@modules/system/service'; +import { Inject } from '@nestjs/common'; export class OidcIdentityProviderMapper { - constructor(@Inject(DefaultEncryptionService) private readonly defaultEncryptionService: IEncryptionService) {} + constructor(@Inject(DefaultEncryptionService) private readonly defaultEncryptionService: EncryptionService) {} public mapToKeycloakIdentityProvider(oidcConfig: OidcConfigDto, flowAlias: string): IdentityProviderRepresentation { return { diff --git a/apps/server/src/infra/identity-management/keycloak-configuration/service/keycloak-configuration.service.spec.ts b/apps/server/src/infra/identity-management/keycloak-configuration/service/keycloak-configuration.service.spec.ts index 61e475fe3e3..0b47e876ba0 100644 --- a/apps/server/src/infra/identity-management/keycloak-configuration/service/keycloak-configuration.service.spec.ts +++ b/apps/server/src/infra/identity-management/keycloak-configuration/service/keycloak-configuration.service.spec.ts @@ -1,4 +1,5 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { SymetricKeyEncryptionService } from '@infra/encryption'; import KeycloakAdminClient from '@keycloak/keycloak-admin-client-cjs/keycloak-admin-client-cjs-index'; import IdentityProviderRepresentation from '@keycloak/keycloak-admin-client/lib/defs/identityProviderRepresentation'; import UserRepresentation from '@keycloak/keycloak-admin-client/lib/defs/userRepresentation'; @@ -6,14 +7,13 @@ import { AuthenticationManagement } from '@keycloak/keycloak-admin-client/lib/re import { Clients } from '@keycloak/keycloak-admin-client/lib/resources/clients'; import { IdentityProviders } from '@keycloak/keycloak-admin-client/lib/resources/identityProviders'; import { Realms } from '@keycloak/keycloak-admin-client/lib/resources/realms'; +import { SystemOidcMapper } from '@modules/system/mapper/system-oidc.mapper'; +import { SystemOidcService } from '@modules/system/service/system-oidc.service'; import { HttpService } from '@nestjs/axios'; import { ConfigService } from '@nestjs/config'; import { Test, TestingModule } from '@nestjs/testing'; import { SystemEntity, SystemTypeEnum } from '@shared/domain'; -import { SymetricKeyEncryptionService } from '@infra/encryption'; import { systemFactory } from '@shared/testing'; -import { SystemOidcMapper } from '@modules/system/mapper/system-oidc.mapper'; -import { SystemOidcService } from '@modules/system/service/system-oidc.service'; import { AxiosResponse } from 'axios'; import { of } from 'rxjs'; import { v1 } from 'uuid'; diff --git a/apps/server/src/infra/identity-management/keycloak-configuration/service/keycloak-configuration.service.ts b/apps/server/src/infra/identity-management/keycloak-configuration/service/keycloak-configuration.service.ts index ae7f2631bce..89389a5318d 100644 --- a/apps/server/src/infra/identity-management/keycloak-configuration/service/keycloak-configuration.service.ts +++ b/apps/server/src/infra/identity-management/keycloak-configuration/service/keycloak-configuration.service.ts @@ -4,11 +4,11 @@ import ClientRepresentation from '@keycloak/keycloak-admin-client/lib/defs/clien import IdentityProviderMapperRepresentation from '@keycloak/keycloak-admin-client/lib/defs/identityProviderMapperRepresentation'; import IdentityProviderRepresentation from '@keycloak/keycloak-admin-client/lib/defs/identityProviderRepresentation'; import ProtocolMapperRepresentation from '@keycloak/keycloak-admin-client/lib/defs/protocolMapperRepresentation'; -import { Injectable } from '@nestjs/common'; -import { ConfigService } from '@nestjs/config'; -import { IServerConfig } from '@modules/server/server.config'; +import { ServerConfig } from '@modules/server/server.config'; import { OidcConfigDto } from '@modules/system/service'; import { SystemOidcService } from '@modules/system/service/system-oidc.service'; +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; import { KeycloakAdministrationService } from '../../keycloak-administration/service/keycloak-administration.service'; import { OidcIdentityProviderMapper } from '../mapper/identity-provider.mapper'; @@ -26,7 +26,7 @@ const oidcExternalSubMapperName = 'External Sub Mapper'; export class KeycloakConfigurationService { constructor( private readonly kcAdmin: KeycloakAdministrationService, - private readonly configService: ConfigService, + private readonly configService: ConfigService, private readonly oidcIdentityProviderMapper: OidcIdentityProviderMapper, private readonly systemOidcService: SystemOidcService ) {} diff --git a/apps/server/src/infra/identity-management/keycloak-configuration/service/keycloak-seed.service.spec.ts b/apps/server/src/infra/identity-management/keycloak-configuration/service/keycloak-seed.service.spec.ts index 19a3b28326e..f94513c1bd5 100644 --- a/apps/server/src/infra/identity-management/keycloak-configuration/service/keycloak-seed.service.spec.ts +++ b/apps/server/src/infra/identity-management/keycloak-configuration/service/keycloak-seed.service.spec.ts @@ -1,18 +1,18 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; -import { LegacyLogger } from '@src/core/logger'; import KeycloakAdminClient from '@keycloak/keycloak-admin-client-cjs/keycloak-admin-client-cjs-index'; import UserRepresentation from '@keycloak/keycloak-admin-client/lib/defs/userRepresentation'; import { AuthenticationManagement } from '@keycloak/keycloak-admin-client/lib/resources/authenticationManagement'; import { Users } from '@keycloak/keycloak-admin-client/lib/resources/users'; import { Test, TestingModule } from '@nestjs/testing'; +import { LegacyLogger } from '@src/core/logger'; import { v1 } from 'uuid'; import { IKeycloakSettings, KeycloakSettings, } from '../../keycloak-administration/interface/keycloak-settings.interface'; import { KeycloakAdministrationService } from '../../keycloak-administration/service/keycloak-administration.service'; -import { IJsonAccount } from '../interface/json-account.interface'; -import { IJsonUser } from '../interface/json-user.interface'; +import { JsonAccount } from '../interface/json-account.interface'; +import { JsonUser } from '../interface/json-user.interface'; import { IKeycloakConfigurationInputFiles, KeycloakConfigurationInputFiles, @@ -21,8 +21,8 @@ import { KeycloakSeedService } from './keycloak-seed.service'; const accountsFile = 'accounts.json'; const usersFile = 'users.json'; -let jsonAccounts: IJsonAccount[]; -let jsonUsers: IJsonUser[]; +let jsonAccounts: JsonAccount[]; +let jsonUsers: JsonUser[]; jest.mock('node:fs/promises', () => { return { @@ -52,8 +52,8 @@ describe('KeycloakSeedService', () => { const kcApiAuthenticationManagementMock = createMock(); const adminUsername = 'admin'; - let validAccountsNoDuplicates: IJsonAccount[]; - let validAccounts: IJsonAccount[]; + let validAccountsNoDuplicates: JsonAccount[]; + let validAccounts: JsonAccount[]; const adminUser: UserRepresentation = { id: v1(), diff --git a/apps/server/src/infra/identity-management/keycloak-configuration/service/keycloak-seed.service.ts b/apps/server/src/infra/identity-management/keycloak-configuration/service/keycloak-seed.service.ts index eaf08cebe27..5ba3071c2b4 100644 --- a/apps/server/src/infra/identity-management/keycloak-configuration/service/keycloak-seed.service.ts +++ b/apps/server/src/infra/identity-management/keycloak-configuration/service/keycloak-seed.service.ts @@ -2,12 +2,12 @@ import UserRepresentation from '@keycloak/keycloak-admin-client/lib/defs/userRep import { Inject } from '@nestjs/common'; import { LegacyLogger } from '@src/core/logger'; import fs from 'node:fs/promises'; -import { IJsonAccount } from '../interface/json-account.interface'; -import { IJsonUser } from '../interface/json-user.interface'; import { KeycloakAdministrationService } from '../../keycloak-administration/service/keycloak-administration.service'; +import { JsonAccount } from '../interface/json-account.interface'; +import { JsonUser } from '../interface/json-user.interface'; import { - KeycloakConfigurationInputFiles, IKeycloakConfigurationInputFiles, + KeycloakConfigurationInputFiles, } from '../interface/keycloak-configuration-input-files.interface'; export class KeycloakSeedService { @@ -57,7 +57,7 @@ export class KeycloakSeedService { return deletedUsers; } - private async createOrUpdateIdmAccount(account: IJsonAccount, user: IJsonUser): Promise { + private async createOrUpdateIdmAccount(account: JsonAccount, user: JsonUser): Promise { const idmUserRepresentation: UserRepresentation = { username: account.username, firstName: user.firstName, @@ -91,13 +91,13 @@ export class KeycloakSeedService { return false; } - private async loadAccounts(): Promise { + private async loadAccounts(): Promise { const data = await fs.readFile(this.inputFiles.accountsFile, { encoding: 'utf-8' }); - return JSON.parse(data) as IJsonAccount[]; + return JSON.parse(data) as JsonAccount[]; } - private async loadUsers(): Promise { + private async loadUsers(): Promise { const data = await fs.readFile(this.inputFiles.usersFile, { encoding: 'utf-8' }); - return JSON.parse(data) as IJsonUser[]; + return JSON.parse(data) as JsonUser[]; } } diff --git a/apps/server/src/infra/identity-management/keycloak/service/keycloak-identity-management-oauth.service.spec.ts b/apps/server/src/infra/identity-management/keycloak/service/keycloak-identity-management-oauth.service.spec.ts index 9ccd20b6f99..8bd354ff379 100644 --- a/apps/server/src/infra/identity-management/keycloak/service/keycloak-identity-management-oauth.service.spec.ts +++ b/apps/server/src/infra/identity-management/keycloak/service/keycloak-identity-management-oauth.service.spec.ts @@ -1,9 +1,9 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { DefaultEncryptionService, EncryptionService, SymetricKeyEncryptionService } from '@infra/encryption'; import KeycloakAdminClient from '@keycloak/keycloak-admin-client'; import { HttpService } from '@nestjs/axios'; import { ConfigService } from '@nestjs/config'; import { Test, TestingModule } from '@nestjs/testing'; -import { DefaultEncryptionService, IEncryptionService, SymetricKeyEncryptionService } from '@infra/encryption'; import { AxiosResponse } from 'axios'; import { of } from 'rxjs'; import { KeycloakAdministrationService } from '../../keycloak-administration/service/keycloak-administration.service'; @@ -38,7 +38,7 @@ describe('KeycloakIdentityManagementService', () => { }, { provide: DefaultEncryptionService, - useValue: createMock(), + useValue: createMock(), }, ], }).compile(); diff --git a/apps/server/src/infra/identity-management/keycloak/service/keycloak-identity-management-oauth.service.ts b/apps/server/src/infra/identity-management/keycloak/service/keycloak-identity-management-oauth.service.ts index 9eab3a4f60a..cbfa289165e 100644 --- a/apps/server/src/infra/identity-management/keycloak/service/keycloak-identity-management-oauth.service.ts +++ b/apps/server/src/infra/identity-management/keycloak/service/keycloak-identity-management-oauth.service.ts @@ -1,8 +1,8 @@ +import { DefaultEncryptionService, EncryptionService } from '@infra/encryption'; +import { OauthConfigDto } from '@modules/system/service'; import { HttpService } from '@nestjs/axios'; import { Inject, Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; -import { DefaultEncryptionService, IEncryptionService } from '@infra/encryption'; -import { OauthConfigDto } from '@modules/system/service'; import qs from 'qs'; import { lastValueFrom } from 'rxjs'; import { IdentityManagementOauthService } from '../../identity-management-oauth.service'; @@ -16,7 +16,7 @@ export class KeycloakIdentityManagementOauthService extends IdentityManagementOa private readonly kcAdminService: KeycloakAdministrationService, private readonly configService: ConfigService, private readonly httpService: HttpService, - @Inject(DefaultEncryptionService) private readonly oAuthEncryptionService: IEncryptionService + @Inject(DefaultEncryptionService) private readonly oAuthEncryptionService: EncryptionService ) { super(); } diff --git a/apps/server/src/infra/mail/interfaces/mail-config.ts b/apps/server/src/infra/mail/interfaces/mail-config.ts index 6dbb0c7864d..d4baab878e7 100644 --- a/apps/server/src/infra/mail/interfaces/mail-config.ts +++ b/apps/server/src/infra/mail/interfaces/mail-config.ts @@ -1,3 +1,3 @@ -export interface IMailConfig { +export interface MailConfig { ADDITIONAL_BLACKLISTED_EMAIL_DOMAINS: string[]; } diff --git a/apps/server/src/infra/mail/mail.module.ts b/apps/server/src/infra/mail/mail.module.ts index ee6d50d59e7..0f30be221a7 100644 --- a/apps/server/src/infra/mail/mail.module.ts +++ b/apps/server/src/infra/mail/mail.module.ts @@ -1,7 +1,7 @@ -import { Module, DynamicModule } from '@nestjs/common'; +import { DynamicModule, Module } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; +import { MailConfig } from './interfaces/mail-config'; import { MailService } from './mail.service'; -import { IMailConfig } from './interfaces/mail-config'; interface MailModuleOptions { exchange: string; @@ -19,7 +19,7 @@ export class MailModule { provide: 'MAIL_SERVICE_OPTIONS', useValue: { exchange: options.exchange, routingKey: options.routingKey }, }, - ConfigService, + ConfigService, ], exports: [MailService], }; diff --git a/apps/server/src/infra/mail/mail.service.spec.ts b/apps/server/src/infra/mail/mail.service.spec.ts index ebc77030252..c72f182ee40 100644 --- a/apps/server/src/infra/mail/mail.service.spec.ts +++ b/apps/server/src/infra/mail/mail.service.spec.ts @@ -1,10 +1,10 @@ import { AmqpConnection } from '@golevelup/nestjs-rabbitmq'; -import { Test, TestingModule } from '@nestjs/testing'; import { createMock } from '@golevelup/ts-jest'; import { ConfigService } from '@nestjs/config'; +import { Test, TestingModule } from '@nestjs/testing'; +import { MailConfig } from './interfaces/mail-config'; import { Mail } from './mail.interface'; import { MailService } from './mail.service'; -import { IMailConfig } from './interfaces/mail-config'; describe('MailService', () => { let module: TestingModule; @@ -24,7 +24,7 @@ describe('MailService', () => { { provide: 'MAIL_SERVICE_OPTIONS', useValue: mailServiceOptions }, { provide: ConfigService, - useValue: createMock>({ get: () => ['schul-cloud.org', 'example.com'] }), + useValue: createMock>({ get: () => ['schul-cloud.org', 'example.com'] }), }, ], }).compile(); diff --git a/apps/server/src/infra/mail/mail.service.ts b/apps/server/src/infra/mail/mail.service.ts index 432f0746934..ce8f68ceddb 100644 --- a/apps/server/src/infra/mail/mail.service.ts +++ b/apps/server/src/infra/mail/mail.service.ts @@ -1,8 +1,8 @@ import { AmqpConnection } from '@golevelup/nestjs-rabbitmq'; import { Inject, Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; +import { MailConfig } from './interfaces/mail-config'; import { Mail } from './mail.interface'; -import { IMailConfig } from './interfaces/mail-config'; interface MailServiceOptions { exchange: string; @@ -16,7 +16,7 @@ export class MailService { constructor( private readonly amqpConnection: AmqpConnection, @Inject('MAIL_SERVICE_OPTIONS') private readonly options: MailServiceOptions, - private readonly configService: ConfigService + private readonly configService: ConfigService ) { this.domainBlacklist = this.configService.get('ADDITIONAL_BLACKLISTED_EMAIL_DOMAINS'); } diff --git a/apps/server/src/infra/rabbitmq/error.mapper.spec.ts b/apps/server/src/infra/rabbitmq/error.mapper.spec.ts index 884dfb35158..33dabb8acf8 100644 --- a/apps/server/src/infra/rabbitmq/error.mapper.spec.ts +++ b/apps/server/src/infra/rabbitmq/error.mapper.spec.ts @@ -1,10 +1,10 @@ +import { IError } from '@infra/rabbitmq'; import { BadRequestException, ConflictException, ForbiddenException, InternalServerErrorException, } from '@nestjs/common'; -import { IError } from '@infra/rabbitmq'; import _ from 'lodash'; import { ErrorMapper } from './error.mapper'; diff --git a/apps/server/src/infra/rabbitmq/error.mapper.ts b/apps/server/src/infra/rabbitmq/error.mapper.ts index 6f7083d3ad9..dcee0654e10 100644 --- a/apps/server/src/infra/rabbitmq/error.mapper.ts +++ b/apps/server/src/infra/rabbitmq/error.mapper.ts @@ -1,6 +1,6 @@ +import { IError } from '@infra/rabbitmq'; import { BadRequestException, ForbiddenException, InternalServerErrorException } from '@nestjs/common'; import { ErrorUtils } from '@src/core/error/utils'; -import { IError } from '@infra/rabbitmq'; export class ErrorMapper { static mapRpcErrorResponseToDomainError( diff --git a/apps/server/src/infra/rabbitmq/exchange/files-storage.ts b/apps/server/src/infra/rabbitmq/exchange/files-storage.ts index 080bcfebae5..86d2f710a57 100644 --- a/apps/server/src/infra/rabbitmq/exchange/files-storage.ts +++ b/apps/server/src/infra/rabbitmq/exchange/files-storage.ts @@ -27,25 +27,25 @@ export enum FileRecordParentType { 'BoardNode' = 'boardnodes', } -export interface ICopyFilesOfParentParams { +export interface CopyFilesOfParentParams { userId: EntityId; - source: IFileRecordParams; - target: IFileRecordParams; + source: FileRecordParams; + target: FileRecordParams; } -export interface IFileRecordParams { +export interface FileRecordParams { schoolId: EntityId; parentId: EntityId; parentType: FileRecordParentType; } -export interface ICopyFileDO { +export interface CopyFileDO { id?: EntityId; sourceId: EntityId; name: string; } -export interface IFileDO { +export interface FileDO { id: string; name: string; parentId: string; diff --git a/apps/server/src/modules/account/account-config.ts b/apps/server/src/modules/account/account-config.ts index cb2bd64e517..38b6acf7e58 100644 --- a/apps/server/src/modules/account/account-config.ts +++ b/apps/server/src/modules/account/account-config.ts @@ -1,4 +1,4 @@ -export interface IAccountConfig { +export interface AccountConfig { LOGIN_BLOCK_TIME: number; TEACHER_STUDENT_VISIBILITY__IS_CONFIGURABLE: boolean; } diff --git a/apps/server/src/modules/account/account.module.ts b/apps/server/src/modules/account/account.module.ts index 2e11af11c6d..9a57ba37a44 100644 --- a/apps/server/src/modules/account/account.module.ts +++ b/apps/server/src/modules/account/account.module.ts @@ -1,19 +1,19 @@ +import { IdentityManagementModule } from '@infra/identity-management'; import { Module } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { PermissionService } from '@shared/domain'; import { SystemRepo, UserRepo } from '@shared/repo'; -import { IdentityManagementModule } from '@infra/identity-management'; import { LoggerModule } from '@src/core/logger/logger.module'; +import { ServerConfig } from '../server/server.config'; +import { AccountIdmToDtoMapper, AccountIdmToDtoMapperDb, AccountIdmToDtoMapperIdm } from './mapper'; import { AccountRepo } from './repo/account.repo'; -import { AccountService } from './services/account.service'; -import { AccountValidationService } from './services/account.validation.service'; import { AccountServiceDb } from './services/account-db.service'; import { AccountServiceIdm } from './services/account-idm.service'; -import { AccountIdmToDtoMapper, AccountIdmToDtoMapperDb, AccountIdmToDtoMapperIdm } from './mapper'; -import { IServerConfig } from '../server/server.config'; import { AccountLookupService } from './services/account-lookup.service'; +import { AccountService } from './services/account.service'; +import { AccountValidationService } from './services/account.validation.service'; -function accountIdmToDtoMapperFactory(configService: ConfigService): AccountIdmToDtoMapper { +function accountIdmToDtoMapperFactory(configService: ConfigService): AccountIdmToDtoMapper { if (configService.get('FEATURE_IDENTITY_MANAGEMENT_LOGIN_ENABLED') === true) { return new AccountIdmToDtoMapperIdm(); } diff --git a/apps/server/src/modules/account/controller/account.controller.ts b/apps/server/src/modules/account/controller/account.controller.ts index 2256cb9fc90..3265693915c 100644 --- a/apps/server/src/modules/account/controller/account.controller.ts +++ b/apps/server/src/modules/account/controller/account.controller.ts @@ -1,8 +1,8 @@ import { Body, Controller, Delete, Get, Param, Patch, Query } from '@nestjs/common'; import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; -import { Authenticate, CurrentUser } from '@src/modules/authentication/decorator/auth.decorator'; import { EntityNotFoundError, ForbiddenOperationError, ValidationError } from '@shared/common'; import { ICurrentUser } from '@src/modules/authentication'; +import { Authenticate, CurrentUser } from '@src/modules/authentication/decorator/auth.decorator'; import { AccountUc } from '../uc/account.uc'; import { AccountByIdBodyParams, diff --git a/apps/server/src/modules/account/controller/api-test/account.api.spec.ts b/apps/server/src/modules/account/controller/api-test/account.api.spec.ts index fefdb006bf8..7a920a40a97 100644 --- a/apps/server/src/modules/account/controller/api-test/account.api.spec.ts +++ b/apps/server/src/modules/account/controller/api-test/account.api.spec.ts @@ -2,7 +2,6 @@ import { EntityManager } from '@mikro-orm/core'; import { ExecutionContext, INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { Account, Permission, RoleName, User } from '@shared/domain'; -import { ICurrentUser } from '@src/modules/authentication'; import { accountFactory, mapUserToCurrentUser, roleFactory, schoolFactory, userFactory } from '@shared/testing'; import { AccountByIdBodyParams, @@ -11,6 +10,7 @@ import { PatchMyAccountParams, PatchMyPasswordParams, } from '@src/modules/account/controller/dto'; +import { ICurrentUser } from '@src/modules/authentication'; import { JwtAuthGuard } from '@src/modules/authentication/guard/jwt-auth.guard'; import { ServerTestModule } from '@src/modules/server/server.module'; import { Request } from 'express'; diff --git a/apps/server/src/modules/account/services/account-db.service.spec.ts b/apps/server/src/modules/account/services/account-db.service.spec.ts index 107273797f9..e847cdf9eb7 100644 --- a/apps/server/src/modules/account/services/account-db.service.spec.ts +++ b/apps/server/src/modules/account/services/account-db.service.spec.ts @@ -1,14 +1,14 @@ -import { DeepMocked, createMock } from '@golevelup/ts-jest'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { IdentityManagementService } from '@infra/identity-management/identity-management.service'; import { ObjectId } from '@mikro-orm/mongodb'; +import { AccountEntityToDtoMapper } from '@modules/account/mapper'; +import { AccountDto } from '@modules/account/services/dto'; +import { ServerConfig } from '@modules/server'; import { ConfigService } from '@nestjs/config'; import { Test, TestingModule } from '@nestjs/testing'; import { EntityNotFoundError } from '@shared/common'; import { Account, EntityId, Permission, Role, RoleName, SchoolEntity, User } from '@shared/domain'; -import { IdentityManagementService } from '@infra/identity-management/identity-management.service'; import { accountFactory, schoolFactory, setupEntities, userFactory } from '@shared/testing'; -import { AccountEntityToDtoMapper } from '@modules/account/mapper'; -import { AccountDto } from '@modules/account/services/dto'; -import { IServerConfig } from '@modules/server'; import bcrypt from 'bcryptjs'; import { LegacyLogger } from '../../../core/logger'; import { AccountRepo } from '../repo/account.repo'; @@ -117,7 +117,7 @@ describe('AccountDbService', () => { }, { provide: ConfigService, - useValue: createMock>(), + useValue: createMock>(), }, { provide: IdentityManagementService, diff --git a/apps/server/src/modules/account/services/account-lookup.service.ts b/apps/server/src/modules/account/services/account-lookup.service.ts index ed67d03232d..41064d077e9 100644 --- a/apps/server/src/modules/account/services/account-lookup.service.ts +++ b/apps/server/src/modules/account/services/account-lookup.service.ts @@ -1,8 +1,8 @@ +import { IdentityManagementService } from '@infra/identity-management'; +import { ServerConfig } from '@modules/server/server.config'; import { Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { EntityId } from '@shared/domain'; -import { IdentityManagementService } from '@infra/identity-management'; -import { IServerConfig } from '@modules/server/server.config'; import { ObjectId } from 'bson'; /** @@ -15,7 +15,7 @@ import { ObjectId } from 'bson'; export class AccountLookupService { constructor( private readonly idmService: IdentityManagementService, - private readonly configService: ConfigService + private readonly configService: ConfigService ) {} /** diff --git a/apps/server/src/modules/account/services/account.service.spec.ts b/apps/server/src/modules/account/services/account.service.spec.ts index 4cb95d96a36..1d7c7d43398 100644 --- a/apps/server/src/modules/account/services/account.service.spec.ts +++ b/apps/server/src/modules/account/services/account.service.spec.ts @@ -1,7 +1,7 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { ServerConfig } from '@modules/server'; import { ConfigService } from '@nestjs/config'; import { Test, TestingModule } from '@nestjs/testing'; -import { IServerConfig } from '@modules/server'; import { LegacyLogger } from '../../../core/logger'; import { AccountServiceDb } from './account-db.service'; import { AccountServiceIdm } from './account-idm.service'; @@ -40,7 +40,7 @@ describe('AccountService', () => { }, { provide: ConfigService, - useValue: createMock>(), + useValue: createMock>(), }, { provide: AccountValidationService, diff --git a/apps/server/src/modules/account/services/account.service.ts b/apps/server/src/modules/account/services/account.service.ts index 6c2070550ab..6e6107954ce 100644 --- a/apps/server/src/modules/account/services/account.service.ts +++ b/apps/server/src/modules/account/services/account.service.ts @@ -5,7 +5,7 @@ import { ValidationError } from '@shared/common'; import { Counted } from '@shared/domain'; import { isEmail, validateOrReject } from 'class-validator'; import { LegacyLogger } from '../../../core/logger'; -import { IServerConfig } from '../../server/server.config'; +import { ServerConfig } from '../../server/server.config'; import { AccountServiceDb } from './account-db.service'; import { AccountServiceIdm } from './account-idm.service'; import { AbstractAccountService } from './account.service.abstract'; @@ -19,7 +19,7 @@ export class AccountService extends AbstractAccountService { constructor( private readonly accountDb: AccountServiceDb, private readonly accountIdm: AccountServiceIdm, - private readonly configService: ConfigService, + private readonly configService: ConfigService, private readonly accountValidationService: AccountValidationService, private readonly logger: LegacyLogger ) { diff --git a/apps/server/src/modules/account/uc/account.uc.spec.ts b/apps/server/src/modules/account/uc/account.uc.spec.ts index aa4cdf56a82..6810c2baa5c 100644 --- a/apps/server/src/modules/account/uc/account.uc.spec.ts +++ b/apps/server/src/modules/account/uc/account.uc.spec.ts @@ -1,4 +1,8 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { AccountService } from '@modules/account/services/account.service'; +import { AccountSaveDto } from '@modules/account/services/dto'; +import { AccountDto } from '@modules/account/services/dto/account.dto'; +import { ICurrentUser } from '@modules/authentication'; import { ConfigService } from '@nestjs/config'; import { Test, TestingModule } from '@nestjs/testing'; import { AuthorizationError, EntityNotFoundError, ForbiddenOperationError, ValidationError } from '@shared/common'; @@ -18,10 +22,6 @@ import { import { UserRepo } from '@shared/repo'; import { accountFactory, schoolFactory, setupEntities, systemFactory, userFactory } from '@shared/testing'; import { BruteForcePrevention } from '@src/imports-from-feathers'; -import { AccountService } from '@modules/account/services/account.service'; -import { AccountSaveDto } from '@modules/account/services/dto'; -import { AccountDto } from '@modules/account/services/dto/account.dto'; -import { ICurrentUser } from '@modules/authentication'; import { ObjectId } from 'bson'; import { AccountByIdBodyParams, diff --git a/apps/server/src/modules/account/uc/account.uc.ts b/apps/server/src/modules/account/uc/account.uc.ts index ac4ac053dae..dac0b936ba5 100644 --- a/apps/server/src/modules/account/uc/account.uc.ts +++ b/apps/server/src/modules/account/uc/account.uc.ts @@ -1,3 +1,5 @@ +import { AccountService } from '@modules/account/services/account.service'; +import { AccountDto } from '@modules/account/services/dto/account.dto'; import { Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { @@ -8,13 +10,11 @@ import { } from '@shared/common/error'; import { Account, EntityId, Permission, PermissionService, Role, RoleName, SchoolEntity, User } from '@shared/domain'; import { UserRepo } from '@shared/repo'; -import { AccountService } from '@modules/account/services/account.service'; -import { AccountDto } from '@modules/account/services/dto/account.dto'; -import { BruteForcePrevention } from '@src/imports-from-feathers'; import { ICurrentUser } from '@modules/authentication'; +import { BruteForcePrevention } from '@src/imports-from-feathers'; import { ObjectId } from 'bson'; -import { IAccountConfig } from '../account-config'; +import { AccountConfig } from '../account-config'; import { AccountByIdBodyParams, AccountByIdParams, @@ -39,7 +39,7 @@ export class AccountUc { private readonly userRepo: UserRepo, private readonly permissionService: PermissionService, private readonly accountValidationService: AccountValidationService, - private readonly configService: ConfigService + private readonly configService: ConfigService ) {} /** diff --git a/apps/server/src/modules/authentication/decorator/auth.decorator.spec.ts b/apps/server/src/modules/authentication/decorator/auth.decorator.spec.ts index f193bd67516..cc6006228b8 100644 --- a/apps/server/src/modules/authentication/decorator/auth.decorator.spec.ts +++ b/apps/server/src/modules/authentication/decorator/auth.decorator.spec.ts @@ -1,9 +1,9 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ -import { Controller, ExecutionContext, ForbiddenException, Get, INestApplication } from '@nestjs/common'; -import request from 'supertest'; -import { Test, TestingModule } from '@nestjs/testing'; import { ICurrentUser } from '@modules/authentication'; import { ServerTestModule } from '@modules/server/server.module'; +import { Controller, ExecutionContext, ForbiddenException, Get, INestApplication } from '@nestjs/common'; +import { Test, TestingModule } from '@nestjs/testing'; +import request from 'supertest'; import { JwtAuthGuard } from '../guard/jwt-auth.guard'; import { Authenticate, CurrentUser, JWT } from './auth.decorator'; diff --git a/apps/server/src/modules/authentication/decorator/auth.decorator.ts b/apps/server/src/modules/authentication/decorator/auth.decorator.ts index 4f0167ec854..6d36784af0d 100644 --- a/apps/server/src/modules/authentication/decorator/auth.decorator.ts +++ b/apps/server/src/modules/authentication/decorator/auth.decorator.ts @@ -6,11 +6,11 @@ import { UnauthorizedException, UseGuards, } from '@nestjs/common'; -import { Request } from 'express'; import { ApiBearerAuth } from '@nestjs/swagger'; +import { Request } from 'express'; import { ExtractJwt } from 'passport-jwt'; -import { ICurrentUser } from '../interface/user'; import { JwtAuthGuard } from '../guard/jwt-auth.guard'; +import { ICurrentUser } from '../interface/user'; import { JwtExtractor } from '../strategy/jwt-extractor'; const STRATEGIES = ['jwt'] as const; diff --git a/apps/server/src/modules/authentication/index.ts b/apps/server/src/modules/authentication/index.ts index 3a3ea0c2755..80e9d64ab69 100644 --- a/apps/server/src/modules/authentication/index.ts +++ b/apps/server/src/modules/authentication/index.ts @@ -1,4 +1,4 @@ -export { ICurrentUser } from './interface'; -export { JWT, CurrentUser, Authenticate } from './decorator'; export { AuthenticationModule } from './authentication.module'; +export { Authenticate, CurrentUser, JWT } from './decorator'; +export { ICurrentUser } from './interface'; export { AuthenticationService } from './services'; diff --git a/apps/server/src/modules/authentication/services/authentication.service.spec.ts b/apps/server/src/modules/authentication/services/authentication.service.spec.ts index 1e5c69ecfb1..de80540a65a 100644 --- a/apps/server/src/modules/authentication/services/authentication.service.spec.ts +++ b/apps/server/src/modules/authentication/services/authentication.service.spec.ts @@ -1,11 +1,11 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { AccountService } from '@modules/account/services/account.service'; +import { AccountDto } from '@modules/account/services/dto'; +import { ICurrentUser } from '@modules/authentication'; import { UnauthorizedException } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { JwtService } from '@nestjs/jwt'; import { Test, TestingModule } from '@nestjs/testing'; -import { AccountService } from '@modules/account/services/account.service'; -import { AccountDto } from '@modules/account/services/dto'; -import { ICurrentUser } from '@modules/authentication'; import jwt from 'jsonwebtoken'; import { BruteForceError } from '../errors/brute-force.error'; import { JwtValidationAdapter } from '../strategy/jwt-validation.adapter'; diff --git a/apps/server/src/modules/authentication/services/authentication.service.ts b/apps/server/src/modules/authentication/services/authentication.service.ts index 41aab6153ea..efa8ec35b27 100644 --- a/apps/server/src/modules/authentication/services/authentication.service.ts +++ b/apps/server/src/modules/authentication/services/authentication.service.ts @@ -1,16 +1,16 @@ +import { AccountService } from '@modules/account'; import { Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { JwtService } from '@nestjs/jwt'; -import { AccountService } from '@modules/account'; // invalid import import { AccountDto } from '@modules/account/services/dto'; // invalid import, can produce dependency cycles -import type { IServerConfig } from '@modules/server'; +import type { ServerConfig } from '@modules/server'; import { randomUUID } from 'crypto'; import jwt, { JwtPayload } from 'jsonwebtoken'; -import { JwtValidationAdapter } from '../strategy/jwt-validation.adapter'; import { BruteForceError, UnauthorizedLoggableException } from '../errors'; import { CreateJwtPayload } from '../interface/jwt-payload'; +import { JwtValidationAdapter } from '../strategy/jwt-validation.adapter'; import { LoginDto } from '../uc/dto'; @Injectable() @@ -19,7 +19,7 @@ export class AuthenticationService { private readonly jwtService: JwtService, private readonly jwtValidationAdapter: JwtValidationAdapter, private readonly accountService: AccountService, - private readonly configService: ConfigService + private readonly configService: ConfigService ) {} async loadAccount(username: string, systemId?: string): Promise { diff --git a/apps/server/src/modules/authentication/strategy/ldap.strategy.spec.ts b/apps/server/src/modules/authentication/strategy/ldap.strategy.spec.ts index 5b23be9eb74..b3067de04eb 100644 --- a/apps/server/src/modules/authentication/strategy/ldap.strategy.spec.ts +++ b/apps/server/src/modules/authentication/strategy/ldap.strategy.spec.ts @@ -1,4 +1,5 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { AccountDto } from '@modules/account/services/dto'; import { UnauthorizedException } from '@nestjs/common'; import { PassportModule } from '@nestjs/passport'; import { Test, TestingModule } from '@nestjs/testing'; @@ -15,7 +16,6 @@ import { userFactory, } from '@shared/testing'; import { Logger } from '@src/core/logger'; -import { AccountDto } from '@modules/account/services/dto'; import { LdapAuthorizationBodyParams } from '../controllers/dto'; import { ICurrentUser } from '../interface'; import { AuthenticationService } from '../services/authentication.service'; diff --git a/apps/server/src/modules/authentication/strategy/local.strategy.spec.ts b/apps/server/src/modules/authentication/strategy/local.strategy.spec.ts index 121d2874fe9..44db3ea4b21 100644 --- a/apps/server/src/modules/authentication/strategy/local.strategy.spec.ts +++ b/apps/server/src/modules/authentication/strategy/local.strategy.spec.ts @@ -1,13 +1,13 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { IdentityManagementOauthService } from '@infra/identity-management'; +import { AccountEntityToDtoMapper } from '@modules/account/mapper'; +import { AccountDto } from '@modules/account/services/dto'; +import { ServerConfig } from '@modules/server'; import { UnauthorizedException } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { RoleName, User } from '@shared/domain'; -import { IdentityManagementOauthService } from '@infra/identity-management'; import { UserRepo } from '@shared/repo'; import { accountFactory, setupEntities, userFactory } from '@shared/testing'; -import { AccountEntityToDtoMapper } from '@modules/account/mapper'; -import { AccountDto } from '@modules/account/services/dto'; -import { IServerConfig } from '@modules/server'; import bcrypt from 'bcryptjs'; import { AuthenticationService } from '../services/authentication.service'; import { LocalStrategy } from './local.strategy'; @@ -28,7 +28,7 @@ describe('LocalStrategy', () => { await setupEntities(); authenticationServiceMock = createMock(); idmOauthServiceMock = createMock(); - configServiceMock = createMock>(); + configServiceMock = createMock>(); userRepoMock = createMock(); strategy = new LocalStrategy(authenticationServiceMock, idmOauthServiceMock, configServiceMock, userRepoMock); mockUser = userFactory.withRoleByName(RoleName.STUDENT).buildWithId(); diff --git a/apps/server/src/modules/authentication/strategy/local.strategy.ts b/apps/server/src/modules/authentication/strategy/local.strategy.ts index 1d31a86d833..c423fc396ff 100644 --- a/apps/server/src/modules/authentication/strategy/local.strategy.ts +++ b/apps/server/src/modules/authentication/strategy/local.strategy.ts @@ -1,14 +1,14 @@ -import { Strategy } from 'passport-local'; +import { IdentityManagementConfig, IdentityManagementOauthService } from '@infra/identity-management'; +import { AccountDto } from '@modules/account/services/dto'; +import { Injectable, UnauthorizedException } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { PassportStrategy } from '@nestjs/passport'; -import { Injectable, UnauthorizedException } from '@nestjs/common'; -import bcrypt from 'bcryptjs'; -import { UserRepo } from '@shared/repo'; -import { AccountDto } from '@modules/account/services/dto'; import { GuardAgainst } from '@shared/common/utils/guard-against'; -import { IdentityManagementOauthService, IIdentityManagementConfig } from '@infra/identity-management'; -import { CurrentUserMapper } from '../mapper'; +import { UserRepo } from '@shared/repo'; +import bcrypt from 'bcryptjs'; +import { Strategy } from 'passport-local'; import { ICurrentUser } from '../interface'; +import { CurrentUserMapper } from '../mapper'; import { AuthenticationService } from '../services/authentication.service'; @Injectable() @@ -16,7 +16,7 @@ export class LocalStrategy extends PassportStrategy(Strategy) { constructor( private readonly authenticationService: AuthenticationService, private readonly idmOauthService: IdentityManagementOauthService, - private readonly configService: ConfigService, + private readonly configService: ConfigService, private readonly userRepo: UserRepo ) { super(); diff --git a/apps/server/src/modules/authentication/strategy/oauth2.strategy.spec.ts b/apps/server/src/modules/authentication/strategy/oauth2.strategy.spec.ts index 428f3c5e048..9f47f0e1d6a 100644 --- a/apps/server/src/modules/authentication/strategy/oauth2.strategy.spec.ts +++ b/apps/server/src/modules/authentication/strategy/oauth2.strategy.spec.ts @@ -1,14 +1,17 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { AccountService } from '@modules/account/services/account.service'; import { AccountDto } from '@modules/account/services/dto'; -import { OAuthTokenDto, OAuthService } from '@modules/oauth'; +import { OAuthService, OAuthTokenDto } from '@modules/oauth'; import { UnauthorizedException } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { EntityId, RoleName } from '@shared/domain'; import { UserDO } from '@shared/domain/domainobject/user.do'; import { userDoFactory } from '@shared/testing'; + import { ICurrentUser, OauthCurrentUser } from '../interface'; + import { SchoolInMigrationLoggableException } from '../loggable'; + import { Oauth2Strategy } from './oauth2.strategy'; describe('Oauth2Strategy', () => { diff --git a/apps/server/src/modules/authentication/strategy/oauth2.strategy.ts b/apps/server/src/modules/authentication/strategy/oauth2.strategy.ts index e5bc6f942f8..320387a7d00 100644 --- a/apps/server/src/modules/authentication/strategy/oauth2.strategy.ts +++ b/apps/server/src/modules/authentication/strategy/oauth2.strategy.ts @@ -1,6 +1,6 @@ import { AccountService } from '@modules/account/services/account.service'; import { AccountDto } from '@modules/account/services/dto'; -import { OAuthTokenDto, OAuthService } from '@modules/oauth'; +import { OAuthService, OAuthTokenDto } from '@modules/oauth'; import { Injectable, UnauthorizedException } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { UserDO } from '@shared/domain/domainobject/user.do'; diff --git a/apps/server/src/modules/authorization/domain/service/reference.loader.ts b/apps/server/src/modules/authorization/domain/service/reference.loader.ts index c0ea22212a0..7fb574a4d6e 100644 --- a/apps/server/src/modules/authorization/domain/service/reference.loader.ts +++ b/apps/server/src/modules/authorization/domain/service/reference.loader.ts @@ -1,4 +1,6 @@ import { BoardDoAuthorizableService } from '@modules/board'; + +import { LessonService } from '@modules/lesson'; import { ContextExternalToolAuthorizableService } from '@modules/tool'; import { Injectable, NotImplementedException } from '@nestjs/common'; import { BaseDO, EntityId } from '@shared/domain'; @@ -13,7 +15,6 @@ import { TeamsRepo, UserRepo, } from '@shared/repo'; -import { LessonService } from '@modules/lesson'; import { AuthorizableReferenceType } from '../type'; type RepoType = @@ -29,14 +30,14 @@ type RepoType = | ContextExternalToolAuthorizableService | LessonService; -interface IRepoLoader { +interface RepoLoader { repo: RepoType; populate?: boolean; } @Injectable() export class ReferenceLoader { - private repos: Map = new Map(); + private repos: Map = new Map(); constructor( private readonly userRepo: UserRepo, @@ -66,7 +67,7 @@ export class ReferenceLoader { }); } - private resolveRepo(type: AuthorizableReferenceType): IRepoLoader { + private resolveRepo(type: AuthorizableReferenceType): RepoLoader { const repo = this.repos.get(type); if (repo) { return repo; @@ -78,7 +79,7 @@ export class ReferenceLoader { objectName: AuthorizableReferenceType, objectId: EntityId ): Promise { - const repoLoader: IRepoLoader = this.resolveRepo(objectName); + const repoLoader: RepoLoader = this.resolveRepo(objectName); let object: AuthorizableObject | BaseDO; if (repoLoader.populate) { diff --git a/apps/server/src/modules/board/controller/api-test/board-delete.api.spec.ts b/apps/server/src/modules/board/controller/api-test/board-delete.api.spec.ts index e92bb645872..5d7633e4c99 100644 --- a/apps/server/src/modules/board/controller/api-test/board-delete.api.spec.ts +++ b/apps/server/src/modules/board/controller/api-test/board-delete.api.spec.ts @@ -1,4 +1,7 @@ import { EntityManager } from '@mikro-orm/mongodb'; +import { ICurrentUser } from '@modules/authentication'; +import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; +import { ServerTestModule } from '@modules/server'; import { ExecutionContext, INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { ApiValidationError } from '@shared/common'; @@ -11,9 +14,6 @@ import { mapUserToCurrentUser, userFactory, } from '@shared/testing'; -import { ICurrentUser } from '@modules/authentication'; -import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; -import { ServerTestModule } from '@modules/server'; import { Request } from 'express'; import request from 'supertest'; import { BoardResponse } from '../dto'; diff --git a/apps/server/src/modules/board/controller/api-test/board-lookup.api.spec.ts b/apps/server/src/modules/board/controller/api-test/board-lookup.api.spec.ts index 8423d68f793..c4f685b9a6a 100644 --- a/apps/server/src/modules/board/controller/api-test/board-lookup.api.spec.ts +++ b/apps/server/src/modules/board/controller/api-test/board-lookup.api.spec.ts @@ -1,4 +1,7 @@ import { EntityManager, ObjectId } from '@mikro-orm/mongodb'; +import { ICurrentUser } from '@modules/authentication'; +import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; +import { ServerTestModule } from '@modules/server/server.module'; import { ExecutionContext, INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { ApiValidationError } from '@shared/common'; @@ -12,9 +15,6 @@ import { mapUserToCurrentUser, userFactory, } from '@shared/testing'; -import { ICurrentUser } from '@modules/authentication'; -import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; -import { ServerTestModule } from '@modules/server/server.module'; import { Request } from 'express'; import request from 'supertest'; import { BoardResponse } from '../dto'; diff --git a/apps/server/src/modules/board/controller/api-test/card-create.api.spec.ts b/apps/server/src/modules/board/controller/api-test/card-create.api.spec.ts index 4b0f57c361d..de006926e8f 100644 --- a/apps/server/src/modules/board/controller/api-test/card-create.api.spec.ts +++ b/apps/server/src/modules/board/controller/api-test/card-create.api.spec.ts @@ -1,4 +1,7 @@ import { EntityManager } from '@mikro-orm/mongodb'; +import { ICurrentUser } from '@modules/authentication'; +import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; +import { ServerTestModule } from '@modules/server/server.module'; import { ExecutionContext, INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { ApiValidationError } from '@shared/common'; @@ -11,9 +14,6 @@ import { mapUserToCurrentUser, userFactory, } from '@shared/testing'; -import { ICurrentUser } from '@modules/authentication'; -import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; -import { ServerTestModule } from '@modules/server/server.module'; import { Request } from 'express'; import request from 'supertest'; import { CardResponse } from '../dto'; diff --git a/apps/server/src/modules/board/controller/api-test/card-delete.api.spec.ts b/apps/server/src/modules/board/controller/api-test/card-delete.api.spec.ts index 3fff41b3660..d2b9a02efda 100644 --- a/apps/server/src/modules/board/controller/api-test/card-delete.api.spec.ts +++ b/apps/server/src/modules/board/controller/api-test/card-delete.api.spec.ts @@ -1,4 +1,7 @@ import { EntityManager } from '@mikro-orm/mongodb'; +import { ICurrentUser } from '@modules/authentication'; +import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; +import { ServerTestModule } from '@modules/server'; import { ExecutionContext, INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { ApiValidationError } from '@shared/common'; @@ -13,9 +16,6 @@ import { richTextElementNodeFactory, userFactory, } from '@shared/testing'; -import { ICurrentUser } from '@modules/authentication'; -import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; -import { ServerTestModule } from '@modules/server'; import { Request } from 'express'; import request from 'supertest'; diff --git a/apps/server/src/modules/board/controller/api-test/card-lookup.api.spec.ts b/apps/server/src/modules/board/controller/api-test/card-lookup.api.spec.ts index 711ccf1ac8f..f43fb954e68 100644 --- a/apps/server/src/modules/board/controller/api-test/card-lookup.api.spec.ts +++ b/apps/server/src/modules/board/controller/api-test/card-lookup.api.spec.ts @@ -1,4 +1,7 @@ import { EntityManager, ObjectId } from '@mikro-orm/mongodb'; +import { ICurrentUser } from '@modules/authentication'; +import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; +import { ServerTestModule } from '@modules/server/server.module'; import { ExecutionContext, INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { ApiValidationError } from '@shared/common'; @@ -15,9 +18,6 @@ import { schoolFactory, userFactory, } from '@shared/testing'; -import { ICurrentUser } from '@modules/authentication'; -import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; -import { ServerTestModule } from '@modules/server/server.module'; import { Request } from 'express'; import request from 'supertest'; import { CardIdsParams, CardListResponse } from '../dto'; diff --git a/apps/server/src/modules/board/controller/api-test/card-move.api.spec.ts b/apps/server/src/modules/board/controller/api-test/card-move.api.spec.ts index e9e55b27dd2..fa67220072a 100644 --- a/apps/server/src/modules/board/controller/api-test/card-move.api.spec.ts +++ b/apps/server/src/modules/board/controller/api-test/card-move.api.spec.ts @@ -1,4 +1,7 @@ import { EntityManager } from '@mikro-orm/mongodb'; +import { ICurrentUser } from '@modules/authentication'; +import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; +import { ServerTestModule } from '@modules/server/server.module'; import { ExecutionContext, INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { ApiValidationError } from '@shared/common'; @@ -12,9 +15,6 @@ import { mapUserToCurrentUser, userFactory, } from '@shared/testing'; -import { ICurrentUser } from '@modules/authentication'; -import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; -import { ServerTestModule } from '@modules/server/server.module'; import { Request } from 'express'; import request from 'supertest'; diff --git a/apps/server/src/modules/board/controller/api-test/column-create.api.spec.ts b/apps/server/src/modules/board/controller/api-test/column-create.api.spec.ts index 52aaa4608ca..dd63155d4a5 100644 --- a/apps/server/src/modules/board/controller/api-test/column-create.api.spec.ts +++ b/apps/server/src/modules/board/controller/api-test/column-create.api.spec.ts @@ -1,4 +1,7 @@ import { EntityManager } from '@mikro-orm/mongodb'; +import { ICurrentUser } from '@modules/authentication'; +import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; +import { ServerTestModule } from '@modules/server/server.module'; import { ExecutionContext, INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { ApiValidationError } from '@shared/common'; @@ -10,9 +13,6 @@ import { mapUserToCurrentUser, userFactory, } from '@shared/testing'; -import { ICurrentUser } from '@modules/authentication'; -import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; -import { ServerTestModule } from '@modules/server/server.module'; import { Request } from 'express'; import request from 'supertest'; import { ColumnResponse } from '../dto'; diff --git a/apps/server/src/modules/board/controller/api-test/column-delete.api.spec.ts b/apps/server/src/modules/board/controller/api-test/column-delete.api.spec.ts index 86e3e666c84..8b7dc463f98 100644 --- a/apps/server/src/modules/board/controller/api-test/column-delete.api.spec.ts +++ b/apps/server/src/modules/board/controller/api-test/column-delete.api.spec.ts @@ -1,4 +1,7 @@ import { EntityManager } from '@mikro-orm/mongodb'; +import { ICurrentUser } from '@modules/authentication'; +import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; +import { ServerTestModule } from '@modules/server/server.module'; import { ExecutionContext, INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { ApiValidationError } from '@shared/common'; @@ -12,9 +15,6 @@ import { mapUserToCurrentUser, userFactory, } from '@shared/testing'; -import { ICurrentUser } from '@modules/authentication'; -import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; -import { ServerTestModule } from '@modules/server/server.module'; import { Request } from 'express'; import request from 'supertest'; diff --git a/apps/server/src/modules/board/controller/api-test/column-move.api.spec.ts b/apps/server/src/modules/board/controller/api-test/column-move.api.spec.ts index 6fda90f739a..f467646766a 100644 --- a/apps/server/src/modules/board/controller/api-test/column-move.api.spec.ts +++ b/apps/server/src/modules/board/controller/api-test/column-move.api.spec.ts @@ -1,4 +1,7 @@ import { EntityManager } from '@mikro-orm/mongodb'; +import { ICurrentUser } from '@modules/authentication'; +import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; +import { ServerTestModule } from '@modules/server/server.module'; import { ExecutionContext, INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { ApiValidationError } from '@shared/common'; @@ -12,9 +15,6 @@ import { mapUserToCurrentUser, userFactory, } from '@shared/testing'; -import { ICurrentUser } from '@modules/authentication'; -import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; -import { ServerTestModule } from '@modules/server/server.module'; import { Request } from 'express'; import request from 'supertest'; diff --git a/apps/server/src/modules/board/controller/api-test/content-element-delete.api.spec.ts b/apps/server/src/modules/board/controller/api-test/content-element-delete.api.spec.ts index 6b99a64e7ab..b19aa8ff11c 100644 --- a/apps/server/src/modules/board/controller/api-test/content-element-delete.api.spec.ts +++ b/apps/server/src/modules/board/controller/api-test/content-element-delete.api.spec.ts @@ -1,4 +1,7 @@ import { EntityManager } from '@mikro-orm/mongodb'; +import { ICurrentUser } from '@modules/authentication'; +import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; +import { ServerTestModule } from '@modules/server/server.module'; import { ExecutionContext, INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { ApiValidationError } from '@shared/common'; @@ -13,9 +16,6 @@ import { richTextElementNodeFactory, userFactory, } from '@shared/testing'; -import { ICurrentUser } from '@modules/authentication'; -import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; -import { ServerTestModule } from '@modules/server/server.module'; import { Request } from 'express'; import request from 'supertest'; diff --git a/apps/server/src/modules/board/controller/api-test/content-element-move.api.spec.ts b/apps/server/src/modules/board/controller/api-test/content-element-move.api.spec.ts index dbcd9acbc31..f913a547f59 100644 --- a/apps/server/src/modules/board/controller/api-test/content-element-move.api.spec.ts +++ b/apps/server/src/modules/board/controller/api-test/content-element-move.api.spec.ts @@ -1,4 +1,7 @@ import { EntityManager } from '@mikro-orm/mongodb'; +import { ICurrentUser } from '@modules/authentication'; +import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; +import { ServerTestModule } from '@modules/server/server.module'; import { ExecutionContext, INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { ApiValidationError } from '@shared/common'; @@ -13,9 +16,6 @@ import { richTextElementNodeFactory, userFactory, } from '@shared/testing'; -import { ICurrentUser } from '@modules/authentication'; -import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; -import { ServerTestModule } from '@modules/server/server.module'; import { Request } from 'express'; import request from 'supertest'; diff --git a/apps/server/src/modules/board/controller/board-submission.controller.ts b/apps/server/src/modules/board/controller/board-submission.controller.ts index 4e81d342849..bc6f7298668 100644 --- a/apps/server/src/modules/board/controller/board-submission.controller.ts +++ b/apps/server/src/modules/board/controller/board-submission.controller.ts @@ -1,3 +1,4 @@ +import { Authenticate, CurrentUser, ICurrentUser } from '@modules/authentication'; import { Body, Controller, @@ -11,8 +12,6 @@ import { } from '@nestjs/common'; import { ApiExtraModels, ApiOperation, ApiResponse, ApiTags, getSchemaPath } from '@nestjs/swagger'; import { ApiValidationError } from '@shared/common'; -import { ICurrentUser, Authenticate, CurrentUser } from '@modules/authentication'; -import { SubmissionsResponse } from './dto/submission-item/submissions.response'; import { CardUc } from '../uc'; import { ElementUc } from '../uc/element.uc'; import { SubmissionItemUc } from '../uc/submission-item.uc'; @@ -24,6 +23,7 @@ import { SubmissionItemUrlParams, UpdateSubmissionItemBodyParams, } from './dto'; +import { SubmissionsResponse } from './dto/submission-item/submissions.response'; import { ContentElementResponseFactory, SubmissionItemResponseMapper } from './mapper'; @ApiTags('Board Submission') diff --git a/apps/server/src/modules/board/controller/board.controller.ts b/apps/server/src/modules/board/controller/board.controller.ts index 0d77aa80b3d..55a54e8f77b 100644 --- a/apps/server/src/modules/board/controller/board.controller.ts +++ b/apps/server/src/modules/board/controller/board.controller.ts @@ -1,3 +1,4 @@ +import { Authenticate, CurrentUser, ICurrentUser } from '@modules/authentication'; import { Body, Controller, @@ -12,7 +13,6 @@ import { } from '@nestjs/common'; import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; import { ApiValidationError } from '@shared/common'; -import { ICurrentUser, Authenticate, CurrentUser } from '@modules/authentication'; import { BoardUc } from '../uc'; import { BoardResponse, BoardUrlParams, ColumnResponse, RenameBodyParams } from './dto'; import { BoardContextResponse } from './dto/board/board-context.reponse'; diff --git a/apps/server/src/modules/board/controller/card.controller.ts b/apps/server/src/modules/board/controller/card.controller.ts index e75d7afc7a5..71a6d2ab0f9 100644 --- a/apps/server/src/modules/board/controller/card.controller.ts +++ b/apps/server/src/modules/board/controller/card.controller.ts @@ -1,3 +1,4 @@ +import { Authenticate, CurrentUser, ICurrentUser } from '@modules/authentication'; import { Body, Controller, @@ -14,7 +15,6 @@ import { } from '@nestjs/common'; import { ApiExtraModels, ApiOperation, ApiResponse, ApiTags, getSchemaPath } from '@nestjs/swagger'; import { ApiValidationError } from '@shared/common'; -import { ICurrentUser, Authenticate, CurrentUser } from '@modules/authentication'; import { CardUc, ColumnUc } from '../uc'; import { AnyContentElementResponse, diff --git a/apps/server/src/modules/board/controller/column.controller.ts b/apps/server/src/modules/board/controller/column.controller.ts index 870bcc5dc06..9a3da989159 100644 --- a/apps/server/src/modules/board/controller/column.controller.ts +++ b/apps/server/src/modules/board/controller/column.controller.ts @@ -1,3 +1,4 @@ +import { Authenticate, CurrentUser, ICurrentUser } from '@modules/authentication'; import { Body, Controller, @@ -12,11 +13,10 @@ import { } from '@nestjs/common'; import { ApiBody, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; import { ApiValidationError } from '@shared/common'; -import { ICurrentUser, Authenticate, CurrentUser } from '@modules/authentication'; import { BoardUc, ColumnUc } from '../uc'; import { CardResponse, ColumnUrlParams, MoveColumnBodyParams, RenameBodyParams } from './dto'; -import { CardResponseMapper } from './mapper'; import { CreateCardBodyParams } from './dto/card/create-card.body.params'; +import { CardResponseMapper } from './mapper'; @ApiTags('Board Column') @Authenticate('jwt') diff --git a/apps/server/src/modules/board/controller/element.controller.ts b/apps/server/src/modules/board/controller/element.controller.ts index 2bed9006a0f..71ade95db66 100644 --- a/apps/server/src/modules/board/controller/element.controller.ts +++ b/apps/server/src/modules/board/controller/element.controller.ts @@ -1,3 +1,4 @@ +import { Authenticate, CurrentUser, ICurrentUser } from '@modules/authentication'; import { Body, Controller, @@ -12,7 +13,6 @@ import { } from '@nestjs/common'; import { ApiBody, ApiExtraModels, ApiOperation, ApiResponse, ApiTags, getSchemaPath } from '@nestjs/swagger'; import { ApiValidationError } from '@shared/common'; -import { ICurrentUser, Authenticate, CurrentUser } from '@modules/authentication'; import { CardUc } from '../uc'; import { ElementUc } from '../uc/element.uc'; import { diff --git a/apps/server/src/modules/class/entity/class.entity.ts b/apps/server/src/modules/class/entity/class.entity.ts index 3f08ba49582..e5b2dce908e 100644 --- a/apps/server/src/modules/class/entity/class.entity.ts +++ b/apps/server/src/modules/class/entity/class.entity.ts @@ -1,10 +1,10 @@ import { Embedded, Entity, Index, Property } from '@mikro-orm/core'; import { ObjectId } from '@mikro-orm/mongodb'; -import { BaseEntityWithTimestamps } from '@shared/domain/entity/base.entity'; import { EntityId } from '@shared/domain'; +import { BaseEntityWithTimestamps } from '@shared/domain/entity/base.entity'; import { ClassSourceOptionsEntity } from './class-source-options.entity'; -export interface IClassEntityProps { +export interface ClassEntityProps { id?: EntityId; name: string; schoolId: ObjectId; @@ -59,13 +59,13 @@ export class ClassEntity extends BaseEntityWithTimestamps { @Embedded(() => ClassSourceOptionsEntity, { object: true, nullable: true }) sourceOptions?: ClassSourceOptionsEntity; - private validate(props: IClassEntityProps) { + private validate(props: ClassEntityProps) { if (props.gradeLevel !== undefined && (props.gradeLevel < 1 || props.gradeLevel > 13)) { throw new Error('gradeLevel must be value beetween 1 and 13'); } } - constructor(props: IClassEntityProps) { + constructor(props: ClassEntityProps) { super(); this.validate(props); diff --git a/apps/server/src/modules/class/entity/testing/factory/class.entity.factory.ts b/apps/server/src/modules/class/entity/testing/factory/class.entity.factory.ts index b98d20853fc..d122a5933d6 100644 --- a/apps/server/src/modules/class/entity/testing/factory/class.entity.factory.ts +++ b/apps/server/src/modules/class/entity/testing/factory/class.entity.factory.ts @@ -1,11 +1,11 @@ -import { DeepPartial } from 'fishery'; +import { ClassEntity, ClassEntityProps, ClassSourceOptionsEntity } from '@modules/class/entity'; import { BaseFactory } from '@shared/testing/factory/base.factory'; -import { ClassEntity, ClassSourceOptionsEntity, IClassEntityProps } from '@modules/class/entity'; import { ObjectId } from 'bson'; +import { DeepPartial } from 'fishery'; -class ClassEntityFactory extends BaseFactory { +class ClassEntityFactory extends BaseFactory { withUserIds(userIds: ObjectId[]): this { - const params: DeepPartial = { + const params: DeepPartial = { userIds, }; diff --git a/apps/server/src/modules/collaborative-storage/controller/collaborative-storage.controller.spec.ts b/apps/server/src/modules/collaborative-storage/controller/collaborative-storage.controller.spec.ts index 4622fed4c00..77b958f9c7a 100644 --- a/apps/server/src/modules/collaborative-storage/controller/collaborative-storage.controller.spec.ts +++ b/apps/server/src/modules/collaborative-storage/controller/collaborative-storage.controller.spec.ts @@ -1,8 +1,8 @@ -import { CollaborativeStorageController } from '@modules/collaborative-storage/controller/collaborative-storage.controller'; -import { Test, TestingModule } from '@nestjs/testing'; -import { CollaborativeStorageUc } from '@modules/collaborative-storage/uc/collaborative-storage.uc'; import { createMock } from '@golevelup/ts-jest'; import { ICurrentUser } from '@modules/authentication'; +import { CollaborativeStorageController } from '@modules/collaborative-storage/controller/collaborative-storage.controller'; +import { CollaborativeStorageUc } from '@modules/collaborative-storage/uc/collaborative-storage.uc'; +import { Test, TestingModule } from '@nestjs/testing'; import { LegacyLogger } from '@src/core/logger'; describe('CollaborativeStorage Controller', () => { diff --git a/apps/server/src/modules/collaborative-storage/controller/collaborative-storage.controller.ts b/apps/server/src/modules/collaborative-storage/controller/collaborative-storage.controller.ts index 19fbccbed1c..4e11b3c46bd 100644 --- a/apps/server/src/modules/collaborative-storage/controller/collaborative-storage.controller.ts +++ b/apps/server/src/modules/collaborative-storage/controller/collaborative-storage.controller.ts @@ -1,6 +1,6 @@ -import { ApiResponse, ApiTags } from '@nestjs/swagger'; -import { Body, Controller, Param, Patch } from '@nestjs/common'; import { Authenticate, CurrentUser, ICurrentUser } from '@modules/authentication'; +import { Body, Controller, Param, Patch } from '@nestjs/common'; +import { ApiResponse, ApiTags } from '@nestjs/swagger'; import { LegacyLogger } from '@src/core/logger'; import { CollaborativeStorageUc } from '../uc/collaborative-storage.uc'; import { TeamPermissionsBody } from './dto/team-permissions.body.params'; diff --git a/apps/server/src/modules/files-storage-client/dto/copy-file.dto.ts b/apps/server/src/modules/files-storage-client/dto/copy-file.dto.ts index eaddf253791..8c6936f33fa 100644 --- a/apps/server/src/modules/files-storage-client/dto/copy-file.dto.ts +++ b/apps/server/src/modules/files-storage-client/dto/copy-file.dto.ts @@ -1,5 +1,5 @@ import { EntityId } from '@shared/domain'; -import { ICopyFileDomainObjectProps } from '../interfaces'; +import { CopyFileDomainObjectProps } from '../interfaces'; export class CopyFileDto { id?: EntityId | undefined; @@ -8,7 +8,7 @@ export class CopyFileDto { name: string; - constructor(data: ICopyFileDomainObjectProps) { + constructor(data: CopyFileDomainObjectProps) { this.id = data.id; this.sourceId = data.sourceId; this.name = data.name; diff --git a/apps/server/src/modules/files-storage-client/dto/file.dto.ts b/apps/server/src/modules/files-storage-client/dto/file.dto.ts index 38d6daf4c3b..c5a5118ceae 100644 --- a/apps/server/src/modules/files-storage-client/dto/file.dto.ts +++ b/apps/server/src/modules/files-storage-client/dto/file.dto.ts @@ -1,6 +1,6 @@ -import { EntityId } from '@shared/domain'; import { FileRecordParentType } from '@infra/rabbitmq'; -import { IFileDomainObjectProps } from '../interfaces'; +import { EntityId } from '@shared/domain'; +import { FileDomainObjectProps } from '../interfaces'; export class FileDto { id: EntityId; @@ -11,7 +11,7 @@ export class FileDto { parentId: EntityId; - constructor(props: IFileDomainObjectProps) { + constructor(props: FileDomainObjectProps) { this.id = props.id; this.name = props.name; this.parentType = props.parentType; diff --git a/apps/server/src/modules/files-storage-client/index.ts b/apps/server/src/modules/files-storage-client/index.ts index 19248d129de..ae847364123 100644 --- a/apps/server/src/modules/files-storage-client/index.ts +++ b/apps/server/src/modules/files-storage-client/index.ts @@ -1,6 +1,6 @@ export { FileDto } from './dto'; export * from './files-storage-client.module'; -export { IFilesStorageClientConfig } from './interfaces'; +export { FilesStorageClientConfig } from './interfaces'; export { FileParamBuilder } from './mapper/files-storage-param.builder'; export * from './service/copy-files.service'; export { FilesStorageClientAdapterService } from './service/files-storage-client.service'; diff --git a/apps/server/src/modules/files-storage-client/interfaces/copy-file-domain-object-props.ts b/apps/server/src/modules/files-storage-client/interfaces/copy-file-domain-object-props.ts index 89348ea8c68..0dfa8455094 100644 --- a/apps/server/src/modules/files-storage-client/interfaces/copy-file-domain-object-props.ts +++ b/apps/server/src/modules/files-storage-client/interfaces/copy-file-domain-object-props.ts @@ -1,6 +1,6 @@ import { EntityId } from '@shared/domain'; -export interface ICopyFileDomainObjectProps { +export interface CopyFileDomainObjectProps { id?: EntityId | undefined; sourceId: EntityId; name: string; diff --git a/apps/server/src/modules/files-storage-client/interfaces/copy-file-request-info.ts b/apps/server/src/modules/files-storage-client/interfaces/copy-file-request-info.ts index e52bbce9b1a..42c971d9a0d 100644 --- a/apps/server/src/modules/files-storage-client/interfaces/copy-file-request-info.ts +++ b/apps/server/src/modules/files-storage-client/interfaces/copy-file-request-info.ts @@ -1,8 +1,8 @@ import { EntityId } from '@shared/domain'; -import { IFileRequestInfo } from './file-request-info'; +import { FileRequestInfo } from './file-request-info'; -export interface ICopyFilesRequestInfo { +export interface CopyFilesRequestInfo { userId: EntityId; - source: IFileRequestInfo; - target: IFileRequestInfo; + source: FileRequestInfo; + target: FileRequestInfo; } diff --git a/apps/server/src/modules/files-storage-client/interfaces/file-domain-object-props.ts b/apps/server/src/modules/files-storage-client/interfaces/file-domain-object-props.ts index f302e53e9a0..6a0fca7ec2e 100644 --- a/apps/server/src/modules/files-storage-client/interfaces/file-domain-object-props.ts +++ b/apps/server/src/modules/files-storage-client/interfaces/file-domain-object-props.ts @@ -1,7 +1,7 @@ import { EntityId } from '@shared/domain'; import { FileRecordParentType } from '@infra/rabbitmq'; -export interface IFileDomainObjectProps { +export interface FileDomainObjectProps { id: EntityId; name: string; parentType: FileRecordParentType; diff --git a/apps/server/src/modules/files-storage-client/interfaces/file-request-info.ts b/apps/server/src/modules/files-storage-client/interfaces/file-request-info.ts index 12a7898d9cf..4d00caf356b 100644 --- a/apps/server/src/modules/files-storage-client/interfaces/file-request-info.ts +++ b/apps/server/src/modules/files-storage-client/interfaces/file-request-info.ts @@ -1,7 +1,7 @@ import { EntityId } from '@shared/domain'; import { FileRecordParentType } from '@infra/rabbitmq'; -export interface IFileRequestInfo { +export interface FileRequestInfo { schoolId: EntityId; parentType: FileRecordParentType; parentId: EntityId; diff --git a/apps/server/src/modules/files-storage-client/interfaces/files-storage-client-config.ts b/apps/server/src/modules/files-storage-client/interfaces/files-storage-client-config.ts index 8e8457f7c4b..8e746ecdd8d 100644 --- a/apps/server/src/modules/files-storage-client/interfaces/files-storage-client-config.ts +++ b/apps/server/src/modules/files-storage-client/interfaces/files-storage-client-config.ts @@ -1,3 +1,3 @@ -export interface IFilesStorageClientConfig { +export interface FilesStorageClientConfig { INCOMING_REQUEST_TIMEOUT_COPY_API: number; } diff --git a/apps/server/src/modules/files-storage-client/mapper/copy-files-of-parent-param.builder.ts b/apps/server/src/modules/files-storage-client/mapper/copy-files-of-parent-param.builder.ts index 9edee50870a..fe1bbf09c08 100644 --- a/apps/server/src/modules/files-storage-client/mapper/copy-files-of-parent-param.builder.ts +++ b/apps/server/src/modules/files-storage-client/mapper/copy-files-of-parent-param.builder.ts @@ -1,9 +1,9 @@ import { EntityId } from '@shared/domain'; -import { IFileRequestInfo } from '../interfaces'; -import { ICopyFilesRequestInfo } from '../interfaces/copy-file-request-info'; +import { FileRequestInfo } from '../interfaces'; +import { CopyFilesRequestInfo } from '../interfaces/copy-file-request-info'; export class CopyFilesOfParentParamBuilder { - static build(userId: EntityId, source: IFileRequestInfo, target: IFileRequestInfo): ICopyFilesRequestInfo { + static build(userId: EntityId, source: FileRequestInfo, target: FileRequestInfo): CopyFilesRequestInfo { const fileRequestInfo = { userId, source, diff --git a/apps/server/src/modules/files-storage-client/mapper/files-storage-client.mapper.spec.ts b/apps/server/src/modules/files-storage-client/mapper/files-storage-client.mapper.spec.ts index f48d66dce48..a33ef506e51 100644 --- a/apps/server/src/modules/files-storage-client/mapper/files-storage-client.mapper.spec.ts +++ b/apps/server/src/modules/files-storage-client/mapper/files-storage-client.mapper.spec.ts @@ -1,5 +1,5 @@ import { FileRecordParentType } from '@infra/rabbitmq'; -import { ICopyFileDomainObjectProps, IFileDomainObjectProps } from '../interfaces'; +import { CopyFileDomainObjectProps, FileDomainObjectProps } from '../interfaces'; import { FilesStorageClientMapper } from './files-storage-client.mapper'; describe('FilesStorageClientMapper', () => { @@ -15,7 +15,7 @@ describe('FilesStorageClientMapper', () => { parentType: FileRecordParentType.Task, }; - const response: IFileDomainObjectProps[] = [record]; + const response: FileDomainObjectProps[] = [record]; describe('mapfileRecordListResponseToDomainFilesDto', () => { it('Should map to valid file Dtos.', () => { @@ -83,13 +83,13 @@ describe('FilesStorageClientMapper', () => { }); describe('copyFileDto mapper', () => { - const copyFileResponse: ICopyFileDomainObjectProps = { + const copyFileResponse: CopyFileDomainObjectProps = { id: 'id123', sourceId: 'sourceId123', name: 'name', }; - const list: ICopyFileDomainObjectProps[] = [copyFileResponse]; + const list: CopyFileDomainObjectProps[] = [copyFileResponse]; describe('mapCopyFileListResponseToCopyFilesDto', () => { it('Should map to valid file Dtos.', () => { diff --git a/apps/server/src/modules/files-storage-client/mapper/files-storage-client.mapper.ts b/apps/server/src/modules/files-storage-client/mapper/files-storage-client.mapper.ts index 233e47fd4c8..dcc2f927b5f 100644 --- a/apps/server/src/modules/files-storage-client/mapper/files-storage-client.mapper.ts +++ b/apps/server/src/modules/files-storage-client/mapper/files-storage-client.mapper.ts @@ -1,11 +1,11 @@ import { LessonEntity, Submission, Task } from '@shared/domain'; import { FileRecordParentType } from '@infra/rabbitmq'; import { CopyFileDto, FileDto } from '../dto'; -import { EntitiesWithFiles, ICopyFileDomainObjectProps, IFileDomainObjectProps } from '../interfaces'; +import { CopyFileDomainObjectProps, EntitiesWithFiles, FileDomainObjectProps } from '../interfaces'; export class FilesStorageClientMapper { - static mapfileRecordListResponseToDomainFilesDto(fileRecordListResponse: IFileDomainObjectProps[]): FileDto[] { - const filesDto = fileRecordListResponse.map((record: IFileDomainObjectProps) => { + static mapfileRecordListResponseToDomainFilesDto(fileRecordListResponse: FileDomainObjectProps[]): FileDto[] { + const filesDto = fileRecordListResponse.map((record: FileDomainObjectProps) => { const fileDto = FilesStorageClientMapper.mapFileRecordResponseToFileDto(record); return fileDto; @@ -14,7 +14,7 @@ export class FilesStorageClientMapper { return filesDto; } - static mapCopyFileListResponseToCopyFilesDto(copyFileListResponse: ICopyFileDomainObjectProps[]): CopyFileDto[] { + static mapCopyFileListResponseToCopyFilesDto(copyFileListResponse: CopyFileDomainObjectProps[]): CopyFileDto[] { const filesDto = copyFileListResponse.map((response) => { const fileDto = FilesStorageClientMapper.mapCopyFileResponseToCopyFileDto(response); @@ -24,7 +24,7 @@ export class FilesStorageClientMapper { return filesDto; } - static mapFileRecordResponseToFileDto(fileRecordResponse: IFileDomainObjectProps) { + static mapFileRecordResponseToFileDto(fileRecordResponse: FileDomainObjectProps) { const parentType = FilesStorageClientMapper.mapStringToParentType(fileRecordResponse.parentType); const fileDto = new FileDto({ id: fileRecordResponse.id, @@ -36,7 +36,7 @@ export class FilesStorageClientMapper { return fileDto; } - static mapCopyFileResponseToCopyFileDto(response: ICopyFileDomainObjectProps) { + static mapCopyFileResponseToCopyFileDto(response: CopyFileDomainObjectProps) { const dto = new CopyFileDto({ id: response.id, sourceId: response.sourceId, diff --git a/apps/server/src/modules/files-storage-client/mapper/files-storage-param.builder.ts b/apps/server/src/modules/files-storage-client/mapper/files-storage-param.builder.ts index bd37c97ceb5..08ae8552bf0 100644 --- a/apps/server/src/modules/files-storage-client/mapper/files-storage-param.builder.ts +++ b/apps/server/src/modules/files-storage-client/mapper/files-storage-param.builder.ts @@ -1,9 +1,9 @@ import { EntityId } from '@shared/domain'; -import { EntitiesWithFiles, IFileRequestInfo } from '../interfaces'; +import { EntitiesWithFiles, FileRequestInfo } from '../interfaces'; import { FilesStorageClientMapper } from './files-storage-client.mapper'; export class FileParamBuilder { - static build(schoolId: EntityId, parent: EntitiesWithFiles): IFileRequestInfo { + static build(schoolId: EntityId, parent: EntitiesWithFiles): FileRequestInfo { const parentType = FilesStorageClientMapper.mapEntityToParentType(parent); const fileRequestInfo = { parentType, diff --git a/apps/server/src/modules/files-storage-client/service/copy-files.service.spec.ts b/apps/server/src/modules/files-storage-client/service/copy-files.service.spec.ts index 5bfc98a361a..24de3426bc9 100644 --- a/apps/server/src/modules/files-storage-client/service/copy-files.service.spec.ts +++ b/apps/server/src/modules/files-storage-client/service/copy-files.service.spec.ts @@ -1,14 +1,14 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { CopyElementType, CopyHelperService } from '@modules/copy-helper'; import { Test, TestingModule } from '@nestjs/testing'; -import { ComponentType, IComponentProperties } from '@shared/domain'; +import { ComponentProperties, ComponentType } from '@shared/domain'; import { courseFactory, + legacyFileEntityMockFactory, lessonFactory, schoolFactory, - legacyFileEntityMockFactory, setupEntities, } from '@shared/testing'; -import { CopyElementType, CopyHelperService } from '@modules/copy-helper'; import { CopyFilesService } from './copy-files.service'; import { FilesStorageClientAdapterService } from './files-storage-client.service'; @@ -69,13 +69,13 @@ describe('copy files service', () => { const lessonSetup = () => { const { school, imageHTML1, imageHTML2 } = setup(); const originalCourse = courseFactory.build({ school }); - const textContent: IComponentProperties = { + const textContent: ComponentProperties = { title: '', hidden: false, component: ComponentType.TEXT, content: { text: `${imageHTML1} test abschnitt ${imageHTML2}` }, }; - const geoGebraContent: IComponentProperties = { + const geoGebraContent: ComponentProperties = { title: 'geoGebra component 1', hidden: false, component: ComponentType.GEOGEBRA, diff --git a/apps/server/src/modules/files-storage-client/service/files-storage-client.service.ts b/apps/server/src/modules/files-storage-client/service/files-storage-client.service.ts index c632631b884..6cb47305248 100644 --- a/apps/server/src/modules/files-storage-client/service/files-storage-client.service.ts +++ b/apps/server/src/modules/files-storage-client/service/files-storage-client.service.ts @@ -2,8 +2,8 @@ import { Injectable } from '@nestjs/common'; import { EntityId } from '@shared/domain'; import { LegacyLogger } from '@src/core/logger'; import { CopyFileDto, FileDto } from '../dto'; -import { IFileRequestInfo } from '../interfaces'; -import { ICopyFilesRequestInfo } from '../interfaces/copy-file-request-info'; +import { FileRequestInfo } from '../interfaces'; +import { CopyFilesRequestInfo } from '../interfaces/copy-file-request-info'; import { FilesStorageClientMapper } from '../mapper'; import { FilesStorageProducer } from './files-storage.producer'; @@ -13,14 +13,14 @@ export class FilesStorageClientAdapterService { this.logger.setContext(FilesStorageClientAdapterService.name); } - async copyFilesOfParent(param: ICopyFilesRequestInfo): Promise { + async copyFilesOfParent(param: CopyFilesRequestInfo): Promise { const response = await this.fileStorageMQProducer.copyFilesOfParent(param); const fileInfos = FilesStorageClientMapper.mapCopyFileListResponseToCopyFilesDto(response); return fileInfos; } - async listFilesOfParent(param: IFileRequestInfo): Promise { + async listFilesOfParent(param: FileRequestInfo): Promise { const response = await this.fileStorageMQProducer.listFilesOfParent(param); const fileInfos = FilesStorageClientMapper.mapfileRecordListResponseToDomainFilesDto(response); diff --git a/apps/server/src/modules/files-storage-client/service/files-storage.producer.ts b/apps/server/src/modules/files-storage-client/service/files-storage.producer.ts index 34927c01831..5f122263a14 100644 --- a/apps/server/src/modules/files-storage-client/service/files-storage.producer.ts +++ b/apps/server/src/modules/files-storage-client/service/files-storage.producer.ts @@ -1,51 +1,51 @@ import { AmqpConnection } from '@golevelup/nestjs-rabbitmq'; -import { Injectable } from '@nestjs/common'; -import { ConfigService } from '@nestjs/config'; -import { EntityId } from '@shared/domain'; -import { LegacyLogger } from '@src/core/logger'; import { + CopyFileDO, + CopyFilesOfParentParams, + FileDO, + FileRecordParams, FilesStorageEvents, FilesStorageExchange, - ICopyFileDO, - ICopyFilesOfParentParams, - IFileDO, - IFileRecordParams, RpcMessageProducer, } from '@infra/rabbitmq'; -import { IFilesStorageClientConfig } from '../interfaces'; +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { EntityId } from '@shared/domain'; +import { LegacyLogger } from '@src/core/logger'; +import { FilesStorageClientConfig } from '../interfaces'; @Injectable() export class FilesStorageProducer extends RpcMessageProducer { constructor( protected readonly amqpConnection: AmqpConnection, private readonly logger: LegacyLogger, - protected readonly configService: ConfigService + protected readonly configService: ConfigService ) { super(amqpConnection, FilesStorageExchange, configService.get('INCOMING_REQUEST_TIMEOUT_COPY_API')); this.logger.setContext(FilesStorageProducer.name); } - async copyFilesOfParent(payload: ICopyFilesOfParentParams): Promise { + async copyFilesOfParent(payload: CopyFilesOfParentParams): Promise { this.logger.debug({ action: 'copyFilesOfParent:started', payload }); - const response = await this.request(FilesStorageEvents.COPY_FILES_OF_PARENT, payload); + const response = await this.request(FilesStorageEvents.COPY_FILES_OF_PARENT, payload); this.logger.debug({ action: 'copyFilesOfParent:finished', payload }); return response; } - async listFilesOfParent(payload: IFileRecordParams): Promise { + async listFilesOfParent(payload: FileRecordParams): Promise { this.logger.debug({ action: 'listFilesOfParent:started', payload }); - const response = await this.request(FilesStorageEvents.LIST_FILES_OF_PARENT, payload); + const response = await this.request(FilesStorageEvents.LIST_FILES_OF_PARENT, payload); this.logger.debug({ action: 'listFilesOfParent:finished', payload }); return response; } - async deleteFilesOfParent(payload: EntityId): Promise { + async deleteFilesOfParent(payload: EntityId): Promise { this.logger.debug({ action: 'deleteFilesOfParent:started', payload }); - const response = await this.request(FilesStorageEvents.DELETE_FILES_OF_PARENT, payload); + const response = await this.request(FilesStorageEvents.DELETE_FILES_OF_PARENT, payload); this.logger.debug({ action: 'deleteFilesOfParent:finished', payload }); diff --git a/apps/server/src/modules/files-storage/controller/api-test/files-storage-copy-files.api.spec.ts b/apps/server/src/modules/files-storage/controller/api-test/files-storage-copy-files.api.spec.ts index 4a966165633..1711acba050 100644 --- a/apps/server/src/modules/files-storage/controller/api-test/files-storage-copy-files.api.spec.ts +++ b/apps/server/src/modules/files-storage/controller/api-test/files-storage-copy-files.api.spec.ts @@ -1,4 +1,6 @@ import { createMock } from '@golevelup/ts-jest'; +import { AntivirusService } from '@infra/antivirus'; +import { S3ClientAdapter } from '@infra/s3-client'; import { EntityManager, ObjectId } from '@mikro-orm/mongodb'; import { ICurrentUser } from '@modules/authentication'; import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; @@ -6,8 +8,6 @@ import { ExecutionContext, INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { ApiValidationError } from '@shared/common'; import { EntityId, Permission } from '@shared/domain'; -import { AntivirusService } from '@infra/antivirus'; -import { S3ClientAdapter } from '@infra/s3-client'; import { cleanupCollections, courseFactory, diff --git a/apps/server/src/modules/files-storage/controller/api-test/files-storage-delete-files.api.spec.ts b/apps/server/src/modules/files-storage/controller/api-test/files-storage-delete-files.api.spec.ts index 6c1087ce371..ea85d9b656b 100644 --- a/apps/server/src/modules/files-storage/controller/api-test/files-storage-delete-files.api.spec.ts +++ b/apps/server/src/modules/files-storage/controller/api-test/files-storage-delete-files.api.spec.ts @@ -1,4 +1,6 @@ import { createMock } from '@golevelup/ts-jest'; +import { AntivirusService } from '@infra/antivirus'; +import { S3ClientAdapter } from '@infra/s3-client'; import { EntityManager, ObjectId } from '@mikro-orm/mongodb'; import { ICurrentUser } from '@modules/authentication'; import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; @@ -6,8 +8,6 @@ import { ExecutionContext, INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { ApiValidationError } from '@shared/common'; import { EntityId, Permission } from '@shared/domain'; -import { AntivirusService } from '@infra/antivirus'; -import { S3ClientAdapter } from '@infra/s3-client'; import { cleanupCollections, fileRecordFactory, diff --git a/apps/server/src/modules/files-storage/controller/api-test/files-storage-download-upload.api.spec.ts b/apps/server/src/modules/files-storage/controller/api-test/files-storage-download-upload.api.spec.ts index 27661af51f8..8d8e7dbf912 100644 --- a/apps/server/src/modules/files-storage/controller/api-test/files-storage-download-upload.api.spec.ts +++ b/apps/server/src/modules/files-storage/controller/api-test/files-storage-download-upload.api.spec.ts @@ -1,4 +1,6 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { AntivirusService } from '@infra/antivirus'; +import { S3ClientAdapter } from '@infra/s3-client'; import { EntityManager } from '@mikro-orm/mongodb'; import { ICurrentUser } from '@modules/authentication'; import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; @@ -6,8 +8,6 @@ import { ExecutionContext, INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { ApiValidationError } from '@shared/common'; import { EntityId, Permission } from '@shared/domain'; -import { AntivirusService } from '@infra/antivirus'; -import { S3ClientAdapter } from '@infra/s3-client'; import { cleanupCollections, mapUserToCurrentUser, roleFactory, schoolFactory, userFactory } from '@shared/testing'; import NodeClam from 'clamscan'; import { Request } from 'express'; diff --git a/apps/server/src/modules/files-storage/controller/api-test/files-storage-restore-files.api.spec.ts b/apps/server/src/modules/files-storage/controller/api-test/files-storage-restore-files.api.spec.ts index 9e666437748..7f56152f7da 100644 --- a/apps/server/src/modules/files-storage/controller/api-test/files-storage-restore-files.api.spec.ts +++ b/apps/server/src/modules/files-storage/controller/api-test/files-storage-restore-files.api.spec.ts @@ -1,4 +1,6 @@ import { createMock } from '@golevelup/ts-jest'; +import { AntivirusService } from '@infra/antivirus'; +import { S3ClientAdapter } from '@infra/s3-client'; import { EntityManager, ObjectId } from '@mikro-orm/mongodb'; import { ICurrentUser } from '@modules/authentication'; import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; @@ -6,8 +8,6 @@ import { ExecutionContext, INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { ApiValidationError } from '@shared/common'; import { EntityId, Permission } from '@shared/domain'; -import { AntivirusService } from '@infra/antivirus'; -import { S3ClientAdapter } from '@infra/s3-client'; import { cleanupCollections, fileRecordFactory, diff --git a/apps/server/src/modules/files-storage/controller/files-storage.consumer.ts b/apps/server/src/modules/files-storage/controller/files-storage.consumer.ts index fc500fedee1..3b6b5654c4c 100644 --- a/apps/server/src/modules/files-storage/controller/files-storage.consumer.ts +++ b/apps/server/src/modules/files-storage/controller/files-storage.consumer.ts @@ -1,10 +1,10 @@ import { RabbitPayload, RabbitRPC } from '@golevelup/nestjs-rabbitmq'; +import { CopyFileDO, FileDO, FilesStorageEvents, FilesStorageExchange } from '@infra/rabbitmq'; +import { RpcMessage } from '@infra/rabbitmq/rpc-message'; import { MikroORM, UseRequestContext } from '@mikro-orm/core'; import { Injectable } from '@nestjs/common'; import { EntityId } from '@shared/domain'; -import { RpcMessage } from '@infra/rabbitmq/rpc-message'; import { LegacyLogger } from '@src/core/logger'; -import { FilesStorageEvents, FilesStorageExchange, ICopyFileDO, IFileDO } from '@infra/rabbitmq'; import { FilesStorageMapper } from '../mapper'; import { FilesStorageService } from '../service/files-storage.service'; import { PreviewService } from '../service/preview.service'; @@ -30,7 +30,7 @@ export class FilesStorageConsumer { @UseRequestContext() public async copyFilesOfParent( @RabbitPayload() payload: CopyFilesOfParentPayload - ): Promise> { + ): Promise> { this.logger.debug({ action: 'copyFilesOfParent', payload }); const { userId, source, target } = payload; @@ -45,7 +45,7 @@ export class FilesStorageConsumer { queue: FilesStorageEvents.LIST_FILES_OF_PARENT, }) @UseRequestContext() - public async getFilesOfParent(@RabbitPayload() payload: FileRecordParams): Promise> { + public async getFilesOfParent(@RabbitPayload() payload: FileRecordParams): Promise> { this.logger.debug({ action: 'getFilesOfParent', payload }); const [fileRecords, total] = await this.filesStorageService.getFileRecordsOfParent(payload.parentId); @@ -60,7 +60,7 @@ export class FilesStorageConsumer { queue: FilesStorageEvents.DELETE_FILES_OF_PARENT, }) @UseRequestContext() - public async deleteFilesOfParent(@RabbitPayload() payload: EntityId): Promise> { + public async deleteFilesOfParent(@RabbitPayload() payload: EntityId): Promise> { this.logger.debug({ action: 'deleteFilesOfParent', payload }); const [fileRecords, total] = await this.filesStorageService.getFileRecordsOfParent(payload); diff --git a/apps/server/src/modules/files-storage/entity/filerecord.entity.spec.ts b/apps/server/src/modules/files-storage/entity/filerecord.entity.spec.ts index f497ff39a6c..a3d28c5bdec 100644 --- a/apps/server/src/modules/files-storage/entity/filerecord.entity.spec.ts +++ b/apps/server/src/modules/files-storage/entity/filerecord.entity.spec.ts @@ -6,8 +6,8 @@ import { PreviewInputMimeTypes } from '../interface'; import { FileRecord, FileRecordParentType, + FileRecordProperties, FileRecordSecurityCheck, - IFileRecordProperties, PreviewStatus, ScanStatus, } from './filerecord.entity'; @@ -18,7 +18,7 @@ describe('FileRecord Entity', () => { }); describe('when creating a new instance using the constructor', () => { - let props: IFileRecordProperties; + let props: FileRecordProperties; beforeEach(() => { props = { diff --git a/apps/server/src/modules/files-storage/entity/filerecord.entity.ts b/apps/server/src/modules/files-storage/entity/filerecord.entity.ts index 26f2807924c..5f7c01ac2d8 100644 --- a/apps/server/src/modules/files-storage/entity/filerecord.entity.ts +++ b/apps/server/src/modules/files-storage/entity/filerecord.entity.ts @@ -34,7 +34,7 @@ export enum PreviewStatus { PREVIEW_NOT_POSSIBLE_WRONG_MIME_TYPE = 'preview_not_possible_wrong_mime_type', } -export interface IFileRecordSecurityCheckProperties { +export interface FileRecordSecurityCheckProperties { status?: ScanStatus; reason?: string; requestToken?: string; @@ -56,7 +56,7 @@ export class FileRecordSecurityCheck { @Property() updatedAt = new Date(); - constructor(props: IFileRecordSecurityCheckProperties) { + constructor(props: FileRecordSecurityCheckProperties) { if (props.status !== undefined) { this.status = props.status; } @@ -69,7 +69,7 @@ export class FileRecordSecurityCheck { } } -export interface IFileRecordProperties { +export interface FileRecordProperties { size: number; name: string; mimeType: string; @@ -81,13 +81,13 @@ export interface IFileRecordProperties { isCopyFrom?: EntityId; } -interface IParentInfo { +interface ParentInfo { schoolId: EntityId; parentId: EntityId; parentType: FileRecordParentType; } -// TODO: IEntityWithSchool +// TODO: EntityWithSchool /** * Note: The file record entity will not manage any entity relations by itself. @@ -150,7 +150,7 @@ export class FileRecord extends BaseEntityWithTimestamps { return result; } - constructor(props: IFileRecordProperties) { + constructor(props: FileRecordProperties) { super(); this.size = props.size; this.name = props.name; @@ -177,7 +177,7 @@ export class FileRecord extends BaseEntityWithTimestamps { return this.securityCheck.requestToken; } - public copy(userId: EntityId, targetParentInfo: IParentInfo): FileRecord { + public copy(userId: EntityId, targetParentInfo: ParentInfo): FileRecord { const { size, name, mimeType, id } = this; const { parentType, parentId, schoolId } = targetParentInfo; @@ -261,7 +261,7 @@ export class FileRecord extends BaseEntityWithTimestamps { return isPreviewPossible; } - public getParentInfo(): IParentInfo { + public getParentInfo(): ParentInfo { const { parentId, parentType, schoolId } = this; return { parentId, parentType, schoolId }; diff --git a/apps/server/src/modules/files-storage/files-storage.config.ts b/apps/server/src/modules/files-storage/files-storage.config.ts index 985b07f0ef1..5c02be598e6 100644 --- a/apps/server/src/modules/files-storage/files-storage.config.ts +++ b/apps/server/src/modules/files-storage/files-storage.config.ts @@ -1,9 +1,9 @@ import { Configuration } from '@hpi-schul-cloud/commons'; import { S3Config } from '@infra/s3-client'; -import { ICoreModuleConfig } from '@src/core'; +import { CoreModuleConfig } from '@src/core'; export const FILES_STORAGE_S3_CONNECTION = 'FILES_STORAGE_S3_CONNECTION'; -export interface IFileStorageConfig extends ICoreModuleConfig { +export interface FileStorageConfig extends CoreModuleConfig { MAX_FILE_SIZE: number; MAX_SECURITY_CHECK_FILE_SIZE: number; USE_STREAM_TO_ANTIVIRUS: boolean; @@ -14,7 +14,7 @@ export const defaultConfig = { INCOMING_REQUEST_TIMEOUT: Configuration.get('FILES_STORAGE__INCOMING_REQUEST_TIMEOUT') as number, }; -const fileStorageConfig: IFileStorageConfig = { +const fileStorageConfig: FileStorageConfig = { INCOMING_REQUEST_TIMEOUT_COPY_API: Configuration.get('INCOMING_REQUEST_TIMEOUT_COPY_API') as number, MAX_FILE_SIZE: Configuration.get('FILES_STORAGE__MAX_FILE_SIZE') as number, MAX_SECURITY_CHECK_FILE_SIZE: Configuration.get('FILES_STORAGE__MAX_FILE_SIZE') as number, diff --git a/apps/server/src/modules/files-storage/service/files-storage.service.ts b/apps/server/src/modules/files-storage/service/files-storage.service.ts index 6eb9e89ea96..aa8cdce73f1 100644 --- a/apps/server/src/modules/files-storage/service/files-storage.service.ts +++ b/apps/server/src/modules/files-storage/service/files-storage.service.ts @@ -25,7 +25,7 @@ import { import { FileDto } from '../dto'; import { FileRecord, ScanStatus } from '../entity'; import { ErrorType } from '../error'; -import { FILES_STORAGE_S3_CONNECTION, IFileStorageConfig } from '../files-storage.config'; +import { FileStorageConfig, FILES_STORAGE_S3_CONNECTION } from '../files-storage.config'; import { createCopyFiles, createFileRecord, @@ -45,7 +45,7 @@ export class FilesStorageService { private readonly fileRecordRepo: FileRecordRepo, @Inject(FILES_STORAGE_S3_CONNECTION) private readonly storageClient: S3ClientAdapter, private readonly antivirusService: AntivirusService, - private readonly configService: ConfigService, + private readonly configService: ConfigService, private logger: LegacyLogger ) { this.logger.setContext(FilesStorageService.name); diff --git a/apps/server/src/modules/group/controller/group.controller.ts b/apps/server/src/modules/group/controller/group.controller.ts index c92ee337050..2ae47b54552 100644 --- a/apps/server/src/modules/group/controller/group.controller.ts +++ b/apps/server/src/modules/group/controller/group.controller.ts @@ -6,7 +6,7 @@ import { Page } from '@shared/domain'; import { ErrorResponse } from '@src/core/error/dto'; import { GroupUc } from '../uc'; import { ClassInfoDto, ResolvedGroupDto } from '../uc/dto'; -import { ClassInfoSearchListResponse, ClassSortParams, GroupIdParams, GroupResponse, ClassFilterParams } from './dto'; +import { ClassFilterParams, ClassInfoSearchListResponse, ClassSortParams, GroupIdParams, GroupResponse } from './dto'; import { GroupResponseMapper } from './mapper'; @ApiTags('Group') diff --git a/apps/server/src/modules/h5p-editor/controller/api-test/h5p-editor-delete.api.spec.ts b/apps/server/src/modules/h5p-editor/controller/api-test/h5p-editor-delete.api.spec.ts index e2af08f3fd5..ff093b2c712 100644 --- a/apps/server/src/modules/h5p-editor/controller/api-test/h5p-editor-delete.api.spec.ts +++ b/apps/server/src/modules/h5p-editor/controller/api-test/h5p-editor-delete.api.spec.ts @@ -1,9 +1,9 @@ -import { DeepMocked, createMock } from '@golevelup/ts-jest/lib/mocks'; +import { createMock, DeepMocked } from '@golevelup/ts-jest/lib/mocks'; +import { S3ClientAdapter } from '@infra/s3-client'; import { EntityManager, ObjectId } from '@mikro-orm/mongodb'; import { ExecutionContext, INestApplication } from '@nestjs/common'; import { Test } from '@nestjs/testing'; import { Permission } from '@shared/domain'; -import { S3ClientAdapter } from '@infra/s3-client'; import { cleanupCollections, mapUserToCurrentUser, roleFactory, schoolFactory, userFactory } from '@shared/testing'; import { ICurrentUser } from '@src/modules/authentication'; import { JwtAuthGuard } from '@src/modules/authentication/guard/jwt-auth.guard'; diff --git a/apps/server/src/modules/h5p-editor/controller/api-test/h5p-editor-files.api.spec.ts b/apps/server/src/modules/h5p-editor/controller/api-test/h5p-editor-files.api.spec.ts index 05132888f71..5ee9d7f15f7 100644 --- a/apps/server/src/modules/h5p-editor/controller/api-test/h5p-editor-files.api.spec.ts +++ b/apps/server/src/modules/h5p-editor/controller/api-test/h5p-editor-files.api.spec.ts @@ -1,10 +1,10 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { S3ClientAdapter } from '@infra/s3-client'; import { ILibraryName } from '@lumieducation/h5p-server'; import { ContentMetadata } from '@lumieducation/h5p-server/build/src/ContentMetadata'; import { EntityManager } from '@mikro-orm/mongodb'; import { HttpStatus, INestApplication } from '@nestjs/common'; import { Test } from '@nestjs/testing'; -import { S3ClientAdapter } from '@infra/s3-client'; import { courseFactory, h5pContentFactory, @@ -14,7 +14,7 @@ import { } from '@shared/testing'; import { ObjectID } from 'bson'; import { Readable } from 'stream'; -import { H5PContent, H5PContentParentType, IH5PContentProperties, H5pEditorTempFile } from '../../entity'; +import { H5PContent, H5PContentParentType, H5PContentProperties, H5pEditorTempFile } from '../../entity'; import { H5PEditorTestModule } from '../../h5p-editor-test.module'; import { H5P_CONTENT_S3_CONNECTION, H5P_LIBRARIES_S3_CONNECTION } from '../../h5p-editor.config'; import { ContentStorage, LibraryStorage, TemporaryFileStorage } from '../../service'; @@ -45,7 +45,7 @@ const helpers = { const content = { data: `Data #${n}`, }; - const h5pContentProperties: IH5PContentProperties = { + const h5pContentProperties: H5PContentProperties = { creatorId: new ObjectID().toString(), parentId: new ObjectID().toString(), schoolId: new ObjectID().toString(), diff --git a/apps/server/src/modules/h5p-editor/controller/api-test/h5p-editor-get-editor.api.spec.ts b/apps/server/src/modules/h5p-editor/controller/api-test/h5p-editor-get-editor.api.spec.ts index 3f738fd67c0..c22c7394fab 100644 --- a/apps/server/src/modules/h5p-editor/controller/api-test/h5p-editor-get-editor.api.spec.ts +++ b/apps/server/src/modules/h5p-editor/controller/api-test/h5p-editor-get-editor.api.spec.ts @@ -1,9 +1,9 @@ -import { DeepMocked, createMock } from '@golevelup/ts-jest/lib/mocks'; +import { createMock, DeepMocked } from '@golevelup/ts-jest/lib/mocks'; +import { S3ClientAdapter } from '@infra/s3-client'; import { EntityManager, ObjectId } from '@mikro-orm/mongodb'; import { ExecutionContext, INestApplication } from '@nestjs/common'; import { Test } from '@nestjs/testing'; import { Permission } from '@shared/domain'; -import { S3ClientAdapter } from '@infra/s3-client'; import { cleanupCollections, mapUserToCurrentUser, roleFactory, schoolFactory, userFactory } from '@shared/testing'; import { ICurrentUser } from '@src/modules/authentication'; import { JwtAuthGuard } from '@src/modules/authentication/guard/jwt-auth.guard'; diff --git a/apps/server/src/modules/h5p-editor/controller/api-test/h5p-editor-get-player.api.spec.ts b/apps/server/src/modules/h5p-editor/controller/api-test/h5p-editor-get-player.api.spec.ts index 6e98bb6905a..cb5a3d487eb 100644 --- a/apps/server/src/modules/h5p-editor/controller/api-test/h5p-editor-get-player.api.spec.ts +++ b/apps/server/src/modules/h5p-editor/controller/api-test/h5p-editor-get-player.api.spec.ts @@ -1,10 +1,10 @@ -import { DeepMocked, createMock } from '@golevelup/ts-jest/lib/mocks'; +import { createMock, DeepMocked } from '@golevelup/ts-jest/lib/mocks'; +import { S3ClientAdapter } from '@infra/s3-client'; import { IPlayerModel } from '@lumieducation/h5p-server'; import { EntityManager, ObjectId } from '@mikro-orm/mongodb'; import { ExecutionContext, INestApplication } from '@nestjs/common'; import { Test } from '@nestjs/testing'; import { Permission } from '@shared/domain'; -import { S3ClientAdapter } from '@infra/s3-client'; import { cleanupCollections, mapUserToCurrentUser, roleFactory, schoolFactory, userFactory } from '@shared/testing'; import { ICurrentUser } from '@src/modules/authentication'; import { JwtAuthGuard } from '@src/modules/authentication/guard/jwt-auth.guard'; diff --git a/apps/server/src/modules/h5p-editor/controller/h5p-editor.controller.ts b/apps/server/src/modules/h5p-editor/controller/h5p-editor.controller.ts index 8c80d6bc0e4..c46c782da2d 100644 --- a/apps/server/src/modules/h5p-editor/controller/h5p-editor.controller.ts +++ b/apps/server/src/modules/h5p-editor/controller/h5p-editor.controller.ts @@ -1,3 +1,5 @@ +import { CurrentUser, ICurrentUser } from '@modules/authentication'; +import { Authenticate } from '@modules/authentication/decorator/auth.decorator'; import { BadRequestException, Body, @@ -18,13 +20,10 @@ import { import { FileFieldsInterceptor } from '@nestjs/platform-express'; import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; import { ApiValidationError } from '@shared/common'; -import { ICurrentUser, CurrentUser } from '@modules/authentication'; import { Request, Response } from 'express'; -import { Authenticate } from '@modules/authentication/decorator/auth.decorator'; import { H5PEditorUc } from '../uc/h5p.uc'; -import { AjaxPostBodyParamsTransformPipe } from './dto/ajax/post.body.params.transform-pipe'; import { AjaxGetQueryParams, AjaxPostBodyParams, @@ -37,6 +36,7 @@ import { PostH5PContentCreateParams, SaveH5PEditorParams, } from './dto'; +import { AjaxPostBodyParamsTransformPipe } from './dto/ajax/post.body.params.transform-pipe'; import { H5PEditorModelContentResponse, H5PEditorModelResponse, H5PSaveResponse } from './dto/h5p-editor.response'; @ApiTags('h5p-editor') diff --git a/apps/server/src/modules/h5p-editor/entity/h5p-content.entity.spec.ts b/apps/server/src/modules/h5p-editor/entity/h5p-content.entity.spec.ts index cea707c8ccf..04655d5036a 100644 --- a/apps/server/src/modules/h5p-editor/entity/h5p-content.entity.spec.ts +++ b/apps/server/src/modules/h5p-editor/entity/h5p-content.entity.spec.ts @@ -1,10 +1,10 @@ import { ObjectId } from '@mikro-orm/mongodb'; -import { ContentMetadata, H5PContent, H5PContentParentType, IH5PContentProperties } from './h5p-content.entity'; +import { ContentMetadata, H5PContent, H5PContentParentType, H5PContentProperties } from './h5p-content.entity'; describe('H5PContent class', () => { describe('when an H5PContent instance is created', () => { const setup = () => { - const dummyIH5PContentProperties: IH5PContentProperties = { + const dummyH5PContentProperties: H5PContentProperties = { creatorId: '507f1f77bcf86cd799439011', parentType: H5PContentParentType.Lesson, parentId: '507f1f77bcf86cd799439012', @@ -23,19 +23,19 @@ describe('H5PContent class', () => { content: {}, }; - const h5pContent = new H5PContent(dummyIH5PContentProperties); - return { h5pContent, dummyIH5PContentProperties }; + const h5pContent = new H5PContent(dummyH5PContentProperties); + return { h5pContent, dummyH5PContentProperties }; }; it('should correctly return the creatorId', () => { - const { h5pContent, dummyIH5PContentProperties } = setup(); - const expectedCreatorId = new ObjectId(dummyIH5PContentProperties.creatorId).toHexString(); + const { h5pContent, dummyH5PContentProperties } = setup(); + const expectedCreatorId = new ObjectId(dummyH5PContentProperties.creatorId).toHexString(); expect(h5pContent.creatorId).toBe(expectedCreatorId); }); it('should correctly return the schoolId', () => { - const { h5pContent, dummyIH5PContentProperties } = setup(); - const expectedSchoolId = new ObjectId(dummyIH5PContentProperties.schoolId).toHexString(); + const { h5pContent, dummyH5PContentProperties } = setup(); + const expectedSchoolId = new ObjectId(dummyH5PContentProperties.schoolId).toHexString(); expect(h5pContent.schoolId).toBe(expectedSchoolId); }); }); diff --git a/apps/server/src/modules/h5p-editor/entity/h5p-content.entity.ts b/apps/server/src/modules/h5p-editor/entity/h5p-content.entity.ts index 3f9e6113172..7b96978294f 100644 --- a/apps/server/src/modules/h5p-editor/entity/h5p-content.entity.ts +++ b/apps/server/src/modules/h5p-editor/entity/h5p-content.entity.ts @@ -106,7 +106,7 @@ export enum H5PContentParentType { 'Lesson' = 'lessons', } -export interface IH5PContentProperties { +export interface H5PContentProperties { creatorId: EntityId; parentType: H5PContentParentType; parentId: EntityId; @@ -149,7 +149,7 @@ export class H5PContent extends BaseEntityWithTimestamps { @Property({ type: JsonType }) content: unknown; - constructor({ parentType, parentId, creatorId, schoolId, metadata, content }: IH5PContentProperties) { + constructor({ parentType, parentId, creatorId, schoolId, metadata, content }: H5PContentProperties) { super(); this.parentType = parentType; diff --git a/apps/server/src/modules/h5p-editor/entity/h5p-editor-tempfile.entity.ts b/apps/server/src/modules/h5p-editor/entity/h5p-editor-tempfile.entity.ts index a4ebeb30e8a..f7a6913eef7 100644 --- a/apps/server/src/modules/h5p-editor/entity/h5p-editor-tempfile.entity.ts +++ b/apps/server/src/modules/h5p-editor/entity/h5p-editor-tempfile.entity.ts @@ -1,8 +1,8 @@ +import { IFileStats, ITemporaryFile } from '@lumieducation/h5p-server'; import { Entity, Property } from '@mikro-orm/core'; -import { ITemporaryFile, IFileStats } from '@lumieducation/h5p-server'; import { BaseEntityWithTimestamps } from '@shared/domain'; -export interface ITemporaryFileProperties { +export interface TemporaryFileProperties { filename: string; ownedByUserId: string; expiresAt: Date; @@ -30,7 +30,7 @@ export class H5pEditorTempFile extends BaseEntityWithTimestamps implements ITemp @Property() size: number; - constructor({ filename, ownedByUserId, expiresAt, birthtime, size }: ITemporaryFileProperties) { + constructor({ filename, ownedByUserId, expiresAt, birthtime, size }: TemporaryFileProperties) { super(); this.filename = filename; this.ownedByUserId = ownedByUserId; diff --git a/apps/server/src/modules/h5p-editor/service/contentStorage.service.spec.ts b/apps/server/src/modules/h5p-editor/service/contentStorage.service.spec.ts index df19f05ae21..4fb094e8e5b 100644 --- a/apps/server/src/modules/h5p-editor/service/contentStorage.service.spec.ts +++ b/apps/server/src/modules/h5p-editor/service/contentStorage.service.spec.ts @@ -1,14 +1,14 @@ import { HeadObjectCommandOutput } from '@aws-sdk/client-s3'; -import { DeepMocked, createMock } from '@golevelup/ts-jest'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { S3ClientAdapter } from '@infra/s3-client'; import { IContentMetadata, ILibraryName, IUser, LibraryName } from '@lumieducation/h5p-server'; import { HttpException, InternalServerErrorException, NotFoundException } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { IEntity } from '@shared/domain'; -import { S3ClientAdapter } from '@infra/s3-client'; import { ObjectID } from 'bson'; import { Readable } from 'stream'; import { GetH5PFileResponse } from '../controller/dto'; -import { H5PContent, H5PContentParentType, IH5PContentProperties } from '../entity'; +import { H5PContent, H5PContentParentType, H5PContentProperties } from '../entity'; import { H5P_CONTENT_S3_CONNECTION } from '../h5p-editor.config'; import { H5PContentRepo } from '../repo'; import { H5PContentParentParams, LumiUserWithContentData } from '../types/lumi-types'; @@ -40,7 +40,7 @@ const helpers = { const content = { data: `Data #${n}`, }; - const h5pContentProperties: IH5PContentProperties = { + const h5pContentProperties: H5PContentProperties = { creatorId: new ObjectID().toString(), parentId: new ObjectID().toString(), schoolId: new ObjectID().toString(), diff --git a/apps/server/src/modules/h5p-editor/uc/h5p-delete.uc.spec.ts b/apps/server/src/modules/h5p-editor/uc/h5p-delete.uc.spec.ts index 174d5c0fd3a..040d5817f8a 100644 --- a/apps/server/src/modules/h5p-editor/uc/h5p-delete.uc.spec.ts +++ b/apps/server/src/modules/h5p-editor/uc/h5p-delete.uc.spec.ts @@ -1,4 +1,4 @@ -import { DeepMocked, createMock } from '@golevelup/ts-jest'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { H5PEditor, H5PPlayer } from '@lumieducation/h5p-server'; import { ForbiddenException, NotFoundException } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; @@ -6,9 +6,9 @@ import { h5pContentFactory, setupEntities } from '@shared/testing'; import { ICurrentUser } from '@src/modules/authentication'; import { AuthorizationContextBuilder, AuthorizationReferenceService } from '@src/modules/authorization/domain'; import { UserService } from '@src/modules/user'; +import { H5PAjaxEndpointProvider } from '../provider'; import { H5PContentRepo } from '../repo'; import { LibraryStorage } from '../service'; -import { H5PAjaxEndpointProvider } from '../provider'; import { H5PEditorUc } from './h5p.uc'; const createParams = () => { diff --git a/apps/server/src/modules/h5p-editor/uc/h5p-files.uc.spec.ts b/apps/server/src/modules/h5p-editor/uc/h5p-files.uc.spec.ts index ab38282cc56..c1c7987ace8 100644 --- a/apps/server/src/modules/h5p-editor/uc/h5p-files.uc.spec.ts +++ b/apps/server/src/modules/h5p-editor/uc/h5p-files.uc.spec.ts @@ -1,4 +1,4 @@ -import { DeepMocked, createMock } from '@golevelup/ts-jest'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { H5PAjaxEndpoint, H5PEditor, IPlayerModel } from '@lumieducation/h5p-server'; import { ForbiddenException, NotFoundException } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; @@ -8,9 +8,9 @@ import { AuthorizationContextBuilder, AuthorizationReferenceService } from '@src import { UserService } from '@src/modules/user'; import { Request } from 'express'; import { Readable } from 'stream'; +import { H5PEditorProvider, H5PPlayerProvider } from '../provider'; import { H5PContentRepo } from '../repo'; import { ContentStorage, LibraryStorage } from '../service'; -import { H5PEditorProvider, H5PPlayerProvider } from '../provider'; import { TemporaryFileStorage } from '../service/temporary-file-storage.service'; import { H5PEditorUc } from './h5p.uc'; diff --git a/apps/server/src/modules/h5p-editor/uc/h5p-get-editor.uc.spec.ts b/apps/server/src/modules/h5p-editor/uc/h5p-get-editor.uc.spec.ts index 4322dd06352..824b1f7de8a 100644 --- a/apps/server/src/modules/h5p-editor/uc/h5p-get-editor.uc.spec.ts +++ b/apps/server/src/modules/h5p-editor/uc/h5p-get-editor.uc.spec.ts @@ -1,4 +1,4 @@ -import { DeepMocked, createMock } from '@golevelup/ts-jest'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { H5PEditor, H5PPlayer, IEditorModel } from '@lumieducation/h5p-server'; import { ForbiddenException, NotFoundException } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; @@ -8,9 +8,9 @@ import { h5pContentFactory, setupEntities } from '@shared/testing'; import { ICurrentUser } from '@src/modules/authentication'; import { AuthorizationContextBuilder, AuthorizationReferenceService } from '@src/modules/authorization/domain'; import { UserService } from '@src/modules/user'; +import { H5PAjaxEndpointProvider } from '../provider'; import { H5PContentRepo } from '../repo'; import { LibraryStorage } from '../service'; -import { H5PAjaxEndpointProvider } from '../provider'; import { H5PEditorUc } from './h5p.uc'; const createParams = () => { diff --git a/apps/server/src/modules/h5p-editor/uc/h5p-get-player.uc.spec.ts b/apps/server/src/modules/h5p-editor/uc/h5p-get-player.uc.spec.ts index 6db9d27a905..c8648cb413c 100644 --- a/apps/server/src/modules/h5p-editor/uc/h5p-get-player.uc.spec.ts +++ b/apps/server/src/modules/h5p-editor/uc/h5p-get-player.uc.spec.ts @@ -1,14 +1,14 @@ -import { DeepMocked, createMock } from '@golevelup/ts-jest'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { H5PEditor, H5PPlayer, IPlayerModel } from '@lumieducation/h5p-server'; +import { ForbiddenException, NotFoundException } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { h5pContentFactory, setupEntities } from '@shared/testing'; import { ICurrentUser } from '@src/modules/authentication'; -import { ForbiddenException, NotFoundException } from '@nestjs/common'; import { AuthorizationContextBuilder, AuthorizationReferenceService } from '@src/modules/authorization/domain'; import { UserService } from '@src/modules/user'; +import { H5PAjaxEndpointProvider } from '../provider'; import { H5PContentRepo } from '../repo'; import { LibraryStorage } from '../service'; -import { H5PAjaxEndpointProvider } from '../provider'; import { H5PEditorUc } from './h5p.uc'; const createParams = () => { diff --git a/apps/server/src/modules/h5p-editor/uc/h5p-save-create.uc.spec.ts b/apps/server/src/modules/h5p-editor/uc/h5p-save-create.uc.spec.ts index 2bd23edecdc..2fba57f5bc2 100644 --- a/apps/server/src/modules/h5p-editor/uc/h5p-save-create.uc.spec.ts +++ b/apps/server/src/modules/h5p-editor/uc/h5p-save-create.uc.spec.ts @@ -1,18 +1,18 @@ -import { DeepMocked, createMock } from '@golevelup/ts-jest'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { H5PEditor, H5PPlayer } from '@lumieducation/h5p-server'; +import { ObjectId } from '@mikro-orm/mongodb'; +import { ForbiddenException } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { h5pContentFactory, setupEntities } from '@shared/testing'; import { ICurrentUser } from '@src/modules/authentication'; -import { ObjectId } from '@mikro-orm/mongodb'; -import { ForbiddenException } from '@nestjs/common'; import { AuthorizationContextBuilder, AuthorizationReferenceService } from '@src/modules/authorization/domain'; import { UserService } from '@src/modules/user'; -import { LibraryStorage } from '../service'; -import { H5PAjaxEndpointProvider } from '../provider'; -import { H5PEditorUc } from './h5p.uc'; import { H5PContentParentType } from '../entity'; +import { H5PAjaxEndpointProvider } from '../provider'; import { H5PContentRepo } from '../repo'; +import { LibraryStorage } from '../service'; import { LumiUserWithContentData } from '../types/lumi-types'; +import { H5PEditorUc } from './h5p.uc'; const createParams = () => { const { content: parameters, metadata } = h5pContentFactory.build(); diff --git a/apps/server/src/modules/learnroom/common-cartridge/common-cartridge-element.interface.ts b/apps/server/src/modules/learnroom/common-cartridge/common-cartridge-element.interface.ts index 7f30cec6977..400b31dabb1 100644 --- a/apps/server/src/modules/learnroom/common-cartridge/common-cartridge-element.interface.ts +++ b/apps/server/src/modules/learnroom/common-cartridge/common-cartridge-element.interface.ts @@ -1,3 +1,3 @@ -export interface ICommonCartridgeElement { +export interface CommonCartridgeElement { transform(): Record; } diff --git a/apps/server/src/modules/learnroom/common-cartridge/common-cartridge-file-builder.spec.ts b/apps/server/src/modules/learnroom/common-cartridge/common-cartridge-file-builder.spec.ts index 9c44732c61e..7b5709f8a9e 100644 --- a/apps/server/src/modules/learnroom/common-cartridge/common-cartridge-file-builder.spec.ts +++ b/apps/server/src/modules/learnroom/common-cartridge/common-cartridge-file-builder.spec.ts @@ -1,7 +1,7 @@ import AdmZip from 'adm-zip'; import { parseStringPromise } from 'xml2js'; -import { CommonCartridgeFileBuilder, ICommonCartridgeFileBuilderOptions } from './common-cartridge-file-builder'; import { CommonCartridgeResourceType, CommonCartridgeVersion } from './common-cartridge-enums'; +import { CommonCartridgeFileBuilder, CommonCartridgeFileBuilderOptions } from './common-cartridge-file-builder'; import { ICommonCartridgeOrganizationProps } from './common-cartridge-organization-item-element'; import { ICommonCartridgeResourceProps } from './common-cartridge-resource-item-element'; @@ -9,7 +9,7 @@ describe('CommonCartridgeFileBuilder', () => { let archive: AdmZip; const getFileContentAsString = (path: string): string | undefined => archive.getEntry(path)?.getData().toString(); - const fileBuilderOptions: ICommonCartridgeFileBuilderOptions = { + const fileBuilderOptions: CommonCartridgeFileBuilderOptions = { identifier: 'file-identifier', copyrightOwners: 'Placeholder Copyright', creationYear: 'Placeholder Creation Year', diff --git a/apps/server/src/modules/learnroom/common-cartridge/common-cartridge-file-builder.ts b/apps/server/src/modules/learnroom/common-cartridge/common-cartridge-file-builder.ts index e119aba85fe..5a40269c57b 100644 --- a/apps/server/src/modules/learnroom/common-cartridge/common-cartridge-file-builder.ts +++ b/apps/server/src/modules/learnroom/common-cartridge/common-cartridge-file-builder.ts @@ -1,18 +1,18 @@ import AdmZip from 'adm-zip'; import { Builder } from 'xml2js'; +import { CommonCartridgeElement } from './common-cartridge-element.interface'; import { CommonCartridgeVersion } from './common-cartridge-enums'; -import { ICommonCartridgeElement } from './common-cartridge-element.interface'; import { CommonCartridgeManifestElement } from './common-cartridge-manifest-element'; -import { - CommonCartridgeResourceItemElement, - ICommonCartridgeResourceProps, -} from './common-cartridge-resource-item-element'; import { CommonCartridgeOrganizationItemElement, ICommonCartridgeOrganizationProps, } from './common-cartridge-organization-item-element'; +import { + CommonCartridgeResourceItemElement, + ICommonCartridgeResourceProps, +} from './common-cartridge-resource-item-element'; -export type ICommonCartridgeFileBuilderOptions = { +export type CommonCartridgeFileBuilderOptions = { identifier: string; title: string; copyrightOwners: string; @@ -39,11 +39,11 @@ class CommonCartridgeOrganizationBuilder implements ICommonCartridgeOrganization private readonly zipBuilder: AdmZip ) {} - get organization(): ICommonCartridgeElement { + get organization(): CommonCartridgeElement { return new CommonCartridgeOrganizationItemElement(this.props); } - get resources(): ICommonCartridgeElement[] { + get resources(): CommonCartridgeElement[] { return this.props.resources.map( (resourceProps) => new CommonCartridgeResourceItemElement(resourceProps, this.xmlBuilder) ); @@ -68,7 +68,7 @@ export class CommonCartridgeFileBuilder implements ICommonCartridgeFileBuilder { private readonly resources = new Array(); - constructor(private readonly options: ICommonCartridgeFileBuilderOptions) {} + constructor(private readonly options: CommonCartridgeFileBuilderOptions) {} addOrganization(props: ICommonCartridgeOrganizationProps): ICommonCartridgeOrganizationBuilder { const organizationBuilder = new CommonCartridgeOrganizationBuilder(props, this.xmlBuilder, this.zipBuilder); diff --git a/apps/server/src/modules/learnroom/common-cartridge/common-cartridge-file.interface.ts b/apps/server/src/modules/learnroom/common-cartridge/common-cartridge-file.interface.ts index 3d936699c64..0969e712b05 100644 --- a/apps/server/src/modules/learnroom/common-cartridge/common-cartridge-file.interface.ts +++ b/apps/server/src/modules/learnroom/common-cartridge/common-cartridge-file.interface.ts @@ -1,4 +1,4 @@ -export interface ICommonCartridgeFile { +export interface CommonCartridgeFile { canInline(): boolean; content(): string; } diff --git a/apps/server/src/modules/learnroom/common-cartridge/common-cartridge-lti-resource.ts b/apps/server/src/modules/learnroom/common-cartridge/common-cartridge-lti-resource.ts index 993ed71a6d6..a374b3687a6 100644 --- a/apps/server/src/modules/learnroom/common-cartridge/common-cartridge-lti-resource.ts +++ b/apps/server/src/modules/learnroom/common-cartridge/common-cartridge-lti-resource.ts @@ -1,7 +1,7 @@ import { Builder } from 'xml2js'; -import { ICommonCartridgeElement } from './common-cartridge-element.interface'; -import { ICommonCartridgeFile } from './common-cartridge-file.interface'; +import { CommonCartridgeElement } from './common-cartridge-element.interface'; import { CommonCartridgeResourceType, CommonCartridgeVersion } from './common-cartridge-enums'; +import { CommonCartridgeFile } from './common-cartridge-file.interface'; export type ICommonCartridgeLtiResourceProps = { type: CommonCartridgeResourceType.LTI; @@ -13,7 +13,7 @@ export type ICommonCartridgeLtiResourceProps = { url: string; }; -export class CommonCartridgeLtiResource implements ICommonCartridgeElement, ICommonCartridgeFile { +export class CommonCartridgeLtiResource implements CommonCartridgeElement, CommonCartridgeFile { constructor(private readonly props: ICommonCartridgeLtiResourceProps, private readonly xmlBuilder: Builder) {} canInline(): boolean { diff --git a/apps/server/src/modules/learnroom/common-cartridge/common-cartridge-manifest-element.ts b/apps/server/src/modules/learnroom/common-cartridge/common-cartridge-manifest-element.ts index 6411359726a..8e71b9adee4 100644 --- a/apps/server/src/modules/learnroom/common-cartridge/common-cartridge-manifest-element.ts +++ b/apps/server/src/modules/learnroom/common-cartridge/common-cartridge-manifest-element.ts @@ -1,19 +1,19 @@ -import { ICommonCartridgeElement } from './common-cartridge-element.interface'; +import { CommonCartridgeElement } from './common-cartridge-element.interface'; +import { CommonCartridgeVersion } from './common-cartridge-enums'; import { CommonCartridgeMetadataElement, ICommonCartridgeMetadataProps } from './common-cartridge-metadata-element'; import { CommonCartridgeOrganizationWrapperElement } from './common-cartridge-organization-wrapper-element'; import { CommonCartridgeResourceWrapperElement } from './common-cartridge-resource-wrapper-element'; -import { CommonCartridgeVersion } from './common-cartridge-enums'; export type ICommonCartridgeManifestProps = { identifier: string; }; -export class CommonCartridgeManifestElement implements ICommonCartridgeElement { +export class CommonCartridgeManifestElement implements CommonCartridgeElement { constructor( private readonly props: ICommonCartridgeManifestProps, private readonly metadataProps: ICommonCartridgeMetadataProps, - private readonly organizations: ICommonCartridgeElement[], - private readonly resources: ICommonCartridgeElement[] + private readonly organizations: CommonCartridgeElement[], + private readonly resources: CommonCartridgeElement[] ) {} transform(): Record { diff --git a/apps/server/src/modules/learnroom/common-cartridge/common-cartridge-metadata-element.ts b/apps/server/src/modules/learnroom/common-cartridge/common-cartridge-metadata-element.ts index b15cc01d8d1..17a0cf45faa 100644 --- a/apps/server/src/modules/learnroom/common-cartridge/common-cartridge-metadata-element.ts +++ b/apps/server/src/modules/learnroom/common-cartridge/common-cartridge-metadata-element.ts @@ -1,4 +1,4 @@ -import { ICommonCartridgeElement } from './common-cartridge-element.interface'; +import { CommonCartridgeElement } from './common-cartridge-element.interface'; import { CommonCartridgeVersion } from './common-cartridge-enums'; export type ICommonCartridgeMetadataProps = { @@ -8,7 +8,7 @@ export type ICommonCartridgeMetadataProps = { version: CommonCartridgeVersion; }; -export class CommonCartridgeMetadataElement implements ICommonCartridgeElement { +export class CommonCartridgeMetadataElement implements CommonCartridgeElement { constructor(private readonly props: ICommonCartridgeMetadataProps) {} transform(): Record { diff --git a/apps/server/src/modules/learnroom/common-cartridge/common-cartridge-organization-item-element.ts b/apps/server/src/modules/learnroom/common-cartridge/common-cartridge-organization-item-element.ts index e77fcbc0905..5d237fc3f98 100644 --- a/apps/server/src/modules/learnroom/common-cartridge/common-cartridge-organization-item-element.ts +++ b/apps/server/src/modules/learnroom/common-cartridge/common-cartridge-organization-item-element.ts @@ -1,4 +1,4 @@ -import { ICommonCartridgeElement } from './common-cartridge-element.interface'; +import { CommonCartridgeElement } from './common-cartridge-element.interface'; import { ICommonCartridgeResourceProps } from './common-cartridge-resource-item-element'; import { createIdentifier } from './utils'; @@ -9,7 +9,7 @@ export type ICommonCartridgeOrganizationProps = { resources: ICommonCartridgeResourceProps[]; }; -export class CommonCartridgeOrganizationItemElement implements ICommonCartridgeElement { +export class CommonCartridgeOrganizationItemElement implements CommonCartridgeElement { constructor(private readonly props: ICommonCartridgeOrganizationProps) {} transform(): Record { diff --git a/apps/server/src/modules/learnroom/common-cartridge/common-cartridge-organization-wrapper-element.spec.ts b/apps/server/src/modules/learnroom/common-cartridge/common-cartridge-organization-wrapper-element.spec.ts index 6f3e08793af..a26e40bc37c 100644 --- a/apps/server/src/modules/learnroom/common-cartridge/common-cartridge-organization-wrapper-element.spec.ts +++ b/apps/server/src/modules/learnroom/common-cartridge/common-cartridge-organization-wrapper-element.spec.ts @@ -1,9 +1,9 @@ -import { ICommonCartridgeElement } from './common-cartridge-element.interface'; +import { CommonCartridgeElement } from './common-cartridge-element.interface'; import { CommonCartridgeOrganizationWrapperElement } from './common-cartridge-organization-wrapper-element'; describe('CommonCartridgeOrganizationWrapperElement', () => { it('should transform the organization elements into the expected structure', () => { - const organizationElementsMock: ICommonCartridgeElement[] = [ + const organizationElementsMock: CommonCartridgeElement[] = [ { transform: jest.fn().mockReturnValue({ identifier: 'element-1' }), }, diff --git a/apps/server/src/modules/learnroom/common-cartridge/common-cartridge-organization-wrapper-element.ts b/apps/server/src/modules/learnroom/common-cartridge/common-cartridge-organization-wrapper-element.ts index 12def098fd3..34200b31e37 100644 --- a/apps/server/src/modules/learnroom/common-cartridge/common-cartridge-organization-wrapper-element.ts +++ b/apps/server/src/modules/learnroom/common-cartridge/common-cartridge-organization-wrapper-element.ts @@ -1,7 +1,7 @@ -import { ICommonCartridgeElement } from './common-cartridge-element.interface'; +import { CommonCartridgeElement } from './common-cartridge-element.interface'; -export class CommonCartridgeOrganizationWrapperElement implements ICommonCartridgeElement { - constructor(private readonly organizationElements: ICommonCartridgeElement[]) {} +export class CommonCartridgeOrganizationWrapperElement implements CommonCartridgeElement { + constructor(private readonly organizationElements: CommonCartridgeElement[]) {} transform(): Record { return { diff --git a/apps/server/src/modules/learnroom/common-cartridge/common-cartridge-resource-item-element.ts b/apps/server/src/modules/learnroom/common-cartridge/common-cartridge-resource-item-element.ts index 1ea4c909cb8..219e7296075 100644 --- a/apps/server/src/modules/learnroom/common-cartridge/common-cartridge-resource-item-element.ts +++ b/apps/server/src/modules/learnroom/common-cartridge/common-cartridge-resource-item-element.ts @@ -1,4 +1,7 @@ import { Builder } from 'xml2js'; +import { CommonCartridgeElement } from './common-cartridge-element.interface'; +import { CommonCartridgeResourceType } from './common-cartridge-enums'; +import { CommonCartridgeFile } from './common-cartridge-file.interface'; import { CommonCartridgeLtiResource, ICommonCartridgeLtiResourceProps } from './common-cartridge-lti-resource'; import { CommonCartridgeWebContentResource, @@ -8,17 +11,14 @@ import { CommonCartridgeWebLinkResourceElement, ICommonCartridgeWebLinkResourceProps, } from './common-cartridge-web-link-resource'; -import { ICommonCartridgeElement } from './common-cartridge-element.interface'; -import { ICommonCartridgeFile } from './common-cartridge-file.interface'; -import { CommonCartridgeResourceType } from './common-cartridge-enums'; export type ICommonCartridgeResourceProps = | ICommonCartridgeLtiResourceProps | ICommonCartridgeWebContentResourceProps | ICommonCartridgeWebLinkResourceProps; -export class CommonCartridgeResourceItemElement implements ICommonCartridgeElement, ICommonCartridgeFile { - private readonly inner: ICommonCartridgeElement & ICommonCartridgeFile; +export class CommonCartridgeResourceItemElement implements CommonCartridgeElement, CommonCartridgeFile { + private readonly inner: CommonCartridgeElement & CommonCartridgeFile; constructor(props: ICommonCartridgeResourceProps, xmlBuilder: Builder) { if (props.type === CommonCartridgeResourceType.LTI) { diff --git a/apps/server/src/modules/learnroom/common-cartridge/common-cartridge-resource-wrapper-element.spec.ts b/apps/server/src/modules/learnroom/common-cartridge/common-cartridge-resource-wrapper-element.spec.ts index 8e267b33b11..cd96c9d6784 100644 --- a/apps/server/src/modules/learnroom/common-cartridge/common-cartridge-resource-wrapper-element.spec.ts +++ b/apps/server/src/modules/learnroom/common-cartridge/common-cartridge-resource-wrapper-element.spec.ts @@ -1,9 +1,9 @@ -import { ICommonCartridgeElement } from './common-cartridge-element.interface'; +import { CommonCartridgeElement } from './common-cartridge-element.interface'; import { CommonCartridgeResourceWrapperElement } from './common-cartridge-resource-wrapper-element'; describe('CommonCartridgeResourceWrapperElement', () => { it('should transform the resource elements into an array of transformed objects', () => { - const resourceElementsMock: ICommonCartridgeElement[] = [ + const resourceElementsMock: CommonCartridgeElement[] = [ { transform: jest.fn().mockReturnValue({ identifier: 'resource-1' }), }, diff --git a/apps/server/src/modules/learnroom/common-cartridge/common-cartridge-resource-wrapper-element.ts b/apps/server/src/modules/learnroom/common-cartridge/common-cartridge-resource-wrapper-element.ts index 613090ae8a8..c188651f3d4 100644 --- a/apps/server/src/modules/learnroom/common-cartridge/common-cartridge-resource-wrapper-element.ts +++ b/apps/server/src/modules/learnroom/common-cartridge/common-cartridge-resource-wrapper-element.ts @@ -1,7 +1,7 @@ -import { ICommonCartridgeElement } from './common-cartridge-element.interface'; +import { CommonCartridgeElement } from './common-cartridge-element.interface'; -export class CommonCartridgeResourceWrapperElement implements ICommonCartridgeElement { - constructor(private readonly resourceElements: ICommonCartridgeElement[]) {} +export class CommonCartridgeResourceWrapperElement implements CommonCartridgeElement { + constructor(private readonly resourceElements: CommonCartridgeElement[]) {} transform(): Record { return { diff --git a/apps/server/src/modules/learnroom/common-cartridge/common-cartridge-web-content-resource.ts b/apps/server/src/modules/learnroom/common-cartridge/common-cartridge-web-content-resource.ts index 740f4779a17..c45b981184f 100644 --- a/apps/server/src/modules/learnroom/common-cartridge/common-cartridge-web-content-resource.ts +++ b/apps/server/src/modules/learnroom/common-cartridge/common-cartridge-web-content-resource.ts @@ -1,10 +1,10 @@ -import { ICommonCartridgeElement } from './common-cartridge-element.interface'; -import { ICommonCartridgeFile } from './common-cartridge-file.interface'; +import { CommonCartridgeElement } from './common-cartridge-element.interface'; import { CommonCartridgeIntendedUseType, CommonCartridgeResourceType, CommonCartridgeVersion, } from './common-cartridge-enums'; +import { CommonCartridgeFile } from './common-cartridge-file.interface'; export type ICommonCartridgeWebContentResourceProps = { type: CommonCartridgeResourceType.WEB_CONTENT; @@ -16,7 +16,7 @@ export type ICommonCartridgeWebContentResourceProps = { intendedUse?: CommonCartridgeIntendedUseType; }; -export class CommonCartridgeWebContentResource implements ICommonCartridgeElement, ICommonCartridgeFile { +export class CommonCartridgeWebContentResource implements CommonCartridgeElement, CommonCartridgeFile { constructor(private readonly props: ICommonCartridgeWebContentResourceProps) {} canInline(): boolean { diff --git a/apps/server/src/modules/learnroom/common-cartridge/common-cartridge-web-link-resource.ts b/apps/server/src/modules/learnroom/common-cartridge/common-cartridge-web-link-resource.ts index f9d78af76f1..09184d5ef0f 100644 --- a/apps/server/src/modules/learnroom/common-cartridge/common-cartridge-web-link-resource.ts +++ b/apps/server/src/modules/learnroom/common-cartridge/common-cartridge-web-link-resource.ts @@ -1,7 +1,7 @@ import { Builder } from 'xml2js'; -import { ICommonCartridgeElement } from './common-cartridge-element.interface'; -import { ICommonCartridgeFile } from './common-cartridge-file.interface'; +import { CommonCartridgeElement } from './common-cartridge-element.interface'; import { CommonCartridgeResourceType, CommonCartridgeVersion } from './common-cartridge-enums'; +import { CommonCartridgeFile } from './common-cartridge-file.interface'; export type ICommonCartridgeWebLinkResourceProps = { type: CommonCartridgeResourceType.WEB_LINK_V1 | CommonCartridgeResourceType.WEB_LINK_V3; @@ -12,7 +12,7 @@ export type ICommonCartridgeWebLinkResourceProps = { url: string; }; -export class CommonCartridgeWebLinkResourceElement implements ICommonCartridgeElement, ICommonCartridgeFile { +export class CommonCartridgeWebLinkResourceElement implements CommonCartridgeElement, CommonCartridgeFile { constructor(private readonly props: ICommonCartridgeWebLinkResourceProps, private readonly xmlBuilder: Builder) {} canInline(): boolean { diff --git a/apps/server/src/modules/learnroom/common-cartridge/common-cartridge.config.ts b/apps/server/src/modules/learnroom/common-cartridge/common-cartridge.config.ts index 2041383512b..afbff1098a5 100644 --- a/apps/server/src/modules/learnroom/common-cartridge/common-cartridge.config.ts +++ b/apps/server/src/modules/learnroom/common-cartridge/common-cartridge.config.ts @@ -1,3 +1,3 @@ -export interface ICommonCartridgeConfig { +export interface CommonCartridgeConfig { FEATURE_IMSCC_COURSE_EXPORT_ENABLED: boolean; } diff --git a/apps/server/src/modules/learnroom/controller/api-test/dashboard.api.spec.ts b/apps/server/src/modules/learnroom/controller/api-test/dashboard.api.spec.ts index b6c27736d93..be9b9fad583 100644 --- a/apps/server/src/modules/learnroom/controller/api-test/dashboard.api.spec.ts +++ b/apps/server/src/modules/learnroom/controller/api-test/dashboard.api.spec.ts @@ -1,13 +1,13 @@ import { EntityManager, ObjectId } from '@mikro-orm/mongodb'; -import { ExecutionContext, INestApplication } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; -import { DashboardEntity, GridElement, Permission, User, RoleName } from '@shared/domain'; import { ICurrentUser } from '@modules/authentication'; -import { IDashboardRepo } from '@shared/repo'; -import { courseFactory, mapUserToCurrentUser, roleFactory, userFactory } from '@shared/testing'; import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; import { DashboardResponse } from '@modules/learnroom/controller/dto'; import { ServerTestModule } from '@modules/server/server.module'; +import { ExecutionContext, INestApplication } from '@nestjs/common'; +import { Test, TestingModule } from '@nestjs/testing'; +import { DashboardEntity, GridElement, Permission, RoleName, User } from '@shared/domain'; +import { IDashboardRepo } from '@shared/repo'; +import { courseFactory, mapUserToCurrentUser, roleFactory, userFactory } from '@shared/testing'; import { Request } from 'express'; import request from 'supertest'; diff --git a/apps/server/src/modules/learnroom/controller/api-test/rooms-copy-timeout.api.spec.ts b/apps/server/src/modules/learnroom/controller/api-test/rooms-copy-timeout.api.spec.ts index 2941e185ca1..c73b9861c1f 100644 --- a/apps/server/src/modules/learnroom/controller/api-test/rooms-copy-timeout.api.spec.ts +++ b/apps/server/src/modules/learnroom/controller/api-test/rooms-copy-timeout.api.spec.ts @@ -1,9 +1,12 @@ +import { createMock } from '@golevelup/ts-jest'; import { Configuration } from '@hpi-schul-cloud/commons/lib'; import { EntityManager } from '@mikro-orm/mongodb'; +import { ICurrentUser } from '@modules/authentication'; +import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; +import { FilesStorageClientAdapterService } from '@modules/files-storage-client'; import { ExecutionContext, INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { Permission } from '@shared/domain'; -import { ICurrentUser } from '@modules/authentication'; import { cleanupCollections, courseFactory, @@ -12,11 +15,8 @@ import { roleFactory, userFactory, } from '@shared/testing'; -import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; import { Request } from 'express'; import request from 'supertest'; -import { FilesStorageClientAdapterService } from '@modules/files-storage-client'; -import { createMock } from '@golevelup/ts-jest'; // config must be set outside before the server module is importat, otherwise the configuration is already set const configBefore = Configuration.toObject({ plainSecrets: true }); diff --git a/apps/server/src/modules/learnroom/controller/api-test/rooms.api.spec.ts b/apps/server/src/modules/learnroom/controller/api-test/rooms.api.spec.ts index 6db5100405a..ec6114f8f60 100644 --- a/apps/server/src/modules/learnroom/controller/api-test/rooms.api.spec.ts +++ b/apps/server/src/modules/learnroom/controller/api-test/rooms.api.spec.ts @@ -1,6 +1,12 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { Configuration } from '@hpi-schul-cloud/commons'; import { EntityManager } from '@mikro-orm/mongodb'; +import { ICurrentUser } from '@modules/authentication'; +import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; +import { CopyApiResponse } from '@modules/copy-helper'; +import { FilesStorageClientAdapterService } from '@modules/files-storage-client'; +import { SingleColumnBoardResponse } from '@modules/learnroom/controller/dto'; +import { ServerTestModule } from '@modules/server/server.module'; import { ExecutionContext, INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { Board, Course, Permission, Task } from '@shared/domain'; @@ -14,12 +20,6 @@ import { taskFactory, userFactory, } from '@shared/testing'; -import { ICurrentUser } from '@modules/authentication'; -import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; -import { CopyApiResponse } from '@modules/copy-helper'; -import { FilesStorageClientAdapterService } from '@modules/files-storage-client'; -import { SingleColumnBoardResponse } from '@modules/learnroom/controller/dto'; -import { ServerTestModule } from '@modules/server/server.module'; import { Request } from 'express'; import request from 'supertest'; diff --git a/apps/server/src/modules/learnroom/controller/course.controller.ts b/apps/server/src/modules/learnroom/controller/course.controller.ts index dfb4e920957..7f026002ea7 100644 --- a/apps/server/src/modules/learnroom/controller/course.controller.ts +++ b/apps/server/src/modules/learnroom/controller/course.controller.ts @@ -1,13 +1,13 @@ +import { Authenticate, CurrentUser, ICurrentUser } from '@modules/authentication'; import { Controller, Get, NotFoundException, Param, Query, Res, StreamableFile } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; import { ApiTags } from '@nestjs/swagger'; -import { ICurrentUser, Authenticate, CurrentUser } from '@modules/authentication'; import { PaginationParams } from '@shared/controller/'; import { Response } from 'express'; -import { ConfigService } from '@nestjs/config'; -import { CourseUc } from '../uc/course.uc'; -import { CourseExportUc } from '../uc/course-export.uc'; -import { CourseMetadataListResponse, CourseUrlParams, CourseQueryParams } from './dto'; import { CourseMapper } from '../mapper/course.mapper'; +import { CourseExportUc } from '../uc/course-export.uc'; +import { CourseUc } from '../uc/course.uc'; +import { CourseMetadataListResponse, CourseQueryParams, CourseUrlParams } from './dto'; @ApiTags('Courses') @Authenticate('jwt') diff --git a/apps/server/src/modules/learnroom/controller/dashboard.controller.spec.ts b/apps/server/src/modules/learnroom/controller/dashboard.controller.spec.ts index 284f18ef862..76ebe449829 100644 --- a/apps/server/src/modules/learnroom/controller/dashboard.controller.spec.ts +++ b/apps/server/src/modules/learnroom/controller/dashboard.controller.spec.ts @@ -1,3 +1,4 @@ +import { ICurrentUser } from '@modules/authentication'; import { Test, TestingModule } from '@nestjs/testing'; import { DashboardEntity, @@ -7,7 +8,6 @@ import { LearnroomMetadata, LearnroomTypes, } from '@shared/domain'; -import { ICurrentUser } from '@modules/authentication'; import { DashboardUc } from '../uc/dashboard.uc'; import { DashboardController } from './dashboard.controller'; import { DashboardResponse } from './dto'; diff --git a/apps/server/src/modules/learnroom/controller/dashboard.controller.ts b/apps/server/src/modules/learnroom/controller/dashboard.controller.ts index 224f6c41ca7..c2c2ff6d9ee 100644 --- a/apps/server/src/modules/learnroom/controller/dashboard.controller.ts +++ b/apps/server/src/modules/learnroom/controller/dashboard.controller.ts @@ -1,6 +1,6 @@ +import { Authenticate, CurrentUser, ICurrentUser } from '@modules/authentication'; import { Body, Controller, Get, Param, Patch, Query } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; -import { ICurrentUser, Authenticate, CurrentUser } from '@modules/authentication'; import { DashboardMapper } from '../mapper/dashboard.mapper'; import { DashboardUc } from '../uc/dashboard.uc'; import { DashboardResponse, DashboardUrlParams, MoveElementParams, PatchGroupParams } from './dto'; diff --git a/apps/server/src/modules/learnroom/controller/rooms.controller.spec.ts b/apps/server/src/modules/learnroom/controller/rooms.controller.spec.ts index c63e2e380ae..4e10bd706a3 100644 --- a/apps/server/src/modules/learnroom/controller/rooms.controller.spec.ts +++ b/apps/server/src/modules/learnroom/controller/rooms.controller.spec.ts @@ -1,8 +1,8 @@ import { createMock } from '@golevelup/ts-jest'; -import { Test, TestingModule } from '@nestjs/testing'; -import { EntityId } from '@shared/domain'; import { ICurrentUser } from '@modules/authentication'; import { CopyApiResponse, CopyElementType, CopyStatus, CopyStatusEnum } from '@modules/copy-helper'; +import { Test, TestingModule } from '@nestjs/testing'; +import { EntityId } from '@shared/domain'; import { RoomBoardResponseMapper } from '../mapper/room-board-response.mapper'; import { RoomBoardDTO } from '../types'; import { CourseCopyUC } from '../uc/course-copy.uc'; diff --git a/apps/server/src/modules/learnroom/controller/rooms.controller.ts b/apps/server/src/modules/learnroom/controller/rooms.controller.ts index 0e0b2f7c7a0..c81b40d6bba 100644 --- a/apps/server/src/modules/learnroom/controller/rooms.controller.ts +++ b/apps/server/src/modules/learnroom/controller/rooms.controller.ts @@ -1,9 +1,9 @@ +import { Authenticate, CurrentUser, ICurrentUser } from '@modules/authentication'; +import { CopyApiResponse, CopyMapper } from '@modules/copy-helper'; +import { serverConfig } from '@modules/server/server.config'; import { Body, Controller, Get, Param, Patch, Post } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; import { RequestTimeout } from '@shared/common'; -import { ICurrentUser, Authenticate, CurrentUser } from '@modules/authentication'; -import { CopyApiResponse, CopyMapper } from '@modules/copy-helper'; -import { serverConfig } from '@modules/server/server.config'; import { RoomBoardResponseMapper } from '../mapper/room-board-response.mapper'; import { CourseCopyUC } from '../uc/course-copy.uc'; import { LessonCopyUC } from '../uc/lesson-copy.uc'; diff --git a/apps/server/src/modules/learnroom/mapper/board-taskStatus.mapper.ts b/apps/server/src/modules/learnroom/mapper/board-taskStatus.mapper.ts index 623a2fe7627..b493f0de91e 100644 --- a/apps/server/src/modules/learnroom/mapper/board-taskStatus.mapper.ts +++ b/apps/server/src/modules/learnroom/mapper/board-taskStatus.mapper.ts @@ -1,8 +1,8 @@ -import { ITaskStatus } from '@shared/domain'; +import { TaskStatus } from '@shared/domain'; import { BoardTaskStatusResponse } from '../controller/dto'; export class BoardTaskStatusMapper { - static mapToResponse(status: ITaskStatus): BoardTaskStatusResponse { + static mapToResponse(status: TaskStatus): BoardTaskStatusResponse { const dto = new BoardTaskStatusResponse(status); return dto; diff --git a/apps/server/src/modules/learnroom/service/common-cartridge-export.service.spec.ts b/apps/server/src/modules/learnroom/service/common-cartridge-export.service.spec.ts index af1bc727d6a..8cd788f7793 100644 --- a/apps/server/src/modules/learnroom/service/common-cartridge-export.service.spec.ts +++ b/apps/server/src/modules/learnroom/service/common-cartridge-export.service.spec.ts @@ -1,18 +1,18 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { CourseService } from '@modules/learnroom/service'; +import { CommonCartridgeExportService } from '@modules/learnroom/service/common-cartridge-export.service'; +import { LessonService } from '@modules/lesson/service'; +import { TaskService } from '@modules/task/service/task.service'; import { Test, TestingModule } from '@nestjs/testing'; import { + ComponentProperties, + ComponentTextProperties, ComponentType, Course, - IComponentProperties, - IComponentTextProperties, LessonEntity, Task, } from '@shared/domain'; import { courseFactory, lessonFactory, setupEntities, taskFactory } from '@shared/testing'; -import { CommonCartridgeExportService } from '@modules/learnroom/service/common-cartridge-export.service'; -import { CourseService } from '@modules/learnroom/service'; -import { LessonService } from '@modules/lesson/service'; -import { TaskService } from '@modules/task/service/task.service'; import AdmZip from 'adm-zip'; import { CommonCartridgeVersion } from '../common-cartridge'; @@ -59,22 +59,22 @@ describe('CommonCartridgeExportService', () => { content: { text: 'text', }, - } as IComponentProperties, + } as ComponentProperties, { component: ComponentType.ETHERPAD, title: 'Etherpad', content: { url: 'url', }, - } as IComponentProperties, + } as ComponentProperties, { component: ComponentType.GEOGEBRA, title: 'Geogebra', content: { materialId: 'materialId', }, - } as IComponentProperties, - {} as IComponentProperties, + } as ComponentProperties, + {} as ComponentProperties, ], }); tasks = taskFactory.buildListWithId(5); @@ -87,8 +87,8 @@ describe('CommonCartridgeExportService', () => { describe('exportCourse', () => { const setupExport = async (version: CommonCartridgeVersion) => { const [lesson] = lessons; - const textContent = { text: 'Some random text' } as IComponentTextProperties; - const lessonContent: IComponentProperties = { + const textContent = { text: 'Some random text' } as ComponentTextProperties; + const lessonContent: ComponentProperties = { _id: 'random_id', title: 'A random title', hidden: false, diff --git a/apps/server/src/modules/learnroom/service/common-cartridge-export.service.ts b/apps/server/src/modules/learnroom/service/common-cartridge-export.service.ts index e25d2a62367..4da9bfeedd0 100644 --- a/apps/server/src/modules/learnroom/service/common-cartridge-export.service.ts +++ b/apps/server/src/modules/learnroom/service/common-cartridge-export.service.ts @@ -1,8 +1,8 @@ -import { Injectable } from '@nestjs/common'; -import { Course, EntityId, IComponentProperties, Task } from '@shared/domain'; import { LessonService } from '@modules/lesson/service'; -import { ComponentType } from '@src/shared/domain/entity/lesson.entity'; import { TaskService } from '@modules/task/service'; +import { Injectable } from '@nestjs/common'; +import { ComponentProperties, Course, EntityId, Task } from '@shared/domain'; +import { ComponentType } from '@src/shared/domain/entity/lesson.entity'; import { CommonCartridgeFileBuilder, CommonCartridgeIntendedUseType, @@ -11,8 +11,8 @@ import { ICommonCartridgeResourceProps, ICommonCartridgeWebContentResourceProps, } from '../common-cartridge'; -import { CourseService } from './course.service'; import { createIdentifier } from '../common-cartridge/utils'; +import { CourseService } from './course.service'; @Injectable() export class CommonCartridgeExportService { @@ -84,7 +84,7 @@ export class CommonCartridgeExportService { private mapContentToResource( lessonId: string, - content: IComponentProperties, + content: ComponentProperties, version: CommonCartridgeVersion ): ICommonCartridgeResourceProps | undefined { const commonProps = { diff --git a/apps/server/src/modules/learnroom/uc/dashboard.uc.ts b/apps/server/src/modules/learnroom/uc/dashboard.uc.ts index 9bade9a6d71..1e1df78fcba 100644 --- a/apps/server/src/modules/learnroom/uc/dashboard.uc.ts +++ b/apps/server/src/modules/learnroom/uc/dashboard.uc.ts @@ -1,6 +1,6 @@ -import { Injectable, Inject, NotFoundException } from '@nestjs/common'; -import { DashboardEntity, EntityId, GridPositionWithGroupIndex, GridPosition, SortOrder } from '@shared/domain'; -import { IDashboardRepo, CourseRepo } from '@shared/repo'; +import { Inject, Injectable, NotFoundException } from '@nestjs/common'; +import { DashboardEntity, EntityId, GridPosition, GridPositionWithGroupIndex, SortOrder } from '@shared/domain'; +import { CourseRepo, IDashboardRepo } from '@shared/repo'; // import { NotFound } from '@feathersjs/errors'; // wrong import? see NotFoundException @Injectable() diff --git a/apps/server/src/modules/learnroom/uc/room-board-dto.factory.ts b/apps/server/src/modules/learnroom/uc/room-board-dto.factory.ts index 91bd043d472..0a74a5a34e8 100644 --- a/apps/server/src/modules/learnroom/uc/room-board-dto.factory.ts +++ b/apps/server/src/modules/learnroom/uc/room-board-dto.factory.ts @@ -1,4 +1,5 @@ import { Configuration } from '@hpi-schul-cloud/commons/lib'; +import { Action, AuthorizationService } from '@modules/authorization'; import { Injectable } from '@nestjs/common'; import { Board, @@ -7,14 +8,13 @@ import { ColumnboardBoardElement, ColumnBoardTarget, Course, - ITaskStatus, LessonEntity, Permission, Task, + TaskStatus, TaskWithStatusVo, User, } from '@shared/domain'; -import { AuthorizationService, Action } from '@modules/authorization'; import { ColumnBoardMetaData, LessonMetaData, @@ -126,8 +126,8 @@ class DtoCreator { return { type: RoomBoardElementTypes.TASK, content }; } - private createTaskStatus(task: Task): ITaskStatus { - let status: ITaskStatus; + private createTaskStatus(task: Task): TaskStatus { + let status: TaskStatus; if (this.isTeacher()) { status = task.createTeacherStatusForUser(this.user); } else { diff --git a/apps/server/src/modules/lesson/controller/lesson.controller.ts b/apps/server/src/modules/lesson/controller/lesson.controller.ts index 14762a47d8c..66e52b06478 100644 --- a/apps/server/src/modules/lesson/controller/lesson.controller.ts +++ b/apps/server/src/modules/lesson/controller/lesson.controller.ts @@ -1,6 +1,6 @@ +import { Authenticate, CurrentUser, ICurrentUser } from '@modules/authentication'; import { Controller, Delete, Param } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; -import { ICurrentUser, Authenticate, CurrentUser } from '@modules/authentication'; import { LessonUC } from '../uc'; import { LessonUrlParams } from './dto'; diff --git a/apps/server/src/modules/lesson/repository/lesson.repo.integration.spec.ts b/apps/server/src/modules/lesson/repository/lesson.repo.integration.spec.ts index eae071d55ae..d6bdafd1971 100644 --- a/apps/server/src/modules/lesson/repository/lesson.repo.integration.spec.ts +++ b/apps/server/src/modules/lesson/repository/lesson.repo.integration.spec.ts @@ -1,6 +1,6 @@ import { EntityManager, ObjectId } from '@mikro-orm/mongodb'; import { Test, TestingModule } from '@nestjs/testing'; -import { ComponentType, IComponentProperties, LessonEntity } from '@shared/domain'; +import { ComponentProperties, ComponentType, LessonEntity } from '@shared/domain'; import { cleanupCollections, courseFactory, lessonFactory, materialFactory, taskFactory } from '@shared/testing'; import { MongoMemoryDatabaseModule } from '@infra/database'; @@ -165,7 +165,7 @@ describe('LessonRepo', () => { it('should return lessons which contains a specific userId', async () => { // Arrange const userId = new ObjectId().toHexString(); - const contentExample: IComponentProperties = { + const contentExample: ComponentProperties = { title: 'title', hidden: false, user: userId, @@ -195,7 +195,7 @@ describe('LessonRepo', () => { it('should update Lessons without deleted user', async () => { // Arrange const userId = new ObjectId().toHexString(); - const contentExample: IComponentProperties = { + const contentExample: ComponentProperties = { title: 'title', hidden: false, user: userId, diff --git a/apps/server/src/modules/lesson/service/lesson-copy.service.spec.ts b/apps/server/src/modules/lesson/service/lesson-copy.service.spec.ts index 5e7c1aa3e59..05f9e452aa5 100644 --- a/apps/server/src/modules/lesson/service/lesson-copy.service.spec.ts +++ b/apps/server/src/modules/lesson/service/lesson-copy.service.spec.ts @@ -6,14 +6,14 @@ import { TaskCopyService } from '@modules/task'; import { Test, TestingModule } from '@nestjs/testing'; import { BaseEntity, + ComponentEtherpadProperties, + ComponentGeogebraProperties, + ComponentInternalProperties, + ComponentNexboardProperties, + ComponentProperties, + ComponentTextProperties, ComponentType, EntityId, - IComponentEtherpadProperties, - IComponentGeogebraProperties, - IComponentInternalProperties, - IComponentNexboardProperties, - IComponentProperties, - IComponentTextProperties, LessonEntity, Material, } from '@shared/domain'; @@ -304,7 +304,7 @@ describe('lesson copy service', () => { describe('when lesson contains at least one content element', () => { const setup = () => { - const contentOne: IComponentProperties = { + const contentOne: ComponentProperties = { title: 'title component 1', hidden: false, component: ComponentType.TEXT, @@ -312,7 +312,7 @@ describe('lesson copy service', () => { text: 'this is a text content', }, }; - const contentTwo: IComponentProperties = { + const contentTwo: ComponentProperties = { title: 'title component 2', hidden: false, component: ComponentType.LERNSTORE, @@ -405,7 +405,7 @@ describe('lesson copy service', () => { describe('when lesson contains text content element', () => { const setup = (text = 'this is a text content') => { - const textContent: IComponentProperties = { + const textContent: ComponentProperties = { title: 'text component 1', hidden: false, component: ComponentType.TEXT, @@ -467,13 +467,13 @@ describe('lesson copy service', () => { const lessonCopy = status.copyEntity as LessonEntity; const contentsStatus = status.elements?.find((el) => el.type === CopyElementType.LESSON_CONTENT_GROUP); expect(contentsStatus).toBeDefined(); - expect((lessonCopy.contents[0].content as IComponentTextProperties).text).not.toContain(FILE_ID_TO_BE_REPLACED); + expect((lessonCopy.contents[0].content as ComponentTextProperties).text).not.toContain(FILE_ID_TO_BE_REPLACED); }); }); describe('when lesson contains LernStore content element', () => { const setup = () => { - const lernStoreContent: IComponentProperties = { + const lernStoreContent: ComponentProperties = { title: 'text component 1', hidden: false, component: ComponentType.LERNSTORE, @@ -510,7 +510,7 @@ describe('lesson copy service', () => { user, }); - const copiedLessonContents = (status.copyEntity as LessonEntity).contents as IComponentProperties[]; + const copiedLessonContents = (status.copyEntity as LessonEntity).contents as ComponentProperties[]; expect(copiedLessonContents[0]).toEqual(lernStoreContent); }); @@ -533,7 +533,7 @@ describe('lesson copy service', () => { describe('when lesson contains LernStore content element without set resource', () => { const setup = () => { - const lernStoreContent: IComponentProperties = { + const lernStoreContent: ComponentProperties = { title: 'text component 1', hidden: false, component: ComponentType.LERNSTORE, @@ -559,7 +559,7 @@ describe('lesson copy service', () => { user, }); - const copiedLessonContents = (status.copyEntity as LessonEntity).contents as IComponentProperties[]; + const copiedLessonContents = (status.copyEntity as LessonEntity).contents as ComponentProperties[]; expect(copiedLessonContents[0]).toEqual(lernStoreContent); }); @@ -582,7 +582,7 @@ describe('lesson copy service', () => { describe('when lesson contains geoGebra content element', () => { const setup = () => { - const geoGebraContent: IComponentProperties = { + const geoGebraContent: ComponentProperties = { title: 'text component 1', hidden: false, component: ComponentType.GEOGEBRA, @@ -611,8 +611,8 @@ describe('lesson copy service', () => { user, }); - const lessonContents = (status.copyEntity as LessonEntity).contents as IComponentProperties[]; - const geoGebraContent = lessonContents[0].content as IComponentGeogebraProperties; + const lessonContents = (status.copyEntity as LessonEntity).contents as ComponentProperties[]; + const geoGebraContent = lessonContents[0].content as ComponentGeogebraProperties; expect(geoGebraContent.materialId).toEqual(''); }); @@ -626,7 +626,7 @@ describe('lesson copy service', () => { user, }); - const lessonContents = (status.copyEntity as LessonEntity).contents as IComponentProperties[]; + const lessonContents = (status.copyEntity as LessonEntity).contents as ComponentProperties[]; expect(lessonContents[0].hidden).toEqual(true); }); @@ -815,7 +815,7 @@ describe('lesson copy service', () => { describe('when lesson contains Etherpad content element', () => { const setup = () => { - const etherpadContent: IComponentProperties = { + const etherpadContent: ComponentProperties = { title: 'text', hidden: false, component: ComponentType.ETHERPAD, @@ -859,7 +859,7 @@ describe('lesson copy service', () => { user, }); - const lessonContents = (status.copyEntity as LessonEntity).contents as IComponentProperties[]; + const lessonContents = (status.copyEntity as LessonEntity).contents as ComponentProperties[]; expect(configurationSpy).toHaveBeenCalledWith('FEATURE_ETHERPAD_ENABLED'); expect(etherpadService.createEtherpad).not.toHaveBeenCalled(); expect(lessonContents).toEqual([]); @@ -897,7 +897,7 @@ describe('lesson copy service', () => { } expect(contentStatus).toEqual(CopyStatusEnum.FAIL); - const lessonContents = (status.copyEntity as LessonEntity).contents as IComponentProperties[]; + const lessonContents = (status.copyEntity as LessonEntity).contents as ComponentProperties[]; expect(lessonContents.length).toEqual(0); }); @@ -911,8 +911,8 @@ describe('lesson copy service', () => { destinationCourse, user, }); - const copiedLessonContents = (status.copyEntity as LessonEntity).contents as IComponentProperties[]; - const copiedEtherpad = copiedLessonContents[0].content as IComponentEtherpadProperties; + const copiedLessonContents = (status.copyEntity as LessonEntity).contents as ComponentProperties[]; + const copiedEtherpad = copiedLessonContents[0].content as ComponentEtherpadProperties; expect(copiedEtherpad.url).toEqual('http://pad.uri/abc'); }); @@ -939,7 +939,7 @@ describe('lesson copy service', () => { const user = userFactory.build(); const originalCourse = courseFactory.build({ school: user.school, teachers: [user] }); const destinationCourse = courseFactory.build({ school: user.school, teachers: [user] }); - const embeddedTaskContent: IComponentProperties = { + const embeddedTaskContent: ComponentProperties = { title: 'title', hidden: false, component: ComponentType.INTERNAL, @@ -1008,7 +1008,7 @@ describe('lesson copy service', () => { describe('when lesson contains neXboard content element', () => { const setup = () => { - const nexboardContent: IComponentProperties = { + const nexboardContent: ComponentProperties = { title: 'text', hidden: false, component: ComponentType.NEXBOARD, @@ -1050,7 +1050,7 @@ describe('lesson copy service', () => { user, }); - const lessonContents = (status.copyEntity as LessonEntity).contents as IComponentProperties[]; + const lessonContents = (status.copyEntity as LessonEntity).contents as ComponentProperties[]; expect(configurationSpy).toHaveBeenCalledWith('FEATURE_NEXBOARD_ENABLED'); expect(nexboardService.createNexboard).not.toHaveBeenCalled(); expect(lessonContents).toEqual([]); @@ -1088,7 +1088,7 @@ describe('lesson copy service', () => { } expect(contentStatus).toEqual(CopyStatusEnum.FAIL); - const lessonContents = (status.copyEntity as LessonEntity).contents as IComponentProperties[]; + const lessonContents = (status.copyEntity as LessonEntity).contents as ComponentProperties[]; expect(lessonContents.length).toEqual(0); }); @@ -1102,8 +1102,8 @@ describe('lesson copy service', () => { destinationCourse, user, }); - const copiedLessonContents = (status.copyEntity as LessonEntity).contents as IComponentProperties[]; - const copiedNexboard = copiedLessonContents[0].content as IComponentNexboardProperties; + const copiedLessonContents = (status.copyEntity as LessonEntity).contents as ComponentProperties[]; + const copiedNexboard = copiedLessonContents[0].content as ComponentNexboardProperties; expect(copiedNexboard.url).toEqual('abc'); expect(copiedNexboard.board).toEqual('123'); }); @@ -1324,7 +1324,7 @@ describe('lesson copy service', () => { const copiedLesson = lessonFactory.buildWithId(); const originalTask = taskFactory.buildWithId({ lesson: originalLesson }); const copiedTask = taskFactory.buildWithId({ lesson: copiedLesson }); - const embeddedTaskContent: IComponentProperties = { + const embeddedTaskContent: ComponentProperties = { title: 'title', hidden: false, component: ComponentType.INTERNAL, @@ -1332,7 +1332,7 @@ describe('lesson copy service', () => { url: `http://somebasedomain.de/homeworks/${originalTask.id}`, }, }; - const textContent: IComponentProperties = { + const textContent: ComponentProperties = { title: 'title component', hidden: false, component: ComponentType.TEXT, @@ -1388,8 +1388,8 @@ describe('lesson copy service', () => { throw new Error('lesson should be part of the copy'); } const content = lesson.contents.find((el) => el.component === ComponentType.INTERNAL); - expect((content?.content as IComponentInternalProperties).url).not.toContain(originalTask.id); - expect((content?.content as IComponentInternalProperties).url).toContain(copiedTask.id); + expect((content?.content as ComponentInternalProperties).url).not.toContain(originalTask.id); + expect((content?.content as ComponentInternalProperties).url).toContain(copiedTask.id); }); it('should maintain order of content elements', () => { @@ -1413,7 +1413,7 @@ describe('lesson copy service', () => { throw new Error('lesson should be part of the copy'); } const content = lesson.contents.find((el) => el.component === ComponentType.INTERNAL); - expect((content?.content as IComponentInternalProperties).url).toEqual( + expect((content?.content as ComponentInternalProperties).url).toEqual( `http://somebasedomain.de/homeworks/${originalTask.id}` ); }); diff --git a/apps/server/src/modules/lesson/service/lesson-copy.service.ts b/apps/server/src/modules/lesson/service/lesson-copy.service.ts index 0ea5110b424..00cf3cee8b9 100644 --- a/apps/server/src/modules/lesson/service/lesson-copy.service.ts +++ b/apps/server/src/modules/lesson/service/lesson-copy.service.ts @@ -1,16 +1,16 @@ import { Configuration } from '@hpi-schul-cloud/commons'; import { CopyDictionary, CopyElementType, CopyHelperService, CopyStatus, CopyStatusEnum } from '@modules/copy-helper'; import { CopyFilesService, FileUrlReplacement } from '@modules/files-storage-client'; -import { TaskCopyService } from '@modules/task'; +import { TaskCopyService } from '@modules/task/service/task-copy.service'; import { Injectable } from '@nestjs/common'; import { + ComponentEtherpadProperties, + ComponentGeogebraProperties, + ComponentLernstoreProperties, + ComponentNexboardProperties, + ComponentProperties, + ComponentTextProperties, ComponentType, - IComponentEtherpadProperties, - IComponentGeogebraProperties, - IComponentLernstoreProperties, - IComponentNexboardProperties, - IComponentProperties, - IComponentTextProperties, LessonEntity, Material, } from '@shared/domain'; @@ -108,7 +108,7 @@ export class LessonCopyService { return lessonStatus; } - copiedLesson.contents = copiedLesson.contents.map((value: IComponentProperties) => + copiedLesson.contents = copiedLesson.contents.map((value: ComponentProperties) => this.updateCopiedEmbeddedTaskId(value, copyDict) ); @@ -117,10 +117,7 @@ export class LessonCopyService { return lessonStatus; } - private updateCopiedEmbeddedTaskId = ( - value: IComponentProperties, - copyDict: CopyDictionary - ): IComponentProperties => { + private updateCopiedEmbeddedTaskId = (value: ComponentProperties, copyDict: CopyDictionary): ComponentProperties => { if (value.component !== ComponentType.INTERNAL || value.content === undefined || value.content.url === undefined) { return value; } @@ -144,10 +141,10 @@ export class LessonCopyService { }; private replaceUrlsInContents( - contents: IComponentProperties[], + contents: ComponentProperties[], fileUrlReplacements: FileUrlReplacement[] - ): IComponentProperties[] { - contents = contents.map((item: IComponentProperties) => { + ): ComponentProperties[] { + contents = contents.map((item: ComponentProperties) => { if (item.component === 'text' && item.content && 'text' in item.content && item.content.text) { let { text } = item.content; fileUrlReplacements.forEach(({ regex, replacement }) => { @@ -162,15 +159,15 @@ export class LessonCopyService { } private async copyLessonContent( - content: IComponentProperties[], + content: ComponentProperties[], params: LessonCopyParams ): Promise<{ - copiedContent: IComponentProperties[]; + copiedContent: ComponentProperties[]; contentStatus: CopyStatus[]; }> { const etherpadEnabled = Configuration.get('FEATURE_ETHERPAD_ENABLED') as boolean; const nexboardEnabled = Configuration.get('FEATURE_NEXBOARD_ENABLED') as boolean; - const copiedContent: IComponentProperties[] = []; + const copiedContent: ComponentProperties[] = []; const copiedContentStatus: CopyStatus[] = []; for (let i = 0; i < content.length; i += 1) { const element = content[i]; @@ -246,20 +243,20 @@ export class LessonCopyService { return { copiedContent, contentStatus }; } - private copyTextContent(element: IComponentProperties): IComponentProperties { + private copyTextContent(element: ComponentProperties): ComponentProperties { return { title: element.title, hidden: element.hidden, component: ComponentType.TEXT, user: element.user, // TODO should be params.user - but that made the server crash, but property is normally undefined content: { - text: (element.content as IComponentTextProperties).text, + text: (element.content as ComponentTextProperties).text, }, }; } - private copyLernStore(element: IComponentProperties): IComponentProperties { - const lernstore: IComponentProperties = { + private copyLernStore(element: ComponentProperties): ComponentProperties { + const lernstore: ComponentProperties = { title: element.title, hidden: element.hidden, component: ComponentType.LERNSTORE, @@ -267,7 +264,7 @@ export class LessonCopyService { }; if (element.content) { - const resources = ((element.content as IComponentLernstoreProperties).resources ?? []).map( + const resources = ((element.content as ComponentLernstoreProperties).resources ?? []).map( ({ client, description, merlinReference, title, url }) => { const result = { client, @@ -280,27 +277,27 @@ export class LessonCopyService { } ); - const lernstoreContent: IComponentLernstoreProperties = { resources }; + const lernstoreContent: ComponentLernstoreProperties = { resources }; lernstore.content = lernstoreContent; } return lernstore; } - private static copyGeogebra(originalElement: IComponentProperties): IComponentProperties { - const copy = { ...originalElement, hidden: true } as IComponentProperties; - copy.content = { ...copy.content, materialId: '' } as IComponentGeogebraProperties; + private static copyGeogebra(originalElement: ComponentProperties): ComponentProperties { + const copy = { ...originalElement, hidden: true } as ComponentProperties; + copy.content = { ...copy.content, materialId: '' } as ComponentGeogebraProperties; delete copy._id; return copy; } private async copyEtherpad( - originalElement: IComponentProperties, + originalElement: ComponentProperties, params: LessonCopyParams - ): Promise { - const copy = { ...originalElement } as IComponentProperties; + ): Promise { + const copy = { ...originalElement } as ComponentProperties; delete copy._id; - const content = { ...copy.content, url: '' } as IComponentEtherpadProperties; + const content = { ...copy.content, url: '' } as ComponentEtherpadProperties; content.title = randomBytes(12).toString('hex'); const etherpadPadId = await this.etherpadService.createEtherpad( @@ -318,12 +315,12 @@ export class LessonCopyService { } private async copyNexboard( - originalElement: IComponentProperties, + originalElement: ComponentProperties, params: LessonCopyParams - ): Promise { - const copy = { ...originalElement } as IComponentProperties; + ): Promise { + const copy = { ...originalElement } as ComponentProperties; delete copy._id; - const content = { ...copy.content, url: '', board: '' } as IComponentNexboardProperties; + const content = { ...copy.content, url: '', board: '' } as ComponentNexboardProperties; const nexboard = await this.nexboardService.createNexboard(params.user.id, content.title, content.description); if (nexboard) { @@ -388,8 +385,8 @@ export class LessonCopyService { return { copiedMaterials, materialsStatus }; } - private copyEmbeddedTaskLink(originalElement: IComponentProperties) { - const copy = JSON.parse(JSON.stringify(originalElement)) as IComponentProperties; + private copyEmbeddedTaskLink(originalElement: ComponentProperties) { + const copy = JSON.parse(JSON.stringify(originalElement)) as ComponentProperties; delete copy._id; return copy; } diff --git a/apps/server/src/modules/lesson/service/lesson.service.spec.ts b/apps/server/src/modules/lesson/service/lesson.service.spec.ts index b82875d7c1f..958677a3e26 100644 --- a/apps/server/src/modules/lesson/service/lesson.service.spec.ts +++ b/apps/server/src/modules/lesson/service/lesson.service.spec.ts @@ -2,7 +2,7 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { ObjectId } from '@mikro-orm/mongodb'; import { FilesStorageClientAdapterService } from '@modules/files-storage-client'; import { Test, TestingModule } from '@nestjs/testing'; -import { ComponentType, IComponentProperties } from '@shared/domain'; +import { ComponentProperties, ComponentType } from '@shared/domain'; import { lessonFactory, setupEntities } from '@shared/testing'; import { LessonRepo } from '../repository'; import { LessonService } from './lesson.service'; @@ -91,7 +91,7 @@ describe('LessonService', () => { describe('when finding by userId', () => { const setup = () => { const userId = new ObjectId().toHexString(); - const contentExample: IComponentProperties = { + const contentExample: ComponentProperties = { title: 'title', hidden: false, user: userId, @@ -132,7 +132,7 @@ describe('LessonService', () => { describe('when deleting by userId', () => { const setup = () => { const userId = new ObjectId().toHexString(); - const contentExample: IComponentProperties = { + const contentExample: ComponentProperties = { title: 'title', hidden: false, user: userId, diff --git a/apps/server/src/modules/lesson/service/lesson.service.ts b/apps/server/src/modules/lesson/service/lesson.service.ts index c98e633469e..3ef2f44d9bf 100644 --- a/apps/server/src/modules/lesson/service/lesson.service.ts +++ b/apps/server/src/modules/lesson/service/lesson.service.ts @@ -1,6 +1,6 @@ import { FilesStorageClientAdapterService } from '@modules/files-storage-client'; import { Injectable } from '@nestjs/common'; -import { Counted, EntityId, IComponentProperties, LessonEntity } from '@shared/domain'; +import { ComponentProperties, Counted, EntityId, LessonEntity } from '@shared/domain'; import { AuthorizationLoaderService } from '@src/modules/authorization'; import { LessonRepo } from '../repository'; @@ -35,7 +35,7 @@ export class LessonService implements AuthorizationLoaderService { const lessons = await this.lessonRepo.findByUserId(userId); const updatedLessons = lessons.map((lesson: LessonEntity) => { - lesson.contents.map((c: IComponentProperties) => { + lesson.contents.map((c: ComponentProperties) => { if (c.user === userId) { c.user = ''; } diff --git a/apps/server/src/modules/management/seed-data/federalstates.ts b/apps/server/src/modules/management/seed-data/federalstates.ts index c5f6447a4b7..2630aa8797a 100644 --- a/apps/server/src/modules/management/seed-data/federalstates.ts +++ b/apps/server/src/modules/management/seed-data/federalstates.ts @@ -1,8 +1,8 @@ -import { County, IFederalStateProperties } from '@shared/domain/entity/federal-state.entity'; +import { County, FederalStateProperties } from '@shared/domain/entity/federal-state.entity'; import { federalStateFactory } from '@shared/testing/factory/federal-state.factory'; import { DeepPartial } from 'fishery'; -type SeedFederalStateProperties = Omit & { +type SeedFederalStateProperties = Omit & { id: string; counties?: (County & { id: string })[]; createdAt?: string; @@ -219,7 +219,7 @@ export function generateFederalStates() { }) ) ?? []; - const params: DeepPartial = { + const params: DeepPartial = { counties, name: federalState.name, abbreviation: federalState.abbreviation, diff --git a/apps/server/src/modules/management/seed-data/roles.ts b/apps/server/src/modules/management/seed-data/roles.ts index f5a4ee0fbdc..3fba9d7d8ee 100644 --- a/apps/server/src/modules/management/seed-data/roles.ts +++ b/apps/server/src/modules/management/seed-data/roles.ts @@ -1,10 +1,10 @@ // All user accounts are organized by school in a single array -import { IRoleProperties, Permission, Role, RoleName } from '@shared/domain'; +import { Permission, Role, RoleName, RoleProperties } from '@shared/domain'; import { roleFactory } from '@shared/testing'; import { DeepPartial } from 'fishery'; -type SeedRoleProperties = Omit & { +type SeedRoleProperties = Omit & { id: string; createdAt: string; updatedAt: string; @@ -443,7 +443,7 @@ export function generateRole(localRoleSeedData?: { [key: string | RoleName]: See if (subRoles.some((r) => !r)) { throw new Error(`Role ${roleName} depends on non existing role`); } - const params: DeepPartial = { + const params: DeepPartial = { name: partial.name, permissions: partial.permissions, roles: subRoles, diff --git a/apps/server/src/modules/management/seed-data/schools.ts b/apps/server/src/modules/management/seed-data/schools.ts index ba09c0d6952..9c0cec78153 100644 --- a/apps/server/src/modules/management/seed-data/schools.ts +++ b/apps/server/src/modules/management/seed-data/schools.ts @@ -1,8 +1,8 @@ /* eslint-disable @typescript-eslint/dot-notation */ import { FederalStateEntity, - ISchoolProperties, SchoolFeatures, + SchoolProperties, SchoolRoles, SchoolYearEntity, SystemEntity, @@ -12,7 +12,7 @@ import { DeepPartial } from 'fishery'; import { EFederalState } from './federalstates'; import { SeedSchoolYearEnum } from './schoolyears'; -type SeedSchoolProperties = Omit & { +type SeedSchoolProperties = Omit & { id: string; updatedAt?: string; createdAt?: string; @@ -288,7 +288,7 @@ export function generateSchools(entities: { entities.federalStates.find((fs) => partial.federalState && fs.name === partial.federalState) ?? federalStateFactory.build(); - const params: DeepPartial = { + const params: DeepPartial = { externalId: partial.externalId, features: partial.features, inMaintenanceSince: partial.inMaintenanceSince, diff --git a/apps/server/src/modules/management/seed-data/schoolyears.ts b/apps/server/src/modules/management/seed-data/schoolyears.ts index afd0a7a8d49..c52dee77642 100644 --- a/apps/server/src/modules/management/seed-data/schoolyears.ts +++ b/apps/server/src/modules/management/seed-data/schoolyears.ts @@ -1,8 +1,8 @@ -import { ISchoolYearProperties } from '@shared/domain'; +import { SchoolYearProperties } from '@shared/domain'; import { schoolYearFactory } from '@shared/testing/factory/schoolyear.factory'; import { DeepPartial } from 'fishery'; -type SeedSchoolYearProperties = Pick & { +type SeedSchoolYearProperties = Pick & { id: string; endDate: string; startDate: string; @@ -79,7 +79,7 @@ const seedSchoolYears: SeedSchoolYearProperties[] = [ export function generateSchoolYears() { return seedSchoolYears.map((year) => { - const params: DeepPartial = { + const params: DeepPartial = { endDate: new Date(year.endDate), name: year.name, startDate: new Date(year.startDate), diff --git a/apps/server/src/modules/management/seed-data/systems.ts b/apps/server/src/modules/management/seed-data/systems.ts index adbffbc4b35..a9140458f6d 100644 --- a/apps/server/src/modules/management/seed-data/systems.ts +++ b/apps/server/src/modules/management/seed-data/systems.ts @@ -1,10 +1,10 @@ /* eslint-disable no-template-curly-in-string */ -import { ISystemProperties } from '@shared/domain'; +import { SystemProperties } from '@shared/domain'; import { SystemProvisioningStrategy } from '@shared/domain/interface/system-provisioning.strategy'; import { systemFactory } from '@shared/testing'; import { DeepPartial } from 'fishery'; -type SystemPartial = DeepPartial & { +type SystemPartial = DeepPartial & { id?: string; createdAt?: string; updatedAt?: string; @@ -66,7 +66,7 @@ const data: SystemPartial[] = [ export function generateSystems(injectEnvVars: (s: string) => string) { const systems = data.map((d) => { d = JSON.parse(injectEnvVars(JSON.stringify(d))) as typeof d; - const params: DeepPartial = { + const params: DeepPartial = { alias: d.alias, displayName: d.displayName, type: d.type, diff --git a/apps/server/src/modules/management/uc/database-management.uc.ts b/apps/server/src/modules/management/uc/database-management.uc.ts index 7b3d034c504..370c4652aeb 100644 --- a/apps/server/src/modules/management/uc/database-management.uc.ts +++ b/apps/server/src/modules/management/uc/database-management.uc.ts @@ -1,17 +1,17 @@ import { Configuration } from '@hpi-schul-cloud/commons'; +import { DatabaseManagementService } from '@infra/database'; +import { DefaultEncryptionService, EncryptionService, LdapEncryptionService } from '@infra/encryption'; +import { FileSystemAdapter } from '@infra/file-system'; import { EntityManager } from '@mikro-orm/mongodb'; import { Inject, Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { StorageProviderEntity, SystemEntity } from '@shared/domain'; -import { DatabaseManagementService } from '@infra/database'; -import { DefaultEncryptionService, IEncryptionService, LdapEncryptionService } from '@infra/encryption'; -import { FileSystemAdapter } from '@infra/file-system'; import { LegacyLogger } from '@src/core/logger'; import { orderBy } from 'lodash'; import { BsonConverter } from '../converter/bson.converter'; import { generateSeedData } from '../seed-data/generateSeedData'; -export interface ICollectionFilePath { +export interface CollectionFilePath { filePath: string; collectionName: string; } @@ -35,8 +35,8 @@ export class DatabaseManagementUc { private readonly configService: ConfigService, private readonly logger: LegacyLogger, private em: EntityManager, - @Inject(DefaultEncryptionService) private readonly defaultEncryptionService: IEncryptionService, - @Inject(LdapEncryptionService) private readonly ldapEncryptionService: IEncryptionService + @Inject(DefaultEncryptionService) private readonly defaultEncryptionService: EncryptionService, + @Inject(LdapEncryptionService) private readonly ldapEncryptionService: EncryptionService ) { this.logger.setContext(DatabaseManagementUc.name); } @@ -75,9 +75,9 @@ export class DatabaseManagementUc { /** * Loads all collection names from database and adds related file paths. - * @returns {ICollectionFilePath} + * @returns {CollectionFilePath} */ - private async loadAllCollectionsFromDatabase(targetFolder: string): Promise { + private async loadAllCollectionsFromDatabase(targetFolder: string): Promise { const collections = await this.databaseManagementService.getCollectionNames(); const collectionsWithFilePaths = collections.map((collectionName) => { return { @@ -90,9 +90,9 @@ export class DatabaseManagementUc { /** * Loads all collection names and file paths from backup files. - * @returns {ICollectionFilePath} + * @returns {CollectionFilePath} */ - private async loadAllCollectionsFromFilesystem(baseDir: string): Promise { + private async loadAllCollectionsFromFilesystem(baseDir: string): Promise { const filenames = await this.fileSystemAdapter.readDir(baseDir); const collectionsWithFilePaths = filenames.map((fileName) => { return { @@ -107,14 +107,14 @@ export class DatabaseManagementUc { * Scans for existing collections and optionally filters them based on * @param source * @param collectionNameFilter - * @returns {ICollectionFilePath} the filtered collection names and related file paths + * @returns {CollectionFilePath} the filtered collection names and related file paths */ private async loadCollectionsAvailableFromSourceAndFilterByCollectionNames( source: 'files' | 'database', folder: string, collectionNameFilter?: string[] ) { - let allCollectionsWithFilePaths: ICollectionFilePath[] = []; + let allCollectionsWithFilePaths: CollectionFilePath[] = []; // load all available collections from source if (source === 'files') { diff --git a/apps/server/src/modules/news/controller/news.controller.ts b/apps/server/src/modules/news/controller/news.controller.ts index 2f1c227401a..c2cf6ba4e98 100644 --- a/apps/server/src/modules/news/controller/news.controller.ts +++ b/apps/server/src/modules/news/controller/news.controller.ts @@ -1,7 +1,7 @@ +import { Authenticate, CurrentUser, ICurrentUser } from '@modules/authentication'; import { Body, Controller, Delete, Get, Param, Patch, Post, Query } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; import { PaginationParams } from '@shared/controller'; -import { ICurrentUser, Authenticate, CurrentUser } from '@modules/authentication'; import { NewsMapper } from '../mapper/news.mapper'; import { NewsUc } from '../uc/news.uc'; import { diff --git a/apps/server/src/modules/news/controller/team-news.controller.ts b/apps/server/src/modules/news/controller/team-news.controller.ts index 2344d6f2ac9..ac70748e439 100644 --- a/apps/server/src/modules/news/controller/team-news.controller.ts +++ b/apps/server/src/modules/news/controller/team-news.controller.ts @@ -1,6 +1,6 @@ +import { Authenticate, CurrentUser, ICurrentUser } from '@modules/authentication'; import { Controller, Get, Param, Query } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; -import { ICurrentUser, Authenticate, CurrentUser } from '@modules/authentication'; import { PaginationParams } from '@shared/controller'; import { NewsMapper } from '../mapper/news.mapper'; import { NewsUc } from '../uc'; diff --git a/apps/server/src/modules/news/mapper/news.mapper.spec.ts b/apps/server/src/modules/news/mapper/news.mapper.spec.ts index 1ca549bfe74..40bd66d8130 100644 --- a/apps/server/src/modules/news/mapper/news.mapper.spec.ts +++ b/apps/server/src/modules/news/mapper/news.mapper.spec.ts @@ -1,21 +1,20 @@ import { ObjectId } from '@mikro-orm/mongodb'; import { CourseNews, - INewsProperties, + CreateNews, + INewsScope, + IUpdateNews, News, + NewsProperties, + NewsTarget, + NewsTargetModel, SchoolEntity, SchoolNews, TeamEntity, TeamNews, User, - NewsTargetModel, - INewsScope, - ICreateNews, - IUpdateNews, - NewsTarget, } from '@shared/domain'; -import { courseFactory, schoolFactory, userFactory, setupEntities } from '@shared/testing'; -import { NewsMapper } from './news.mapper'; +import { courseFactory, schoolFactory, setupEntities, userFactory } from '@shared/testing'; import { CreateNewsParams, FilterNewsParams, @@ -25,6 +24,7 @@ import { UserInfoResponse, } from '../controller/dto'; import { TargetInfoResponse } from '../controller/dto/target-info.response'; +import { NewsMapper } from './news.mapper'; const getTargetModel = (news: News): NewsTargetModel => { if (news instanceof SchoolNews) { @@ -42,14 +42,14 @@ const date = new Date(2021, 1, 1, 0, 0, 0); const createNews = ( newsProps, - NewsType: { new (props: INewsProperties): T }, + NewsType: { new (props: NewsProperties): T }, school: SchoolEntity, creator: User, target: NewsTarget ): T => { const newsId = new ObjectId().toHexString(); // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const props: INewsProperties = { + const props: NewsProperties = { id: newsId, displayAt: date, updatedAt: date, @@ -204,8 +204,8 @@ describe('NewsMapper', () => { targetModel, targetId, }; - const result: ICreateNews = NewsMapper.mapCreateNewsToDomain(params); - const expected: ICreateNews = { + const result: CreateNews = NewsMapper.mapCreateNewsToDomain(params); + const expected: CreateNews = { title: params.title, content: params.content, displayAt: date, diff --git a/apps/server/src/modules/news/mapper/news.mapper.ts b/apps/server/src/modules/news/mapper/news.mapper.ts index aa064a97b1a..36c17f57607 100644 --- a/apps/server/src/modules/news/mapper/news.mapper.ts +++ b/apps/server/src/modules/news/mapper/news.mapper.ts @@ -1,4 +1,4 @@ -import { News, ICreateNews, INewsScope, IUpdateNews, NewsTargetModel } from '@shared/domain'; +import { CreateNews, INewsScope, IUpdateNews, News, NewsTargetModel } from '@shared/domain'; import { LogMessageData } from '@src/core/logger'; import { CreateNewsParams, FilterNewsParams, NewsResponse, UpdateNewsParams } from '../controller/dto'; import { SchoolInfoMapper } from './school-info.mapper'; @@ -49,7 +49,7 @@ export class NewsMapper { return dto; } - static mapCreateNewsToDomain(params: CreateNewsParams): ICreateNews { + static mapCreateNewsToDomain(params: CreateNewsParams): CreateNews { const dto = { title: params.title, content: params.content, diff --git a/apps/server/src/modules/news/uc/news.uc.spec.ts b/apps/server/src/modules/news/uc/news.uc.spec.ts index d0671dd4259..91166132320 100644 --- a/apps/server/src/modules/news/uc/news.uc.spec.ts +++ b/apps/server/src/modules/news/uc/news.uc.spec.ts @@ -1,12 +1,12 @@ import { createMock } from '@golevelup/ts-jest'; import { ObjectId } from '@mikro-orm/mongodb'; +import { FeathersAuthorizationService } from '@modules/authorization'; import { UnauthorizedException } from '@nestjs/common'; import { NotFoundException } from '@nestjs/common/exceptions/not-found.exception'; import { Test, TestingModule } from '@nestjs/testing'; -import { ICreateNews, NewsTargetModel, Permission } from '@shared/domain'; +import { CreateNews, NewsTargetModel, Permission } from '@shared/domain'; import { NewsRepo } from '@shared/repo'; import { Logger } from '@src/core/logger'; -import { FeathersAuthorizationService } from '@modules/authorization'; import { NewsUc } from './news.uc'; describe('NewsUc', () => { @@ -232,7 +232,7 @@ describe('NewsUc', () => { content: 'content', displayAt: new Date(), target: { targetModel: NewsTargetModel.School, targetId: schoolId }, - } as ICreateNews; + } as CreateNews; const createdNews = await service.create(userId, schoolId, params); expect(createdNews.school).toEqual(schoolId); expect(createdNews.creator).toEqual(userId); @@ -247,7 +247,7 @@ describe('NewsUc', () => { content: 'content', displayAt: new Date(), target: { targetModel: NewsTargetModel.School, targetId: schoolId }, - } as ICreateNews; + } as CreateNews; await service.create(userId, schoolId, params); expect(createSpy).toHaveBeenCalled(); }); diff --git a/apps/server/src/modules/news/uc/news.uc.ts b/apps/server/src/modules/news/uc/news.uc.ts index de0608b0234..002eaa45819 100644 --- a/apps/server/src/modules/news/uc/news.uc.ts +++ b/apps/server/src/modules/news/uc/news.uc.ts @@ -1,8 +1,9 @@ +import { FeathersAuthorizationService } from '@modules/authorization'; import { Injectable } from '@nestjs/common'; import { Counted, + CreateNews, EntityId, - ICreateNews, IFindOptions, INewsScope, IUpdateNews, @@ -14,7 +15,6 @@ import { import { NewsRepo, NewsTargetFilter } from '@shared/repo'; import { CrudOperation } from '@shared/types'; import { Logger } from '@src/core/logger'; -import { FeathersAuthorizationService } from '@modules/authorization'; import { NewsCrudOperationLoggable } from '../loggable/news-crud-operation.loggable'; type NewsPermission = Permission.NEWS_VIEW | Permission.NEWS_EDIT; @@ -36,7 +36,7 @@ export class NewsUc { * @param params * @returns */ - public async create(userId: EntityId, schoolId: EntityId, params: ICreateNews): Promise { + public async create(userId: EntityId, schoolId: EntityId, params: CreateNews): Promise { const { targetModel, targetId } = params.target; await this.authorizationService.checkEntityPermissions(userId, targetModel, targetId, [Permission.NEWS_CREATE]); diff --git a/apps/server/src/modules/oauth-provider/controller/oauth-provider.controller.spec.ts b/apps/server/src/modules/oauth-provider/controller/oauth-provider.controller.spec.ts index bc993271066..515f54f0a39 100644 --- a/apps/server/src/modules/oauth-provider/controller/oauth-provider.controller.spec.ts +++ b/apps/server/src/modules/oauth-provider/controller/oauth-provider.controller.spec.ts @@ -1,8 +1,12 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { OauthProviderLogoutFlowUc } from '@modules/oauth-provider/uc/oauth-provider.logout-flow.uc'; import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { Configuration } from '@hpi-schul-cloud/commons/lib'; -import { OauthProviderResponseMapper } from '@modules/oauth-provider/mapper/oauth-provider-response.mapper'; +import { + ProviderConsentResponse, + ProviderConsentSessionResponse, + ProviderLoginResponse, + ProviderRedirectResponse, +} from '@infra/oauth-provider/dto'; +import { ICurrentUser } from '@modules/authentication'; import { AcceptQuery, ChallengeParams, @@ -15,18 +19,14 @@ import { OauthClientResponse, RedirectResponse, } from '@modules/oauth-provider/controller/dto'; -import { - ProviderConsentResponse, - ProviderConsentSessionResponse, - ProviderLoginResponse, - ProviderRedirectResponse, -} from '@infra/oauth-provider/dto'; +import { OauthProviderResponseMapper } from '@modules/oauth-provider/mapper/oauth-provider-response.mapper'; import { OauthProviderConsentFlowUc } from '@modules/oauth-provider/uc/oauth-provider.consent-flow.uc'; -import { ICurrentUser } from '@modules/authentication'; +import { OauthProviderLogoutFlowUc } from '@modules/oauth-provider/uc/oauth-provider.logout-flow.uc'; import { OauthProviderUc } from '@modules/oauth-provider/uc/oauth-provider.uc'; -import { OauthProviderController } from './oauth-provider.controller'; +import { Test, TestingModule } from '@nestjs/testing'; import { OauthProviderClientCrudUc } from '../uc/oauth-provider.client-crud.uc'; import { OauthProviderLoginFlowUc } from '../uc/oauth-provider.login-flow.uc'; +import { OauthProviderController } from './oauth-provider.controller'; describe('OauthProviderController', () => { let module: TestingModule; diff --git a/apps/server/src/modules/oauth-provider/controller/oauth-provider.controller.ts b/apps/server/src/modules/oauth-provider/controller/oauth-provider.controller.ts index 97b16e8e49f..29644154a3a 100644 --- a/apps/server/src/modules/oauth-provider/controller/oauth-provider.controller.ts +++ b/apps/server/src/modules/oauth-provider/controller/oauth-provider.controller.ts @@ -1,22 +1,20 @@ import { Configuration } from '@hpi-schul-cloud/commons'; +import { Authenticate, CurrentUser, ICurrentUser } from '@modules/authentication'; import { Body, Controller, Delete, Get, Param, Patch, Post, Put, Query } from '@nestjs/common'; -import { ICurrentUser, Authenticate, CurrentUser } from '@modules/authentication'; // import should be @infra/oauth-provider import { ProviderConsentResponse, + ProviderConsentSessionResponse, ProviderLoginResponse, ProviderOauthClient, ProviderRedirectResponse, - ProviderConsentSessionResponse, } from '@infra/oauth-provider/dto'; import { ApiTags } from '@nestjs/swagger'; -import { OauthProviderLogoutFlowUc } from '../uc/oauth-provider.logout-flow.uc'; -import { OauthProviderLoginFlowUc } from '../uc/oauth-provider.login-flow.uc'; import { OauthProviderResponseMapper } from '../mapper/oauth-provider-response.mapper'; -import { OauthProviderConsentFlowUc } from '../uc/oauth-provider.consent-flow.uc'; -import { ConsentResponse } from './dto/response/consent.response'; import { OauthProviderClientCrudUc } from '../uc/oauth-provider.client-crud.uc'; -import { RedirectResponse } from './dto/response/redirect.response'; +import { OauthProviderConsentFlowUc } from '../uc/oauth-provider.consent-flow.uc'; +import { OauthProviderLoginFlowUc } from '../uc/oauth-provider.login-flow.uc'; +import { OauthProviderLogoutFlowUc } from '../uc/oauth-provider.logout-flow.uc'; import { OauthProviderUc } from '../uc/oauth-provider.uc'; import { AcceptQuery, @@ -31,6 +29,8 @@ import { OauthClientResponse, RevokeConsentParams, } from './dto'; +import { ConsentResponse } from './dto/response/consent.response'; +import { RedirectResponse } from './dto/response/redirect.response'; @Controller('oauth2') @ApiTags('Oauth2') diff --git a/apps/server/src/modules/oauth-provider/service/oauth-provider.login-flow.service.spec.ts b/apps/server/src/modules/oauth-provider/service/oauth-provider.login-flow.service.spec.ts index 3c85dde6c62..052f3f1c6a2 100644 --- a/apps/server/src/modules/oauth-provider/service/oauth-provider.login-flow.service.spec.ts +++ b/apps/server/src/modules/oauth-provider/service/oauth-provider.login-flow.service.spec.ts @@ -1,12 +1,12 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; -import { NotFoundException } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; -import { LtiToolDO } from '@shared/domain'; -import { externalToolFactory, ltiToolDOFactory, setupEntities } from '@shared/testing'; import { LtiToolService } from '@modules/lti-tool'; import { ExternalTool } from '@modules/tool/external-tool/domain'; import { ExternalToolService } from '@modules/tool/external-tool/service'; import { IToolFeatures, ToolFeatures } from '@modules/tool/tool-config'; +import { NotFoundException } from '@nestjs/common'; +import { Test, TestingModule } from '@nestjs/testing'; +import { LtiToolDO } from '@shared/domain'; +import { externalToolFactory, ltiToolDOFactory, setupEntities } from '@shared/testing'; import { OauthProviderLoginFlowService } from './oauth-provider.login-flow.service'; describe('OauthProviderLoginFlowService', () => { diff --git a/apps/server/src/modules/oauth-provider/service/oauth-provider.login-flow.service.ts b/apps/server/src/modules/oauth-provider/service/oauth-provider.login-flow.service.ts index 0b1948baa28..adf363415fd 100644 --- a/apps/server/src/modules/oauth-provider/service/oauth-provider.login-flow.service.ts +++ b/apps/server/src/modules/oauth-provider/service/oauth-provider.login-flow.service.ts @@ -1,11 +1,11 @@ -import { Inject } from '@nestjs/common'; -import { Injectable } from '@nestjs/common/decorators/core/injectable.decorator'; -import { NotFoundException } from '@nestjs/common/exceptions/not-found.exception'; -import { LtiToolDO } from '@shared/domain/domainobject/ltitool.do'; import { LtiToolService } from '@modules/lti-tool/service'; import { ExternalTool } from '@modules/tool/external-tool/domain'; import { ExternalToolService } from '@modules/tool/external-tool/service'; import { IToolFeatures, ToolFeatures } from '@modules/tool/tool-config'; +import { Inject } from '@nestjs/common'; +import { Injectable } from '@nestjs/common/decorators/core/injectable.decorator'; +import { NotFoundException } from '@nestjs/common/exceptions/not-found.exception'; +import { LtiToolDO } from '@shared/domain/domainobject/ltitool.do'; @Injectable() export class OauthProviderLoginFlowService { diff --git a/apps/server/src/modules/oauth-provider/uc/oauth-provider.client-crud.uc.spec.ts b/apps/server/src/modules/oauth-provider/uc/oauth-provider.client-crud.uc.spec.ts index d2eb1636e53..8e80da47969 100644 --- a/apps/server/src/modules/oauth-provider/uc/oauth-provider.client-crud.uc.spec.ts +++ b/apps/server/src/modules/oauth-provider/uc/oauth-provider.client-crud.uc.spec.ts @@ -1,12 +1,12 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { OauthProviderService } from '@infra/oauth-provider'; +import { ProviderOauthClient } from '@infra/oauth-provider/dto'; +import { ICurrentUser } from '@modules/authentication'; +import { AuthorizationService } from '@modules/authorization'; import { UnauthorizedException } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { Permission, User } from '@shared/domain'; -import { OauthProviderService } from '@infra/oauth-provider'; -import { ProviderOauthClient } from '@infra/oauth-provider/dto'; import { setupEntities, userFactory } from '@shared/testing'; -import { AuthorizationService } from '@modules/authorization'; -import { ICurrentUser } from '@modules/authentication'; import { OauthProviderClientCrudUc } from './oauth-provider.client-crud.uc'; import resetAllMocks = jest.resetAllMocks; diff --git a/apps/server/src/modules/oauth-provider/uc/oauth-provider.client-crud.uc.ts b/apps/server/src/modules/oauth-provider/uc/oauth-provider.client-crud.uc.ts index 18fd23ae788..84221847796 100644 --- a/apps/server/src/modules/oauth-provider/uc/oauth-provider.client-crud.uc.ts +++ b/apps/server/src/modules/oauth-provider/uc/oauth-provider.client-crud.uc.ts @@ -1,9 +1,9 @@ -import { Injectable } from '@nestjs/common'; -import { OauthProviderService } from '@infra/oauth-provider/index'; -import { Permission, User } from '@shared/domain/index'; -import { AuthorizationService } from '@modules/authorization'; import { ProviderOauthClient } from '@infra/oauth-provider/dto'; +import { OauthProviderService } from '@infra/oauth-provider/index'; import { ICurrentUser } from '@modules/authentication'; +import { AuthorizationService } from '@modules/authorization'; +import { Injectable } from '@nestjs/common'; +import { Permission, User } from '@shared/domain/index'; @Injectable() export class OauthProviderClientCrudUc { diff --git a/apps/server/src/modules/oauth-provider/uc/oauth-provider.consent-flow.uc.spec.ts b/apps/server/src/modules/oauth-provider/uc/oauth-provider.consent-flow.uc.spec.ts index e56700477a8..e1a1663818e 100644 --- a/apps/server/src/modules/oauth-provider/uc/oauth-provider.consent-flow.uc.spec.ts +++ b/apps/server/src/modules/oauth-provider/uc/oauth-provider.consent-flow.uc.spec.ts @@ -1,13 +1,13 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { OauthProviderService } from '@infra/oauth-provider'; import { createMock, DeepMocked } from '@golevelup/ts-jest'; -import { AcceptQuery, ConsentRequestBody } from '@modules/oauth-provider/controller/dto'; +import { OauthProviderService } from '@infra/oauth-provider'; import { AcceptConsentRequestBody, ProviderConsentResponse, ProviderRedirectResponse } from '@infra/oauth-provider/dto'; -import { OauthProviderConsentFlowUc } from '@modules/oauth-provider/uc/oauth-provider.consent-flow.uc'; import { ICurrentUser } from '@modules/authentication'; -import { ForbiddenException } from '@nestjs/common'; -import { IdTokenService } from '@modules/oauth-provider/service/id-token.service'; +import { AcceptQuery, ConsentRequestBody } from '@modules/oauth-provider/controller/dto'; import { IdToken } from '@modules/oauth-provider/interface/id-token'; +import { IdTokenService } from '@modules/oauth-provider/service/id-token.service'; +import { OauthProviderConsentFlowUc } from '@modules/oauth-provider/uc/oauth-provider.consent-flow.uc'; +import { ForbiddenException } from '@nestjs/common'; +import { Test, TestingModule } from '@nestjs/testing'; describe('OauthProviderConsentFlowUc', () => { let module: TestingModule; diff --git a/apps/server/src/modules/oauth-provider/uc/oauth-provider.consent-flow.uc.ts b/apps/server/src/modules/oauth-provider/uc/oauth-provider.consent-flow.uc.ts index 126f68f1b80..a9c54b4f502 100644 --- a/apps/server/src/modules/oauth-provider/uc/oauth-provider.consent-flow.uc.ts +++ b/apps/server/src/modules/oauth-provider/uc/oauth-provider.consent-flow.uc.ts @@ -1,15 +1,15 @@ +import { OauthProviderService } from '@infra/oauth-provider'; import { AcceptConsentRequestBody, ProviderConsentResponse, ProviderRedirectResponse, RejectRequestBody, } from '@infra/oauth-provider/dto'; -import { AcceptQuery, ConsentRequestBody } from '@modules/oauth-provider/controller/dto'; import { ICurrentUser } from '@modules/authentication'; -import { ForbiddenException, Injectable } from '@nestjs/common'; -import { IdTokenService } from '@modules/oauth-provider/service/id-token.service'; -import { OauthProviderService } from '@infra/oauth-provider'; +import { AcceptQuery, ConsentRequestBody } from '@modules/oauth-provider/controller/dto'; import { IdToken } from '@modules/oauth-provider/interface/id-token'; +import { IdTokenService } from '@modules/oauth-provider/service/id-token.service'; +import { ForbiddenException, Injectable } from '@nestjs/common'; @Injectable() export class OauthProviderConsentFlowUc { diff --git a/apps/server/src/modules/oauth/service/hydra.service.ts b/apps/server/src/modules/oauth/service/hydra.service.ts index 360926d080a..9c02a537d42 100644 --- a/apps/server/src/modules/oauth/service/hydra.service.ts +++ b/apps/server/src/modules/oauth/service/hydra.service.ts @@ -1,26 +1,26 @@ import { Configuration } from '@hpi-schul-cloud/commons/lib'; +import { DefaultEncryptionService, EncryptionService } from '@infra/encryption'; +import { AuthorizationParams } from '@modules/oauth/controller/dto/authorization.params'; +import { CookiesDto } from '@modules/oauth/service/dto/cookies.dto'; +import { HydraRedirectDto } from '@modules/oauth/service/dto/hydra.redirect.dto'; import { HttpService } from '@nestjs/axios'; import { Inject, InternalServerErrorException } from '@nestjs/common'; import { Injectable } from '@nestjs/common/decorators/core/injectable.decorator'; import { OauthConfig } from '@shared/domain'; import { LtiToolDO } from '@shared/domain/domainobject/ltitool.do'; -import { DefaultEncryptionService, IEncryptionService } from '@infra/encryption'; import { LtiToolRepo } from '@shared/repo'; import { LegacyLogger } from '@src/core/logger'; -import { AuthorizationParams } from '@modules/oauth/controller/dto/authorization.params'; -import { CookiesDto } from '@modules/oauth/service/dto/cookies.dto'; -import { HydraRedirectDto } from '@modules/oauth/service/dto/hydra.redirect.dto'; import { AxiosRequestConfig, AxiosResponse } from 'axios'; import { nanoid } from 'nanoid'; import QueryString from 'qs'; -import { Observable, firstValueFrom } from 'rxjs'; +import { firstValueFrom, Observable } from 'rxjs'; @Injectable() export class HydraSsoService { constructor( private readonly ltiRepo: LtiToolRepo, private readonly httpService: HttpService, - @Inject(DefaultEncryptionService) private readonly oAuthEncryptionService: IEncryptionService, + @Inject(DefaultEncryptionService) private readonly oAuthEncryptionService: EncryptionService, private readonly logger: LegacyLogger ) {} diff --git a/apps/server/src/modules/oauth/service/oauth.service.spec.ts b/apps/server/src/modules/oauth/service/oauth.service.spec.ts index adfa9603bc6..368586af7c4 100644 --- a/apps/server/src/modules/oauth/service/oauth.service.spec.ts +++ b/apps/server/src/modules/oauth/service/oauth.service.spec.ts @@ -1,6 +1,6 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { Configuration } from '@hpi-schul-cloud/commons'; -import { DefaultEncryptionService, IEncryptionService, SymetricKeyEncryptionService } from '@infra/encryption'; +import { DefaultEncryptionService, EncryptionService, SymetricKeyEncryptionService } from '@infra/encryption'; import { ObjectId } from '@mikro-orm/mongodb'; import { LegacySchoolService } from '@modules/legacy-school'; import { ProvisioningService } from '@modules/provisioning'; @@ -70,7 +70,7 @@ describe('OAuthService', () => { }, { provide: DefaultEncryptionService, - useValue: createMock(), + useValue: createMock(), }, { provide: LegacyLogger, diff --git a/apps/server/src/modules/oauth/service/oauth.service.ts b/apps/server/src/modules/oauth/service/oauth.service.ts index 5e787d79082..299198ef33a 100644 --- a/apps/server/src/modules/oauth/service/oauth.service.ts +++ b/apps/server/src/modules/oauth/service/oauth.service.ts @@ -1,6 +1,6 @@ -import { DefaultEncryptionService, IEncryptionService } from '@infra/encryption'; +import { DefaultEncryptionService, EncryptionService } from '@infra/encryption'; import { LegacySchoolService } from '@modules/legacy-school'; -import { ProvisioningService, OauthDataDto } from '@modules/provisioning'; +import { OauthDataDto, ProvisioningService } from '@modules/provisioning'; import { SystemService } from '@modules/system'; import { SystemDto } from '@modules/system/service'; import { UserService } from '@modules/user'; @@ -21,7 +21,7 @@ export class OAuthService { constructor( private readonly userService: UserService, private readonly oauthAdapterService: OauthAdapterService, - @Inject(DefaultEncryptionService) private readonly oAuthEncryptionService: IEncryptionService, + @Inject(DefaultEncryptionService) private readonly oAuthEncryptionService: EncryptionService, private readonly logger: LegacyLogger, private readonly provisioningService: ProvisioningService, private readonly systemService: SystemService, diff --git a/apps/server/src/modules/pseudonym/controller/pseudonym.controller.ts b/apps/server/src/modules/pseudonym/controller/pseudonym.controller.ts index f7e378e050a..993f3ba9ded 100644 --- a/apps/server/src/modules/pseudonym/controller/pseudonym.controller.ts +++ b/apps/server/src/modules/pseudonym/controller/pseudonym.controller.ts @@ -1,3 +1,4 @@ +import { Authenticate, CurrentUser, ICurrentUser } from '@modules/authentication'; import { Controller, Get, Param } from '@nestjs/common'; import { ApiForbiddenResponse, @@ -7,7 +8,6 @@ import { ApiUnauthorizedResponse, } from '@nestjs/swagger'; import { Pseudonym } from '@shared/domain'; -import { Authenticate, CurrentUser, ICurrentUser } from '@modules/authentication'; import { PseudonymMapper } from '../mapper/pseudonym.mapper'; import { PseudonymUc } from '../uc'; import { PseudonymResponse } from './dto'; diff --git a/apps/server/src/modules/pseudonym/entity/external-tool-pseudonym.entity.ts b/apps/server/src/modules/pseudonym/entity/external-tool-pseudonym.entity.ts index 3505f0bc7df..9fabd37f91c 100644 --- a/apps/server/src/modules/pseudonym/entity/external-tool-pseudonym.entity.ts +++ b/apps/server/src/modules/pseudonym/entity/external-tool-pseudonym.entity.ts @@ -1,9 +1,9 @@ import { Entity, Property, Unique } from '@mikro-orm/core'; import { ObjectId } from '@mikro-orm/mongodb'; -import { BaseEntityWithTimestamps } from '@shared/domain/entity/base.entity'; import { EntityId } from '@shared/domain'; +import { BaseEntityWithTimestamps } from '@shared/domain/entity/base.entity'; -export interface IExternalToolPseudonymEntityProps { +export interface ExternalToolPseudonymEntityProps { id?: EntityId; pseudonym: string; toolId: ObjectId; @@ -23,7 +23,7 @@ export class ExternalToolPseudonymEntity extends BaseEntityWithTimestamps { @Property() userId: ObjectId; - constructor(props: IExternalToolPseudonymEntityProps) { + constructor(props: ExternalToolPseudonymEntityProps) { super(); if (props.id != null) { this.id = props.id; diff --git a/apps/server/src/modules/pseudonym/repo/external-tool-pseudonym.repo.ts b/apps/server/src/modules/pseudonym/repo/external-tool-pseudonym.repo.ts index 79a17a80541..65ef03cd539 100644 --- a/apps/server/src/modules/pseudonym/repo/external-tool-pseudonym.repo.ts +++ b/apps/server/src/modules/pseudonym/repo/external-tool-pseudonym.repo.ts @@ -1,9 +1,9 @@ import { EntityManager, ObjectId } from '@mikro-orm/mongodb'; import { Injectable } from '@nestjs/common'; -import { EntityId, IFindOptions, IPagination, Page, Pseudonym } from '@shared/domain'; +import { EntityId, IFindOptions, Page, Pagination, Pseudonym } from '@shared/domain'; import { Scope } from '@shared/repo'; import { PseudonymSearchQuery } from '../domain'; -import { ExternalToolPseudonymEntity, IExternalToolPseudonymEntityProps } from '../entity'; +import { ExternalToolPseudonymEntity, ExternalToolPseudonymEntityProps } from '../entity'; import { PseudonymScope } from '../entity/pseudonym.scope'; @Injectable() @@ -50,7 +50,7 @@ export class ExternalToolPseudonymRepo { .getUnitOfWork() .getById(ExternalToolPseudonymEntity.name, domainObject.id); - const entityProps: IExternalToolPseudonymEntityProps = this.mapDomainObjectToEntityProperties(domainObject); + const entityProps: ExternalToolPseudonymEntityProps = this.mapDomainObjectToEntityProperties(domainObject); let entity: ExternalToolPseudonymEntity = new ExternalToolPseudonymEntity(entityProps); if (existing) { @@ -101,7 +101,7 @@ export class ExternalToolPseudonymRepo { return pseudonym; } - protected mapDomainObjectToEntityProperties(entityDO: Pseudonym): IExternalToolPseudonymEntityProps { + protected mapDomainObjectToEntityProperties(entityDO: Pseudonym): ExternalToolPseudonymEntityProps { return { pseudonym: entityDO.pseudonym, toolId: new ObjectId(entityDO.toolId), @@ -110,7 +110,7 @@ export class ExternalToolPseudonymRepo { } async findPseudonym(query: PseudonymSearchQuery, options?: IFindOptions): Promise> { - const pagination: IPagination = options?.pagination ?? {}; + const pagination: Pagination = options?.pagination ?? {}; const scope: Scope = new PseudonymScope() .byPseudonym(query.pseudonym) .byToolId(query.toolId) diff --git a/apps/server/src/modules/server/server.config.ts b/apps/server/src/modules/server/server.config.ts index 5d1ea95cc3b..c119d0fa25a 100644 --- a/apps/server/src/modules/server/server.config.ts +++ b/apps/server/src/modules/server/server.config.ts @@ -1,11 +1,11 @@ import { Configuration } from '@hpi-schul-cloud/commons'; -import type { IIdentityManagementConfig } from '@infra/identity-management'; -import type { ICoreModuleConfig } from '@src/core'; -import type { IAccountConfig } from '@modules/account'; -import type { IFilesStorageClientConfig } from '@modules/files-storage-client'; -import type { IUserConfig } from '@modules/user'; -import type { ICommonCartridgeConfig } from '@modules/learnroom/common-cartridge'; -import { IMailConfig } from '@src/infra/mail/interfaces/mail-config'; +import type { IdentityManagementConfig } from '@infra/identity-management'; +import type { AccountConfig } from '@modules/account'; +import type { FilesStorageClientConfig } from '@modules/files-storage-client'; +import type { CommonCartridgeConfig } from '@modules/learnroom/common-cartridge'; +import type { UserConfig } from '@modules/user'; +import type { CoreModuleConfig } from '@src/core'; +import { MailConfig } from '@src/infra/mail/interfaces/mail-config'; export enum NodeEnvType { TEST = 'test', @@ -14,19 +14,19 @@ export enum NodeEnvType { MIGRATION = 'migration', } -export interface IServerConfig - extends ICoreModuleConfig, - IUserConfig, - IFilesStorageClientConfig, - IAccountConfig, - IIdentityManagementConfig, - ICommonCartridgeConfig, - IMailConfig { +export interface ServerConfig + extends CoreModuleConfig, + UserConfig, + FilesStorageClientConfig, + AccountConfig, + IdentityManagementConfig, + CommonCartridgeConfig, + MailConfig { NODE_ENV: string; SC_DOMAIN: string; } -const config: IServerConfig = { +const config: ServerConfig = { SC_DOMAIN: Configuration.get('SC_DOMAIN') as string, INCOMING_REQUEST_TIMEOUT: Configuration.get('INCOMING_REQUEST_TIMEOUT_API') as number, INCOMING_REQUEST_TIMEOUT_COPY_API: Configuration.get('INCOMING_REQUEST_TIMEOUT_COPY_API') as number, diff --git a/apps/server/src/modules/sharing/controller/api-test/sharing-create-token.api.spec.ts b/apps/server/src/modules/sharing/controller/api-test/sharing-create-token.api.spec.ts index 7890f778f86..a79b02046ac 100644 --- a/apps/server/src/modules/sharing/controller/api-test/sharing-create-token.api.spec.ts +++ b/apps/server/src/modules/sharing/controller/api-test/sharing-create-token.api.spec.ts @@ -1,12 +1,12 @@ -import { Request } from 'express'; -import request from 'supertest'; import { Configuration } from '@hpi-schul-cloud/commons/lib'; import { EntityManager, ObjectId } from '@mikro-orm/mongodb'; +import { ICurrentUser } from '@modules/authentication'; +import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; +import { ServerTestModule } from '@modules/server/server.module'; import { ExecutionContext, HttpStatus, INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { ApiValidationError } from '@shared/common'; import { Permission } from '@shared/domain'; -import { ICurrentUser } from '@modules/authentication'; import { cleanupCollections, courseFactory, @@ -15,10 +15,10 @@ import { schoolFactory, userFactory, } from '@shared/testing'; -import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; -import { ServerTestModule } from '@modules/server/server.module'; -import { ShareTokenBodyParams, ShareTokenResponse } from '../dto'; +import { Request } from 'express'; +import request from 'supertest'; import { ShareTokenParentType } from '../../domainobject/share-token.do'; +import { ShareTokenBodyParams, ShareTokenResponse } from '../dto'; const baseRouteName = '/sharetoken'; diff --git a/apps/server/src/modules/sharing/controller/api-test/sharing-import-token.api.spec.ts b/apps/server/src/modules/sharing/controller/api-test/sharing-import-token.api.spec.ts index f19dea681f2..9d8df39e195 100644 --- a/apps/server/src/modules/sharing/controller/api-test/sharing-import-token.api.spec.ts +++ b/apps/server/src/modules/sharing/controller/api-test/sharing-import-token.api.spec.ts @@ -1,10 +1,13 @@ import { Configuration } from '@hpi-schul-cloud/commons/lib'; import { EntityManager } from '@mikro-orm/mongodb'; +import { ICurrentUser } from '@modules/authentication'; +import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; +import { CopyApiResponse, CopyElementType, CopyStatusEnum } from '@modules/copy-helper'; +import { ServerTestModule } from '@modules/server'; import { ExecutionContext, HttpStatus, INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { ApiValidationError } from '@shared/common'; import { Permission } from '@shared/domain'; -import { ICurrentUser } from '@modules/authentication'; import { cleanupCollections, courseFactory, @@ -13,9 +16,6 @@ import { schoolFactory, userFactory, } from '@shared/testing'; -import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; -import { CopyApiResponse, CopyElementType, CopyStatusEnum } from '@modules/copy-helper'; -import { ServerTestModule } from '@modules/server'; import { Request } from 'express'; import request from 'supertest'; import { ShareTokenContext, ShareTokenContextType, ShareTokenParentType } from '../../domainobject/share-token.do'; diff --git a/apps/server/src/modules/sharing/controller/share-token.controller.spec.ts b/apps/server/src/modules/sharing/controller/share-token.controller.spec.ts index 7d6f51fa471..d37b8b435ff 100644 --- a/apps/server/src/modules/sharing/controller/share-token.controller.spec.ts +++ b/apps/server/src/modules/sharing/controller/share-token.controller.spec.ts @@ -1,8 +1,8 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; -import { Test, TestingModule } from '@nestjs/testing'; -import { courseFactory, setupEntities, shareTokenFactory } from '@shared/testing'; import { ICurrentUser } from '@modules/authentication'; import { CopyElementType, CopyStatus, CopyStatusEnum } from '@modules/copy-helper'; +import { Test, TestingModule } from '@nestjs/testing'; +import { courseFactory, setupEntities, shareTokenFactory } from '@shared/testing'; import { ShareTokenParentType } from '../domainobject/share-token.do'; import { ShareTokenUC } from '../uc'; import { ShareTokenInfoDto } from '../uc/dto'; diff --git a/apps/server/src/modules/sharing/controller/share-token.controller.ts b/apps/server/src/modules/sharing/controller/share-token.controller.ts index 373e1169600..5d990e1d99f 100644 --- a/apps/server/src/modules/sharing/controller/share-token.controller.ts +++ b/apps/server/src/modules/sharing/controller/share-token.controller.ts @@ -1,3 +1,5 @@ +import { Authenticate, CurrentUser, ICurrentUser } from '@modules/authentication'; +import { CopyApiResponse, CopyMapper } from '@modules/copy-helper'; import { Body, Controller, @@ -11,8 +13,6 @@ import { } from '@nestjs/common'; import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; import { ApiValidationError, RequestTimeout } from '@shared/common'; -import { Authenticate, CurrentUser, ICurrentUser } from '@modules/authentication'; -import { CopyApiResponse, CopyMapper } from '@modules/copy-helper'; // invalid import can produce dependency cycles import { serverConfig } from '@modules/server/server.config'; import { ShareTokenInfoResponseMapper, ShareTokenResponseMapper } from '../mapper'; diff --git a/apps/server/src/modules/sharing/entity/share-token.entity.ts b/apps/server/src/modules/sharing/entity/share-token.entity.ts index fd83e054029..c5e2b41e6d0 100644 --- a/apps/server/src/modules/sharing/entity/share-token.entity.ts +++ b/apps/server/src/modules/sharing/entity/share-token.entity.ts @@ -4,7 +4,7 @@ import { BaseEntityWithTimestamps } from '@shared/domain/entity/base.entity'; import { EntityId } from '@shared/domain/types/entity-id'; import { ShareTokenContextType, ShareTokenParentType, ShareTokenString } from '../domainobject/share-token.do'; -export interface IShareTokenProperties { +export interface ShareTokenProperties { token: ShareTokenString; parentType: ShareTokenParentType; parentId: EntityId | ObjectId; @@ -42,7 +42,7 @@ export class ShareToken extends BaseEntityWithTimestamps { @Property({ nullable: true }) expiresAt?: Date; - constructor(props: IShareTokenProperties) { + constructor(props: ShareTokenProperties) { super(); this.token = props.token; this.parentType = props.parentType; diff --git a/apps/server/src/modules/sharing/repo/share-token.repo.ts b/apps/server/src/modules/sharing/repo/share-token.repo.ts index a04808df2de..ea5fee7194f 100644 --- a/apps/server/src/modules/sharing/repo/share-token.repo.ts +++ b/apps/server/src/modules/sharing/repo/share-token.repo.ts @@ -2,15 +2,15 @@ import { EntityName } from '@mikro-orm/core'; import { Injectable } from '@nestjs/common'; import { BaseDORepo } from '@shared/repo/base.do.repo'; import { ShareTokenContext, ShareTokenDO, ShareTokenPayload, ShareTokenString } from '../domainobject/share-token.do'; -import { IShareTokenProperties, ShareToken } from '../entity/share-token.entity'; +import { ShareToken, ShareTokenProperties } from '../entity/share-token.entity'; @Injectable() -export class ShareTokenRepo extends BaseDORepo { +export class ShareTokenRepo extends BaseDORepo { get entityName(): EntityName { return ShareToken; } - entityFactory(props: IShareTokenProperties): ShareToken { + entityFactory(props: ShareTokenProperties): ShareToken { return new ShareToken(props); } @@ -43,8 +43,8 @@ export class ShareTokenRepo extends BaseDORepo { +const createExpectedResponse = (task: Task, status: TaskStatus, descriptions: TaskParentDescriptions): TaskResponse => { const expectedStatus = Object.create(TaskStatusResponse.prototype) as TaskStatusResponse; expectedStatus.graded = status.graded; expectedStatus.maxSubmissions = status.maxSubmissions; @@ -88,7 +84,7 @@ describe('task.mapper', () => { }; const result = TaskMapper.mapTaskUpdateToDomain(params); - const expected: ITaskUpdate = { + const expected: TaskUpdate = { name: params.name, courseId: params.courseId, lessonId: params.lessonId, @@ -112,7 +108,7 @@ describe('task.mapper', () => { }; const result = TaskMapper.mapTaskCreateToDomain(params); - const expected: ITaskUpdate = { + const expected: TaskUpdate = { name: params.name, courseId: params.courseId, lessonId: params.lessonId, diff --git a/apps/server/src/modules/task/mapper/task.mapper.ts b/apps/server/src/modules/task/mapper/task.mapper.ts index a8db8e59af4..2e8d02ae977 100644 --- a/apps/server/src/modules/task/mapper/task.mapper.ts +++ b/apps/server/src/modules/task/mapper/task.mapper.ts @@ -1,4 +1,4 @@ -import { InputFormat, ITaskCreate, ITaskUpdate, RichText, TaskWithStatusVo } from '@shared/domain'; +import { InputFormat, RichText, TaskCreate, TaskUpdate, TaskWithStatusVo } from '@shared/domain'; import { TaskCreateParams, TaskResponse, TaskUpdateParams } from '../controller/dto'; import { TaskStatusMapper } from './task-status.mapper'; @@ -36,8 +36,8 @@ export class TaskMapper { return dto; } - static mapTaskUpdateToDomain(params: TaskUpdateParams): ITaskUpdate { - const dto: ITaskUpdate = { + static mapTaskUpdateToDomain(params: TaskUpdateParams): TaskUpdate { + const dto: TaskUpdate = { name: params.name, courseId: params.courseId, lessonId: params.lessonId, @@ -51,8 +51,8 @@ export class TaskMapper { return dto; } - static mapTaskCreateToDomain(params: TaskCreateParams): ITaskCreate { - const dto: ITaskCreate = { + static mapTaskCreateToDomain(params: TaskCreateParams): TaskCreate { + const dto: TaskCreate = { name: params.name || 'Draft', courseId: params.courseId, lessonId: params.lessonId, diff --git a/apps/server/src/modules/task/uc/task.uc.spec.ts b/apps/server/src/modules/task/uc/task.uc.spec.ts index e836f907f26..cc11f214bcf 100644 --- a/apps/server/src/modules/task/uc/task.uc.spec.ts +++ b/apps/server/src/modules/task/uc/task.uc.spec.ts @@ -1,9 +1,10 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { Action, AuthorizationService } from '@modules/authorization'; +import { LessonService } from '@modules/lesson'; import { ForbiddenException, UnauthorizedException } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { PaginationParams } from '@shared/controller'; -import { ITaskStatus, Permission, SortOrder } from '@shared/domain'; +import { Permission, SortOrder, TaskStatus } from '@shared/domain'; import { CourseRepo, TaskRepo } from '@shared/repo'; import { courseFactory, @@ -14,7 +15,6 @@ import { taskFactory, userFactory, } from '@shared/testing'; -import { LessonService } from '@modules/lesson'; import { TaskService } from '../service'; import { TaskUC } from './task.uc'; @@ -616,7 +616,7 @@ describe('TaskUC', () => { }); describe('[method] changeFinishedForUser', () => { - const mockStatus: ITaskStatus = { + const mockStatus: TaskStatus = { submitted: 1, graded: 1, maxSubmissions: 1, diff --git a/apps/server/src/modules/task/uc/task.uc.ts b/apps/server/src/modules/task/uc/task.uc.ts index 69ca7b32c8f..e385a7f7309 100644 --- a/apps/server/src/modules/task/uc/task.uc.ts +++ b/apps/server/src/modules/task/uc/task.uc.ts @@ -1,19 +1,19 @@ import { Action, AuthorizationContextBuilder, AuthorizationService } from '@modules/authorization'; +import { LessonService } from '@modules/lesson'; import { Injectable, UnauthorizedException } from '@nestjs/common'; import { Counted, Course, EntityId, - IPagination, - ITaskStatus, LessonEntity, + Pagination, Permission, SortOrder, + TaskStatus, TaskWithStatusVo, User, } from '@shared/domain'; import { CourseRepo, TaskRepo } from '@shared/repo'; -import { LessonService } from '@modules/lesson'; import { TaskService } from '../service'; @Injectable() @@ -26,7 +26,7 @@ export class TaskUC { private readonly taskService: TaskService ) {} - async findAllFinished(userId: EntityId, pagination?: IPagination): Promise> { + async findAllFinished(userId: EntityId, pagination?: Pagination): Promise> { const user = await this.authorizationService.getUserWithPermissions(userId); this.authorizationService.checkOneOfPermissions(user, [ @@ -54,7 +54,7 @@ export class TaskUC { ); const taskWithStatusVos = tasks.map((task) => { - let status: ITaskStatus; + let status: TaskStatus; if (this.authorizationService.hasPermission(user, task, AuthorizationContextBuilder.write([]))) { status = task.createTeacherStatusForUser(user); } else { @@ -67,7 +67,7 @@ export class TaskUC { return [taskWithStatusVos, total]; } - async findAll(userId: EntityId, pagination: IPagination): Promise> { + async findAll(userId: EntityId, pagination: Pagination): Promise> { let response: Counted; const user = await this.authorizationService.getUserWithPermissions(userId); @@ -124,7 +124,7 @@ export class TaskUC { return result; } - private async findAllForStudent(user: User, pagination: IPagination): Promise> { + private async findAllForStudent(user: User, pagination: Pagination): Promise> { const courses = await this.getPermittedCourses(user, Action.read); const openCourses = courses.filter((c) => !c.isFinished()); const lessons = await this.getPermittedLessons(user, openCourses); @@ -153,7 +153,7 @@ export class TaskUC { return [taskWithStatusVos, total]; } - private async findAllForTeacher(user: User, pagination: IPagination): Promise> { + private async findAllForTeacher(user: User, pagination: Pagination): Promise> { const courses = await this.getPermittedCourses(user, Action.write); const openCourses = courses.filter((c) => !c.isFinished()); const lessons = await this.getPermittedLessons(user, openCourses); diff --git a/apps/server/src/modules/tool/context-external-tool/controller/tool-reference.controller.ts b/apps/server/src/modules/tool/context-external-tool/controller/tool-reference.controller.ts index 3c00d30e6e2..4f7b7ed2703 100644 --- a/apps/server/src/modules/tool/context-external-tool/controller/tool-reference.controller.ts +++ b/apps/server/src/modules/tool/context-external-tool/controller/tool-reference.controller.ts @@ -1,6 +1,6 @@ +import { Authenticate, CurrentUser, ICurrentUser } from '@modules/authentication'; import { Controller, Get, Param } from '@nestjs/common'; import { ApiForbiddenResponse, ApiOkResponse, ApiOperation, ApiTags, ApiUnauthorizedResponse } from '@nestjs/swagger'; -import { Authenticate, CurrentUser, ICurrentUser } from '@modules/authentication'; import { ToolReference } from '../domain'; import { ContextExternalToolResponseMapper } from '../mapper'; import { ToolReferenceUc } from '../uc'; diff --git a/apps/server/src/modules/tool/context-external-tool/entity/context-external-tool.entity.ts b/apps/server/src/modules/tool/context-external-tool/entity/context-external-tool.entity.ts index aebab1a8d5d..b82475a64ca 100644 --- a/apps/server/src/modules/tool/context-external-tool/entity/context-external-tool.entity.ts +++ b/apps/server/src/modules/tool/context-external-tool/entity/context-external-tool.entity.ts @@ -4,7 +4,7 @@ import { CustomParameterEntryEntity } from '../../common/entity'; import { SchoolExternalToolEntity } from '../../school-external-tool/entity'; import { ContextExternalToolType } from './context-external-tool-type.enum'; -export interface IContextExternalToolProperties { +export interface ContextExternalToolProperties { schoolTool: SchoolExternalToolEntity; contextId: string; @@ -38,7 +38,7 @@ export class ContextExternalToolEntity extends BaseEntityWithTimestamps { @Property() toolVersion: number; - constructor(props: IContextExternalToolProperties) { + constructor(props: ContextExternalToolProperties) { super(); this.schoolTool = props.schoolTool; this.contextId = props.contextId; diff --git a/apps/server/src/modules/tool/context-external-tool/service/tool-version-service.spec.ts b/apps/server/src/modules/tool/context-external-tool/service/tool-version-service.spec.ts index 33c77ecb7ea..87f96a096d2 100644 --- a/apps/server/src/modules/tool/context-external-tool/service/tool-version-service.spec.ts +++ b/apps/server/src/modules/tool/context-external-tool/service/tool-version-service.spec.ts @@ -1,13 +1,13 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { Test, TestingModule } from '@nestjs/testing'; -import { contextExternalToolFactory, externalToolFactory, schoolExternalToolFactory } from '@shared/testing'; import { ApiValidationError } from '@shared/common'; -import { SchoolExternalToolValidationService } from '../../school-external-tool/service'; -import { ToolVersionService } from './tool-version-service'; -import { ContextExternalToolValidationService } from './context-external-tool-validation.service'; +import { contextExternalToolFactory, externalToolFactory, schoolExternalToolFactory } from '@shared/testing'; +import { ToolConfigurationStatus } from '../../common/enum'; import { CommonToolService } from '../../common/service'; +import { SchoolExternalToolValidationService } from '../../school-external-tool/service'; import { IToolFeatures, ToolFeatures } from '../../tool-config'; -import { ToolConfigurationStatus } from '../../common/enum'; +import { ContextExternalToolValidationService } from './context-external-tool-validation.service'; +import { ToolVersionService } from './tool-version-service'; describe('ToolVersionService', () => { let module: TestingModule; diff --git a/apps/server/src/modules/tool/context-external-tool/service/tool-version-service.ts b/apps/server/src/modules/tool/context-external-tool/service/tool-version-service.ts index 2ba0709372a..91c77a1aa98 100644 --- a/apps/server/src/modules/tool/context-external-tool/service/tool-version-service.ts +++ b/apps/server/src/modules/tool/context-external-tool/service/tool-version-service.ts @@ -1,13 +1,13 @@ -import { Injectable } from '@nestjs/common/decorators/core/injectable.decorator'; import { Inject } from '@nestjs/common'; -import { ContextExternalToolValidationService } from './context-external-tool-validation.service'; -import { SchoolExternalToolValidationService } from '../../school-external-tool/service'; -import { IToolFeatures, ToolFeatures } from '../../tool-config'; +import { Injectable } from '@nestjs/common/decorators/core/injectable.decorator'; +import { ToolConfigurationStatus } from '../../common/enum'; +import { CommonToolService } from '../../common/service'; import { ExternalTool } from '../../external-tool/domain'; import { SchoolExternalTool } from '../../school-external-tool/domain'; +import { SchoolExternalToolValidationService } from '../../school-external-tool/service'; +import { IToolFeatures, ToolFeatures } from '../../tool-config'; import { ContextExternalTool } from '../domain'; -import { ToolConfigurationStatus } from '../../common/enum'; -import { CommonToolService } from '../../common/service'; +import { ContextExternalToolValidationService } from './context-external-tool-validation.service'; @Injectable() export class ToolVersionService { diff --git a/apps/server/src/modules/tool/external-tool/controller/tool-configuration.controller.ts b/apps/server/src/modules/tool/external-tool/controller/tool-configuration.controller.ts index 589a1e38e7c..5dbe2db8e9b 100644 --- a/apps/server/src/modules/tool/external-tool/controller/tool-configuration.controller.ts +++ b/apps/server/src/modules/tool/external-tool/controller/tool-configuration.controller.ts @@ -1,3 +1,4 @@ +import { Authenticate, CurrentUser, ICurrentUser } from '@modules/authentication'; import { Controller, Get, Param } from '@nestjs/common'; import { ApiForbiddenResponse, @@ -7,7 +8,6 @@ import { ApiTags, ApiUnauthorizedResponse, } from '@nestjs/swagger'; -import { Authenticate, CurrentUser, ICurrentUser } from '@modules/authentication'; import { ExternalTool } from '../domain'; import { ToolConfigurationMapper } from '../mapper/tool-configuration.mapper'; import { ContextExternalToolTemplateInfo, ExternalToolConfigurationUc } from '../uc'; diff --git a/apps/server/src/modules/tool/external-tool/controller/tool.controller.ts b/apps/server/src/modules/tool/external-tool/controller/tool.controller.ts index 448be593367..b534b01b83b 100644 --- a/apps/server/src/modules/tool/external-tool/controller/tool.controller.ts +++ b/apps/server/src/modules/tool/external-tool/controller/tool.controller.ts @@ -1,3 +1,4 @@ +import { Authenticate, CurrentUser, ICurrentUser } from '@modules/authentication'; import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Query, Res } from '@nestjs/common'; import { ApiCreatedResponse, @@ -14,7 +15,6 @@ import { ValidationError } from '@shared/common'; import { PaginationParams } from '@shared/controller'; import { IFindOptions, Page } from '@shared/domain'; import { LegacyLogger } from '@src/core/logger'; -import { Authenticate, CurrentUser, ICurrentUser } from '@modules/authentication'; import { Response } from 'express'; import { ExternalToolSearchQuery } from '../../common/interface'; import { ExternalTool, ExternalToolMetadata } from '../domain'; diff --git a/apps/server/src/modules/tool/external-tool/service/external-tool-configuration.service.ts b/apps/server/src/modules/tool/external-tool/service/external-tool-configuration.service.ts index 3b5a186d1ba..3d021c592fc 100644 --- a/apps/server/src/modules/tool/external-tool/service/external-tool-configuration.service.ts +++ b/apps/server/src/modules/tool/external-tool/service/external-tool-configuration.service.ts @@ -1,11 +1,11 @@ import { Inject, Injectable } from '@nestjs/common'; import { EntityId, Page } from '@shared/domain'; +import { CustomParameter } from '../../common/domain'; +import { CustomParameterScope } from '../../common/enum'; +import { ContextExternalTool } from '../../context-external-tool/domain'; +import { SchoolExternalTool } from '../../school-external-tool/domain'; import { IToolFeatures, ToolFeatures } from '../../tool-config'; import { ExternalTool } from '../domain'; -import { SchoolExternalTool } from '../../school-external-tool/domain'; -import { ContextExternalTool } from '../../context-external-tool/domain'; -import { CustomParameterScope } from '../../common/enum'; -import { CustomParameter } from '../../common/domain'; import { ContextExternalToolTemplateInfo } from '../uc/dto'; @Injectable() diff --git a/apps/server/src/modules/tool/external-tool/service/external-tool.service.spec.ts b/apps/server/src/modules/tool/external-tool/service/external-tool.service.spec.ts index be146f30941..05416f30fb6 100644 --- a/apps/server/src/modules/tool/external-tool/service/external-tool.service.spec.ts +++ b/apps/server/src/modules/tool/external-tool/service/external-tool.service.spec.ts @@ -1,10 +1,10 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { DefaultEncryptionService, EncryptionService } from '@infra/encryption'; +import { OauthProviderService } from '@infra/oauth-provider'; +import { ProviderOauthClient } from '@infra/oauth-provider/dto'; import { UnprocessableEntityException } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { IFindOptions, Page, SortOrder } from '@shared/domain'; -import { DefaultEncryptionService, IEncryptionService } from '@infra/encryption'; -import { OauthProviderService } from '@infra/oauth-provider'; -import { ProviderOauthClient } from '@infra/oauth-provider/dto'; import { ContextExternalToolRepo, ExternalToolRepo, SchoolExternalToolRepo } from '@shared/repo'; import { externalToolFactory, @@ -28,7 +28,7 @@ describe('ExternalToolService', () => { let courseToolRepo: DeepMocked; let oauthProviderService: DeepMocked; let mapper: DeepMocked; - let encryptionService: DeepMocked; + let encryptionService: DeepMocked; let versionService: DeepMocked; beforeAll(async () => { @@ -49,7 +49,7 @@ describe('ExternalToolService', () => { }, { provide: DefaultEncryptionService, - useValue: createMock(), + useValue: createMock(), }, { provide: SchoolExternalToolRepo, diff --git a/apps/server/src/modules/tool/external-tool/service/external-tool.service.ts b/apps/server/src/modules/tool/external-tool/service/external-tool.service.ts index d8481c28c71..8fb5d8e4387 100644 --- a/apps/server/src/modules/tool/external-tool/service/external-tool.service.ts +++ b/apps/server/src/modules/tool/external-tool/service/external-tool.service.ts @@ -1,8 +1,8 @@ -import { Inject, Injectable, UnprocessableEntityException } from '@nestjs/common'; -import { EntityId, IFindOptions, Page } from '@shared/domain'; -import { DefaultEncryptionService, IEncryptionService } from '@infra/encryption'; +import { DefaultEncryptionService, EncryptionService } from '@infra/encryption'; import { OauthProviderService } from '@infra/oauth-provider'; import { ProviderOauthClient } from '@infra/oauth-provider/dto'; +import { Inject, Injectable, UnprocessableEntityException } from '@nestjs/common'; +import { EntityId, IFindOptions, Page } from '@shared/domain'; import { ContextExternalToolRepo, ExternalToolRepo, SchoolExternalToolRepo } from '@shared/repo'; import { LegacyLogger } from '@src/core/logger'; import { TokenEndpointAuthMethod } from '../../common/enum'; @@ -20,7 +20,7 @@ export class ExternalToolService { private readonly mapper: ExternalToolServiceMapper, private readonly schoolExternalToolRepo: SchoolExternalToolRepo, private readonly contextExternalToolRepo: ContextExternalToolRepo, - @Inject(DefaultEncryptionService) private readonly encryptionService: IEncryptionService, + @Inject(DefaultEncryptionService) private readonly encryptionService: EncryptionService, private readonly legacyLogger: LegacyLogger, private readonly externalToolVersionService: ExternalToolVersionIncrementService ) {} diff --git a/apps/server/src/modules/tool/external-tool/uc/external-tool.uc.spec.ts b/apps/server/src/modules/tool/external-tool/uc/external-tool.uc.spec.ts index 501eef61de9..ff209ac301d 100644 --- a/apps/server/src/modules/tool/external-tool/uc/external-tool.uc.spec.ts +++ b/apps/server/src/modules/tool/external-tool/uc/external-tool.uc.spec.ts @@ -1,5 +1,7 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { ObjectId } from '@mikro-orm/mongodb'; +import { ICurrentUser } from '@modules/authentication'; +import { Action, AuthorizationService } from '@modules/authorization'; import { UnauthorizedException, UnprocessableEntityException } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { IFindOptions, Permission, Role, SortOrder, User } from '@shared/domain'; @@ -9,8 +11,6 @@ import { externalToolFactory, oauth2ToolConfigFactory, } from '@shared/testing/factory/domainobject/tool/external-tool.factory'; -import { ICurrentUser } from '@modules/authentication'; -import { Action, AuthorizationService } from '@modules/authorization'; import { ExternalToolSearchQuery } from '../../common/interface'; import { ExternalTool, ExternalToolMetadata, Oauth2ToolConfig } from '../domain'; import { diff --git a/apps/server/src/modules/tool/school-external-tool/controller/tool-school.controller.ts b/apps/server/src/modules/tool/school-external-tool/controller/tool-school.controller.ts index ccfef902bee..44b1754ea9a 100644 --- a/apps/server/src/modules/tool/school-external-tool/controller/tool-school.controller.ts +++ b/apps/server/src/modules/tool/school-external-tool/controller/tool-school.controller.ts @@ -1,36 +1,36 @@ +import { Authenticate, CurrentUser, ICurrentUser } from '@modules/authentication'; +import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Put, Query } from '@nestjs/common'; import { + ApiBadRequestResponse, ApiCreatedResponse, ApiForbiddenResponse, ApiFoundResponse, - ApiResponse, ApiOkResponse, - ApiBadRequestResponse, + ApiOperation, + ApiResponse, ApiTags, ApiUnauthorizedResponse, ApiUnprocessableEntityResponse, - ApiOperation, } from '@nestjs/swagger'; -import { Body, Controller, Delete, Get, Param, Post, Query, Put, HttpCode, HttpStatus } from '@nestjs/common'; import { ValidationError } from '@shared/common'; import { LegacyLogger } from '@src/core/logger'; -import { Authenticate, CurrentUser, ICurrentUser } from '@modules/authentication'; +import { ExternalToolSearchListResponse } from '../../external-tool/controller/dto'; +import { SchoolExternalTool, SchoolExternalToolMetadata } from '../domain'; import { + SchoolExternalToolMetadataMapper, SchoolExternalToolRequestMapper, SchoolExternalToolResponseMapper, - SchoolExternalToolMetadataMapper, } from '../mapper'; -import { ExternalToolSearchListResponse } from '../../external-tool/controller/dto'; +import { SchoolExternalToolUc } from '../uc'; +import { SchoolExternalToolDto } from '../uc/dto/school-external-tool.types'; import { SchoolExternalToolIdParams, + SchoolExternalToolMetadataResponse, SchoolExternalToolPostParams, SchoolExternalToolResponse, SchoolExternalToolSearchListResponse, SchoolExternalToolSearchParams, - SchoolExternalToolMetadataResponse, } from './dto'; -import { SchoolExternalToolDto } from '../uc/dto/school-external-tool.types'; -import { SchoolExternalToolUc } from '../uc'; -import { SchoolExternalTool, SchoolExternalToolMetadata } from '../domain'; @ApiTags('Tool') @Authenticate('jwt') diff --git a/apps/server/src/modules/tool/school-external-tool/entity/school-external-tool.entity.ts b/apps/server/src/modules/tool/school-external-tool/entity/school-external-tool.entity.ts index fc7f6703d05..b5545239042 100644 --- a/apps/server/src/modules/tool/school-external-tool/entity/school-external-tool.entity.ts +++ b/apps/server/src/modules/tool/school-external-tool/entity/school-external-tool.entity.ts @@ -4,7 +4,7 @@ import { SchoolEntity } from '@shared/domain/entity/school.entity'; import { CustomParameterEntryEntity } from '../../common/entity'; import { ExternalToolEntity } from '../../external-tool/entity'; -export interface ISchoolExternalToolProperties { +export interface SchoolExternalToolProperties { tool: ExternalToolEntity; school: SchoolEntity; schoolParameters?: CustomParameterEntryEntity[]; @@ -25,7 +25,7 @@ export class SchoolExternalToolEntity extends BaseEntityWithTimestamps { @Property() toolVersion: number; - constructor(props: ISchoolExternalToolProperties) { + constructor(props: SchoolExternalToolProperties) { super(); this.tool = props.tool; this.school = props.school; diff --git a/apps/server/src/modules/tool/school-external-tool/service/school-external-tool.service.spec.ts b/apps/server/src/modules/tool/school-external-tool/service/school-external-tool.service.spec.ts index b8c2789322d..1def9935806 100644 --- a/apps/server/src/modules/tool/school-external-tool/service/school-external-tool.service.spec.ts +++ b/apps/server/src/modules/tool/school-external-tool/service/school-external-tool.service.spec.ts @@ -1,16 +1,16 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { Test, TestingModule } from '@nestjs/testing'; +import { ApiValidationError } from '@shared/common'; import { SchoolExternalToolRepo } from '@shared/repo'; import { externalToolFactory } from '@shared/testing/factory/domainobject/tool/external-tool.factory'; import { schoolExternalToolFactory } from '@shared/testing/factory/domainobject/tool/school-external-tool.factory'; -import { ApiValidationError } from '@shared/common'; import { ToolConfigurationStatus } from '../../common/enum'; import { ExternalTool } from '../../external-tool/domain'; import { ExternalToolService } from '../../external-tool/service'; -import { SchoolExternalTool } from '../domain'; -import { SchoolExternalToolService } from './school-external-tool.service'; import { IToolFeatures, ToolFeatures } from '../../tool-config'; +import { SchoolExternalTool } from '../domain'; import { SchoolExternalToolValidationService } from './school-external-tool-validation.service'; +import { SchoolExternalToolService } from './school-external-tool.service'; describe('SchoolExternalToolService', () => { let module: TestingModule; diff --git a/apps/server/src/modules/tool/school-external-tool/service/school-external-tool.service.ts b/apps/server/src/modules/tool/school-external-tool/service/school-external-tool.service.ts index e654f82f96a..2ead0933582 100644 --- a/apps/server/src/modules/tool/school-external-tool/service/school-external-tool.service.ts +++ b/apps/server/src/modules/tool/school-external-tool/service/school-external-tool.service.ts @@ -4,10 +4,10 @@ import { SchoolExternalToolRepo } from '@shared/repo'; import { ToolConfigurationStatus } from '../../common/enum'; import { ExternalTool } from '../../external-tool/domain'; import { ExternalToolService } from '../../external-tool/service'; +import { IToolFeatures, ToolFeatures } from '../../tool-config'; import { SchoolExternalTool } from '../domain'; import { SchoolExternalToolQuery } from '../uc/dto/school-external-tool.types'; import { SchoolExternalToolValidationService } from './school-external-tool-validation.service'; -import { IToolFeatures, ToolFeatures } from '../../tool-config'; @Injectable() export class SchoolExternalToolService { diff --git a/apps/server/src/modules/tool/tool-launch/controller/tool-launch.controller.ts b/apps/server/src/modules/tool/tool-launch/controller/tool-launch.controller.ts index 12424b294ed..3ac323b1000 100644 --- a/apps/server/src/modules/tool/tool-launch/controller/tool-launch.controller.ts +++ b/apps/server/src/modules/tool/tool-launch/controller/tool-launch.controller.ts @@ -1,3 +1,4 @@ +import { Authenticate, CurrentUser, ICurrentUser } from '@modules/authentication'; import { Controller, Get, Param } from '@nestjs/common'; import { ApiBadRequestResponse, @@ -7,11 +8,10 @@ import { ApiTags, ApiUnauthorizedResponse, } from '@nestjs/swagger'; -import { Authenticate, CurrentUser, ICurrentUser } from '@modules/authentication'; -import { ToolLaunchUc } from '../uc'; -import { ToolLaunchParams, ToolLaunchRequestResponse } from './dto'; import { ToolLaunchMapper } from '../mapper'; import { ToolLaunchRequest } from '../types'; +import { ToolLaunchUc } from '../uc'; +import { ToolLaunchParams, ToolLaunchRequestResponse } from './dto'; @ApiTags('Tool') @Authenticate('jwt') diff --git a/apps/server/src/modules/tool/tool-launch/service/launch-strategy/abstract-launch.strategy.spec.ts b/apps/server/src/modules/tool/tool-launch/service/launch-strategy/abstract-launch.strategy.spec.ts index 04868663671..7e170eb412b 100644 --- a/apps/server/src/modules/tool/tool-launch/service/launch-strategy/abstract-launch.strategy.spec.ts +++ b/apps/server/src/modules/tool/tool-launch/service/launch-strategy/abstract-launch.strategy.spec.ts @@ -30,7 +30,7 @@ import { AutoSchoolNumberStrategy, } from '../auto-parameter-strategy'; import { AbstractLaunchStrategy } from './abstract-launch.strategy'; -import { IToolLaunchParams } from './tool-launch-params.interface'; +import { ToolLaunchParams } from './tool-launch-params.interface'; const concreteConfigParameter: PropertyData = { location: PropertyLocation.QUERY, @@ -48,7 +48,7 @@ class TestLaunchStrategy extends AbstractLaunchStrategy { // eslint-disable-next-line @typescript-eslint/no-unused-vars userId: EntityId, // eslint-disable-next-line @typescript-eslint/no-unused-vars - config: IToolLaunchParams + config: ToolLaunchParams ): Promise { // Implement this method with your own logic for the mock launch strategy return Promise.resolve([concreteConfigParameter]); diff --git a/apps/server/src/modules/tool/tool-launch/service/launch-strategy/abstract-launch.strategy.ts b/apps/server/src/modules/tool/tool-launch/service/launch-strategy/abstract-launch.strategy.ts index ce0f07c5731..d5e920bacf4 100644 --- a/apps/server/src/modules/tool/tool-launch/service/launch-strategy/abstract-launch.strategy.ts +++ b/apps/server/src/modules/tool/tool-launch/service/launch-strategy/abstract-launch.strategy.ts @@ -16,11 +16,11 @@ import { AutoSchoolIdStrategy, AutoSchoolNumberStrategy, } from '../auto-parameter-strategy'; -import { IToolLaunchParams } from './tool-launch-params.interface'; -import { IToolLaunchStrategy } from './tool-launch-strategy.interface'; +import { ToolLaunchParams } from './tool-launch-params.interface'; +import { ToolLaunchStrategy } from './tool-launch-strategy.interface'; @Injectable() -export abstract class AbstractLaunchStrategy implements IToolLaunchStrategy { +export abstract class AbstractLaunchStrategy implements ToolLaunchStrategy { private readonly autoParameterStrategyMap: Map; constructor( @@ -37,7 +37,7 @@ export abstract class AbstractLaunchStrategy implements IToolLaunchStrategy { ]); } - public async createLaunchData(userId: EntityId, data: IToolLaunchParams): Promise { + public async createLaunchData(userId: EntityId, data: ToolLaunchParams): Promise { const launchData: ToolLaunchData = this.buildToolLaunchDataFromExternalTool(data.externalTool); const launchDataProperties: PropertyData[] = await this.buildToolLaunchDataFromTools(data); @@ -54,7 +54,7 @@ export abstract class AbstractLaunchStrategy implements IToolLaunchStrategy { public abstract buildToolLaunchDataFromConcreteConfig( userId: EntityId, - config: IToolLaunchParams + config: ToolLaunchParams ): Promise; public abstract buildToolLaunchRequestPayload(url: string, properties: PropertyData[]): string | null; @@ -136,7 +136,7 @@ export abstract class AbstractLaunchStrategy implements IToolLaunchStrategy { return launchData; } - private async buildToolLaunchDataFromTools(data: IToolLaunchParams): Promise { + private async buildToolLaunchDataFromTools(data: ToolLaunchParams): Promise { const propertyData: PropertyData[] = []; const { externalTool, schoolExternalTool, contextExternalTool } = data; const customParameters = externalTool.parameters || []; diff --git a/apps/server/src/modules/tool/tool-launch/service/launch-strategy/basic-tool-launch.strategy.spec.ts b/apps/server/src/modules/tool/tool-launch/service/launch-strategy/basic-tool-launch.strategy.spec.ts index db80f78498f..32ca68a74f0 100644 --- a/apps/server/src/modules/tool/tool-launch/service/launch-strategy/basic-tool-launch.strategy.spec.ts +++ b/apps/server/src/modules/tool/tool-launch/service/launch-strategy/basic-tool-launch.strategy.spec.ts @@ -12,7 +12,7 @@ import { AutoSchoolNumberStrategy, } from '../auto-parameter-strategy'; import { BasicToolLaunchStrategy } from './basic-tool-launch.strategy'; -import { IToolLaunchParams } from './tool-launch-params.interface'; +import { ToolLaunchParams } from './tool-launch-params.interface'; describe('BasicToolLaunchStrategy', () => { let module: TestingModule; @@ -118,7 +118,7 @@ describe('BasicToolLaunchStrategy', () => { const schoolExternalTool: SchoolExternalTool = schoolExternalToolFactory.build(); const contextExternalTool: ContextExternalTool = contextExternalToolFactory.build(); - const data: IToolLaunchParams = { + const data: ToolLaunchParams = { contextExternalTool, schoolExternalTool, externalTool, diff --git a/apps/server/src/modules/tool/tool-launch/service/launch-strategy/basic-tool-launch.strategy.ts b/apps/server/src/modules/tool/tool-launch/service/launch-strategy/basic-tool-launch.strategy.ts index abe5810820d..399b428a710 100644 --- a/apps/server/src/modules/tool/tool-launch/service/launch-strategy/basic-tool-launch.strategy.ts +++ b/apps/server/src/modules/tool/tool-launch/service/launch-strategy/basic-tool-launch.strategy.ts @@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common'; import { EntityId } from '@shared/domain'; import { LaunchRequestMethod, PropertyData, PropertyLocation } from '../../types'; import { AbstractLaunchStrategy } from './abstract-launch.strategy'; -import { IToolLaunchParams } from './tool-launch-params.interface'; +import { ToolLaunchParams } from './tool-launch-params.interface'; @Injectable() export class BasicToolLaunchStrategy extends AbstractLaunchStrategy { @@ -10,7 +10,7 @@ export class BasicToolLaunchStrategy extends AbstractLaunchStrategy { // eslint-disable-next-line @typescript-eslint/no-unused-vars userId: EntityId, // eslint-disable-next-line @typescript-eslint/no-unused-vars - data: IToolLaunchParams + data: ToolLaunchParams ): Promise { return Promise.resolve([]); } diff --git a/apps/server/src/modules/tool/tool-launch/service/launch-strategy/lti11-tool-launch.strategy.spec.ts b/apps/server/src/modules/tool/tool-launch/service/launch-strategy/lti11-tool-launch.strategy.spec.ts index ee2932cd535..69ebd3e4d26 100644 --- a/apps/server/src/modules/tool/tool-launch/service/launch-strategy/lti11-tool-launch.strategy.spec.ts +++ b/apps/server/src/modules/tool/tool-launch/service/launch-strategy/lti11-tool-launch.strategy.spec.ts @@ -26,7 +26,7 @@ import { } from '../auto-parameter-strategy'; import { Lti11EncryptionService } from '../lti11-encryption.service'; import { Lti11ToolLaunchStrategy } from './lti11-tool-launch.strategy'; -import { IToolLaunchParams } from './tool-launch-params.interface'; +import { ToolLaunchParams } from './tool-launch-params.interface'; describe('Lti11ToolLaunchStrategy', () => { let module: TestingModule; @@ -108,7 +108,7 @@ describe('Lti11ToolLaunchStrategy', () => { const schoolExternalTool: SchoolExternalTool = schoolExternalToolFactory.buildWithId(); const contextExternalTool: ContextExternalTool = contextExternalToolFactory.buildWithId(); - const data: IToolLaunchParams = { + const data: ToolLaunchParams = { contextExternalTool, schoolExternalTool, externalTool, @@ -205,7 +205,7 @@ describe('Lti11ToolLaunchStrategy', () => { contextRef: { id: contextId, type: ToolContextType.COURSE }, }); - const data: IToolLaunchParams = { + const data: ToolLaunchParams = { contextExternalTool, schoolExternalTool, externalTool, @@ -252,7 +252,7 @@ describe('Lti11ToolLaunchStrategy', () => { const schoolExternalTool: SchoolExternalTool = schoolExternalToolFactory.buildWithId(); const contextExternalTool: ContextExternalTool = contextExternalToolFactory.buildWithId(); - const data: IToolLaunchParams = { + const data: ToolLaunchParams = { contextExternalTool, schoolExternalTool, externalTool, @@ -301,7 +301,7 @@ describe('Lti11ToolLaunchStrategy', () => { const schoolExternalTool: SchoolExternalTool = schoolExternalToolFactory.buildWithId(); const contextExternalTool: ContextExternalTool = contextExternalToolFactory.buildWithId(); - const data: IToolLaunchParams = { + const data: ToolLaunchParams = { contextExternalTool, schoolExternalTool, externalTool, @@ -352,7 +352,7 @@ describe('Lti11ToolLaunchStrategy', () => { const schoolExternalTool: SchoolExternalTool = schoolExternalToolFactory.buildWithId(); const contextExternalTool: ContextExternalTool = contextExternalToolFactory.buildWithId(); - const data: IToolLaunchParams = { + const data: ToolLaunchParams = { contextExternalTool, schoolExternalTool, externalTool, @@ -390,7 +390,7 @@ describe('Lti11ToolLaunchStrategy', () => { const schoolExternalTool: SchoolExternalTool = schoolExternalToolFactory.buildWithId(); const contextExternalTool: ContextExternalTool = contextExternalToolFactory.buildWithId(); - const data: IToolLaunchParams = { + const data: ToolLaunchParams = { contextExternalTool, schoolExternalTool, externalTool, diff --git a/apps/server/src/modules/tool/tool-launch/service/launch-strategy/lti11-tool-launch.strategy.ts b/apps/server/src/modules/tool/tool-launch/service/launch-strategy/lti11-tool-launch.strategy.ts index 09516395234..fca44aaf90b 100644 --- a/apps/server/src/modules/tool/tool-launch/service/launch-strategy/lti11-tool-launch.strategy.ts +++ b/apps/server/src/modules/tool/tool-launch/service/launch-strategy/lti11-tool-launch.strategy.ts @@ -16,7 +16,7 @@ import { } from '../auto-parameter-strategy'; import { Lti11EncryptionService } from '../lti11-encryption.service'; import { AbstractLaunchStrategy } from './abstract-launch.strategy'; -import { IToolLaunchParams } from './tool-launch-params.interface'; +import { ToolLaunchParams } from './tool-launch-params.interface'; @Injectable() export class Lti11ToolLaunchStrategy extends AbstractLaunchStrategy { @@ -35,7 +35,7 @@ export class Lti11ToolLaunchStrategy extends AbstractLaunchStrategy { // eslint-disable-next-line @typescript-eslint/no-unused-vars public override async buildToolLaunchDataFromConcreteConfig( userId: EntityId, - data: IToolLaunchParams + data: ToolLaunchParams ): Promise { const { config } = data.externalTool; const contextId: EntityId = data.contextExternalTool.contextRef.id; diff --git a/apps/server/src/modules/tool/tool-launch/service/launch-strategy/oauth2-tool-launch.strategy.spec.ts b/apps/server/src/modules/tool/tool-launch/service/launch-strategy/oauth2-tool-launch.strategy.spec.ts index 8f81a1d0eb2..4e80d439a97 100644 --- a/apps/server/src/modules/tool/tool-launch/service/launch-strategy/oauth2-tool-launch.strategy.spec.ts +++ b/apps/server/src/modules/tool/tool-launch/service/launch-strategy/oauth2-tool-launch.strategy.spec.ts @@ -12,7 +12,7 @@ import { AutoSchoolNumberStrategy, } from '../auto-parameter-strategy'; import { OAuth2ToolLaunchStrategy } from './oauth2-tool-launch.strategy'; -import { IToolLaunchParams } from './tool-launch-params.interface'; +import { ToolLaunchParams } from './tool-launch-params.interface'; describe('OAuth2ToolLaunchStrategy', () => { let module: TestingModule; @@ -61,7 +61,7 @@ describe('OAuth2ToolLaunchStrategy', () => { const schoolExternalTool: SchoolExternalTool = schoolExternalToolFactory.build(); const contextExternalTool: ContextExternalTool = contextExternalToolFactory.build(); - const data: IToolLaunchParams = { + const data: ToolLaunchParams = { contextExternalTool, schoolExternalTool, externalTool, diff --git a/apps/server/src/modules/tool/tool-launch/service/launch-strategy/oauth2-tool-launch.strategy.ts b/apps/server/src/modules/tool/tool-launch/service/launch-strategy/oauth2-tool-launch.strategy.ts index 5052310a77d..833b0742a7b 100644 --- a/apps/server/src/modules/tool/tool-launch/service/launch-strategy/oauth2-tool-launch.strategy.ts +++ b/apps/server/src/modules/tool/tool-launch/service/launch-strategy/oauth2-tool-launch.strategy.ts @@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common'; import { EntityId } from '@shared/domain'; import { LaunchRequestMethod, PropertyData } from '../../types'; import { AbstractLaunchStrategy } from './abstract-launch.strategy'; -import { IToolLaunchParams } from './tool-launch-params.interface'; +import { ToolLaunchParams } from './tool-launch-params.interface'; @Injectable() export class OAuth2ToolLaunchStrategy extends AbstractLaunchStrategy { @@ -10,7 +10,7 @@ export class OAuth2ToolLaunchStrategy extends AbstractLaunchStrategy { // eslint-disable-next-line @typescript-eslint/no-unused-vars userId: EntityId, // eslint-disable-next-line @typescript-eslint/no-unused-vars - data: IToolLaunchParams + data: ToolLaunchParams ): Promise { return Promise.resolve([]); } diff --git a/apps/server/src/modules/tool/tool-launch/service/launch-strategy/tool-launch-params.interface.ts b/apps/server/src/modules/tool/tool-launch/service/launch-strategy/tool-launch-params.interface.ts index 24e368476f5..73fdf1d78b7 100644 --- a/apps/server/src/modules/tool/tool-launch/service/launch-strategy/tool-launch-params.interface.ts +++ b/apps/server/src/modules/tool/tool-launch/service/launch-strategy/tool-launch-params.interface.ts @@ -2,7 +2,7 @@ import { ContextExternalTool } from '../../../context-external-tool/domain'; import { ExternalTool } from '../../../external-tool/domain'; import { SchoolExternalTool } from '../../../school-external-tool/domain'; -export interface IToolLaunchParams { +export interface ToolLaunchParams { externalTool: ExternalTool; schoolExternalTool: SchoolExternalTool; diff --git a/apps/server/src/modules/tool/tool-launch/service/launch-strategy/tool-launch-strategy.interface.ts b/apps/server/src/modules/tool/tool-launch/service/launch-strategy/tool-launch-strategy.interface.ts index 09ea919b0c7..a0ba86c5648 100644 --- a/apps/server/src/modules/tool/tool-launch/service/launch-strategy/tool-launch-strategy.interface.ts +++ b/apps/server/src/modules/tool/tool-launch/service/launch-strategy/tool-launch-strategy.interface.ts @@ -1,9 +1,9 @@ import { EntityId } from '@shared/domain'; import { ToolLaunchData, ToolLaunchRequest } from '../../types'; -import { IToolLaunchParams } from './tool-launch-params.interface'; +import { ToolLaunchParams } from './tool-launch-params.interface'; -export interface IToolLaunchStrategy { - createLaunchData(userId: EntityId, params: IToolLaunchParams): Promise; +export interface ToolLaunchStrategy { + createLaunchData(userId: EntityId, params: ToolLaunchParams): Promise; createLaunchRequest(toolLaunchDataDO: ToolLaunchData): ToolLaunchRequest; } diff --git a/apps/server/src/modules/tool/tool-launch/service/tool-launch.service.spec.ts b/apps/server/src/modules/tool/tool-launch/service/tool-launch.service.spec.ts index 16e8e093e9e..c16dc16f365 100644 --- a/apps/server/src/modules/tool/tool-launch/service/tool-launch.service.spec.ts +++ b/apps/server/src/modules/tool/tool-launch/service/tool-launch.service.spec.ts @@ -17,9 +17,9 @@ import { ToolStatusOutdatedLoggableException } from '../error'; import { LaunchRequestMethod, ToolLaunchData, ToolLaunchDataType, ToolLaunchRequest } from '../types'; import { BasicToolLaunchStrategy, - IToolLaunchParams, Lti11ToolLaunchStrategy, OAuth2ToolLaunchStrategy, + ToolLaunchParams, } from './launch-strategy'; import { ToolLaunchService } from './tool-launch.service'; import { ToolVersionService } from '../../context-external-tool/service/tool-version-service'; @@ -98,7 +98,7 @@ describe('ToolLaunchService', () => { openNewTab: false, }); - const launchParams: IToolLaunchParams = { + const launchParams: ToolLaunchParams = { externalTool, schoolExternalTool, contextExternalTool, @@ -165,7 +165,7 @@ describe('ToolLaunchService', () => { const externalTool: ExternalTool = externalToolFactory.build(); externalTool.config.type = 'unknown' as ToolConfigType; - const launchParams: IToolLaunchParams = { + const launchParams: ToolLaunchParams = { externalTool, schoolExternalTool, contextExternalTool, @@ -207,7 +207,7 @@ describe('ToolLaunchService', () => { openNewTab: false, }); - const launchParams: IToolLaunchParams = { + const launchParams: ToolLaunchParams = { externalTool, schoolExternalTool, contextExternalTool, diff --git a/apps/server/src/modules/tool/tool-launch/service/tool-launch.service.ts b/apps/server/src/modules/tool/tool-launch/service/tool-launch.service.ts index 5b090f9778a..4ddbd55c3aa 100644 --- a/apps/server/src/modules/tool/tool-launch/service/tool-launch.service.ts +++ b/apps/server/src/modules/tool/tool-launch/service/tool-launch.service.ts @@ -11,15 +11,15 @@ import { ToolLaunchMapper } from '../mapper'; import { ToolLaunchData, ToolLaunchRequest } from '../types'; import { BasicToolLaunchStrategy, - IToolLaunchStrategy, Lti11ToolLaunchStrategy, OAuth2ToolLaunchStrategy, + ToolLaunchStrategy, } from './launch-strategy'; import { ToolVersionService } from '../../context-external-tool/service/tool-version-service'; @Injectable() export class ToolLaunchService { - private strategies: Map; + private strategies: Map; constructor( private readonly schoolExternalToolService: SchoolExternalToolService, @@ -37,7 +37,7 @@ export class ToolLaunchService { generateLaunchRequest(toolLaunchData: ToolLaunchData): ToolLaunchRequest { const toolConfigType: ToolConfigType = ToolLaunchMapper.mapToToolConfigType(toolLaunchData.type); - const strategy: IToolLaunchStrategy | undefined = this.strategies.get(toolConfigType); + const strategy: ToolLaunchStrategy | undefined = this.strategies.get(toolConfigType); if (!strategy) { throw new InternalServerErrorException('Unknown tool launch data type'); @@ -55,7 +55,7 @@ export class ToolLaunchService { await this.isToolStatusLatestOrThrow(userId, externalTool, schoolExternalTool, contextExternalTool); - const strategy: IToolLaunchStrategy | undefined = this.strategies.get(externalTool.config.type); + const strategy: ToolLaunchStrategy | undefined = this.strategies.get(externalTool.config.type); if (!strategy) { throw new InternalServerErrorException('Unknown tool config type'); diff --git a/apps/server/src/modules/user-import/controller/api-test/import-user.api.spec.ts b/apps/server/src/modules/user-import/controller/api-test/import-user.api.spec.ts index 29b7796b40d..51bea6b1224 100644 --- a/apps/server/src/modules/user-import/controller/api-test/import-user.api.spec.ts +++ b/apps/server/src/modules/user-import/controller/api-test/import-user.api.spec.ts @@ -1,5 +1,24 @@ import { Configuration } from '@hpi-schul-cloud/commons'; import { EntityManager, ObjectId } from '@mikro-orm/mongodb'; +import { ICurrentUser } from '@modules/authentication'; +import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; +import { ServerTestModule } from '@modules/server/server.module'; +import { + FilterImportUserParams, + FilterMatchType, + FilterRoleType, + FilterUserParams, + ImportUserListResponse, + ImportUserResponse, + ImportUserSortOrder, + MatchType, + SortImportUserParams, + UpdateFlagParams, + UpdateMatchParams, + UserMatchListResponse, + UserMatchResponse, + UserRole, +} from '@modules/user-import/controller/dto'; import { ExecutionContext, INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { PaginationParams } from '@shared/controller'; @@ -14,7 +33,6 @@ import { SystemEntity, User, } from '@shared/domain'; -import { ICurrentUser } from '@modules/authentication'; import { cleanupCollections, importUserFactory, @@ -24,24 +42,6 @@ import { systemFactory, userFactory, } from '@shared/testing'; -import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; -import { ServerTestModule } from '@modules/server/server.module'; -import { - FilterImportUserParams, - FilterMatchType, - FilterRoleType, - FilterUserParams, - ImportUserListResponse, - ImportUserResponse, - ImportUserSortOrder, - MatchType, - SortImportUserParams, - UpdateFlagParams, - UpdateMatchParams, - UserMatchListResponse, - UserMatchResponse, - UserRole, -} from '@modules/user-import/controller/dto'; import { Request } from 'express'; import request from 'supertest'; diff --git a/apps/server/src/modules/user-import/controller/import-user.controller.ts b/apps/server/src/modules/user-import/controller/import-user.controller.ts index c4368b5ea3f..bc29f09d6c9 100644 --- a/apps/server/src/modules/user-import/controller/import-user.controller.ts +++ b/apps/server/src/modules/user-import/controller/import-user.controller.ts @@ -1,8 +1,8 @@ +import { Authenticate, CurrentUser, ICurrentUser } from '@modules/authentication'; import { Body, Controller, Delete, Get, Param, Patch, Post, Query } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; import { PaginationParams } from '@shared/controller'; import { IFindOptions, ImportUser, User } from '@shared/domain'; -import { Authenticate, CurrentUser, ICurrentUser } from '@modules/authentication'; import { ImportUserMapper } from '../mapper/import-user.mapper'; import { UserMatchMapper } from '../mapper/user-match.mapper'; import { UserImportUc } from '../uc/user-import.uc'; diff --git a/apps/server/src/modules/user-import/mapper/import-user.mapper.ts b/apps/server/src/modules/user-import/mapper/import-user.mapper.ts index 6b19653f4de..e15b3f36b60 100644 --- a/apps/server/src/modules/user-import/mapper/import-user.mapper.ts +++ b/apps/server/src/modules/user-import/mapper/import-user.mapper.ts @@ -1,9 +1,9 @@ import { BadRequestException } from '@nestjs/common'; import { StringValidator } from '@shared/common'; -import { ImportUser, IImportUserScope, SortOrderMap } from '@shared/domain'; +import { IImportUserScope, ImportUser, SortOrderMap } from '@shared/domain'; import { - ImportUserResponse, FilterImportUserParams, + ImportUserResponse, ImportUserSortOrder, SortImportUserParams, } from '../controller/dto'; diff --git a/apps/server/src/modules/user-import/mapper/user-match.mapper.ts b/apps/server/src/modules/user-import/mapper/user-match.mapper.ts index 5d99bc1d860..8a826a613af 100644 --- a/apps/server/src/modules/user-import/mapper/user-match.mapper.ts +++ b/apps/server/src/modules/user-import/mapper/user-match.mapper.ts @@ -1,12 +1,12 @@ import { StringValidator } from '@shared/common'; -import { INameMatch, MatchCreator, User } from '@shared/domain'; -import { UserRole, UserMatchResponse } from '../controller/dto'; +import { MatchCreator, NameMatch, User } from '@shared/domain'; +import { UserMatchResponse, UserRole } from '../controller/dto'; import { FilterUserParams } from '../controller/dto/filter-user.params'; import { ImportUserMatchMapper } from './match.mapper'; export class UserMatchMapper { - static mapToDomain(query: FilterUserParams): INameMatch { - const scope: INameMatch = {}; + static mapToDomain(query: FilterUserParams): NameMatch { + const scope: NameMatch = {}; if (query.name) { if (StringValidator.isNotEmptyString(query.name, true)) { scope.name = query.name; diff --git a/apps/server/src/modules/user-import/uc/user-import.uc.ts b/apps/server/src/modules/user-import/uc/user-import.uc.ts index 2dda936b1fb..a9290bb5e42 100644 --- a/apps/server/src/modules/user-import/uc/user-import.uc.ts +++ b/apps/server/src/modules/user-import/uc/user-import.uc.ts @@ -1,4 +1,8 @@ import { Configuration } from '@hpi-schul-cloud/commons'; +import { AccountService } from '@modules/account/services/account.service'; +import { AccountDto } from '@modules/account/services/dto/account.dto'; +import { AuthorizationService } from '@modules/authorization'; +import { LegacySchoolService } from '@modules/legacy-school'; import { BadRequestException, ForbiddenException, Injectable, InternalServerErrorException } from '@nestjs/common'; import { UserAlreadyAssignedToImportUserError } from '@shared/common'; import { @@ -8,10 +12,10 @@ import { IFindOptions, IImportUserScope, ImportUser, - INameMatch, LegacySchoolDo, MatchCreator, MatchCreatorScope, + NameMatch, Permission, SchoolFeatures, SystemEntity, @@ -19,10 +23,6 @@ import { } from '@shared/domain'; import { ImportUserRepo, SystemRepo, UserRepo } from '@shared/repo'; import { Logger } from '@src/core/logger'; -import { AccountService } from '@modules/account/services/account.service'; -import { AccountDto } from '@modules/account/services/dto/account.dto'; -import { AuthorizationService } from '@modules/authorization'; -import { LegacySchoolService } from '@modules/legacy-school'; import { AccountSaveDto } from '../../account/services/dto'; import { MigrationMayBeCompleted, @@ -164,7 +164,7 @@ export class UserImportUc { */ async findAllUnmatchedUsers( currentUserId: EntityId, - query: INameMatch, + query: NameMatch, options?: IFindOptions ): Promise> { const currentUser = await this.getCurrentUser(currentUserId, Permission.SCHOOL_IMPORT_USERS_VIEW); diff --git a/apps/server/src/modules/user/controller/api-test/user-language.api.spec.ts b/apps/server/src/modules/user/controller/api-test/user-language.api.spec.ts index 28bc7c5d14e..50b1c08f8d9 100644 --- a/apps/server/src/modules/user/controller/api-test/user-language.api.spec.ts +++ b/apps/server/src/modules/user/controller/api-test/user-language.api.spec.ts @@ -2,12 +2,12 @@ import { EntityManager } from '@mikro-orm/mongodb'; import { ExecutionContext, INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; -import { ApiValidationError } from '@shared/common'; -import { LanguageType, User } from '@shared/domain'; -import { cleanupCollections, mapUserToCurrentUser, roleFactory, userFactory } from '@shared/testing'; import { ICurrentUser } from '@modules/authentication'; import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; import { ServerTestModule } from '@modules/server/server.module'; +import { ApiValidationError } from '@shared/common'; +import { LanguageType, User } from '@shared/domain'; +import { cleanupCollections, mapUserToCurrentUser, roleFactory, userFactory } from '@shared/testing'; import { Request } from 'express'; import request from 'supertest'; diff --git a/apps/server/src/modules/user/controller/api-test/user-me.api.spec.ts b/apps/server/src/modules/user/controller/api-test/user-me.api.spec.ts index 370de7e4fa6..c77f23f2e59 100644 --- a/apps/server/src/modules/user/controller/api-test/user-me.api.spec.ts +++ b/apps/server/src/modules/user/controller/api-test/user-me.api.spec.ts @@ -5,13 +5,13 @@ import { Test, TestingModule } from '@nestjs/testing'; import { Request } from 'express'; import request from 'supertest'; -import { ApiValidationError } from '@shared/common'; -import { LanguageType } from '@shared/domain'; import { ICurrentUser } from '@modules/authentication'; -import { cleanupCollections, mapUserToCurrentUser, roleFactory, userFactory } from '@shared/testing'; import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; import { ServerTestModule } from '@modules/server/server.module'; import { ResolvedUserResponse } from '@modules/user/controller/dto'; +import { ApiValidationError } from '@shared/common'; +import { LanguageType } from '@shared/domain'; +import { cleanupCollections, mapUserToCurrentUser, roleFactory, userFactory } from '@shared/testing'; const baseRouteName = '/user/me'; diff --git a/apps/server/src/modules/user/controller/user.controller.ts b/apps/server/src/modules/user/controller/user.controller.ts index 16729c2667b..1d3739b3144 100644 --- a/apps/server/src/modules/user/controller/user.controller.ts +++ b/apps/server/src/modules/user/controller/user.controller.ts @@ -1,6 +1,6 @@ +import { Authenticate, CurrentUser, ICurrentUser } from '@modules/authentication'; import { Body, Controller, Get, Patch } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; -import { Authenticate, CurrentUser, ICurrentUser } from '@modules/authentication'; import { ResolvedUserMapper } from '../mapper'; import { UserUc } from '../uc'; import { ChangeLanguageParams, ResolvedUserResponse, SuccessfulResponse } from './dto'; diff --git a/apps/server/src/modules/user/interfaces/user-config.ts b/apps/server/src/modules/user/interfaces/user-config.ts index cd8576522e1..95935d4866c 100644 --- a/apps/server/src/modules/user/interfaces/user-config.ts +++ b/apps/server/src/modules/user/interfaces/user-config.ts @@ -1,3 +1,3 @@ -export interface IUserConfig { +export interface UserConfig { AVAILABLE_LANGUAGES: string[]; } diff --git a/apps/server/src/modules/user/service/user.service.ts b/apps/server/src/modules/user/service/user.service.ts index 2cc95991f96..6ef014f8696 100644 --- a/apps/server/src/modules/user/service/user.service.ts +++ b/apps/server/src/modules/user/service/user.service.ts @@ -1,6 +1,7 @@ import { AccountService } from '@modules/account'; import { AccountDto } from '@modules/account/services/dto'; // invalid import +import { OauthCurrentUser } from '@modules/authentication/interface'; import { CurrentUserMapper } from '@modules/authentication/mapper'; import { RoleDto } from '@modules/role/service/dto/role.dto'; import { RoleService } from '@modules/role/service/role.service'; @@ -10,8 +11,7 @@ import { EntityId, IFindOptions, LanguageType, User } from '@shared/domain'; import { Page, RoleReference, UserDO } from '@shared/domain/domainobject'; import { UserRepo } from '@shared/repo'; import { UserDORepo } from '@shared/repo/user/user-do.repo'; -import { OauthCurrentUser } from '@modules/authentication/interface'; -import { IUserConfig } from '../interfaces'; +import { UserConfig } from '../interfaces'; import { UserMapper } from '../mapper/user.mapper'; import { UserDto } from '../uc/dto/user.dto'; import { UserQuery } from './user-query.type'; @@ -21,7 +21,7 @@ export class UserService { constructor( private readonly userRepo: UserRepo, private readonly userDORepo: UserDORepo, - private readonly configService: ConfigService, + private readonly configService: ConfigService, private readonly roleService: RoleService, private readonly accountService: AccountService ) {} diff --git a/apps/server/src/modules/user/uc/user.uc.ts b/apps/server/src/modules/user/uc/user.uc.ts index 0996fa6bd9c..2f2661eb0a2 100644 --- a/apps/server/src/modules/user/uc/user.uc.ts +++ b/apps/server/src/modules/user/uc/user.uc.ts @@ -3,11 +3,11 @@ import { ConfigService } from '@nestjs/config'; import { EntityId, LanguageType, User } from '@shared/domain'; import { UserRepo } from '@shared/repo'; import { ChangeLanguageParams } from '../controller/dto'; -import { IUserConfig } from '../interfaces'; +import { UserConfig } from '../interfaces'; @Injectable() export class UserUc { - constructor(private readonly userRepo: UserRepo, private readonly configService: ConfigService) {} + constructor(private readonly userRepo: UserRepo, private readonly configService: ConfigService) {} async me(userId: EntityId): Promise<[User, string[]]> { const user = await this.userRepo.findById(userId, true); diff --git a/apps/server/src/modules/video-conference/bbb/bbb.service.ts b/apps/server/src/modules/video-conference/bbb/bbb.service.ts index 54b9ef4549b..2fcc3db9981 100644 --- a/apps/server/src/modules/video-conference/bbb/bbb.service.ts +++ b/apps/server/src/modules/video-conference/bbb/bbb.service.ts @@ -4,7 +4,7 @@ import { ConverterUtil } from '@shared/common/utils'; import { ErrorUtils } from '@src/core/error/utils'; import { AxiosResponse } from 'axios'; import crypto from 'crypto'; -import { Observable, firstValueFrom } from 'rxjs'; +import { firstValueFrom, Observable } from 'rxjs'; import { URL, URLSearchParams } from 'url'; import { BbbSettings, IBbbSettings } from './bbb-settings.interface'; import { BBBBaseMeetingConfig, BBBCreateConfig, BBBJoinConfig } from './request'; diff --git a/apps/server/src/modules/video-conference/controller/video-conference-deprecated.controller.spec.ts b/apps/server/src/modules/video-conference/controller/video-conference-deprecated.controller.spec.ts index 6d78b513d75..01a62786582 100644 --- a/apps/server/src/modules/video-conference/controller/video-conference-deprecated.controller.spec.ts +++ b/apps/server/src/modules/video-conference/controller/video-conference-deprecated.controller.spec.ts @@ -1,8 +1,8 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { ObjectId } from '@mikro-orm/mongodb'; +import { ICurrentUser } from '@modules/authentication'; import { Test, TestingModule } from '@nestjs/testing'; import { VideoConferenceScope } from '@shared/domain/interface'; -import { ICurrentUser } from '@modules/authentication'; import { BBBBaseResponse, BBBCreateResponse } from '../bbb'; import { defaultVideoConferenceOptions } from '../interface'; import { VideoConferenceDeprecatedUc } from '../uc'; diff --git a/apps/server/src/modules/video-conference/controller/video-conference-deprecated.controller.ts b/apps/server/src/modules/video-conference/controller/video-conference-deprecated.controller.ts index 8dbec66972e..d32273b98f7 100644 --- a/apps/server/src/modules/video-conference/controller/video-conference-deprecated.controller.ts +++ b/apps/server/src/modules/video-conference/controller/video-conference-deprecated.controller.ts @@ -1,3 +1,4 @@ +import { Authenticate, CurrentUser, ICurrentUser } from '@modules/authentication'; import { BadRequestException, Body, @@ -11,7 +12,6 @@ import { } from '@nestjs/common'; import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; import { VideoConferenceScope } from '@shared/domain/interface'; -import { Authenticate, CurrentUser, ICurrentUser } from '@modules/authentication'; import { BBBBaseResponse } from '../bbb'; import { defaultVideoConferenceOptions } from '../interface'; import { VideoConferenceResponseDeprecatedMapper } from '../mapper/vc-deprecated-response.mapper'; diff --git a/apps/server/src/modules/video-conference/controller/video-conference.controller.ts b/apps/server/src/modules/video-conference/controller/video-conference.controller.ts index 442e9132758..b55eac17f64 100644 --- a/apps/server/src/modules/video-conference/controller/video-conference.controller.ts +++ b/apps/server/src/modules/video-conference/controller/video-conference.controller.ts @@ -1,6 +1,6 @@ +import { Authenticate, CurrentUser, ICurrentUser } from '@modules/authentication'; import { Body, Controller, Get, HttpStatus, Param, Put, Req } from '@nestjs/common'; import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; -import { Authenticate, CurrentUser, ICurrentUser } from '@modules/authentication'; import { Request } from 'express'; import { InvalidOriginForLogoutUrlLoggableException } from '../error'; import { VideoConferenceOptions } from '../interface'; diff --git a/apps/server/src/modules/video-conference/service/video-conference.service.spec.ts b/apps/server/src/modules/video-conference/service/video-conference.service.spec.ts index 28711a06c1a..2a5d223b553 100644 --- a/apps/server/src/modules/video-conference/service/video-conference.service.spec.ts +++ b/apps/server/src/modules/video-conference/service/video-conference.service.spec.ts @@ -1,6 +1,12 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; -import { Test, TestingModule } from '@nestjs/testing'; +import { CalendarEventDto, CalendarService } from '@infra/calendar'; +import { AuthorizationContextBuilder, AuthorizationService } from '@modules/authorization'; +import { CourseService } from '@modules/learnroom/service'; +import { LegacySchoolService } from '@modules/legacy-school'; +import { UserService } from '@modules/user'; import { BadRequestException, ForbiddenException } from '@nestjs/common'; +import { NotFoundException } from '@nestjs/common/exceptions/not-found.exception'; +import { Test, TestingModule } from '@nestjs/testing'; import { Course, EntityId, @@ -12,23 +18,17 @@ import { VideoConferenceDO, VideoConferenceScope, } from '@shared/domain'; -import { CalendarEventDto, CalendarService } from '@infra/calendar'; import { TeamsRepo, VideoConferenceRepo } from '@shared/repo'; -import { AuthorizationContextBuilder, AuthorizationService } from '@modules/authorization'; -import { LegacySchoolService } from '@modules/legacy-school'; -import { UserService } from '@modules/user'; import { courseFactory, roleFactory, setupEntities, userDoFactory, userFactory } from '@shared/testing'; -import { videoConferenceDOFactory } from '@shared/testing/factory/video-conference.do.factory'; -import { ObjectId } from 'bson'; import { teamFactory } from '@shared/testing/factory/team.factory'; -import { NotFoundException } from '@nestjs/common/exceptions/not-found.exception'; import { teamUserFactory } from '@shared/testing/factory/teamuser.factory'; -import { CourseService } from '@modules/learnroom/service'; -import { VideoConferenceService } from './video-conference.service'; -import { ErrorStatus } from '../error'; +import { videoConferenceDOFactory } from '@shared/testing/factory/video-conference.do.factory'; +import { ObjectId } from 'bson'; import { BBBRole } from '../bbb'; -import { IScopeInfo, ScopeRef, VideoConferenceState } from '../uc/dto'; +import { ErrorStatus } from '../error'; import { IVideoConferenceSettings, VideoConferenceOptions, VideoConferenceSettings } from '../interface'; +import { ScopeInfo, ScopeRef, VideoConferenceState } from '../uc/dto'; +import { VideoConferenceService } from './video-conference.service'; describe('VideoConferenceService', () => { let service: DeepMocked; @@ -561,7 +561,7 @@ describe('VideoConferenceService', () => { course.id = scopeId; courseService.findById.mockResolvedValue(course); - const result: IScopeInfo = await service.getScopeInfo(userId, scopeId, conferenceScope); + const result: ScopeInfo = await service.getScopeInfo(userId, scopeId, conferenceScope); expect(result).toEqual({ scopeId, @@ -580,7 +580,7 @@ describe('VideoConferenceService', () => { const event: CalendarEventDto = { title: 'Event', teamId }; calendarService.findEvent.mockResolvedValue(event); - const result: IScopeInfo = await service.getScopeInfo(userId, scopeId, VideoConferenceScope.EVENT); + const result: ScopeInfo = await service.getScopeInfo(userId, scopeId, VideoConferenceScope.EVENT); expect(result).toEqual({ scopeId: teamId, diff --git a/apps/server/src/modules/video-conference/service/video-conference.service.ts b/apps/server/src/modules/video-conference/service/video-conference.service.ts index 401cc0a0015..706a00e6e3b 100644 --- a/apps/server/src/modules/video-conference/service/video-conference.service.ts +++ b/apps/server/src/modules/video-conference/service/video-conference.service.ts @@ -1,3 +1,8 @@ +import { CalendarEventDto, CalendarService } from '@infra/calendar'; +import { AuthorizationContextBuilder, AuthorizationService } from '@modules/authorization'; +import { CourseService } from '@modules/learnroom'; +import { LegacySchoolService } from '@modules/legacy-school'; +import { UserService } from '@modules/user'; import { BadRequestException, ForbiddenException, Inject, Injectable } from '@nestjs/common'; import { Course, @@ -14,16 +19,11 @@ import { VideoConferenceOptionsDO, VideoConferenceScope, } from '@shared/domain'; -import { CalendarEventDto, CalendarService } from '@infra/calendar'; import { TeamsRepo, VideoConferenceRepo } from '@shared/repo'; -import { AuthorizationContextBuilder, AuthorizationService } from '@modules/authorization'; -import { CourseService } from '@modules/learnroom'; -import { LegacySchoolService } from '@modules/legacy-school'; -import { UserService } from '@modules/user'; import { BBBRole } from '../bbb'; import { ErrorStatus } from '../error'; import { IVideoConferenceSettings, VideoConferenceOptions, VideoConferenceSettings } from '../interface'; -import { IScopeInfo, VideoConferenceState } from '../uc/dto'; +import { ScopeInfo, VideoConferenceState } from '../uc/dto'; @Injectable() export class VideoConferenceService { @@ -165,7 +165,7 @@ export class VideoConferenceService { return text.replace(/[^\dA-Za-zÀ-ÖØ-öø-ÿ.\-=_`´ ]/g, ''); } - async getScopeInfo(userId: EntityId, scopeId: string, scope: VideoConferenceScope): Promise { + async getScopeInfo(userId: EntityId, scopeId: string, scope: VideoConferenceScope): Promise { switch (scope) { case VideoConferenceScope.COURSE: { const course: Course = await this.courseService.findById(scopeId); @@ -197,7 +197,7 @@ export class VideoConferenceService { scopeId: EntityId, scope: VideoConferenceScope ): Promise<{ role: BBBRole; isGuest: boolean }> { - const scopeInfo: IScopeInfo = await this.getScopeInfo(userId, scopeId, scope); + const scopeInfo: ScopeInfo = await this.getScopeInfo(userId, scopeId, scope); const role: BBBRole = await this.determineBbbRole(userId, scopeInfo.scopeId, scope); diff --git a/apps/server/src/modules/video-conference/uc/dto/scope-info.interface.ts b/apps/server/src/modules/video-conference/uc/dto/scope-info.interface.ts index 1f304e3a235..2469f49ad29 100644 --- a/apps/server/src/modules/video-conference/uc/dto/scope-info.interface.ts +++ b/apps/server/src/modules/video-conference/uc/dto/scope-info.interface.ts @@ -1,6 +1,6 @@ import { EntityId } from '@shared/domain'; -export interface IScopeInfo { +export interface ScopeInfo { scopeId: EntityId; scopeName: string; diff --git a/apps/server/src/modules/video-conference/uc/video-conference-create.uc.spec.ts b/apps/server/src/modules/video-conference/uc/video-conference-create.uc.spec.ts index 4c38bf18467..1361cb053ad 100644 --- a/apps/server/src/modules/video-conference/uc/video-conference-create.uc.spec.ts +++ b/apps/server/src/modules/video-conference/uc/video-conference-create.uc.spec.ts @@ -1,16 +1,16 @@ -import { Test, TestingModule } from '@nestjs/testing'; import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { UserService } from '@modules/user'; -import { userDoFactory } from '@shared/testing'; +import { ForbiddenException } from '@nestjs/common'; +import { Test, TestingModule } from '@nestjs/testing'; import { UserDO, VideoConferenceScope } from '@shared/domain'; +import { userDoFactory } from '@shared/testing'; import { ObjectId } from 'bson'; -import { ForbiddenException } from '@nestjs/common'; -import { VideoConferenceCreateUc } from './video-conference-create.uc'; -import { BBBService, VideoConferenceService } from '../service'; -import { VideoConferenceOptions } from '../interface'; import { BBBCreateResponse, BBBMeetingInfoResponse, BBBResponse, BBBRole, BBBStatus } from '../bbb'; -import { IScopeInfo, ScopeRef } from './dto'; import { ErrorStatus } from '../error/error-status.enum'; +import { VideoConferenceOptions } from '../interface'; +import { BBBService, VideoConferenceService } from '../service'; +import { ScopeInfo, ScopeRef } from './dto'; +import { VideoConferenceCreateUc } from './video-conference-create.uc'; describe('VideoConferenceCreateUc', () => { let module: TestingModule; @@ -87,7 +87,7 @@ describe('VideoConferenceCreateUc', () => { moderatorMustApproveJoinRequests: true, }; - const scopeInfo: IScopeInfo = { + const scopeInfo: ScopeInfo = { scopeId: scope.id, scopeName: 'scopeName', title: 'title', @@ -162,7 +162,7 @@ describe('VideoConferenceCreateUc', () => { moderatorMustApproveJoinRequests: true, }; - const scopeInfo: IScopeInfo = { + const scopeInfo: ScopeInfo = { scopeId: scope.id, scopeName: 'scopeName', title: 'title', diff --git a/apps/server/src/modules/video-conference/uc/video-conference-create.uc.ts b/apps/server/src/modules/video-conference/uc/video-conference-create.uc.ts index 940113276a2..d9ba4e0d61c 100644 --- a/apps/server/src/modules/video-conference/uc/video-conference-create.uc.ts +++ b/apps/server/src/modules/video-conference/uc/video-conference-create.uc.ts @@ -1,6 +1,6 @@ +import { UserService } from '@modules/user'; import { ForbiddenException, Injectable } from '@nestjs/common'; import { EntityId, UserDO } from '@shared/domain'; -import { UserService } from '@modules/user'; import { BBBBaseMeetingConfig, BBBCreateConfigBuilder, @@ -13,7 +13,7 @@ import { import { ErrorStatus } from '../error/error-status.enum'; import { VideoConferenceOptions } from '../interface'; import { VideoConferenceService } from '../service'; -import { IScopeInfo, ScopeRef } from './dto'; +import { ScopeInfo, ScopeRef } from './dto'; @Injectable() export class VideoConferenceCreateUc { @@ -48,7 +48,7 @@ export class VideoConferenceCreateUc { await this.verifyFeaturesEnabled(user.schoolId); - const scopeInfo: IScopeInfo = await this.videoConferenceService.getScopeInfo(currentUserId, scope.id, scope.scope); + const scopeInfo: ScopeInfo = await this.videoConferenceService.getScopeInfo(currentUserId, scope.id, scope.scope); const bbbRole: BBBRole = await this.videoConferenceService.determineBbbRole( currentUserId, @@ -67,7 +67,7 @@ export class VideoConferenceCreateUc { private prepareBBBCreateConfigBuilder( scope: ScopeRef, options: VideoConferenceOptions, - scopeInfo: IScopeInfo + scopeInfo: ScopeInfo ): BBBCreateConfigBuilder { const configBuilder: BBBCreateConfigBuilder = new BBBCreateConfigBuilder({ name: this.videoConferenceService.sanitizeString(scopeInfo.title), diff --git a/apps/server/src/modules/video-conference/uc/video-conference-deprecated.uc.spec.ts b/apps/server/src/modules/video-conference/uc/video-conference-deprecated.uc.spec.ts index bf1fd3e1394..a8caf8f2dc9 100644 --- a/apps/server/src/modules/video-conference/uc/video-conference-deprecated.uc.spec.ts +++ b/apps/server/src/modules/video-conference/uc/video-conference-deprecated.uc.spec.ts @@ -1,5 +1,12 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { Configuration } from '@hpi-schul-cloud/commons/lib'; +import { CalendarService } from '@infra/calendar'; +import { CalendarEventDto } from '@infra/calendar/dto/calendar-event.dto'; +import { ICurrentUser } from '@modules/authentication'; +import { AuthorizationReferenceService } from '@modules/authorization/domain'; +import { CourseService } from '@modules/learnroom/service'; +import { LegacySchoolService } from '@modules/legacy-school'; +import { UserService } from '@modules/user'; import { BadRequestException, ForbiddenException, InternalServerErrorException } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { @@ -14,18 +21,9 @@ import { VideoConferenceDO, } from '@shared/domain'; import { VideoConferenceScope } from '@shared/domain/interface'; -import { CalendarService } from '@infra/calendar'; -import { CalendarEventDto } from '@infra/calendar/dto/calendar-event.dto'; import { TeamsRepo, VideoConferenceRepo } from '@shared/repo'; import { roleFactory, setupEntities, userDoFactory } from '@shared/testing'; import { teamFactory } from '@shared/testing/factory/team.factory'; -import { AuthorizationReferenceService } from '@modules/authorization/domain'; -import { ICurrentUser } from '@modules/authentication'; -import { CourseService } from '@modules/learnroom/service'; -import { LegacySchoolService } from '@modules/legacy-school'; -import { UserService } from '@modules/user'; -import { IScopeInfo, VideoConference, VideoConferenceJoin, VideoConferenceState } from './dto'; -import { VideoConferenceDeprecatedUc } from './video-conference-deprecated.uc'; import { BBBBaseMeetingConfig, BBBBaseResponse, @@ -39,11 +37,13 @@ import { BBBStatus, GuestPolicy, } from '../bbb'; -import { defaultVideoConferenceOptions, VideoConferenceOptions } from '../interface'; import { ErrorStatus } from '../error/error-status.enum'; +import { defaultVideoConferenceOptions, VideoConferenceOptions } from '../interface'; +import { ScopeInfo, VideoConference, VideoConferenceJoin, VideoConferenceState } from './dto'; +import { VideoConferenceDeprecatedUc } from './video-conference-deprecated.uc'; class VideoConferenceDeprecatedUcSpec extends VideoConferenceDeprecatedUc { - async getScopeInfoSpec(userId: EntityId, conferenceScope: VideoConferenceScope, refId: string): Promise { + async getScopeInfoSpec(userId: EntityId, conferenceScope: VideoConferenceScope, refId: string): Promise { return this.getScopeInfo(userId, conferenceScope, refId); } @@ -198,7 +198,7 @@ describe('VideoConferenceUc', () => { describe('getScopeInfo', () => { it('should return scope info for courses', async () => { // Act - const scopeInfo: IScopeInfo = await useCase.getScopeInfoSpec('userId', VideoConferenceScope.COURSE, course.id); + const scopeInfo: ScopeInfo = await useCase.getScopeInfoSpec('userId', VideoConferenceScope.COURSE, course.id); // Assert expect(scopeInfo.scopeId).toEqual(course.id); @@ -209,7 +209,7 @@ describe('VideoConferenceUc', () => { it('should return scope info for teams', async () => { // Act - const scopeInfo: IScopeInfo = await useCase.getScopeInfoSpec('userId', VideoConferenceScope.EVENT, eventId); + const scopeInfo: ScopeInfo = await useCase.getScopeInfoSpec('userId', VideoConferenceScope.EVENT, eventId); // Assert expect(scopeInfo.scopeId).toEqual(event.teamId); diff --git a/apps/server/src/modules/video-conference/uc/video-conference-deprecated.uc.ts b/apps/server/src/modules/video-conference/uc/video-conference-deprecated.uc.ts index 95470053e6d..14d8b594fb9 100644 --- a/apps/server/src/modules/video-conference/uc/video-conference-deprecated.uc.ts +++ b/apps/server/src/modules/video-conference/uc/video-conference-deprecated.uc.ts @@ -1,4 +1,12 @@ import { Configuration } from '@hpi-schul-cloud/commons/lib'; +import { CalendarService } from '@infra/calendar'; +import { CalendarEventDto } from '@infra/calendar/dto/calendar-event.dto'; +import { ICurrentUser } from '@modules/authentication'; +import { Action, AuthorizationContextBuilder } from '@modules/authorization'; +import { AuthorizableReferenceType, AuthorizationReferenceService } from '@modules/authorization/domain'; +import { CourseService } from '@modules/learnroom'; +import { LegacySchoolService } from '@modules/legacy-school'; +import { UserService } from '@modules/user'; import { BadRequestException, ForbiddenException, Injectable } from '@nestjs/common'; import { Course, @@ -13,16 +21,8 @@ import { VideoConferenceOptionsDO, } from '@shared/domain'; import { VideoConferenceScope } from '@shared/domain/interface'; -import { CalendarService } from '@infra/calendar'; -import { CalendarEventDto } from '@infra/calendar/dto/calendar-event.dto'; import { TeamsRepo } from '@shared/repo'; import { VideoConferenceRepo } from '@shared/repo/videoconference/video-conference.repo'; -import { ICurrentUser } from '@modules/authentication'; -import { Action, AuthorizationContextBuilder } from '@modules/authorization'; -import { AuthorizationReferenceService, AuthorizableReferenceType } from '@modules/authorization/domain'; -import { LegacySchoolService } from '@modules/legacy-school'; -import { CourseService } from '@modules/learnroom'; -import { UserService } from '@modules/user'; import { BBBBaseMeetingConfig, BBBBaseResponse, @@ -37,7 +37,7 @@ import { } from '../bbb'; import { ErrorStatus } from '../error/error-status.enum'; import { defaultVideoConferenceOptions, VideoConferenceOptions } from '../interface'; -import { IScopeInfo, VideoConference, VideoConferenceInfo, VideoConferenceJoin, VideoConferenceState } from './dto'; +import { ScopeInfo, VideoConference, VideoConferenceInfo, VideoConferenceJoin, VideoConferenceState } from './dto'; const PermissionMapping = { [BBBRole.MODERATOR]: Permission.START_MEETING, @@ -86,7 +86,7 @@ export class VideoConferenceDeprecatedUc { const { userId, schoolId } = currentUser; await this.throwOnFeaturesDisabled(schoolId); - const scopeInfo: IScopeInfo = await this.getScopeInfo(userId, conferenceScope, refId); + const scopeInfo: ScopeInfo = await this.getScopeInfo(userId, conferenceScope, refId); const bbbRole: BBBRole = await this.checkPermission(userId, conferenceScope, scopeInfo.scopeId); @@ -150,7 +150,7 @@ export class VideoConferenceDeprecatedUc { await this.throwOnFeaturesDisabled(schoolId); - const scopeInfo: IScopeInfo = await this.getScopeInfo(userId, conferenceScope, refId); + const scopeInfo: ScopeInfo = await this.getScopeInfo(userId, conferenceScope, refId); const bbbRole: BBBRole = await this.checkPermission(userId, conferenceScope, scopeInfo.scopeId); @@ -205,7 +205,7 @@ export class VideoConferenceDeprecatedUc { await this.throwOnFeaturesDisabled(schoolId); - const scopeInfo: IScopeInfo = await this.getScopeInfo(userId, conferenceScope, refId); + const scopeInfo: ScopeInfo = await this.getScopeInfo(userId, conferenceScope, refId); const bbbRole: BBBRole = await this.checkPermission(userId, conferenceScope, scopeInfo.scopeId); @@ -337,13 +337,13 @@ export class VideoConferenceDeprecatedUc { * @param {EntityId} userId * @param {VideoConferenceScope} conferenceScope * @param {EntityId} refId eventId or courseId, depending on scope. - * @returns {Promise} + * @returns {Promise} */ protected async getScopeInfo( userId: EntityId, conferenceScope: VideoConferenceScope, refId: string - ): Promise { + ): Promise { switch (conferenceScope) { case VideoConferenceScope.COURSE: { const course: Course = await this.courseService.findById(refId); diff --git a/apps/server/src/modules/video-conference/uc/video-conference-end.uc.spec.ts b/apps/server/src/modules/video-conference/uc/video-conference-end.uc.spec.ts index d1dbfc18b9d..e18c7d7dc65 100644 --- a/apps/server/src/modules/video-conference/uc/video-conference-end.uc.spec.ts +++ b/apps/server/src/modules/video-conference/uc/video-conference-end.uc.spec.ts @@ -1,15 +1,15 @@ -import { Test, TestingModule } from '@nestjs/testing'; import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { UserService } from '@modules/user'; -import { userDoFactory } from '@shared/testing'; +import { ForbiddenException } from '@nestjs/common'; +import { Test, TestingModule } from '@nestjs/testing'; import { UserDO, VideoConferenceScope } from '@shared/domain'; +import { userDoFactory } from '@shared/testing'; import { ObjectId } from 'bson'; -import { ForbiddenException } from '@nestjs/common'; -import { BBBService, VideoConferenceService } from '../service'; import { BBBBaseResponse, BBBResponse, BBBRole, BBBStatus } from '../bbb'; -import { IScopeInfo, VideoConference, VideoConferenceState } from './dto'; -import { VideoConferenceEndUc } from './video-conference-end.uc'; import { ErrorStatus } from '../error/error-status.enum'; +import { BBBService, VideoConferenceService } from '../service'; +import { ScopeInfo, VideoConference, VideoConferenceState } from './dto'; +import { VideoConferenceEndUc } from './video-conference-end.uc'; describe('VideoConferenceEndUc', () => { let module: TestingModule; @@ -57,7 +57,7 @@ describe('VideoConferenceEndUc', () => { const user: UserDO = userDoFactory.buildWithId(); const currentUserId: string = user.id as string; const scope = { scope: VideoConferenceScope.COURSE, id: new ObjectId().toHexString() }; - const scopeInfo: IScopeInfo = { + const scopeInfo: ScopeInfo = { scopeId: scope.id, scopeName: 'scopeName', title: 'title', @@ -93,7 +93,7 @@ describe('VideoConferenceEndUc', () => { const user: UserDO = userDoFactory.buildWithId(); const currentUserId: string = user.id as string; const scope = { scope: VideoConferenceScope.COURSE, id: new ObjectId().toHexString() }; - const scopeInfo: IScopeInfo = { + const scopeInfo: ScopeInfo = { scopeId: scope.id, scopeName: 'scopeName', title: 'title', diff --git a/apps/server/src/modules/video-conference/uc/video-conference-end.uc.ts b/apps/server/src/modules/video-conference/uc/video-conference-end.uc.ts index 6cb70edc176..a1648a4cba1 100644 --- a/apps/server/src/modules/video-conference/uc/video-conference-end.uc.ts +++ b/apps/server/src/modules/video-conference/uc/video-conference-end.uc.ts @@ -1,11 +1,11 @@ +import { UserService } from '@modules/user'; +import { ErrorStatus } from '@modules/video-conference/error/error-status.enum'; import { ForbiddenException, Injectable } from '@nestjs/common'; import { EntityId, UserDO } from '@shared/domain'; -import { ErrorStatus } from '@modules/video-conference/error/error-status.enum'; -import { UserService } from '@modules/user'; import { BBBBaseMeetingConfig, BBBBaseResponse, BBBResponse, BBBRole, BBBService } from '../bbb'; -import { IScopeInfo, ScopeRef, VideoConference, VideoConferenceState } from './dto'; -import { VideoConferenceService } from '../service'; import { PermissionMapping } from '../mapper/video-conference.mapper'; +import { VideoConferenceService } from '../service'; +import { ScopeInfo, ScopeRef, VideoConference, VideoConferenceState } from './dto'; @Injectable() export class VideoConferenceEndUc { @@ -27,7 +27,7 @@ export class VideoConferenceEndUc { await this.videoConferenceService.throwOnFeaturesDisabled(user.schoolId); - const scopeInfo: IScopeInfo = await this.videoConferenceService.getScopeInfo(userId, scope.id, scope.scope); + const scopeInfo: ScopeInfo = await this.videoConferenceService.getScopeInfo(userId, scope.id, scope.scope); const bbbRole: BBBRole = await this.videoConferenceService.determineBbbRole(userId, scopeInfo.scopeId, scope.scope); diff --git a/apps/server/src/modules/video-conference/uc/video-conference-info.uc.spec.ts b/apps/server/src/modules/video-conference/uc/video-conference-info.uc.spec.ts index ebc8daa4084..3aac5d6e8ff 100644 --- a/apps/server/src/modules/video-conference/uc/video-conference-info.uc.spec.ts +++ b/apps/server/src/modules/video-conference/uc/video-conference-info.uc.spec.ts @@ -1,17 +1,17 @@ -import { Test, TestingModule } from '@nestjs/testing'; import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { UserService } from '@modules/user'; -import { userDoFactory } from '@shared/testing'; +import { ForbiddenException } from '@nestjs/common'; +import { Test, TestingModule } from '@nestjs/testing'; import { Permission, UserDO, VideoConferenceDO, VideoConferenceScope } from '@shared/domain'; -import { ObjectId } from 'bson'; +import { userDoFactory } from '@shared/testing'; import { videoConferenceDOFactory } from '@shared/testing/factory/video-conference.do.factory'; -import { ForbiddenException } from '@nestjs/common'; -import { BBBService, VideoConferenceService } from '../service'; +import { ObjectId } from 'bson'; import { BBBMeetingInfoResponse, BBBResponse, BBBRole, BBBStatus } from '../bbb'; -import { IScopeInfo, VideoConferenceInfo, VideoConferenceState } from './dto'; -import { VideoConferenceInfoUc } from './video-conference-info.uc'; -import { defaultVideoConferenceOptions, VideoConferenceOptions } from '../interface'; import { ErrorStatus } from '../error/error-status.enum'; +import { defaultVideoConferenceOptions, VideoConferenceOptions } from '../interface'; +import { BBBService, VideoConferenceService } from '../service'; +import { ScopeInfo, VideoConferenceInfo, VideoConferenceState } from './dto'; +import { VideoConferenceInfoUc } from './video-conference-info.uc'; describe('VideoConferenceInfoUc', () => { let module: TestingModule; @@ -76,7 +76,7 @@ describe('VideoConferenceInfoUc', () => { const user: UserDO = userDoFactory.buildWithId(); const currentUserId: string = user.id as string; const scope = { scope: VideoConferenceScope.COURSE, id: new ObjectId().toHexString() }; - const scopeInfo: IScopeInfo = { + const scopeInfo: ScopeInfo = { scopeId: scope.id, scopeName: 'scopeName', title: 'title', @@ -117,7 +117,7 @@ describe('VideoConferenceInfoUc', () => { const user: UserDO = userDoFactory.buildWithId(); const currentUserId: string = user.id as string; const scope = { scope: VideoConferenceScope.COURSE, id: new ObjectId().toHexString() }; - const scopeInfo: IScopeInfo = { + const scopeInfo: ScopeInfo = { scopeId: scope.id, scopeName: 'scopeName', title: 'title', @@ -235,7 +235,7 @@ describe('VideoConferenceInfoUc', () => { const user: UserDO = userDoFactory.buildWithId(); const currentUserId: string = user.id as string; const scope = { scope: VideoConferenceScope.COURSE, id: new ObjectId().toHexString() }; - const scopeInfo: IScopeInfo = { + const scopeInfo: ScopeInfo = { scopeId: scope.id, scopeName: 'scopeName', title: 'title', @@ -283,7 +283,7 @@ describe('VideoConferenceInfoUc', () => { const user: UserDO = userDoFactory.buildWithId(); const currentUserId: string = user.id as string; const scope = { scope: VideoConferenceScope.COURSE, id: new ObjectId().toHexString() }; - const scopeInfo: IScopeInfo = { + const scopeInfo: ScopeInfo = { scopeId: scope.id, scopeName: 'scopeName', title: 'title', @@ -327,7 +327,7 @@ describe('VideoConferenceInfoUc', () => { const user: UserDO = userDoFactory.buildWithId(); const currentUserId: string = user.id as string; const scope = { scope: VideoConferenceScope.COURSE, id: new ObjectId().toHexString() }; - const scopeInfo: IScopeInfo = { + const scopeInfo: ScopeInfo = { scopeId: scope.id, scopeName: 'scopeName', title: 'title', diff --git a/apps/server/src/modules/video-conference/uc/video-conference-info.uc.ts b/apps/server/src/modules/video-conference/uc/video-conference-info.uc.ts index 1c3736d1d01..7e39db7bb7b 100644 --- a/apps/server/src/modules/video-conference/uc/video-conference-info.uc.ts +++ b/apps/server/src/modules/video-conference/uc/video-conference-info.uc.ts @@ -1,12 +1,12 @@ +import { UserService } from '@modules/user'; +import { ErrorStatus } from '@modules/video-conference/error/error-status.enum'; import { ForbiddenException, Injectable } from '@nestjs/common'; import { EntityId, UserDO, VideoConferenceDO, VideoConferenceOptionsDO } from '@shared/domain'; -import { ErrorStatus } from '@modules/video-conference/error/error-status.enum'; -import { UserService } from '@modules/user'; import { BBBBaseMeetingConfig, BBBMeetingInfoResponse, BBBResponse, BBBRole, BBBService } from '../bbb'; -import { IScopeInfo, ScopeRef, VideoConferenceInfo, VideoConferenceState } from './dto'; -import { VideoConferenceService } from '../service'; -import { PermissionMapping } from '../mapper/video-conference.mapper'; import { defaultVideoConferenceOptions, VideoConferenceOptions } from '../interface'; +import { PermissionMapping } from '../mapper/video-conference.mapper'; +import { VideoConferenceService } from '../service'; +import { ScopeInfo, ScopeRef, VideoConferenceInfo, VideoConferenceState } from './dto'; @Injectable() export class VideoConferenceInfoUc { @@ -27,7 +27,7 @@ export class VideoConferenceInfoUc { await this.videoConferenceService.throwOnFeaturesDisabled(user.schoolId); - const scopeInfo: IScopeInfo = await this.videoConferenceService.getScopeInfo(currentUserId, scope.id, scope.scope); + const scopeInfo: ScopeInfo = await this.videoConferenceService.getScopeInfo(currentUserId, scope.id, scope.scope); const bbbRole: BBBRole = await this.videoConferenceService.determineBbbRole( currentUserId, diff --git a/apps/server/src/shared/common/error/business.error.ts b/apps/server/src/shared/common/error/business.error.ts index b65a43d0fa9..cb90422071c 100644 --- a/apps/server/src/shared/common/error/business.error.ts +++ b/apps/server/src/shared/common/error/business.error.ts @@ -1,7 +1,7 @@ import { HttpException, HttpStatus } from '@nestjs/common'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { ErrorResponse } from '@src/core/error/dto/error.response'; -import { IErrorType } from '@src/core/error/interface'; +import { ErrorType } from '@src/core/error/interface'; /** * Abstract base class for business errors, errors that are handled @@ -25,7 +25,7 @@ export abstract class BusinessError extends HttpException { readonly details?: Record; protected constructor( - { type, title, defaultMessage }: IErrorType, + { type, title, defaultMessage }: ErrorType, code: HttpStatus = HttpStatus.CONFLICT, details?: Record, cause?: unknown diff --git a/apps/server/src/shared/common/interceptor/interfaces/interceptor-config.ts b/apps/server/src/shared/common/interceptor/interfaces/interceptor-config.ts index 7bcbe5fad3f..510ada13458 100644 --- a/apps/server/src/shared/common/interceptor/interfaces/interceptor-config.ts +++ b/apps/server/src/shared/common/interceptor/interfaces/interceptor-config.ts @@ -1,4 +1,4 @@ -export interface IInterceptorConfig { +export interface InterceptorConfig { INCOMING_REQUEST_TIMEOUT: number; INCOMING_REQUEST_TIMEOUT_COPY_API: number; } diff --git a/apps/server/src/shared/common/interceptor/request-logging.interceptor.ts b/apps/server/src/shared/common/interceptor/request-logging.interceptor.ts index 838ae020039..4e3c256733f 100644 --- a/apps/server/src/shared/common/interceptor/request-logging.interceptor.ts +++ b/apps/server/src/shared/common/interceptor/request-logging.interceptor.ts @@ -1,9 +1,9 @@ +import { ICurrentUser } from '@modules/authentication/interface/user'; import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common'; import { LegacyLogger, RequestLoggingBody } from '@src/core/logger'; import { Request } from 'express'; import { Observable, throwError } from 'rxjs'; import { catchError, tap } from 'rxjs/operators'; -import { ICurrentUser } from '@modules/authentication/interface/user'; @Injectable() export class RequestLoggingInterceptor implements NestInterceptor { diff --git a/apps/server/src/shared/domain/entity/course.entity.ts b/apps/server/src/shared/domain/entity/course.entity.ts index 81fdcb741d9..e0fb4d4bcc3 100644 --- a/apps/server/src/shared/domain/entity/course.entity.ts +++ b/apps/server/src/shared/domain/entity/course.entity.ts @@ -1,17 +1,17 @@ import { Collection, Entity, Enum, Index, ManyToMany, ManyToOne, OneToMany, Property, Unique } from '@mikro-orm/core'; -import { InternalServerErrorException } from '@nestjs/common/exceptions/internal-server-error.exception'; -import { IEntityWithSchool, ILearnroom } from '@shared/domain/interface'; import { ClassEntity } from '@modules/class/entity/class.entity'; import { GroupEntity } from '@modules/group/entity/group.entity'; +import { InternalServerErrorException } from '@nestjs/common/exceptions/internal-server-error.exception'; +import { EntityWithSchool, Learnroom } from '@shared/domain/interface'; import { EntityId, LearnroomMetadata, LearnroomTypes } from '../types'; import { BaseEntityWithTimestamps } from './base.entity'; import { CourseGroup } from './coursegroup.entity'; -import type { ILessonParent } from './lesson.entity'; +import type { LessonParent } from './lesson.entity'; import { SchoolEntity } from './school.entity'; -import type { ITaskParent } from './task.entity'; +import type { TaskParent } from './task.entity'; import type { User } from './user.entity'; -export interface ICourseProperties { +export interface CourseProperties { name?: string; description?: string; school: SchoolEntity; @@ -49,10 +49,7 @@ export class UsersList { } @Entity({ tableName: 'courses' }) -export class Course - extends BaseEntityWithTimestamps - implements ILearnroom, IEntityWithSchool, ITaskParent, ILessonParent -{ +export class Course extends BaseEntityWithTimestamps implements Learnroom, EntityWithSchool, TaskParent, LessonParent { @Property() name: string = DEFAULT.name; @@ -105,7 +102,7 @@ export class Course @ManyToMany(() => GroupEntity, undefined, { fieldName: 'groupIds' }) groups = new Collection(this); - constructor(props: ICourseProperties) { + constructor(props: CourseProperties) { super(); if (props.name) this.name = props.name; if (props.description) this.description = props.description; diff --git a/apps/server/src/shared/domain/entity/coursegroup.entity.ts b/apps/server/src/shared/domain/entity/coursegroup.entity.ts index c883e6adcce..8a2cade7aca 100644 --- a/apps/server/src/shared/domain/entity/coursegroup.entity.ts +++ b/apps/server/src/shared/domain/entity/coursegroup.entity.ts @@ -1,14 +1,14 @@ import { Collection, Entity, Index, ManyToMany, ManyToOne, Property } from '@mikro-orm/core'; -import { IEntityWithSchool } from '../interface'; +import { EntityWithSchool } from '../interface'; import { EntityId } from '../types'; import { BaseEntityWithTimestamps } from './base.entity'; import type { Course } from './course.entity'; -import type { ILessonParent } from './lesson.entity'; +import type { LessonParent } from './lesson.entity'; import { SchoolEntity } from './school.entity'; -import type { ITaskParent } from './task.entity'; +import type { TaskParent } from './task.entity'; import type { User } from './user.entity'; -export interface ICourseGroupProperties { +export interface CourseGroupProperties { name: string; course: Course; students?: User[]; @@ -16,7 +16,7 @@ export interface ICourseGroupProperties { @Entity({ tableName: 'coursegroups' }) @Index({ properties: ['school', 'course'] }) -export class CourseGroup extends BaseEntityWithTimestamps implements IEntityWithSchool, ITaskParent, ILessonParent { +export class CourseGroup extends BaseEntityWithTimestamps implements EntityWithSchool, TaskParent, LessonParent { @Property() name: string; @@ -32,7 +32,7 @@ export class CourseGroup extends BaseEntityWithTimestamps implements IEntityWith @Index() school: SchoolEntity; - constructor(props: ICourseGroupProperties) { + constructor(props: CourseGroupProperties) { super(); this.name = props.name; this.course = props.course; diff --git a/apps/server/src/shared/domain/entity/dashboard.entity.spec.ts b/apps/server/src/shared/domain/entity/dashboard.entity.spec.ts index b16eb7c0a16..5086255e6cf 100644 --- a/apps/server/src/shared/domain/entity/dashboard.entity.spec.ts +++ b/apps/server/src/shared/domain/entity/dashboard.entity.spec.ts @@ -1,8 +1,8 @@ -import { NotFoundException, BadRequestException } from '@nestjs/common'; -import { ILearnroom, LearnroomTypes } from '@shared/domain'; +import { BadRequestException, NotFoundException } from '@nestjs/common'; +import { Learnroom, LearnroomTypes } from '@shared/domain'; import { DashboardEntity, GridElement } from './dashboard.entity'; -const getLearnroomMock = (id: string): ILearnroom => { +const getLearnroomMock = (id: string): Learnroom => { return { getMetadata: () => { return { diff --git a/apps/server/src/shared/domain/entity/dashboard.entity.ts b/apps/server/src/shared/domain/entity/dashboard.entity.ts index b3165dedf8a..cb49032e9fc 100644 --- a/apps/server/src/shared/domain/entity/dashboard.entity.ts +++ b/apps/server/src/shared/domain/entity/dashboard.entity.ts @@ -1,6 +1,6 @@ -import { NotFoundException, BadRequestException } from '@nestjs/common'; +import { BadRequestException, NotFoundException } from '@nestjs/common'; +import { Learnroom } from '@shared/domain/interface'; import { EntityId, LearnroomMetadata } from '@shared/domain/types'; -import { ILearnroom } from '@shared/domain/interface'; const defaultColumns = 4; @@ -15,11 +15,11 @@ export interface IGridElement { removeReferenceByIndex(index: number): void; - removeReference(reference: ILearnroom): void; + removeReference(reference: Learnroom): void; - getReferences(): ILearnroom[]; + getReferences(): Learnroom[]; - addReferences(anotherReference: ILearnroom[]): void; + addReferences(anotherReference: Learnroom[]): void; setGroupName(newGroupName: string): void; } @@ -39,7 +39,7 @@ export class GridElement implements IGridElement { title?: string; - private sortReferences = (a: ILearnroom, b: ILearnroom) => { + private sortReferences = (a: Learnroom, b: Learnroom) => { const titleA = a.getMetadata().title; const titleB = b.getMetadata().title; if (titleA < titleB) { @@ -51,29 +51,29 @@ export class GridElement implements IGridElement { return 0; }; - private constructor(props: { id?: EntityId; title?: string; references: ILearnroom[] }) { + private constructor(props: { id?: EntityId; title?: string; references: Learnroom[] }) { if (props.id) this.id = props.id; if (props.title) this.title = props.title; this.references = props.references.sort(this.sortReferences); } - static FromPersistedReference(id: EntityId, reference: ILearnroom): GridElement { + static FromPersistedReference(id: EntityId, reference: Learnroom): GridElement { return new GridElement({ id, references: [reference] }); } - static FromPersistedGroup(id: EntityId, title: string | undefined, group: ILearnroom[]): GridElement { + static FromPersistedGroup(id: EntityId, title: string | undefined, group: Learnroom[]): GridElement { return new GridElement({ id, title, references: group }); } - static FromSingleReference(reference: ILearnroom): GridElement { + static FromSingleReference(reference: Learnroom): GridElement { return new GridElement({ references: [reference] }); } - static FromGroup(title: string, references: ILearnroom[]): GridElement { + static FromGroup(title: string, references: Learnroom[]): GridElement { return new GridElement({ title, references }); } - references: ILearnroom[]; + references: Learnroom[]; hasId(): boolean { return !!this.id; @@ -83,7 +83,7 @@ export class GridElement implements IGridElement { return this.id; } - getReferences(): ILearnroom[] { + getReferences(): Learnroom[] { return this.references; } @@ -97,7 +97,7 @@ export class GridElement implements IGridElement { this.references.splice(index, 1); } - removeReference(reference: ILearnroom): void { + removeReference(reference: Learnroom): void { const index = this.references.indexOf(reference); if (index === -1) { throw new BadRequestException('reference not found.'); @@ -105,7 +105,7 @@ export class GridElement implements IGridElement { this.references.splice(index, 1); } - addReferences(anotherReference: ILearnroom[]): void { + addReferences(anotherReference: Learnroom[]): void { if (!this.isGroup()) { this.references = this.references.concat(anotherReference).sort(this.sortReferences); this.setGroupName(''); @@ -228,7 +228,7 @@ export class DashboardEntity { }; } - setLearnRooms(rooms: ILearnroom[]): void { + setLearnRooms(rooms: Learnroom[]): void { this.removeRoomsNotInList(rooms); const newRooms = this.determineNewRoomsIn(rooms); @@ -237,7 +237,7 @@ export class DashboardEntity { }); } - private removeRoomsNotInList(roomList: ILearnroom[]): void { + private removeRoomsNotInList(roomList: Learnroom[]): void { [...this.grid.keys()].forEach((key) => { const element = this.grid.get(key) as IGridElement; const currentRooms = element.getReferences(); @@ -252,8 +252,8 @@ export class DashboardEntity { }); } - private determineNewRoomsIn(rooms: ILearnroom[]): ILearnroom[] { - const result: ILearnroom[] = []; + private determineNewRoomsIn(rooms: Learnroom[]): Learnroom[] { + const result: Learnroom[] = []; const existingRooms = this.allRooms(); rooms.forEach((room) => { if (!existingRooms.includes(room)) { @@ -263,13 +263,13 @@ export class DashboardEntity { return result; } - private allRooms(): ILearnroom[] { + private allRooms(): Learnroom[] { const elements = [...this.grid.values()]; const references = elements.map((el) => el.getReferences()).flat(); return references; } - private addRoom(room: ILearnroom): void { + private addRoom(room: Learnroom): void { const index = this.getFirstOpenIndex(); const newElement = GridElement.FromSingleReference(room); this.grid.set(index, newElement); diff --git a/apps/server/src/shared/domain/entity/dashboard.model.entity.ts b/apps/server/src/shared/domain/entity/dashboard.model.entity.ts index b7d6acbe8d6..b0463ff5355 100644 --- a/apps/server/src/shared/domain/entity/dashboard.model.entity.ts +++ b/apps/server/src/shared/domain/entity/dashboard.model.entity.ts @@ -56,7 +56,7 @@ export class DashboardGridElementModel extends BaseEntityWithTimestamps { dashboard: IdentifiedReference; } -export interface IDashboardModelProperties { +export interface DashboardModelProperties { id: string; user: User; gridElements?: DashboardGridElementModel[]; @@ -64,7 +64,7 @@ export interface IDashboardModelProperties { @Entity({ tableName: 'dashboard' }) export class DashboardModelEntity extends BaseEntityWithTimestamps { - constructor(props: IDashboardModelProperties) { + constructor(props: DashboardModelProperties) { super(); this._id = ObjectId.createFromHexString(props.id); this.id = props.id; diff --git a/apps/server/src/shared/domain/entity/federal-state.entity.ts b/apps/server/src/shared/domain/entity/federal-state.entity.ts index d65b36c9826..d7c84567fec 100644 --- a/apps/server/src/shared/domain/entity/federal-state.entity.ts +++ b/apps/server/src/shared/domain/entity/federal-state.entity.ts @@ -1,7 +1,7 @@ import { Embeddable, Embedded, Entity, Property } from '@mikro-orm/core'; import { BaseEntityWithTimestamps } from './base.entity'; -export interface IFederalStateProperties { +export interface FederalStateProperties { name: string; abbreviation: string; logoUrl: string; @@ -39,7 +39,7 @@ export class FederalStateEntity extends BaseEntityWithTimestamps { @Embedded(() => County, { array: true, nullable: true }) counties?: County[]; - constructor(props: IFederalStateProperties) { + constructor(props: FederalStateProperties) { super(); this.name = props.name; this.abbreviation = props.abbreviation; diff --git a/apps/server/src/shared/domain/entity/import-user.entity.ts b/apps/server/src/shared/domain/entity/import-user.entity.ts index 09a10179e3d..34d1f8b64e8 100644 --- a/apps/server/src/shared/domain/entity/import-user.entity.ts +++ b/apps/server/src/shared/domain/entity/import-user.entity.ts @@ -1,5 +1,5 @@ import { Entity, Enum, IdentifiedReference, ManyToOne, Property, Unique, wrap } from '@mikro-orm/core'; -import { IEntityWithSchool, RoleName } from '../interface'; +import { EntityWithSchool, RoleName } from '../interface'; import { BaseEntityReference, BaseEntityWithTimestamps } from './base.entity'; import { SchoolEntity } from './school.entity'; import { SystemEntity } from './system.entity'; @@ -7,7 +7,7 @@ import type { User } from './user.entity'; export type IImportUserRoleName = RoleName.ADMINISTRATOR | RoleName.TEACHER | RoleName.STUDENT; -export interface IImportUserProperties { +export interface ImportUserProperties { // references school: SchoolEntity; system: SystemEntity; @@ -34,8 +34,8 @@ export enum MatchCreator { @Unique({ properties: ['school', 'externalId'] }) @Unique({ properties: ['school', 'ldapDn'] }) @Unique({ properties: ['school', 'email'] }) -export class ImportUser extends BaseEntityWithTimestamps implements IEntityWithSchool { - constructor(props: IImportUserProperties) { +export class ImportUser extends BaseEntityWithTimestamps implements EntityWithSchool { + constructor(props: ImportUserProperties) { super(); this.school = wrap(props.school).toReference(); this.system = wrap(props.system).toReference(); diff --git a/apps/server/src/shared/domain/entity/legacy-board/board.entity.ts b/apps/server/src/shared/domain/entity/legacy-board/board.entity.ts index 073c30e8d94..18f6e7d864f 100644 --- a/apps/server/src/shared/domain/entity/legacy-board/board.entity.ts +++ b/apps/server/src/shared/domain/entity/legacy-board/board.entity.ts @@ -1,6 +1,6 @@ import { Collection, Entity, IdentifiedReference, ManyToMany, OneToOne, wrap } from '@mikro-orm/core'; import { BadRequestException, NotFoundException } from '@nestjs/common'; -import { ILearnroomElement } from '../../interface'; +import { LearnroomElement } from '../../interface'; import { EntityId } from '../../types'; import { BaseEntityWithTimestamps } from '../base.entity'; import type { Course } from '../course.entity'; @@ -31,7 +31,7 @@ export class Board extends BaseEntityWithTimestamps { @ManyToMany('BoardElement', undefined, { fieldName: 'referenceIds' }) references = new Collection(this); - getByTargetId(id: EntityId): ILearnroomElement { + getByTargetId(id: EntityId): LearnroomElement { const element = this.getElementByTargetId(id); return element.target; } diff --git a/apps/server/src/shared/domain/entity/legacy-board/column-board-target.entity.ts b/apps/server/src/shared/domain/entity/legacy-board/column-board-target.entity.ts index 33545699362..6008c97c550 100644 --- a/apps/server/src/shared/domain/entity/legacy-board/column-board-target.entity.ts +++ b/apps/server/src/shared/domain/entity/legacy-board/column-board-target.entity.ts @@ -1,5 +1,5 @@ import { Entity, Property } from '@mikro-orm/core'; -import { ILearnroomElement } from '@shared/domain/interface'; +import { LearnroomElement } from '@shared/domain/interface'; import { EntityId } from '@shared/domain/types'; import { ObjectId } from 'bson'; import { BaseEntityWithTimestamps } from '../base.entity'; @@ -10,7 +10,7 @@ type ColumnBoardTargetProps = { }; @Entity() -export class ColumnBoardTarget extends BaseEntityWithTimestamps implements ILearnroomElement { +export class ColumnBoardTarget extends BaseEntityWithTimestamps implements LearnroomElement { constructor(props: ColumnBoardTargetProps) { super(); this._columnBoardId = new ObjectId(props.columnBoardId); diff --git a/apps/server/src/shared/domain/entity/lesson.entity.spec.ts b/apps/server/src/shared/domain/entity/lesson.entity.spec.ts index 631d04f0380..daca93d86e5 100644 --- a/apps/server/src/shared/domain/entity/lesson.entity.spec.ts +++ b/apps/server/src/shared/domain/entity/lesson.entity.spec.ts @@ -8,7 +8,7 @@ import { setupEntities, taskFactory, } from '../../testing'; -import { ComponentType, IComponentProperties } from './lesson.entity'; +import { ComponentProperties, ComponentType } from './lesson.entity'; import { Material } from './materials.entity'; import { Task } from './task.entity'; @@ -152,7 +152,7 @@ describe('Lesson Entity', () => { describe('getLessonComponents', () => { it('should return the content components', () => { - const expectedTextContent: IComponentProperties = { + const expectedTextContent: ComponentProperties = { title: 'test component', hidden: false, component: ComponentType.TEXT, diff --git a/apps/server/src/shared/domain/entity/lesson.entity.ts b/apps/server/src/shared/domain/entity/lesson.entity.ts index a47aab2a8b3..eae1cb6169b 100644 --- a/apps/server/src/shared/domain/entity/lesson.entity.ts +++ b/apps/server/src/shared/domain/entity/lesson.entity.ts @@ -1,21 +1,21 @@ import { Collection, Entity, Index, ManyToMany, ManyToOne, OneToMany, Property } from '@mikro-orm/core'; import { InternalServerErrorException } from '@nestjs/common'; -import { ILearnroomElement } from '@shared/domain/interface'; +import { LearnroomElement } from '@shared/domain/interface'; import { EntityId } from '../types'; import { BaseEntityWithTimestamps } from './base.entity'; import type { Course } from './course.entity'; import { CourseGroup } from './coursegroup.entity'; import { Material } from './materials.entity'; +import type { TaskParent } from './task.entity'; import { Task } from './task.entity'; -import type { ITaskParent } from './task.entity'; -export interface ILessonProperties { +export interface LessonProperties { name: string; hidden: boolean; course: Course; courseGroup?: CourseGroup; position?: number; - contents: IComponentProperties[] | []; + contents: ComponentProperties[] | []; materials?: Material[]; } @@ -28,15 +28,15 @@ export enum ComponentType { NEXBOARD = 'neXboard', } -export interface IComponentTextProperties { +export interface ComponentTextProperties { text: string; } -export interface IComponentGeogebraProperties { +export interface ComponentGeogebraProperties { materialId: string; } -export interface IComponentLernstoreProperties { +export interface ComponentLernstoreProperties { resources: { client: string; description: string; @@ -46,43 +46,43 @@ export interface IComponentLernstoreProperties { }[]; } -export interface IComponentEtherpadProperties { +export interface ComponentEtherpadProperties { description: string; title: string; url: string; } -export interface IComponentNexboardProperties { +export interface ComponentNexboardProperties { board: string; description: string; title: string; url: string; } -export interface IComponentInternalProperties { +export interface ComponentInternalProperties { url: string; } -export type IComponentProperties = { +export type ComponentProperties = { _id?: string; title: string; hidden: boolean; user?: EntityId; } & ( - | { component: ComponentType.TEXT; content: IComponentTextProperties } - | { component: ComponentType.ETHERPAD; content: IComponentEtherpadProperties } - | { component: ComponentType.GEOGEBRA; content: IComponentGeogebraProperties } - | { component: ComponentType.INTERNAL; content: IComponentInternalProperties } - | { component: ComponentType.LERNSTORE; content?: IComponentLernstoreProperties } - | { component: ComponentType.NEXBOARD; content: IComponentNexboardProperties } + | { component: ComponentType.TEXT; content: ComponentTextProperties } + | { component: ComponentType.ETHERPAD; content: ComponentEtherpadProperties } + | { component: ComponentType.GEOGEBRA; content: ComponentGeogebraProperties } + | { component: ComponentType.INTERNAL; content: ComponentInternalProperties } + | { component: ComponentType.LERNSTORE; content?: ComponentLernstoreProperties } + | { component: ComponentType.NEXBOARD; content: ComponentNexboardProperties } ); -export interface ILessonParent { +export interface LessonParent { getStudentIds(): EntityId[]; } @Entity({ tableName: 'lessons' }) -export class LessonEntity extends BaseEntityWithTimestamps implements ILearnroomElement, ITaskParent { +export class LessonEntity extends BaseEntityWithTimestamps implements LearnroomElement, TaskParent { @Property() name: string; @@ -101,7 +101,7 @@ export class LessonEntity extends BaseEntityWithTimestamps implements ILearnroom position: number; @Property() - contents: IComponentProperties[] | []; + contents: ComponentProperties[] | []; @ManyToMany('Material', undefined, { fieldName: 'materialIds' }) materials = new Collection(this); @@ -109,7 +109,7 @@ export class LessonEntity extends BaseEntityWithTimestamps implements ILearnroom @OneToMany('Task', 'lesson', { orphanRemoval: true }) tasks = new Collection(this); - constructor(props: ILessonProperties) { + constructor(props: LessonProperties) { super(); this.name = props.name; if (props.hidden !== undefined) this.hidden = props.hidden; @@ -120,7 +120,7 @@ export class LessonEntity extends BaseEntityWithTimestamps implements ILearnroom if (props.materials) this.materials.set(props.materials); } - private getParent(): ILessonParent { + private getParent(): LessonParent { const parent = this.courseGroup || this.course; return parent; @@ -152,7 +152,7 @@ export class LessonEntity extends BaseEntityWithTimestamps implements ILearnroom return filtered.length; } - getLessonComponents(): IComponentProperties[] | [] { + getLessonComponents(): ComponentProperties[] | [] { return this.contents; } diff --git a/apps/server/src/shared/domain/entity/materials.entity.ts b/apps/server/src/shared/domain/entity/materials.entity.ts index 9bd73edcfd8..273174f9782 100644 --- a/apps/server/src/shared/domain/entity/materials.entity.ts +++ b/apps/server/src/shared/domain/entity/materials.entity.ts @@ -1,26 +1,26 @@ import { Entity, Property } from '@mikro-orm/core'; import { BaseEntityWithTimestamps } from './base.entity'; -export interface ITargetGroupProperties { +export interface TargetGroupProperties { state?: string; schoolType?: string; grade?: string; } -export interface IRelatedResourceProperties { +export interface RelatedResourceProperties { originId?: string; relationType?: string; } -export interface IMaterialProperties { +export interface MaterialProperties { client: string; description?: string; license: string[]; merlinReference?: string; - relatedResources: IRelatedResourceProperties[]; + relatedResources: RelatedResourceProperties[]; subjects: string[]; tags: string[]; - targetGroups: ITargetGroupProperties[]; + targetGroups: TargetGroupProperties[]; title: string; url: string; } @@ -40,7 +40,7 @@ export class Material extends BaseEntityWithTimestamps { merlinReference?: string; @Property() - relatedResources: IRelatedResourceProperties[] | []; + relatedResources: RelatedResourceProperties[] | []; @Property() subjects: string[] | []; @@ -49,7 +49,7 @@ export class Material extends BaseEntityWithTimestamps { tags: string[] | []; @Property() - targetGroups: ITargetGroupProperties[] | []; + targetGroups: TargetGroupProperties[] | []; @Property() title: string; @@ -57,7 +57,7 @@ export class Material extends BaseEntityWithTimestamps { @Property() url: string; - constructor(props: IMaterialProperties) { + constructor(props: MaterialProperties) { super(); this.client = props.client; this.description = props.description || ''; diff --git a/apps/server/src/shared/domain/entity/news.entity.ts b/apps/server/src/shared/domain/entity/news.entity.ts index 7b22c38f364..682192770a1 100644 --- a/apps/server/src/shared/domain/entity/news.entity.ts +++ b/apps/server/src/shared/domain/entity/news.entity.ts @@ -1,13 +1,13 @@ import { Entity, Enum, Index, ManyToOne, Property } from '@mikro-orm/core'; +import { EntityId } from '../types'; +import { NewsTarget, NewsTargetModel } from '../types/news.types'; import { BaseEntityWithTimestamps } from './base.entity'; import type { Course } from './course.entity'; import { SchoolEntity } from './school.entity'; import type { TeamEntity } from './team.entity'; import type { User } from './user.entity'; -import { NewsTarget, NewsTargetModel } from '../types/news.types'; -import { EntityId } from '../types'; -export interface INewsProperties { +export interface NewsProperties { title: string; content: string; displayAt: Date; @@ -69,7 +69,7 @@ export abstract class News extends BaseEntityWithTimestamps { permissions: string[] = []; - constructor(props: INewsProperties) { + constructor(props: NewsProperties) { super(); this.title = props.title; this.content = props.content; @@ -80,7 +80,7 @@ export abstract class News extends BaseEntityWithTimestamps { this.sourceDescription = props.sourceDescription; } - static createInstance(targetModel: NewsTargetModel, props: INewsProperties): News { + static createInstance(targetModel: NewsTargetModel, props: NewsProperties): News { let news: News; if (targetModel === NewsTargetModel.Course) { // eslint-disable-next-line @typescript-eslint/no-use-before-define @@ -101,7 +101,7 @@ export class SchoolNews extends News { @ManyToOne(() => SchoolEntity) target!: SchoolEntity; - constructor(props: INewsProperties) { + constructor(props: NewsProperties) { super(props); this.targetModel = NewsTargetModel.School; } @@ -115,7 +115,7 @@ export class CourseNews extends News { @ManyToOne('Course', { nullable: true }) target!: Course; - constructor(props: INewsProperties) { + constructor(props: NewsProperties) { super(props); this.targetModel = NewsTargetModel.Course; } @@ -126,7 +126,7 @@ export class TeamNews extends News { @ManyToOne('TeamEntity') target!: TeamEntity; - constructor(props: INewsProperties) { + constructor(props: NewsProperties) { super(props); this.targetModel = NewsTargetModel.Team; } diff --git a/apps/server/src/shared/domain/entity/role.entity.ts b/apps/server/src/shared/domain/entity/role.entity.ts index 66dc6e24191..923b75c8873 100644 --- a/apps/server/src/shared/domain/entity/role.entity.ts +++ b/apps/server/src/shared/domain/entity/role.entity.ts @@ -2,7 +2,7 @@ import { Collection, Entity, ManyToMany, Property, Unique } from '@mikro-orm/cor import { Permission, RoleName } from '../interface'; import { BaseEntityWithTimestamps } from './base.entity'; -export interface IRoleProperties { +export interface RoleProperties { permissions?: Permission[]; roles?: Role[]; name: RoleName; @@ -20,7 +20,7 @@ export class Role extends BaseEntityWithTimestamps { @ManyToMany({ entity: 'Role' }) roles = new Collection(this); - constructor(props: IRoleProperties) { + constructor(props: RoleProperties) { super(); this.name = props.name; if (props.permissions) this.permissions = props.permissions; diff --git a/apps/server/src/shared/domain/entity/school.entity.ts b/apps/server/src/shared/domain/entity/school.entity.ts index 98502a127eb..81802f595ed 100644 --- a/apps/server/src/shared/domain/entity/school.entity.ts +++ b/apps/server/src/shared/domain/entity/school.entity.ts @@ -11,9 +11,9 @@ import { } from '@mikro-orm/core'; import { UserLoginMigrationEntity } from '@shared/domain/entity/user-login-migration.entity'; import { BaseEntity } from './base.entity'; +import { FederalStateEntity } from './federal-state.entity'; import { SchoolYearEntity } from './schoolyear.entity'; import { SystemEntity } from './system.entity'; -import { FederalStateEntity } from './federal-state.entity'; export enum SchoolFeatures { ROCKET_CHAT = 'rocketChat', @@ -26,7 +26,7 @@ export enum SchoolFeatures { ENABLE_LDAP_SYNC_DURING_MIGRATION = 'enableLdapSyncDuringMigration', } -export interface ISchoolProperties { +export interface SchoolProperties { _id?: string; externalId?: string; inMaintenanceSince?: Date; @@ -106,7 +106,7 @@ export class SchoolEntity extends BaseEntity { @ManyToOne(() => FederalStateEntity, { fieldName: 'federalState', nullable: false }) federalState: FederalStateEntity; - constructor(props: ISchoolProperties) { + constructor(props: SchoolProperties) { super(); if (props.externalId) { this.externalId = props.externalId; diff --git a/apps/server/src/shared/domain/entity/schoolyear.entity.ts b/apps/server/src/shared/domain/entity/schoolyear.entity.ts index e1d2c1c9895..f538119652c 100644 --- a/apps/server/src/shared/domain/entity/schoolyear.entity.ts +++ b/apps/server/src/shared/domain/entity/schoolyear.entity.ts @@ -1,14 +1,14 @@ import { Entity, Property } from '@mikro-orm/core'; import { BaseEntity } from './base.entity'; -export interface ISchoolYearProperties { +export interface SchoolYearProperties { name: string; startDate: Date; endDate: Date; } @Entity({ tableName: 'years' }) -export class SchoolYearEntity extends BaseEntity implements ISchoolYearProperties { +export class SchoolYearEntity extends BaseEntity implements SchoolYearProperties { @Property() name: string; @@ -18,7 +18,7 @@ export class SchoolYearEntity extends BaseEntity implements ISchoolYearPropertie @Property() endDate: Date; - constructor(props: ISchoolYearProperties) { + constructor(props: SchoolYearProperties) { super(); this.name = props.name; this.startDate = props.startDate; diff --git a/apps/server/src/shared/domain/entity/storageprovider.entity.ts b/apps/server/src/shared/domain/entity/storageprovider.entity.ts index 7bac9b3380a..e2c0fe885ef 100644 --- a/apps/server/src/shared/domain/entity/storageprovider.entity.ts +++ b/apps/server/src/shared/domain/entity/storageprovider.entity.ts @@ -2,7 +2,7 @@ import { Entity, Property } from '@mikro-orm/core'; import { StorageProviderEncryptedStringType } from '@shared/repo/types/StorageProviderEncryptedString.type'; import { BaseEntityWithTimestamps } from './base.entity'; -export interface IStorageProviderProperties { +export interface StorageProviderProperties { endpointUrl: string; accessKeyId: string; secretAccessKey: string; @@ -23,7 +23,7 @@ export class StorageProviderEntity extends BaseEntityWithTimestamps { @Property({ nullable: true }) region?: string; - constructor(props: IStorageProviderProperties) { + constructor(props: StorageProviderProperties) { super(); this.endpointUrl = props.endpointUrl; this.accessKeyId = props.accessKeyId; diff --git a/apps/server/src/shared/domain/entity/submission.entity.ts b/apps/server/src/shared/domain/entity/submission.entity.ts index de86ba9f814..12893ed3bd5 100644 --- a/apps/server/src/shared/domain/entity/submission.entity.ts +++ b/apps/server/src/shared/domain/entity/submission.entity.ts @@ -8,7 +8,7 @@ import { SchoolEntity } from './school.entity'; import type { Task } from './task.entity'; import type { User } from './user.entity'; -export interface ISubmissionProperties { +export interface SubmissionProperties { school: SchoolEntity; task: Task; student: User; @@ -57,7 +57,7 @@ export class Submission extends BaseEntityWithTimestamps { @Property({ nullable: true }) gradeComment?: string; - constructor(props: ISubmissionProperties) { + constructor(props: SubmissionProperties) { super(); this.school = props.school; this.student = props.student; diff --git a/apps/server/src/shared/domain/entity/system.entity.ts b/apps/server/src/shared/domain/entity/system.entity.ts index 0633e515589..07cfea5bb59 100644 --- a/apps/server/src/shared/domain/entity/system.entity.ts +++ b/apps/server/src/shared/domain/entity/system.entity.ts @@ -3,7 +3,7 @@ import { SystemProvisioningStrategy } from '@shared/domain/interface/system-prov import { EntityId } from '../types'; import { BaseEntityWithTimestamps } from './base.entity'; -export interface ISystemProperties { +export interface SystemProperties { type: string; url?: string; alias?: string; @@ -189,7 +189,7 @@ export class OidcConfig { @Entity({ tableName: 'systems' }) export class SystemEntity extends BaseEntityWithTimestamps { - constructor(props: ISystemProperties) { + constructor(props: SystemProperties) { super(); this.type = props.type; this.url = props.url; diff --git a/apps/server/src/shared/domain/entity/task.entity.ts b/apps/server/src/shared/domain/entity/task.entity.ts index c63156e6aa4..88af11a0a7d 100644 --- a/apps/server/src/shared/domain/entity/task.entity.ts +++ b/apps/server/src/shared/domain/entity/task.entity.ts @@ -2,10 +2,10 @@ import { Collection, Entity, Index, ManyToMany, ManyToOne, OneToMany, Property } import { InternalServerErrorException } from '@nestjs/common'; import { SchoolEntity } from '@shared/domain/entity/school.entity'; import { InputFormat } from '@shared/domain/types/input-format.types'; -import type { IEntityWithSchool } from '../interface'; -import type { ILearnroomElement } from '../interface/learnroom'; +import type { EntityWithSchool } from '../interface'; +import type { LearnroomElement } from '../interface/learnroom'; import type { EntityId } from '../types/entity-id'; -import type { ITaskProperties, ITaskStatus } from '../types/task.types'; +import type { TaskProperties, TaskStatus } from '../types/task.types'; import { BaseEntityWithTimestamps } from './base.entity'; import type { Course } from './course.entity'; import type { LessonEntity } from './lesson.entity'; @@ -15,9 +15,9 @@ import { User } from './user.entity'; export class TaskWithStatusVo { task!: Task; - status!: ITaskStatus; + status!: TaskStatus; - constructor(task: Task, status: ITaskStatus) { + constructor(task: Task, status: TaskStatus) { this.task = task; this.status = status; } @@ -31,7 +31,7 @@ export type TaskParentDescriptions = { color: string; }; -export interface ITaskParent { +export interface TaskParent { getStudentIds(): EntityId[]; } @@ -40,7 +40,7 @@ export interface ITaskParent { @Index({ properties: ['id', 'private'] }) @Index({ properties: ['finished', 'course'] }) @Index({ properties: ['finished', 'course'] }) -export class Task extends BaseEntityWithTimestamps implements ILearnroomElement, IEntityWithSchool { +export class Task extends BaseEntityWithTimestamps implements LearnroomElement, EntityWithSchool { @Property() name: string; @@ -89,7 +89,7 @@ export class Task extends BaseEntityWithTimestamps implements ILearnroomElement, @ManyToMany('User', undefined, { fieldName: 'archived' }) finished = new Collection(this); - constructor(props: ITaskProperties) { + constructor(props: TaskProperties) { super(); this.name = props.name; this.description = props.description || ''; @@ -128,7 +128,7 @@ export class Task extends BaseEntityWithTimestamps implements ILearnroomElement, return finishedIds; } - private getParent(): ITaskParent | User { + private getParent(): TaskParent | User { const parent = this.lesson || this.course || this.creator; return parent; @@ -234,7 +234,7 @@ export class Task extends BaseEntityWithTimestamps implements ILearnroomElement, return isSubstitutionTeacher; } - public createTeacherStatusForUser(user: User): ITaskStatus { + public createTeacherStatusForUser(user: User): TaskStatus { const submittedSubmissions = this.getSubmittedSubmissions(); const gradedSubmissions = this.getGradedSubmissions(); @@ -257,7 +257,7 @@ export class Task extends BaseEntityWithTimestamps implements ILearnroomElement, return status; } - public createStudentStatusForUser(user: User): ITaskStatus { + public createStudentStatusForUser(user: User): TaskStatus { const isSubmitted = this.isSubmittedForUser(user); const isGraded = this.isGradedForUser(user); const maxSubmissions = 1; diff --git a/apps/server/src/shared/domain/entity/team.entity.ts b/apps/server/src/shared/domain/entity/team.entity.ts index 70e7d142885..dd39e8c9ff6 100644 --- a/apps/server/src/shared/domain/entity/team.entity.ts +++ b/apps/server/src/shared/domain/entity/team.entity.ts @@ -4,12 +4,12 @@ import { Role } from './role.entity'; import { SchoolEntity } from './school.entity'; import { User } from './user.entity'; -export interface ITeamProperties { +export interface TeamProperties { name: string; teamUsers?: TeamUserEntity[]; } -export interface ITeamUserProperties { +export interface TeamUserProperties { user: User; role: Role; school: SchoolEntity; @@ -17,7 +17,7 @@ export interface ITeamUserProperties { @Embeddable() export class TeamUserEntity { - constructor(props: ITeamUserProperties) { + constructor(props: TeamUserProperties) { this.userId = props.user; this.role = props.role; this.schoolId = props.school; @@ -66,7 +66,7 @@ export class TeamEntity extends BaseEntityWithTimestamps { this.userIds = value; } - constructor(props: ITeamProperties) { + constructor(props: TeamProperties) { super(); this.name = props.name; this.userIds = props.teamUsers ? props.teamUsers.map((teamUser) => new TeamUserEntity(teamUser)) : []; diff --git a/apps/server/src/shared/domain/entity/user.entity.ts b/apps/server/src/shared/domain/entity/user.entity.ts index 3a648dacf2b..c9a982c3854 100644 --- a/apps/server/src/shared/domain/entity/user.entity.ts +++ b/apps/server/src/shared/domain/entity/user.entity.ts @@ -1,5 +1,5 @@ import { Collection, Entity, Index, ManyToMany, ManyToOne, Property } from '@mikro-orm/core'; -import { IEntityWithSchool } from '../interface'; +import { EntityWithSchool } from '../interface'; import { BaseEntityWithTimestamps } from './base.entity'; import { Role } from './role.entity'; import { SchoolEntity } from './school.entity'; @@ -11,7 +11,7 @@ export enum LanguageType { UK = 'uk', } -export interface IUserProperties { +export interface UserProperties { email: string; firstName: string; lastName: string; @@ -35,7 +35,7 @@ export interface IUserProperties { @Index({ properties: ['externalId', 'school'] }) @Index({ properties: ['school', 'ldapDn'] }) @Index({ properties: ['school', 'roles'] }) -export class User extends BaseEntityWithTimestamps implements IEntityWithSchool { +export class User extends BaseEntityWithTimestamps implements EntityWithSchool { @Property() @Index() // @Unique() @@ -100,7 +100,7 @@ export class User extends BaseEntityWithTimestamps implements IEntityWithSchool @Property({ nullable: true }) birthday?: Date; - constructor(props: IUserProperties) { + constructor(props: UserProperties) { super(); this.firstName = props.firstName; this.lastName = props.lastName; diff --git a/apps/server/src/shared/domain/interface/entity.ts b/apps/server/src/shared/domain/interface/entity.ts index dc28d8469d3..97618b0734d 100644 --- a/apps/server/src/shared/domain/interface/entity.ts +++ b/apps/server/src/shared/domain/interface/entity.ts @@ -11,6 +11,6 @@ export interface IEntityWithTimestamps extends IEntity { updatedAt: Date; } -export interface IEntityWithSchool extends IEntity { +export interface EntityWithSchool extends IEntity { school: SchoolEntity; } diff --git a/apps/server/src/shared/domain/interface/find-options.ts b/apps/server/src/shared/domain/interface/find-options.ts index 2b02990d436..37fc49a5909 100644 --- a/apps/server/src/shared/domain/interface/find-options.ts +++ b/apps/server/src/shared/domain/interface/find-options.ts @@ -1,4 +1,4 @@ -export interface IPagination { +export interface Pagination { skip?: number; limit?: number; } @@ -11,6 +11,6 @@ export enum SortOrder { export type SortOrderMap = Partial>; export interface IFindOptions { - pagination?: IPagination; + pagination?: Pagination; order?: SortOrderMap; } diff --git a/apps/server/src/shared/domain/interface/learnroom.ts b/apps/server/src/shared/domain/interface/learnroom.ts index 1bbfaac1298..a0c8e794d68 100644 --- a/apps/server/src/shared/domain/interface/learnroom.ts +++ b/apps/server/src/shared/domain/interface/learnroom.ts @@ -1,10 +1,10 @@ import { LearnroomMetadata } from '@shared/domain/types'; -export interface ILearnroom { +export interface Learnroom { getMetadata: () => LearnroomMetadata; } -export interface ILearnroomElement { +export interface LearnroomElement { publish: () => void; unpublish: () => void; } diff --git a/apps/server/src/shared/domain/types/importuser.types.ts b/apps/server/src/shared/domain/types/importuser.types.ts index 52edf935fb9..de189aed43e 100644 --- a/apps/server/src/shared/domain/types/importuser.types.ts +++ b/apps/server/src/shared/domain/types/importuser.types.ts @@ -16,7 +16,7 @@ export interface IImportUserScope { classes?: string; } -export interface INameMatch { +export interface NameMatch { /** * Match filter for lastName or firstName */ diff --git a/apps/server/src/shared/domain/types/news.types.ts b/apps/server/src/shared/domain/types/news.types.ts index 241b4d90384..671545c8ceb 100644 --- a/apps/server/src/shared/domain/types/news.types.ts +++ b/apps/server/src/shared/domain/types/news.types.ts @@ -1,7 +1,7 @@ -import { EntityId } from './entity-id'; import type { Course } from '../entity/course.entity'; import type { SchoolEntity } from '../entity/school.entity'; import type { TeamEntity } from '../entity/team.entity'; +import { EntityId } from './entity-id'; export enum NewsTargetModel { 'School' = 'schools', @@ -10,7 +10,7 @@ export enum NewsTargetModel { } /** news interface for ceating news */ -export interface ICreateNews { +export interface CreateNews { title: string; content: string; displayAt?: Date; @@ -18,7 +18,7 @@ export interface ICreateNews { } /** news interface for updating news */ -export type IUpdateNews = Partial; +export type IUpdateNews = Partial; /** interface for finding news with optional targetId */ export interface INewsScope { diff --git a/apps/server/src/shared/domain/types/task.types.ts b/apps/server/src/shared/domain/types/task.types.ts index 3282fd24738..8f77e209eed 100644 --- a/apps/server/src/shared/domain/types/task.types.ts +++ b/apps/server/src/shared/domain/types/task.types.ts @@ -8,17 +8,17 @@ interface ITask { dueDate?: Date; } -export interface ITaskUpdate extends ITask { +export interface TaskUpdate extends ITask { courseId?: string; lessonId?: string; } -export interface ITaskCreate extends ITask { +export interface TaskCreate extends ITask { courseId?: string; lessonId?: string; } -export interface ITaskProperties extends ITask { +export interface TaskProperties extends ITask { course?: Course; lesson?: LessonEntity; creator: User; @@ -30,7 +30,7 @@ export interface ITaskProperties extends ITask { teamSubmissions?: boolean; } -export interface ITaskStatus { +export interface TaskStatus { submitted: number; maxSubmissions: number; graded: number; diff --git a/apps/server/src/shared/repo/base.do.repo.integration.spec.ts b/apps/server/src/shared/repo/base.do.repo.integration.spec.ts index 1ca91bd184e..b9683fbf1ea 100644 --- a/apps/server/src/shared/repo/base.do.repo.integration.spec.ts +++ b/apps/server/src/shared/repo/base.do.repo.integration.spec.ts @@ -1,12 +1,12 @@ +import { createMock } from '@golevelup/ts-jest'; +import { MongoMemoryDatabaseModule } from '@infra/database'; +import { Entity, EntityName, Property } from '@mikro-orm/core'; import { EntityManager, ObjectId } from '@mikro-orm/mongodb'; +import { Injectable } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; -import { Entity, EntityName, Property } from '@mikro-orm/core'; import { BaseDO, BaseEntityWithTimestamps } from '@shared/domain'; -import { MongoMemoryDatabaseModule } from '@infra/database'; -import { Injectable } from '@nestjs/common'; import { BaseDORepo } from '@shared/repo/base.do.repo'; import { LegacyLogger } from '@src/core/logger'; -import { createMock } from '@golevelup/ts-jest'; describe('BaseDORepo', () => { @Entity() @@ -14,7 +14,7 @@ describe('BaseDORepo', () => { @Property() name: string; - constructor(props: ITestEntityProperties = { name: 'test' }) { + constructor(props: TestEntityProperties = { name: 'test' }) { super(); this.name = props.name; } @@ -30,17 +30,17 @@ describe('BaseDORepo', () => { } } - interface ITestEntityProperties { + interface TestEntityProperties { name: string; } @Injectable() - class TestRepo extends BaseDORepo { + class TestRepo extends BaseDORepo { get entityName(): EntityName { return TestEntity; } - entityFactory(props: ITestEntityProperties): TestEntity { + entityFactory(props: TestEntityProperties): TestEntity { return new TestEntity(props); } @@ -48,7 +48,7 @@ describe('BaseDORepo', () => { return new TestDO({ id: entity.id, name: entity.name }); } - mapDOToEntityProperties(entityDO: TestDO): ITestEntityProperties { + mapDOToEntityProperties(entityDO: TestDO): TestEntityProperties { return { name: entityDO.name, }; @@ -95,7 +95,7 @@ describe('BaseDORepo', () => { }); describe('entityFactory', () => { - const props: ITestEntityProperties = { + const props: TestEntityProperties = { name: 'name', }; diff --git a/apps/server/src/shared/repo/contextexternaltool/context-external-tool.repo.ts b/apps/server/src/shared/repo/contextexternaltool/context-external-tool.repo.ts index 31053cd48b5..e51dac73b1c 100644 --- a/apps/server/src/shared/repo/contextexternaltool/context-external-tool.repo.ts +++ b/apps/server/src/shared/repo/contextexternaltool/context-external-tool.repo.ts @@ -1,18 +1,18 @@ import { EntityName } from '@mikro-orm/core'; import { EntityManager } from '@mikro-orm/mongodb'; -import { Injectable } from '@nestjs/common'; -import { BaseDORepo } from '@shared/repo'; -import { LegacyLogger } from '@src/core/logger'; import { ToolContextType } from '@modules/tool/common/enum/tool-context-type.enum'; import { ContextExternalTool, ContextRef } from '@modules/tool/context-external-tool/domain'; import { ContextExternalToolEntity, + ContextExternalToolProperties, ContextExternalToolType, - IContextExternalToolProperties, } from '@modules/tool/context-external-tool/entity'; import { ContextExternalToolQuery } from '@modules/tool/context-external-tool/uc/dto/context-external-tool.types'; import { SchoolExternalToolRefDO } from '@modules/tool/school-external-tool/domain'; import { SchoolExternalToolEntity } from '@modules/tool/school-external-tool/entity'; +import { Injectable } from '@nestjs/common'; +import { BaseDORepo } from '@shared/repo'; +import { LegacyLogger } from '@src/core/logger'; import { EntityId } from '../../domain'; import { ExternalToolRepoMapper } from '../externaltool'; import { ContextExternalToolScope } from './context-external-tool.scope'; @@ -21,7 +21,7 @@ import { ContextExternalToolScope } from './context-external-tool.scope'; export class ContextExternalToolRepo extends BaseDORepo< ContextExternalTool, ContextExternalToolEntity, - IContextExternalToolProperties + ContextExternalToolProperties > { constructor(protected readonly _em: EntityManager, protected readonly logger: LegacyLogger) { super(_em, logger); @@ -31,7 +31,7 @@ export class ContextExternalToolRepo extends BaseDORepo< return ContextExternalToolEntity; } - entityFactory(props: IContextExternalToolProperties): ContextExternalToolEntity { + entityFactory(props: ContextExternalToolProperties): ContextExternalToolEntity { return new ContextExternalToolEntity(props); } @@ -108,7 +108,7 @@ export class ContextExternalToolRepo extends BaseDORepo< }); } - mapDOToEntityProperties(entityDO: ContextExternalTool): IContextExternalToolProperties { + mapDOToEntityProperties(entityDO: ContextExternalTool): ContextExternalToolProperties { return { contextId: entityDO.contextRef.id, contextType: this.mapContextTypeToEntityType(entityDO.contextRef.type), diff --git a/apps/server/src/shared/repo/dashboard/dashboard.model.mapper.ts b/apps/server/src/shared/repo/dashboard/dashboard.model.mapper.ts index 4cac8dc0b10..e43f08d147d 100644 --- a/apps/server/src/shared/repo/dashboard/dashboard.model.mapper.ts +++ b/apps/server/src/shared/repo/dashboard/dashboard.model.mapper.ts @@ -1,14 +1,14 @@ -import { wrap, EntityManager } from '@mikro-orm/core'; +import { EntityManager, wrap } from '@mikro-orm/core'; import { Injectable, InternalServerErrorException } from '@nestjs/common'; import { + Course, DashboardEntity, + DashboardGridElementModel, + DashboardModelEntity, GridElement, GridElementWithPosition, - ILearnroom, + Learnroom, LearnroomTypes, - DashboardGridElementModel, - DashboardModelEntity, - Course, User, } from '@shared/domain'; @@ -39,7 +39,7 @@ export class DashboardModelMapper { return new DashboardEntity(modelEntity.id, { grid, userId: modelEntity.user.id }); } - mapReferenceToModel(reference: ILearnroom): Course { + mapReferenceToModel(reference: Learnroom): Course { const metadata = reference.getMetadata(); if (metadata.type === LearnroomTypes.Course) { const course = reference as Course; diff --git a/apps/server/src/shared/repo/dashboard/dashboard.repo.ts b/apps/server/src/shared/repo/dashboard/dashboard.repo.ts index c0353719696..30b463e9344 100644 --- a/apps/server/src/shared/repo/dashboard/dashboard.repo.ts +++ b/apps/server/src/shared/repo/dashboard/dashboard.repo.ts @@ -1,6 +1,6 @@ -import { Injectable } from '@nestjs/common'; -import { EntityId, DashboardEntity, GridElementWithPosition, DashboardModelEntity } from '@shared/domain'; import { EntityManager, ObjectId } from '@mikro-orm/mongodb'; +import { Injectable } from '@nestjs/common'; +import { DashboardEntity, DashboardModelEntity, EntityId, GridElementWithPosition } from '@shared/domain'; import { DashboardModelMapper } from './dashboard.model.mapper'; const generateEmptyDashboard = (userId: EntityId) => { diff --git a/apps/server/src/shared/repo/externaltool/external-tool.repo.ts b/apps/server/src/shared/repo/externaltool/external-tool.repo.ts index 4ea69a54855..3d071311d82 100644 --- a/apps/server/src/shared/repo/externaltool/external-tool.repo.ts +++ b/apps/server/src/shared/repo/externaltool/external-tool.repo.ts @@ -1,13 +1,13 @@ import { EntityName, QueryOrderMap } from '@mikro-orm/core'; import { EntityManager } from '@mikro-orm/mongodb'; -import { Injectable } from '@nestjs/common/decorators/core/injectable.decorator'; -import { IFindOptions, IPagination, Page, SortOrder } from '@shared/domain'; -import { BaseDORepo, ExternalToolRepoMapper, ExternalToolSortingMapper, Scope } from '@shared/repo'; -import { LegacyLogger } from '@src/core/logger'; import { ToolConfigType } from '@modules/tool/common/enum'; import { ExternalToolSearchQuery } from '@modules/tool/common/interface'; import { ExternalTool } from '@modules/tool/external-tool/domain'; import { ExternalToolEntity, IExternalToolProperties } from '@modules/tool/external-tool/entity'; +import { Injectable } from '@nestjs/common/decorators/core/injectable.decorator'; +import { IFindOptions, Page, Pagination, SortOrder } from '@shared/domain'; +import { BaseDORepo, ExternalToolRepoMapper, ExternalToolSortingMapper, Scope } from '@shared/repo'; +import { LegacyLogger } from '@src/core/logger'; import { ExternalToolScope } from './external-tool.scope'; @Injectable() @@ -52,7 +52,7 @@ export class ExternalToolRepo extends BaseDORepo): Promise> { - const pagination: IPagination = options?.pagination || {}; + const pagination: Pagination = options?.pagination || {}; const order: QueryOrderMap = ExternalToolSortingMapper.mapDOSortOrderToQueryOrder( options?.order || {} ); diff --git a/apps/server/src/shared/repo/school/legacy-school.repo.integration.spec.ts b/apps/server/src/shared/repo/school/legacy-school.repo.integration.spec.ts index 775c193675d..097f5498685 100644 --- a/apps/server/src/shared/repo/school/legacy-school.repo.integration.spec.ts +++ b/apps/server/src/shared/repo/school/legacy-school.repo.integration.spec.ts @@ -1,19 +1,19 @@ import { createMock } from '@golevelup/ts-jest'; +import { MongoMemoryDatabaseModule } from '@infra/database'; import { EntityManager } from '@mikro-orm/core'; import { ObjectId } from '@mikro-orm/mongodb'; import { InternalServerErrorException } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { - ISchoolProperties, LegacySchoolDo, SchoolEntity, + SchoolProperties, SchoolRolePermission, SchoolRoles, SchoolYearEntity, SystemEntity, UserLoginMigrationEntity, } from '@shared/domain'; -import { MongoMemoryDatabaseModule } from '@infra/database'; import { legacySchoolDoFactory, schoolFactory, @@ -241,10 +241,10 @@ describe('LegacySchoolRepo', () => { }; }; - it('should map SchoolDO properties to ISchoolProperties', async () => { + it('should map SchoolDO properties to SchoolProperties', async () => { const { entityDO, emGetReferenceSpy, system1, system2, userLoginMigration } = await setup(); - const result: ISchoolProperties = repo.mapDOToEntityProperties(entityDO); + const result: SchoolProperties = repo.mapDOToEntityProperties(entityDO); expect(result.externalId).toEqual(entityDO.externalId); expect(result.features).toEqual(entityDO.features); diff --git a/apps/server/src/shared/repo/school/legacy-school.repo.ts b/apps/server/src/shared/repo/school/legacy-school.repo.ts index 711eaea27d9..a8b5265a5c4 100644 --- a/apps/server/src/shared/repo/school/legacy-school.repo.ts +++ b/apps/server/src/shared/repo/school/legacy-school.repo.ts @@ -3,9 +3,9 @@ import { EntityManager } from '@mikro-orm/mongodb'; import { Injectable, InternalServerErrorException } from '@nestjs/common'; import { EntityId, - ISchoolProperties, LegacySchoolDo, SchoolEntity, + SchoolProperties, SystemEntity, UserLoginMigrationEntity, } from '@shared/domain'; @@ -16,7 +16,7 @@ import { BaseDORepo } from '../base.do.repo'; * @deprecated because it uses the deprecated LegacySchoolDo. */ @Injectable() -export class LegacySchoolRepo extends BaseDORepo { +export class LegacySchoolRepo extends BaseDORepo { constructor(protected readonly _em: EntityManager, protected readonly logger: LegacyLogger) { super(_em, logger); } @@ -42,7 +42,7 @@ export class LegacySchoolRepo extends BaseDORepo { constructor(protected readonly _em: EntityManager, protected readonly logger: LegacyLogger) { super(_em, logger); @@ -25,7 +25,7 @@ export class SchoolExternalToolRepo extends BaseDORepo< return SchoolExternalToolEntity; } - entityFactory(props: ISchoolExternalToolProperties): SchoolExternalToolEntity { + entityFactory(props: SchoolExternalToolProperties): SchoolExternalToolEntity { return new SchoolExternalToolEntity(props); } @@ -82,7 +82,7 @@ export class SchoolExternalToolRepo extends BaseDORepo< }); } - mapDOToEntityProperties(entityDO: SchoolExternalTool): ISchoolExternalToolProperties { + mapDOToEntityProperties(entityDO: SchoolExternalTool): SchoolExternalToolProperties { return { school: this._em.getReference(SchoolEntity, entityDO.schoolId), tool: this._em.getReference(ExternalToolEntity, entityDO.toolId), diff --git a/apps/server/src/shared/repo/scope.spec.ts b/apps/server/src/shared/repo/scope.spec.ts index cf0e88b4d3b..894f7c9fe65 100644 --- a/apps/server/src/shared/repo/scope.spec.ts +++ b/apps/server/src/shared/repo/scope.spec.ts @@ -2,7 +2,7 @@ import { Entity, Property } from '@mikro-orm/core'; import { EmptyResultQuery } from './query/empty-result.query'; import { Scope } from './scope'; -export interface ITestEntityProperties { +export interface TestEntityProperties { name: string; numbers?: number[]; } @@ -15,7 +15,7 @@ class TestEntity { @Property() numbers: number[]; - constructor(props: ITestEntityProperties) { + constructor(props: TestEntityProperties) { this.name = props.name; this.numbers = props.numbers || []; } diff --git a/apps/server/src/shared/repo/user/user-do.repo.integration.spec.ts b/apps/server/src/shared/repo/user/user-do.repo.integration.spec.ts index 327c1728ccb..451285eebca 100644 --- a/apps/server/src/shared/repo/user/user-do.repo.integration.spec.ts +++ b/apps/server/src/shared/repo/user/user-do.repo.integration.spec.ts @@ -7,7 +7,6 @@ import { Test, TestingModule } from '@nestjs/testing'; import { EntityNotFoundError } from '@shared/common'; import { IFindOptions, - IUserProperties, LanguageType, Role, RoleName, @@ -15,6 +14,7 @@ import { SortOrder, SystemEntity, User, + UserProperties, } from '@shared/domain'; import { Page } from '@shared/domain/domainobject/page'; import { UserDO } from '@shared/domain/domainobject/user.do'; @@ -67,7 +67,7 @@ describe('UserRepo', () => { }); describe('entityFactory', () => { - const props: IUserProperties = { + const props: UserProperties = { email: 'email@email.email', firstName: 'firstName', lastName: 'lastName', @@ -306,9 +306,9 @@ describe('UserRepo', () => { 'testId' ); - const result: IUserProperties = repo.mapDOToEntityProperties(testDO); + const result: UserProperties = repo.mapDOToEntityProperties(testDO); - expect(result).toEqual({ + expect(result).toEqual({ email: testDO.email, firstName: testDO.firstName, lastName: testDO.lastName, 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 7d536596b77..c28a1915eda 100644 --- a/apps/server/src/shared/repo/user/user-do.repo.ts +++ b/apps/server/src/shared/repo/user/user-do.repo.ts @@ -5,14 +5,14 @@ import { EntityNotFoundError } from '@shared/common'; import { EntityId, IFindOptions, - IPagination, - IUserProperties, + Pagination, Role, SchoolEntity, SortOrder, SortOrderMap, SystemEntity, User, + UserProperties, } from '@shared/domain'; import { RoleReference } from '@shared/domain/domainobject'; import { Page } from '@shared/domain/domainobject/page'; @@ -21,17 +21,17 @@ import { BaseDORepo, Scope } from '@shared/repo'; import { UserScope } from './user.scope'; @Injectable() -export class UserDORepo extends BaseDORepo { +export class UserDORepo extends BaseDORepo { get entityName(): EntityName { return User; } - entityFactory(props: IUserProperties): User { + entityFactory(props: UserProperties): User { return new User(props); } async find(query: UserQuery, options?: IFindOptions) { - const pagination: IPagination = options?.pagination || {}; + const pagination: Pagination = options?.pagination || {}; const order: QueryOrderMap = this.createQueryOrderMap(options?.order || {}); const scope: Scope = new UserScope() .bySchoolId(query.schoolId) @@ -121,7 +121,7 @@ export class UserDORepo extends BaseDORepo { return user; } - mapDOToEntityProperties(entityDO: UserDO): IUserProperties { + mapDOToEntityProperties(entityDO: UserDO): UserProperties { return { email: entityDO.email, firstName: entityDO.firstName, diff --git a/apps/server/src/shared/repo/user/user.repo.ts b/apps/server/src/shared/repo/user/user.repo.ts index a067953faba..44acafe6a80 100644 --- a/apps/server/src/shared/repo/user/user.repo.ts +++ b/apps/server/src/shared/repo/user/user.repo.ts @@ -7,7 +7,7 @@ import { EntityId, IFindOptions, ImportUser, - INameMatch, + NameMatch, Role, SchoolEntity, SortOrder, @@ -47,7 +47,7 @@ export class UserRepo extends BaseRepo { */ async findWithoutImportUser( school: SchoolEntity, - filters?: INameMatch, + filters?: NameMatch, options?: IFindOptions ): Promise> { const { _id: schoolId } = school; diff --git a/apps/server/src/shared/testing/factory/base.factory.spec.ts b/apps/server/src/shared/testing/factory/base.factory.spec.ts index 8e681aff656..6c07cfceca8 100644 --- a/apps/server/src/shared/testing/factory/base.factory.spec.ts +++ b/apps/server/src/shared/testing/factory/base.factory.spec.ts @@ -1,7 +1,7 @@ import { BaseFactory } from './base.factory'; describe('BaseFactory', () => { - interface IUserProperties { + interface UserProperties { email: string; roles: string[]; nickName?: string; @@ -14,7 +14,7 @@ describe('BaseFactory', () => { nickName?: string; - constructor(props: IUserProperties) { + constructor(props: UserProperties) { this.email = props.email; this.roles = props.roles; this.nickName = props.nickName; @@ -29,7 +29,7 @@ describe('BaseFactory', () => { describe('when defining the factory', () => { it('should call the constructor', () => { const Constructor = jest.fn(); - const factory = BaseFactory.define(Constructor, () => { + const factory = BaseFactory.define(Constructor, () => { return { email: 'joe@example.com', roles: ['member'], @@ -41,7 +41,7 @@ describe('BaseFactory', () => { }); it('should create an instance of the class', () => { - const factory = BaseFactory.define(User, () => { + const factory = BaseFactory.define(User, () => { return { email: 'joe@example.com', roles: ['member'], @@ -52,7 +52,7 @@ describe('BaseFactory', () => { }); it('should override default properties', () => { - const factory = BaseFactory.define(User, () => { + const factory = BaseFactory.define(User, () => { return { email: 'joe@example.com', roles: ['member'], @@ -63,7 +63,7 @@ describe('BaseFactory', () => { }); it('should call afterBuild hook', () => { - const factory = BaseFactory.define(User, () => { + const factory = BaseFactory.define(User, () => { return { email: 'joe@example.com', roles: ['member'], @@ -79,7 +79,7 @@ describe('BaseFactory', () => { }); it('should delegate transient params as a trait', () => { - const factory = BaseFactory.define(User, ({ transientParams }) => { + const factory = BaseFactory.define(User, ({ transientParams }) => { const { registered, numTasks } = transientParams; return { email: `joe-${registered ? 'r' : 'u'}-${numTasks || '0'}@example.com`, roles: ['member'] }; }); @@ -88,7 +88,7 @@ describe('BaseFactory', () => { }); it('should delegate transientParams as a build option', () => { - const factory = BaseFactory.define(User, ({ transientParams }) => { + const factory = BaseFactory.define(User, ({ transientParams }) => { const { registered, numTasks } = transientParams; return { email: `joe-${registered ? 'r' : 'u'}-${numTasks || '0'}@example.com`, roles: ['member'] }; }); @@ -97,7 +97,7 @@ describe('BaseFactory', () => { }); it('should delegate associations as a trait', () => { - const factory = BaseFactory.define(User, ({ associations }) => { + const factory = BaseFactory.define(User, ({ associations }) => { return { email: 'joe@example.com', roles: associations.roles || ['member'], @@ -108,7 +108,7 @@ describe('BaseFactory', () => { }); it('should delegate associations as build option', () => { - const factory = BaseFactory.define(User, ({ associations }) => { + const factory = BaseFactory.define(User, ({ associations }) => { return { email: 'joe@example.com', roles: associations.roles || ['member'], @@ -120,7 +120,7 @@ describe('BaseFactory', () => { }); describe('when subclassing the factory', () => { - class UserFactory extends BaseFactory { + class UserFactory extends BaseFactory { admin() { return this.params({ roles: ['admin'] }); } @@ -168,7 +168,7 @@ describe('BaseFactory', () => { describe('when builing a list of objects', () => { it('should call the constructor for each item', () => { const Constructor = jest.fn(); - const factory = BaseFactory.define(Constructor, ({ sequence }) => { + const factory = BaseFactory.define(Constructor, ({ sequence }) => { return { email: `joe-${sequence}@example.com`, roles: ['member'], @@ -179,7 +179,7 @@ describe('BaseFactory', () => { }); it('should create an instance of the class for each item', () => { - const factory = BaseFactory.define(User, ({ sequence }) => { + const factory = BaseFactory.define(User, ({ sequence }) => { return { email: `joe-${sequence}@example.com`, roles: ['member'], @@ -192,7 +192,7 @@ describe('BaseFactory', () => { }); it('should override properties', () => { - const factory = BaseFactory.define(User, ({ sequence }) => { + const factory = BaseFactory.define(User, ({ sequence }) => { return { email: `joe-${sequence}@example.com`, roles: ['member'], diff --git a/apps/server/src/shared/testing/factory/context-external-tool-entity.factory.ts b/apps/server/src/shared/testing/factory/context-external-tool-entity.factory.ts index fb545c1e2e7..3e7f4e4b1cb 100644 --- a/apps/server/src/shared/testing/factory/context-external-tool-entity.factory.ts +++ b/apps/server/src/shared/testing/factory/context-external-tool-entity.factory.ts @@ -1,16 +1,16 @@ -import { BaseFactory } from '@shared/testing/factory/base.factory'; import { CustomParameterEntryEntity } from '@modules/tool/common/entity'; import { ContextExternalToolEntity, + ContextExternalToolProperties, ContextExternalToolType, - IContextExternalToolProperties, } from '@modules/tool/context-external-tool/entity'; +import { BaseFactory } from '@shared/testing/factory/base.factory'; import { courseFactory } from './course.factory'; import { schoolExternalToolEntityFactory } from './school-external-tool-entity.factory'; export const contextExternalToolEntityFactory = BaseFactory.define< ContextExternalToolEntity, - IContextExternalToolProperties + ContextExternalToolProperties >(ContextExternalToolEntity, () => { return { contextId: courseFactory.buildWithId().id, diff --git a/apps/server/src/shared/testing/factory/course.factory.ts b/apps/server/src/shared/testing/factory/course.factory.ts index 150b7b2270b..a0418771486 100644 --- a/apps/server/src/shared/testing/factory/course.factory.ts +++ b/apps/server/src/shared/testing/factory/course.factory.ts @@ -1,38 +1,38 @@ import { DeepPartial } from 'fishery'; -import { Course, ICourseProperties } from '@shared/domain'; +import { Course, CourseProperties } from '@shared/domain'; -import { schoolFactory } from './school.factory'; import { BaseFactory } from './base.factory'; +import { schoolFactory } from './school.factory'; import { userFactory } from './user.factory'; const oneDay = 24 * 60 * 60 * 1000; -class CourseFactory extends BaseFactory { +class CourseFactory extends BaseFactory { isFinished(): this { const untilDate = new Date(Date.now() - oneDay); - const params: DeepPartial = { untilDate }; + const params: DeepPartial = { untilDate }; return this.params(params); } isOpen(): this { const untilDate = new Date(Date.now() + oneDay); - const params: DeepPartial = { untilDate }; + const params: DeepPartial = { untilDate }; return this.params(params); } studentsWithId(numberOfStudents: number): this { const students = userFactory.buildListWithId(numberOfStudents); - const params: DeepPartial = { students }; + const params: DeepPartial = { students }; return this.params(params); } teachersWithId(numberOfTeachers: number): this { const teachers = userFactory.buildListWithId(numberOfTeachers); - const params: DeepPartial = { teachers }; + const params: DeepPartial = { teachers }; return this.params(params); } diff --git a/apps/server/src/shared/testing/factory/coursegroup.factory.ts b/apps/server/src/shared/testing/factory/coursegroup.factory.ts index f1408fd664d..3b98de4a17c 100644 --- a/apps/server/src/shared/testing/factory/coursegroup.factory.ts +++ b/apps/server/src/shared/testing/factory/coursegroup.factory.ts @@ -1,13 +1,13 @@ -import { CourseGroup, ICourseGroupProperties } from '@shared/domain'; +import { CourseGroup, CourseGroupProperties } from '@shared/domain'; import { DeepPartial } from 'fishery'; -import { courseFactory } from './course.factory'; import { BaseFactory } from './base.factory'; +import { courseFactory } from './course.factory'; import { userFactory } from './user.factory'; -class CourseGroupFactory extends BaseFactory { +class CourseGroupFactory extends BaseFactory { studentsWithId(numberOfStudents: number): this { const students = userFactory.buildListWithId(numberOfStudents); - const params: DeepPartial = { students }; + const params: DeepPartial = { students }; return this.params(params); } diff --git a/apps/server/src/shared/testing/factory/external-tool-pseudonym.factory.ts b/apps/server/src/shared/testing/factory/external-tool-pseudonym.factory.ts index b3ee2595412..d21bfbcff78 100644 --- a/apps/server/src/shared/testing/factory/external-tool-pseudonym.factory.ts +++ b/apps/server/src/shared/testing/factory/external-tool-pseudonym.factory.ts @@ -1,10 +1,10 @@ -import { BaseFactory } from '@shared/testing/factory/base.factory'; import { ObjectId } from '@mikro-orm/mongodb'; -import { ExternalToolPseudonymEntity, IExternalToolPseudonymEntityProps } from '@modules/pseudonym/entity'; +import { ExternalToolPseudonymEntity, ExternalToolPseudonymEntityProps } from '@modules/pseudonym/entity'; +import { BaseFactory } from '@shared/testing/factory/base.factory'; export const externalToolPseudonymEntityFactory = BaseFactory.define< ExternalToolPseudonymEntity, - IExternalToolPseudonymEntityProps + ExternalToolPseudonymEntityProps >(ExternalToolPseudonymEntity, ({ sequence }) => { return { pseudonym: `pseudonym-${sequence}`, diff --git a/apps/server/src/shared/testing/factory/federal-state.factory.ts b/apps/server/src/shared/testing/factory/federal-state.factory.ts index 71290151668..11a685140a5 100644 --- a/apps/server/src/shared/testing/factory/federal-state.factory.ts +++ b/apps/server/src/shared/testing/factory/federal-state.factory.ts @@ -1,7 +1,7 @@ -import { County, FederalStateEntity, IFederalStateProperties } from '@shared/domain'; +import { County, FederalStateEntity, FederalStateProperties } from '@shared/domain'; import { BaseFactory } from './base.factory'; -export const federalStateFactory = BaseFactory.define( +export const federalStateFactory = BaseFactory.define( FederalStateEntity, () => { return { diff --git a/apps/server/src/shared/testing/factory/filerecord.factory.ts b/apps/server/src/shared/testing/factory/filerecord.factory.ts index 4e12787f661..c05c1679ed2 100644 --- a/apps/server/src/shared/testing/factory/filerecord.factory.ts +++ b/apps/server/src/shared/testing/factory/filerecord.factory.ts @@ -1,14 +1,14 @@ import { FileRecordParentType } from '@infra/rabbitmq'; -import { FileRecord, FileRecordSecurityCheck, IFileRecordProperties } from '@modules/files-storage/entity'; +import { FileRecord, FileRecordProperties, FileRecordSecurityCheck } from '@modules/files-storage/entity'; import { ObjectId } from 'bson'; import { DeepPartial } from 'fishery'; import { BaseFactory } from './base.factory'; const yesterday = new Date(Date.now() - 86400000); -class FileRecordFactory extends BaseFactory { +class FileRecordFactory extends BaseFactory { markedForDelete(): this { - const params: DeepPartial = { deletedSince: yesterday }; + const params: DeepPartial = { deletedSince: yesterday }; return this.params(params); } } diff --git a/apps/server/src/shared/testing/factory/h5p-content.factory.ts b/apps/server/src/shared/testing/factory/h5p-content.factory.ts index 4d07c369cd5..7931f9b1529 100644 --- a/apps/server/src/shared/testing/factory/h5p-content.factory.ts +++ b/apps/server/src/shared/testing/factory/h5p-content.factory.ts @@ -2,12 +2,12 @@ import { ContentMetadata, H5PContent, H5PContentParentType, - IH5PContentProperties, + H5PContentProperties, } from '@src/modules/h5p-editor/entity'; import { ObjectID } from 'bson'; import { BaseFactory } from './base.factory'; -class H5PContentFactory extends BaseFactory {} +class H5PContentFactory extends BaseFactory {} export const h5pContentFactory = H5PContentFactory.define(H5PContent, ({ sequence }) => { return { diff --git a/apps/server/src/shared/testing/factory/h5p-temporary-file.factory.ts b/apps/server/src/shared/testing/factory/h5p-temporary-file.factory.ts index 4c9fbea5b11..4f2205b0490 100644 --- a/apps/server/src/shared/testing/factory/h5p-temporary-file.factory.ts +++ b/apps/server/src/shared/testing/factory/h5p-temporary-file.factory.ts @@ -1,14 +1,14 @@ -import { ITemporaryFileProperties, H5pEditorTempFile } from '@src/modules/h5p-editor/entity'; +import { H5pEditorTempFile, TemporaryFileProperties } from '@src/modules/h5p-editor/entity'; import { DeepPartial } from 'fishery'; import { BaseFactory } from './base.factory'; const oneDay = 24 * 60 * 60 * 1000; -class H5PTemporaryFileFactory extends BaseFactory { +class H5PTemporaryFileFactory extends BaseFactory { isExpired(): this { const birthtime = new Date(Date.now() - oneDay * 2); // Created two days ago const expiresAt = new Date(Date.now() - oneDay); // Expired yesterday - const params: DeepPartial = { expiresAt, birthtime }; + const params: DeepPartial = { expiresAt, birthtime }; return this.params(params); } diff --git a/apps/server/src/shared/testing/factory/import-user.factory.ts b/apps/server/src/shared/testing/factory/import-user.factory.ts index 1dd8fd3c024..d2cc011b995 100644 --- a/apps/server/src/shared/testing/factory/import-user.factory.ts +++ b/apps/server/src/shared/testing/factory/import-user.factory.ts @@ -1,14 +1,14 @@ import { v4 as uuidv4 } from 'uuid'; -import { IImportUserProperties, IImportUserRoleName, ImportUser, MatchCreator, RoleName, User } from '@shared/domain'; +import { IImportUserRoleName, ImportUser, ImportUserProperties, MatchCreator, RoleName, User } from '@shared/domain'; import { DeepPartial } from 'fishery'; import { BaseFactory } from './base.factory'; import { schoolFactory } from './school.factory'; import { systemFactory } from './system.factory'; -class ImportUserFactory extends BaseFactory { +class ImportUserFactory extends BaseFactory { matched(matchedBy: MatchCreator, user: User): this { - const params: DeepPartial = { matchedBy, user }; + const params: DeepPartial = { matchedBy, user }; return this.params(params); } } diff --git a/apps/server/src/shared/testing/factory/lesson.factory.ts b/apps/server/src/shared/testing/factory/lesson.factory.ts index af7aff56fc1..a83d47cec80 100644 --- a/apps/server/src/shared/testing/factory/lesson.factory.ts +++ b/apps/server/src/shared/testing/factory/lesson.factory.ts @@ -1,11 +1,11 @@ -import { Course, IComponentProperties, ILessonProperties, LessonEntity } from '@shared/domain'; +import { ComponentProperties, Course, LessonEntity, LessonProperties } from '@shared/domain'; import { BaseFactory } from './base.factory'; import { courseFactory } from './course.factory'; -class LessonFactory extends BaseFactory {} +class LessonFactory extends BaseFactory {} -export const lessonFactory = LessonFactory.define( +export const lessonFactory = LessonFactory.define( LessonEntity, ({ sequence, params }) => { let course: Course; @@ -15,7 +15,7 @@ export const lessonFactory = LessonFactory.define { contents.push(element); diff --git a/apps/server/src/shared/testing/factory/material.factory.ts b/apps/server/src/shared/testing/factory/material.factory.ts index 62d77f1e116..5d0e07e4936 100644 --- a/apps/server/src/shared/testing/factory/material.factory.ts +++ b/apps/server/src/shared/testing/factory/material.factory.ts @@ -1,9 +1,9 @@ -import { IMaterialProperties, Material } from '@shared/domain/entity/materials.entity'; +import { Material, MaterialProperties } from '@shared/domain/entity/materials.entity'; import { BaseFactory } from './base.factory'; -class MaterialFactory extends BaseFactory {} +class MaterialFactory extends BaseFactory {} -export const materialFactory = MaterialFactory.define(Material, ({ sequence }) => { +export const materialFactory = MaterialFactory.define(Material, ({ sequence }) => { return { client: 'test material client', description: 'test material description', diff --git a/apps/server/src/shared/testing/factory/news.factory.ts b/apps/server/src/shared/testing/factory/news.factory.ts index 56b59cedf6c..55788cea9de 100644 --- a/apps/server/src/shared/testing/factory/news.factory.ts +++ b/apps/server/src/shared/testing/factory/news.factory.ts @@ -1,11 +1,11 @@ -import { SchoolNews, CourseNews, TeamNews, INewsProperties } from '@shared/domain'; +import { CourseNews, NewsProperties, SchoolNews, TeamNews } from '@shared/domain'; import { BaseFactory } from './base.factory'; import { courseFactory } from './course.factory'; import { schoolFactory } from './school.factory'; import { teamFactory } from './team.factory'; import { userFactory } from './user.factory'; -export const schoolNewsFactory = BaseFactory.define(SchoolNews, ({ sequence }) => { +export const schoolNewsFactory = BaseFactory.define(SchoolNews, ({ sequence }) => { return { title: `news ${sequence}`, content: `content of news ${sequence}`, @@ -16,7 +16,7 @@ export const schoolNewsFactory = BaseFactory.define }; }); -export const courseNewsFactory = BaseFactory.define(CourseNews, ({ sequence }) => { +export const courseNewsFactory = BaseFactory.define(CourseNews, ({ sequence }) => { return { title: `news ${sequence}`, content: `content of news ${sequence}`, @@ -27,7 +27,7 @@ export const courseNewsFactory = BaseFactory.define }; }); -export const teamNewsFactory = BaseFactory.define(TeamNews, ({ sequence }) => { +export const teamNewsFactory = BaseFactory.define(TeamNews, ({ sequence }) => { return { title: `news ${sequence}`, content: `content of news ${sequence}`, @@ -38,7 +38,7 @@ export const teamNewsFactory = BaseFactory.define(Tea }; }); -export const schoolUnpublishedNewsFactory = BaseFactory.define( +export const schoolUnpublishedNewsFactory = BaseFactory.define( SchoolNews, ({ sequence }) => { return { @@ -52,7 +52,7 @@ export const schoolUnpublishedNewsFactory = BaseFactory.define( +export const courseUnpublishedNewsFactory = BaseFactory.define( CourseNews, ({ sequence }) => { return { @@ -66,7 +66,7 @@ export const courseUnpublishedNewsFactory = BaseFactory.define(TeamNews, ({ sequence }) => { +export const teamUnpublishedNewsFactory = BaseFactory.define(TeamNews, ({ sequence }) => { return { title: `news ${sequence}`, content: `content of news ${sequence}`, diff --git a/apps/server/src/shared/testing/factory/role.factory.ts b/apps/server/src/shared/testing/factory/role.factory.ts index f37236c2587..bd987e198b5 100644 --- a/apps/server/src/shared/testing/factory/role.factory.ts +++ b/apps/server/src/shared/testing/factory/role.factory.ts @@ -1,7 +1,7 @@ -import { IRoleProperties, Role, RoleName } from '@shared/domain'; +import { Role, RoleName, RoleProperties } from '@shared/domain'; import { BaseFactory } from './base.factory'; -export const roleFactory = BaseFactory.define(Role, ({ sequence }) => { +export const roleFactory = BaseFactory.define(Role, ({ sequence }) => { return { name: `role${sequence}` as unknown as RoleName, }; diff --git a/apps/server/src/shared/testing/factory/school-external-tool-entity.factory.ts b/apps/server/src/shared/testing/factory/school-external-tool-entity.factory.ts index 456356e6e23..023e3a2626d 100644 --- a/apps/server/src/shared/testing/factory/school-external-tool-entity.factory.ts +++ b/apps/server/src/shared/testing/factory/school-external-tool-entity.factory.ts @@ -1,11 +1,11 @@ +import { SchoolExternalToolEntity, SchoolExternalToolProperties } from '@modules/tool/school-external-tool/entity'; import { BaseFactory } from '@shared/testing/factory/base.factory'; -import { ISchoolExternalToolProperties, SchoolExternalToolEntity } from '@modules/tool/school-external-tool/entity'; import { externalToolEntityFactory } from './external-tool-entity.factory'; import { schoolFactory } from './school.factory'; export const schoolExternalToolEntityFactory = BaseFactory.define< SchoolExternalToolEntity, - ISchoolExternalToolProperties + SchoolExternalToolProperties >(SchoolExternalToolEntity, () => { return { tool: externalToolEntityFactory.buildWithId(), diff --git a/apps/server/src/shared/testing/factory/school.factory.ts b/apps/server/src/shared/testing/factory/school.factory.ts index c14dd95130d..1e71e816923 100644 --- a/apps/server/src/shared/testing/factory/school.factory.ts +++ b/apps/server/src/shared/testing/factory/school.factory.ts @@ -1,9 +1,9 @@ -import { ISchoolProperties, SchoolEntity } from '@shared/domain'; +import { SchoolEntity, SchoolProperties } from '@shared/domain'; import { BaseFactory } from './base.factory'; -import { schoolYearFactory } from './schoolyear.factory'; import { federalStateFactory } from './federal-state.factory'; +import { schoolYearFactory } from './schoolyear.factory'; -export const schoolFactory = BaseFactory.define(SchoolEntity, ({ sequence }) => { +export const schoolFactory = BaseFactory.define(SchoolEntity, ({ sequence }) => { return { name: `school #${sequence}`, schoolYear: schoolYearFactory.build(), diff --git a/apps/server/src/shared/testing/factory/schoolyear.factory.ts b/apps/server/src/shared/testing/factory/schoolyear.factory.ts index a1184ed66d3..b60e85c72e9 100644 --- a/apps/server/src/shared/testing/factory/schoolyear.factory.ts +++ b/apps/server/src/shared/testing/factory/schoolyear.factory.ts @@ -1,7 +1,7 @@ -import { ISchoolYearProperties, SchoolYearEntity } from '@shared/domain/entity/schoolyear.entity'; +import { SchoolYearEntity, SchoolYearProperties } from '@shared/domain/entity/schoolyear.entity'; import { BaseFactory } from './base.factory'; -export const schoolYearFactory = BaseFactory.define(SchoolYearEntity, () => { +export const schoolYearFactory = BaseFactory.define(SchoolYearEntity, () => { const year = new Date().getFullYear(); const nextYear = (year + 1).toString().substr(-2); const name = `${year}/${nextYear}`; diff --git a/apps/server/src/shared/testing/factory/storageprovider.factory.ts b/apps/server/src/shared/testing/factory/storageprovider.factory.ts index d0ad53be107..0406b647978 100644 --- a/apps/server/src/shared/testing/factory/storageprovider.factory.ts +++ b/apps/server/src/shared/testing/factory/storageprovider.factory.ts @@ -1,7 +1,7 @@ -import { StorageProviderEntity, IStorageProviderProperties } from '@shared/domain'; +import { StorageProviderEntity, StorageProviderProperties } from '@shared/domain'; import { BaseFactory } from './base.factory'; -export const storageProviderFactory = BaseFactory.define( +export const storageProviderFactory = BaseFactory.define( StorageProviderEntity, () => { return { diff --git a/apps/server/src/shared/testing/factory/submission.factory.ts b/apps/server/src/shared/testing/factory/submission.factory.ts index 09e057da78e..a667e2de37c 100644 --- a/apps/server/src/shared/testing/factory/submission.factory.ts +++ b/apps/server/src/shared/testing/factory/submission.factory.ts @@ -1,32 +1,32 @@ -import { ISubmissionProperties, Submission } from '@shared/domain'; +import { Submission, SubmissionProperties } from '@shared/domain'; import { DeepPartial } from 'fishery'; import { BaseFactory } from './base.factory'; import { schoolFactory } from './school.factory'; import { taskFactory } from './task.factory'; import { userFactory } from './user.factory'; -class SubmissionFactory extends BaseFactory { +class SubmissionFactory extends BaseFactory { graded(): this { - const params: DeepPartial = { graded: true }; + const params: DeepPartial = { graded: true }; return this.params(params); } submitted(): this { - const params: DeepPartial = { submitted: true }; + const params: DeepPartial = { submitted: true }; return this.params(params); } studentWithId(): this { - const params: DeepPartial = { student: userFactory.buildWithId() }; + const params: DeepPartial = { student: userFactory.buildWithId() }; return this.params(params); } teamMembersWithId(numberOfTeamMembers: number): this { const teamMembers = userFactory.buildListWithId(numberOfTeamMembers); - const params: DeepPartial = { teamMembers }; + const params: DeepPartial = { teamMembers }; return this.params(params); } diff --git a/apps/server/src/shared/testing/factory/system.factory.ts b/apps/server/src/shared/testing/factory/system.factory.ts index 3745736b7e1..f686c406851 100644 --- a/apps/server/src/shared/testing/factory/system.factory.ts +++ b/apps/server/src/shared/testing/factory/system.factory.ts @@ -1,11 +1,11 @@ -import { ISystemProperties, LdapConfig, OauthConfig, OidcConfig, SystemEntity } from '@shared/domain'; +import { LdapConfig, OauthConfig, OidcConfig, SystemEntity, SystemProperties } from '@shared/domain'; import { SystemProvisioningStrategy } from '@shared/domain/interface/system-provisioning.strategy'; import { DeepPartial } from 'fishery'; import { BaseFactory } from './base.factory'; -export class SystemFactory extends BaseFactory { +export class SystemFactory extends BaseFactory { withOauthConfig(): this { - const params: DeepPartial = { + const params: DeepPartial = { oauthConfig: new OauthConfig({ clientId: '12345', clientSecret: 'mocksecret', @@ -26,7 +26,7 @@ export class SystemFactory extends BaseFactory } withLdapConfig(otherParams?: DeepPartial): this { - const params: DeepPartial = { + const params: DeepPartial = { ldapConfig: new LdapConfig({ url: 'ldaps:mock.de:389', active: true, diff --git a/apps/server/src/shared/testing/factory/task.factory.ts b/apps/server/src/shared/testing/factory/task.factory.ts index 40162267dd2..c3860940163 100644 --- a/apps/server/src/shared/testing/factory/task.factory.ts +++ b/apps/server/src/shared/testing/factory/task.factory.ts @@ -1,4 +1,4 @@ -import { ITaskProperties, Task } from '@shared/domain'; +import { Task, TaskProperties } from '@shared/domain'; import { User } from '@shared/domain/entity'; import { DeepPartial } from 'fishery'; import { BaseFactory } from './base.factory'; @@ -7,27 +7,27 @@ import { userFactory } from './user.factory'; const yesterday = new Date(Date.now() - 86400000); -class TaskFactory extends BaseFactory { +class TaskFactory extends BaseFactory { draft(): this { - const params: DeepPartial = { private: true }; + const params: DeepPartial = { private: true }; return this.params(params); } isPlanned(): this { - const params: DeepPartial = { private: false, availableDate: new Date(Date.now() + 10000) }; + const params: DeepPartial = { private: false, availableDate: new Date(Date.now() + 10000) }; return this.params(params); } isPublished(): this { - const params: DeepPartial = { private: false, availableDate: new Date(Date.now() - 10000) }; + const params: DeepPartial = { private: false, availableDate: new Date(Date.now() - 10000) }; return this.params(params); } finished(user: User): this { - const params: DeepPartial = { finished: [user] }; + const params: DeepPartial = { finished: [user] }; return this.params(params); } } diff --git a/apps/server/src/shared/testing/factory/team.factory.ts b/apps/server/src/shared/testing/factory/team.factory.ts index 1a72b84969f..332d3190c34 100644 --- a/apps/server/src/shared/testing/factory/team.factory.ts +++ b/apps/server/src/shared/testing/factory/team.factory.ts @@ -1,18 +1,18 @@ -import { ITeamProperties, Role, TeamEntity, TeamUserEntity } from '@shared/domain'; -import { DeepPartial } from 'fishery'; -import { teamUserFactory } from '@shared/testing/factory/teamuser.factory'; +import { Role, TeamEntity, TeamProperties, TeamUserEntity } from '@shared/domain'; import { BaseFactory } from '@shared/testing/factory/base.factory'; +import { teamUserFactory } from '@shared/testing/factory/teamuser.factory'; +import { DeepPartial } from 'fishery'; -class TeamFactory extends BaseFactory { +class TeamFactory extends BaseFactory { withRoleAndUserId(role: Role, userId: string): this { - const params: DeepPartial = { + const params: DeepPartial = { teamUsers: [teamUserFactory.withRoleAndUserId(role, userId).buildWithId()], }; return this.params(params); } withTeamUser(teamUser: TeamUserEntity[]): this { - const params: DeepPartial = { + const params: DeepPartial = { teamUsers: teamUser, }; return this.params(params); diff --git a/apps/server/src/shared/testing/factory/user.factory.ts b/apps/server/src/shared/testing/factory/user.factory.ts index 1557b3ccd35..5b09e5c46b5 100644 --- a/apps/server/src/shared/testing/factory/user.factory.ts +++ b/apps/server/src/shared/testing/factory/user.factory.ts @@ -1,5 +1,5 @@ /* istanbul ignore file */ -import { IUserProperties, Permission, Role, RoleName, User } from '@shared/domain'; +import { Permission, Role, RoleName, User, UserProperties } from '@shared/domain'; import { DeepPartial } from 'fishery'; import _ from 'lodash'; import { adminPermissions, studentPermissions, teacherPermissions, userPermissions } from '../user-role-permissions'; @@ -7,15 +7,15 @@ import { BaseFactory } from './base.factory'; import { roleFactory } from './role.factory'; import { schoolFactory } from './school.factory'; -class UserFactory extends BaseFactory { +class UserFactory extends BaseFactory { withRoleByName(name: RoleName): this { - const params: DeepPartial = { roles: [roleFactory.buildWithId({ name })] }; + const params: DeepPartial = { roles: [roleFactory.buildWithId({ name })] }; return this.params(params); } withRole(role: Role): this { - const params: DeepPartial = { roles: [role] }; + const params: DeepPartial = { roles: [role] }; return this.params(params); } @@ -24,7 +24,7 @@ class UserFactory extends BaseFactory { const permissions = _.union(userPermissions, studentPermissions, additionalPermissions); const role = roleFactory.buildWithId({ permissions, name: RoleName.STUDENT }); - const params: DeepPartial = { roles: [role] }; + const params: DeepPartial = { roles: [role] }; return this.params(params); } @@ -33,7 +33,7 @@ class UserFactory extends BaseFactory { const permissions = _.union(userPermissions, teacherPermissions, additionalPermissions); const role = roleFactory.buildWithId({ permissions, name: RoleName.TEACHER }); - const params: DeepPartial = { roles: [role] }; + const params: DeepPartial = { roles: [role] }; return this.params(params); } @@ -42,7 +42,7 @@ class UserFactory extends BaseFactory { const permissions = _.union(userPermissions, adminPermissions, additionalPermissions); const role = roleFactory.buildWithId({ permissions, name: RoleName.ADMINISTRATOR }); - const params: DeepPartial = { roles: [role] }; + const params: DeepPartial = { roles: [role] }; return this.params(params); } diff --git a/apps/server/src/shared/testing/map-user-to-current-user.ts b/apps/server/src/shared/testing/map-user-to-current-user.ts index b8c975f125d..3c6a1eadc27 100644 --- a/apps/server/src/shared/testing/map-user-to-current-user.ts +++ b/apps/server/src/shared/testing/map-user-to-current-user.ts @@ -1,6 +1,6 @@ import { ObjectId } from '@mikro-orm/mongodb'; -import { Account, EntityId, User } from '@shared/domain'; import { ICurrentUser } from '@modules/authentication'; +import { Account, EntityId, User } from '@shared/domain'; export const mapUserToCurrentUser = ( user: User, diff --git a/config/README.md b/config/README.md index cbdf77a73a9..69081ee6460 100644 --- a/config/README.md +++ b/config/README.md @@ -149,12 +149,12 @@ This code shows a minimal flow. ``` javascript // needed configuration for a module - export interface IUserConfig { + export interface UserConfig { AVAILABLE_LANGUAGES: string[]; } // server.config.ts - export interface IServerConfig extends ICoreModuleConfig, IUserConfig, IFilesStorageClientConfig { + export interface ServerConfig extends ICoreModuleConfig, UserConfig, IFilesStorageClientConfig { NODE_ENV: string; } @@ -176,9 +176,9 @@ This code shows a minimal flow. //use via injections import { ConfigService } from '@nestjs/config'; - import { IUserConfig } from '../interfaces'; + import { UserConfig } from '../interfaces'; - constructor(private readonly configService: ConfigService){} + constructor(private readonly configService: ConfigService){} this.configService.get('AVAILABLE_LANGUAGES');