Skip to content

Commit

Permalink
Finish functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
marode-cap committed Jun 28, 2024
1 parent de2cb27 commit f9a8a17
Show file tree
Hide file tree
Showing 5 changed files with 139 additions and 41 deletions.
25 changes: 9 additions & 16 deletions src/modules/itslearning/actions/create-person.action.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,8 @@
import { DomainError } from '../../../shared/error/domain.error.js';
import { IMS_COMMON_SCHEMA, IMS_PERSON_MAN_DATA_SCHEMA, IMS_PERSON_MAN_MESS_SCHEMA } from '../schemas.js';
import { ItsLearningRoleType } from '../types/role.enum.js';
import { IMSESAction } from './base-action.js';

type InstitutionRoleType =
| 'Student'
| 'Faculty'
| 'Member'
| 'Learner'
| 'Instructor'
| 'Mentor'
| 'Staff'
| 'Alumni'
| 'ProspectiveStudent'
| 'Guest'
| 'Other'
| 'Administrator'
| 'Observer';

// Incomplete
export type CreatePersonParams = {
id: string;
Expand All @@ -26,7 +12,7 @@ export type CreatePersonParams = {

username: string;

institutionRoleType: InstitutionRoleType;
institutionRoleType: ItsLearningRoleType;
};

type CreatePersonResponseBody = {
Expand Down Expand Up @@ -66,6 +52,13 @@ export class CreatePersonAction extends IMSESAction<CreatePersonResponseBody, vo
'ims2:institutionRoleType': this.params.institutionRoleType,
'ims2:primaryRoleType': false, // ?
},
'ims2:extension': {
'ims1:extensionField': {
'ims1:fieldName': 'passwordchange',
'ims1:fieldType': 'String',
'ims1:fieldValue': 'NotAllowed',
},
},
},
},
};
Expand Down
20 changes: 3 additions & 17 deletions src/modules/itslearning/actions/read-person.action.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,11 @@
import { DomainError } from '../../../shared/error/domain.error.js';
import { IMS_COMMON_SCHEMA, IMS_PERSON_MAN_MESS_SCHEMA } from '../schemas.js';
import { ItsLearningRoleType } from '../types/role.enum.js';
import { IMSESAction } from './base-action.js';

type InstitutionRoleType =
| 'Student'
| 'Faculty'
| 'Member'
| 'Learner'
| 'Instructor'
| 'Mentor'
| 'Staff'
| 'Alumni'
| 'ProspectiveStudent'
| 'Guest'
| 'Other'
| 'Administrator'
| 'Observer';

export type PersonResponse = {
formatName?: string;
institutionRole: InstitutionRoleType;
institutionRole: ItsLearningRoleType;
primaryRoleType: boolean;
userId: string;
};
Expand All @@ -40,7 +26,7 @@ type ReadPersonResponseBody = {
userIdValue: string;
};
institutionRole: {
institutionRoleType: InstitutionRoleType;
institutionRoleType: ItsLearningRoleType;
primaryRoleType: boolean;
};
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,57 @@
import { Injectable } from '@nestjs/common';
import { ItsLearningConfig, ServerConfig } from '../../../shared/config/index.js';
import { ConfigService } from '@nestjs/config';
import { ItsLearningIMSESService } from '../itslearning.service.js';
import { ClassLogger } from '../../../core/logging/class-logger.js';
import { Mutex } from 'async-mutex';

import { EventHandler } from '../../../core/eventbus/decorators/event-handler.decorator.js';
import { ClassLogger } from '../../../core/logging/class-logger.js';
import { ItsLearningConfig, ServerConfig } from '../../../shared/config/index.js';
import { DomainError } from '../../../shared/error/domain.error.js';
import { PersonenkontextCreatedEvent } from '../../../shared/events/personenkontext-created.event.js';
import { RolleID } from '../../../shared/types/aggregate-ids.types.js';
import { Person } from '../../person/domain/person.js';
import { PersonRepository } from '../../person/persistence/person.repository.js';
import { Personenkontext } from '../../personenkontext/domain/personenkontext.js';
import { DBiamPersonenkontextRepo } from '../../personenkontext/persistence/dbiam-personenkontext.repo.js';
import { RollenArt } from '../../rolle/domain/rolle.enums.js';
import { RolleRepo } from '../../rolle/repo/rolle.repo.js';
import { CreatePersonAction } from '../actions/create-person.action.js';
import { ReadPersonAction } from '../actions/read-person.action.js';
import { ItsLearningIMSESService } from '../itslearning.service.js';
import { ItsLearningRoleType } from '../types/role.enum.js';

// Maps our roles to itsLearning roles
const ROLLENART_TO_ITSLEARNING_ROLE: Record<RollenArt, ItsLearningRoleType> = {
[RollenArt.EXTERN]: ItsLearningRoleType.GUEST,
[RollenArt.LERN]: ItsLearningRoleType.STUDENT,
[RollenArt.LEHR]: ItsLearningRoleType.STAFF,
[RollenArt.LEIT]: ItsLearningRoleType.ADMINISTRATOR,
[RollenArt.ORGADMIN]: ItsLearningRoleType.ADMINISTRATOR,
[RollenArt.SYSADMIN]: ItsLearningRoleType.SYSTEM_ADMINISTRATOR,
};

// Determines order of roles.
// example: If person has both a EXTERN and a LEHR role, the LEHR role has priority
const ROLLENART_ORDER: RollenArt[] = [
RollenArt.EXTERN,
RollenArt.LERN,
RollenArt.LEHR,
RollenArt.LEIT,
RollenArt.ORGADMIN,
RollenArt.SYSADMIN,
];

@Injectable()
export class ItsLearningPersonsEventHandler {
public ENABLED: boolean;

private readonly mutex: Mutex = new Mutex();

public constructor(
private readonly logger: ClassLogger,
private readonly itsLearningService: ItsLearningIMSESService,
private readonly personenRepository: PersonRepository,
private readonly rolleRepo: RolleRepo,
private readonly personenkontextRepository: DBiamPersonenkontextRepo,
configService: ConfigService<ServerConfig>,
) {
const itsLearningConfig: ItsLearningConfig = configService.getOrThrow<ItsLearningConfig>('ITSLEARNING');
Expand All @@ -25,8 +64,71 @@ export class ItsLearningPersonsEventHandler {
this.logger.info(`Received PersonenkontextCreatedEvent, ${event.personId}`);

if (!this.ENABLED) {
this.logger.info('Not enabled, ignoring event.');
return;
return this.logger.info('Not enabled, ignoring event.');
}

return this.mutex.runExclusive(async () => {
const personenkontexte: Personenkontext<true>[] = await this.personenkontextRepository.findByPerson(
event.personId,
);

if (personenkontexte.length === 0) {
// Delete person?
return this.logger.info(`No Personenkontexte found for Person ${event.personId}.`);
}

const targetRole: ItsLearningRoleType = await this.determineItsLearningRole(personenkontexte);

const personResult = await this.itsLearningService.send(new ReadPersonAction(event.personId));

Check warning on line 82 in src/modules/itslearning/event-handlers/itslearning-persons.event-handler.ts

View workflow job for this annotation

GitHub Actions / Linting / Nest Lint

Expected personResult to have a type annotation

if (personResult.ok && personResult.value.institutionRole === targetRole) {
return this.logger.info('Person already exists with correct role');
}

const person: Option<Person<true>> = await this.personenRepository.findById(event.personId);

if (!person) {
return this.logger.info(`Person with ID ${event.personId} not found.`);
}

if (!person.referrer) {
return this.logger.error(`Person with ID ${event.personId} has no username!`);
}

const createAction: CreatePersonAction = new CreatePersonAction({
id: event.personId,
firstName: person.vorname,
lastName: person.familienname,
username: person.referrer,
institutionRoleType: targetRole,
});

const createResult: Result<void, DomainError> = await this.itsLearningService.send(createAction);

if (!createResult.ok) {
return this.logger.error(`Person with ID ${event.personId} could not be sent to itsLearning!`);
}

return this.logger.info(`Person with ID ${event.personId} created in itsLearning!`);
});
}

/**
* Determines which role the user should have in itsLearning (User needs to have a primary role)
* @param personenkontexte
* @returns
*/
private async determineItsLearningRole(personenkontexte: Personenkontext<true>[]): Promise<ItsLearningRoleType> {
const rollenIDs: RolleID[] = personenkontexte.map((pk: Personenkontext<true>) => pk.rolleId);
const rollenMap = await this.rolleRepo.findByIds(rollenIDs);

Check warning on line 123 in src/modules/itslearning/event-handlers/itslearning-persons.event-handler.ts

View workflow job for this annotation

GitHub Actions / Linting / Nest Lint

Expected rollenMap to have a type annotation

let highestRole: number = 0;

for (const rolle of rollenMap.values()) {
highestRole = Math.max(highestRole, ROLLENART_ORDER.indexOf(rolle.rollenart));
}

// Null assertion is valid here, highestRole can never be OOB
return ROLLENART_TO_ITSLEARNING_ROLE[ROLLENART_ORDER[highestRole]!];
}
}
16 changes: 13 additions & 3 deletions src/modules/itslearning/itslearning.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,23 @@ import { HttpModule } from '@nestjs/axios';
import { Module } from '@nestjs/common';

import { LoggerModule } from '../../core/logging/logger.module.js';
import { ItsLearningIMSESService } from './itslearning.service.js';
import { ItsLearningEventHandler } from './itslearning-event-handler.js';
import { OrganisationModule } from '../organisation/organisation.module.js';
import { PersonenKontextModule } from '../personenkontext/personenkontext.module.js';
import { RolleModule } from '../rolle/rolle.module.js';
import { ItsLearningPersonsEventHandler } from './event-handlers/itslearning-persons.event-handler.js';
import { ItsLearningEventHandler } from './itslearning-event-handler.js';
import { ItsLearningIMSESService } from './itslearning.service.js';
import { PersonModule } from '../person/person.module.js';

@Module({
imports: [LoggerModule.register(ItsLearningModule.name), HttpModule, OrganisationModule],
imports: [
LoggerModule.register(ItsLearningModule.name),
HttpModule,
OrganisationModule,
RolleModule,
PersonModule,
PersonenKontextModule,
],
providers: [ItsLearningIMSESService, ItsLearningEventHandler, ItsLearningPersonsEventHandler],
exports: [ItsLearningIMSESService],
})
Expand Down
7 changes: 7 additions & 0 deletions src/modules/itslearning/types/role.enum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export enum ItsLearningRoleType {
GUEST = 'Guest',
STUDENT = 'Student',
STAFF = 'Staff',
ADMINISTRATOR = 'Administrator',
SYSTEM_ADMINISTRATOR = 'System Administrator',
}

0 comments on commit f9a8a17

Please sign in to comment.