diff --git a/apps/server/src/modules/group/domain/group.spec.ts b/apps/server/src/modules/group/domain/group.spec.ts new file mode 100644 index 00000000000..b5ae8a03321 --- /dev/null +++ b/apps/server/src/modules/group/domain/group.spec.ts @@ -0,0 +1,138 @@ +import { groupFactory, roleFactory, userDoFactory } from '@shared/testing'; + +import { ObjectId } from 'bson'; +import { RoleReference, UserDO } from '@shared/domain'; +import { Group } from './group'; +import { GroupUser } from './group-user'; + +describe('Group (Domain Object)', () => { + describe('removeUser', () => { + describe('when the user is in the group', () => { + const setup = () => { + const user: UserDO = userDoFactory.buildWithId(); + const groupUser1 = new GroupUser({ + userId: user.id as string, + roleId: new ObjectId().toHexString(), + }); + const groupUser2 = new GroupUser({ + userId: new ObjectId().toHexString(), + roleId: new ObjectId().toHexString(), + }); + const group: Group = groupFactory.build({ + users: [groupUser1, groupUser2], + }); + + return { + user, + groupUser1, + groupUser2, + group, + }; + }; + + it('should remove the user', () => { + const { user, group, groupUser1 } = setup(); + + group.removeUser(user); + + expect(group.users).not.toContain(groupUser1); + }); + + it('should keep all other users', () => { + const { user, group, groupUser2 } = setup(); + + group.removeUser(user); + + expect(group.users).toContain(groupUser2); + }); + }); + + describe('when the user is not in the group', () => { + const setup = () => { + const user: UserDO = userDoFactory.buildWithId(); + const groupUser2 = new GroupUser({ + userId: new ObjectId().toHexString(), + roleId: new ObjectId().toHexString(), + }); + const group: Group = groupFactory.build({ + users: [groupUser2], + }); + + return { + user, + groupUser2, + group, + }; + }; + + it('should do nothing', () => { + const { user, group, groupUser2 } = setup(); + + group.removeUser(user); + + expect(group.users).toEqual([groupUser2]); + }); + }); + + describe('when the group is empty', () => { + const setup = () => { + const user: UserDO = userDoFactory.buildWithId(); + const group: Group = groupFactory.build({ users: [] }); + + return { + user, + group, + }; + }; + + it('should stay empty', () => { + const { user, group } = setup(); + + group.removeUser(user); + + expect(group.users).toEqual([]); + }); + }); + }); + + describe('isEmpty', () => { + describe('when no users in group exist', () => { + const setup = () => { + const group: Group = groupFactory.build({ users: [] }); + + return { + group, + }; + }; + + it('should return true', () => { + const { group } = setup(); + + const isEmpty = group.isEmpty(); + + expect(isEmpty).toEqual(true); + }); + }); + + describe('when users in group exist', () => { + const setup = () => { + const externalUserId = 'externalUserId'; + const role: RoleReference = roleFactory.buildWithId(); + const user: UserDO = userDoFactory.buildWithId({ roles: [role], externalId: externalUserId }); + const group: Group = groupFactory.build({ users: [{ userId: user.id as string, roleId: role.id }] }); + + return { + group, + }; + }; + + it('should return false', () => { + const { group } = setup(); + + const isEmpty = group.isEmpty(); + + expect(isEmpty).toEqual(false); + }); + }); + }); +}); diff --git a/apps/server/src/modules/group/domain/group.ts b/apps/server/src/modules/group/domain/group.ts index cbc5a416ffe..049043618ac 100644 --- a/apps/server/src/modules/group/domain/group.ts +++ b/apps/server/src/modules/group/domain/group.ts @@ -1,4 +1,4 @@ -import { EntityId, ExternalSource } from '@shared/domain'; +import { EntityId, ExternalSource, type UserDO } from '@shared/domain'; import { AuthorizableObject, DomainObject } from '@shared/domain/domain-object'; import { GroupTypes } from './group-types'; import { GroupUser } from './group-user'; @@ -37,4 +37,12 @@ export class Group extends DomainObject { get organizationId(): string | undefined { return this.props.organizationId; } + + removeUser(user: UserDO): void { + this.props.users = this.props.users.filter((groupUser: GroupUser): boolean => groupUser.userId !== user.id); + } + + isEmpty(): boolean { + return this.props.users.length === 0; + } } diff --git a/apps/server/src/modules/group/repo/group.repo.spec.ts b/apps/server/src/modules/group/repo/group.repo.spec.ts index 6b7c9daf741..358b3c13983 100644 --- a/apps/server/src/modules/group/repo/group.repo.spec.ts +++ b/apps/server/src/modules/group/repo/group.repo.spec.ts @@ -1,8 +1,16 @@ import { EntityManager, ObjectId } from '@mikro-orm/mongodb'; import { Test, TestingModule } from '@nestjs/testing'; -import { ExternalSource, SchoolEntity } from '@shared/domain'; +import { ExternalSource, SchoolEntity, UserDO, User } from '@shared/domain'; import { MongoMemoryDatabaseModule } from '@shared/infra/database'; -import { cleanupCollections, groupEntityFactory, groupFactory, schoolFactory } from '@shared/testing'; +import { + cleanupCollections, + groupEntityFactory, + groupFactory, + roleFactory, + schoolFactory, + userDoFactory, + userFactory, +} from '@shared/testing'; import { Group, GroupProps, GroupTypes, GroupUser } from '../domain'; import { GroupEntity, GroupEntityTypes } from '../entity'; import { GroupRepo } from './group.repo'; @@ -82,6 +90,62 @@ describe('GroupRepo', () => { }); }); + describe('findByUser', () => { + describe('when the user has groups', () => { + const setup = async () => { + const userEntity: User = userFactory.buildWithId(); + const user: UserDO = userDoFactory.build({ id: userEntity.id }); + const groups: GroupEntity[] = groupEntityFactory.buildListWithId(3, { + users: [{ user: userEntity, role: roleFactory.buildWithId() }], + }); + + const otherGroups: GroupEntity[] = groupEntityFactory.buildListWithId(2); + + await em.persistAndFlush([userEntity, ...groups, ...otherGroups]); + em.clear(); + + return { + user, + groups, + }; + }; + + it('should return the groups', async () => { + const { user, groups } = await setup(); + + const result: Group[] = await repo.findByUser(user); + + expect(result.map((group) => group.id).sort((a, b) => a.localeCompare(b))).toEqual( + groups.map((group) => group.id).sort((a, b) => a.localeCompare(b)) + ); + }); + }); + + describe('when the user has no groups exists', () => { + const setup = async () => { + const userEntity: User = userFactory.buildWithId(); + const user: UserDO = userDoFactory.build({ id: userEntity.id }); + + const otherGroups: GroupEntity[] = groupEntityFactory.buildListWithId(2); + + await em.persistAndFlush([userEntity, ...otherGroups]); + em.clear(); + + return { + user, + }; + }; + + it('should return an empty array', async () => { + const { user } = await setup(); + + const result: Group[] = await repo.findByUser(user); + + expect(result).toHaveLength(0); + }); + }); + }); + describe('findClassesForSchool', () => { describe('when groups of type class for the school exist', () => { const setup = async () => { diff --git a/apps/server/src/modules/group/repo/group.repo.ts b/apps/server/src/modules/group/repo/group.repo.ts index 2c920b9a39d..920647ee7e8 100644 --- a/apps/server/src/modules/group/repo/group.repo.ts +++ b/apps/server/src/modules/group/repo/group.repo.ts @@ -1,6 +1,7 @@ -import { EntityManager } from '@mikro-orm/mongodb'; +import { EntityManager, ObjectId } from '@mikro-orm/mongodb'; import { Injectable } from '@nestjs/common'; import { EntityId } from '@shared/domain/types'; +import { type UserDO } from '@shared/domain'; import { Group, GroupProps } from '../domain'; import { GroupEntity, GroupEntityProps, GroupEntityTypes } from '../entity'; import { GroupDomainMapper } from './group-domain.mapper'; @@ -42,6 +43,20 @@ export class GroupRepo { return domainObject; } + public async findByUser(user: UserDO): Promise { + const entities: GroupEntity[] = await this.em.find(GroupEntity, { + users: { user: new ObjectId(user.id) }, + }); + + const domainObjects = entities.map((entity) => { + const props: GroupProps = GroupDomainMapper.mapEntityToDomainObjectProperties(entity); + + return new Group(props); + }); + + return domainObjects; + } + public async findClassesForSchool(schoolId: EntityId): Promise { const entities: GroupEntity[] = await this.em.find(GroupEntity, { type: GroupEntityTypes.CLASS, diff --git a/apps/server/src/modules/group/service/group.service.spec.ts b/apps/server/src/modules/group/service/group.service.spec.ts index 71cc9eaeb6a..19c66f266ee 100644 --- a/apps/server/src/modules/group/service/group.service.spec.ts +++ b/apps/server/src/modules/group/service/group.service.spec.ts @@ -2,7 +2,8 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { ObjectId } from '@mikro-orm/mongodb'; import { Test, TestingModule } from '@nestjs/testing'; import { NotFoundLoggableException } from '@shared/common/loggable-exception'; -import { groupFactory } from '@shared/testing'; +import { groupFactory, userDoFactory } from '@shared/testing'; +import { UserDO } from '@shared/domain'; import { Group } from '../domain'; import { GroupRepo } from '../repo'; import { GroupService } from './group.service'; @@ -120,6 +121,50 @@ describe('GroupService', () => { }); }); + describe('findByUser', () => { + describe('when groups with the user exists', () => { + const setup = () => { + const user: UserDO = userDoFactory.buildWithId(); + const groups: Group[] = groupFactory.buildList(2); + + groupRepo.findByUser.mockResolvedValue(groups); + + return { + user, + groups, + }; + }; + + it('should return the groups', async () => { + const { user, groups } = setup(); + + const result: Group[] = await service.findByUser(user); + + expect(result).toEqual(groups); + }); + }); + + describe('when no groups with the user exists', () => { + const setup = () => { + const user: UserDO = userDoFactory.buildWithId(); + + groupRepo.findByUser.mockResolvedValue([]); + + return { + user, + }; + }; + + it('should return empty array', async () => { + const { user } = setup(); + + const result: Group[] = await service.findByUser(user); + + expect(result).toEqual([]); + }); + }); + }); + describe('findClassesForSchool', () => { describe('when the school has groups of type class', () => { const setup = () => { diff --git a/apps/server/src/modules/group/service/group.service.ts b/apps/server/src/modules/group/service/group.service.ts index dcba9377de3..f3ce6a287e8 100644 --- a/apps/server/src/modules/group/service/group.service.ts +++ b/apps/server/src/modules/group/service/group.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@nestjs/common'; import { NotFoundLoggableException } from '@shared/common/loggable-exception'; -import { EntityId } from '@shared/domain'; +import { EntityId, type UserDO } from '@shared/domain'; import { AuthorizationLoaderServiceGeneric } from '@src/modules/authorization'; import { Group } from '../domain'; import { GroupRepo } from '../repo'; @@ -31,6 +31,12 @@ export class GroupService implements AuthorizationLoaderServiceGeneric { return group; } + public async findByUser(user: UserDO): Promise { + const groups: Group[] = await this.groupRepo.findByUser(user); + + return groups; + } + public async findClassesForSchool(schoolId: EntityId): Promise { const group: Group[] = await this.groupRepo.findClassesForSchool(schoolId); diff --git a/apps/server/src/modules/provisioning/strategy/oidc/oidc.strategy.spec.ts b/apps/server/src/modules/provisioning/strategy/oidc/oidc.strategy.spec.ts index 80bdfdf4c88..2b838159524 100644 --- a/apps/server/src/modules/provisioning/strategy/oidc/oidc.strategy.spec.ts +++ b/apps/server/src/modules/provisioning/strategy/oidc/oidc.strategy.spec.ts @@ -98,7 +98,7 @@ describe('OidcStrategy', () => { }; }; - it('should call the OidcProvisioningService.provisionExternalSchool', async () => { + it('should provision school', async () => { const { oauthData } = setup(); await strategy.apply(oauthData); @@ -150,7 +150,7 @@ describe('OidcStrategy', () => { }; }; - it('should call the OidcProvisioningService.provisionExternalUser', async () => { + it('should provision external user', async () => { const { oauthData, schoolId } = setup(); await strategy.apply(oauthData); @@ -198,7 +198,19 @@ describe('OidcStrategy', () => { }; }; - it('should call the OidcProvisioningService.provisionExternalGroup for each group', async () => { + it('should remove external groups and affiliation', async () => { + const { oauthData } = setup(); + + await strategy.apply(oauthData); + + expect(oidcProvisioningService.removeExternalGroupsAndAffiliation).toHaveBeenCalledWith( + oauthData.externalUser.externalId, + oauthData.externalGroups, + oauthData.system.systemId + ); + }); + + it('should provision every external group', async () => { const { oauthData } = setup(); await strategy.apply(oauthData); @@ -241,7 +253,15 @@ describe('OidcStrategy', () => { }; }; - it('should not call the OidcProvisioningService.provisionExternalGroup', async () => { + it('should not remove external groups and affiliation', async () => { + const { oauthData } = setup(); + + await strategy.apply(oauthData); + + expect(oidcProvisioningService.removeExternalGroupsAndAffiliation).not.toHaveBeenCalled(); + }); + + it('should not provision groups', async () => { const { oauthData } = setup(); await strategy.apply(oauthData); diff --git a/apps/server/src/modules/provisioning/strategy/oidc/oidc.strategy.ts b/apps/server/src/modules/provisioning/strategy/oidc/oidc.strategy.ts index f51fc49abed..7804f2190f9 100644 --- a/apps/server/src/modules/provisioning/strategy/oidc/oidc.strategy.ts +++ b/apps/server/src/modules/provisioning/strategy/oidc/oidc.strategy.ts @@ -24,7 +24,11 @@ export abstract class OidcProvisioningStrategy extends ProvisioningStrategy { ); if (Configuration.get('FEATURE_SANIS_GROUP_PROVISIONING_ENABLED') && data.externalGroups) { - // TODO: N21-1212 remove user from groups + await this.oidcProvisioningService.removeExternalGroupsAndAffiliation( + data.externalUser.externalId, + data.externalGroups, + data.system.systemId + ); await Promise.all( data.externalGroups.map((externalGroup) => 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 1084f21bbf4..c4b27cac34b 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 @@ -1,7 +1,7 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { UnprocessableEntityException } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; -import { LegacySchoolDo, RoleName, SchoolFeatures } from '@shared/domain'; +import { ExternalSource, LegacySchoolDo, RoleName, RoleReference, SchoolFeatures } from '@shared/domain'; import { UserDO } from '@shared/domain/domainobject/user.do'; import { externalGroupDtoFactory, @@ -11,6 +11,7 @@ import { legacySchoolDoFactory, schoolYearFactory, userDoFactory, + roleFactory, } from '@shared/testing'; import { Logger } from '@src/core/logger'; import { AccountService } from '@src/modules/account/services/account.service'; @@ -21,6 +22,7 @@ import { RoleDto } from '@src/modules/role/service/dto/role.dto'; import { FederalStateService, LegacySchoolService, SchoolYearService } from '@src/modules/legacy-school'; import { UserService } from '@src/modules/user'; import CryptoJS from 'crypto-js'; +import { NotFoundLoggableException } from '@shared/common/loggable-exception'; import { ExternalGroupDto, ExternalSchoolDto, ExternalUserDto } from '../../../dto'; import { SchoolForGroupNotFoundLoggable, UserForGroupNotFoundLoggable } from '../../../loggable'; import { OidcProvisioningService } from './oidc-provisioning.service'; @@ -342,6 +344,204 @@ describe('OidcProvisioningService', () => { }); describe('provisionExternalGroup', () => { + describe('when group membership of user has not changed', () => { + const setup = () => { + const systemId = 'systemId'; + const externalUserId = 'externalUserId'; + const role: RoleReference = roleFactory.buildWithId(); + const user: UserDO = userDoFactory.buildWithId({ roles: [role], externalId: externalUserId }); + + const existingGroups: Group[] = groupFactory.buildList(2, { + users: [{ userId: user.id as string, roleId: role.id }], + }); + + const firstExternalGroup: ExternalGroupDto = externalGroupDtoFactory.build({ + externalId: existingGroups[0].externalSource?.externalId, + users: [{ externalUserId, roleName: role.name }], + }); + const secondExternalGroup: ExternalGroupDto = externalGroupDtoFactory.build({ + externalId: existingGroups[1].externalSource?.externalId, + users: [{ externalUserId, roleName: role.name }], + }); + const externalGroups: ExternalGroupDto[] = [firstExternalGroup, secondExternalGroup]; + + userService.findByExternalId.mockResolvedValue(user); + groupService.findByUser.mockResolvedValue(existingGroups); + + return { + externalGroups, + systemId, + externalUserId, + }; + }; + + it('should not save the group', async () => { + const { externalGroups, systemId, externalUserId } = setup(); + + await service.removeExternalGroupsAndAffiliation(externalUserId, externalGroups, systemId); + + expect(groupService.save).not.toHaveBeenCalled(); + }); + + it('should not delete the group', async () => { + const { externalGroups, systemId, externalUserId } = setup(); + + await service.removeExternalGroupsAndAffiliation(externalUserId, externalGroups, systemId); + + expect(groupService.delete).not.toHaveBeenCalled(); + }); + }); + + describe('when user is not part of a group anymore', () => { + describe('when group is empty after removal of the User', () => { + const setup = () => { + const systemId = 'systemId'; + const externalUserId = 'externalUserId'; + const role: RoleReference = roleFactory.buildWithId(); + const user: UserDO = userDoFactory.buildWithId({ roles: [role], externalId: externalUserId }); + + const firstExistingGroup: Group = groupFactory.build({ + users: [{ userId: user.id as string, roleId: role.id }], + externalSource: new ExternalSource({ + externalId: 'externalId-1', + systemId, + }), + }); + const secondExistingGroup: Group = groupFactory.build({ + users: [{ userId: user.id as string, roleId: role.id }], + externalSource: new ExternalSource({ + externalId: 'externalId-2', + systemId, + }), + }); + const existingGroups = [firstExistingGroup, secondExistingGroup]; + + const firstExternalGroup: ExternalGroupDto = externalGroupDtoFactory.build({ + externalId: existingGroups[0].externalSource?.externalId, + users: [{ externalUserId, roleName: role.name }], + }); + const externalGroups: ExternalGroupDto[] = [firstExternalGroup]; + + userService.findByExternalId.mockResolvedValue(user); + groupService.findByUser.mockResolvedValue(existingGroups); + + return { + externalGroups, + systemId, + externalUserId, + existingGroups, + }; + }; + + it('should delete the group', async () => { + const { externalGroups, systemId, externalUserId, existingGroups } = setup(); + + await service.removeExternalGroupsAndAffiliation(externalUserId, externalGroups, systemId); + + expect(groupService.delete).toHaveBeenCalledWith(existingGroups[1]); + }); + + it('should not save the group', async () => { + const { externalGroups, systemId, externalUserId } = setup(); + + await service.removeExternalGroupsAndAffiliation(externalUserId, externalGroups, systemId); + + expect(groupService.save).not.toHaveBeenCalled(); + }); + }); + + describe('when group is not empty after removal of the User', () => { + const setup = () => { + const systemId = 'systemId'; + const externalUserId = 'externalUserId'; + const anotherExternalUserId = 'anotherExternalUserId'; + const role: RoleReference = roleFactory.buildWithId(); + const user: UserDO = userDoFactory.buildWithId({ roles: [role], externalId: externalUserId }); + const anotherUser: UserDO = userDoFactory.buildWithId({ roles: [role], externalId: anotherExternalUserId }); + + const firstExistingGroup: Group = groupFactory.build({ + users: [ + { userId: user.id as string, roleId: role.id }, + { userId: anotherUser.id as string, roleId: role.id }, + ], + externalSource: new ExternalSource({ + externalId: `externalId-1`, + systemId, + }), + }); + + const secondExistingGroup: Group = groupFactory.build({ + users: [ + { userId: user.id as string, roleId: role.id }, + { userId: anotherUser.id as string, roleId: role.id }, + ], + externalSource: new ExternalSource({ + externalId: `externalId-2`, + systemId, + }), + }); + + const existingGroups: Group[] = [firstExistingGroup, secondExistingGroup]; + + const firstExternalGroup: ExternalGroupDto = externalGroupDtoFactory.build({ + externalId: existingGroups[0].externalSource?.externalId, + users: [{ externalUserId, roleName: role.name }], + }); + const externalGroups: ExternalGroupDto[] = [firstExternalGroup]; + + userService.findByExternalId.mockResolvedValue(user); + groupService.findByUser.mockResolvedValue(existingGroups); + + return { + externalGroups, + systemId, + externalUserId, + existingGroups, + }; + }; + + it('should save the group', async () => { + const { externalGroups, systemId, externalUserId, existingGroups } = setup(); + + await service.removeExternalGroupsAndAffiliation(externalUserId, externalGroups, systemId); + + expect(groupService.save).toHaveBeenCalledWith(existingGroups[1]); + }); + + it('should not delete the group', async () => { + const { externalGroups, systemId, externalUserId } = setup(); + + await service.removeExternalGroupsAndAffiliation(externalUserId, externalGroups, systemId); + + expect(groupService.delete).not.toHaveBeenCalled(); + }); + }); + }); + + describe('when user could not be found', () => { + const setup = () => { + const systemId = 'systemId'; + const externalUserId = 'externalUserId'; + const externalGroups: ExternalGroupDto[] = [externalGroupDtoFactory.build()]; + + userService.findByExternalId.mockResolvedValue(null); + + return { + systemId, + externalUserId, + externalGroups, + }; + }; + + it('should throw NotFoundLoggableException', async () => { + const { externalGroups, systemId, externalUserId } = setup(); + + const func = async () => service.removeExternalGroupsAndAffiliation(externalUserId, externalGroups, systemId); + + await expect(func).rejects.toThrow(new NotFoundLoggableException('User', 'externalId', externalUserId)); + }); + }); + describe('when the group has no users', () => { const setup = () => { const externalGroupDto: ExternalGroupDto = externalGroupDtoFactory.build({ users: [] }); 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 1a16b8578c9..0aef3fdecb9 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 @@ -12,6 +12,7 @@ import { RoleDto } from '@src/modules/role/service/dto/role.dto'; import { UserService } from '@src/modules/user'; import { ObjectId } from 'bson'; import CryptoJS from 'crypto-js'; +import { NotFoundLoggableException } from '@shared/common/loggable-exception'; import { ExternalGroupDto, ExternalGroupUserDto, ExternalSchoolDto, ExternalUserDto } from '../../../dto'; import { SchoolForGroupNotFoundLoggable, UserForGroupNotFoundLoggable } from '../../../loggable'; @@ -185,4 +186,43 @@ export class OidcProvisioningService { return filteredUsers; } + + async removeExternalGroupsAndAffiliation( + externalUserId: EntityId, + externalGroups: ExternalGroupDto[], + systemId: EntityId + ): Promise { + const user: UserDO | null = await this.userService.findByExternalId(externalUserId, systemId); + + if (!user) { + throw new NotFoundLoggableException(UserDO.name, 'externalId', externalUserId); + } + + const existingGroupsOfUser: Group[] = await this.groupService.findByUser(user); + + const groupsFromSystem: Group[] = existingGroupsOfUser.filter( + (existingGroup: Group) => existingGroup.externalSource?.systemId === systemId + ); + + const groupsWithoutUser: Group[] = groupsFromSystem.filter((existingGroupFromSystem: Group) => { + const isUserInGroup = externalGroups.some( + (externalGroup: ExternalGroupDto) => + externalGroup.externalId === existingGroupFromSystem.externalSource?.externalId + ); + + return !isUserInGroup; + }); + + await Promise.all( + groupsWithoutUser.map(async (group: Group) => { + group.removeUser(user); + + if (group.isEmpty()) { + await this.groupService.delete(group); + } else { + await this.groupService.save(group); + } + }) + ); + } } diff --git a/apps/server/src/shared/common/loggable-exception/not-found.loggable-exception.ts b/apps/server/src/shared/common/loggable-exception/not-found.loggable-exception.ts index 261f4161a30..4ffd8e5b70e 100644 --- a/apps/server/src/shared/common/loggable-exception/not-found.loggable-exception.ts +++ b/apps/server/src/shared/common/loggable-exception/not-found.loggable-exception.ts @@ -1,5 +1,4 @@ import { NotFoundException } from '@nestjs/common'; -import { EntityId } from '@shared/domain'; import { Loggable } from '@src/core/logger/interfaces'; import { ErrorLogMessage } from '@src/core/logger/types'; @@ -7,7 +6,7 @@ export class NotFoundLoggableException extends NotFoundException implements Logg constructor( private readonly resourceName: string, private readonly identifierName: string, - private readonly resourceId: EntityId + private readonly resourceId: string ) { super(); }