Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EW-1056: Remove for await and improve legibility. #5425

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 101 additions & 56 deletions apps/server/src/infra/sync/tsp/tsp-oauth-data.mapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,21 @@ export class TspOauthDataMapper {
provisioningStrategy: SystemProvisioningStrategy.TSP,
});

const externalSchools = new Map<string, ExternalSchoolDto>();
const externalClasses = new Map<string, ExternalClassDto>();
const teacherForClasses = new Map<string, Array<string>>();
const externalSchools = this.createMapOfExternalSchoolDtos(schools);
const { externalClasses, teacherForClasses } = this.createMapsOfClasses(tspClasses);
const oauthDataDtos: OauthDataDto[] = [];

oauthDataDtos.push(
...this.mapTspTeachersToOauthDataDtos(tspTeachers, systemDto, externalSchools, externalClasses, teacherForClasses)
);
oauthDataDtos.push(...this.mapTspStudentsToOauthDataDtos(tspStudents, systemDto, externalSchools, externalClasses));

return oauthDataDtos;
}

private createMapOfExternalSchoolDtos(schools: School[]): Map<string, ExternalSchoolDto> {
const externalSchools = new Map<string, ExternalSchoolDto>();

schools.forEach((school) => {
if (!school.externalId) {
throw new BadDataLoggableException(`School ${school.id} has no externalId`);
Expand All @@ -51,6 +61,16 @@ export class TspOauthDataMapper {
);
});

return externalSchools;
}

private createMapsOfClasses(tspClasses: RobjExportKlasse[]): {
externalClasses: Map<string, ExternalClassDto>;
teacherForClasses: Map<string, Array<string>>;
} {
const externalClasses = new Map<string, ExternalClassDto>();
const teacherForClasses = new Map<string, Array<string>>();

tspClasses.forEach((tspClass) => {
if (!tspClass.klasseId) {
this.logger.info(new TspMissingExternalIdLoggable('class'));
Expand All @@ -71,62 +91,87 @@ export class TspOauthDataMapper {
}
});

tspTeachers.forEach((tspTeacher) => {
if (!tspTeacher.lehrerUid) {
this.logger.info(new TspMissingExternalIdLoggable('teacher'));
return;
}

const externalUser = new ExternalUserDto({
externalId: tspTeacher.lehrerUid,
firstName: tspTeacher.lehrerVorname,
lastName: tspTeacher.lehrerNachname,
roles: [RoleName.TEACHER],
});

const classIds = teacherForClasses.get(tspTeacher.lehrerUid) ?? [];
const classes: ExternalClassDto[] = classIds
.map((classId) => externalClasses.get(classId))
.filter((externalClass: ExternalClassDto | undefined): externalClass is ExternalClassDto => !!externalClass);

const externalSchool = tspTeacher.schuleNummer == null ? undefined : externalSchools.get(tspTeacher.schuleNummer);

const oauthDataDto = new OauthDataDto({
system: systemDto,
externalUser,
externalSchool,
externalClasses: classes,
});

oauthDataDtos.push(oauthDataDto);
});

tspStudents.forEach((tspStudent) => {
if (!tspStudent.schuelerUid) {
this.logger.info(new TspMissingExternalIdLoggable('student'));
return;
}

const externalUser = new ExternalUserDto({
externalId: tspStudent.schuelerUid,
firstName: tspStudent.schuelerVorname,
lastName: tspStudent.schuelerNachname,
roles: [RoleName.STUDENT],
});

const classStudent = tspStudent.klasseId == null ? undefined : externalClasses.get(tspStudent.klasseId);
return { externalClasses, teacherForClasses };
}

const externalSchool = tspStudent.schuleNummer == null ? undefined : externalSchools.get(tspStudent.schuleNummer);
private mapTspTeachersToOauthDataDtos(
tspTeachers: RobjExportLehrer[],
systemDto: ProvisioningSystemDto,
externalSchools: Map<string, ExternalSchoolDto>,
externalClasses: Map<string, ExternalClassDto>,
teacherForClasses: Map<string, Array<string>>
): OauthDataDto[] {
const oauthDataDtos = tspTeachers
.map((tspTeacher) => {
if (!tspTeacher.lehrerUid) {
this.logger.info(new TspMissingExternalIdLoggable('teacher'));
return null;
}

const externalUser = new ExternalUserDto({
externalId: tspTeacher.lehrerUid,
firstName: tspTeacher.lehrerVorname,
lastName: tspTeacher.lehrerNachname,
roles: [RoleName.TEACHER],
});

const classIds = teacherForClasses.get(tspTeacher.lehrerUid) ?? [];
const classes: ExternalClassDto[] = classIds
.map((classId) => externalClasses.get(classId))
.filter((externalClass: ExternalClassDto | undefined): externalClass is ExternalClassDto => !!externalClass);

const externalSchool =
tspTeacher.schuleNummer == null ? undefined : externalSchools.get(tspTeacher.schuleNummer);

const oauthDataDto = new OauthDataDto({
system: systemDto,
externalUser,
externalSchool,
externalClasses: classes,
});

return oauthDataDto;
})
.filter((oauthDataDto) => oauthDataDto !== null);

const oauthDataDto = new OauthDataDto({
system: systemDto,
externalUser,
externalSchool,
externalClasses: classStudent ? [classStudent] : [],
});
return oauthDataDtos;
}

oauthDataDtos.push(oauthDataDto);
});
private mapTspStudentsToOauthDataDtos(
tspStudents: RobjExportSchueler[],
systemDto: ProvisioningSystemDto,
externalSchools: Map<string, ExternalSchoolDto>,
externalClasses: Map<string, ExternalClassDto>
): OauthDataDto[] {
const oauthDataDtos = tspStudents
.map((tspStudent) => {
if (!tspStudent.schuelerUid) {
this.logger.info(new TspMissingExternalIdLoggable('student'));
return null;
}

const externalUser = new ExternalUserDto({
externalId: tspStudent.schuelerUid,
firstName: tspStudent.schuelerVorname,
lastName: tspStudent.schuelerNachname,
roles: [RoleName.STUDENT],
});

const classStudent = tspStudent.klasseId == null ? undefined : externalClasses.get(tspStudent.klasseId);

const externalSchool =
tspStudent.schuleNummer == null ? undefined : externalSchools.get(tspStudent.schuleNummer);

const oauthDataDto = new OauthDataDto({
system: systemDto,
externalUser,
externalSchool,
externalClasses: classStudent ? [classStudent] : [],
});

return oauthDataDto;
})
.filter((oauthDataDto) => oauthDataDto !== null);

return oauthDataDtos;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ export class TspSyncMigrationService {
}

private async saveUsersAndAccounts(users: UserDO[], accounts: Account[]): Promise<void> {
// These statement should probably not be combined with Promise.all() because last time I tried the performance massively decreased.
await this.userService.saveAll(users);
await this.accountService.saveAll(accounts);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ describe('TspProvisioningService', () => {

beforeEach(() => {
jest.resetAllMocks();
jest.restoreAllMocks();
jest.clearAllMocks();
});

it('should be defined', () => {
Expand Down Expand Up @@ -133,7 +135,7 @@ describe('TspProvisioningService', () => {
});

describe('provisionClasses', () => {
describe('when user ID is missing', () => {
describe('when user ID is missing and class exists', () => {
const setup = () => {
const school = schoolFactory.build();
const classes = [setupExternalClass()];
Expand All @@ -149,6 +151,24 @@ describe('TspProvisioningService', () => {
});
});

describe('when user ID is missing and class does not exist', () => {
const setup = () => {
const school = schoolFactory.build();
const classes = [setupExternalClass()];
const user = userDoFactory.build();

classServiceMock.findClassWithSchoolIdAndExternalId.mockResolvedValueOnce(null);

return { school, classes, user };
};

it('should throw', async () => {
const { school, classes, user } = setup();

await expect(sut.provisionClasses(school, classes, user)).rejects.toThrow(BadDataLoggableException);
});
});

describe('when class exists', () => {
const setup = () => {
const school = schoolFactory.build();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { AccountSave, AccountService } from '@modules/account';
import { ClassFactory, ClassService, ClassSourceOptions } from '@modules/class';
import { Class, ClassFactory, ClassService, ClassSourceOptions } from '@modules/class';
import { RoleService } from '@modules/role';
import { Injectable } from '@nestjs/common';
import { NotFoundLoggableException } from '@shared/common/loggable-exception';
Expand Down Expand Up @@ -45,41 +45,60 @@ export class TspProvisioningService {
}

public async provisionClasses(school: School, classes: ExternalClassDto[], user: UserDO): Promise<void> {
if (!user.id)
const promises = classes.map(async (clazz) => {
const currentClass = await this.classService.findClassWithSchoolIdAndExternalId(school.id, clazz.externalId);

if (currentClass) {
await this.updateClass(currentClass, clazz, school, user);
} else {
await this.createClass(clazz, school, user);
}
});

await Promise.all(promises);
}

private async updateClass(currentClass: Class, clazz: ExternalClassDto, school: School, user: UserDO): Promise<void> {
if (!user.id) {
throw new BadDataLoggableException('User ID is missing', {
externalId: user.externalId,
});
}

for await (const clazz of classes) {
const currentClass = await this.classService.findClassWithSchoolIdAndExternalId(school.id, clazz.externalId);
currentClass.schoolId = school.id;
currentClass.name = clazz.name ?? currentClass.name;
currentClass.year = school.currentYear?.id;
currentClass.source = this.ENTITY_SOURCE;
currentClass.sourceOptions = new ClassSourceOptions({ tspUid: clazz.externalId });

if (currentClass) {
// Case: Class exists -> update class
currentClass.schoolId = school.id;
currentClass.name = clazz.name ?? currentClass.name;
currentClass.year = school.currentYear?.id;
currentClass.source = this.ENTITY_SOURCE;
currentClass.sourceOptions = new ClassSourceOptions({ tspUid: clazz.externalId });
if (user.roles.some((role) => role.name === RoleName.TEACHER)) {
currentClass.addTeacher(user.id);
}
if (user.roles.some((role) => role.name === RoleName.STUDENT)) {
currentClass.addUser(user.id);
}

if (user.roles.some((role) => role.name === RoleName.TEACHER)) currentClass.addTeacher(user.id);
if (user.roles.some((role) => role.name === RoleName.STUDENT)) currentClass.addUser(user.id);
await this.classService.save(currentClass);
}

await this.classService.save(currentClass);
} else {
// Case: Class does not exist yet -> create new class
const newClass = ClassFactory.create({
name: clazz.name,
schoolId: school.id,
year: school.currentYear?.id,
teacherIds: user.roles.some((role) => role.name === RoleName.TEACHER) ? [user.id] : [],
userIds: user.roles.some((role) => role.name === RoleName.STUDENT) ? [user.id] : [],
source: this.ENTITY_SOURCE,
sourceOptions: new ClassSourceOptions({ tspUid: clazz.externalId }),
});

await this.classService.save(newClass);
}
private async createClass(clazz: ExternalClassDto, school: School, user: UserDO): Promise<void> {
if (!user.id) {
throw new BadDataLoggableException('User ID is missing', {
externalId: user.externalId,
});
}

const newClass = ClassFactory.create({
name: clazz.name,
schoolId: school.id,
year: school.currentYear?.id,
teacherIds: user.roles.some((role) => role.name === RoleName.TEACHER) ? [user.id] : [],
userIds: user.roles.some((role) => role.name === RoleName.STUDENT) ? [user.id] : [],
source: this.ENTITY_SOURCE,
sourceOptions: new ClassSourceOptions({ tspUid: clazz.externalId }),
});

await this.classService.save(newClass);
}

public async provisionUser(data: OauthDataDto, school: School): Promise<UserDO> {
Expand Down
Loading
Loading