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() ); });