From b8631ab7b53275cf6b49dee84e9eb85e6cc78a65 Mon Sep 17 00:00:00 2001 From: Guillaume Cauchois Date: Fri, 11 Oct 2024 13:26:55 +0200 Subject: [PATCH 01/29] [EN-7609] feat: role CANDIDATE_EXTERNAL to CANDIDAT & COACH_EXTERNAL to REFERRER --- .../20241008205334-add-referrer-to-user.js | 16 + src/mails/mails.service.ts | 8 +- src/organizations/organizations.controller.ts | 9 +- src/user-profiles/user-profiles.controller.ts | 2 +- .../users-creation.controller.ts | 79 +- src/users-creation/users-creation.service.ts | 10 - src/users/models/user-candidat.model.ts | 2 +- src/users/models/user.attributes.ts | 1 + src/users/models/user.include.ts | 34 + src/users/models/user.model.ts | 21 +- src/users/user-candidats.service.ts | 3 +- src/users/users.controller.ts | 16 +- src/users/users.service.ts | 12 +- src/users/users.types.ts | 42 +- src/users/users.utils.ts | 18 +- tests/organizations/organizations.e2e-spec.ts | 4 +- tests/users/users.e2e-spec.ts | 1861 +---------------- 17 files changed, 225 insertions(+), 1913 deletions(-) create mode 100644 src/db/migrations/20241008205334-add-referrer-to-user.js diff --git a/src/db/migrations/20241008205334-add-referrer-to-user.js b/src/db/migrations/20241008205334-add-referrer-to-user.js new file mode 100644 index 00000000..c524999f --- /dev/null +++ b/src/db/migrations/20241008205334-add-referrer-to-user.js @@ -0,0 +1,16 @@ +'use strict'; + +/** @type {import('sequelize-cli').Migration} */ +module.exports = { + async up(queryInterface, Sequelize) { + await queryInterface.addColumn('Users', 'referrerId', { + type: Sequelize.UUID, + allowNull: true, + defaultValue: null, + }); + }, + + async down(queryInterface, Sequelize) { + await queryInterface.removeColumn('Users', 'referrerId'); + }, +}; diff --git a/src/mails/mails.service.ts b/src/mails/mails.service.ts index c18f209a..3d506538 100644 --- a/src/mails/mails.service.ts +++ b/src/mails/mails.service.ts @@ -164,7 +164,7 @@ export class MailsService { const toEmail: CustomMailParams['toEmail'] = { to: candidate.email }; const coach = getCoachFromCandidate(candidate); - if (coach && coach.role !== UserRoles.COACH_EXTERNAL) { + if (coach && coach.role !== UserRoles.REFERRER) { toEmail.cc = coach.email; } const { candidatesAdminMail } = getAdminMailsFromZone(candidate.zone); @@ -194,7 +194,7 @@ export class MailsService { const coach = getCoachFromCandidate(candidate); const toEmail: CustomMailParams['toEmail'] = - coach && coach.role !== UserRoles.COACH_EXTERNAL + coach && coach.role !== UserRoles.REFERRER ? { to: candidate.email, cc: coach.email } : { to: candidate.email }; @@ -247,7 +247,7 @@ export class MailsService { }; const coach = getCoachFromCandidate(candidate); - if (coach && coach.role !== UserRoles.COACH_EXTERNAL) { + if (coach && coach.role !== UserRoles.REFERRER) { toEmail.cc = coach.email; } @@ -274,7 +274,7 @@ export class MailsService { to: candidate.email, }; const coach = getCoachFromCandidate(candidate); - if (coach && coach.role !== UserRoles.COACH_EXTERNAL) { + if (coach && coach.role !== UserRoles.REFERRER) { toEmail.cc = coach.email; } const { candidatesAdminMail } = getAdminMailsFromZone(candidate.zone); diff --git a/src/organizations/organizations.controller.ts b/src/organizations/organizations.controller.ts index a5152235..367eb163 100644 --- a/src/organizations/organizations.controller.ts +++ b/src/organizations/organizations.controller.ts @@ -53,14 +53,19 @@ export class OrganizationsController { return Promise.all( organizations.map(async (organization) => { - const { candidatesCount, coachesCount } = + const { candidatesCount, coachesCount, referrersCount } = await this.organizationsService.countAssociatedUsers(organization.id); return { ...(organization.toJSON() as Organization), candidatesCount, coachesCount, - } as Organization & { candidatesCount: number; coachesCount: number }; + referrersCount, + } as Organization & { + candidatesCount: number; + coachesCount: number; + referrersCount: number; + }; }) ); } diff --git a/src/user-profiles/user-profiles.controller.ts b/src/user-profiles/user-profiles.controller.ts index a8c2ed23..e25ff18f 100644 --- a/src/user-profiles/user-profiles.controller.ts +++ b/src/user-profiles/user-profiles.controller.ts @@ -132,7 +132,7 @@ export class UserProfilesController { throw new BadRequestException(); } - if (role.includes(UserRoles.COACH_EXTERNAL)) { + if (role.includes(UserRoles.REFERRER)) { throw new BadRequestException(); } diff --git a/src/users-creation/users-creation.controller.ts b/src/users-creation/users-creation.controller.ts index 4c103d71..16a85bef 100644 --- a/src/users-creation/users-creation.controller.ts +++ b/src/users-creation/users-creation.controller.ts @@ -4,7 +4,6 @@ import { Body, ConflictException, Controller, - NotFoundException, Post, UseGuards, } from '@nestjs/common'; @@ -14,17 +13,13 @@ import { Public } from 'src/auth/guards'; import { UserPermissions, UserPermissionsGuard } from 'src/users/guards'; import { User } from 'src/users/models'; import { - ExternalUserRoles, NormalUserRoles, Permissions, Programs, + RolesWithOrganization, SequelizeUniqueConstraintError, - UserRoles, } from 'src/users/users.types'; -import { - getCandidateAndCoachIdDependingOnRoles, - isRoleIncluded, -} from 'src/users/users.utils'; +import { isRoleIncluded } from 'src/users/users.utils'; import { getZoneFromDepartment, isValidPhone } from 'src/utils/misc'; import { CreateUserDto, @@ -49,9 +44,9 @@ export class UsersCreationController { async createUser(@Body(new CreateUserPipe()) createUserDto: CreateUserDto) { if ( (createUserDto.OrganizationId && - !isRoleIncluded(ExternalUserRoles, createUserDto.role)) || + !isRoleIncluded(RolesWithOrganization, createUserDto.role)) || (!createUserDto.OrganizationId && - isRoleIncluded(ExternalUserRoles, createUserDto.role)) + isRoleIncluded(RolesWithOrganization, createUserDto.role)) ) { throw new BadRequestException(); } @@ -98,72 +93,6 @@ export class UsersCreationController { jwtToken ); - if (userToCreate.userToLinkId) { - if ( - (createdUser.role !== UserRoles.COACH_EXTERNAL && - Array.isArray(userToCreate.userToLinkId)) || - (createdUser.role === UserRoles.COACH_EXTERNAL && - !Array.isArray(userToCreate.userToLinkId)) - ) { - throw new BadRequestException(); - } - - const usersToLinkIds = Array.isArray(userToCreate.userToLinkId) - ? userToCreate.userToLinkId - : [userToCreate.userToLinkId]; - - const userCandidatesToUpdate = await Promise.all( - usersToLinkIds.map(async (userToLinkId) => { - const userToLink = await this.usersCreationService.findOneUser( - userToLinkId - ); - - if (!userToLink) { - throw new NotFoundException(); - } - - const { candidateId, coachId } = - getCandidateAndCoachIdDependingOnRoles(createdUser, userToLink); - - const userCandidate = - await this.usersCreationService.findOneUserCandidatByCandidateId( - candidateId - ); - - if (!userCandidate) { - throw new NotFoundException(); - } - - return { candidateId: candidateId, coachId: coachId }; - }) - ); - - const updatedUserCandidates = - await this.usersCreationService.updateAllUserCandidatLinkedUserByCandidateId( - userCandidatesToUpdate, - createdUser.role === UserRoles.CANDIDATE_EXTERNAL - ); - - if (!updatedUserCandidates) { - throw new NotFoundException(); - } - - await Promise.all( - updatedUserCandidates.map(async (updatedUserCandidate) => { - const previousCoach = updatedUserCandidate.previous('coach'); - if ( - updatedUserCandidate.coach && - updatedUserCandidate.coach.id !== previousCoach?.id - ) { - await this.usersCreationService.sendMailsAfterMatching( - updatedUserCandidate.candidat.id - ); - } - return updatedUserCandidate.toJSON(); - }) - ); - } - return this.usersCreationService.findOneUser(createdUser.id); } diff --git a/src/users-creation/users-creation.service.ts b/src/users-creation/users-creation.service.ts index 4772aa54..53b2dfa3 100644 --- a/src/users-creation/users-creation.service.ts +++ b/src/users-creation/users-creation.service.ts @@ -105,16 +105,6 @@ export class UsersCreationService { ); } - async updateAllUserCandidatLinkedUserByCandidateId( - candidatesAndCoachesIds: { candidateId: string; coachId: string }[], - isExternalCandidate: boolean - ): Promise { - return this.userCandidatsService.updateAllLinkedCoachesByCandidatesIds( - candidatesAndCoachesIds, - isExternalCandidate - ); - } - async updateUserProfileByUserId( userId: string, updateUserProfileDto: Partial diff --git a/src/users/models/user-candidat.model.ts b/src/users/models/user-candidat.model.ts index 18b091ea..811fcc67 100644 --- a/src/users/models/user-candidat.model.ts +++ b/src/users/models/user-candidat.model.ts @@ -126,7 +126,7 @@ export class UserCandidat extends Model { previousUserCandidatValues && previousUserCandidatValues.coachId !== undefined && previousUserCandidatValues.coachId !== userCandidatToUpdate.coachId && - coach.role !== UserRoles.COACH_EXTERNAL + coach.role !== UserRoles.REFERRER ) { await UserCandidat.update( { coachId: null }, diff --git a/src/users/models/user.attributes.ts b/src/users/models/user.attributes.ts index 3e0c4b61..769a21ab 100644 --- a/src/users/models/user.attributes.ts +++ b/src/users/models/user.attributes.ts @@ -13,6 +13,7 @@ export const UserAttributes = [ 'lastConnection', 'isEmailVerified', 'createdAt', + 'referrerId', /*'updatedAt',*/ 'deletedAt', 'whatsappZoneCoachName', diff --git a/src/users/models/user.include.ts b/src/users/models/user.include.ts index 28958ead..122307c0 100644 --- a/src/users/models/user.include.ts +++ b/src/users/models/user.include.ts @@ -52,6 +52,40 @@ export const UserCandidatInclude: Includeable[] = [ }, ], }, + { + model: User, + as: 'referredCandidates', + attributes: [...UserAttributes], + include: [ + // { + // model: UserProfile, + // as: 'userProfile', + // attributes: UserProfilesAttributes, + // include: getUserProfileInclude(), + // }, + { + model: UserCandidat, + as: 'candidat', + attributes: [...UserCandidatAttributes], + paranoid: false, + include: [ + { + model: User, + as: 'candidat', + attributes: [...UserAttributes], + // include: [ + // { + // model: UserProfile, + // as: 'userProfile', + // attributes: UserProfilesAttributes, + // include: getUserProfileInclude(), + // }, + // ], + }, + ], + }, + ], + }, { model: Organization, as: 'organization', diff --git a/src/users/models/user.model.ts b/src/users/models/user.model.ts index 93881f39..4b808a05 100644 --- a/src/users/models/user.model.ts +++ b/src/users/models/user.model.ts @@ -34,9 +34,9 @@ import { AdminRole, CandidateUserRoles, CoachUserRoles, - ExternalUserRoles, Gender, Genders, + RolesWithOrganization, UserRole, UserRoles, } from '../users.types'; @@ -72,6 +72,15 @@ export class User extends HistorizedModel { @Column OrganizationId: string; + @ApiProperty() + @IsString() + @IsOptional() + @IsUUID(4) + @ForeignKey(() => User) + @AllowNull(true) + @Column + referrerId: string; + @ApiProperty() @IsString() @AllowNull(false) @@ -217,6 +226,9 @@ export class User extends HistorizedModel { @BelongsTo(() => Organization, 'OrganizationId') organization?: Organization; + @BelongsTo(() => User, 'referrerId') + referrer?: User; + @HasOne(() => UserProfile, { foreignKey: 'UserId', hooks: true, @@ -232,6 +244,9 @@ export class User extends HistorizedModel { @HasMany(() => ReadDocument, 'UserId') readDocuments: ReadDocument[]; + @HasMany(() => User, 'referrerId') + referredCandidates: User[]; + @BeforeCreate @BeforeUpdate static trimValues(user: User) { @@ -318,8 +333,8 @@ export class User extends HistorizedModel { } if ( - isRoleIncluded(ExternalUserRoles, previousUserValues.role) && - !isRoleIncluded(ExternalUserRoles, userToUpdate.role) + isRoleIncluded(RolesWithOrganization, previousUserValues.role) && + !isRoleIncluded(RolesWithOrganization, userToUpdate.role) ) { await User.update( { diff --git a/src/users/user-candidats.service.ts b/src/users/user-candidats.service.ts index 5952082e..957db8bd 100644 --- a/src/users/user-candidats.service.ts +++ b/src/users/user-candidats.service.ts @@ -87,14 +87,13 @@ export class UserCandidatsService { async updateAllLinkedCoachesByCandidatesIds( candidatesAndCoachesIds: { candidateId: string; coachId: string }[], - isExternalCandidate: boolean, shouldRemove = false ): Promise { try { return this.userCandidatModel.sequelize.transaction(async (t) => { const updatedUserCandidates = await Promise.all( candidatesAndCoachesIds.map(async ({ candidateId, coachId }) => { - if (shouldRemove || !isExternalCandidate) { + if (shouldRemove) { await this.userCandidatModel.update( { coachId: null, diff --git a/src/users/users.controller.ts b/src/users/users.controller.ts index 4bc3a5c6..d77ef281 100644 --- a/src/users/users.controller.ts +++ b/src/users/users.controller.ts @@ -152,7 +152,7 @@ export class UsersController { coachId: isRoleIncluded(CoachUserRoles, role) ? userId : undefined, }; - if (role === UserRoles.COACH_EXTERNAL) { + if (role === UserRoles.REFERRER) { const userCandidates = await this.userCandidatsService.findAllByCoachId( ids.coachId ); @@ -330,7 +330,7 @@ export class UsersController { @Put('linkUser/:userId') async linkUser( @Param('userId', new ParseUUIDPipe()) userId: string, - @Body('userToLinkId') userToLinkId: string | string[] + @Body('userToLinkId') userToLinkId: string ) { // check if users are already linked and remove existing link if needed const shouldRemoveLinkedUser = @@ -352,17 +352,6 @@ export class UsersController { throw new NotFoundException(); } - if ( - !shouldRemoveLinkedUser && - // only external coach can have multiple candidates - ((user.role !== UserRoles.COACH_EXTERNAL && - Array.isArray(userToLinkId)) || - (user.role === UserRoles.COACH_EXTERNAL && - !Array.isArray(userToLinkId))) - ) { - throw new BadRequestException(); - } - const usersToLinkIds = Array.isArray(userToLinkId) ? userToLinkId : [userToLinkId]; @@ -403,7 +392,6 @@ export class UsersController { const updatedUserCandidates = await this.userCandidatsService.updateAllLinkedCoachesByCandidatesIds( userCandidatesToUpdate, - user.role === UserRoles.CANDIDATE_EXTERNAL, shouldRemoveLinkedUser ); diff --git a/src/users/users.service.ts b/src/users/users.service.ts index 2a72e43f..198022b7 100644 --- a/src/users/users.service.ts +++ b/src/users/users.service.ts @@ -406,20 +406,28 @@ export class UsersService { const { count: candidatesCount } = await this.userModel.findAndCountAll({ where: { OrganizationId: organizationId, - role: UserRoles.CANDIDATE_EXTERNAL, + role: UserRoles.CANDIDATE, }, }); const { count: coachesCount } = await this.userModel.findAndCountAll({ where: { OrganizationId: organizationId, - role: UserRoles.COACH_EXTERNAL, + role: UserRoles.COACH, + }, + }); + + const { count: referrersCount } = await this.userModel.findAndCountAll({ + where: { + OrganizationId: organizationId, + role: UserRoles.REFERRER, }, }); return { candidatesCount, coachesCount, + referrersCount, }; } diff --git a/src/users/users.types.ts b/src/users/users.types.ts index b7dedc70..64e28a4d 100644 --- a/src/users/users.types.ts +++ b/src/users/users.types.ts @@ -13,15 +13,15 @@ import { export const UserRoles = { CANDIDATE: 'Candidat', - CANDIDATE_EXTERNAL: 'Candidat externe', COACH: 'Coach', - COACH_EXTERNAL: 'Coach externe', + REFERRER: 'Orienteur', ADMIN: 'Admin', } as const; export type UserRole = (typeof UserRoles)[keyof typeof UserRoles]; export const Permissions = { + REFERRER: 'Orienteur', CANDIDATE: 'Candidat', COACH: 'Coach', RESTRICTED_COACH: 'Restricted_Coach', @@ -32,9 +32,8 @@ export type Permission = (typeof Permissions)[keyof typeof Permissions]; export const UserPermissions: { [K in UserRole]: Permission | Permission[] } = { [UserRoles.CANDIDATE]: Permissions.CANDIDATE, - [UserRoles.CANDIDATE_EXTERNAL]: Permissions.CANDIDATE, + [UserRoles.REFERRER]: Permissions.COACH, [UserRoles.COACH]: [Permissions.COACH, Permissions.RESTRICTED_COACH], - [UserRoles.COACH_EXTERNAL]: Permissions.COACH, [UserRoles.ADMIN]: Permissions.ADMIN, }; @@ -47,32 +46,13 @@ export const NormalUserRoles: NormalUserRole[] = [ UserRoles.COACH, ]; -export type ExternalUserRole = - | typeof UserRoles.CANDIDATE_EXTERNAL - | typeof UserRoles.COACH_EXTERNAL; +export type CandidateUserRole = typeof UserRoles.CANDIDATE; +export const CandidateUserRoles: CandidateUserRole[] = [UserRoles.CANDIDATE]; +export const RolesWithOrganization: UserRole[] = [UserRoles.REFERRER]; -export const ExternalUserRoles: ExternalUserRole[] = [ - UserRoles.CANDIDATE_EXTERNAL, - UserRoles.COACH_EXTERNAL, -]; - -export type CandidateUserRole = - | typeof UserRoles.CANDIDATE - | typeof UserRoles.CANDIDATE_EXTERNAL; - -export const CandidateUserRoles: CandidateUserRole[] = [ - UserRoles.CANDIDATE, - UserRoles.CANDIDATE_EXTERNAL, -]; +export type CoachUserRole = typeof UserRoles.COACH; -export type CoachUserRole = - | typeof UserRoles.COACH - | typeof UserRoles.COACH_EXTERNAL; - -export const CoachUserRoles: CoachUserRole[] = [ - UserRoles.COACH, - UserRoles.COACH_EXTERNAL, -]; +export const CoachUserRoles: CoachUserRole[] = [UserRoles.COACH]; export const AllUserRoles: (CandidateUserRole | CoachUserRole)[] = [ ...CandidateUserRoles, ...CoachUserRoles, @@ -87,12 +67,8 @@ export type AdminRole = (typeof AdminRoles)[keyof typeof AdminRoles]; export const UserRolesFilters = [ { value: UserRoles.CANDIDATE, label: `${UserRoles.CANDIDATE} LKO` }, - { - value: UserRoles.CANDIDATE_EXTERNAL, - label: UserRoles.CANDIDATE_EXTERNAL, - }, { value: UserRoles.COACH, label: `${UserRoles.COACH} LKO` }, - { value: UserRoles.COACH_EXTERNAL, label: UserRoles.COACH_EXTERNAL }, + { value: UserRoles.REFERRER, label: UserRoles.REFERRER }, { value: UserRoles.ADMIN, label: UserRoles.ADMIN }, ]; diff --git a/src/users/users.utils.ts b/src/users/users.utils.ts index 0f9044bf..4288f9fc 100644 --- a/src/users/users.utils.ts +++ b/src/users/users.utils.ts @@ -13,7 +13,6 @@ import { CandidateUserRoles, CoachUserRoles, CVStatuses, - ExternalUserRoles, MemberConstantType, MemberFilterKey, MemberFilters, @@ -124,9 +123,14 @@ export function getCandidateIdFromCoachOrCandidate(member: User) { if (isRoleIncluded(CandidateUserRoles, member.role)) { return member.id; } + if (isRoleIncluded([UserRoles.REFERRER], member.role)) { + return member.referredCandidates.map(({ candidat }) => { + return candidat.candidat.id; + }); + } if ( - isRoleIncluded(CoachUserRoles, member.role) && + isRoleIncluded([UserRoles.COACH], member.role) && member.coaches && member.coaches.length > 0 ) { @@ -388,16 +392,6 @@ export function getCandidateAndCoachIdDependingOnRoles( candidateId: user.role === UserRoles.CANDIDATE ? user.id : userToLink.id, coachId: user.role === UserRoles.COACH ? user.id : userToLink.id, }; - } else if ( - isRoleIncluded(ExternalUserRoles, [user.role, userToLink.role]) && - user.role !== userToLink.role && - user.OrganizationId === userToLink.OrganizationId - ) { - return { - candidateId: - user.role === UserRoles.CANDIDATE_EXTERNAL ? user.id : userToLink.id, - coachId: user.role === UserRoles.COACH_EXTERNAL ? user.id : userToLink.id, - }; } else if (shouldRemoveLinkedUser) { return { candidateId: isRoleIncluded(CandidateUserRoles, user.role) diff --git a/tests/organizations/organizations.e2e-spec.ts b/tests/organizations/organizations.e2e-spec.ts index 71c8d85b..7c8f8cd2 100644 --- a/tests/organizations/organizations.e2e-spec.ts +++ b/tests/organizations/organizations.e2e-spec.ts @@ -383,7 +383,7 @@ describe('Organizations', () => { userFactory, 3, { - role: UserRoles.CANDIDATE_EXTERNAL, + role: UserRoles.CANDIDATE, OrganizationId: organization.id, }, {}, @@ -393,7 +393,7 @@ describe('Organizations', () => { await databaseHelper.createEntities( userFactory, 7, - { role: UserRoles.COACH_EXTERNAL, OrganizationId: organization.id }, + { role: UserRoles.COACH, OrganizationId: organization.id }, {}, true ); diff --git a/tests/users/users.e2e-spec.ts b/tests/users/users.e2e-spec.ts index b4b676e8..ca1712df 100644 --- a/tests/users/users.e2e-spec.ts +++ b/tests/users/users.e2e-spec.ts @@ -762,828 +762,6 @@ describe('Users', () => { expect(response.status).toBe(400); }); - - it('Should return 400 if candidate with external coach', async () => { - const { - password, - hashReset, - salt, - saltReset, - revision, - updatedAt, - createdAt, - lastConnection, - whatsappZoneCoachName, - whatsappZoneCoachUrl, - whatsappZoneCoachQR, - ...candidate - } = await userFactory.create( - { role: UserRoles.CANDIDATE }, - {}, - false - ); - - const externalCoach = await userFactory.create( - { role: UserRoles.COACH_EXTERNAL }, - {}, - true - ); - - const response: APIResponse = - await request(server) - .post(`${route}`) - .set('authorization', `Bearer ${loggedInAdmin.token}`) - .send({ - ...candidate, - userToLinkId: externalCoach.id, - }); - expect(response.status).toBe(400); - }); - it('Should return 400 if coach with external candidate', async () => { - const { - password, - hashReset, - salt, - saltReset, - revision, - updatedAt, - createdAt, - lastConnection, - whatsappZoneCoachName, - whatsappZoneCoachUrl, - whatsappZoneCoachQR, - ...coach - } = await userFactory.create({ role: UserRoles.COACH }, {}, false); - - const externalCandidate = await userFactory.create( - { role: UserRoles.CANDIDATE_EXTERNAL }, - {}, - true - ); - - const response: APIResponse = - await request(server) - .post(`${route}`) - .set('authorization', `Bearer ${loggedInAdmin.token}`) - .send({ - ...coach, - userToLinkId: externalCandidate.id, - }); - expect(response.status).toBe(400); - }); - }); - describe('/ - Create external candidate or coach', () => { - it('Should return 200 and a created external candidate without external coach', async () => { - const { - password, - hashReset, - salt, - saltReset, - revision, - updatedAt, - createdAt, - lastConnection, - OrganizationId, - whatsappZoneCoachName, - whatsappZoneCoachUrl, - whatsappZoneCoachQR, - ...candidate - } = await userFactory.create( - { - role: UserRoles.CANDIDATE_EXTERNAL, - }, - {}, - false - ); - - const { - createdAt: organizationCreatedAt, - updatedAt: organizationUpdatedAt, - organizationReferent, - ...restOrganization - } = organization; - - const response: APIResponse = - await request(server) - .post(`${route}`) - .set('authorization', `Bearer ${loggedInAdmin.token}`) - .send({ ...candidate, OrganizationId: organization.id }); - expect(response.status).toBe(201); - expect(response.body).toEqual( - expect.objectContaining({ - ...candidate, - organization: expect.objectContaining({ ...restOrganization }), - }) - ); - }); - it('Should return 200 and a created external candidate with external coach', async () => { - const candidate = await userFactory.create( - { - role: UserRoles.CANDIDATE_EXTERNAL, - }, - {}, - false - ); - - let coach = await userFactory.create( - { - role: UserRoles.COACH_EXTERNAL, - OrganizationId: organization.id, - }, - {}, - true - ); - - const { - createdAt: organizationCreatedAt, - updatedAt: organizationUpdatedAt, - organizationReferent, - ...restOrganization - } = organization; - - let otherExternalCandidate = await userFactory.create( - { - role: UserRoles.CANDIDATE_EXTERNAL, - OrganizationId: organization.id, - }, - {}, - true - ); - - ({ coach: coach, candidate: otherExternalCandidate } = - await userCandidatsHelper.associateCoachAndCandidate( - coach, - otherExternalCandidate, - false - )); - - const { - password: coachPassword, - hashReset: coachHashReset, - salt: coachSalt, - saltReset: coachSaltReset, - revision: coachRevision, - updatedAt: coachUpdatedAt, - createdAt: coachCreatedAt, - lastConnection: coachLastConnection, - candidat, - coaches, - organization: coachOrganization, - readDocuments: coachReadDocuments, - whatsappZoneCoachName: coachWhatsappZoneCoachName, - whatsappZoneCoachUrl: coachWhatsappZoneCoachUrl, - whatsappZoneCoachQR: coachWhatsappZoneCoachQR, - ...restCoach - } = coach; - - const { - password, - hashReset, - salt, - saltReset, - revision, - updatedAt, - createdAt, - lastConnection, - candidat: candidateCandidat, - readDocuments: candidateReadDocuments, - whatsappZoneCoachName: candidatWhatsappZoneCoachName, - whatsappZoneCoachUrl: candidatWhatsappZoneCoachUrl, - whatsappZoneCoachQR: candidatWhatsappZoneCoachQR, - ...restCandidate - } = candidate; - - const { - candidat: { - coach: otherExternalCandidateCoach, - ...restOtherExternalCandidateCandidat - }, - coaches: otherExternalCandidateCoaches, - lastConnection: otherCandidateLastConnection, - organization: otherCandidateOrganization, - readDocuments: otherCandidateReadDocuments, - ...restOtherExternalCandidate - } = otherExternalCandidate; - - const response: APIResponse = - await request(server) - .post(`${route}`) - .set('authorization', `Bearer ${loggedInAdmin.token}`) - .send({ - ...restCandidate, - OrganizationId: organization.id, - userToLinkId: coach.id, - }); - - const coachFromDB = await usersHelper.findUser(coach.id); - - expect(response.status).toBe(201); - expect(response.body).toEqual( - expect.objectContaining({ - ...restCandidate, - candidat: expect.objectContaining({ - coach: expect.objectContaining({ - ...restCoach, - }), - }), - organization: expect.objectContaining({ - ...restOrganization, - }), - }) - ); - - const { - candidat: { coach: candidateCoach, ...restCandidateCandidat }, - } = response.body; - - expect(coachFromDB).toEqual( - expect.objectContaining({ - // coachReadDocuments, - ...restCoach, - coaches: expect.arrayContaining([ - expect.objectContaining({ - ...restCandidateCandidat, - candidat: expect.objectContaining({ - ...restCandidate, - }), - }), - expect.objectContaining({ - ...restOtherExternalCandidateCandidat, - candidat: expect.objectContaining({ - ...restOtherExternalCandidate, - }), - }), - ]), - }) - ); - }); - it('Should return 404 if create external candidate with unexisting coach', async () => { - const { - password, - hashReset, - salt, - saltReset, - revision, - updatedAt, - createdAt, - lastConnection, - whatsappZoneCoachName, - whatsappZoneCoachUrl, - whatsappZoneCoachQR, - ...candidate - } = await userFactory.create( - { - role: UserRoles.CANDIDATE_EXTERNAL, - }, - {}, - false - ); - - const response: APIResponse = - await request(server) - .post(`${route}`) - .set('authorization', `Bearer ${loggedInAdmin.token}`) - .send({ - ...candidate, - OrganizationId: organization.id, - userToLinkId: uuid(), - }); - expect(response.status).toBe(404); - }); - - it('Should return 200 and a created external coach without external candidate', async () => { - const { - password, - hashReset, - salt, - saltReset, - revision, - updatedAt, - createdAt, - lastConnection, - whatsappZoneCoachName, - whatsappZoneCoachUrl, - whatsappZoneCoachQR, - ...coach - } = await userFactory.create( - { - role: UserRoles.COACH_EXTERNAL, - }, - {}, - false - ); - - const { - createdAt: organizationCreatedAt, - updatedAt: organizationUpdatedAt, - organizationReferent, - ...restOrganization - } = organization; - - const response: APIResponse = - await request(server) - .post(`${route}`) - .set('authorization', `Bearer ${loggedInAdmin.token}`) - .send({ ...coach, OrganizationId: organization.id }); - expect(response.status).toBe(201); - expect(response.body).toEqual( - expect.objectContaining({ - ...coach, - organization: expect.objectContaining({ - ...restOrganization, - }), - }) - ); - }); - it('Should return 200 and a created external coach with external candidate', async () => { - const { - password, - hashReset, - salt, - saltReset, - revision, - updatedAt, - createdAt, - lastConnection, - whatsappZoneCoachName, - whatsappZoneCoachUrl, - whatsappZoneCoachQR, - ...coach - } = await userFactory.create( - { - role: UserRoles.COACH_EXTERNAL, - }, - {}, - false - ); - - const { - password: candidatePassword, - hashReset: candidateHashReset, - salt: candidateSalt, - saltReset: candidateSaltReset, - revision: candidateRevision, - updatedAt: candidateUpdatedAt, - createdAt: candidateCreatedAt, - lastConnection: candidateLastConnection, - candidat, - coaches, - organization: candidateOrganization, - readDocuments, - ...candidate - } = await userFactory.create( - { - role: UserRoles.CANDIDATE_EXTERNAL, - OrganizationId: organization.id, - }, - {}, - true - ); - - const { - createdAt: organizationCreatedAt, - updatedAt: organizationUpdatedAt, - organizationReferent, - ...restOrganization - } = organization; - - const response: APIResponse = - await request(server) - .post(`${route}`) - .set('authorization', `Bearer ${loggedInAdmin.token}`) - .send({ - ...coach, - OrganizationId: organization.id, - userToLinkId: [candidate.id], - }); - expect(response.status).toBe(201); - expect(response.body).toEqual( - expect.objectContaining({ - ...coach, - coaches: [ - expect.objectContaining({ - candidat: expect.objectContaining({ - ...candidate, - }), - }), - ], - organization: expect.objectContaining({ - ...restOrganization, - }), - }) - ); - }); - it('Should return 404 if created external coach with unexisting candidate', async () => { - const { - password, - hashReset, - salt, - saltReset, - revision, - updatedAt, - createdAt, - lastConnection, - whatsappZoneCoachName, - whatsappZoneCoachUrl, - whatsappZoneCoachQR, - ...coach - } = await userFactory.create( - { - role: UserRoles.COACH_EXTERNAL, - }, - {}, - false - ); - - const response: APIResponse = - await request(server) - .post(`${route}`) - .set('authorization', `Bearer ${loggedInAdmin.token}`) - .send({ - ...coach, - OrganizationId: organization.id, - userToLinkId: [uuid()], - }); - - expect(response.status).toBe(404); - }); - - it('Should return 400 if external candidate with another external candidate as coach', async () => { - const { - password, - hashReset, - salt, - saltReset, - revision, - updatedAt, - createdAt, - lastConnection, - ...candidate - } = await userFactory.create( - { - role: UserRoles.CANDIDATE_EXTERNAL, - }, - {}, - false - ); - - const { id } = await userFactory.create( - { - role: UserRoles.CANDIDATE_EXTERNAL, - OrganizationId: organization.id, - }, - {}, - true - ); - - const response: APIResponse = - await request(server) - .post(`${route}`) - .set('authorization', `Bearer ${loggedInAdmin.token}`) - .send({ - ...candidate, - OrganizationId: organization.id, - userToLinkId: id, - }); - expect(response.status).toBe(400); - }); - it('Should return 400 if external coach with another external coach as candidate', async () => { - const { - password, - hashReset, - salt, - saltReset, - revision, - updatedAt, - createdAt, - lastConnection, - ...coach - } = await userFactory.create( - { - role: UserRoles.COACH_EXTERNAL, - }, - {}, - false - ); - - const { id } = await userFactory.create( - { - role: UserRoles.COACH_EXTERNAL, - OrganizationId: organization.id, - }, - {}, - true - ); - - const response: APIResponse = - await request(server) - .post(`${route}`) - .set('authorization', `Bearer ${loggedInAdmin.token}`) - .send({ - ...coach, - OrganizationId: organization.id, - userToLinkId: id, - }); - expect(response.status).toBe(400); - }); - - it('Should return 400 if external candidate with multiple external coaches', async () => { - const { - password, - hashReset, - salt, - saltReset, - revision, - updatedAt, - createdAt, - lastConnection, - ...candidate - } = await userFactory.create( - { - role: UserRoles.CANDIDATE_EXTERNAL, - }, - {}, - false - ); - - const { id: coach1Id } = await userFactory.create( - { - role: UserRoles.COACH_EXTERNAL, - OrganizationId: organization.id, - }, - {}, - true - ); - const { id: coach2Id } = await userFactory.create( - { - role: UserRoles.COACH_EXTERNAL, - OrganizationId: organization.id, - }, - {}, - true - ); - - const response: APIResponse = - await request(server) - .post(`${route}`) - .set('authorization', `Bearer ${loggedInAdmin.token}`) - .send({ - ...candidate, - OrganizationId: organization.id, - userToLinkId: [coach1Id, coach2Id], - }); - expect(response.status).toBe(400); - }); - - it('Should return 400 if external candidate without organization', async () => { - const { - password, - hashReset, - salt, - saltReset, - revision, - updatedAt, - createdAt, - lastConnection, - whatsappZoneCoachName, - whatsappZoneCoachUrl, - whatsappZoneCoachQR, - ...coach - } = await userFactory.create( - { role: UserRoles.COACH_EXTERNAL }, - {}, - false - ); - - const response: APIResponse = - await request(server) - .post(`${route}`) - .set('authorization', `Bearer ${loggedInAdmin.token}`) - .send({ - ...coach, - }); - expect(response.status).toBe(400); - }); - it('Should return 400 if external coach without organization', async () => { - const { - password, - hashReset, - salt, - saltReset, - revision, - updatedAt, - createdAt, - lastConnection, - whatsappZoneCoachName, - whatsappZoneCoachUrl, - whatsappZoneCoachQR, - ...candidate - } = await userFactory.create( - { role: UserRoles.CANDIDATE_EXTERNAL }, - {}, - false - ); - - const response: APIResponse = - await request(server) - .post(`${route}`) - .set('authorization', `Bearer ${loggedInAdmin.token}`) - .send({ - ...candidate, - }); - expect(response.status).toBe(400); - }); - - it('Should return 400 if external candidate with external coach from another organization', async () => { - const otherOrganization = await organizationFactory.create( - {}, - {}, - true - ); - - const { - password, - hashReset, - salt, - saltReset, - revision, - updatedAt, - createdAt, - lastConnection, - whatsappZoneCoachName, - whatsappZoneCoachUrl, - whatsappZoneCoachQR, - ...candidate - } = await userFactory.create( - { - role: UserRoles.CANDIDATE_EXTERNAL, - }, - {}, - false - ); - - const { - password: coachPassword, - hashReset: coachHashReset, - salt: coachSalt, - saltReset: coachSaltReset, - revision: coachRevision, - updatedAt: coachUpdatedAt, - createdAt: coachCreatedAt, - lastConnection: coachLastConnection, - candidat, - coaches, - ...coach - } = await userFactory.create( - { - role: UserRoles.COACH_EXTERNAL, - OrganizationId: otherOrganization.id, - }, - {}, - true - ); - - const response: APIResponse = - await request(server) - .post(`${route}`) - .set('authorization', `Bearer ${loggedInAdmin.token}`) - .send({ - ...candidate, - OrganizationId: organization.id, - userToLinkId: coach.id, - }); - expect(response.status).toBe(400); - }); - it('Should return 400 if external coach with external candidate from another organization', async () => { - const otherOrganization = await organizationFactory.create( - {}, - {}, - true - ); - - const { - password, - hashReset, - salt, - saltReset, - revision, - updatedAt, - createdAt, - lastConnection, - ...coach - } = await userFactory.create( - { - role: UserRoles.COACH_EXTERNAL, - }, - {}, - false - ); - - const { - password: candidatePassword, - hashReset: candidateHashReset, - salt: candidateSalt, - saltReset: candidateSaltReset, - revision: candidateRevision, - updatedAt: candidateUpdatedAt, - createdAt: candidateCreatedAt, - lastConnection: candidateLastConnection, - candidat, - coaches, - ...candidate - } = await userFactory.create( - { - role: UserRoles.CANDIDATE_EXTERNAL, - OrganizationId: otherOrganization.id, - }, - {}, - true - ); - - const response: APIResponse = - await request(server) - .post(`${route}`) - .set('authorization', `Bearer ${loggedInAdmin.token}`) - .send({ - ...coach, - OrganizationId: organization.id, - userToLinkId: candidate.id, - }); - expect(response.status).toBe(400); - }); - - it('Should return 400 if external candidate with normal coach', async () => { - const { - password, - hashReset, - salt, - saltReset, - revision, - updatedAt, - createdAt, - lastConnection, - ...externalCandidate - } = await userFactory.create( - { - role: UserRoles.CANDIDATE_EXTERNAL, - }, - {}, - false - ); - - const normalCoach = await userFactory.create( - { role: UserRoles.COACH }, - {}, - true - ); - - const response: APIResponse = - await request(server) - .post(`${route}`) - .set('authorization', `Bearer ${loggedInAdmin.token}`) - .send({ - ...externalCandidate, - OrganizationId: organization.id, - userToLinkId: normalCoach.id, - }); - expect(response.status).toBe(400); - }); - it('Should return 400 if external coach with normal candidate', async () => { - const { - password, - hashReset, - salt, - saltReset, - revision, - updatedAt, - createdAt, - lastConnection, - ...externalCoach - } = await userFactory.create( - { - role: UserRoles.COACH_EXTERNAL, - }, - {}, - false - ); - - const normalCandidate = await userFactory.create( - { role: UserRoles.CANDIDATE }, - {}, - true - ); - - const response: APIResponse = - await request(server) - .post(`${route}`) - .set('authorization', `Bearer ${loggedInAdmin.token}`) - .send({ - ...externalCoach, - OrganizationId: organization.id, - userToLinkId: normalCandidate.id, - }); - expect(response.status).toBe(400); - }); }); }); describe('/registration - Create user through registration', () => { @@ -1874,7 +1052,7 @@ describe('Users', () => { }); it('Should return 400 when user has wrong role', async () => { const user = await userFactory.create( - { role: UserRoles.COACH_EXTERNAL }, + { role: UserRoles.COACH }, {}, false ); @@ -2117,139 +1295,7 @@ describe('Users', () => { expect(response.body.coach.id).toBe(loggedInCoach.user.id); expect(response.body.candidat.id).toBe(loggedInCandidate.user.id); }); - it('Should return 200 and related user candidates if external coach is associated to multiple candidate', async () => { - const organization = await organizationFactory.create({}, {}, true); - - let externalLoggedInCoach = await usersHelper.createLoggedInUser( - { role: UserRoles.COACH_EXTERNAL, OrganizationId: organization.id }, - {}, - true - ); - - let externalLoggedInCandidate1 = await usersHelper.createLoggedInUser( - { - role: UserRoles.CANDIDATE_EXTERNAL, - OrganizationId: organization.id, - }, - {}, - true - ); - - let externalLoggedInCandidate2 = await usersHelper.createLoggedInUser( - { - role: UserRoles.CANDIDATE_EXTERNAL, - OrganizationId: organization.id, - }, - {}, - true - ); - - ({ - loggedInCoach: externalLoggedInCoach, - loggedInCandidate: externalLoggedInCandidate1, - } = await userCandidatsHelper.associateCoachAndCandidate( - externalLoggedInCoach, - externalLoggedInCandidate1, - true - )); - - ({ - loggedInCoach: externalLoggedInCoach, - loggedInCandidate: externalLoggedInCandidate2, - } = await userCandidatsHelper.associateCoachAndCandidate( - externalLoggedInCoach, - externalLoggedInCandidate2, - true - )); - - const { - user: { - password, - hashReset, - salt, - saltReset, - revision, - updatedAt, - createdAt, - lastConnection, - candidat, - coaches, - organization: coachOrganization, - readDocuments: coachReadDocuments, - ...restExternalLoggedInCoach - }, - } = externalLoggedInCoach; - - const { - user: { - password: candidate1Password, - hashReset: candidate1HashReset, - salt: candidate1Salt, - saltReset: candidate1SaltReset, - revision: candidate1Revision, - updatedAt: candidate1UpdatedAt, - createdAt: candidate1CreatedAt, - lastConnection: candidate1LastConnection, - candidat: candidate1Candidat, - coaches: candidate1Coaches, - organization: candidate1Organization, - readDocuments: candidate1ReadDocuments, - ...restExternalLoggedInCandidate1 - }, - } = externalLoggedInCandidate1; - - const { - user: { - password: candidate2Password, - hashReset: candidate2HashReset, - salt: candidate2Salt, - saltReset: candidate2SaltReset, - revision: candidate2Revision, - updatedAt: candidate2UpdatedAt, - createdAt: candidate2CreatedAt, - lastConnection: candidate2LastConnection, - candidat: candidate2Candidat, - coaches: candidate2Coaches, - organization: candidate2Organization, - readDocuments: candidate2ReadDocuments, - ...restExternalLoggedInCandidate2 - }, - } = externalLoggedInCandidate2; - - const response: APIResponse = - await request(server) - .get(`${route}/candidate`) - .set('authorization', `Bearer ${externalLoggedInCoach.token}`) - .query({ - coachId: externalLoggedInCoach.user.id, - }); - expect(response.status).toBe(200); - expect(Array.isArray(response.body)).toBeTruthy(); - assertCondition(Array.isArray(response.body)); - expect(response.body).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - ...candidate1Candidat, - coach: expect.objectContaining({ - ...restExternalLoggedInCoach, - }), - candidat: expect.objectContaining({ - ...restExternalLoggedInCandidate1, - }), - }), - expect.objectContaining({ - ...candidate2Candidat, - coach: expect.objectContaining({ - ...restExternalLoggedInCoach, - }), - candidat: expect.objectContaining({ - ...restExternalLoggedInCandidate2, - }), - }), - ]) - ); - }); it('Should return 403 if logged in user is admin ', async () => { const response: APIResponse = await request(server) @@ -2267,13 +1313,8 @@ describe('Users', () => { let loggedInCoach: LoggedUser; let candidates: User[]; - let externalCandidates: User[]; let coaches: User[]; - let externalCoaches: User[]; let organization: Organization; - let otherOrganization: Organization; - let otherExternalCandidates: User[]; - let otherExternalCoaches: User[]; beforeEach(async () => { loggedInAdmin = await usersHelper.createLoggedInUser({ @@ -2306,52 +1347,6 @@ describe('Users', () => { {}, true ); - - externalCandidates = await databaseHelper.createEntities( - userFactory, - 3, - { - role: UserRoles.CANDIDATE_EXTERNAL, - OrganizationId: organization.id, - }, - {}, - true - ); - - externalCoaches = await databaseHelper.createEntities( - userFactory, - 3, - { - role: UserRoles.COACH_EXTERNAL, - OrganizationId: organization.id, - }, - {}, - true - ); - - otherOrganization = await organizationFactory.create({}, {}, true); - - otherExternalCandidates = await databaseHelper.createEntities( - userFactory, - 3, - { - role: UserRoles.CANDIDATE_EXTERNAL, - OrganizationId: otherOrganization.id, - }, - {}, - true - ); - - otherExternalCoaches = await databaseHelper.createEntities( - userFactory, - 3, - { - role: UserRoles.COACH_EXTERNAL, - OrganizationId: otherOrganization.id, - }, - {}, - true - ); }); it('Should return 200 and part of candidates if user is logged in as admin and filters by candidates and searches by first name', async () => { @@ -2409,42 +1404,6 @@ describe('Users', () => { expect.arrayContaining(response.body.map(({ id }) => id)) ); }); - it('Should return 200 and external candidates if user is logged in as admin and filters by external candidates', async () => { - const expectedUsersId = [ - ...externalCandidates.map(({ id }) => id), - ...otherExternalCandidates.map(({ id }) => id), - ]; - - const response: APIResponse = - await request(server) - .get(`${route}/search?&role[]=${UserRoles.CANDIDATE_EXTERNAL}`) - .set('authorization', `Bearer ${loggedInAdmin.token}`); - - expect(response.status).toBe(200); - expect(expectedUsersId).toEqual( - expect.arrayContaining(response.body.map(({ id }) => id)) - ); - }); - it('Should return 200 and all candidates if user is logged in as admin and filters by all candidates', async () => { - const expectedUsersId = [ - ...candidates.map(({ id }) => id), - ...externalCandidates.map(({ id }) => id), - ...otherExternalCandidates.map(({ id }) => id), - loggedInCandidate.user.id, - ]; - - const response: APIResponse = - await request(server) - .get( - `${route}/search?&role[]=${UserRoles.CANDIDATE_EXTERNAL}&role[]=${UserRoles.CANDIDATE}` - ) - .set('authorization', `Bearer ${loggedInAdmin.token}`); - - expect(response.status).toBe(200); - expect(expectedUsersId).toEqual( - expect.arrayContaining(response.body.map(({ id }) => id)) - ); - }); it('Should return 200 and normal coaches if user is logged in as admin and filters by normal coaches', async () => { const expectedUsersId = [ @@ -2462,66 +1421,16 @@ describe('Users', () => { expect.arrayContaining(response.body.map(({ id }) => id)) ); }); - it('Should return 200 and external coaches if user is logged in as admin and filters by external coaches', async () => { - const expectedUsersId = [ - ...externalCoaches.map(({ id }) => id), - ...otherExternalCoaches.map(({ id }) => id), - ]; - - const response: APIResponse = - await request(server) - .get(`${route}/search?&role[]=${UserRoles.COACH_EXTERNAL}`) - .set('authorization', `Bearer ${loggedInAdmin.token}`); - expect(response.status).toBe(200); - expect(expectedUsersId).toEqual( - expect.arrayContaining(response.body.map(({ id }) => id)) - ); - }); it('Should return 200 and all coaches if user is logged in as admin and filters by all coaches', async () => { const expectedUsersId = [ ...coaches.map(({ id }) => id), - ...externalCoaches.map(({ id }) => id), - ...otherExternalCoaches.map(({ id }) => id), loggedInCoach.user.id, ]; const response: APIResponse = await request(server) - .get( - `${route}/search?&role[]=${UserRoles.COACH_EXTERNAL}&role[]=${UserRoles.COACH}` - ) - .set('authorization', `Bearer ${loggedInAdmin.token}`); - - expect(response.status).toBe(200); - expect(expectedUsersId).toEqual( - expect.arrayContaining(response.body.map(({ id }) => id)) - ); - }); - - it('Should return 200 and part of external candidates from specific organization if user is logged in as admin and filters by external candidates from an organization', async () => { - const expectedUsersId = externalCandidates.map(({ id }) => id); - - const response: APIResponse = - await request(server) - .get( - `${route}/search?&role[]=${UserRoles.CANDIDATE_EXTERNAL}&organizationId=${organization.id}` - ) - .set('authorization', `Bearer ${loggedInAdmin.token}`); - - expect(response.status).toBe(200); - expect(expectedUsersId).toEqual( - expect.arrayContaining(response.body.map(({ id }) => id)) - ); - }); - it('Should return 200 and part of external coaches from specific organization if user is logged in as admin and filters by external coaches from an organization', async () => { - const expectedUsersId = externalCoaches.map(({ id }) => id); - - const response: APIResponse = - await request(server) - .get( - `${route}/search?&role[]=${UserRoles.COACH_EXTERNAL}&organizationId=${organization.id}` - ) + .get(`${route}/search?&role[]=${UserRoles.COACH}`) .set('authorization', `Bearer ${loggedInAdmin.token}`); expect(response.status).toBe(200); @@ -3362,24 +2271,27 @@ describe('Users', () => { it('Should return 200, and all the candidates that match all the filters', async () => { const organization = await organizationFactory.create({}, {}, true); - const lyonAssociatedExternalCoaches = - await databaseHelper.createEntities(userFactory, 2, { + const lyonAssociatedCoaches = await databaseHelper.createEntities( + userFactory, + 2, + { firstName: 'XXX', - role: UserRoles.COACH_EXTERNAL, + role: UserRoles.COACH, zone: AdminZones.LYON, OrganizationId: organization.id, - }); + } + ); - const lyonAssociatedExternalCandidates = + const lyonAssociatedCandidates = await databaseHelper.createEntities(userFactory, 2, { firstName: 'XXX', - role: UserRoles.CANDIDATE_EXTERNAL, + role: UserRoles.CANDIDATE, zone: AdminZones.LYON, OrganizationId: organization.id, }); await Promise.all( - lyonAssociatedExternalCandidates.map(async ({ id }) => { + lyonAssociatedCandidates.map(async ({ id }) => { return cvFactory.create( { UserId: id, @@ -3392,22 +2304,22 @@ describe('Users', () => { ); await Promise.all( - lyonAssociatedExternalCandidates.map(async (candidate, index) => { + lyonAssociatedCandidates.map(async (candidate, index) => { return userCandidatsHelper.associateCoachAndCandidate( - lyonAssociatedExternalCoaches[index], + lyonAssociatedCoaches[index], candidate ); }) ); const expectedCandidatesIds = [ - ...lyonAssociatedExternalCandidates.map(({ id }) => id), + ...lyonAssociatedCandidates.map(({ id }) => id), ]; const response: APIResponse = await request(server) .get( - `${route}/members?limit=50&offset=0&role[]=${UserRoles.CANDIDATE}&role[]=${UserRoles.CANDIDATE_EXTERNAL}&hidden[]=false&employed[]=false&query=XXX&zone[]=${AdminZones.LYON}&cvStatus[]=${CVStatuses.PUBLISHED.value}&businessLines[]=rh&associatedUser[]=true` + `${route}/members?limit=50&offset=0&role[]=${UserRoles.CANDIDATE}&role[]=${UserRoles.CANDIDATE}&hidden[]=false&employed[]=false&query=XXX&zone[]=${AdminZones.LYON}&cvStatus[]=${CVStatuses.PUBLISHED.value}&businessLines[]=rh&associatedUser[]=true` ) .set('authorization', `Bearer ${loggedInAdmin.token}`); expect(response.status).toBe(200); @@ -3419,39 +2331,45 @@ describe('Users', () => { it('Should return 200, and all the coaches that match all the filters', async () => { const organization = await organizationFactory.create({}, {}, true); - const lyonAssociatedExternalCoaches = - await databaseHelper.createEntities(userFactory, 2, { + const lyonAssociatedCoaches = await databaseHelper.createEntities( + userFactory, + 2, + { firstName: 'XXX', - role: UserRoles.COACH_EXTERNAL, + role: UserRoles.COACH, zone: AdminZones.LYON, OrganizationId: organization.id, - }); + } + ); - const associatedExternalCandidates = - await databaseHelper.createEntities(userFactory, 2, { + const associatedCandidates = await databaseHelper.createEntities( + userFactory, + 2, + { firstName: 'XXX', - role: UserRoles.CANDIDATE_EXTERNAL, + role: UserRoles.CANDIDATE, zone: AdminZones.LYON, OrganizationId: organization.id, - }); + } + ); await Promise.all( - associatedExternalCandidates.map(async (candidate, index) => { + associatedCandidates.map(async (candidate, index) => { return userCandidatsHelper.associateCoachAndCandidate( - lyonAssociatedExternalCoaches[index], + lyonAssociatedCoaches[index], candidate ); }) ); const expectedCoachesIds = [ - ...lyonAssociatedExternalCoaches.map(({ id }) => id), + ...lyonAssociatedCoaches.map(({ id }) => id), ]; const response: APIResponse = await request(server) .get( - `${route}/members?limit=50&offset=0&role[]=${UserRoles.COACH}&role[]=${UserRoles.COACH_EXTERNAL}&query=XXX&zone[]=${AdminZones.LYON}&associatedUser[]=true` + `${route}/members?limit=50&offset=0&role[]=${UserRoles.COACH}&query=XXX&zone[]=${AdminZones.LYON}&associatedUser[]=true` ) .set('authorization', `Bearer ${loggedInAdmin.token}`); expect(response.status).toBe(200); @@ -3674,9 +2592,7 @@ describe('Users', () => { describe('/profile/recommendations/:userId - Get user recommendations', () => { let loggedInAdmin: LoggedUser; let loggedInCandidate: LoggedUser; - let loggedInExternalCandidate: LoggedUser; let loggedInCoach: LoggedUser; - let loggedInExternalCoach: LoggedUser; beforeEach(async () => { loggedInAdmin = await usersHelper.createLoggedInUser({ @@ -3685,15 +2601,8 @@ describe('Users', () => { loggedInCandidate = await usersHelper.createLoggedInUser({ role: UserRoles.CANDIDATE, }); - loggedInCoach = await usersHelper.createLoggedInUser({ - role: UserRoles.COACH, - }); - - loggedInExternalCandidate = await usersHelper.createLoggedInUser({ - role: UserRoles.CANDIDATE_EXTERNAL, - }); - loggedInExternalCoach = await usersHelper.createLoggedInUser({ - role: UserRoles.COACH_EXTERNAL, + loggedInCoach = await usersHelper.createLoggedInUser({ + role: UserRoles.COACH, }); ({ loggedInCoach, loggedInCandidate } = @@ -3702,15 +2611,6 @@ describe('Users', () => { loggedInCandidate, true )); - - ({ - loggedInCoach: loggedInExternalCoach, - loggedInCandidate: loggedInExternalCandidate, - } = await userCandidatsHelper.associateCoachAndCandidate( - loggedInExternalCoach, - loggedInExternalCandidate, - true - )); }); it('Should return 401, if user not logged in', async () => { @@ -3740,27 +2640,6 @@ describe('Users', () => { .set('authorization', `Bearer ${loggedInAdmin.token}`); expect(response.status).toBe(403); }); - it("Should return 403, if external coach get his candidate's recommendations", async () => { - const response: APIResponse< - UserProfilesController['findRecommendationsByUserId'] - > = await request(server) - .get( - `${route}/profile/recommendations/${loggedInExternalCandidate.user.id}` - ) - .set('authorization', `Bearer ${loggedInExternalCoach.token}`); - - expect(response.status).toBe(403); - }); - it('Should return 403, if external coach gets recommendations for another user', async () => { - const response: APIResponse< - UserProfilesController['findRecommendationsByUserId'] - > = await request(server) - .get( - `${route}/profile/recommendations/${loggedInExternalCoach.user.id}` - ) - .set('authorization', `Bearer ${loggedInExternalCoach.token}`); - expect(response.status).toBe(403); - }); it('Should return 403, if coach gets recommendations for another user', async () => { const response: APIResponse< UserProfilesController['findRecommendationsByUserId'] @@ -4952,18 +3831,6 @@ describe('Users', () => { .set('authorization', `Bearer ${loggedInCandidate.token}`); expect(response.status).toBe(400); }); - it('Should return 400 if external coach in role parameter', async () => { - const loggedInCandidate = await usersHelper.createLoggedInUser({ - role: UserRoles.CANDIDATE, - }); - const response: APIResponse = - await request(server) - .get( - `${route}/profile?offset=0&limit=25&role[]=${UserRoles.COACH_EXTERNAL}&role[]=${UserRoles.COACH}` - ) - .set('authorization', `Bearer ${loggedInCandidate.token}`); - expect(response.status).toBe(400); - }); describe('/profile?limit=&offset=&role[]= - Get paginated and creation date sorted users filtered by role', () => { let loggedInCandidate: LoggedUser; @@ -5913,7 +4780,7 @@ describe('Users', () => { const response: APIResponse = await request(server) .get( - `${route}/profile?limit=50&offset=0&role[]=${UserRoles.CANDIDATE}&role[]=${UserRoles.CANDIDATE_EXTERNAL}&query=XXX&departments[]=Rhône (69)&businessLines[]=rh&helps[]=network` + `${route}/profile?limit=50&offset=0&role[]=${UserRoles.CANDIDATE}&query=XXX&departments[]=Rhône (69)&businessLines[]=rh&helps[]=network` ) .set('authorization', `Bearer ${loggedInAdmin.token}`); expect(response.status).toBe(200); @@ -6337,9 +5204,6 @@ describe('Users', () => { let loggedInAdmin: LoggedUser; let loggedInCandidate: LoggedUser; let loggedInCoach: LoggedUser; - let externalCoach: User; - let externalCandidate: User; - let organization: Organization; beforeEach(async () => { loggedInAdmin = await usersHelper.createLoggedInUser({ @@ -6351,23 +5215,6 @@ describe('Users', () => { loggedInCoach = await usersHelper.createLoggedInUser({ role: UserRoles.COACH, }); - - organization = await organizationFactory.create({}, {}, true); - - externalCoach = await userFactory.create( - { role: UserRoles.COACH_EXTERNAL, OrganizationId: organization.id }, - {}, - true - ); - - externalCandidate = await userFactory.create( - { - role: UserRoles.CANDIDATE_EXTERNAL, - OrganizationId: organization.id, - }, - {}, - true - ); }); it('Should return 401, if user not logged in', async () => { @@ -6446,291 +5293,30 @@ describe('Users', () => { }); expect(response.status).toBe(200); expect(response.body).toEqual( - expect.objectContaining({ - ...restCandidate, - candidat: expect.objectContaining({ - ...candidat, - coach: expect.objectContaining({ - ...restCoach, - }), - }), - }) - ); - }); - it('Should return 200 if admin updates linked candidate for coach', async () => { - const { - candidat: { coach, ...restCandidateCandidat }, - coaches, - lastConnection, - createdAt, - organization, - readDocuments, - whatsappZoneCoachName, - whatsappZoneCoachUrl, - whatsappZoneCoachQR, - ...restCandidate - } = loggedInCandidate.user; - - const { - candidat: coachCandidat, - coaches: coachCoaches, - lastConnection: lastConnectionCoach, - createdAt: createdAtCoach, - organization: coachOrganization, - whatsappZoneCoachName: coachWhatsappZoneCoachName, - whatsappZoneCoachUrl: coachWhatsappZoneCoachUrl, - whatsappZoneCoachQR: coachWhatsappZoneCoachQR, - ...restCoach - } = loggedInCoach.user; - - const response: APIResponse = - await request(server) - .put(`${route}/linkUser/${loggedInCoach.user.id}`) - .set('authorization', `Bearer ${loggedInAdmin.token}`) - .send({ - userToLinkId: loggedInCandidate.user.id, - }); - expect(response.status).toBe(200); - expect(response.body).toEqual( - expect.objectContaining({ - ...restCoach, - coaches: [ - expect.objectContaining({ - ...restCandidateCandidat, - candidat: expect.objectContaining({ - ...restCandidate, - }), - }), - ], - }) - ); - }); - - it('Should return 200 if admin removes linked coach for candidate', async () => { - ({ loggedInCandidate, loggedInCoach } = - await userCandidatsHelper.associateCoachAndCandidate( - loggedInCoach, - loggedInCandidate, - true - )); - - const { - candidat, - coaches, - lastConnection, - createdAt, - organization, - ...restCandidate - } = loggedInCandidate.user; - - const response: APIResponse = - await request(server) - .put(`${route}/linkUser/${loggedInCandidate.user.id}`) - .set('authorization', `Bearer ${loggedInAdmin.token}`) - .send({ - userToLinkId: null, - }); - expect(response.status).toBe(200); - expect(response.body).toEqual( - expect.objectContaining({ - ...restCandidate, - candidat: expect.objectContaining({ - ...candidat, - coach: null, - }), - }) - ); - }); - it('Should return 200 if admin removes linked candidate for coach', async () => { - ({ loggedInCandidate, loggedInCoach } = - await userCandidatsHelper.associateCoachAndCandidate( - loggedInCoach, - loggedInCandidate, - true - )); - - const { - candidat, - coaches, - lastConnection, - createdAt, - organization, - whatsappZoneCoachName, - whatsappZoneCoachUrl, - whatsappZoneCoachQR, - ...restCoach - } = loggedInCoach.user; - - const response: APIResponse = - await request(server) - .put(`${route}/linkUser/${loggedInCoach.user.id}`) - .set('authorization', `Bearer ${loggedInAdmin.token}`) - .send({ - userToLinkId: null, - }); - expect(response.status).toBe(200); - expect(response.body).toEqual( - expect.objectContaining({ - ...restCoach, - coaches: [], - }) - ); - }); - - it('Should return 400 if admin updates linked multiple coaches for candidate', async () => { - const otherCoach = await userFactory.create( - { role: UserRoles.COACH }, - {}, - true - ); - - const response: APIResponse = - await request(server) - .put(`${route}/linkUser/${loggedInCandidate.user.id}`) - .set('authorization', `Bearer ${loggedInAdmin.token}`) - .send({ - userToLinkId: [loggedInCoach.user.id, otherCoach.id], - }); - expect(response.status).toBe(400); - }); - it('Should return 400 if admin updates linked multiple candidates for coach', async () => { - const otherCandidate = await userFactory.create( - { role: UserRoles.CANDIDATE }, - {}, - true - ); - const response: APIResponse = - await request(server) - .put(`${route}/linkUser/${loggedInCoach.user.id}`) - .set('authorization', `Bearer ${loggedInAdmin.token}`) - .send({ - userToLinkId: [loggedInCandidate.user.id, otherCandidate.id], - }); - expect(response.status).toBe(400); - }); - - it('Should return 200 if admin updates linked external coach for external candidate with same organization', async () => { - const { - candidat: { coach, ...restExternalCandidateCandidat }, - coaches, - lastConnection, - createdAt, - organization: candidateOrganization, - readDocuments: candidateReadDocuments, - ...restExternalCandidate - } = externalCandidate; - - let otherExternalCandidate = await userFactory.create( - { - role: UserRoles.CANDIDATE_EXTERNAL, - OrganizationId: organization.id, - }, - {}, - true - ); - - ({ coach: externalCoach, candidate: otherExternalCandidate } = - await userCandidatsHelper.associateCoachAndCandidate( - externalCoach, - otherExternalCandidate, - false - )); - - const { - candidat: { - coach: otherExternalCandidateCoach, - ...restOtherExternalCandidateCandidat - }, - coaches: otherCandidateCoaches, - lastConnection: otherCandidateLastConnection, - createdAt: otherCandidateCreatedAt, - organization: otherCandidateOrganization, - readDocuments: otherCandidateReadDocuments, - ...restOtherExternalCandidate - } = otherExternalCandidate; - - const { - candidat: coachCandidat, - coaches: coachCoaches, - lastConnection: lastConnectionCoach, - createdAt: createdAtCoach, - organization: coachOrganization, - readDocuments: externalCoachReadDocuments, - ...restExternalCoach - } = externalCoach; - - const response: APIResponse = - await request(server) - .put(`${route}/linkUser/${externalCandidate.id}`) - .set('authorization', `Bearer ${loggedInAdmin.token}`) - .send({ - userToLinkId: externalCoach.id, - }); - - const coachFromDB = await usersHelper.findUser(externalCoach.id); - - expect(response.status).toBe(200); - expect(response.body).toEqual( - expect.objectContaining({ - ...restExternalCandidate, - candidat: expect.objectContaining({ - ...restExternalCandidateCandidat, - coach: expect.objectContaining({ - ...restExternalCoach, - }), - }), - }) - ); - expect(coachFromDB).toEqual( - expect.objectContaining({ - ...restExternalCoach, - coaches: expect.arrayContaining([ - expect.objectContaining({ - ...restExternalCandidateCandidat, - candidat: expect.objectContaining({ - ...restExternalCandidate, - }), - }), - expect.objectContaining({ - ...restOtherExternalCandidateCandidat, - candidat: expect.objectContaining({ - ...restOtherExternalCandidate, - }), + expect.objectContaining({ + ...restCandidate, + candidat: expect.objectContaining({ + ...candidat, + coach: expect.objectContaining({ + ...restCoach, }), - ]), + }), }) ); }); - it('Should return 200 if admin updates linked multiple external candidate for external coach with same organization', async () => { - const { - candidat: { - coach: otherExternalCandidateCoach, - ...restOtherExternalCandidateCandidat - }, - coaches: otherCandidateCoaches, - lastConnection: otherCandidateLastConnection, - createdAt: otherCandidateCreatedAt, - organization: otherCandidateOrganization, - readDocuments: otherCandidateReadDocuments, - ...restOtherExternalCandidate - } = await userFactory.create( - { - role: UserRoles.CANDIDATE_EXTERNAL, - OrganizationId: organization.id, - }, - {}, - true - ); - + it('Should return 200 if admin updates linked candidate for coach', async () => { const { - candidat: { coach, ...restExternalCandidateCandidat }, + candidat: { coach, ...restCandidateCandidat }, coaches, lastConnection, createdAt, - organization: candidateOrganization, - readDocuments: candidateReadDocuments, - ...restExternalCandidate - } = externalCandidate; + organization, + readDocuments, + whatsappZoneCoachName, + whatsappZoneCoachUrl, + whatsappZoneCoachQR, + ...restCandidate + } = loggedInCandidate.user; const { candidat: coachCandidat, @@ -6738,47 +5324,41 @@ describe('Users', () => { lastConnection: lastConnectionCoach, createdAt: createdAtCoach, organization: coachOrganization, - ...restExternalCoach - } = externalCoach; + whatsappZoneCoachName: coachWhatsappZoneCoachName, + whatsappZoneCoachUrl: coachWhatsappZoneCoachUrl, + whatsappZoneCoachQR: coachWhatsappZoneCoachQR, + ...restCoach + } = loggedInCoach.user; const response: APIResponse = await request(server) - .put(`${route}/linkUser/${externalCoach.id}`) + .put(`${route}/linkUser/${loggedInCoach.user.id}`) .set('authorization', `Bearer ${loggedInAdmin.token}`) .send({ - userToLinkId: [ - externalCandidate.id, - restOtherExternalCandidate.id, - ], + userToLinkId: loggedInCandidate.user.id, }); expect(response.status).toBe(200); expect(response.body).toEqual( expect.objectContaining({ - ...restExternalCoach, - coaches: expect.arrayContaining([ - expect.objectContaining({ - ...restExternalCandidateCandidat, - candidat: expect.objectContaining({ - ...restExternalCandidate, - }), - }), + ...restCoach, + coaches: [ expect.objectContaining({ - ...restOtherExternalCandidateCandidat, + ...restCandidateCandidat, candidat: expect.objectContaining({ - ...restOtherExternalCandidate, + ...restCandidate, }), }), - ]), + ], }) ); }); - it('Should return 200 if admin removes linked external coach for external candidate', async () => { - ({ coach: externalCoach, candidate: externalCandidate } = + it('Should return 200 if admin removes linked coach for candidate', async () => { + ({ loggedInCandidate, loggedInCoach } = await userCandidatsHelper.associateCoachAndCandidate( - externalCoach, - externalCandidate, - false + loggedInCoach, + loggedInCandidate, + true )); const { @@ -6786,22 +5366,21 @@ describe('Users', () => { coaches, lastConnection, createdAt, - organization: candidateOrganization, - ...restExternalCandidate - } = externalCandidate; + organization, + ...restCandidate + } = loggedInCandidate.user; const response: APIResponse = await request(server) - .put(`${route}/linkUser/${externalCandidate.id}`) + .put(`${route}/linkUser/${loggedInCandidate.user.id}`) .set('authorization', `Bearer ${loggedInAdmin.token}`) .send({ userToLinkId: null, }); - expect(response.status).toBe(200); expect(response.body).toEqual( expect.objectContaining({ - ...restExternalCandidate, + ...restCandidate, candidat: expect.objectContaining({ ...candidat, coach: null, @@ -6809,26 +5388,29 @@ describe('Users', () => { }) ); }); - it('Should return 200 if admin removes linked multiple external candidate for external coach', async () => { - ({ coach: externalCoach, candidate: externalCandidate } = + it('Should return 200 if admin removes linked candidate for coach', async () => { + ({ loggedInCandidate, loggedInCoach } = await userCandidatsHelper.associateCoachAndCandidate( - externalCoach, - externalCandidate, - false + loggedInCoach, + loggedInCandidate, + true )); const { - candidat: coachCandidat, - coaches: coachCoaches, - lastConnection: lastConnectionCoach, - createdAt: createdAtCoach, - organization: coachOrganization, - ...restExternalCoach - } = externalCoach; + candidat, + coaches, + lastConnection, + createdAt, + organization, + whatsappZoneCoachName, + whatsappZoneCoachUrl, + whatsappZoneCoachQR, + ...restCoach + } = loggedInCoach.user; const response: APIResponse = await request(server) - .put(`${route}/linkUser/${externalCoach.id}`) + .put(`${route}/linkUser/${loggedInCoach.user.id}`) .set('authorization', `Bearer ${loggedInAdmin.token}`) .send({ userToLinkId: null, @@ -6836,191 +5418,64 @@ describe('Users', () => { expect(response.status).toBe(200); expect(response.body).toEqual( expect.objectContaining({ - ...restExternalCoach, + ...restCoach, coaches: [], }) ); }); - it('Should return 400 if admin updates linked external multiple coaches for candidate', async () => { - const otherExternalCoach = await userFactory.create( - { role: UserRoles.COACH, OrganizationId: organization.id }, - {}, - true - ); - - const response: APIResponse = - await request(server) - .put(`${route}/linkUser/${externalCandidate.id}`) - .set('authorization', `Bearer ${loggedInAdmin.token}`) - .send({ - userToLinkId: [externalCoach.id, otherExternalCoach.id], - }); - expect(response.status).toBe(400); - }); - - it('Should return 400 if admin updates normal candidate with another normal candidate as coach', async () => { - const otherCandidate = await userFactory.create( - { role: UserRoles.CANDIDATE }, - {}, - true - ); - - const response: APIResponse = - await request(server) - .put(`${route}/linkUser/${loggedInCandidate.user.id}`) - .set('authorization', `Bearer ${loggedInAdmin.token}`) - .send({ - userToLinkId: otherCandidate.id, - }); - expect(response.status).toBe(400); - }); - it('Should return 400 if admin updates normal coach with another normal coach as candidate', async () => { + it('Should return 400 if admin updates linked multiple coaches for candidate', async () => { const otherCoach = await userFactory.create( - { - role: UserRoles.COACH, - }, - {}, - true - ); - - const response: APIResponse = - await request(server) - .put(`${route}/linkUser/${otherCoach.id}`) - .set('authorization', `Bearer ${loggedInAdmin.token}`) - .send({ - userToLinkId: otherCoach.id, - }); - expect(response.status).toBe(400); - }); - - it('Should return 400 if admin updates external candidate with another external candidate as coach', async () => { - const otherExternalCandidate = await userFactory.create( - { - role: UserRoles.CANDIDATE_EXTERNAL, - OrganizationId: organization.id, - }, - {}, - true - ); - - const response: APIResponse = - await request(server) - .put(`${route}/linkUser/${externalCandidate.id}`) - .set('authorization', `Bearer ${loggedInAdmin.token}`) - .send({ - userToLinkId: otherExternalCandidate.id, - }); - expect(response.status).toBe(400); - }); - it('Should return 400 if admin updates external coach with another external coach as candidate', async () => { - const otherExternalCoach = await userFactory.create( - { - role: UserRoles.COACH_EXTERNAL, - OrganizationId: organization.id, - }, + { role: UserRoles.COACH }, {}, true ); - const response: APIResponse = - await request(server) - .put(`${route}/linkUser/${externalCoach.id}`) - .set('authorization', `Bearer ${loggedInAdmin.token}`) - .send({ - userToLinkId: otherExternalCoach.id, - }); - expect(response.status).toBe(400); - }); - - it('Should return 400 if admin updates normal candidate with external coach', async () => { - const externalCoach = await userFactory.create( - { role: UserRoles.COACH_EXTERNAL }, - {}, - true - ); const response: APIResponse = await request(server) .put(`${route}/linkUser/${loggedInCandidate.user.id}`) .set('authorization', `Bearer ${loggedInAdmin.token}`) .send({ - userToLinkId: externalCoach.id, + userToLinkId: [loggedInCoach.user.id, otherCoach.id], }); expect(response.status).toBe(400); }); - it('Should return 400 if admin updates normal coach with external candidate', async () => { - const externalCandidate = await userFactory.create( - { role: UserRoles.CANDIDATE_EXTERNAL }, + it('Should return 400 if admin updates linked multiple candidates for coach', async () => { + const otherCandidate = await userFactory.create( + { role: UserRoles.CANDIDATE }, {}, true ); - const response: APIResponse = await request(server) .put(`${route}/linkUser/${loggedInCoach.user.id}`) .set('authorization', `Bearer ${loggedInAdmin.token}`) .send({ - userToLinkId: externalCandidate.id, - }); - expect(response.status).toBe(400); - }); - - it('Should return 400 if admin updates external candidate with normal coach', async () => { - const response: APIResponse = - await request(server) - .put(`${route}/linkUser/${externalCandidate.id}`) - .set('authorization', `Bearer ${loggedInAdmin.token}`) - .send({ - userToLinkId: loggedInCoach.user.id, - }); - expect(response.status).toBe(400); - }); - it('Should return 400 if admin updates external coach with normal candidate', async () => { - const response: APIResponse = - await request(server) - .put(`${route}/linkUser/${externalCoach.id}`) - .set('authorization', `Bearer ${loggedInAdmin.token}`) - .send({ - userToLinkId: loggedInCandidate.user.id, + userToLinkId: [loggedInCandidate.user.id, otherCandidate.id], }); expect(response.status).toBe(400); }); - it('Should return 400 if admin updates external candidate with external coach from another organization', async () => { - const otherOrganization = await organizationFactory.create( - {}, + it('Should return 400 if admin updates normal candidate with another normal candidate as coach', async () => { + const otherCandidate = await userFactory.create( + { role: UserRoles.CANDIDATE }, {}, true ); - const otherExternalCoach = await userFactory.create( - { - role: UserRoles.COACH_EXTERNAL, - OrganizationId: otherOrganization.id, - }, - {}, - true - ); const response: APIResponse = await request(server) - .put(`${route}/linkUser/${externalCandidate.id}`) + .put(`${route}/linkUser/${loggedInCandidate.user.id}`) .set('authorization', `Bearer ${loggedInAdmin.token}`) .send({ - userToLinkId: otherExternalCoach.id, + userToLinkId: otherCandidate.id, }); expect(response.status).toBe(400); }); - it('Should return 400 if admin updates external coach with external candidate from another organization', async () => { - const otherOrganization = await organizationFactory.create( - {}, - {}, - true - ); - - const otherExternalCandidate = await userFactory.create( + it('Should return 400 if admin updates normal coach with another normal coach as candidate', async () => { + const otherCoach = await userFactory.create( { - role: UserRoles.CANDIDATE_EXTERNAL, - OrganizationId: otherOrganization.id, + role: UserRoles.COACH, }, {}, true @@ -7028,10 +5483,10 @@ describe('Users', () => { const response: APIResponse = await request(server) - .put(`${route}/linkUser/${externalCoach.id}`) + .put(`${route}/linkUser/${otherCoach.id}`) .set('authorization', `Bearer ${loggedInAdmin.token}`) .send({ - userToLinkId: otherExternalCandidate.id, + userToLinkId: otherCoach.id, }); expect(response.status).toBe(400); }); @@ -7252,9 +5707,7 @@ describe('Users', () => { describe('/profile/:userId - Update user profile', () => { let loggedInAdmin: LoggedUser; let loggedInCandidate: LoggedUser; - let loggedInExternalCandidate: LoggedUser; let loggedInCoach: LoggedUser; - let loggedInExternalCoach: LoggedUser; beforeEach(async () => { loggedInAdmin = await usersHelper.createLoggedInUser({ @@ -7291,41 +5744,12 @@ describe('Users', () => { } ); - loggedInExternalCandidate = await usersHelper.createLoggedInUser( - { - role: UserRoles.CANDIDATE_EXTERNAL, - zone: AdminZones.LYON, - }, - { - userProfile: { - department: 'Rhône (69)', - isAvailable: true, - searchBusinessLines: [{ name: 'bat' }] as BusinessLine[], - searchAmbitions: [{ name: 'menuisier' }] as Ambition[], - helpNeeds: [{ name: 'interview' }] as HelpNeed[], - }, - } - ); - loggedInExternalCoach = await usersHelper.createLoggedInUser({ - role: UserRoles.COACH_EXTERNAL, - zone: AdminZones.LYON, - }); - ({ loggedInCoach, loggedInCandidate } = await userCandidatsHelper.associateCoachAndCandidate( loggedInCoach, loggedInCandidate, true )); - - ({ - loggedInCoach: loggedInExternalCoach, - loggedInCandidate: loggedInExternalCandidate, - } = await userCandidatsHelper.associateCoachAndCandidate( - loggedInExternalCoach, - loggedInExternalCandidate, - true - )); }); it('Should return 401, if user not logged in', async () => { const response: APIResponse< @@ -7362,32 +5786,6 @@ describe('Users', () => { }); expect(response.status).toBe(403); }); - it('Should return 403, if external coach updates his user profile', async () => { - const response: APIResponse< - UserProfilesController['updateByUserId'] - > = await request(server) - .put(`${route}/profile/${loggedInExternalCoach.user.id}`) - .set('authorization', `Bearer ${loggedInExternalCoach.token}`) - .send({ - description: 'hello', - isAvailable: false, - department: 'Paris (75)', - }); - expect(response.status).toBe(403); - }); - it('Should return 403, if external coach updates the profile for another user', async () => { - const response: APIResponse< - UserProfilesController['updateByUserId'] - > = await request(server) - .put(`${route}/profile/${loggedInExternalCandidate.user.id}`) - .set('authorization', `Bearer ${loggedInExternalCoach.token}`) - .send({ - description: 'hello', - isAvailable: false, - department: 'Paris (75)', - }); - expect(response.status).toBe(403); - }); it('Should return 403, if coach updates a profile for another user', async () => { const response: APIResponse< UserProfilesController['updateByUserId'] @@ -7540,9 +5938,7 @@ describe('Users', () => { let loggedInAdmin: LoggedUser; let loggedInCandidate: LoggedUser; - let loggedInExternalCandidate: LoggedUser; let loggedInCoach: LoggedUser; - let loggedInExternalCoach: LoggedUser; beforeEach(async () => { path = userProfilesHelper.getTestImagePath(); @@ -7556,12 +5952,6 @@ describe('Users', () => { loggedInCoach = await usersHelper.createLoggedInUser({ role: UserRoles.COACH, }); - loggedInExternalCandidate = await usersHelper.createLoggedInUser({ - role: UserRoles.CANDIDATE_EXTERNAL, - }); - loggedInExternalCoach = await usersHelper.createLoggedInUser({ - role: UserRoles.COACH_EXTERNAL, - }); ({ loggedInCoach, loggedInCandidate } = await userCandidatsHelper.associateCoachAndCandidate( @@ -7569,15 +5959,6 @@ describe('Users', () => { loggedInCandidate, true )); - - ({ - loggedInCoach: loggedInExternalCoach, - loggedInCandidate: loggedInExternalCandidate, - } = await userCandidatsHelper.associateCoachAndCandidate( - loggedInExternalCoach, - loggedInExternalCandidate, - true - )); }); it('Should return 401, if user not logged in', async () => { @@ -7610,30 +5991,6 @@ describe('Users', () => { .attach('profileImage', path); expect(response.status).toBe(403); }); - it('Should return 201, if external coach uploads his profile picture', async () => { - const response: APIResponse< - UserProfilesController['uploadProfileImage'] - > = await request(server) - .post( - `${route}/profile/uploadImage/${loggedInExternalCoach.user.id}` - ) - .set('authorization', `Bearer ${loggedInExternalCoach.token}`) - .set('Content-Type', 'multipart/form-data') - .attach('profileImage', path); - expect(response.status).toBe(201); - }); - it('Should return 403, if external coach uploads a profile picture for another user', async () => { - const response: APIResponse< - UserProfilesController['uploadProfileImage'] - > = await request(server) - .post( - `${route}/profile/uploadImage/${loggedInExternalCandidate.user.id}` - ) - .set('authorization', `Bearer ${loggedInExternalCoach.token}`) - .set('Content-Type', 'multipart/form-data') - .attach('profileImage', path); - expect(response.status).toBe(403); - }); it('Should return 403, if coach uploads profile picture for another user', async () => { const response: APIResponse< UserProfilesController['uploadProfileImage'] From d2ddc945f4b24ffd92ec3f97af49ab1acdb94dcf Mon Sep 17 00:00:00 2001 From: "Dorian P." Date: Tue, 22 Oct 2024 16:34:35 +0200 Subject: [PATCH 02/29] [EN-7456] Mail de confirmation orienteur (#223) * Created the method to send the referer onboarding confirmation * Adding cascade when removing user formation * Revert "Adding cascade when removing user formation" This reverts commit a5a475096157eda33ebbb5e3cfb2862c7325147c. --- src/external-services/mailjet/mailjet.types.ts | 1 + src/mails/mails.service.ts | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/src/external-services/mailjet/mailjet.types.ts b/src/external-services/mailjet/mailjet.types.ts index 9b5ba466..9c97c32c 100644 --- a/src/external-services/mailjet/mailjet.types.ts +++ b/src/external-services/mailjet/mailjet.types.ts @@ -92,6 +92,7 @@ export const MailjetTemplates = { USER_REPORTED_ADMIN: 6223181, ONBOARDING_J1_BAO: 6129684, ONBOARDING_J3_PROFILE_COMPLETION: 6129711, + REFERER_ONBOARDING_CONFIRMATION: 6324339, } as const; export type MailjetTemplateKey = keyof typeof MailjetTemplates; diff --git a/src/mails/mails.service.ts b/src/mails/mails.service.ts index c18f209a..ee46f150 100644 --- a/src/mails/mails.service.ts +++ b/src/mails/mails.service.ts @@ -772,6 +772,24 @@ export class MailsService { }, }); } + + // TODO: Call this method after completing the referer onboarding + async sendRefererOnboardingConfirmationMail(referer: User, candidate: User) { + const { candidatesAdminMail } = getAdminMailsFromZone(referer.zone); + + await this.queuesService.addToWorkQueue(Jobs.SEND_MAIL, { + toEmail: referer.email, + templateId: MailjetTemplates.REFERER_ONBOARDING_CONFIRMATION, + replyTo: candidatesAdminMail, + variables: { + refererFirstName: referer.firstName, + candidateFirstName: candidate.firstName, + candidateLastName: candidate.lastName, + loginUrl: `${process.env.FRONTEND_URL}/login`, + zone: referer.zone, + }, + }); + } } const getRoleString = (user: User): string => { From 42ae6c6e59532d086d698ea45082856fbc2801ae Mon Sep 17 00:00:00 2001 From: Guillaume Cauchois Date: Fri, 25 Oct 2024 10:56:49 +0200 Subject: [PATCH 03/29] [EN-7460] feat: create referrer registration funnel --- .../external-databases.service.ts | 3 +++ src/external-services/mailjet/mailjet.types.ts | 1 + src/mails/mails.service.ts | 7 ++++++- src/organizations/organizations.controller.ts | 7 +++++-- .../dto/create-user-registration.dto.ts | 18 +++++++++++++++--- .../users-creation.controller.ts | 14 +++++++++++--- src/users/users.types.ts | 11 +++++++++++ 7 files changed, 52 insertions(+), 9 deletions(-) diff --git a/src/external-databases/external-databases.service.ts b/src/external-databases/external-databases.service.ts index 341f274c..c52e74ff 100644 --- a/src/external-databases/external-databases.service.ts +++ b/src/external-databases/external-databases.service.ts @@ -66,6 +66,9 @@ export class ExternalDatabasesService { case Genders.OTHER: conertedGenderType = CandidateGenders.OTHER; break; + case undefined: + conertedGenderType = null; + break; default: throw new Error('Invalid gender value'); } diff --git a/src/external-services/mailjet/mailjet.types.ts b/src/external-services/mailjet/mailjet.types.ts index 9c97c32c..f777e9cc 100644 --- a/src/external-services/mailjet/mailjet.types.ts +++ b/src/external-services/mailjet/mailjet.types.ts @@ -89,6 +89,7 @@ export const MailjetTemplates = { INTERNAL_MESSAGE: 5625323, INTERNAL_MESSAGE_CONFIRMATION: 5625495, USER_EMAIL_VERIFICATION: 5899611, + USER_EMAIL_VERIFICATION_REFERRER: 6324333, USER_REPORTED_ADMIN: 6223181, ONBOARDING_J1_BAO: 6129684, ONBOARDING_J3_PROFILE_COMPLETION: 6129711, diff --git a/src/mails/mails.service.ts b/src/mails/mails.service.ts index 71f702a2..9f4985a1 100644 --- a/src/mails/mails.service.ts +++ b/src/mails/mails.service.ts @@ -111,13 +111,18 @@ export class MailsService { } async sendVerificationMail(user: User, token: string) { + const templateId = + user.role === UserRoles.REFERRER + ? MailjetTemplates.USER_EMAIL_VERIFICATION_REFERRER + : MailjetTemplates.USER_EMAIL_VERIFICATION; return this.queuesService.addToWorkQueue(Jobs.SEND_MAIL, { toEmail: user.email, - templateId: MailjetTemplates.USER_EMAIL_VERIFICATION, + templateId, variables: { firstName: user.firstName, toEmail: user.email, token, + zone: user.zone, }, }); } diff --git a/src/organizations/organizations.controller.ts b/src/organizations/organizations.controller.ts index 367eb163..9de9dc0c 100644 --- a/src/organizations/organizations.controller.ts +++ b/src/organizations/organizations.controller.ts @@ -13,6 +13,8 @@ import { UseGuards, } from '@nestjs/common'; import { ApiBearerAuth } from '@nestjs/swagger'; +import { Throttle } from '@nestjs/throttler'; +import { Public } from 'src/auth/guards'; import { UserPermissions, UserPermissionsGuard } from 'src/users/guards'; import { Permissions } from 'src/users/users.types'; import { isValidPhone } from 'src/utils/misc'; @@ -35,7 +37,7 @@ export class OrganizationsController { private readonly organizationReferentsService: OrganizationReferentsService ) {} - @UserPermissions(Permissions.ADMIN) + @Public() @UseGuards(UserPermissionsGuard) @Get() async findAll( @@ -81,7 +83,8 @@ export class OrganizationsController { return organization; } - @UserPermissions(Permissions.ADMIN) + @Public() + @Throttle(5, 60) @UseGuards(UserPermissionsGuard) @Post() async create( diff --git a/src/users-creation/dto/create-user-registration.dto.ts b/src/users-creation/dto/create-user-registration.dto.ts index e2cf4da7..9a0baac3 100644 --- a/src/users-creation/dto/create-user-registration.dto.ts +++ b/src/users-creation/dto/create-user-registration.dto.ts @@ -1,5 +1,5 @@ import { ApiProperty, PickType } from '@nestjs/swagger'; -import { IsArray, IsOptional, IsString } from 'class-validator'; +import { IsArray, IsNumber, IsOptional, IsString } from 'class-validator'; import { Ambition } from 'src/common/ambitions/models'; import { BusinessLine } from 'src/common/business-lines/models'; import { Department } from 'src/common/locations/locations.types'; @@ -15,7 +15,7 @@ import { } from 'src/contacts/contacts.types'; import { HelpNeed } from 'src/user-profiles/models'; import { User } from 'src/users/models'; -import { NormalUserRole, Program } from 'src/users/users.types'; +import { Gender, Program, RegistrableUserRole } from 'src/users/users.types'; export class CreateUserRegistrationDto extends PickType(User, [ 'firstName', @@ -26,9 +26,14 @@ export class CreateUserRegistrationDto extends PickType(User, [ 'phone', 'password', ] as const) { + @ApiProperty() + @IsNumber() + @IsOptional() + gender: Gender; + @ApiProperty() @IsString() - role: NormalUserRole; + role: RegistrableUserRole; @ApiProperty() @IsString() @@ -36,16 +41,23 @@ export class CreateUserRegistrationDto extends PickType(User, [ @ApiProperty() @IsString() + @IsOptional() program: Program; @ApiProperty() @IsString() + @IsOptional() birthDate: Date; @ApiProperty() @IsString() department: Department; + @ApiProperty() + @IsString() + @IsOptional() + organizationId?: string; + @ApiProperty() @IsString() @IsOptional() diff --git a/src/users-creation/users-creation.controller.ts b/src/users-creation/users-creation.controller.ts index 16a85bef..4cfe3b2b 100644 --- a/src/users-creation/users-creation.controller.ts +++ b/src/users-creation/users-creation.controller.ts @@ -13,11 +13,12 @@ import { Public } from 'src/auth/guards'; import { UserPermissions, UserPermissionsGuard } from 'src/users/guards'; import { User } from 'src/users/models'; import { - NormalUserRoles, Permissions, Programs, + RegistrableUserRoles, RolesWithOrganization, SequelizeUniqueConstraintError, + UserRoles, } from 'src/users/users.types'; import { isRoleIncluded } from 'src/users/users.utils'; import { getZoneFromDepartment, isValidPhone } from 'src/utils/misc'; @@ -105,7 +106,14 @@ export class UsersCreationController { ) { if ( !isValidPhone(createUserRegistrationDto.phone) || - !isRoleIncluded(NormalUserRoles, createUserRegistrationDto.role) + !isRoleIncluded(RegistrableUserRoles, createUserRegistrationDto.role) + ) { + throw new BadRequestException(); + } + + if ( + !isRoleIncluded([UserRoles.REFERRER], createUserRegistrationDto.role) && + !createUserRegistrationDto.program ) { throw new BadRequestException(); } @@ -121,7 +129,7 @@ export class UsersCreationController { role: createUserRegistrationDto.role, gender: createUserRegistrationDto.gender, phone: createUserRegistrationDto.phone, - OrganizationId: null, + OrganizationId: createUserRegistrationDto.organizationId, address: null, adminRole: null, zone, diff --git a/src/users/users.types.ts b/src/users/users.types.ts index 64e28a4d..23b138d0 100644 --- a/src/users/users.types.ts +++ b/src/users/users.types.ts @@ -46,6 +46,17 @@ export const NormalUserRoles: NormalUserRole[] = [ UserRoles.COACH, ]; +export type RegistrableUserRole = + | typeof UserRoles.CANDIDATE + | typeof UserRoles.COACH + | typeof UserRoles.REFERRER; + +export const RegistrableUserRoles: RegistrableUserRole[] = [ + UserRoles.CANDIDATE, + UserRoles.COACH, + UserRoles.REFERRER, +]; + export type CandidateUserRole = typeof UserRoles.CANDIDATE; export const CandidateUserRoles: CandidateUserRole[] = [UserRoles.CANDIDATE]; export const RolesWithOrganization: UserRole[] = [UserRoles.REFERRER]; From c3ae50107a24ba0bbcb4672c4fe00c551d067b4e Mon Sep 17 00:00:00 2001 From: Guillaume Cauchois Date: Fri, 25 Oct 2024 13:18:27 +0200 Subject: [PATCH 04/29] refacto: remove useless comments --- src/users/models/user.include.ts | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/users/models/user.include.ts b/src/users/models/user.include.ts index 122307c0..0e39eb9c 100644 --- a/src/users/models/user.include.ts +++ b/src/users/models/user.include.ts @@ -57,12 +57,6 @@ export const UserCandidatInclude: Includeable[] = [ as: 'referredCandidates', attributes: [...UserAttributes], include: [ - // { - // model: UserProfile, - // as: 'userProfile', - // attributes: UserProfilesAttributes, - // include: getUserProfileInclude(), - // }, { model: UserCandidat, as: 'candidat', @@ -73,14 +67,6 @@ export const UserCandidatInclude: Includeable[] = [ model: User, as: 'candidat', attributes: [...UserAttributes], - // include: [ - // { - // model: UserProfile, - // as: 'userProfile', - // attributes: UserProfilesAttributes, - // include: getUserProfileInclude(), - // }, - // ], }, ], }, From bd4f1439752904af064fc531f5401a8f730433d8 Mon Sep 17 00:00:00 2001 From: Guillaume Cauchois Date: Fri, 25 Oct 2024 15:25:48 +0200 Subject: [PATCH 05/29] refacto: remove CandidateUserRoles / CandidateUserRole / CoachUserRoles / CoachUserRole --- src/mails/mails.service.ts | 13 ++--- src/messages/messages.controller.ts | 5 +- src/opportunities/opportunities.service.ts | 6 +- src/shares/shares.service.ts | 5 +- .../models/user-profile.include.ts | 15 ++--- src/user-profiles/user-profiles.controller.ts | 3 +- src/user-profiles/user-profiles.service.ts | 58 +++++++++---------- src/users/models/user.model.ts | 21 +++---- src/users/users.controller.ts | 16 ++--- src/users/users.service.ts | 8 +-- src/users/users.types.ts | 10 +--- src/users/users.utils.ts | 20 +++---- tests/users/user.factory.ts | 13 ++--- 13 files changed, 75 insertions(+), 118 deletions(-) diff --git a/src/mails/mails.service.ts b/src/mails/mails.service.ts index 71f702a2..5a7b7b21 100644 --- a/src/mails/mails.service.ts +++ b/src/mails/mails.service.ts @@ -27,15 +27,10 @@ import { QueuesService } from 'src/queues/producers/queues.service'; import { Jobs } from 'src/queues/queues.types'; import { ReportAbuseUserProfileDto } from 'src/user-profiles/dto/report-abuse-user-profile.dto'; import { User } from 'src/users/models'; -import { - CandidateUserRoles, - CoachUserRoles, - UserRoles, -} from 'src/users/users.types'; +import { UserRoles } from 'src/users/users.types'; import { getCandidateFromCoach, getCoachFromCandidate, - isRoleIncluded, } from 'src/users/users.utils'; import { getAdminMailsFromDepartment, @@ -218,7 +213,7 @@ export class MailsService { let candidate, coach: User; let toEmail: string; // if user is a a candidate then get the coach from the candidate - if (isRoleIncluded(CandidateUserRoles, submittingUser.role)) { + if (submittingUser.role === UserRoles.CANDIDATE) { coach = getCoachFromCandidate(submittingUser); toEmail = getAdminMailsFromZone(submittingUser.zone).candidatesAdminMail; } else { @@ -793,9 +788,9 @@ export class MailsService { } const getRoleString = (user: User): string => { - if (isRoleIncluded(CandidateUserRoles, user.role)) { + if (user.role === UserRoles.CANDIDATE) { return 'Candidat'; - } else if (isRoleIncluded(CoachUserRoles, user.role)) { + } else if (user.role === UserRoles.COACH) { return 'Coach'; } else { return 'Admin'; diff --git a/src/messages/messages.controller.ts b/src/messages/messages.controller.ts index 97a66938..d2204fd1 100644 --- a/src/messages/messages.controller.ts +++ b/src/messages/messages.controller.ts @@ -12,8 +12,7 @@ import { import { validate as uuidValidate } from 'uuid'; import { Public, UserPayload } from 'src/auth/guards'; import { ThrottleUserIdGuard } from 'src/users/guards/throttle-user-id.guard'; -import { CandidateUserRoles, UserRole, UserRoles } from 'src/users/users.types'; -import { isRoleIncluded } from 'src/users/users.utils'; +import { UserRole, UserRoles } from 'src/users/users.types'; import { isValidPhone } from 'src/utils/misc'; import { CreateExternalMessageDto, @@ -45,7 +44,7 @@ export class MessagesController { if ( (createMessageDto.senderPhone && !isValidPhone(createMessageDto.senderPhone)) || - !isRoleIncluded(CandidateUserRoles, candidate.role) + candidate.role !== UserRoles.CANDIDATE ) { throw new BadRequestException(); } diff --git a/src/opportunities/opportunities.service.ts b/src/opportunities/opportunities.service.ts index 8e21f5b6..894175ac 100644 --- a/src/opportunities/opportunities.service.ts +++ b/src/opportunities/opportunities.service.ts @@ -18,8 +18,8 @@ import { Jobs } from 'src/queues/queues.types'; import { SMSService } from 'src/sms/sms.service'; import { User } from 'src/users/models'; import { UsersService } from 'src/users/users.service'; -import { CandidateUserRoles } from 'src/users/users.types'; -import { getCoachFromCandidate, isRoleIncluded } from 'src/users/users.utils'; +import { UserRoles } from 'src/users/users.types'; +import { getCoachFromCandidate } from 'src/users/users.utils'; import { getZoneFromDepartment } from 'src/utils/misc'; import { AdminZone, FilterParams } from 'src/utils/types'; import { @@ -352,7 +352,7 @@ export class OpportunitiesService { async findOneCandidate(candidateId: string) { const user = await this.usersService.findOne(candidateId); - if (!user || !isRoleIncluded(CandidateUserRoles, user.role)) { + if (!user || user.role !== UserRoles.CANDIDATE) { return null; } return user; diff --git a/src/shares/shares.service.ts b/src/shares/shares.service.ts index 01000490..96657568 100644 --- a/src/shares/shares.service.ts +++ b/src/shares/shares.service.ts @@ -3,8 +3,7 @@ import { InjectModel } from '@nestjs/sequelize'; import { Cache } from 'cache-manager'; import { Sequelize } from 'sequelize'; import { UsersService } from 'src/users/users.service'; -import { CandidateUserRoles } from 'src/users/users.types'; -import { isRoleIncluded } from 'src/users/users.utils'; +import { UserRoles } from 'src/users/users.types'; import { RedisKeys } from 'src/utils/types'; import { Share } from './models'; @@ -28,7 +27,7 @@ export class SharesService { async updateByCandidateId(candidateId: string, type: ShareType) { const candidate = await this.usersService.findOne(candidateId); - if (!candidate || !isRoleIncluded(CandidateUserRoles, candidate.role)) { + if (!candidate || candidate.role !== UserRoles.CANDIDATE) { return null; } const candidatShares = await this.shareModel.findOne({ diff --git a/src/user-profiles/models/user-profile.include.ts b/src/user-profiles/models/user-profile.include.ts index 8560f48c..019b5fc5 100644 --- a/src/user-profiles/models/user-profile.include.ts +++ b/src/user-profiles/models/user-profile.include.ts @@ -2,11 +2,7 @@ import _ from 'lodash'; import { Includeable, WhereOptions } from 'sequelize'; import { Ambition } from 'src/common/ambitions/models'; import { BusinessLine } from 'src/common/business-lines/models'; -import { - CandidateUserRoles, - CoachUserRoles, - UserRole, -} from 'src/users/users.types'; +import { UserRole, UserRoles } from 'src/users/users.types'; import { isRoleIncluded } from 'src/users/users.utils'; import { HelpNeed } from './help-need.model'; import { HelpOffer } from './help-offer.model'; @@ -17,8 +13,9 @@ export function getUserProfileHelpsInclude( ) { const isHelpsRequired = role && !_.isEmpty(helpsOptions); const isCandidateHelps = - isHelpsRequired && isRoleIncluded(CandidateUserRoles, role); - const isCoachHelps = isHelpsRequired && isRoleIncluded(CoachUserRoles, role); + isHelpsRequired && isRoleIncluded([UserRoles.CANDIDATE], role); + const isCoachHelps = + isHelpsRequired && isRoleIncluded([UserRoles.COACH], role); return [ { @@ -44,9 +41,9 @@ export function getUserProfileBusinessLinesInclude( ) { const isBusinessLinesRequired = role && !_.isEmpty(businessLinesOptions); const isCandidateBusinessLines = - isBusinessLinesRequired && isRoleIncluded(CandidateUserRoles, role); + isBusinessLinesRequired && isRoleIncluded([UserRoles.CANDIDATE], role); const isCoachBusinessLines = - isBusinessLinesRequired && isRoleIncluded(CoachUserRoles, role); + isBusinessLinesRequired && isRoleIncluded([UserRoles.CANDIDATE], role); return [ { diff --git a/src/user-profiles/user-profiles.controller.ts b/src/user-profiles/user-profiles.controller.ts index e25ff18f..7acc7926 100644 --- a/src/user-profiles/user-profiles.controller.ts +++ b/src/user-profiles/user-profiles.controller.ts @@ -30,7 +30,6 @@ import { } from 'src/users/guards'; import { AllUserRoles, - CandidateUserRoles, Permissions, UserRole, UserRoles, @@ -314,7 +313,7 @@ export class UserProfilesController { currentUserId ); - if (isRoleIncluded(CandidateUserRoles, user.role)) { + if (user.role === UserRoles.CANDIDATE) { const userCandidate = await this.userProfilesService.findUserCandidateByCandidateId( userIdToGet diff --git a/src/user-profiles/user-profiles.service.ts b/src/user-profiles/user-profiles.service.ts index 61dd137b..8057c4f0 100644 --- a/src/user-profiles/user-profiles.service.ts +++ b/src/user-profiles/user-profiles.service.ts @@ -18,12 +18,7 @@ import { InternalMessage } from 'src/messages/models'; import { User } from 'src/users/models'; import { UserCandidatsService } from 'src/users/user-candidats.service'; import { UsersService } from 'src/users/users.service'; -import { - CandidateUserRoles, - CoachUserRoles, - UserRole, - UserRoles, -} from 'src/users/users.types'; +import { UserRole, UserRoles } from 'src/users/users.types'; import { isRoleIncluded } from 'src/users/users.utils'; import { ReportAbuseUserProfileDto } from './dto/report-abuse-user-profile.dto'; import { @@ -447,9 +442,10 @@ export class UserProfilesService { this.findOneByUserId(userId), ]); - const rolesToFind = isRoleIncluded(CandidateUserRoles, user.role) - ? [UserRoles.COACH] - : CandidateUserRoles; + const rolesToFind = + user.role === UserRoles.CANDIDATE + ? [UserRoles.COACH] + : [UserRoles.CANDIDATE]; const sameRegionDepartmentsOptions = userProfile.department ? Departments.filter( @@ -480,10 +476,10 @@ export class UserProfilesService { '$user.receivedMessages.id$': null, '$user.sentMessages.id$': null, [Op.not]: { - ...(isRoleIncluded(CandidateUserRoles, rolesToFind) + ...(isRoleIncluded([UserRoles.CANDIDATE], rolesToFind) ? { '$helpNeeds.id$': null } : {}), - ...(isRoleIncluded(CoachUserRoles, rolesToFind) + ...(isRoleIncluded([UserRoles.COACH], rolesToFind) ? { '$helpOffers.id$': null } : {}), }, @@ -496,7 +492,7 @@ export class UserProfilesService { as: 'helpNeeds', required: false, attributes: ['name'], - where: isRoleIncluded(CandidateUserRoles, rolesToFind) + where: isRoleIncluded([UserRoles.CANDIDATE], rolesToFind) ? helpsOptions : {}, }, @@ -505,7 +501,7 @@ export class UserProfilesService { as: 'helpOffers', required: false, attributes: ['name'], - where: isRoleIncluded(CoachUserRoles, rolesToFind) + where: isRoleIncluded([UserRoles.COACH], rolesToFind) ? helpsOptions : {}, }, @@ -590,9 +586,9 @@ export class UserProfilesService { // this.findOneByUserId(userId), // ]); - // const rolesToFind = isRoleIncluded(CandidateUserRoles, user.role) + // const rolesToFind = isRoleIncluded([UserRoles.CANDIDATE], user.role) // ? [UserRoles.COACH] - // : CandidateUserRoles; + // : [UserRoles.CANDIDATE]; // const sameRegionDepartmentsOptions = userProfile.department // ? Departments.filter( @@ -623,10 +619,10 @@ export class UserProfilesService { // isAvailable: true, // department: sameRegionDepartmentsOptions, // [Op.not]: { - // ...(isRoleIncluded(CandidateUserRoles, rolesToFind) + // ...(isRoleIncluded([UserRoles.CANDIDATE], rolesToFind) // ? { '$helpNeeds.id$': null } // : {}), - // ...(isRoleIncluded(CoachUserRoles, rolesToFind) + // ...(isRoleIncluded([UserRoles.COACH], rolesToFind) // ? { '$helpOffers.id$': null } // : {}), // }, @@ -637,7 +633,7 @@ export class UserProfilesService { // as: 'helpNeeds', // required: false, // attributes: ['id'], - // where: isRoleIncluded(CandidateUserRoles, rolesToFind) + // where: isRoleIncluded([UserRoles.CANDIDATE], rolesToFind) // ? helpsOptions // : {}, // }, @@ -646,7 +642,7 @@ export class UserProfilesService { // as: 'helpOffers', // required: false, // attributes: ['id'], - // where: isRoleIncluded(CoachUserRoles, rolesToFind) + // where: isRoleIncluded([UserRoles.COACH], rolesToFind) // ? helpsOptions // : {}, // }, @@ -730,9 +726,9 @@ export class UserProfilesService { // const user = await this.findOneUser(userId); // const userProfile = await this.findOneByUserId(userId); - // const rolesToFind = isRoleIncluded(CandidateUserRoles, user.role) + // const rolesToFind = isRoleIncluded([UserRoles.CANDIDATE], user.role) // ? [UserRoles.COACH] - // : CandidateUserRoles; + // : [UserRoles.CANDIDATE]; // const sameRegionDepartmentsOptions = userProfile.department // ? Departments.filter( @@ -768,10 +764,10 @@ export class UserProfilesService { // // '$user.receivedMessages.id$': null, // // '$user.sentMessages.id$': null, // // [Op.not]: { - // // ...(isRoleIncluded(CandidateUserRoles, rolesToFind) + // // ...(isRoleIncluded([UserRoles.CANDIDATE], rolesToFind) // // ? { '$helpNeeds.id$': null } // // : {}), - // // ...(isRoleIncluded(CoachUserRoles, rolesToFind) + // // ...(isRoleIncluded([UserRoles.COACH] rolesToFind) // // ? { '$helpOffers.id$': null } // // : {}), // // }, @@ -784,7 +780,7 @@ export class UserProfilesService { // // as: 'helpNeeds', // // required: false, // // attributes: ['id'], - // // ...(isRoleIncluded(CandidateUserRoles, rolesToFind) + // // ...(isRoleIncluded([UserRoles.CANDIDATE], rolesToFind) // // ? { where: helpsOptions } // // : {}), // // }, @@ -793,7 +789,7 @@ export class UserProfilesService { // // as: 'helpOffers', // // required: false, // // attributes: ['id'], - // // ...(isRoleIncluded(CoachUserRoles, rolesToFind) + // // ...(isRoleIncluded([UserRoles.COACH] rolesToFind) // // ? { where: helpsOptions } // // : {}), // // }, @@ -834,13 +830,13 @@ export class UserProfilesService { // department: sameRegionDepartmentsOptions, // '$user.receivedMessages.id$': null as null, // '$user.sentMessages.id$': null as null, // Explicitly define the type as null - // ...((isRoleIncluded(CandidateUserRoles, rolesToFind) || - // isRoleIncluded(CoachUserRoles, rolesToFind)) && { + // ...((isRoleIncluded([UserRoles.CANDIDATE], rolesToFind) || + // isRoleIncluded([UserRoles.COACH], rolesToFind)) && { // [Op.not]: { - // ...(isRoleIncluded(CandidateUserRoles, rolesToFind) + // ...(isRoleIncluded([UserRoles.CANDIDATE], rolesToFind) // ? { '$helpNeeds.id$': null } // : {}), - // ...(isRoleIncluded(CoachUserRoles, rolesToFind) + // ...(isRoleIncluded([UserRoles.COACH], rolesToFind) // ? { '$helpOffers.id$': null } // : {}), // }, @@ -855,7 +851,7 @@ export class UserProfilesService { // as: 'helpNeeds', // required: false, // attributes: ['id'], - // where: isRoleIncluded(CandidateUserRoles, rolesToFind) + // where: isRoleIncluded([UserRoles.CANDIDATE], rolesToFind) // ? helpsOptions // : undefined, // }, @@ -864,7 +860,7 @@ export class UserProfilesService { // as: 'helpOffers', // required: false, // attributes: ['id'], - // where: isRoleIncluded(CoachUserRoles, rolesToFind) + // where: isRoleIncluded([UserRoles.COACH], rolesToFind) // ? helpsOptions // : undefined, // }, diff --git a/src/users/models/user.model.ts b/src/users/models/user.model.ts index 4b808a05..de8694a3 100644 --- a/src/users/models/user.model.ts +++ b/src/users/models/user.model.ts @@ -32,8 +32,6 @@ import { } from 'sequelize-typescript'; import { AdminRole, - CandidateUserRoles, - CoachUserRoles, Gender, Genders, RolesWithOrganization, @@ -259,7 +257,7 @@ export class User extends HistorizedModel { @AfterCreate static async createAssociations(createdUser: User) { - if (isRoleIncluded(CandidateUserRoles, createdUser.role)) { + if (createdUser.role === UserRoles.CANDIDATE) { await UserCandidat.create( { candidatId: createdUser.id, @@ -292,8 +290,8 @@ export class User extends HistorizedModel { previousUserValues.role !== userToUpdate.role ) { if ( - isRoleIncluded(CandidateUserRoles, previousUserValues.role) && - !isRoleIncluded(CandidateUserRoles, userToUpdate.role) + previousUserValues.role === UserRoles.CANDIDATE && + userToUpdate.role !== UserRoles.CANDIDATE ) { await UserCandidat.destroy({ where: { @@ -301,10 +299,10 @@ export class User extends HistorizedModel { }, }); } else if ( - !isRoleIncluded(CandidateUserRoles, previousUserValues.role) && - isRoleIncluded(CandidateUserRoles, userToUpdate.role) + previousUserValues.role !== UserRoles.CANDIDATE && + userToUpdate.role === UserRoles.CANDIDATE ) { - if (isRoleIncluded(CoachUserRoles, previousUserValues.role)) { + if (previousUserValues.role === UserRoles.COACH) { await UserCandidat.update( { coachId: null, @@ -355,7 +353,7 @@ export class User extends HistorizedModel { const previousUserValues = userToUpdate.previous(); if ( userToUpdate && - isRoleIncluded(CandidateUserRoles, userToUpdate.role) && + userToUpdate.role === UserRoles.CANDIDATE && previousUserValues && previousUserValues.firstName != undefined && previousUserValues.firstName !== userToUpdate.firstName @@ -381,9 +379,8 @@ export class User extends HistorizedModel { }, { where: { - [isRoleIncluded(CoachUserRoles, destroyedUser.role) - ? 'coachId' - : 'candidatId']: destroyedUser.id, + [destroyedUser.role === UserRoles.COACH ? 'coachId' : 'candidatId']: + destroyedUser.id, }, } ); diff --git a/src/users/users.controller.ts b/src/users/users.controller.ts index d77ef281..599780fd 100644 --- a/src/users/users.controller.ts +++ b/src/users/users.controller.ts @@ -42,8 +42,6 @@ import { User, UserCandidat } from './models'; import { UserCandidatsService } from './user-candidats.service'; import { UsersService } from './users.service'; import { - CandidateUserRoles, - CoachUserRoles, MemberFilterKey, Permissions, SequelizeUniqueConstraintError, @@ -84,20 +82,20 @@ export class UsersController { throw new BadRequestException(); } - if (isRoleIncluded(CandidateUserRoles, role)) { + if (isRoleIncluded([UserRoles.CANDIDATE], role)) { return this.usersService.findAllCandidateMembers({ ...query, - role: role as typeof CandidateUserRoles, + role: [UserRoles.CANDIDATE], limit, offset, search, }); } - if (isRoleIncluded(CoachUserRoles, role)) { + if (isRoleIncluded([UserRoles.COACH], role)) { return this.usersService.findAllCoachMembers({ ...query, - role: role as typeof CoachUserRoles, + role: [UserRoles.COACH], limit, offset, search, @@ -146,10 +144,8 @@ export class UsersController { @UserPayload('role') role: UserRole ) { const ids = { - candidateId: isRoleIncluded(CandidateUserRoles, role) - ? userId - : undefined, - coachId: isRoleIncluded(CoachUserRoles, role) ? userId : undefined, + candidateId: role === UserRoles.CANDIDATE ? userId : undefined, + coachId: role === UserRoles.COACH ? userId : undefined, }; if (role === UserRoles.REFERRER) { diff --git a/src/users/users.service.ts b/src/users/users.service.ts index 198022b7..cbefafab 100644 --- a/src/users/users.service.ts +++ b/src/users/users.service.ts @@ -23,8 +23,6 @@ import { } from './models'; import { UserCandidatInclude } from './models/user.include'; import { - CandidateUserRoles, - CoachUserRoles, CVStatuses, MemberFilterKey, UserRole, @@ -94,7 +92,7 @@ export class UsersService { limit: number; offset: number; search: string; - role: typeof CandidateUserRoles; + role: (typeof UserRoles.CANDIDATE)[]; } & FilterParams ): Promise { const { limit, offset, search, ...restParams } = params; @@ -199,7 +197,7 @@ export class UsersService { limit: number; offset: number; search: string; - role: typeof CoachUserRoles; + role: (typeof UserRoles.COACH)[]; } & FilterParams ): Promise { const { limit, offset, search, ...restParams } = params; @@ -371,7 +369,7 @@ export class UsersService { const options: FindOptions = { where: { ...whereOptions, - role: CandidateUserRoles, + role: UserRoles.CANDIDATE, } as WhereOptions, attributes: [], include: [ diff --git a/src/users/users.types.ts b/src/users/users.types.ts index 64e28a4d..a0ace4eb 100644 --- a/src/users/users.types.ts +++ b/src/users/users.types.ts @@ -46,17 +46,9 @@ export const NormalUserRoles: NormalUserRole[] = [ UserRoles.COACH, ]; -export type CandidateUserRole = typeof UserRoles.CANDIDATE; -export const CandidateUserRoles: CandidateUserRole[] = [UserRoles.CANDIDATE]; export const RolesWithOrganization: UserRole[] = [UserRoles.REFERRER]; -export type CoachUserRole = typeof UserRoles.COACH; - -export const CoachUserRoles: CoachUserRole[] = [UserRoles.COACH]; -export const AllUserRoles: (CandidateUserRole | CoachUserRole)[] = [ - ...CandidateUserRoles, - ...CoachUserRoles, -]; +export const AllUserRoles = [UserRoles.CANDIDATE, UserRoles.COACH]; export const AdminRoles = { CANDIDATES: 'Candidats', diff --git a/src/users/users.utils.ts b/src/users/users.utils.ts index 4288f9fc..3f3c1643 100644 --- a/src/users/users.utils.ts +++ b/src/users/users.utils.ts @@ -10,8 +10,6 @@ import { import { FilterObject, FilterParams } from 'src/utils/types'; import { User, UserCandidat } from './models'; import { - CandidateUserRoles, - CoachUserRoles, CVStatuses, MemberConstantType, MemberFilterKey, @@ -97,7 +95,7 @@ export function getRelatedUser(member: User) { } export function getCoachFromCandidate(candidate: User) { - if (candidate && isRoleIncluded(CandidateUserRoles, candidate.role)) { + if (candidate && candidate.role === UserRoles.CANDIDATE) { if (candidate.candidat && candidate.candidat.coach) { return candidate.candidat.coach; } @@ -107,7 +105,7 @@ export function getCoachFromCandidate(candidate: User) { } export function getCandidateFromCoach(coach: User, candidateId: string) { - if (coach && isRoleIncluded(CoachUserRoles, coach.role)) { + if (coach && coach.role === UserRoles.COACH) { if (coach.coaches && coach.coaches.length > 0) { return coach.coaches.find(({ candidat }) => { return candidat.id === candidateId; @@ -120,7 +118,7 @@ export function getCandidateFromCoach(coach: User, candidateId: string) { export function getCandidateIdFromCoachOrCandidate(member: User) { if (member) { - if (isRoleIncluded(CandidateUserRoles, member.role)) { + if (member.role === UserRoles.CANDIDATE) { return member.id; } if (isRoleIncluded([UserRoles.REFERRER], member.role)) { @@ -161,9 +159,9 @@ export function getMemberOptions(filtersObj: FilterObject) { let associatedUserOptionKey: string; const rolesFilters = filtersObj.role.map(({ value }) => value); - if (isRoleIncluded(CandidateUserRoles, rolesFilters)) { + if (isRoleIncluded([UserRoles.CANDIDATE], rolesFilters)) { associatedUserOptionKey = '"candidat"."coachId"'; - } else if (isRoleIncluded(CoachUserRoles, rolesFilters)) { + } else if (isRoleIncluded([UserRoles.COACH], rolesFilters)) { associatedUserOptionKey = '"coaches"."candidatId"'; } else { return []; @@ -394,12 +392,8 @@ export function getCandidateAndCoachIdDependingOnRoles( }; } else if (shouldRemoveLinkedUser) { return { - candidateId: isRoleIncluded(CandidateUserRoles, user.role) - ? user.id - : userToLink.id, - coachId: isRoleIncluded(CoachUserRoles, user.role) - ? user.id - : userToLink.id, + candidateId: user.role === UserRoles.CANDIDATE ? user.id : userToLink.id, + coachId: user.role === UserRoles.COACH ? user.id : userToLink.id, }; } else { throw new BadRequestException(); diff --git a/tests/users/user.factory.ts b/tests/users/user.factory.ts index 12491582..270d59f3 100644 --- a/tests/users/user.factory.ts +++ b/tests/users/user.factory.ts @@ -9,13 +9,8 @@ import { UserProfile } from 'src/user-profiles/models'; import { UserProfilesService } from 'src/user-profiles/user-profiles.service'; import { User, UserCandidat } from 'src/users/models'; import { UsersService } from 'src/users/users.service'; -import { - CandidateUserRoles, - CoachUserRoles, - Gender, - UserRoles, -} from 'src/users/users.types'; -import { capitalizeNameAndTrim, isRoleIncluded } from 'src/users/users.utils'; +import { Gender, UserRoles } from 'src/users/users.types'; +import { capitalizeNameAndTrim } from 'src/users/users.utils'; import { AdminZones, Factory } from 'src/utils/types'; @Injectable() @@ -74,7 +69,7 @@ export class UserFactory implements Factory { if (insertInDB) { await this.userModel.create({ ...userData, id: userId }, { hooks: true }); if (userAssociationsProps?.userCandidat) { - if (isRoleIncluded(CandidateUserRoles, userData.role)) { + if (userData.role === UserRoles.CANDIDATE) { await this.userCandidatModel.update( { ...userAssociationsProps.userCandidat }, { @@ -84,7 +79,7 @@ export class UserFactory implements Factory { individualHooks: true, } ); - } else if (isRoleIncluded(CoachUserRoles, userData.role)) { + } else if (userData.role === UserRoles.COACH) { await this.userCandidatModel.update( { ...userAssociationsProps.userCandidat }, { From fcd899b69337168521553336a3da24ad12cb1cc6 Mon Sep 17 00:00:00 2001 From: Guillaume Cauchois Date: Fri, 25 Oct 2024 15:31:51 +0200 Subject: [PATCH 06/29] fix: add referrer to AllUserRoles --- src/users/users.types.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/users/users.types.ts b/src/users/users.types.ts index a0ace4eb..1e095445 100644 --- a/src/users/users.types.ts +++ b/src/users/users.types.ts @@ -48,7 +48,11 @@ export const NormalUserRoles: NormalUserRole[] = [ export const RolesWithOrganization: UserRole[] = [UserRoles.REFERRER]; -export const AllUserRoles = [UserRoles.CANDIDATE, UserRoles.COACH]; +export const AllUserRoles = [ + UserRoles.CANDIDATE, + UserRoles.COACH, + UserRoles.REFERRER, +]; export const AdminRoles = { CANDIDATES: 'Candidats', From 0eb82b969219c175c40ab1ce5951d7c5a1619e8e Mon Sep 17 00:00:00 2001 From: Guillaume Cauchois Date: Thu, 31 Oct 2024 14:00:46 +0100 Subject: [PATCH 07/29] fix: tests --- .../models/user-profile.include.ts | 2 +- src/users-creation/dto/create-user.dto.ts | 2 +- .../users-creation.controller.ts | 60 ++++++- src/users-creation/users-creation.service.ts | 8 + src/users/users.controller.ts | 18 +- tests/users/users.e2e-spec.ts | 165 ++---------------- 6 files changed, 90 insertions(+), 165 deletions(-) diff --git a/src/user-profiles/models/user-profile.include.ts b/src/user-profiles/models/user-profile.include.ts index 019b5fc5..32468d61 100644 --- a/src/user-profiles/models/user-profile.include.ts +++ b/src/user-profiles/models/user-profile.include.ts @@ -43,7 +43,7 @@ export function getUserProfileBusinessLinesInclude( const isCandidateBusinessLines = isBusinessLinesRequired && isRoleIncluded([UserRoles.CANDIDATE], role); const isCoachBusinessLines = - isBusinessLinesRequired && isRoleIncluded([UserRoles.CANDIDATE], role); + isBusinessLinesRequired && isRoleIncluded([UserRoles.COACH], role); return [ { diff --git a/src/users-creation/dto/create-user.dto.ts b/src/users-creation/dto/create-user.dto.ts index 1b7666d4..7c11e88e 100644 --- a/src/users-creation/dto/create-user.dto.ts +++ b/src/users-creation/dto/create-user.dto.ts @@ -16,5 +16,5 @@ export class CreateUserDto extends PickType(User, [ ] as const) { @ApiProperty() @IsOptional() - userToLinkId?: string | string[]; + userToLinkId?: string; } diff --git a/src/users-creation/users-creation.controller.ts b/src/users-creation/users-creation.controller.ts index 39e58514..5b543d08 100644 --- a/src/users-creation/users-creation.controller.ts +++ b/src/users-creation/users-creation.controller.ts @@ -4,6 +4,7 @@ import { Body, ConflictException, Controller, + NotFoundException, Post, UseGuards, } from '@nestjs/common'; @@ -21,7 +22,10 @@ import { SequelizeUniqueConstraintError, UserRoles, } from 'src/users/users.types'; -import { isRoleIncluded } from 'src/users/users.utils'; +import { + getCandidateAndCoachIdDependingOnRoles, + isRoleIncluded, +} from 'src/users/users.utils'; import { getZoneFromDepartment, isValidPhone } from 'src/utils/misc'; import { CreateUserDto, @@ -96,6 +100,60 @@ export class UsersCreationController { jwtToken ); + if (userToCreate.userToLinkId) { + const usersToLinkIds = [userToCreate.userToLinkId]; + + const userCandidatesToUpdate = await Promise.all( + usersToLinkIds.map(async (userToLinkId) => { + const userToLink = await this.usersCreationService.findOneUser( + userToLinkId + ); + + if (!userToLink) { + throw new NotFoundException(); + } + + const { candidateId, coachId } = + getCandidateAndCoachIdDependingOnRoles(createdUser, userToLink); + + const userCandidate = + await this.usersCreationService.findOneUserCandidatByCandidateId( + candidateId + ); + + if (!userCandidate) { + throw new NotFoundException(); + } + + return { candidateId: candidateId, coachId: coachId }; + }) + ); + + const updatedUserCandidates = + await this.usersCreationService.updateAllUserCandidatLinkedUserByCandidateId( + userCandidatesToUpdate + ); + + if (!updatedUserCandidates) { + throw new NotFoundException(); + } + + await Promise.all( + updatedUserCandidates.map(async (updatedUserCandidate) => { + const previousCoach = updatedUserCandidate.previous('coach'); + if ( + updatedUserCandidate.coach && + updatedUserCandidate.coach.id !== previousCoach?.id + ) { + await this.usersCreationService.sendMailsAfterMatching( + updatedUserCandidate.candidat.id + ); + } + return updatedUserCandidate.toJSON(); + }) + ); + } + return this.usersCreationService.findOneUser(createdUser.id); } diff --git a/src/users-creation/users-creation.service.ts b/src/users-creation/users-creation.service.ts index 53b2dfa3..e8487fb0 100644 --- a/src/users-creation/users-creation.service.ts +++ b/src/users-creation/users-creation.service.ts @@ -105,6 +105,14 @@ export class UsersCreationService { ); } + async updateAllUserCandidatLinkedUserByCandidateId( + candidatesAndCoachesIds: { candidateId: string; coachId: string }[] + ): Promise { + return this.userCandidatsService.updateAllLinkedCoachesByCandidatesIds( + candidatesAndCoachesIds + ); + } + async updateUserProfileByUserId( userId: string, updateUserProfileDto: Partial diff --git a/src/users/users.controller.ts b/src/users/users.controller.ts index 3c89d563..88b1b0a9 100644 --- a/src/users/users.controller.ts +++ b/src/users/users.controller.ts @@ -329,17 +329,9 @@ export class UsersController { @Param('userId', new ParseUUIDPipe()) userId: string, @Body('userToLinkId') userToLinkId: string ) { - // check if users are already linked and remove existing link if needed - const shouldRemoveLinkedUser = - (Array.isArray(userToLinkId) && userToLinkId.length === 0) || - (!Array.isArray(userToLinkId) && userToLinkId === null); - - if ( - !shouldRemoveLinkedUser && - (Array.isArray(userToLinkId) // external coach has possibly multiple candidates - ? !userToLinkId.every((id) => uuidValidate(id)) - : !uuidValidate(userId)) - ) { + const shouldRemoveLinkedUser = userToLinkId === null; + + if (!shouldRemoveLinkedUser && !uuidValidate(userId)) { throw new BadRequestException(); } @@ -349,9 +341,7 @@ export class UsersController { throw new NotFoundException(); } - const usersToLinkIds = Array.isArray(userToLinkId) - ? userToLinkId - : [userToLinkId]; + const usersToLinkIds = [userToLinkId]; const usersToLinkOrToRemoveIds = shouldRemoveLinkedUser ? getRelatedUser(user)?.map(({ id }) => id) diff --git a/tests/users/users.e2e-spec.ts b/tests/users/users.e2e-spec.ts index be81e8d0..31cc5e28 100644 --- a/tests/users/users.e2e-spec.ts +++ b/tests/users/users.e2e-spec.ts @@ -381,6 +381,7 @@ describe('Users', () => { whatsappZoneCoachName, whatsappZoneCoachUrl, whatsappZoneCoachQR, + OrganizationId, ...candidate } = await userFactory.create( { role: UserRoles.CANDIDATE }, @@ -404,6 +405,8 @@ describe('Users', () => { whatsappZoneCoachName: coachWhatsappZoneCoachName, whatsappZoneCoachUrl: coachWhatsappZoneCoachUrl, whatsappZoneCoachQR: coachWhatsappZoneCoachQR, + OrganizationId: coachOrganizationId, + referredCandidates: coachReferredCandidates, ...coach } = await userFactory.create({ role: UserRoles.COACH }, {}, true); @@ -493,6 +496,8 @@ describe('Users', () => { whatsappZoneCoachName: coachWhatsappZoneCoachName, whatsappZoneCoachUrl: coachWhatsappZoneCoachUrl, whatsappZoneCoachQR: coachWhatsappZoneCoachQR, + referredCandidates: coachReferredCandidates, + referrerId: coachReferrerId, ...coach } = await userFactory.create({ role: UserRoles.COACH }, {}, false); @@ -509,9 +514,11 @@ describe('Users', () => { organization, candidat, coaches, - whatsappZoneCoachName: candidatWhatappZoneCoachName, - whatsappZoneCoachUrl: candidatWhatappZoneCoachUrl, - whatsappZoneCoachQR: candidatWhatappZoneCoachQR, + whatsappZoneCoachName: candidateWhatappZoneCoachName, + whatsappZoneCoachUrl: candidateWhatappZoneCoachUrl, + whatsappZoneCoachQR: candidateWhatappZoneCoachQR, + referrerId: candidateReferrerId, + referredCandidates: candidateReferredCandidates, ...candidate } = await userFactory.create( { role: UserRoles.CANDIDATE }, @@ -626,85 +633,6 @@ describe('Users', () => { expect(response.status).toBe(400); }); - it('Should return 400 if candidate with multiple coaches', async () => { - const { - password, - hashReset, - salt, - saltReset, - revision, - updatedAt, - createdAt, - lastConnection, - whatsappZoneCoachName, - whatsappZoneCoachUrl, - whatsappZoneCoachQR, - ...candidate - } = await userFactory.create( - { role: UserRoles.CANDIDATE }, - {}, - false - ); - - const { id: coach1Id } = await userFactory.create( - { role: UserRoles.COACH }, - {}, - true - ); - const { id: coach2Id } = await userFactory.create( - { role: UserRoles.COACH }, - {}, - true - ); - - const response: APIResponse = - await request(server) - .post(`${route}`) - .set('authorization', `Bearer ${loggedInAdmin.token}`) - .send({ - ...candidate, - userToLinkId: [coach1Id, coach2Id], - }); - expect(response.status).toBe(400); - }); - it('Should return 400 if coach with multiple candidates', async () => { - const { - password, - hashReset, - salt, - saltReset, - revision, - updatedAt, - createdAt, - lastConnection, - whatsappZoneCoachName, - whatsappZoneCoachUrl, - whatsappZoneCoachQR, - ...coach - } = await userFactory.create({ role: UserRoles.COACH }, {}, false); - - const { id: candidate1Id } = await userFactory.create( - { role: UserRoles.CANDIDATE }, - {}, - true - ); - const { id: candidate2Id } = await userFactory.create( - { role: UserRoles.CANDIDATE }, - {}, - true - ); - - const response: APIResponse = - await request(server) - .post(`${route}`) - .set('authorization', `Bearer ${loggedInAdmin.token}`) - .send({ - ...coach, - userToLinkId: [candidate1Id, candidate2Id], - }); - expect(response.status).toBe(400); - }); - it('Should return 400 if candidate with organization', async () => { const { password, @@ -1050,41 +978,7 @@ describe('Users', () => { .send(userToSend); expect(response.status).toBe(400); }); - it('Should return 400 when user has wrong role', async () => { - const user = await userFactory.create( - { role: UserRoles.COACH }, - {}, - false - ); - const userValues = { - firstName: user.firstName, - lastName: user.lastName, - email: user.email, - phone: user.phone, - role: user.role, - gender: user.gender, - }; - - const userProfileValues = { - department: 'Paris (75)' as Department, - }; - - const userToSend = { - ...userValues, - ...userProfileValues, - password: user.password, - program: Programs.THREE_SIXTY, - birthDate: '1996-24-04', - }; - - const response: APIResponse< - UsersCreationController['createUserRegistration'] - > = await request(server) - .post(`${route}/registration`) - .send(userToSend); - expect(response.status).toBe(400); - }); it('Should return 409 when the email already exist', async () => { const existingUser = await userFactory.create({}, {}, true); @@ -1366,6 +1260,7 @@ describe('Users', () => { whatsappZoneCoachName: loggedInCandidate.user.whatsappZoneCoachName, whatsappZoneCoachUrl: loggedInCandidate.user.whatsappZoneCoachUrl, whatsappZoneCoachQR: loggedInCandidate.user.whatsappZoneCoachQR, + referrerId: loggedInCandidate.user.referrerId, organization: null, }; @@ -5268,6 +5163,7 @@ describe('Users', () => { whatsappZoneCoachName, whatsappZoneCoachUrl, whatsappZoneCoachQR, + referrerId, ...restCandidate } = loggedInCandidate.user; @@ -5281,6 +5177,8 @@ describe('Users', () => { whatsappZoneCoachName: coachWhatsappZoneCoachName, whatsappZoneCoachUrl: coachWhatsappZoneCoachUrl, whatsappZoneCoachQR: coachWhatsappZoneCoachQR, + referrerId: coachReferrerId, + referredCandidates: coachReferredCandidates, ...restCoach } = loggedInCoach.user; @@ -5315,6 +5213,8 @@ describe('Users', () => { whatsappZoneCoachName, whatsappZoneCoachUrl, whatsappZoneCoachQR, + referrerId, + referredCandidates, ...restCandidate } = loggedInCandidate.user; @@ -5327,6 +5227,7 @@ describe('Users', () => { whatsappZoneCoachName: coachWhatsappZoneCoachName, whatsappZoneCoachUrl: coachWhatsappZoneCoachUrl, whatsappZoneCoachQR: coachWhatsappZoneCoachQR, + referredCandidates: coachReferredCandidates, ...restCoach } = loggedInCoach.user; @@ -5424,38 +5325,6 @@ describe('Users', () => { ); }); - it('Should return 400 if admin updates linked multiple coaches for candidate', async () => { - const otherCoach = await userFactory.create( - { role: UserRoles.COACH }, - {}, - true - ); - - const response: APIResponse = - await request(server) - .put(`${route}/linkUser/${loggedInCandidate.user.id}`) - .set('authorization', `Bearer ${loggedInAdmin.token}`) - .send({ - userToLinkId: [loggedInCoach.user.id, otherCoach.id], - }); - expect(response.status).toBe(400); - }); - it('Should return 400 if admin updates linked multiple candidates for coach', async () => { - const otherCandidate = await userFactory.create( - { role: UserRoles.CANDIDATE }, - {}, - true - ); - const response: APIResponse = - await request(server) - .put(`${route}/linkUser/${loggedInCoach.user.id}`) - .set('authorization', `Bearer ${loggedInAdmin.token}`) - .send({ - userToLinkId: [loggedInCandidate.user.id, otherCandidate.id], - }); - expect(response.status).toBe(400); - }); - it('Should return 400 if admin updates normal candidate with another normal candidate as coach', async () => { const otherCandidate = await userFactory.create( { role: UserRoles.CANDIDATE }, From 4a46a250f81fbec76c5b4c9b9fab6786b2d08c9c Mon Sep 17 00:00:00 2001 From: Guillaume Cauchois Date: Thu, 31 Oct 2024 15:55:44 +0100 Subject: [PATCH 08/29] fix: tests + referrer to referer --- .../20241008205334-add-referrer-to-user.js | 4 +- .../mailjet/mailjet.types.ts | 2 +- src/mails/mails.service.ts | 12 +- src/organizations/organizations.controller.ts | 11 +- src/user-profiles/user-profiles.controller.ts | 2 +- .../users-creation.controller.ts | 2 +- src/users/models/user-candidat.model.ts | 2 +- src/users/models/user.attributes.ts | 2 +- src/users/models/user.model.ts | 8 +- src/users/users.controller.ts | 2 +- src/users/users.service.ts | 6 +- src/users/users.types.ts | 16 +- src/users/users.utils.ts | 2 +- tests/organizations/organizations.e2e-spec.ts | 198 ++---------------- tests/users/users.e2e-spec.ts | 12 +- 15 files changed, 57 insertions(+), 224 deletions(-) diff --git a/src/db/migrations/20241008205334-add-referrer-to-user.js b/src/db/migrations/20241008205334-add-referrer-to-user.js index c524999f..41802417 100644 --- a/src/db/migrations/20241008205334-add-referrer-to-user.js +++ b/src/db/migrations/20241008205334-add-referrer-to-user.js @@ -3,7 +3,7 @@ /** @type {import('sequelize-cli').Migration} */ module.exports = { async up(queryInterface, Sequelize) { - await queryInterface.addColumn('Users', 'referrerId', { + await queryInterface.addColumn('Users', 'refererId', { type: Sequelize.UUID, allowNull: true, defaultValue: null, @@ -11,6 +11,6 @@ module.exports = { }, async down(queryInterface, Sequelize) { - await queryInterface.removeColumn('Users', 'referrerId'); + await queryInterface.removeColumn('Users', 'refererId'); }, }; diff --git a/src/external-services/mailjet/mailjet.types.ts b/src/external-services/mailjet/mailjet.types.ts index a050e979..87433e83 100644 --- a/src/external-services/mailjet/mailjet.types.ts +++ b/src/external-services/mailjet/mailjet.types.ts @@ -90,7 +90,7 @@ export const MailjetTemplates = { INTERNAL_MESSAGE_CONFIRMATION: 5625495, MESSAGING_MESSAGE: 6305900, USER_EMAIL_VERIFICATION: 5899611, - USER_EMAIL_VERIFICATION_REFERRER: 6324333, + USER_EMAIL_VERIFICATION_REFERER: 6324333, USER_REPORTED_ADMIN: 6223181, CONVERSATION_REPORTED_ADMIN: 6276909, ONBOARDING_J1_BAO: 6129684, diff --git a/src/mails/mails.service.ts b/src/mails/mails.service.ts index bf733958..22d5b914 100644 --- a/src/mails/mails.service.ts +++ b/src/mails/mails.service.ts @@ -109,8 +109,8 @@ export class MailsService { async sendVerificationMail(user: User, token: string) { const templateId = - user.role === UserRoles.REFERRER - ? MailjetTemplates.USER_EMAIL_VERIFICATION_REFERRER + user.role === UserRoles.REFERER + ? MailjetTemplates.USER_EMAIL_VERIFICATION_REFERER : MailjetTemplates.USER_EMAIL_VERIFICATION; return this.queuesService.addToWorkQueue(Jobs.SEND_MAIL, { toEmail: user.email, @@ -166,7 +166,7 @@ export class MailsService { const toEmail: CustomMailParams['toEmail'] = { to: candidate.email }; const coach = getCoachFromCandidate(candidate); - if (coach && coach.role !== UserRoles.REFERRER) { + if (coach && coach.role !== UserRoles.REFERER) { toEmail.cc = coach.email; } const { candidatesAdminMail } = getAdminMailsFromZone(candidate.zone); @@ -196,7 +196,7 @@ export class MailsService { const coach = getCoachFromCandidate(candidate); const toEmail: CustomMailParams['toEmail'] = - coach && coach.role !== UserRoles.REFERRER + coach && coach.role !== UserRoles.REFERER ? { to: candidate.email, cc: coach.email } : { to: candidate.email }; @@ -249,7 +249,7 @@ export class MailsService { }; const coach = getCoachFromCandidate(candidate); - if (coach && coach.role !== UserRoles.REFERRER) { + if (coach && coach.role !== UserRoles.REFERER) { toEmail.cc = coach.email; } @@ -276,7 +276,7 @@ export class MailsService { to: candidate.email, }; const coach = getCoachFromCandidate(candidate); - if (coach && coach.role !== UserRoles.REFERRER) { + if (coach && coach.role !== UserRoles.REFERER) { toEmail.cc = coach.email; } const { candidatesAdminMail } = getAdminMailsFromZone(candidate.zone); diff --git a/src/organizations/organizations.controller.ts b/src/organizations/organizations.controller.ts index 9cc1c506..39f84b59 100644 --- a/src/organizations/organizations.controller.ts +++ b/src/organizations/organizations.controller.ts @@ -39,7 +39,6 @@ export class OrganizationsController { ) {} @Public() - @UseGuards(UserPermissionsGuard) @Get() async findAll( @Query('limit', new ParseIntPipe()) limit: number, @@ -56,25 +55,24 @@ export class OrganizationsController { return Promise.all( organizations.map(async (organization) => { - const { candidatesCount, coachesCount, referrersCount } = + const { candidatesCount, coachesCount, referersCount } = await this.organizationsService.countAssociatedUsers(organization.id); return { ...(organization.toJSON() as Organization), candidatesCount, coachesCount, - referrersCount, + referersCount, } as Organization & { candidatesCount: number; coachesCount: number; - referrersCount: number; + referersCount: number; }; }) ); } - @UserPermissions(Permissions.ADMIN) - @UseGuards(UserPermissionsGuard) + @Public() @Get(':id') async findOne(@Param('id', new ParseUUIDPipe()) id: string) { const organization = await this.organizationsService.findOne(id); @@ -86,7 +84,6 @@ export class OrganizationsController { @Public() @Throttle(5, 60) - @UseGuards(UserPermissionsGuard) @Post() async create( @Body(new CreateOrganizationPipe()) diff --git a/src/user-profiles/user-profiles.controller.ts b/src/user-profiles/user-profiles.controller.ts index a204892f..5cb9b611 100644 --- a/src/user-profiles/user-profiles.controller.ts +++ b/src/user-profiles/user-profiles.controller.ts @@ -131,7 +131,7 @@ export class UserProfilesController { throw new BadRequestException(); } - if (role.includes(UserRoles.REFERRER)) { + if (role.includes(UserRoles.REFERER)) { throw new BadRequestException(); } diff --git a/src/users-creation/users-creation.controller.ts b/src/users-creation/users-creation.controller.ts index 5b543d08..9d722fbb 100644 --- a/src/users-creation/users-creation.controller.ts +++ b/src/users-creation/users-creation.controller.ts @@ -172,7 +172,7 @@ export class UsersCreationController { } if ( - !isRoleIncluded([UserRoles.REFERRER], createUserRegistrationDto.role) && + !isRoleIncluded([UserRoles.REFERER], createUserRegistrationDto.role) && !createUserRegistrationDto.program ) { throw new BadRequestException(); diff --git a/src/users/models/user-candidat.model.ts b/src/users/models/user-candidat.model.ts index 811fcc67..b133fadd 100644 --- a/src/users/models/user-candidat.model.ts +++ b/src/users/models/user-candidat.model.ts @@ -126,7 +126,7 @@ export class UserCandidat extends Model { previousUserCandidatValues && previousUserCandidatValues.coachId !== undefined && previousUserCandidatValues.coachId !== userCandidatToUpdate.coachId && - coach.role !== UserRoles.REFERRER + coach.role !== UserRoles.REFERER ) { await UserCandidat.update( { coachId: null }, diff --git a/src/users/models/user.attributes.ts b/src/users/models/user.attributes.ts index 769a21ab..27d2574a 100644 --- a/src/users/models/user.attributes.ts +++ b/src/users/models/user.attributes.ts @@ -13,7 +13,7 @@ export const UserAttributes = [ 'lastConnection', 'isEmailVerified', 'createdAt', - 'referrerId', + 'refererId', /*'updatedAt',*/ 'deletedAt', 'whatsappZoneCoachName', diff --git a/src/users/models/user.model.ts b/src/users/models/user.model.ts index de8694a3..942ac50e 100644 --- a/src/users/models/user.model.ts +++ b/src/users/models/user.model.ts @@ -77,7 +77,7 @@ export class User extends HistorizedModel { @ForeignKey(() => User) @AllowNull(true) @Column - referrerId: string; + refererId: string; @ApiProperty() @IsString() @@ -224,8 +224,8 @@ export class User extends HistorizedModel { @BelongsTo(() => Organization, 'OrganizationId') organization?: Organization; - @BelongsTo(() => User, 'referrerId') - referrer?: User; + @BelongsTo(() => User, 'refererId') + referer?: User; @HasOne(() => UserProfile, { foreignKey: 'UserId', @@ -242,7 +242,7 @@ export class User extends HistorizedModel { @HasMany(() => ReadDocument, 'UserId') readDocuments: ReadDocument[]; - @HasMany(() => User, 'referrerId') + @HasMany(() => User, 'refererId') referredCandidates: User[]; @BeforeCreate diff --git a/src/users/users.controller.ts b/src/users/users.controller.ts index 88b1b0a9..46a1ff6d 100644 --- a/src/users/users.controller.ts +++ b/src/users/users.controller.ts @@ -149,7 +149,7 @@ export class UsersController { coachId: role === UserRoles.COACH ? userId : undefined, }; - if (role === UserRoles.REFERRER) { + if (role === UserRoles.REFERER) { const userCandidates = await this.userCandidatsService.findAllByCoachId( ids.coachId ); diff --git a/src/users/users.service.ts b/src/users/users.service.ts index cbefafab..5521ba89 100644 --- a/src/users/users.service.ts +++ b/src/users/users.service.ts @@ -415,17 +415,17 @@ export class UsersService { }, }); - const { count: referrersCount } = await this.userModel.findAndCountAll({ + const { count: referersCount } = await this.userModel.findAndCountAll({ where: { OrganizationId: organizationId, - role: UserRoles.REFERRER, + role: UserRoles.REFERER, }, }); return { candidatesCount, coachesCount, - referrersCount, + referersCount, }; } diff --git a/src/users/users.types.ts b/src/users/users.types.ts index 1412b002..d1ff1a28 100644 --- a/src/users/users.types.ts +++ b/src/users/users.types.ts @@ -14,14 +14,14 @@ import { export const UserRoles = { CANDIDATE: 'Candidat', COACH: 'Coach', - REFERRER: 'Orienteur', + REFERER: 'Orienteur', ADMIN: 'Admin', } as const; export type UserRole = (typeof UserRoles)[keyof typeof UserRoles]; export const Permissions = { - REFERRER: 'Orienteur', + REFERER: 'Orienteur', CANDIDATE: 'Candidat', COACH: 'Coach', RESTRICTED_COACH: 'Restricted_Coach', @@ -32,7 +32,7 @@ export type Permission = (typeof Permissions)[keyof typeof Permissions]; export const UserPermissions: { [K in UserRole]: Permission | Permission[] } = { [UserRoles.CANDIDATE]: Permissions.CANDIDATE, - [UserRoles.REFERRER]: Permissions.COACH, + [UserRoles.REFERER]: Permissions.COACH, [UserRoles.COACH]: [Permissions.COACH, Permissions.RESTRICTED_COACH], [UserRoles.ADMIN]: Permissions.ADMIN, }; @@ -49,20 +49,20 @@ export const NormalUserRoles: NormalUserRole[] = [ export type RegistrableUserRole = | typeof UserRoles.CANDIDATE | typeof UserRoles.COACH - | typeof UserRoles.REFERRER; + | typeof UserRoles.REFERER; export const RegistrableUserRoles: RegistrableUserRole[] = [ UserRoles.CANDIDATE, UserRoles.COACH, - UserRoles.REFERRER, + UserRoles.REFERER, ]; -export const RolesWithOrganization: UserRole[] = [UserRoles.REFERRER]; +export const RolesWithOrganization: UserRole[] = [UserRoles.REFERER]; export const AllUserRoles = [ UserRoles.CANDIDATE, UserRoles.COACH, - UserRoles.REFERRER, + UserRoles.REFERER, ]; export const AdminRoles = { @@ -75,7 +75,7 @@ export type AdminRole = (typeof AdminRoles)[keyof typeof AdminRoles]; export const UserRolesFilters = [ { value: UserRoles.CANDIDATE, label: `${UserRoles.CANDIDATE} LKO` }, { value: UserRoles.COACH, label: `${UserRoles.COACH} LKO` }, - { value: UserRoles.REFERRER, label: UserRoles.REFERRER }, + { value: UserRoles.REFERER, label: UserRoles.REFERER }, { value: UserRoles.ADMIN, label: UserRoles.ADMIN }, ]; diff --git a/src/users/users.utils.ts b/src/users/users.utils.ts index 3f3c1643..42777e98 100644 --- a/src/users/users.utils.ts +++ b/src/users/users.utils.ts @@ -121,7 +121,7 @@ export function getCandidateIdFromCoachOrCandidate(member: User) { if (member.role === UserRoles.CANDIDATE) { return member.id; } - if (isRoleIncluded([UserRoles.REFERRER], member.role)) { + if (isRoleIncluded([UserRoles.REFERER], member.role)) { return member.referredCandidates.map(({ candidat }) => { return candidat.candidat.id; }); diff --git a/tests/organizations/organizations.e2e-spec.ts b/tests/organizations/organizations.e2e-spec.ts index 7c8f8cd2..471c59e2 100644 --- a/tests/organizations/organizations.e2e-spec.ts +++ b/tests/organizations/organizations.e2e-spec.ts @@ -66,22 +66,6 @@ describe('Organizations', () => { describe('CRUD Organization', () => { describe('C - Create 1 Organization', () => { describe('/ - Create organization', () => { - let loggedInAdmin: LoggedUser; - let loggedInCandidate: LoggedUser; - let loggedInCoach: LoggedUser; - - beforeEach(async () => { - loggedInAdmin = await usersHelper.createLoggedInUser({ - role: UserRoles.ADMIN, - }); - loggedInCandidate = await usersHelper.createLoggedInUser({ - role: UserRoles.CANDIDATE, - }); - loggedInCoach = await usersHelper.createLoggedInUser({ - role: UserRoles.COACH, - }); - }); - it('Should return 201 when admin creates an organization', async () => { const organization = await organizationFactory.create({}, {}, false); @@ -89,10 +73,7 @@ describe('Organizations', () => { await organizationsHelper.mapOrganizationProps(organization); const response: APIResponse = - await request(server) - .post(`${route}`) - .set('authorization', `Bearer ${loggedInAdmin.token}`) - .send(organizationToCreate); + await request(server).post(`${route}`).send(organizationToCreate); expect(response.status).toBe(201); expect(response.body).toEqual( expect.objectContaining({ @@ -110,10 +91,7 @@ describe('Organizations', () => { await organizationsHelper.mapOrganizationProps(organization); const response: APIResponse = - await request(server) - .post(`${route}`) - .set('authorization', `Bearer ${loggedInAdmin.token}`) - .send(organizationToCreate); + await request(server).post(`${route}`).send(organizationToCreate); expect(response.status).toBe(201); expect(response.body).toEqual( expect.objectContaining({ @@ -133,10 +111,7 @@ describe('Organizations', () => { }; const response: APIResponse = - await request(server) - .post(`${route}`) - .set('authorization', `Bearer ${loggedInAdmin.token}`) - .send(wrongData); + await request(server).post(`${route}`).send(wrongData); expect(response.status).toBe(400); }); it('Should return 400 when organization has invalid phone number', async () => { @@ -151,76 +126,22 @@ describe('Organizations', () => { }; const response: APIResponse = - await request(server) - .post(`${route}`) - .set('authorization', `Bearer ${loggedInAdmin.token}`) - .send(wrongData); + await request(server).post(`${route}`).send(wrongData); expect(response.status).toBe(400); }); - it('Should return 401 when the user is not logged in', async () => { - const organization = await organizationFactory.create({}, {}, false); - - const organizationToCreate = - await organizationsHelper.mapOrganizationProps(organization); - - const response: APIResponse = - await request(server).post(`${route}`).send(organizationToCreate); - expect(response.status).toBe(401); - }); - it('Should return 403 when the user is not a candidate', async () => { - const organization = await organizationFactory.create({}, {}, false); - - const organizationToCreate = - await organizationsHelper.mapOrganizationProps(organization); - - const response: APIResponse = - await request(server) - .post(`${route}`) - .set('authorization', `Bearer ${loggedInCandidate.token}`) - .send(organizationToCreate); - expect(response.status).toBe(403); - }); - it('Should return 403 when the user is a coach', async () => { - const organization = await organizationFactory.create({}, {}, false); - - const organizationToCreate = - await organizationsHelper.mapOrganizationProps(organization); - - const response: APIResponse = - await request(server) - .post(`${route}`) - .set('authorization', `Bearer ${loggedInCoach.token}`) - .send(organizationToCreate); - expect(response.status).toBe(403); - }); }); }); describe('R - Read 1 Organization', () => { describe('/:id - Get an organization by id', () => { - let loggedInAdmin: LoggedUser; - let loggedInCandidate: LoggedUser; - let loggedInCoach: LoggedUser; - let organization: Organization; beforeEach(async () => { - loggedInAdmin = await usersHelper.createLoggedInUser({ - role: UserRoles.ADMIN, - }); - loggedInCandidate = await usersHelper.createLoggedInUser({ - role: UserRoles.CANDIDATE, - }); - loggedInCoach = await usersHelper.createLoggedInUser({ - role: UserRoles.COACH, - }); organization = await organizationFactory.create({}, {}, true); }); - it('Should return 200 when admin gets an organization', async () => { + it('Should return 200 when get an organization', async () => { const response: APIResponse = - await request(server) - .get(`${route}/${organization.id}`) - .set('authorization', `Bearer ${loggedInAdmin.token}`); + await request(server).get(`${route}/${organization.id}`); expect(response.status).toBe(200); expect(response.body).toEqual( expect.objectContaining({ @@ -240,79 +161,14 @@ describe('Organizations', () => { it('Should return 404 when the organization does not exist', async () => { const response: APIResponse = - await request(server) - .get(`${route}/${uuid()}`) - .set('authorization', `Bearer ${loggedInAdmin.token}`); + await request(server).get(`${route}/${uuid()}`); expect(response.status).toBe(404); }); - it('Should return 401 when the user is not logged in', async () => { - const response: APIResponse = - await request(server).get(`${route}/${organization.id}`); - expect(response.status).toBe(401); - }); - it('Should return 403 when the user is a candidate', async () => { - const response: APIResponse = - await request(server) - .get(`${route}/${organization.id}`) - .set('authorization', `Bearer ${loggedInCandidate.token}`); - expect(response.status).toBe(403); - }); - it('Should return 403 when the user is a coach', async () => { - const response: APIResponse = - await request(server) - .get(`${route}/${organization.id}`) - .set('authorization', `Bearer ${loggedInCoach.token}`); - expect(response.status).toBe(403); - }); }); }); describe('R - Read many Organizations', () => { - it('Should return 401 when the user is not logged in', async () => { - const response: APIResponse = - await request(server).get(`${route}/?search=X`); - expect(response.status).toBe(401); - }); - it('Should return 403 when the user is a candidate', async () => { - const organization = await organizationFactory.create( - { name: 'GGG', zone: AdminZones.LILLE }, - {}, - true - ); - const loggedInCandidate = await usersHelper.createLoggedInUser({ - role: UserRoles.CANDIDATE, - OrganizationId: organization.id, - }); - - const response: APIResponse = - await request(server) - .get(`${route}/?search=X`) - .set('authorization', `Bearer ${loggedInCandidate.token}`); - expect(response.status).toBe(403); - }); - it('Should return 403 when the user is a coach', async () => { - const organization = await organizationFactory.create( - { name: 'GGG', zone: AdminZones.LILLE }, - {}, - true - ); - const loggedInCoach = await usersHelper.createLoggedInUser({ - role: UserRoles.COACH, - OrganizationId: organization.id, - }); - const response: APIResponse = - await request(server) - .get(`${route}/?search=X`) - .set('authorization', `Bearer ${loggedInCoach.token}`); - expect(response.status).toBe(403); - }); - describe('/?limit=&offset= - Get paginated and alphabetically sorted organizations', () => { - let loggedInAdmin: LoggedUser; - beforeEach(async () => { - loggedInAdmin = await usersHelper.createLoggedInUser({ - role: UserRoles.ADMIN, - }); await organizationFactory.create({ name: 'A', }); @@ -331,9 +187,7 @@ describe('Organizations', () => { }); it('Should return 200 and 2 first organizations', async () => { const response: APIResponse = - await request(server) - .get(`${route}/?limit=2&offset=0`) - .set('authorization', `Bearer ${loggedInAdmin.token}`); + await request(server).get(`${route}/?limit=2&offset=0`); expect(response.status).toBe(200); expect(response.body.length).toBe(2); expect(response.body[0].name).toMatch('A'); @@ -341,9 +195,7 @@ describe('Organizations', () => { }); it('Should return 200 and 3 first organizations', async () => { const response: APIResponse = - await request(server) - .get(`${route}/?limit=3&offset=0`) - .set('authorization', `Bearer ${loggedInAdmin.token}`); + await request(server).get(`${route}/?limit=3&offset=0`); expect(response.status).toBe(200); expect(response.body.length).toBe(3); expect(response.body[0].name).toMatch('A'); @@ -352,9 +204,7 @@ describe('Organizations', () => { }); it('Should return 200 and the 3rd and 4th organization', async () => { const response: APIResponse = - await request(server) - .get(`${route}/?limit=2&offset=2`) - .set('authorization', `Bearer ${loggedInAdmin.token}`); + await request(server).get(`${route}/?limit=2&offset=2`); expect(response.status).toBe(200); expect(response.body.length).toBe(2); expect(response.body[0].name).toMatch('C'); @@ -362,8 +212,6 @@ describe('Organizations', () => { }); }); describe('/?search=&zone= - Read all organizations matching search query and filters', () => { - let loggedInAdmin: LoggedUser; - let organization: Organization; let organizations: Organization[]; @@ -375,10 +223,6 @@ describe('Organizations', () => { true ); - loggedInAdmin = await usersHelper.createLoggedInUser({ - role: UserRoles.ADMIN, - }); - await databaseHelper.createEntities( userFactory, 3, @@ -419,9 +263,7 @@ describe('Organizations', () => { ...organizations.map(({ id }) => id), ]; const response: APIResponse = - await request(server) - .get(`${route}?limit=50&offset=0&search=X`) - .set('authorization', `Bearer ${loggedInAdmin.token}`); + await request(server).get(`${route}?limit=50&offset=0&search=X`); expect(response.status).toBe(200); expect(response.body.length).toBe(5); expect(expectedOrganizationsId).toEqual( @@ -434,9 +276,9 @@ describe('Organizations', () => { ...organizations.map(({ id }) => id), ]; const response: APIResponse = - await request(server) - .get(`${route}?limit=50&offset=0&zone[]=${AdminZones.PARIS}`) - .set('authorization', `Bearer ${loggedInAdmin.token}`); + await request(server).get( + `${route}?limit=50&offset=0&zone[]=${AdminZones.PARIS}` + ); expect(response.status).toBe(200); expect(response.body.length).toBe(5); expect(expectedOrganizationsId).toEqual( @@ -445,9 +287,7 @@ describe('Organizations', () => { }); it('Should return 200 and all organizations candidates and coaches count when admin gets organizations ', async () => { const response: APIResponse = - await request(server) - .get(`${route}/?limit=50&offset=0`) - .set('authorization', `Bearer ${loggedInAdmin.token}`); + await request(server).get(`${route}/?limit=50&offset=0`); expect(response.status).toBe(200); expect(response.body).toEqual( expect.arrayContaining([ @@ -460,17 +300,13 @@ describe('Organizations', () => { }); it('Should return 200 and all organizations when admin gets organizations without search query or filters', async () => { const response: APIResponse = - await request(server) - .get(`${route}/?limit=50&offset=0`) - .set('authorization', `Bearer ${loggedInAdmin.token}`); + await request(server).get(`${route}/?limit=50&offset=0`); expect(response.status).toBe(200); expect(response.body.length).toBe(11); }); it('Should return 200 and empty array when no matching organizations', async () => { const response: APIResponse = - await request(server) - .get(`${route}/?limit=50&offset=0&search=Z`) - .set('authorization', `Bearer ${loggedInAdmin.token}`); + await request(server).get(`${route}/?limit=50&offset=0&search=Z`); expect(response.status).toBe(200); expect(response.body.length).toBe(0); }); diff --git a/tests/users/users.e2e-spec.ts b/tests/users/users.e2e-spec.ts index 31cc5e28..110978a6 100644 --- a/tests/users/users.e2e-spec.ts +++ b/tests/users/users.e2e-spec.ts @@ -497,7 +497,7 @@ describe('Users', () => { whatsappZoneCoachUrl: coachWhatsappZoneCoachUrl, whatsappZoneCoachQR: coachWhatsappZoneCoachQR, referredCandidates: coachReferredCandidates, - referrerId: coachReferrerId, + refererId: coachRefererId, ...coach } = await userFactory.create({ role: UserRoles.COACH }, {}, false); @@ -517,7 +517,7 @@ describe('Users', () => { whatsappZoneCoachName: candidateWhatappZoneCoachName, whatsappZoneCoachUrl: candidateWhatappZoneCoachUrl, whatsappZoneCoachQR: candidateWhatappZoneCoachQR, - referrerId: candidateReferrerId, + refererId: candidateRefererId, referredCandidates: candidateReferredCandidates, ...candidate } = await userFactory.create( @@ -1260,7 +1260,7 @@ describe('Users', () => { whatsappZoneCoachName: loggedInCandidate.user.whatsappZoneCoachName, whatsappZoneCoachUrl: loggedInCandidate.user.whatsappZoneCoachUrl, whatsappZoneCoachQR: loggedInCandidate.user.whatsappZoneCoachQR, - referrerId: loggedInCandidate.user.referrerId, + refererId: loggedInCandidate.user.refererId, organization: null, }; @@ -5163,7 +5163,7 @@ describe('Users', () => { whatsappZoneCoachName, whatsappZoneCoachUrl, whatsappZoneCoachQR, - referrerId, + refererId, ...restCandidate } = loggedInCandidate.user; @@ -5177,7 +5177,7 @@ describe('Users', () => { whatsappZoneCoachName: coachWhatsappZoneCoachName, whatsappZoneCoachUrl: coachWhatsappZoneCoachUrl, whatsappZoneCoachQR: coachWhatsappZoneCoachQR, - referrerId: coachReferrerId, + refererId: coachRefererId, referredCandidates: coachReferredCandidates, ...restCoach } = loggedInCoach.user; @@ -5213,7 +5213,7 @@ describe('Users', () => { whatsappZoneCoachName, whatsappZoneCoachUrl, whatsappZoneCoachQR, - referrerId, + refererId, referredCandidates, ...restCandidate } = loggedInCandidate.user; From d7728d001c20f76fd9f2ba274f3d344afb71490b Mon Sep 17 00:00:00 2001 From: Guillaume Cauchois Date: Mon, 4 Nov 2024 16:30:08 +0100 Subject: [PATCH 09/29] feat: referer tests in users.e2e --- tests/users/users.e2e-spec.ts | 246 +++++- yarn.lock | 1446 +++++++++++++++++---------------- 2 files changed, 997 insertions(+), 695 deletions(-) diff --git a/tests/users/users.e2e-spec.ts b/tests/users/users.e2e-spec.ts index 110978a6..46c074de 100644 --- a/tests/users/users.e2e-spec.ts +++ b/tests/users/users.e2e-spec.ts @@ -804,6 +804,62 @@ describe('Users', () => { }) ); }); + it('Should return 200 and a created referer if valid referer data', async () => { + const user = await userFactory.create( + { role: UserRoles.REFERER }, + {}, + false + ); + + const organization = await organizationFactory.create({}, {}, true); + + const userValues = { + firstName: user.firstName, + lastName: user.lastName, + email: user.email, + phone: user.phone, + role: user.role, + gender: user.gender, + }; + + const userOrganizationValues = { + organizationId: organization.id, + }; + + const userProfileValues = { + department: 'Paris (75)' as Department, + }; + + const userToSend = { + ...userValues, + ...userProfileValues, + ...userOrganizationValues, + password: user.password, + birthDate: '1996-24-04', + }; + + const response: APIResponse< + UsersCreationController['createUserRegistration'] + > = await request(server) + .post(`${route}/registration`) + .send(userToSend); + expect(response.status).toBe(201); + expect(response.body).toEqual( + expect.objectContaining({ + ...userValues, + zone: getZoneFromDepartment(userProfileValues.department), + organization: { + id: organization.id, + name: organization.name, + zone: organization.zone, + address: organization.address, + }, + userProfile: expect.objectContaining({ + department: userProfileValues.department, + }), + }) + ); + }); it('Should return 200 and a created candidate if missing optional fields', async () => { const user = await userFactory.create( { role: UserRoles.CANDIDATE }, @@ -908,6 +964,36 @@ describe('Users', () => { .send(userToSend); expect(response.status).toBe(400); }); + it('Should return 400 when referer has missing mandatory fields', async () => { + const user = await userFactory.create( + { role: UserRoles.REFERER }, + {}, + false + ); + + const userValues = { + firstName: user.firstName, + lastName: user.lastName, + email: user.email, + }; + + const userProfileValues = { + department: 'Paris (75)' as Department, + }; + + const userToSend = { + ...userValues, + ...userProfileValues, + password: user.password, + }; + + const response: APIResponse< + UsersCreationController['createUserRegistration'] + > = await request(server) + .post(`${route}/registration`) + .send(userToSend); + expect(response.status).toBe(400); + }); it('Should return 400 when user has invalid email', async () => { const user = await userFactory.create( { role: UserRoles.COACH }, @@ -1023,6 +1109,7 @@ describe('Users', () => { let loggedInAdmin: LoggedUser; let loggedInCandidate: LoggedUser; let loggedInCoach: LoggedUser; + let loggedInReferer: LoggedUser; beforeEach(async () => { loggedInAdmin = await usersHelper.createLoggedInUser({ @@ -1034,6 +1121,9 @@ describe('Users', () => { loggedInCoach = await usersHelper.createLoggedInUser({ role: UserRoles.COACH, }); + loggedInReferer = await usersHelper.createLoggedInUser({ + role: UserRoles.REFERER, + }); }); it('Should return 401 when the user is not logged in', async () => { const candidate = await userFactory.create( @@ -1063,6 +1153,14 @@ describe('Users', () => { expect(response.status).toBe(200); expect(response.body.email).toEqual(loggedInCoach.user.email); }); + it('Should return 200 when logged in referer gets himself', async () => { + const response: APIResponse = + await request(server) + .get(`${route}/${loggedInReferer.user.email}`) + .set('authorization', `Bearer ${loggedInReferer.token}`); + expect(response.status).toBe(200); + expect(response.body.email).toEqual(loggedInReferer.user.email); + }); it('Should return 403 when logged in coach get a candidate', async () => { const response: APIResponse = await request(server) @@ -1070,6 +1168,13 @@ describe('Users', () => { .set('authorization', `Bearer ${loggedInCoach.token}`); expect(response.status).toBe(403); }); + it('Should return 403 when logged in referer get a candidate', async () => { + const response: APIResponse = + await request(server) + .get(`${route}/${loggedInCandidate.user.email}`) + .set('authorization', `Bearer ${loggedInReferer.token}`); + expect(response.status).toBe(403); + }); it('Should return 200 and get a user by email when logged in as admin', async () => { const response: APIResponse = await request(server) @@ -1205,6 +1310,7 @@ describe('Users', () => { let loggedInAdmin: LoggedUser; let loggedInCandidate: LoggedUser; let loggedInCoach: LoggedUser; + let loggedInReferer: LoggedUser; let candidates: User[]; let coaches: User[]; @@ -1220,6 +1326,9 @@ describe('Users', () => { loggedInCoach = await usersHelper.createLoggedInUser({ role: UserRoles.COACH, }); + loggedInReferer = await usersHelper.createLoggedInUser({ + role: UserRoles.REFERER, + }); organization = await organizationFactory.create({}, {}, true); candidates = await databaseHelper.createEntities( @@ -1371,6 +1480,13 @@ describe('Users', () => { .set('authorization', `Bearer ${loggedInCoach.token}`); expect(response.status).toBe(403); }); + it('Should return 403 if user is logged in as referer', async () => { + const response: APIResponse = + await request(server) + .get(`${route}/search?query=e&role[]=${UserRoles.CANDIDATE}`) + .set('authorization', `Bearer ${loggedInReferer.token}`); + expect(response.status).toBe(403); + }); }); describe('/search/candidates?query= - Search a public candidate where query string in email, first name or last name', () => { let candidate: User; @@ -2273,6 +2389,16 @@ describe('Users', () => { expect.arrayContaining(response.body.map(({ id }) => id)) ); }); + + it('Should return 400, when try to list members with role referer', async () => { + const response: APIResponse = + await request(server) + .get( + `${route}/members?limit=50&offset=0&role[]=${UserRoles.REFERER}&query=XXX&zone[]=${AdminZones.LYON}&associatedUser[]=true` + ) + .set('authorization', `Bearer ${loggedInAdmin.token}`); + expect(response.status).toBe(400); + }); }); }); describe('/members/count - Count all pending members', () => { @@ -2488,6 +2614,7 @@ describe('Users', () => { let loggedInAdmin: LoggedUser; let loggedInCandidate: LoggedUser; let loggedInCoach: LoggedUser; + let loggedInReferer: LoggedUser; beforeEach(async () => { loggedInAdmin = await usersHelper.createLoggedInUser({ @@ -2499,6 +2626,9 @@ describe('Users', () => { loggedInCoach = await usersHelper.createLoggedInUser({ role: UserRoles.COACH, }); + loggedInReferer = await usersHelper.createLoggedInUser({ + role: UserRoles.REFERER, + }); ({ loggedInCoach, loggedInCandidate } = await userCandidatsHelper.associateCoachAndCandidate( @@ -2545,6 +2675,16 @@ describe('Users', () => { .set('authorization', `Bearer ${loggedInCoach.token}`); expect(response.status).toBe(403); }); + it('Should return 403, if referer gets recommendations for another user', async () => { + const response: APIResponse< + UserProfilesController['findRecommendationsByUserId'] + > = await request(server) + .get( + `${route}/profile/recommendations/${loggedInCandidate.user.id}` + ) + .set('authorization', `Bearer ${loggedInReferer.token}`); + expect(response.status).toBe(403); + }); it('Should return 403, if candidate gets recommendations for another user', async () => { const response: APIResponse< UserProfilesController['findRecommendationsByUserId'] @@ -3872,6 +4012,15 @@ describe('Users', () => { ) ); }); + it('Should return 400, if search for referer profile', async () => { + const response: APIResponse = + await request(server) + .get( + `${route}/profile?limit=2&offset=0&role[]=${UserRoles.REFERER}` + ) + .set('authorization', `Bearer ${loggedInCandidate.token}`); + expect(response.status).toBe(400); + }); it('Should return 200 and 3 first coaches', async () => { const response: APIResponse = await request(server) @@ -5577,6 +5726,7 @@ describe('Users', () => { let loggedInAdmin: LoggedUser; let loggedInCandidate: LoggedUser; let loggedInCoach: LoggedUser; + let loggedInReferer: LoggedUser; beforeEach(async () => { loggedInAdmin = await usersHelper.createLoggedInUser({ @@ -5613,6 +5763,10 @@ describe('Users', () => { } ); + loggedInReferer = await usersHelper.createLoggedInUser({ + role: UserRoles.REFERER, + }); + ({ loggedInCoach, loggedInCandidate } = await userCandidatsHelper.associateCoachAndCandidate( loggedInCoach, @@ -5655,6 +5809,19 @@ describe('Users', () => { }); expect(response.status).toBe(403); }); + it('Should return 403, if referer updates a profile for another user', async () => { + const response: APIResponse< + UserProfilesController['updateByUserId'] + > = await request(server) + .put(`${route}/profile/${loggedInCandidate.user.id}`) + .set('authorization', `Bearer ${loggedInReferer.token}`) + .send({ + description: 'hello', + isAvailable: false, + department: 'Paris (75)', + }); + expect(response.status).toBe(403); + }); it('Should return 403, if coach updates a profile for another user', async () => { const response: APIResponse< UserProfilesController['updateByUserId'] @@ -5783,6 +5950,22 @@ describe('Users', () => { expect(updatedUser.zone).toMatch(AdminZones.PARIS); }); + it('Should return 403, if referer updates his profile referer properties', async () => { + const updatedProfile: Partial = { + description: 'hello', + department: 'Paris (75)', + isAvailable: false, + }; + + const response: APIResponse< + UserProfilesController['updateByUserId'] + > = await request(server) + .put(`${route}/profile/${loggedInReferer.user.id}`) + .set('authorization', `Bearer ${loggedInReferer.token}`) + .send(updatedProfile); + + expect(response.status).toBe(403); + }); it('Should return 400, if coach updates his profile with candidate properties', async () => { const updatedProfile: Partial = { description: 'hello', @@ -5808,6 +5991,7 @@ describe('Users', () => { let loggedInAdmin: LoggedUser; let loggedInCandidate: LoggedUser; let loggedInCoach: LoggedUser; + let loggedInReferer: LoggedUser; beforeEach(async () => { path = userProfilesHelper.getTestImagePath(); @@ -5821,6 +6005,9 @@ describe('Users', () => { loggedInCoach = await usersHelper.createLoggedInUser({ role: UserRoles.COACH, }); + loggedInReferer = await usersHelper.createLoggedInUser({ + role: UserRoles.REFERER, + }); ({ loggedInCoach, loggedInCandidate } = await userCandidatsHelper.associateCoachAndCandidate( @@ -5860,6 +6047,16 @@ describe('Users', () => { .attach('profileImage', path); expect(response.status).toBe(403); }); + it('Should return 403, if referer uploads a profile picture for another user', async () => { + const response: APIResponse< + UserProfilesController['uploadProfileImage'] + > = await request(server) + .post(`${route}/profile/uploadImage/${loggedInCandidate.user.id}`) + .set('authorization', `Bearer ${loggedInReferer.token}`) + .set('Content-Type', 'multipart/form-data') + .attach('profileImage', path); + expect(response.status).toBe(403); + }); it('Should return 403, if coach uploads profile picture for another user', async () => { const response: APIResponse< UserProfilesController['uploadProfileImage'] @@ -5890,6 +6087,16 @@ describe('Users', () => { .attach('profileImage', path); expect(response.status).toBe(201); }); + it('Should return 201, if referer uploads his profile picture', async () => { + const response: APIResponse< + UserProfilesController['uploadProfileImage'] + > = await request(server) + .post(`${route}/profile/uploadImage/${loggedInReferer.user.id}`) + .set('authorization', `Bearer ${loggedInReferer.token}`) + .set('Content-Type', 'multipart/form-data') + .attach('profileImage', path); + expect(response.status).toBe(201); + }); it('Should return 400, if candidate uploads empty profile picture', async () => { const response: APIResponse< UserProfilesController['uploadProfileImage'] @@ -6045,8 +6252,10 @@ describe('Users', () => { describe('/:id - Delete user and all associated dto', () => { let loggedInAdmin: LoggedUser; let loggedInCoach: LoggedUser; + let loggedInReferer: LoggedUser; let candidate: User; let coach: User; + let referer: User; const uniqIdToFind = uuid(); const uniqId2ToFind = uuid(); let cvId: string; @@ -6065,6 +6274,10 @@ describe('Users', () => { role: UserRoles.COACH, }); + referer = await userFactory.create({ + role: UserRoles.REFERER, + }); + ({ candidate, coach } = await userCandidatsHelper.associateCoachAndCandidate( coach, @@ -6076,6 +6289,10 @@ describe('Users', () => { role: UserRoles.COACH, }); + loggedInReferer = await usersHelper.createLoggedInUser({ + role: UserRoles.REFERER, + }); + ({ id: cvId, experiences: [{ id: experienceId }], @@ -6122,13 +6339,21 @@ describe('Users', () => { true )); }); - it('Should return 403 if not logged in admin', async () => { + it('Should return 403 if logged as coach', async () => { const response: APIResponse = await request(server) .delete(`${route}/${candidate.id}`) .set('authorization', `Bearer ${loggedInCoach.token}`); expect(response.status).toBe(403); }); + + it('Should return 403 if logged as referer', async () => { + const response: APIResponse = + await request(server) + .delete(`${route}/${candidate.id}`) + .set('authorization', `Bearer ${loggedInReferer.token}`); + expect(response.status).toBe(403); + }); it('Should return 200 if logged in as admin and deletes candidate', async () => { const response: APIResponse = await request(server) @@ -6288,6 +6513,25 @@ describe('Users', () => { ); expect(userProfile).toBeFalsy(); }); + + it('Should return 200 if logged in as admin and deletes referer', async () => { + const response: APIResponse = + await request(server) + .delete(`${route}/${referer.id}`) + .set('authorization', `Bearer ${loggedInAdmin.token}`); + + expect(response.status).toBe(200); + expect(response.body.userDeleted).toBe(1); + expect(response.body.cvsDeleted).toBe(0); + + const user = await usersHelper.findUser(referer.id); + expect(user).toBeFalsy(); + + const userProfile = await userProfilesHelper.findOneProfileByUserId( + referer.id + ); + expect(userProfile).toBeFalsy(); + }); }); }); }); diff --git a/yarn.lock b/yarn.lock index feae4e95..b27683a1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -113,529 +113,554 @@ tslib "^2.6.2" "@aws-sdk/client-cloudfront@^3.142.0": - version "3.623.0" + version "3.682.0" + resolved "https://registry.npmjs.org/@aws-sdk/client-cloudfront/-/client-cloudfront-3.682.0.tgz" + integrity sha512-K4RXR+6mlQe4XEp+tBj0nkoiQ5yDPdef0StEfcJQ9NbwwJb2Vdm8ImeEkJjisPcc0h3D6NhaZHumYwWAKb3BpA== dependencies: "@aws-crypto/sha256-browser" "5.2.0" "@aws-crypto/sha256-js" "5.2.0" - "@aws-sdk/client-sso-oidc" "3.623.0" - "@aws-sdk/client-sts" "3.623.0" - "@aws-sdk/core" "3.623.0" - "@aws-sdk/credential-provider-node" "3.623.0" - "@aws-sdk/middleware-host-header" "3.620.0" - "@aws-sdk/middleware-logger" "3.609.0" - "@aws-sdk/middleware-recursion-detection" "3.620.0" - "@aws-sdk/middleware-user-agent" "3.620.0" - "@aws-sdk/region-config-resolver" "3.614.0" - "@aws-sdk/types" "3.609.0" - "@aws-sdk/util-endpoints" "3.614.0" - "@aws-sdk/util-user-agent-browser" "3.609.0" - "@aws-sdk/util-user-agent-node" "3.614.0" - "@aws-sdk/xml-builder" "3.609.0" - "@smithy/config-resolver" "^3.0.5" - "@smithy/core" "^2.3.2" - "@smithy/fetch-http-handler" "^3.2.4" - "@smithy/hash-node" "^3.0.3" - "@smithy/invalid-dependency" "^3.0.3" - "@smithy/middleware-content-length" "^3.0.5" - "@smithy/middleware-endpoint" "^3.1.0" - "@smithy/middleware-retry" "^3.0.14" - "@smithy/middleware-serde" "^3.0.3" - "@smithy/middleware-stack" "^3.0.3" - "@smithy/node-config-provider" "^3.1.4" - "@smithy/node-http-handler" "^3.1.4" - "@smithy/protocol-http" "^4.1.0" - "@smithy/smithy-client" "^3.1.12" - "@smithy/types" "^3.3.0" - "@smithy/url-parser" "^3.0.3" + "@aws-sdk/client-sso-oidc" "3.682.0" + "@aws-sdk/client-sts" "3.682.0" + "@aws-sdk/core" "3.679.0" + "@aws-sdk/credential-provider-node" "3.682.0" + "@aws-sdk/middleware-host-header" "3.679.0" + "@aws-sdk/middleware-logger" "3.679.0" + "@aws-sdk/middleware-recursion-detection" "3.679.0" + "@aws-sdk/middleware-user-agent" "3.682.0" + "@aws-sdk/region-config-resolver" "3.679.0" + "@aws-sdk/types" "3.679.0" + "@aws-sdk/util-endpoints" "3.679.0" + "@aws-sdk/util-user-agent-browser" "3.679.0" + "@aws-sdk/util-user-agent-node" "3.682.0" + "@aws-sdk/xml-builder" "3.679.0" + "@smithy/config-resolver" "^3.0.9" + "@smithy/core" "^2.4.8" + "@smithy/fetch-http-handler" "^3.2.9" + "@smithy/hash-node" "^3.0.7" + "@smithy/invalid-dependency" "^3.0.7" + "@smithy/middleware-content-length" "^3.0.9" + "@smithy/middleware-endpoint" "^3.1.4" + "@smithy/middleware-retry" "^3.0.23" + "@smithy/middleware-serde" "^3.0.7" + "@smithy/middleware-stack" "^3.0.7" + "@smithy/node-config-provider" "^3.1.8" + "@smithy/node-http-handler" "^3.2.4" + "@smithy/protocol-http" "^4.1.4" + "@smithy/smithy-client" "^3.4.0" + "@smithy/types" "^3.5.0" + "@smithy/url-parser" "^3.0.7" "@smithy/util-base64" "^3.0.0" "@smithy/util-body-length-browser" "^3.0.0" "@smithy/util-body-length-node" "^3.0.0" - "@smithy/util-defaults-mode-browser" "^3.0.14" - "@smithy/util-defaults-mode-node" "^3.0.14" - "@smithy/util-endpoints" "^2.0.5" - "@smithy/util-middleware" "^3.0.3" - "@smithy/util-retry" "^3.0.3" - "@smithy/util-stream" "^3.1.3" + "@smithy/util-defaults-mode-browser" "^3.0.23" + "@smithy/util-defaults-mode-node" "^3.0.23" + "@smithy/util-endpoints" "^2.1.3" + "@smithy/util-middleware" "^3.0.7" + "@smithy/util-retry" "^3.0.7" + "@smithy/util-stream" "^3.1.9" "@smithy/util-utf8" "^3.0.0" - "@smithy/util-waiter" "^3.1.2" + "@smithy/util-waiter" "^3.1.6" tslib "^2.6.2" "@aws-sdk/client-s3@^3.142.0": - version "3.623.0" + version "3.685.0" + resolved "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.685.0.tgz" + integrity sha512-ClvMeQHbLhWkpxnVymo4qWS5/yZcPXjorDbSday3joCWYWCSHTO409nWd+jx6eA4MKT/EY/uJ6ZBJRFfByKLuA== dependencies: "@aws-crypto/sha1-browser" "5.2.0" "@aws-crypto/sha256-browser" "5.2.0" "@aws-crypto/sha256-js" "5.2.0" - "@aws-sdk/client-sso-oidc" "3.623.0" - "@aws-sdk/client-sts" "3.623.0" - "@aws-sdk/core" "3.623.0" - "@aws-sdk/credential-provider-node" "3.623.0" - "@aws-sdk/middleware-bucket-endpoint" "3.620.0" - "@aws-sdk/middleware-expect-continue" "3.620.0" - "@aws-sdk/middleware-flexible-checksums" "3.620.0" - "@aws-sdk/middleware-host-header" "3.620.0" - "@aws-sdk/middleware-location-constraint" "3.609.0" - "@aws-sdk/middleware-logger" "3.609.0" - "@aws-sdk/middleware-recursion-detection" "3.620.0" - "@aws-sdk/middleware-sdk-s3" "3.622.0" - "@aws-sdk/middleware-signing" "3.620.0" - "@aws-sdk/middleware-ssec" "3.609.0" - "@aws-sdk/middleware-user-agent" "3.620.0" - "@aws-sdk/region-config-resolver" "3.614.0" - "@aws-sdk/signature-v4-multi-region" "3.622.0" - "@aws-sdk/types" "3.609.0" - "@aws-sdk/util-endpoints" "3.614.0" - "@aws-sdk/util-user-agent-browser" "3.609.0" - "@aws-sdk/util-user-agent-node" "3.614.0" - "@aws-sdk/xml-builder" "3.609.0" - "@smithy/config-resolver" "^3.0.5" - "@smithy/core" "^2.3.2" - "@smithy/eventstream-serde-browser" "^3.0.5" - "@smithy/eventstream-serde-config-resolver" "^3.0.3" - "@smithy/eventstream-serde-node" "^3.0.4" - "@smithy/fetch-http-handler" "^3.2.4" - "@smithy/hash-blob-browser" "^3.1.2" - "@smithy/hash-node" "^3.0.3" - "@smithy/hash-stream-node" "^3.1.2" - "@smithy/invalid-dependency" "^3.0.3" - "@smithy/md5-js" "^3.0.3" - "@smithy/middleware-content-length" "^3.0.5" - "@smithy/middleware-endpoint" "^3.1.0" - "@smithy/middleware-retry" "^3.0.14" - "@smithy/middleware-serde" "^3.0.3" - "@smithy/middleware-stack" "^3.0.3" - "@smithy/node-config-provider" "^3.1.4" - "@smithy/node-http-handler" "^3.1.4" - "@smithy/protocol-http" "^4.1.0" - "@smithy/smithy-client" "^3.1.12" - "@smithy/types" "^3.3.0" - "@smithy/url-parser" "^3.0.3" + "@aws-sdk/client-sso-oidc" "3.682.0" + "@aws-sdk/client-sts" "3.682.0" + "@aws-sdk/core" "3.679.0" + "@aws-sdk/credential-provider-node" "3.682.0" + "@aws-sdk/middleware-bucket-endpoint" "3.679.0" + "@aws-sdk/middleware-expect-continue" "3.679.0" + "@aws-sdk/middleware-flexible-checksums" "3.682.0" + "@aws-sdk/middleware-host-header" "3.679.0" + "@aws-sdk/middleware-location-constraint" "3.679.0" + "@aws-sdk/middleware-logger" "3.679.0" + "@aws-sdk/middleware-recursion-detection" "3.679.0" + "@aws-sdk/middleware-sdk-s3" "3.685.0" + "@aws-sdk/middleware-ssec" "3.679.0" + "@aws-sdk/middleware-user-agent" "3.682.0" + "@aws-sdk/region-config-resolver" "3.679.0" + "@aws-sdk/signature-v4-multi-region" "3.685.0" + "@aws-sdk/types" "3.679.0" + "@aws-sdk/util-endpoints" "3.679.0" + "@aws-sdk/util-user-agent-browser" "3.679.0" + "@aws-sdk/util-user-agent-node" "3.682.0" + "@aws-sdk/xml-builder" "3.679.0" + "@smithy/config-resolver" "^3.0.9" + "@smithy/core" "^2.4.8" + "@smithy/eventstream-serde-browser" "^3.0.10" + "@smithy/eventstream-serde-config-resolver" "^3.0.7" + "@smithy/eventstream-serde-node" "^3.0.9" + "@smithy/fetch-http-handler" "^3.2.9" + "@smithy/hash-blob-browser" "^3.1.6" + "@smithy/hash-node" "^3.0.7" + "@smithy/hash-stream-node" "^3.1.6" + "@smithy/invalid-dependency" "^3.0.7" + "@smithy/md5-js" "^3.0.7" + "@smithy/middleware-content-length" "^3.0.9" + "@smithy/middleware-endpoint" "^3.1.4" + "@smithy/middleware-retry" "^3.0.23" + "@smithy/middleware-serde" "^3.0.7" + "@smithy/middleware-stack" "^3.0.7" + "@smithy/node-config-provider" "^3.1.8" + "@smithy/node-http-handler" "^3.2.4" + "@smithy/protocol-http" "^4.1.4" + "@smithy/smithy-client" "^3.4.0" + "@smithy/types" "^3.5.0" + "@smithy/url-parser" "^3.0.7" "@smithy/util-base64" "^3.0.0" "@smithy/util-body-length-browser" "^3.0.0" "@smithy/util-body-length-node" "^3.0.0" - "@smithy/util-defaults-mode-browser" "^3.0.14" - "@smithy/util-defaults-mode-node" "^3.0.14" - "@smithy/util-endpoints" "^2.0.5" - "@smithy/util-retry" "^3.0.3" - "@smithy/util-stream" "^3.1.3" + "@smithy/util-defaults-mode-browser" "^3.0.23" + "@smithy/util-defaults-mode-node" "^3.0.23" + "@smithy/util-endpoints" "^2.1.3" + "@smithy/util-middleware" "^3.0.7" + "@smithy/util-retry" "^3.0.7" + "@smithy/util-stream" "^3.1.9" "@smithy/util-utf8" "^3.0.0" - "@smithy/util-waiter" "^3.1.2" + "@smithy/util-waiter" "^3.1.6" tslib "^2.6.2" -"@aws-sdk/client-sso-oidc@3.623.0": - version "3.623.0" +"@aws-sdk/client-sso-oidc@3.682.0": + version "3.682.0" + resolved "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.682.0.tgz" + integrity sha512-ZPZ7Y/r/w3nx/xpPzGSqSQsB090Xk5aZZOH+WBhTDn/pBEuim09BYXCLzvvxb7R7NnuoQdrTJiwimdJAhHl7ZQ== dependencies: "@aws-crypto/sha256-browser" "5.2.0" "@aws-crypto/sha256-js" "5.2.0" - "@aws-sdk/core" "3.623.0" - "@aws-sdk/credential-provider-node" "3.623.0" - "@aws-sdk/middleware-host-header" "3.620.0" - "@aws-sdk/middleware-logger" "3.609.0" - "@aws-sdk/middleware-recursion-detection" "3.620.0" - "@aws-sdk/middleware-user-agent" "3.620.0" - "@aws-sdk/region-config-resolver" "3.614.0" - "@aws-sdk/types" "3.609.0" - "@aws-sdk/util-endpoints" "3.614.0" - "@aws-sdk/util-user-agent-browser" "3.609.0" - "@aws-sdk/util-user-agent-node" "3.614.0" - "@smithy/config-resolver" "^3.0.5" - "@smithy/core" "^2.3.2" - "@smithy/fetch-http-handler" "^3.2.4" - "@smithy/hash-node" "^3.0.3" - "@smithy/invalid-dependency" "^3.0.3" - "@smithy/middleware-content-length" "^3.0.5" - "@smithy/middleware-endpoint" "^3.1.0" - "@smithy/middleware-retry" "^3.0.14" - "@smithy/middleware-serde" "^3.0.3" - "@smithy/middleware-stack" "^3.0.3" - "@smithy/node-config-provider" "^3.1.4" - "@smithy/node-http-handler" "^3.1.4" - "@smithy/protocol-http" "^4.1.0" - "@smithy/smithy-client" "^3.1.12" - "@smithy/types" "^3.3.0" - "@smithy/url-parser" "^3.0.3" + "@aws-sdk/core" "3.679.0" + "@aws-sdk/credential-provider-node" "3.682.0" + "@aws-sdk/middleware-host-header" "3.679.0" + "@aws-sdk/middleware-logger" "3.679.0" + "@aws-sdk/middleware-recursion-detection" "3.679.0" + "@aws-sdk/middleware-user-agent" "3.682.0" + "@aws-sdk/region-config-resolver" "3.679.0" + "@aws-sdk/types" "3.679.0" + "@aws-sdk/util-endpoints" "3.679.0" + "@aws-sdk/util-user-agent-browser" "3.679.0" + "@aws-sdk/util-user-agent-node" "3.682.0" + "@smithy/config-resolver" "^3.0.9" + "@smithy/core" "^2.4.8" + "@smithy/fetch-http-handler" "^3.2.9" + "@smithy/hash-node" "^3.0.7" + "@smithy/invalid-dependency" "^3.0.7" + "@smithy/middleware-content-length" "^3.0.9" + "@smithy/middleware-endpoint" "^3.1.4" + "@smithy/middleware-retry" "^3.0.23" + "@smithy/middleware-serde" "^3.0.7" + "@smithy/middleware-stack" "^3.0.7" + "@smithy/node-config-provider" "^3.1.8" + "@smithy/node-http-handler" "^3.2.4" + "@smithy/protocol-http" "^4.1.4" + "@smithy/smithy-client" "^3.4.0" + "@smithy/types" "^3.5.0" + "@smithy/url-parser" "^3.0.7" "@smithy/util-base64" "^3.0.0" "@smithy/util-body-length-browser" "^3.0.0" "@smithy/util-body-length-node" "^3.0.0" - "@smithy/util-defaults-mode-browser" "^3.0.14" - "@smithy/util-defaults-mode-node" "^3.0.14" - "@smithy/util-endpoints" "^2.0.5" - "@smithy/util-middleware" "^3.0.3" - "@smithy/util-retry" "^3.0.3" + "@smithy/util-defaults-mode-browser" "^3.0.23" + "@smithy/util-defaults-mode-node" "^3.0.23" + "@smithy/util-endpoints" "^2.1.3" + "@smithy/util-middleware" "^3.0.7" + "@smithy/util-retry" "^3.0.7" "@smithy/util-utf8" "^3.0.0" tslib "^2.6.2" -"@aws-sdk/client-sso@3.623.0": - version "3.623.0" +"@aws-sdk/client-sso@3.682.0": + version "3.682.0" + resolved "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.682.0.tgz" + integrity sha512-PYH9RFUMYLFl66HSBq4tIx6fHViMLkhJHTYJoJONpBs+Td+NwVJ895AdLtDsBIhMS0YseCbPpuyjUCJgsUrwUw== dependencies: "@aws-crypto/sha256-browser" "5.2.0" "@aws-crypto/sha256-js" "5.2.0" - "@aws-sdk/core" "3.623.0" - "@aws-sdk/middleware-host-header" "3.620.0" - "@aws-sdk/middleware-logger" "3.609.0" - "@aws-sdk/middleware-recursion-detection" "3.620.0" - "@aws-sdk/middleware-user-agent" "3.620.0" - "@aws-sdk/region-config-resolver" "3.614.0" - "@aws-sdk/types" "3.609.0" - "@aws-sdk/util-endpoints" "3.614.0" - "@aws-sdk/util-user-agent-browser" "3.609.0" - "@aws-sdk/util-user-agent-node" "3.614.0" - "@smithy/config-resolver" "^3.0.5" - "@smithy/core" "^2.3.2" - "@smithy/fetch-http-handler" "^3.2.4" - "@smithy/hash-node" "^3.0.3" - "@smithy/invalid-dependency" "^3.0.3" - "@smithy/middleware-content-length" "^3.0.5" - "@smithy/middleware-endpoint" "^3.1.0" - "@smithy/middleware-retry" "^3.0.14" - "@smithy/middleware-serde" "^3.0.3" - "@smithy/middleware-stack" "^3.0.3" - "@smithy/node-config-provider" "^3.1.4" - "@smithy/node-http-handler" "^3.1.4" - "@smithy/protocol-http" "^4.1.0" - "@smithy/smithy-client" "^3.1.12" - "@smithy/types" "^3.3.0" - "@smithy/url-parser" "^3.0.3" + "@aws-sdk/core" "3.679.0" + "@aws-sdk/middleware-host-header" "3.679.0" + "@aws-sdk/middleware-logger" "3.679.0" + "@aws-sdk/middleware-recursion-detection" "3.679.0" + "@aws-sdk/middleware-user-agent" "3.682.0" + "@aws-sdk/region-config-resolver" "3.679.0" + "@aws-sdk/types" "3.679.0" + "@aws-sdk/util-endpoints" "3.679.0" + "@aws-sdk/util-user-agent-browser" "3.679.0" + "@aws-sdk/util-user-agent-node" "3.682.0" + "@smithy/config-resolver" "^3.0.9" + "@smithy/core" "^2.4.8" + "@smithy/fetch-http-handler" "^3.2.9" + "@smithy/hash-node" "^3.0.7" + "@smithy/invalid-dependency" "^3.0.7" + "@smithy/middleware-content-length" "^3.0.9" + "@smithy/middleware-endpoint" "^3.1.4" + "@smithy/middleware-retry" "^3.0.23" + "@smithy/middleware-serde" "^3.0.7" + "@smithy/middleware-stack" "^3.0.7" + "@smithy/node-config-provider" "^3.1.8" + "@smithy/node-http-handler" "^3.2.4" + "@smithy/protocol-http" "^4.1.4" + "@smithy/smithy-client" "^3.4.0" + "@smithy/types" "^3.5.0" + "@smithy/url-parser" "^3.0.7" "@smithy/util-base64" "^3.0.0" "@smithy/util-body-length-browser" "^3.0.0" "@smithy/util-body-length-node" "^3.0.0" - "@smithy/util-defaults-mode-browser" "^3.0.14" - "@smithy/util-defaults-mode-node" "^3.0.14" - "@smithy/util-endpoints" "^2.0.5" - "@smithy/util-middleware" "^3.0.3" - "@smithy/util-retry" "^3.0.3" + "@smithy/util-defaults-mode-browser" "^3.0.23" + "@smithy/util-defaults-mode-node" "^3.0.23" + "@smithy/util-endpoints" "^2.1.3" + "@smithy/util-middleware" "^3.0.7" + "@smithy/util-retry" "^3.0.7" "@smithy/util-utf8" "^3.0.0" tslib "^2.6.2" -"@aws-sdk/client-sts@3.623.0": - version "3.623.0" +"@aws-sdk/client-sts@3.682.0": + version "3.682.0" + resolved "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.682.0.tgz" + integrity sha512-xKuo4HksZ+F8m9DOfx/ZuWNhaPuqZFPwwy0xqcBT6sWH7OAuBjv/fnpOTzyQhpVTWddlf+ECtMAMrxjxuOExGQ== dependencies: "@aws-crypto/sha256-browser" "5.2.0" "@aws-crypto/sha256-js" "5.2.0" - "@aws-sdk/client-sso-oidc" "3.623.0" - "@aws-sdk/core" "3.623.0" - "@aws-sdk/credential-provider-node" "3.623.0" - "@aws-sdk/middleware-host-header" "3.620.0" - "@aws-sdk/middleware-logger" "3.609.0" - "@aws-sdk/middleware-recursion-detection" "3.620.0" - "@aws-sdk/middleware-user-agent" "3.620.0" - "@aws-sdk/region-config-resolver" "3.614.0" - "@aws-sdk/types" "3.609.0" - "@aws-sdk/util-endpoints" "3.614.0" - "@aws-sdk/util-user-agent-browser" "3.609.0" - "@aws-sdk/util-user-agent-node" "3.614.0" - "@smithy/config-resolver" "^3.0.5" - "@smithy/core" "^2.3.2" - "@smithy/fetch-http-handler" "^3.2.4" - "@smithy/hash-node" "^3.0.3" - "@smithy/invalid-dependency" "^3.0.3" - "@smithy/middleware-content-length" "^3.0.5" - "@smithy/middleware-endpoint" "^3.1.0" - "@smithy/middleware-retry" "^3.0.14" - "@smithy/middleware-serde" "^3.0.3" - "@smithy/middleware-stack" "^3.0.3" - "@smithy/node-config-provider" "^3.1.4" - "@smithy/node-http-handler" "^3.1.4" - "@smithy/protocol-http" "^4.1.0" - "@smithy/smithy-client" "^3.1.12" - "@smithy/types" "^3.3.0" - "@smithy/url-parser" "^3.0.3" + "@aws-sdk/client-sso-oidc" "3.682.0" + "@aws-sdk/core" "3.679.0" + "@aws-sdk/credential-provider-node" "3.682.0" + "@aws-sdk/middleware-host-header" "3.679.0" + "@aws-sdk/middleware-logger" "3.679.0" + "@aws-sdk/middleware-recursion-detection" "3.679.0" + "@aws-sdk/middleware-user-agent" "3.682.0" + "@aws-sdk/region-config-resolver" "3.679.0" + "@aws-sdk/types" "3.679.0" + "@aws-sdk/util-endpoints" "3.679.0" + "@aws-sdk/util-user-agent-browser" "3.679.0" + "@aws-sdk/util-user-agent-node" "3.682.0" + "@smithy/config-resolver" "^3.0.9" + "@smithy/core" "^2.4.8" + "@smithy/fetch-http-handler" "^3.2.9" + "@smithy/hash-node" "^3.0.7" + "@smithy/invalid-dependency" "^3.0.7" + "@smithy/middleware-content-length" "^3.0.9" + "@smithy/middleware-endpoint" "^3.1.4" + "@smithy/middleware-retry" "^3.0.23" + "@smithy/middleware-serde" "^3.0.7" + "@smithy/middleware-stack" "^3.0.7" + "@smithy/node-config-provider" "^3.1.8" + "@smithy/node-http-handler" "^3.2.4" + "@smithy/protocol-http" "^4.1.4" + "@smithy/smithy-client" "^3.4.0" + "@smithy/types" "^3.5.0" + "@smithy/url-parser" "^3.0.7" "@smithy/util-base64" "^3.0.0" "@smithy/util-body-length-browser" "^3.0.0" "@smithy/util-body-length-node" "^3.0.0" - "@smithy/util-defaults-mode-browser" "^3.0.14" - "@smithy/util-defaults-mode-node" "^3.0.14" - "@smithy/util-endpoints" "^2.0.5" - "@smithy/util-middleware" "^3.0.3" - "@smithy/util-retry" "^3.0.3" + "@smithy/util-defaults-mode-browser" "^3.0.23" + "@smithy/util-defaults-mode-node" "^3.0.23" + "@smithy/util-endpoints" "^2.1.3" + "@smithy/util-middleware" "^3.0.7" + "@smithy/util-retry" "^3.0.7" "@smithy/util-utf8" "^3.0.0" tslib "^2.6.2" -"@aws-sdk/core@3.623.0": - version "3.623.0" - dependencies: - "@smithy/core" "^2.3.2" - "@smithy/node-config-provider" "^3.1.4" - "@smithy/protocol-http" "^4.1.0" - "@smithy/signature-v4" "^4.1.0" - "@smithy/smithy-client" "^3.1.12" - "@smithy/types" "^3.3.0" - "@smithy/util-middleware" "^3.0.3" +"@aws-sdk/core@3.679.0": + version "3.679.0" + resolved "https://registry.npmjs.org/@aws-sdk/core/-/core-3.679.0.tgz" + integrity sha512-CS6PWGX8l4v/xyvX8RtXnBisdCa5+URzKd0L6GvHChype9qKUVxO/Gg6N/y43Hvg7MNWJt9FBPNWIxUB+byJwg== + dependencies: + "@aws-sdk/types" "3.679.0" + "@smithy/core" "^2.4.8" + "@smithy/node-config-provider" "^3.1.8" + "@smithy/property-provider" "^3.1.7" + "@smithy/protocol-http" "^4.1.4" + "@smithy/signature-v4" "^4.2.0" + "@smithy/smithy-client" "^3.4.0" + "@smithy/types" "^3.5.0" + "@smithy/util-middleware" "^3.0.7" fast-xml-parser "4.4.1" tslib "^2.6.2" -"@aws-sdk/credential-provider-env@3.620.1": - version "3.620.1" - resolved "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.620.1.tgz" - integrity sha512-ExuILJ2qLW5ZO+rgkNRj0xiAipKT16Rk77buvPP8csR7kkCflT/gXTyzRe/uzIiETTxM7tr8xuO9MP/DQXqkfg== +"@aws-sdk/credential-provider-env@3.679.0": + version "3.679.0" + resolved "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.679.0.tgz" + integrity sha512-EdlTYbzMm3G7VUNAMxr9S1nC1qUNqhKlAxFU8E7cKsAe8Bp29CD5HAs3POc56AVo9GC4yRIS+/mtlZSmrckzUA== dependencies: - "@aws-sdk/types" "3.609.0" - "@smithy/property-provider" "^3.1.3" - "@smithy/types" "^3.3.0" + "@aws-sdk/core" "3.679.0" + "@aws-sdk/types" "3.679.0" + "@smithy/property-provider" "^3.1.7" + "@smithy/types" "^3.5.0" tslib "^2.6.2" -"@aws-sdk/credential-provider-http@3.622.0": - version "3.622.0" - resolved "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.622.0.tgz" - integrity sha512-VUHbr24Oll1RK3WR8XLUugLpgK9ZuxEm/NVeVqyFts1Ck9gsKpRg1x4eH7L7tW3SJ4TDEQNMbD7/7J+eoL2svg== - dependencies: - "@aws-sdk/types" "3.609.0" - "@smithy/fetch-http-handler" "^3.2.4" - "@smithy/node-http-handler" "^3.1.4" - "@smithy/property-provider" "^3.1.3" - "@smithy/protocol-http" "^4.1.0" - "@smithy/smithy-client" "^3.1.12" - "@smithy/types" "^3.3.0" - "@smithy/util-stream" "^3.1.3" +"@aws-sdk/credential-provider-http@3.679.0": + version "3.679.0" + resolved "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.679.0.tgz" + integrity sha512-ZoKLubW5DqqV1/2a3TSn+9sSKg0T8SsYMt1JeirnuLJF0mCoYFUaWMyvxxKuxPoqvUsaycxKru4GkpJ10ltNBw== + dependencies: + "@aws-sdk/core" "3.679.0" + "@aws-sdk/types" "3.679.0" + "@smithy/fetch-http-handler" "^3.2.9" + "@smithy/node-http-handler" "^3.2.4" + "@smithy/property-provider" "^3.1.7" + "@smithy/protocol-http" "^4.1.4" + "@smithy/smithy-client" "^3.4.0" + "@smithy/types" "^3.5.0" + "@smithy/util-stream" "^3.1.9" tslib "^2.6.2" -"@aws-sdk/credential-provider-ini@3.623.0": - version "3.623.0" - dependencies: - "@aws-sdk/credential-provider-env" "3.620.1" - "@aws-sdk/credential-provider-http" "3.622.0" - "@aws-sdk/credential-provider-process" "3.620.1" - "@aws-sdk/credential-provider-sso" "3.623.0" - "@aws-sdk/credential-provider-web-identity" "3.621.0" - "@aws-sdk/types" "3.609.0" - "@smithy/credential-provider-imds" "^3.2.0" - "@smithy/property-provider" "^3.1.3" - "@smithy/shared-ini-file-loader" "^3.1.4" - "@smithy/types" "^3.3.0" +"@aws-sdk/credential-provider-ini@3.682.0": + version "3.682.0" + resolved "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.682.0.tgz" + integrity sha512-6eqWeHdK6EegAxqDdiCi215nT3QZPwukgWAYuVxNfJ/5m0/P7fAzF+D5kKVgByUvGJEbq/FEL8Fw7OBe64AA+g== + dependencies: + "@aws-sdk/core" "3.679.0" + "@aws-sdk/credential-provider-env" "3.679.0" + "@aws-sdk/credential-provider-http" "3.679.0" + "@aws-sdk/credential-provider-process" "3.679.0" + "@aws-sdk/credential-provider-sso" "3.682.0" + "@aws-sdk/credential-provider-web-identity" "3.679.0" + "@aws-sdk/types" "3.679.0" + "@smithy/credential-provider-imds" "^3.2.4" + "@smithy/property-provider" "^3.1.7" + "@smithy/shared-ini-file-loader" "^3.1.8" + "@smithy/types" "^3.5.0" tslib "^2.6.2" -"@aws-sdk/credential-provider-node@3.623.0": - version "3.623.0" - dependencies: - "@aws-sdk/credential-provider-env" "3.620.1" - "@aws-sdk/credential-provider-http" "3.622.0" - "@aws-sdk/credential-provider-ini" "3.623.0" - "@aws-sdk/credential-provider-process" "3.620.1" - "@aws-sdk/credential-provider-sso" "3.623.0" - "@aws-sdk/credential-provider-web-identity" "3.621.0" - "@aws-sdk/types" "3.609.0" - "@smithy/credential-provider-imds" "^3.2.0" - "@smithy/property-provider" "^3.1.3" - "@smithy/shared-ini-file-loader" "^3.1.4" - "@smithy/types" "^3.3.0" +"@aws-sdk/credential-provider-node@3.682.0": + version "3.682.0" + resolved "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.682.0.tgz" + integrity sha512-HSmDqZcBVZrTctHCT9m++vdlDfJ1ARI218qmZa+TZzzOFNpKWy6QyHMEra45GB9GnkkMmV6unoDSPMuN0AqcMg== + dependencies: + "@aws-sdk/credential-provider-env" "3.679.0" + "@aws-sdk/credential-provider-http" "3.679.0" + "@aws-sdk/credential-provider-ini" "3.682.0" + "@aws-sdk/credential-provider-process" "3.679.0" + "@aws-sdk/credential-provider-sso" "3.682.0" + "@aws-sdk/credential-provider-web-identity" "3.679.0" + "@aws-sdk/types" "3.679.0" + "@smithy/credential-provider-imds" "^3.2.4" + "@smithy/property-provider" "^3.1.7" + "@smithy/shared-ini-file-loader" "^3.1.8" + "@smithy/types" "^3.5.0" tslib "^2.6.2" -"@aws-sdk/credential-provider-process@3.620.1": - version "3.620.1" - resolved "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.620.1.tgz" - integrity sha512-hWqFMidqLAkaV9G460+1at6qa9vySbjQKKc04p59OT7lZ5cO5VH5S4aI05e+m4j364MBROjjk2ugNvfNf/8ILg== +"@aws-sdk/credential-provider-process@3.679.0": + version "3.679.0" + resolved "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.679.0.tgz" + integrity sha512-u/p4TV8kQ0zJWDdZD4+vdQFTMhkDEJFws040Gm113VHa/Xo1SYOjbpvqeuFoz6VmM0bLvoOWjxB9MxnSQbwKpQ== dependencies: - "@aws-sdk/types" "3.609.0" - "@smithy/property-provider" "^3.1.3" - "@smithy/shared-ini-file-loader" "^3.1.4" - "@smithy/types" "^3.3.0" + "@aws-sdk/core" "3.679.0" + "@aws-sdk/types" "3.679.0" + "@smithy/property-provider" "^3.1.7" + "@smithy/shared-ini-file-loader" "^3.1.8" + "@smithy/types" "^3.5.0" tslib "^2.6.2" -"@aws-sdk/credential-provider-sso@3.623.0": - version "3.623.0" - dependencies: - "@aws-sdk/client-sso" "3.623.0" - "@aws-sdk/token-providers" "3.614.0" - "@aws-sdk/types" "3.609.0" - "@smithy/property-provider" "^3.1.3" - "@smithy/shared-ini-file-loader" "^3.1.4" - "@smithy/types" "^3.3.0" +"@aws-sdk/credential-provider-sso@3.682.0": + version "3.682.0" + resolved "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.682.0.tgz" + integrity sha512-h7IH1VsWgV6YAJSWWV6y8uaRjGqLY3iBpGZlXuTH/c236NMLaNv+WqCBLeBxkFGUb2WeQ+FUPEJDCD69rgLIkg== + dependencies: + "@aws-sdk/client-sso" "3.682.0" + "@aws-sdk/core" "3.679.0" + "@aws-sdk/token-providers" "3.679.0" + "@aws-sdk/types" "3.679.0" + "@smithy/property-provider" "^3.1.7" + "@smithy/shared-ini-file-loader" "^3.1.8" + "@smithy/types" "^3.5.0" tslib "^2.6.2" -"@aws-sdk/credential-provider-web-identity@3.621.0": - version "3.621.0" - resolved "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.621.0.tgz" - integrity sha512-w7ASSyfNvcx7+bYGep3VBgC3K6vEdLmlpjT7nSIHxxQf+WSdvy+HynwJosrpZax0sK5q0D1Jpn/5q+r5lwwW6w== +"@aws-sdk/credential-provider-web-identity@3.679.0": + version "3.679.0" + resolved "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.679.0.tgz" + integrity sha512-a74tLccVznXCaBefWPSysUcLXYJiSkeUmQGtalNgJ1vGkE36W5l/8czFiiowdWdKWz7+x6xf0w+Kjkjlj42Ung== dependencies: - "@aws-sdk/types" "3.609.0" - "@smithy/property-provider" "^3.1.3" - "@smithy/types" "^3.3.0" + "@aws-sdk/core" "3.679.0" + "@aws-sdk/types" "3.679.0" + "@smithy/property-provider" "^3.1.7" + "@smithy/types" "^3.5.0" tslib "^2.6.2" "@aws-sdk/lib-storage@^3.180.0": - version "3.623.0" + version "3.685.0" + resolved "https://registry.npmjs.org/@aws-sdk/lib-storage/-/lib-storage-3.685.0.tgz" + integrity sha512-6oGVXmtSr04xzq5XVBHj3yZTWJVfhUElEER5Bg9SrdoWTFRUsjnbOzqOAhYmlrEmEoGTtnZyj7wDI2EY7iCBTQ== dependencies: - "@smithy/abort-controller" "^3.1.1" - "@smithy/middleware-endpoint" "^3.1.0" - "@smithy/smithy-client" "^3.1.12" + "@smithy/abort-controller" "^3.1.5" + "@smithy/middleware-endpoint" "^3.1.4" + "@smithy/smithy-client" "^3.4.0" buffer "5.6.0" events "3.3.0" stream-browserify "3.0.0" tslib "^2.6.2" -"@aws-sdk/middleware-bucket-endpoint@3.620.0": - version "3.620.0" - resolved "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.620.0.tgz" - integrity sha512-eGLL0W6L3HDb3OACyetZYOWpHJ+gLo0TehQKeQyy2G8vTYXqNTeqYhuI6up9HVjBzU9eQiULVQETmgQs7TFaRg== +"@aws-sdk/middleware-bucket-endpoint@3.679.0": + version "3.679.0" + resolved "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.679.0.tgz" + integrity sha512-5EpiPhhGgnF+uJR4DzWUk6Lx3pOn9oM6JGXxeHsiynfoBfq7vHMleq+uABHHSQS+y7XzbyZ7x8tXNQlliMwOsg== dependencies: - "@aws-sdk/types" "3.609.0" - "@aws-sdk/util-arn-parser" "3.568.0" - "@smithy/node-config-provider" "^3.1.4" - "@smithy/protocol-http" "^4.1.0" - "@smithy/types" "^3.3.0" + "@aws-sdk/types" "3.679.0" + "@aws-sdk/util-arn-parser" "3.679.0" + "@smithy/node-config-provider" "^3.1.8" + "@smithy/protocol-http" "^4.1.4" + "@smithy/types" "^3.5.0" "@smithy/util-config-provider" "^3.0.0" tslib "^2.6.2" -"@aws-sdk/middleware-expect-continue@3.620.0": - version "3.620.0" - resolved "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.620.0.tgz" - integrity sha512-QXeRFMLfyQ31nAHLbiTLtk0oHzG9QLMaof5jIfqcUwnOkO8YnQdeqzakrg1Alpy/VQ7aqzIi8qypkBe2KXZz0A== +"@aws-sdk/middleware-expect-continue@3.679.0": + version "3.679.0" + resolved "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.679.0.tgz" + integrity sha512-nYsh9PdWrF4EahTRdXHGlNud82RPc508CNGdh1lAGfPU3tNveGfMBX3PcGBtPOse3p9ebNKRWVmUc9eXSjGvHA== dependencies: - "@aws-sdk/types" "3.609.0" - "@smithy/protocol-http" "^4.1.0" - "@smithy/types" "^3.3.0" + "@aws-sdk/types" "3.679.0" + "@smithy/protocol-http" "^4.1.4" + "@smithy/types" "^3.5.0" tslib "^2.6.2" -"@aws-sdk/middleware-flexible-checksums@3.620.0": - version "3.620.0" - resolved "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.620.0.tgz" - integrity sha512-ftz+NW7qka2sVuwnnO1IzBku5ccP+s5qZGeRTPgrKB7OzRW85gthvIo1vQR2w+OwHFk7WJbbhhWwbCbktnP4UA== +"@aws-sdk/middleware-flexible-checksums@3.682.0": + version "3.682.0" + resolved "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.682.0.tgz" + integrity sha512-5u1STth6iZUtAvPDO0NJVYKUX2EYKU7v84MYYaZ3O27HphRjFqDos0keL2KTnHn/KmMD68rM3yiUareWR8hnAQ== dependencies: "@aws-crypto/crc32" "5.2.0" "@aws-crypto/crc32c" "5.2.0" - "@aws-sdk/types" "3.609.0" + "@aws-sdk/core" "3.679.0" + "@aws-sdk/types" "3.679.0" "@smithy/is-array-buffer" "^3.0.0" - "@smithy/protocol-http" "^4.1.0" - "@smithy/types" "^3.3.0" + "@smithy/node-config-provider" "^3.1.8" + "@smithy/protocol-http" "^4.1.4" + "@smithy/types" "^3.5.0" + "@smithy/util-middleware" "^3.0.7" "@smithy/util-utf8" "^3.0.0" tslib "^2.6.2" -"@aws-sdk/middleware-host-header@3.620.0": - version "3.620.0" - resolved "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.620.0.tgz" - integrity sha512-VMtPEZwqYrII/oUkffYsNWY9PZ9xpNJpMgmyU0rlDQ25O1c0Hk3fJmZRe6pEkAJ0omD7kLrqGl1DUjQVxpd/Rg== +"@aws-sdk/middleware-host-header@3.679.0": + version "3.679.0" + resolved "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.679.0.tgz" + integrity sha512-y176HuQ8JRY3hGX8rQzHDSbCl9P5Ny9l16z4xmaiLo+Qfte7ee4Yr3yaAKd7GFoJ3/Mhud2XZ37fR015MfYl2w== dependencies: - "@aws-sdk/types" "3.609.0" - "@smithy/protocol-http" "^4.1.0" - "@smithy/types" "^3.3.0" + "@aws-sdk/types" "3.679.0" + "@smithy/protocol-http" "^4.1.4" + "@smithy/types" "^3.5.0" tslib "^2.6.2" -"@aws-sdk/middleware-location-constraint@3.609.0": - version "3.609.0" - resolved "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.609.0.tgz" - integrity sha512-xzsdoTkszGVqGVPjUmgoP7TORiByLueMHieI1fhQL888WPdqctwAx3ES6d/bA9Q/i8jnc6hs+Fjhy8UvBTkE9A== +"@aws-sdk/middleware-location-constraint@3.679.0": + version "3.679.0" + resolved "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.679.0.tgz" + integrity sha512-SA1C1D3XgoKTGxyNsOqd016ONpk46xJLWDgJUd00Zb21Ox5wYCoY6aDRKiaMRW+1VfCJdezs1Do3XLyIU9KxyA== dependencies: - "@aws-sdk/types" "3.609.0" - "@smithy/types" "^3.3.0" + "@aws-sdk/types" "3.679.0" + "@smithy/types" "^3.5.0" tslib "^2.6.2" -"@aws-sdk/middleware-logger@3.609.0": - version "3.609.0" - resolved "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.609.0.tgz" - integrity sha512-S62U2dy4jMDhDFDK5gZ4VxFdWzCtLzwbYyFZx2uvPYTECkepLUfzLic2BHg2Qvtu4QjX+oGE3P/7fwaGIsGNuQ== +"@aws-sdk/middleware-logger@3.679.0": + version "3.679.0" + resolved "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.679.0.tgz" + integrity sha512-0vet8InEj7nvIvGKk+ch7bEF5SyZ7Us9U7YTEgXPrBNStKeRUsgwRm0ijPWWd0a3oz2okaEwXsFl7G/vI0XiEA== dependencies: - "@aws-sdk/types" "3.609.0" - "@smithy/types" "^3.3.0" + "@aws-sdk/types" "3.679.0" + "@smithy/types" "^3.5.0" tslib "^2.6.2" -"@aws-sdk/middleware-recursion-detection@3.620.0": - version "3.620.0" - resolved "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.620.0.tgz" - integrity sha512-nh91S7aGK3e/o1ck64sA/CyoFw+gAYj2BDOnoNa6ouyCrVJED96ZXWbhye/fz9SgmNUZR2g7GdVpiLpMKZoI5w== +"@aws-sdk/middleware-recursion-detection@3.679.0": + version "3.679.0" + resolved "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.679.0.tgz" + integrity sha512-sQoAZFsQiW/LL3DfKMYwBoGjYDEnMbA9WslWN8xneCmBAwKo6IcSksvYs23PP8XMIoBGe2I2J9BSr654XWygTQ== dependencies: - "@aws-sdk/types" "3.609.0" - "@smithy/protocol-http" "^4.1.0" - "@smithy/types" "^3.3.0" + "@aws-sdk/types" "3.679.0" + "@smithy/protocol-http" "^4.1.4" + "@smithy/types" "^3.5.0" tslib "^2.6.2" -"@aws-sdk/middleware-sdk-s3@3.622.0": - version "3.622.0" - resolved "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.622.0.tgz" - integrity sha512-tX9wZ2ALx5Ez4bkY+SvSj6DpNZ6TmY4zlsVsdgV95LZFLjNwqnZkKkS+uKnsIyLBiBp6g92JVQwnUEIp7ov2Zw== - dependencies: - "@aws-sdk/types" "3.609.0" - "@aws-sdk/util-arn-parser" "3.568.0" - "@smithy/node-config-provider" "^3.1.4" - "@smithy/protocol-http" "^4.1.0" - "@smithy/signature-v4" "^4.1.0" - "@smithy/smithy-client" "^3.1.12" - "@smithy/types" "^3.3.0" +"@aws-sdk/middleware-sdk-s3@3.685.0": + version "3.685.0" + resolved "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.685.0.tgz" + integrity sha512-C4w92b3A99NbghrA2Ssw6y1RbDF3I3Bgzi2Izh0pXgyIoDiX0xs9bUs/FGYLK4uepYr78DAZY8DwEpzjWIXkSA== + dependencies: + "@aws-sdk/core" "3.679.0" + "@aws-sdk/types" "3.679.0" + "@aws-sdk/util-arn-parser" "3.679.0" + "@smithy/core" "^2.4.8" + "@smithy/node-config-provider" "^3.1.8" + "@smithy/protocol-http" "^4.1.4" + "@smithy/signature-v4" "^4.2.0" + "@smithy/smithy-client" "^3.4.0" + "@smithy/types" "^3.5.0" "@smithy/util-config-provider" "^3.0.0" - "@smithy/util-stream" "^3.1.3" + "@smithy/util-middleware" "^3.0.7" + "@smithy/util-stream" "^3.1.9" "@smithy/util-utf8" "^3.0.0" tslib "^2.6.2" -"@aws-sdk/middleware-signing@3.620.0": - version "3.620.0" - resolved "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.620.0.tgz" - integrity sha512-gxI7rubiaanUXaLfJ4NybERa9MGPNg2Ycl/OqANsozrBnR3Pw8vqy3EuVImQOyn2pJ2IFvl8ZPoSMHf4pX56FQ== +"@aws-sdk/middleware-ssec@3.679.0": + version "3.679.0" + resolved "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.679.0.tgz" + integrity sha512-4GNUxXbs1M71uFHRiCAZtN0/g23ogI9YjMe5isAuYMHXwDB3MhqF7usKf954mBP6tplvN44vYlbJ84faaLrTtg== dependencies: - "@aws-sdk/types" "3.609.0" - "@smithy/property-provider" "^3.1.3" - "@smithy/protocol-http" "^4.1.0" - "@smithy/signature-v4" "^4.1.0" - "@smithy/types" "^3.3.0" - "@smithy/util-middleware" "^3.0.3" + "@aws-sdk/types" "3.679.0" + "@smithy/types" "^3.5.0" tslib "^2.6.2" -"@aws-sdk/middleware-ssec@3.609.0": - version "3.609.0" - resolved "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.609.0.tgz" - integrity sha512-GZSD1s7+JswWOTamVap79QiDaIV7byJFssBW68GYjyRS5EBjNfwA/8s+6uE6g39R3ojyTbYOmvcANoZEhSULXg== - dependencies: - "@aws-sdk/types" "3.609.0" - "@smithy/types" "^3.3.0" +"@aws-sdk/middleware-user-agent@3.682.0": + version "3.682.0" + resolved "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.682.0.tgz" + integrity sha512-7TyvYR9HdGH1/Nq0eeApUTM4izB6rExiw87khVYuJwZHr6FmvIL1FsOVFro/4WlXa0lg4LiYOm/8H8dHv+fXTg== + dependencies: + "@aws-sdk/core" "3.679.0" + "@aws-sdk/types" "3.679.0" + "@aws-sdk/util-endpoints" "3.679.0" + "@smithy/core" "^2.4.8" + "@smithy/protocol-http" "^4.1.4" + "@smithy/types" "^3.5.0" tslib "^2.6.2" -"@aws-sdk/middleware-user-agent@3.620.0": - version "3.620.0" - resolved "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.620.0.tgz" - integrity sha512-bvS6etn+KsuL32ubY5D3xNof1qkenpbJXf/ugGXbg0n98DvDFQ/F+SMLxHgbnER5dsKYchNnhmtI6/FC3HFu/A== +"@aws-sdk/region-config-resolver@3.679.0": + version "3.679.0" + resolved "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.679.0.tgz" + integrity sha512-Ybx54P8Tg6KKq5ck7uwdjiKif7n/8g1x+V0V9uTjBjRWqaIgiqzXwKWoPj6NCNkE7tJNtqI4JrNxp/3S3HvmRw== dependencies: - "@aws-sdk/types" "3.609.0" - "@aws-sdk/util-endpoints" "3.614.0" - "@smithy/protocol-http" "^4.1.0" - "@smithy/types" "^3.3.0" - tslib "^2.6.2" - -"@aws-sdk/region-config-resolver@3.614.0": - version "3.614.0" - resolved "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.614.0.tgz" - integrity sha512-vDCeMXvic/LU0KFIUjpC3RiSTIkkvESsEfbVHiHH0YINfl8HnEqR5rj+L8+phsCeVg2+LmYwYxd5NRz4PHxt5g== - dependencies: - "@aws-sdk/types" "3.609.0" - "@smithy/node-config-provider" "^3.1.4" - "@smithy/types" "^3.3.0" + "@aws-sdk/types" "3.679.0" + "@smithy/node-config-provider" "^3.1.8" + "@smithy/types" "^3.5.0" "@smithy/util-config-provider" "^3.0.0" - "@smithy/util-middleware" "^3.0.3" + "@smithy/util-middleware" "^3.0.7" tslib "^2.6.2" "@aws-sdk/s3-request-presigner@^3.142.0": - version "3.623.0" - dependencies: - "@aws-sdk/signature-v4-multi-region" "3.622.0" - "@aws-sdk/types" "3.609.0" - "@aws-sdk/util-format-url" "3.609.0" - "@smithy/middleware-endpoint" "^3.1.0" - "@smithy/protocol-http" "^4.1.0" - "@smithy/smithy-client" "^3.1.12" - "@smithy/types" "^3.3.0" + version "3.685.0" + resolved "https://registry.npmjs.org/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.685.0.tgz" + integrity sha512-OTFQRXlAff/tRE2GfhqAgVaWkRSHUzj9ebvdd3979zcIELHY3kWBJ/XbefElXg1lhsumKdZl/gLpeA76GyQDPQ== + dependencies: + "@aws-sdk/signature-v4-multi-region" "3.685.0" + "@aws-sdk/types" "3.679.0" + "@aws-sdk/util-format-url" "3.679.0" + "@smithy/middleware-endpoint" "^3.1.4" + "@smithy/protocol-http" "^4.1.4" + "@smithy/smithy-client" "^3.4.0" + "@smithy/types" "^3.5.0" tslib "^2.6.2" -"@aws-sdk/signature-v4-multi-region@3.622.0": - version "3.622.0" - resolved "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.622.0.tgz" - integrity sha512-K7ddofVNzwTFRjmLZLfs/v+hiE9m5LguajHk8WULxXQgkcDI3nPgOfmMMGuslYohaQhRwW+ic+dzYlateLUudQ== +"@aws-sdk/signature-v4-multi-region@3.685.0": + version "3.685.0" + resolved "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.685.0.tgz" + integrity sha512-IHLwuAZGqfUWVrNqw0ugnBa7iL8uBP4x6A7bfBDXRXWCWjUCed/1/D//0lKDHwpFkV74fGW6KoBacnWSUlXmwA== dependencies: - "@aws-sdk/middleware-sdk-s3" "3.622.0" - "@aws-sdk/types" "3.609.0" - "@smithy/protocol-http" "^4.1.0" - "@smithy/signature-v4" "^4.1.0" - "@smithy/types" "^3.3.0" + "@aws-sdk/middleware-sdk-s3" "3.685.0" + "@aws-sdk/types" "3.679.0" + "@smithy/protocol-http" "^4.1.4" + "@smithy/signature-v4" "^4.2.0" + "@smithy/types" "^3.5.0" tslib "^2.6.2" -"@aws-sdk/token-providers@3.614.0": - version "3.614.0" - resolved "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.614.0.tgz" - integrity sha512-okItqyY6L9IHdxqs+Z116y5/nda7rHxLvROxtAJdLavWTYDydxrZstImNgGWTeVdmc0xX2gJCI77UYUTQWnhRw== +"@aws-sdk/token-providers@3.679.0": + version "3.679.0" + resolved "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.679.0.tgz" + integrity sha512-1/+Zso/x2jqgutKixYFQEGli0FELTgah6bm7aB+m2FAWH4Hz7+iMUsazg6nSWm714sG9G3h5u42Dmpvi9X6/hA== dependencies: - "@aws-sdk/types" "3.609.0" - "@smithy/property-provider" "^3.1.3" - "@smithy/shared-ini-file-loader" "^3.1.4" - "@smithy/types" "^3.3.0" + "@aws-sdk/types" "3.679.0" + "@smithy/property-provider" "^3.1.7" + "@smithy/shared-ini-file-loader" "^3.1.8" + "@smithy/types" "^3.5.0" tslib "^2.6.2" -"@aws-sdk/types@^3.222.0", "@aws-sdk/types@3.609.0": +"@aws-sdk/types@^3.222.0": version "3.609.0" resolved "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz" integrity sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q== @@ -643,31 +668,39 @@ "@smithy/types" "^3.3.0" tslib "^2.6.2" -"@aws-sdk/util-arn-parser@3.568.0": - version "3.568.0" - resolved "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.568.0.tgz" - integrity sha512-XUKJWWo+KOB7fbnPP0+g/o5Ulku/X53t7i/h+sPHr5xxYTJJ9CYnbToo95mzxe7xWvkLrsNtJ8L+MnNn9INs2w== +"@aws-sdk/types@3.679.0": + version "3.679.0" + resolved "https://registry.npmjs.org/@aws-sdk/types/-/types-3.679.0.tgz" + integrity sha512-NwVq8YvInxQdJ47+zz4fH3BRRLC6lL+WLkvr242PVBbUOLRyK/lkwHlfiKUoeVIMyK5NF+up6TRg71t/8Bny6Q== dependencies: + "@smithy/types" "^3.5.0" tslib "^2.6.2" -"@aws-sdk/util-endpoints@3.614.0": - version "3.614.0" - resolved "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.614.0.tgz" - integrity sha512-wK2cdrXHH4oz4IomV/yrGkftU9A+ITB6nFL+rxxyO78is2ifHJpFdV4aqk4LSkXYPi6CXWNru/Dqc7yiKXgJPw== +"@aws-sdk/util-arn-parser@3.679.0": + version "3.679.0" + resolved "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.679.0.tgz" + integrity sha512-CwzEbU8R8rq9bqUFryO50RFBlkfufV9UfMArHPWlo+lmsC+NlSluHQALoj6Jkq3zf5ppn1CN0c1DDLrEqdQUXg== dependencies: - "@aws-sdk/types" "3.609.0" - "@smithy/types" "^3.3.0" - "@smithy/util-endpoints" "^2.0.5" tslib "^2.6.2" -"@aws-sdk/util-format-url@3.609.0": - version "3.609.0" - resolved "https://registry.npmjs.org/@aws-sdk/util-format-url/-/util-format-url-3.609.0.tgz" - integrity sha512-fuk29BI/oLQlJ7pfm6iJ4gkEpHdavffAALZwXh9eaY1vQ0ip0aKfRTiNudPoJjyyahnz5yJ1HkmlcDitlzsOrQ== +"@aws-sdk/util-endpoints@3.679.0": + version "3.679.0" + resolved "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.679.0.tgz" + integrity sha512-YL6s4Y/1zC45OvddvgE139fjeWSKKPgLlnfrvhVL7alNyY9n7beR4uhoDpNrt5mI6sn9qiBF17790o+xLAXjjg== dependencies: - "@aws-sdk/types" "3.609.0" - "@smithy/querystring-builder" "^3.0.3" - "@smithy/types" "^3.3.0" + "@aws-sdk/types" "3.679.0" + "@smithy/types" "^3.5.0" + "@smithy/util-endpoints" "^2.1.3" + tslib "^2.6.2" + +"@aws-sdk/util-format-url@3.679.0": + version "3.679.0" + resolved "https://registry.npmjs.org/@aws-sdk/util-format-url/-/util-format-url-3.679.0.tgz" + integrity sha512-pqV1b/hJ/kumtF8AwObJ7bsGgs/2zuAdZtalSD8Pu4jdjOji3IBwP79giAHyhVwoXaMjkpG3mG4ldn9CVtzZJA== + dependencies: + "@aws-sdk/types" "3.679.0" + "@smithy/querystring-builder" "^3.0.7" + "@smithy/types" "^3.5.0" tslib "^2.6.2" "@aws-sdk/util-locate-window@^3.0.0": @@ -677,32 +710,33 @@ dependencies: tslib "^2.6.2" -"@aws-sdk/util-user-agent-browser@3.609.0": - version "3.609.0" - resolved "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.609.0.tgz" - integrity sha512-fojPU+mNahzQ0YHYBsx0ZIhmMA96H+ZIZ665ObU9tl+SGdbLneVZVikGve+NmHTQwHzwkFsZYYnVKAkreJLAtA== +"@aws-sdk/util-user-agent-browser@3.679.0": + version "3.679.0" + resolved "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.679.0.tgz" + integrity sha512-CusSm2bTBG1kFypcsqU8COhnYc6zltobsqs3nRrvYqYaOqtMnuE46K4XTWpnzKgwDejgZGOE+WYyprtAxrPvmQ== dependencies: - "@aws-sdk/types" "3.609.0" - "@smithy/types" "^3.3.0" + "@aws-sdk/types" "3.679.0" + "@smithy/types" "^3.5.0" bowser "^2.11.0" tslib "^2.6.2" -"@aws-sdk/util-user-agent-node@3.614.0": - version "3.614.0" - resolved "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.614.0.tgz" - integrity sha512-15ElZT88peoHnq5TEoEtZwoXTXRxNrk60TZNdpl/TUBJ5oNJ9Dqb5Z4ryb8ofN6nm9aFf59GVAerFDz8iUoHBA== +"@aws-sdk/util-user-agent-node@3.682.0": + version "3.682.0" + resolved "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.682.0.tgz" + integrity sha512-so5s+j0gPoTS0HM4HPL+G0ajk0T6cQAg8JXzRgvyiQAxqie+zGCZAV3VuVeMNWMVbzsgZl0pYZaatPFTLG/AxA== dependencies: - "@aws-sdk/types" "3.609.0" - "@smithy/node-config-provider" "^3.1.4" - "@smithy/types" "^3.3.0" + "@aws-sdk/middleware-user-agent" "3.682.0" + "@aws-sdk/types" "3.679.0" + "@smithy/node-config-provider" "^3.1.8" + "@smithy/types" "^3.5.0" tslib "^2.6.2" -"@aws-sdk/xml-builder@3.609.0": - version "3.609.0" - resolved "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.609.0.tgz" - integrity sha512-l9XxNcA4HX98rwCC2/KoiWcmEiRfZe4G+mYwDbCFT87JIMj6GBhLDkAzr/W8KAaA2IDr8Vc6J8fZPgVulxxfMA== +"@aws-sdk/xml-builder@3.679.0": + version "3.679.0" + resolved "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.679.0.tgz" + integrity sha512-nPmhVZb39ty5bcQ7mAwtjezBcsBqTYZ9A2D9v/lE92KCLdu5RhSkPH7O71ZqbZx1mUSg9fAOxHPiG79U5VlpLQ== dependencies: - "@smithy/types" "^3.3.0" + "@smithy/types" "^3.5.0" tslib "^2.6.2" "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.16.7", "@babel/code-frame@^7.24.7": @@ -1710,156 +1744,167 @@ p-queue "^6.6.1" p-retry "^4.0.0" -"@smithy/abort-controller@^3.1.1": - version "3.1.1" - resolved "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.1.tgz" - integrity sha512-MBJBiidoe+0cTFhyxT8g+9g7CeVccLM0IOKKUMCNQ1CNMJ/eIfoo0RTfVrXOONEI1UCN1W+zkiHSbzUNE9dZtQ== +"@smithy/abort-controller@^3.1.5", "@smithy/abort-controller@^3.1.6": + version "3.1.6" + resolved "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.6.tgz" + integrity sha512-0XuhuHQlEqbNQZp7QxxrFTdVWdwxch4vjxYgfInF91hZFkPxf9QDrdQka0KfxFMPqLNzSw0b95uGTrLliQUavQ== dependencies: - "@smithy/types" "^3.3.0" + "@smithy/types" "^3.6.0" tslib "^2.6.2" -"@smithy/chunked-blob-reader-native@^3.0.0": - version "3.0.0" - resolved "https://registry.npmjs.org/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-3.0.0.tgz" - integrity sha512-VDkpCYW+peSuM4zJip5WDfqvg2Mo/e8yxOv3VF1m11y7B8KKMKVFtmZWDe36Fvk8rGuWrPZHHXZ7rR7uM5yWyg== +"@smithy/chunked-blob-reader-native@^3.0.1": + version "3.0.1" + resolved "https://registry.npmjs.org/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-3.0.1.tgz" + integrity sha512-VEYtPvh5rs/xlyqpm5NRnfYLZn+q0SRPELbvBV+C/G7IQ+ouTuo+NKKa3ShG5OaFR8NYVMXls9hPYLTvIKKDrQ== dependencies: "@smithy/util-base64" "^3.0.0" tslib "^2.6.2" -"@smithy/chunked-blob-reader@^3.0.0": - version "3.0.0" - resolved "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-3.0.0.tgz" - integrity sha512-sbnURCwjF0gSToGlsBiAmd1lRCmSn72nu9axfJu5lIx6RUEgHu6GwTMbqCdhQSi0Pumcm5vFxsi9XWXb2mTaoA== +"@smithy/chunked-blob-reader@^4.0.0": + version "4.0.0" + resolved "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-4.0.0.tgz" + integrity sha512-jSqRnZvkT4egkq/7b6/QRCNXmmYVcHwnJldqJ3IhVpQE2atObVJ137xmGeuGFhjFUr8gCEVAOKwSY79OvpbDaQ== dependencies: tslib "^2.6.2" -"@smithy/config-resolver@^3.0.5": - version "3.0.5" - resolved "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-3.0.5.tgz" - integrity sha512-SkW5LxfkSI1bUC74OtfBbdz+grQXYiPYolyu8VfpLIjEoN/sHVBlLeGXMQ1vX4ejkgfv6sxVbQJ32yF2cl1veA== +"@smithy/config-resolver@^3.0.10", "@smithy/config-resolver@^3.0.9": + version "3.0.10" + resolved "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-3.0.10.tgz" + integrity sha512-Uh0Sz9gdUuz538nvkPiyv1DZRX9+D15EKDtnQP5rYVAzM/dnYk3P8cg73jcxyOitPgT3mE3OVj7ky7sibzHWkw== dependencies: - "@smithy/node-config-provider" "^3.1.4" - "@smithy/types" "^3.3.0" + "@smithy/node-config-provider" "^3.1.9" + "@smithy/types" "^3.6.0" "@smithy/util-config-provider" "^3.0.0" - "@smithy/util-middleware" "^3.0.3" + "@smithy/util-middleware" "^3.0.8" tslib "^2.6.2" -"@smithy/core@^2.3.2": - version "2.3.2" - resolved "https://registry.npmjs.org/@smithy/core/-/core-2.3.2.tgz" - integrity sha512-in5wwt6chDBcUv1Lw1+QzZxN9fBffi+qOixfb65yK4sDuKG7zAUO9HAFqmVzsZM3N+3tTyvZjtnDXePpvp007Q== +"@smithy/core@^2.4.8", "@smithy/core@^2.5.1": + version "2.5.1" + resolved "https://registry.npmjs.org/@smithy/core/-/core-2.5.1.tgz" + integrity sha512-DujtuDA7BGEKExJ05W5OdxCoyekcKT3Rhg1ZGeiUWaz2BJIWXjZmsG/DIP4W48GHno7AQwRsaCb8NcBgH3QZpg== dependencies: - "@smithy/middleware-endpoint" "^3.1.0" - "@smithy/middleware-retry" "^3.0.14" - "@smithy/middleware-serde" "^3.0.3" - "@smithy/protocol-http" "^4.1.0" - "@smithy/smithy-client" "^3.1.12" - "@smithy/types" "^3.3.0" - "@smithy/util-middleware" "^3.0.3" + "@smithy/middleware-serde" "^3.0.8" + "@smithy/protocol-http" "^4.1.5" + "@smithy/types" "^3.6.0" + "@smithy/util-body-length-browser" "^3.0.0" + "@smithy/util-middleware" "^3.0.8" + "@smithy/util-stream" "^3.2.1" + "@smithy/util-utf8" "^3.0.0" tslib "^2.6.2" -"@smithy/credential-provider-imds@^3.2.0": - version "3.2.0" - resolved "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-3.2.0.tgz" - integrity sha512-0SCIzgd8LYZ9EJxUjLXBmEKSZR/P/w6l7Rz/pab9culE/RWuqelAKGJvn5qUOl8BgX8Yj5HWM50A5hiB/RzsgA== +"@smithy/credential-provider-imds@^3.2.4", "@smithy/credential-provider-imds@^3.2.5": + version "3.2.5" + resolved "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-3.2.5.tgz" + integrity sha512-4FTQGAsuwqTzVMmiRVTn0RR9GrbRfkP0wfu/tXWVHd2LgNpTY0uglQpIScXK4NaEyXbB3JmZt8gfVqO50lP8wg== dependencies: - "@smithy/node-config-provider" "^3.1.4" - "@smithy/property-provider" "^3.1.3" - "@smithy/types" "^3.3.0" - "@smithy/url-parser" "^3.0.3" + "@smithy/node-config-provider" "^3.1.9" + "@smithy/property-provider" "^3.1.8" + "@smithy/types" "^3.6.0" + "@smithy/url-parser" "^3.0.8" tslib "^2.6.2" -"@smithy/eventstream-codec@^3.1.2": - version "3.1.2" - resolved "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-3.1.2.tgz" - integrity sha512-0mBcu49JWt4MXhrhRAlxASNy0IjDRFU+aWNDRal9OtUJvJNiwDuyKMUONSOjLjSCeGwZaE0wOErdqULer8r7yw== +"@smithy/eventstream-codec@^3.1.7": + version "3.1.7" + resolved "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-3.1.7.tgz" + integrity sha512-kVSXScIiRN7q+s1x7BrQtZ1Aa9hvvP9FeCqCdBxv37GimIHgBCOnZ5Ip80HLt0DhnAKpiobFdGqTFgbaJNrazA== dependencies: "@aws-crypto/crc32" "5.2.0" - "@smithy/types" "^3.3.0" + "@smithy/types" "^3.6.0" "@smithy/util-hex-encoding" "^3.0.0" tslib "^2.6.2" -"@smithy/eventstream-serde-browser@^3.0.5": - version "3.0.5" - resolved "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-3.0.5.tgz" - integrity sha512-dEyiUYL/ekDfk+2Ra4GxV+xNnFoCmk1nuIXg+fMChFTrM2uI/1r9AdiTYzPqgb72yIv/NtAj6C3dG//1wwgakQ== +"@smithy/eventstream-serde-browser@^3.0.10": + version "3.0.11" + resolved "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-3.0.11.tgz" + integrity sha512-Pd1Wnq3CQ/v2SxRifDUihvpXzirJYbbtXfEnnLV/z0OGCTx/btVX74P86IgrZkjOydOASBGXdPpupYQI+iO/6A== dependencies: - "@smithy/eventstream-serde-universal" "^3.0.4" - "@smithy/types" "^3.3.0" + "@smithy/eventstream-serde-universal" "^3.0.10" + "@smithy/types" "^3.6.0" tslib "^2.6.2" -"@smithy/eventstream-serde-config-resolver@^3.0.3": - version "3.0.3" - resolved "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-3.0.3.tgz" - integrity sha512-NVTYjOuYpGfrN/VbRQgn31x73KDLfCXCsFdad8DiIc3IcdxL+dYA9zEQPyOP7Fy2QL8CPy2WE4WCUD+ZsLNfaQ== +"@smithy/eventstream-serde-config-resolver@^3.0.7": + version "3.0.8" + resolved "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-3.0.8.tgz" + integrity sha512-zkFIG2i1BLbfoGQnf1qEeMqX0h5qAznzaZmMVNnvPZz9J5AWBPkOMckZWPedGUPcVITacwIdQXoPcdIQq5FRcg== dependencies: - "@smithy/types" "^3.3.0" + "@smithy/types" "^3.6.0" tslib "^2.6.2" -"@smithy/eventstream-serde-node@^3.0.4": - version "3.0.4" - resolved "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-3.0.4.tgz" - integrity sha512-mjlG0OzGAYuUpdUpflfb9zyLrBGgmQmrobNT8b42ZTsGv/J03+t24uhhtVEKG/b2jFtPIHF74Bq+VUtbzEKOKg== +"@smithy/eventstream-serde-node@^3.0.9": + version "3.0.10" + resolved "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-3.0.10.tgz" + integrity sha512-hjpU1tIsJ9qpcoZq9zGHBJPBOeBGYt+n8vfhDwnITPhEre6APrvqq/y3XMDEGUT2cWQ4ramNqBPRbx3qn55rhw== dependencies: - "@smithy/eventstream-serde-universal" "^3.0.4" - "@smithy/types" "^3.3.0" + "@smithy/eventstream-serde-universal" "^3.0.10" + "@smithy/types" "^3.6.0" tslib "^2.6.2" -"@smithy/eventstream-serde-universal@^3.0.4": - version "3.0.4" - resolved "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-3.0.4.tgz" - integrity sha512-Od9dv8zh3PgOD7Vj4T3HSuox16n0VG8jJIM2gvKASL6aCtcS8CfHZDWe1Ik3ZXW6xBouU+45Q5wgoliWDZiJ0A== +"@smithy/eventstream-serde-universal@^3.0.10": + version "3.0.10" + resolved "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-3.0.10.tgz" + integrity sha512-ewG1GHbbqsFZ4asaq40KmxCmXO+AFSM1b+DcO2C03dyJj/ZH71CiTg853FSE/3SHK9q3jiYQIFjlGSwfxQ9kww== dependencies: - "@smithy/eventstream-codec" "^3.1.2" - "@smithy/types" "^3.3.0" + "@smithy/eventstream-codec" "^3.1.7" + "@smithy/types" "^3.6.0" tslib "^2.6.2" -"@smithy/fetch-http-handler@^3.2.4": - version "3.2.4" - resolved "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-3.2.4.tgz" - integrity sha512-kBprh5Gs5h7ug4nBWZi1FZthdqSM+T7zMmsZxx0IBvWUn7dK3diz2SHn7Bs4dQGFDk8plDv375gzenDoNwrXjg== +"@smithy/fetch-http-handler@^3.2.9": + version "3.2.9" + resolved "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-3.2.9.tgz" + integrity sha512-hYNVQOqhFQ6vOpenifFME546f0GfJn2OiQ3M0FDmuUu8V/Uiwy2wej7ZXxFBNqdx0R5DZAqWM1l6VRhGz8oE6A== dependencies: - "@smithy/protocol-http" "^4.1.0" - "@smithy/querystring-builder" "^3.0.3" - "@smithy/types" "^3.3.0" + "@smithy/protocol-http" "^4.1.4" + "@smithy/querystring-builder" "^3.0.7" + "@smithy/types" "^3.5.0" "@smithy/util-base64" "^3.0.0" tslib "^2.6.2" -"@smithy/hash-blob-browser@^3.1.2": - version "3.1.2" - resolved "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-3.1.2.tgz" - integrity sha512-hAbfqN2UbISltakCC2TP0kx4LqXBttEv2MqSPE98gVuDFMf05lU+TpC41QtqGP3Ff5A3GwZMPfKnEy0VmEUpmg== +"@smithy/fetch-http-handler@^4.0.0": + version "4.0.0" + resolved "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-4.0.0.tgz" + integrity sha512-MLb1f5tbBO2X6K4lMEKJvxeLooyg7guq48C2zKr4qM7F2Gpkz4dc+hdSgu77pCJ76jVqFBjZczHYAs6dp15N+g== dependencies: - "@smithy/chunked-blob-reader" "^3.0.0" - "@smithy/chunked-blob-reader-native" "^3.0.0" - "@smithy/types" "^3.3.0" + "@smithy/protocol-http" "^4.1.5" + "@smithy/querystring-builder" "^3.0.8" + "@smithy/types" "^3.6.0" + "@smithy/util-base64" "^3.0.0" tslib "^2.6.2" -"@smithy/hash-node@^3.0.3": - version "3.0.3" - resolved "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-3.0.3.tgz" - integrity sha512-2ctBXpPMG+B3BtWSGNnKELJ7SH9e4TNefJS0cd2eSkOOROeBnnVBnAy9LtJ8tY4vUEoe55N4CNPxzbWvR39iBw== +"@smithy/hash-blob-browser@^3.1.6": + version "3.1.7" + resolved "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-3.1.7.tgz" + integrity sha512-4yNlxVNJifPM5ThaA5HKnHkn7JhctFUHvcaz6YXxHlYOSIrzI6VKQPTN8Gs1iN5nqq9iFcwIR9THqchUCouIfg== dependencies: - "@smithy/types" "^3.3.0" + "@smithy/chunked-blob-reader" "^4.0.0" + "@smithy/chunked-blob-reader-native" "^3.0.1" + "@smithy/types" "^3.6.0" + tslib "^2.6.2" + +"@smithy/hash-node@^3.0.7": + version "3.0.8" + resolved "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-3.0.8.tgz" + integrity sha512-tlNQYbfpWXHimHqrvgo14DrMAgUBua/cNoz9fMYcDmYej7MAmUcjav/QKQbFc3NrcPxeJ7QClER4tWZmfwoPng== + dependencies: + "@smithy/types" "^3.6.0" "@smithy/util-buffer-from" "^3.0.0" "@smithy/util-utf8" "^3.0.0" tslib "^2.6.2" -"@smithy/hash-stream-node@^3.1.2": - version "3.1.2" - resolved "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-3.1.2.tgz" - integrity sha512-PBgDMeEdDzi6JxKwbfBtwQG9eT9cVwsf0dZzLXoJF4sHKHs5HEo/3lJWpn6jibfJwT34I1EBXpBnZE8AxAft6g== +"@smithy/hash-stream-node@^3.1.6": + version "3.1.7" + resolved "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-3.1.7.tgz" + integrity sha512-xMAsvJ3hLG63lsBVi1Hl6BBSfhd8/Qnp8fC06kjOpJvyyCEXdwHITa5Kvdsk6gaAXLhbZMhQMIGvgUbfnJDP6Q== dependencies: - "@smithy/types" "^3.3.0" + "@smithy/types" "^3.6.0" "@smithy/util-utf8" "^3.0.0" tslib "^2.6.2" -"@smithy/invalid-dependency@^3.0.3": - version "3.0.3" - resolved "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-3.0.3.tgz" - integrity sha512-ID1eL/zpDULmHJbflb864k72/SNOZCADRc9i7Exq3RUNJw6raWUSlFEQ+3PX3EYs++bTxZB2dE9mEHTQLv61tw== +"@smithy/invalid-dependency@^3.0.7": + version "3.0.8" + resolved "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-3.0.8.tgz" + integrity sha512-7Qynk6NWtTQhnGTTZwks++nJhQ1O54Mzi7fz4PqZOiYXb4Z1Flpb2yRvdALoggTS8xjtohWUM+RygOtB30YL3Q== dependencies: - "@smithy/types" "^3.3.0" + "@smithy/types" "^3.6.0" tslib "^2.6.2" "@smithy/is-array-buffer@^2.2.0": @@ -1876,161 +1921,163 @@ dependencies: tslib "^2.6.2" -"@smithy/md5-js@^3.0.3": - version "3.0.3" - resolved "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-3.0.3.tgz" - integrity sha512-O/SAkGVwpWmelpj/8yDtsaVe6sINHLB1q8YE/+ZQbDxIw3SRLbTZuRaI10K12sVoENdnHqzPp5i3/H+BcZ3m3Q== +"@smithy/md5-js@^3.0.7": + version "3.0.8" + resolved "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-3.0.8.tgz" + integrity sha512-LwApfTK0OJ/tCyNUXqnWCKoE2b4rDSr4BJlDAVCkiWYeHESr+y+d5zlAanuLW6fnitVJRD/7d9/kN/ZM9Su4mA== dependencies: - "@smithy/types" "^3.3.0" + "@smithy/types" "^3.6.0" "@smithy/util-utf8" "^3.0.0" tslib "^2.6.2" -"@smithy/middleware-content-length@^3.0.5": - version "3.0.5" - resolved "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-3.0.5.tgz" - integrity sha512-ILEzC2eyxx6ncej3zZSwMpB5RJ0zuqH7eMptxC4KN3f+v9bqT8ohssKbhNR78k/2tWW+KS5Spw+tbPF4Ejyqvw== +"@smithy/middleware-content-length@^3.0.9": + version "3.0.10" + resolved "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-3.0.10.tgz" + integrity sha512-T4dIdCs1d/+/qMpwhJ1DzOhxCZjZHbHazEPJWdB4GDi2HjIZllVzeBEcdJUN0fomV8DURsgOyrbEUzg3vzTaOg== dependencies: - "@smithy/protocol-http" "^4.1.0" - "@smithy/types" "^3.3.0" + "@smithy/protocol-http" "^4.1.5" + "@smithy/types" "^3.6.0" tslib "^2.6.2" -"@smithy/middleware-endpoint@^3.1.0": - version "3.1.0" - resolved "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-3.1.0.tgz" - integrity sha512-5y5aiKCEwg9TDPB4yFE7H6tYvGFf1OJHNczeY10/EFF8Ir8jZbNntQJxMWNfeQjC1mxPsaQ6mR9cvQbf+0YeMw== - dependencies: - "@smithy/middleware-serde" "^3.0.3" - "@smithy/node-config-provider" "^3.1.4" - "@smithy/shared-ini-file-loader" "^3.1.4" - "@smithy/types" "^3.3.0" - "@smithy/url-parser" "^3.0.3" - "@smithy/util-middleware" "^3.0.3" +"@smithy/middleware-endpoint@^3.1.4", "@smithy/middleware-endpoint@^3.2.1": + version "3.2.1" + resolved "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-3.2.1.tgz" + integrity sha512-wWO3xYmFm6WRW8VsEJ5oU6h7aosFXfszlz3Dj176pTij6o21oZnzkCLzShfmRaaCHDkBXWBdO0c4sQAvLFP6zA== + dependencies: + "@smithy/core" "^2.5.1" + "@smithy/middleware-serde" "^3.0.8" + "@smithy/node-config-provider" "^3.1.9" + "@smithy/shared-ini-file-loader" "^3.1.9" + "@smithy/types" "^3.6.0" + "@smithy/url-parser" "^3.0.8" + "@smithy/util-middleware" "^3.0.8" tslib "^2.6.2" -"@smithy/middleware-retry@^3.0.14": - version "3.0.14" - resolved "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-3.0.14.tgz" - integrity sha512-7ZaWZJOjUxa5hgmuMspyt8v/zVsh0GXYuF7OvCmdcbVa/xbnKQoYC+uYKunAqRGTkxjOyuOCw9rmFUFOqqC0eQ== - dependencies: - "@smithy/node-config-provider" "^3.1.4" - "@smithy/protocol-http" "^4.1.0" - "@smithy/service-error-classification" "^3.0.3" - "@smithy/smithy-client" "^3.1.12" - "@smithy/types" "^3.3.0" - "@smithy/util-middleware" "^3.0.3" - "@smithy/util-retry" "^3.0.3" +"@smithy/middleware-retry@^3.0.23": + version "3.0.25" + resolved "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-3.0.25.tgz" + integrity sha512-m1F70cPaMBML4HiTgCw5I+jFNtjgz5z5UdGnUbG37vw6kh4UvizFYjqJGHvicfgKMkDL6mXwyPp5mhZg02g5sg== + dependencies: + "@smithy/node-config-provider" "^3.1.9" + "@smithy/protocol-http" "^4.1.5" + "@smithy/service-error-classification" "^3.0.8" + "@smithy/smithy-client" "^3.4.2" + "@smithy/types" "^3.6.0" + "@smithy/util-middleware" "^3.0.8" + "@smithy/util-retry" "^3.0.8" tslib "^2.6.2" uuid "^9.0.1" -"@smithy/middleware-serde@^3.0.3": - version "3.0.3" - resolved "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-3.0.3.tgz" - integrity sha512-puUbyJQBcg9eSErFXjKNiGILJGtiqmuuNKEYNYfUD57fUl4i9+mfmThtQhvFXU0hCVG0iEJhvQUipUf+/SsFdA== +"@smithy/middleware-serde@^3.0.7", "@smithy/middleware-serde@^3.0.8": + version "3.0.8" + resolved "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-3.0.8.tgz" + integrity sha512-Xg2jK9Wc/1g/MBMP/EUn2DLspN8LNt+GMe7cgF+Ty3vl+Zvu+VeZU5nmhveU+H8pxyTsjrAkci8NqY6OuvZnjA== dependencies: - "@smithy/types" "^3.3.0" + "@smithy/types" "^3.6.0" tslib "^2.6.2" -"@smithy/middleware-stack@^3.0.3": - version "3.0.3" - resolved "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-3.0.3.tgz" - integrity sha512-r4klY9nFudB0r9UdSMaGSyjyQK5adUyPnQN/ZM6M75phTxOdnc/AhpvGD1fQUvgmqjQEBGCwpnPbDm8pH5PapA== +"@smithy/middleware-stack@^3.0.7", "@smithy/middleware-stack@^3.0.8": + version "3.0.8" + resolved "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-3.0.8.tgz" + integrity sha512-d7ZuwvYgp1+3682Nx0MD3D/HtkmZd49N3JUndYWQXfRZrYEnCWYc8BHcNmVsPAp9gKvlurdg/mubE6b/rPS9MA== dependencies: - "@smithy/types" "^3.3.0" + "@smithy/types" "^3.6.0" tslib "^2.6.2" -"@smithy/node-config-provider@^3.1.4": - version "3.1.4" - resolved "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-3.1.4.tgz" - integrity sha512-YvnElQy8HR4vDcAjoy7Xkx9YT8xZP4cBXcbJSgm/kxmiQu08DwUwj8rkGnyoJTpfl/3xYHH+d8zE+eHqoDCSdQ== +"@smithy/node-config-provider@^3.1.8", "@smithy/node-config-provider@^3.1.9": + version "3.1.9" + resolved "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-3.1.9.tgz" + integrity sha512-qRHoah49QJ71eemjuS/WhUXB+mpNtwHRWQr77J/m40ewBVVwvo52kYAmb7iuaECgGTTcYxHS4Wmewfwy++ueew== dependencies: - "@smithy/property-provider" "^3.1.3" - "@smithy/shared-ini-file-loader" "^3.1.4" - "@smithy/types" "^3.3.0" + "@smithy/property-provider" "^3.1.8" + "@smithy/shared-ini-file-loader" "^3.1.9" + "@smithy/types" "^3.6.0" tslib "^2.6.2" -"@smithy/node-http-handler@^3.1.4": - version "3.1.4" - resolved "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.1.4.tgz" - integrity sha512-+UmxgixgOr/yLsUxcEKGH0fMNVteJFGkmRltYFHnBMlogyFdpzn2CwqWmxOrfJELhV34v0WSlaqG1UtE1uXlJg== +"@smithy/node-http-handler@^3.2.4", "@smithy/node-http-handler@^3.2.5": + version "3.2.5" + resolved "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.2.5.tgz" + integrity sha512-PkOwPNeKdvX/jCpn0A8n9/TyoxjGZB8WVoJmm9YzsnAgggTj4CrjpRHlTQw7dlLZ320n1mY1y+nTRUDViKi/3w== dependencies: - "@smithy/abort-controller" "^3.1.1" - "@smithy/protocol-http" "^4.1.0" - "@smithy/querystring-builder" "^3.0.3" - "@smithy/types" "^3.3.0" + "@smithy/abort-controller" "^3.1.6" + "@smithy/protocol-http" "^4.1.5" + "@smithy/querystring-builder" "^3.0.8" + "@smithy/types" "^3.6.0" tslib "^2.6.2" -"@smithy/property-provider@^3.1.3": - version "3.1.3" - resolved "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-3.1.3.tgz" - integrity sha512-zahyOVR9Q4PEoguJ/NrFP4O7SMAfYO1HLhB18M+q+Z4KFd4V2obiMnlVoUFzFLSPeVt1POyNWneHHrZaTMoc/g== +"@smithy/property-provider@^3.1.7", "@smithy/property-provider@^3.1.8": + version "3.1.8" + resolved "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-3.1.8.tgz" + integrity sha512-ukNUyo6rHmusG64lmkjFeXemwYuKge1BJ8CtpVKmrxQxc6rhUX0vebcptFA9MmrGsnLhwnnqeH83VTU9hwOpjA== dependencies: - "@smithy/types" "^3.3.0" + "@smithy/types" "^3.6.0" tslib "^2.6.2" -"@smithy/protocol-http@^4.1.0": - version "4.1.0" - resolved "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz" - integrity sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA== +"@smithy/protocol-http@^4.1.4", "@smithy/protocol-http@^4.1.5": + version "4.1.5" + resolved "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.5.tgz" + integrity sha512-hsjtwpIemmCkm3ZV5fd/T0bPIugW1gJXwZ/hpuVubt2hEUApIoUTrf6qIdh9MAWlw0vjMrA1ztJLAwtNaZogvg== dependencies: - "@smithy/types" "^3.3.0" + "@smithy/types" "^3.6.0" tslib "^2.6.2" -"@smithy/querystring-builder@^3.0.3": - version "3.0.3" - resolved "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.3.tgz" - integrity sha512-vyWckeUeesFKzCDaRwWLUA1Xym9McaA6XpFfAK5qI9DKJ4M33ooQGqvM4J+LalH4u/Dq9nFiC8U6Qn1qi0+9zw== +"@smithy/querystring-builder@^3.0.7", "@smithy/querystring-builder@^3.0.8": + version "3.0.8" + resolved "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.8.tgz" + integrity sha512-btYxGVqFUARbUrN6VhL9c3dnSviIwBYD9Rz1jHuN1hgh28Fpv2xjU1HeCeDJX68xctz7r4l1PBnFhGg1WBBPuA== dependencies: - "@smithy/types" "^3.3.0" + "@smithy/types" "^3.6.0" "@smithy/util-uri-escape" "^3.0.0" tslib "^2.6.2" -"@smithy/querystring-parser@^3.0.3": - version "3.0.3" - resolved "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-3.0.3.tgz" - integrity sha512-zahM1lQv2YjmznnfQsWbYojFe55l0SLG/988brlLv1i8z3dubloLF+75ATRsqPBboUXsW6I9CPGE5rQgLfY0vQ== +"@smithy/querystring-parser@^3.0.8": + version "3.0.8" + resolved "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-3.0.8.tgz" + integrity sha512-BtEk3FG7Ks64GAbt+JnKqwuobJNX8VmFLBsKIwWr1D60T426fGrV2L3YS5siOcUhhp6/Y6yhBw1PSPxA5p7qGg== dependencies: - "@smithy/types" "^3.3.0" + "@smithy/types" "^3.6.0" tslib "^2.6.2" -"@smithy/service-error-classification@^3.0.3": - version "3.0.3" - resolved "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-3.0.3.tgz" - integrity sha512-Jn39sSl8cim/VlkLsUhRFq/dKDnRUFlfRkvhOJaUbLBXUsLRLNf9WaxDv/z9BjuQ3A6k/qE8af1lsqcwm7+DaQ== +"@smithy/service-error-classification@^3.0.8": + version "3.0.8" + resolved "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-3.0.8.tgz" + integrity sha512-uEC/kCCFto83bz5ZzapcrgGqHOh/0r69sZ2ZuHlgoD5kYgXJEThCoTuw/y1Ub3cE7aaKdznb+jD9xRPIfIwD7g== dependencies: - "@smithy/types" "^3.3.0" + "@smithy/types" "^3.6.0" -"@smithy/shared-ini-file-loader@^3.1.4": - version "3.1.4" - resolved "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-3.1.4.tgz" - integrity sha512-qMxS4hBGB8FY2GQqshcRUy1K6k8aBWP5vwm8qKkCT3A9K2dawUwOIJfqh9Yste/Bl0J2lzosVyrXDj68kLcHXQ== +"@smithy/shared-ini-file-loader@^3.1.8", "@smithy/shared-ini-file-loader@^3.1.9": + version "3.1.9" + resolved "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-3.1.9.tgz" + integrity sha512-/+OsJRNtoRbtsX0UpSgWVxFZLsJHo/4sTr+kBg/J78sr7iC+tHeOvOJrS5hCpVQ6sWBbhWLp1UNiuMyZhE6pmA== dependencies: - "@smithy/types" "^3.3.0" + "@smithy/types" "^3.6.0" tslib "^2.6.2" -"@smithy/signature-v4@^4.1.0": - version "4.1.0" - resolved "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-4.1.0.tgz" - integrity sha512-aRryp2XNZeRcOtuJoxjydO6QTaVhxx/vjaR+gx7ZjaFgrgPRyZ3HCTbfwqYj6ZWEBHkCSUfcaymKPURaByukag== +"@smithy/signature-v4@^4.2.0": + version "4.2.1" + resolved "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-4.2.1.tgz" + integrity sha512-NsV1jF4EvmO5wqmaSzlnTVetemBS3FZHdyc5CExbDljcyJCEEkJr8ANu2JvtNbVg/9MvKAWV44kTrGS+Pi4INg== dependencies: "@smithy/is-array-buffer" "^3.0.0" - "@smithy/protocol-http" "^4.1.0" - "@smithy/types" "^3.3.0" + "@smithy/protocol-http" "^4.1.5" + "@smithy/types" "^3.6.0" "@smithy/util-hex-encoding" "^3.0.0" - "@smithy/util-middleware" "^3.0.3" + "@smithy/util-middleware" "^3.0.8" "@smithy/util-uri-escape" "^3.0.0" "@smithy/util-utf8" "^3.0.0" tslib "^2.6.2" -"@smithy/smithy-client@^3.1.12": - version "3.1.12" - resolved "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-3.1.12.tgz" - integrity sha512-wtm8JtsycthkHy1YA4zjIh2thJgIQ9vGkoR639DBx5lLlLNU0v4GARpQZkr2WjXue74nZ7MiTSWfVrLkyD8RkA== - dependencies: - "@smithy/middleware-endpoint" "^3.1.0" - "@smithy/middleware-stack" "^3.0.3" - "@smithy/protocol-http" "^4.1.0" - "@smithy/types" "^3.3.0" - "@smithy/util-stream" "^3.1.3" +"@smithy/smithy-client@^3.4.0", "@smithy/smithy-client@^3.4.2": + version "3.4.2" + resolved "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-3.4.2.tgz" + integrity sha512-dxw1BDxJiY9/zI3cBqfVrInij6ShjpV4fmGHesGZZUiP9OSE/EVfdwdRz0PgvkEvrZHpsj2htRaHJfftE8giBA== + dependencies: + "@smithy/core" "^2.5.1" + "@smithy/middleware-endpoint" "^3.2.1" + "@smithy/middleware-stack" "^3.0.8" + "@smithy/protocol-http" "^4.1.5" + "@smithy/types" "^3.6.0" + "@smithy/util-stream" "^3.2.1" tslib "^2.6.2" "@smithy/types@^3.3.0": @@ -2040,13 +2087,20 @@ dependencies: tslib "^2.6.2" -"@smithy/url-parser@^3.0.3": - version "3.0.3" - resolved "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-3.0.3.tgz" - integrity sha512-pw3VtZtX2rg+s6HMs6/+u9+hu6oY6U7IohGhVNnjbgKy86wcIsSZwgHrFR+t67Uyxvp4Xz3p3kGXXIpTNisq8A== +"@smithy/types@^3.5.0", "@smithy/types@^3.6.0": + version "3.6.0" + resolved "https://registry.npmjs.org/@smithy/types/-/types-3.6.0.tgz" + integrity sha512-8VXK/KzOHefoC65yRgCn5vG1cysPJjHnOVt9d0ybFQSmJgQj152vMn4EkYhGuaOmnnZvCPav/KnYyE6/KsNZ2w== dependencies: - "@smithy/querystring-parser" "^3.0.3" - "@smithy/types" "^3.3.0" + tslib "^2.6.2" + +"@smithy/url-parser@^3.0.7", "@smithy/url-parser@^3.0.8": + version "3.0.8" + resolved "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-3.0.8.tgz" + integrity sha512-4FdOhwpTW7jtSFWm7SpfLGKIBC9ZaTKG5nBF0wK24aoQKQyDIKUw3+KFWCQ9maMzrgTJIuOvOnsV2lLGW5XjTg== + dependencies: + "@smithy/querystring-parser" "^3.0.8" + "@smithy/types" "^3.6.0" tslib "^2.6.2" "@smithy/util-base64@^3.0.0": @@ -2095,37 +2149,37 @@ dependencies: tslib "^2.6.2" -"@smithy/util-defaults-mode-browser@^3.0.14": - version "3.0.14" - resolved "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-3.0.14.tgz" - integrity sha512-0iwTgKKmAIf+vFLV8fji21Jb2px11ktKVxbX6LIDPAUJyWQqGqBVfwba7xwa1f2FZUoolYQgLvxQEpJycXuQ5w== +"@smithy/util-defaults-mode-browser@^3.0.23": + version "3.0.25" + resolved "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-3.0.25.tgz" + integrity sha512-fRw7zymjIDt6XxIsLwfJfYUfbGoO9CmCJk6rjJ/X5cd20+d2Is7xjU5Kt/AiDt6hX8DAf5dztmfP5O82gR9emA== dependencies: - "@smithy/property-provider" "^3.1.3" - "@smithy/smithy-client" "^3.1.12" - "@smithy/types" "^3.3.0" + "@smithy/property-provider" "^3.1.8" + "@smithy/smithy-client" "^3.4.2" + "@smithy/types" "^3.6.0" bowser "^2.11.0" tslib "^2.6.2" -"@smithy/util-defaults-mode-node@^3.0.14": - version "3.0.14" - resolved "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-3.0.14.tgz" - integrity sha512-e9uQarJKfXApkTMMruIdxHprhcXivH1flYCe8JRDTzkkLx8dA3V5J8GZlST9yfDiRWkJpZJlUXGN9Rc9Ade3OQ== - dependencies: - "@smithy/config-resolver" "^3.0.5" - "@smithy/credential-provider-imds" "^3.2.0" - "@smithy/node-config-provider" "^3.1.4" - "@smithy/property-provider" "^3.1.3" - "@smithy/smithy-client" "^3.1.12" - "@smithy/types" "^3.3.0" +"@smithy/util-defaults-mode-node@^3.0.23": + version "3.0.25" + resolved "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-3.0.25.tgz" + integrity sha512-H3BSZdBDiVZGzt8TG51Pd2FvFO0PAx/A0mJ0EH8a13KJ6iUCdYnw/Dk/MdC1kTd0eUuUGisDFaxXVXo4HHFL1g== + dependencies: + "@smithy/config-resolver" "^3.0.10" + "@smithy/credential-provider-imds" "^3.2.5" + "@smithy/node-config-provider" "^3.1.9" + "@smithy/property-provider" "^3.1.8" + "@smithy/smithy-client" "^3.4.2" + "@smithy/types" "^3.6.0" tslib "^2.6.2" -"@smithy/util-endpoints@^2.0.5": - version "2.0.5" - resolved "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-2.0.5.tgz" - integrity sha512-ReQP0BWihIE68OAblC/WQmDD40Gx+QY1Ez8mTdFMXpmjfxSyz2fVQu3A4zXRfQU9sZXtewk3GmhfOHswvX+eNg== +"@smithy/util-endpoints@^2.1.3": + version "2.1.4" + resolved "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-2.1.4.tgz" + integrity sha512-kPt8j4emm7rdMWQyL0F89o92q10gvCUa6sBkBtDJ7nV2+P7wpXczzOfoDJ49CKXe5CCqb8dc1W+ZdLlrKzSAnQ== dependencies: - "@smithy/node-config-provider" "^3.1.4" - "@smithy/types" "^3.3.0" + "@smithy/node-config-provider" "^3.1.9" + "@smithy/types" "^3.6.0" tslib "^2.6.2" "@smithy/util-hex-encoding@^3.0.0": @@ -2135,31 +2189,31 @@ dependencies: tslib "^2.6.2" -"@smithy/util-middleware@^3.0.3": - version "3.0.3" - resolved "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.3.tgz" - integrity sha512-l+StyYYK/eO3DlVPbU+4Bi06Jjal+PFLSMmlWM1BEwyLxZ3aKkf1ROnoIakfaA7mC6uw3ny7JBkau4Yc+5zfWw== +"@smithy/util-middleware@^3.0.7", "@smithy/util-middleware@^3.0.8": + version "3.0.8" + resolved "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.8.tgz" + integrity sha512-p7iYAPaQjoeM+AKABpYWeDdtwQNxasr4aXQEA/OmbOaug9V0odRVDy3Wx4ci8soljE/JXQo+abV0qZpW8NX0yA== dependencies: - "@smithy/types" "^3.3.0" + "@smithy/types" "^3.6.0" tslib "^2.6.2" -"@smithy/util-retry@^3.0.3": - version "3.0.3" - resolved "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-3.0.3.tgz" - integrity sha512-AFw+hjpbtVApzpNDhbjNG5NA3kyoMs7vx0gsgmlJF4s+yz1Zlepde7J58zpIRIsdjc+emhpAITxA88qLkPF26w== +"@smithy/util-retry@^3.0.7", "@smithy/util-retry@^3.0.8": + version "3.0.8" + resolved "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-3.0.8.tgz" + integrity sha512-TCEhLnY581YJ+g1x0hapPz13JFqzmh/pMWL2KEFASC51qCfw3+Y47MrTmea4bUE5vsdxQ4F6/KFbUeSz22Q1ow== dependencies: - "@smithy/service-error-classification" "^3.0.3" - "@smithy/types" "^3.3.0" + "@smithy/service-error-classification" "^3.0.8" + "@smithy/types" "^3.6.0" tslib "^2.6.2" -"@smithy/util-stream@^3.1.3": - version "3.1.3" - resolved "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-3.1.3.tgz" - integrity sha512-FIv/bRhIlAxC0U7xM1BCnF2aDRPq0UaelqBHkM2lsCp26mcBbgI0tCVTv+jGdsQLUmAMybua/bjDsSu8RQHbmw== +"@smithy/util-stream@^3.1.9", "@smithy/util-stream@^3.2.1": + version "3.2.1" + resolved "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-3.2.1.tgz" + integrity sha512-R3ufuzJRxSJbE58K9AEnL/uSZyVdHzud9wLS8tIbXclxKzoe09CRohj2xV8wpx5tj7ZbiJaKYcutMm1eYgz/0A== dependencies: - "@smithy/fetch-http-handler" "^3.2.4" - "@smithy/node-http-handler" "^3.1.4" - "@smithy/types" "^3.3.0" + "@smithy/fetch-http-handler" "^4.0.0" + "@smithy/node-http-handler" "^3.2.5" + "@smithy/types" "^3.6.0" "@smithy/util-base64" "^3.0.0" "@smithy/util-buffer-from" "^3.0.0" "@smithy/util-hex-encoding" "^3.0.0" @@ -2189,13 +2243,13 @@ "@smithy/util-buffer-from" "^3.0.0" tslib "^2.6.2" -"@smithy/util-waiter@^3.1.2": - version "3.1.2" - resolved "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-3.1.2.tgz" - integrity sha512-4pP0EV3iTsexDx+8PPGAKCQpd/6hsQBaQhqWzU4hqKPHN5epPsxKbvUTIiYIHTxaKt6/kEaqPBpu/ufvfbrRzw== +"@smithy/util-waiter@^3.1.6": + version "3.1.7" + resolved "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-3.1.7.tgz" + integrity sha512-d5yGlQtmN/z5eoTtIYgkvOw27US2Ous4VycnXatyoImIF9tzlcpnKqQ/V7qhvJmb2p6xZne1NopCLakdTnkBBQ== dependencies: - "@smithy/abort-controller" "^3.1.1" - "@smithy/types" "^3.3.0" + "@smithy/abort-controller" "^3.1.6" + "@smithy/types" "^3.6.0" tslib "^2.6.2" "@tootallnate/once@1": @@ -3686,7 +3740,9 @@ camelcase@^6.2.0: integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== caniuse-lite@^1.0.30001646: - version "1.0.30001649" + version "1.0.30001677" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001677.tgz" + integrity sha512-fmfjsOlJUpMWu+mAAtZZZHz7UEwsUxIIvu1TJfO1HqFQvB/B+ii0xr9B5HpbZY/mC4XZ8SvjHJqtAY6pDPQEog== caseless@~0.12.0: version "0.12.0" @@ -7190,6 +7246,8 @@ lru-queue@^0.1.0: luxon@^3.2.1: version "3.5.0" + resolved "https://registry.npmjs.org/luxon/-/luxon-3.5.0.tgz" + integrity sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ== macos-release@^2.5.0: version "2.5.1" From 5e6bfa23b04414667496189d0ced8561fe85776a Mon Sep 17 00:00:00 2001 From: Guillaume Cauchois Date: Thu, 7 Nov 2024 16:08:19 +0100 Subject: [PATCH 10/29] refacto: findAllCandidateMembers / findAllCoachMembers role param --- src/users/users.controller.ts | 2 -- src/users/users.service.ts | 14 ++++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/users/users.controller.ts b/src/users/users.controller.ts index 46a1ff6d..687d59cc 100644 --- a/src/users/users.controller.ts +++ b/src/users/users.controller.ts @@ -86,7 +86,6 @@ export class UsersController { if (isRoleIncluded([UserRoles.CANDIDATE], role)) { return this.usersService.findAllCandidateMembers({ ...query, - role: [UserRoles.CANDIDATE], limit, offset, search, @@ -96,7 +95,6 @@ export class UsersController { if (isRoleIncluded([UserRoles.COACH], role)) { return this.usersService.findAllCoachMembers({ ...query, - role: [UserRoles.COACH], limit, offset, search, diff --git a/src/users/users.service.ts b/src/users/users.service.ts index 2f49ba9d..2c1e6b8b 100644 --- a/src/users/users.service.ts +++ b/src/users/users.service.ts @@ -92,13 +92,14 @@ export class UsersService { limit: number; offset: number; search: string; - role: (typeof UserRoles.CANDIDATE)[]; } & FilterParams ): Promise { const { limit, offset, search, ...restParams } = params; - const { filterOptions, replacements } = - getCommonMembersFilterOptions(restParams); + const { filterOptions, replacements } = getCommonMembersFilterOptions({ + ...restParams, + role: [UserRoles.CANDIDATE], + }); const lastCVVersions = await this.findAllLastCVVersions(); @@ -216,13 +217,14 @@ export class UsersService { limit: number; offset: number; search: string; - role: (typeof UserRoles.COACH)[]; } & FilterParams ): Promise { const { limit, offset, search, ...restParams } = params; - const { replacements, filterOptions } = - getCommonMembersFilterOptions(restParams); + const { replacements, filterOptions } = getCommonMembersFilterOptions({ + ...restParams, + role: [UserRoles.COACH], + }); const coachesIds: { nameAndId: string; userId: string }[] = await this.userModel.sequelize.query( From cd4385282d4c4d59d8b3c389019b9c1dfcac5415 Mon Sep 17 00:00:00 2001 From: Guillaume Cauchois Date: Thu, 7 Nov 2024 16:31:42 +0100 Subject: [PATCH 11/29] refacto: only one template for confirmation email --- src/external-services/mailjet/mailjet.types.ts | 1 - src/mails/mails.service.ts | 6 +----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/external-services/mailjet/mailjet.types.ts b/src/external-services/mailjet/mailjet.types.ts index 87433e83..e417082e 100644 --- a/src/external-services/mailjet/mailjet.types.ts +++ b/src/external-services/mailjet/mailjet.types.ts @@ -90,7 +90,6 @@ export const MailjetTemplates = { INTERNAL_MESSAGE_CONFIRMATION: 5625495, MESSAGING_MESSAGE: 6305900, USER_EMAIL_VERIFICATION: 5899611, - USER_EMAIL_VERIFICATION_REFERER: 6324333, USER_REPORTED_ADMIN: 6223181, CONVERSATION_REPORTED_ADMIN: 6276909, ONBOARDING_J1_BAO: 6129684, diff --git a/src/mails/mails.service.ts b/src/mails/mails.service.ts index 23686880..1ee17db2 100644 --- a/src/mails/mails.service.ts +++ b/src/mails/mails.service.ts @@ -108,13 +108,9 @@ export class MailsService { } async sendVerificationMail(user: User, token: string) { - const templateId = - user.role === UserRoles.REFERER - ? MailjetTemplates.USER_EMAIL_VERIFICATION_REFERER - : MailjetTemplates.USER_EMAIL_VERIFICATION; return this.queuesService.addToWorkQueue(Jobs.SEND_MAIL, { toEmail: user.email, - templateId, + templateId: MailjetTemplates.USER_EMAIL_VERIFICATION, variables: { firstName: user.firstName, toEmail: user.email, From 5059560a27bb14e3140371f84084055c2171c92a Mon Sep 17 00:00:00 2001 From: Guillaume Cauchois Date: Fri, 8 Nov 2024 15:34:13 +0100 Subject: [PATCH 12/29] feat: add welcome mail referer --- src/external-services/mailjet/mailjet.types.ts | 1 + src/mails/mails.service.ts | 11 +++++++++++ src/users-creation/users-creation.controller.ts | 5 ++++- 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/external-services/mailjet/mailjet.types.ts b/src/external-services/mailjet/mailjet.types.ts index e417082e..23610953 100644 --- a/src/external-services/mailjet/mailjet.types.ts +++ b/src/external-services/mailjet/mailjet.types.ts @@ -57,6 +57,7 @@ export const MailjetTemplates = { ACCOUNT_CREATED: 3920498, WELCOME_COACH: 5786622, WELCOME_CANDIDATE: 5786606, + WELCOME_REFERER: 6324333, CV_PREPARE: 3782475, CV_REMINDER_10: 3782934, CV_REMINDER_20: 3917533, diff --git a/src/mails/mails.service.ts b/src/mails/mails.service.ts index 1ee17db2..e625081a 100644 --- a/src/mails/mails.service.ts +++ b/src/mails/mails.service.ts @@ -105,6 +105,17 @@ export class MailsService { }, }); } + + if (user.role === UserRoles.REFERER) { + return this.queuesService.addToWorkQueue(Jobs.SEND_MAIL, { + toEmail: user.email, + replyTo: candidatesAdminMail, + templateId: MailjetTemplates.WELCOME_REFERER, + variables: { + ..._.omitBy(user, _.isNil), + }, + }); + } } async sendVerificationMail(user: User, token: string) { diff --git a/src/users-creation/users-creation.controller.ts b/src/users-creation/users-creation.controller.ts index 9d722fbb..72fd68fe 100644 --- a/src/users-creation/users-creation.controller.ts +++ b/src/users-creation/users-creation.controller.ts @@ -231,7 +231,10 @@ export class UsersCreationController { gender: createUserRegistrationDto.gender, }); - if (createUserRegistrationDto.program === Programs.BOOST) { + if ( + createUserRegistrationDto.program === Programs.BOOST || + createUserRegistrationDto.role === UserRoles.REFERER + ) { await this.usersCreationService.sendWelcomeMail({ id: createdUser.id, firstName: createdUser.firstName, From 831a2363995fa056155c9a8a1dacb69867ec73b4 Mon Sep 17 00:00:00 2001 From: "Dorian P." Date: Fri, 8 Nov 2024 16:51:41 +0100 Subject: [PATCH 13/29] [EN-7457] Finalize refered user (#232) * created sendMail method and controller * naming and removed dev tools * adding organisation name * reverted --- src/auth/auth.controller.ts | 48 +++++++++++++++++++ .../mailjet/mailjet.types.ts | 1 + src/mails/mails.service.ts | 26 ++++++++++ 3 files changed, 75 insertions(+) diff --git a/src/auth/auth.controller.ts b/src/auth/auth.controller.ts index 3aac93e1..b0fa6095 100644 --- a/src/auth/auth.controller.ts +++ b/src/auth/auth.controller.ts @@ -239,4 +239,52 @@ export class AuthController { return; } + + @Throttle(60, 60) + @Public() + @Post('finalize-refered-user') + async finalizeReferedUser( + @Body('token') token?: string, + @Body('password') password?: string + ): Promise { + if (!token || !password) { + throw new BadRequestException(); + } + + const decodedToken = this.authService.decodeJWT(token, true); + const { sub: userId, exp } = decodedToken; + + const expirationDate = new Date(exp * 1000); + const currentDate = new Date(); + + if (!decodedToken || !exp || !userId) { + throw new BadRequestException('INVALID_TOKEN'); + } + const user = await this.authService.findOneUserComplete(userId); + if (!user) { + throw new NotFoundException(); + } + if (user.isEmailVerified && user.password) { + throw new BadRequestException('EMAIL_ALREADY_VERIFIED'); + } + if (expirationDate.getTime() < currentDate.getTime()) { + throw new BadRequestException('TOKEN_EXPIRED'); + } + + const { hash, salt } = encryptPassword(password); + + const updatedUser = await this.authService.updateUser(userId, { + isEmailVerified: true, + password: hash, + salt, + hashReset: null, + saltReset: null, + }); + + if (!updatedUser) { + throw new NotFoundException(); + } + + return updatedUser.email; + } } diff --git a/src/external-services/mailjet/mailjet.types.ts b/src/external-services/mailjet/mailjet.types.ts index e417082e..bfdd6187 100644 --- a/src/external-services/mailjet/mailjet.types.ts +++ b/src/external-services/mailjet/mailjet.types.ts @@ -95,6 +95,7 @@ export const MailjetTemplates = { ONBOARDING_J1_BAO: 6129684, ONBOARDING_J3_PROFILE_COMPLETION: 6129711, REFERER_ONBOARDING_CONFIRMATION: 6324339, + REFERED_CANDIDATE_FINALIZE_ACCOUNT: 6324039, } as const; export type MailjetTemplateKey = keyof typeof MailjetTemplates; diff --git a/src/mails/mails.service.ts b/src/mails/mails.service.ts index 1ee17db2..8026a387 100644 --- a/src/mails/mails.service.ts +++ b/src/mails/mails.service.ts @@ -829,6 +829,32 @@ export class MailsService { }, }); } + + // TODO: Call this method after completing refering funnel + // Generate a token with + // const token = await this.authService.generateVerificationToken(candidate); + async sendReferedCandidateFinalizeAccountMail( + // TODO after merge + // referer: UserWithOrganization, + referer: User, + candidate: User, + token: string + ) { + await this.queuesService.addToWorkQueue(Jobs.SEND_MAIL, { + toEmail: referer.email, + templateId: MailjetTemplates.REFERED_CANDIDATE_FINALIZE_ACCOUNT, + variables: { + candidateFirstName: candidate.firstName, + refererFirstName: referer.firstName, + refererLastName: referer.lastName, + // organizationName: referer.organization.name, + // TODO after merge + // structure: referer.structure, + finalizeAccountUrl: `${process.env.FRONTEND_URL}/finaliser-compte-oriente?token=${token}`, + zone: candidate.zone, + }, + }); + } } const getRoleString = (user: User): string => { From 8369f7f666356ed8d6834d72b092ff7b4c1ae81e Mon Sep 17 00:00:00 2001 From: Guillaume Cauchois Date: Mon, 18 Nov 2024 13:35:12 +0100 Subject: [PATCH 14/29] feat(Refering): Add refer candidate api endpoint --- src/auth/auth.controller.ts | 10 ++ src/auth/auth.service.ts | 14 +++ src/mails/mails.service.ts | 16 +-- .../dto/create-user-refering.dto.ts | 106 ++++++++++++++++++ .../dto/create-user-refering.pipe.ts | 39 +++++++ .../users-creation.controller.ts | 90 ++++++++++++++- src/users-creation/users-creation.service.ts | 9 ++ src/users/users.types.ts | 2 +- 8 files changed, 273 insertions(+), 13 deletions(-) create mode 100644 src/users-creation/dto/create-user-refering.dto.ts create mode 100644 src/users-creation/dto/create-user-refering.pipe.ts diff --git a/src/auth/auth.controller.ts b/src/auth/auth.controller.ts index b0fa6095..63ff283f 100644 --- a/src/auth/auth.controller.ts +++ b/src/auth/auth.controller.ts @@ -285,6 +285,16 @@ export class AuthController { throw new NotFoundException(); } + await this.authService.sendWelcomeMail({ + id: updatedUser.id, + firstName: updatedUser.firstName, + role: updatedUser.role, + zone: updatedUser.zone, + email: updatedUser.email, + }); + await this.authService.sendOnboardingJ1BAOMail(updatedUser); + await this.authService.sendOnboardingJ3ProfileCompletionMail(updatedUser); + return updatedUser.email; } } diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts index 4b9a0508..93a799d2 100644 --- a/src/auth/auth.service.ts +++ b/src/auth/auth.service.ts @@ -146,4 +146,18 @@ export class AuthService { } ); } + + async sendWelcomeMail( + user: Pick + ) { + return this.mailsService.sendWelcomeMail(user); + } + + async sendOnboardingJ1BAOMail(user: User) { + return this.mailsService.sendOnboardingJ1BAOMail(user); + } + + async sendOnboardingJ3ProfileCompletionMail(user: User) { + return this.mailsService.sendOnboardingJ3ProfileCompletionMail(user); + } } diff --git a/src/mails/mails.service.ts b/src/mails/mails.service.ts index 59d7cb57..904081c1 100644 --- a/src/mails/mails.service.ts +++ b/src/mails/mails.service.ts @@ -835,33 +835,27 @@ export class MailsService { refererFirstName: referer.firstName, candidateFirstName: candidate.firstName, candidateLastName: candidate.lastName, - loginUrl: `${process.env.FRONTEND_URL}/login`, + loginUrl: `${process.env.FRONT_URL}/login`, zone: referer.zone, }, }); } - // TODO: Call this method after completing refering funnel - // Generate a token with - // const token = await this.authService.generateVerificationToken(candidate); async sendReferedCandidateFinalizeAccountMail( - // TODO after merge - // referer: UserWithOrganization, referer: User, candidate: User, token: string ) { await this.queuesService.addToWorkQueue(Jobs.SEND_MAIL, { - toEmail: referer.email, + toEmail: candidate.email, templateId: MailjetTemplates.REFERED_CANDIDATE_FINALIZE_ACCOUNT, variables: { + id: candidate.id, candidateFirstName: candidate.firstName, refererFirstName: referer.firstName, refererLastName: referer.lastName, - // organizationName: referer.organization.name, - // TODO after merge - // structure: referer.structure, - finalizeAccountUrl: `${process.env.FRONTEND_URL}/finaliser-compte-oriente?token=${token}`, + organizationName: referer.organization.name, + finalizeAccountUrl: `${process.env.FRONT_URL}/finaliser-compte-oriente?token=${token}`, zone: candidate.zone, }, }); diff --git a/src/users-creation/dto/create-user-refering.dto.ts b/src/users-creation/dto/create-user-refering.dto.ts new file mode 100644 index 00000000..20e7e24c --- /dev/null +++ b/src/users-creation/dto/create-user-refering.dto.ts @@ -0,0 +1,106 @@ +import { ApiProperty, PickType } from '@nestjs/swagger'; +import { IsArray, IsNumber, IsOptional, IsString } from 'class-validator'; +import { Ambition } from 'src/common/ambitions/models'; +import { BusinessLine } from 'src/common/business-lines/models'; +import { Department } from 'src/common/locations/locations.types'; +import { + CandidateAccommodation, + CandidateResource, + CandidateYesNoNSPPValue, + JobSearchDuration, + Nationality, + StudiesLevel, + WorkingExperience, + YesNoJNSPRValue, +} from 'src/contacts/contacts.types'; +import { HelpNeed } from 'src/user-profiles/models'; +import { User } from 'src/users/models'; +import { Gender, Program } from 'src/users/users.types'; + +export class CreateUserReferingDto extends PickType(User, [ + 'firstName', + 'lastName', + 'email', + 'gender', + 'phone', + 'password', +] as const) { + @ApiProperty() + @IsNumber() + @IsOptional() + gender: Gender; + + @ApiProperty() + @IsString() + @IsOptional() + program: Program; + + @ApiProperty() + @IsString() + @IsOptional() + birthDate: Date; + + @ApiProperty() + @IsString() + department: Department; + + @ApiProperty() + @IsString() + @IsOptional() + campaign?: string; + + @ApiProperty() + @IsArray() + @IsOptional() + helpNeeds?: HelpNeed[]; + + @ApiProperty() + @IsString() + @IsOptional() + workingRight?: CandidateYesNoNSPPValue; + + @ApiProperty() + @IsArray() + @IsOptional() + searchBusinessLines?: BusinessLine[]; + + @ApiProperty() + @IsArray() + @IsOptional() + searchAmbitions?: Ambition[]; + + @ApiProperty() + @IsString() + @IsOptional() + nationality?: Nationality; + + @ApiProperty() + @IsString() + @IsOptional() + accommodation?: CandidateAccommodation; + + @ApiProperty() + @IsString() + @IsOptional() + hasSocialWorker?: YesNoJNSPRValue; + + @ApiProperty() + @IsString() + @IsOptional() + resources?: CandidateResource; + + @ApiProperty() + @IsString() + @IsOptional() + studiesLevel?: StudiesLevel; + + @ApiProperty() + @IsString() + @IsOptional() + workingExperience?: WorkingExperience; + + @ApiProperty() + @IsString() + @IsOptional() + jobSearchDuration?: JobSearchDuration; +} diff --git a/src/users-creation/dto/create-user-refering.pipe.ts b/src/users-creation/dto/create-user-refering.pipe.ts new file mode 100644 index 00000000..d5f8553a --- /dev/null +++ b/src/users-creation/dto/create-user-refering.pipe.ts @@ -0,0 +1,39 @@ +/* eslint-disable @typescript-eslint/ban-types */ +import { + ArgumentMetadata, + BadRequestException, + PipeTransform, +} from '@nestjs/common'; +import { plainToInstance } from 'class-transformer'; +import { validate } from 'class-validator'; +import { CreateUserReferingDto } from './create-user-refering.dto'; + +export class CreateUserReferingPipe + implements + PipeTransform> +{ + private static toValidate(metatype: Function): boolean { + const types: Function[] = [String, Boolean, Number, Array, Object]; + return !types.includes(metatype); + } + + async transform( + value: CreateUserReferingDto, + { metatype }: ArgumentMetadata + ): Promise { + if (!metatype || !CreateUserReferingPipe.toValidate(metatype)) { + return value; + } + const object = plainToInstance(metatype, value); + const errors = await validate(object, { + whitelist: true, + forbidNonWhitelisted: true, + forbidUnknownValues: true, + }); + + if (errors.length > 0) { + throw new BadRequestException(); + } + return value; + } +} diff --git a/src/users-creation/users-creation.controller.ts b/src/users-creation/users-creation.controller.ts index 72fd68fe..b638b381 100644 --- a/src/users-creation/users-creation.controller.ts +++ b/src/users-creation/users-creation.controller.ts @@ -11,7 +11,7 @@ import { import { ApiTags } from '@nestjs/swagger'; import { Throttle } from '@nestjs/throttler'; import { encryptPassword } from 'src/auth/auth.utils'; -import { Public } from 'src/auth/guards'; +import { Public, UserPayload } from 'src/auth/guards'; import { UserPermissions, UserPermissionsGuard } from 'src/users/guards'; import { User } from 'src/users/models'; import { @@ -33,6 +33,8 @@ import { CreateUserRegistrationDto, CreateUserRegistrationPipe, } from './dto'; +import { CreateUserReferingDto } from './dto/create-user-refering.dto'; +import { CreateUserReferingPipe } from './dto/create-user-refering.pipe'; import { UsersCreationService } from './users-creation.service'; function generateFakePassword() { @@ -259,4 +261,90 @@ export class UsersCreationController { } } } + + @UserPermissions(Permissions.REFERER) + @UseGuards(UserPermissionsGuard) + @Throttle(10, 60) + @Post('refering') + async createUserRefering( + @Body(new CreateUserReferingPipe()) + createUserReferingDto: CreateUserReferingDto, + @UserPayload() + referer: User + ) { + if (!isValidPhone(createUserReferingDto.phone)) { + throw new BadRequestException(); + } + + if (!createUserReferingDto.program) { + throw new BadRequestException(); + } + + const userRandomPassword = generateFakePassword(); + const { hash, salt } = encryptPassword(userRandomPassword); + + const zone = getZoneFromDepartment(createUserReferingDto.department); + + const userToCreate: Partial = { + refererId: referer.id, + OrganizationId: referer.OrganizationId, + firstName: createUserReferingDto.firstName, + lastName: createUserReferingDto.lastName, + email: createUserReferingDto.email, + role: UserRoles.CANDIDATE, + gender: createUserReferingDto.gender, + phone: createUserReferingDto.phone, + address: null, + adminRole: null, + zone, + password: hash, + salt, + }; + + try { + const { id: createdUserId } = await this.usersCreationService.createUser( + userToCreate + ); + + await this.usersCreationService.updateUserProfileByUserId(createdUserId, { + department: createUserReferingDto.department, + helpNeeds: createUserReferingDto.helpNeeds, + searchBusinessLines: createUserReferingDto.searchBusinessLines, + searchAmbitions: createUserReferingDto.searchAmbitions, + }); + + const createdUser = await this.usersCreationService.findOneUser( + createdUserId + ); + + await this.usersCreationService.createExternalDBUser(createdUserId, { + program: createUserReferingDto.program, + birthDate: createUserReferingDto.birthDate, + campaign: + createUserReferingDto.program === Programs.THREE_SIXTY + ? createUserReferingDto.campaign + : undefined, + workingRight: createUserReferingDto.workingRight, + nationality: createUserReferingDto.nationality, + accommodation: createUserReferingDto.accommodation, + hasSocialWorker: createUserReferingDto.hasSocialWorker, + resources: createUserReferingDto.resources, + studiesLevel: createUserReferingDto.studiesLevel, + workingExperience: createUserReferingDto.workingExperience, + jobSearchDuration: createUserReferingDto.jobSearchDuration, + gender: createUserReferingDto.gender, + }); + + await this.usersCreationService.sendFinalizeAccountReferedUser( + createdUser, + referer + ); + + return createdUser; + } catch (err) { + if (((err as Error).name = SequelizeUniqueConstraintError)) { + throw new ConflictException(); + } + } + } } diff --git a/src/users-creation/users-creation.service.ts b/src/users-creation/users-creation.service.ts index e8487fb0..6a9c9520 100644 --- a/src/users-creation/users-creation.service.ts +++ b/src/users-creation/users-creation.service.ts @@ -83,6 +83,15 @@ export class UsersCreationService { return this.mailsService.sendVerificationMail(user, token); } + async sendFinalizeAccountReferedUser(candidate: User, referer: User) { + const token = await this.authService.generateVerificationToken(candidate); + return this.mailsService.sendReferedCandidateFinalizeAccountMail( + referer, + candidate, + token + ); + } + async sendOnboardingJ3ProfileCompletionMail(user: User) { return this.mailsService.sendOnboardingJ3ProfileCompletionMail(user); } diff --git a/src/users/users.types.ts b/src/users/users.types.ts index d1ff1a28..8651d6b0 100644 --- a/src/users/users.types.ts +++ b/src/users/users.types.ts @@ -32,7 +32,7 @@ export type Permission = (typeof Permissions)[keyof typeof Permissions]; export const UserPermissions: { [K in UserRole]: Permission | Permission[] } = { [UserRoles.CANDIDATE]: Permissions.CANDIDATE, - [UserRoles.REFERER]: Permissions.COACH, + [UserRoles.REFERER]: Permissions.REFERER, [UserRoles.COACH]: [Permissions.COACH, Permissions.RESTRICTED_COACH], [UserRoles.ADMIN]: Permissions.ADMIN, }; From 2483856462c91c522e722efa9f6662c9c508bcca Mon Sep 17 00:00:00 2001 From: Guillaume Cauchois Date: Tue, 19 Nov 2024 15:26:23 +0100 Subject: [PATCH 15/29] feat(UserProfile): filter by refererId --- src/user-profiles/user-profiles.controller.ts | 5 ++++- src/user-profiles/user-profiles.service.ts | 20 +++++++++++++++++-- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/user-profiles/user-profiles.controller.ts b/src/user-profiles/user-profiles.controller.ts index 5cb9b611..50a8c7bf 100644 --- a/src/user-profiles/user-profiles.controller.ts +++ b/src/user-profiles/user-profiles.controller.ts @@ -121,7 +121,9 @@ export class UserProfilesController { @Query('departments') departments: Department[], @Query('businessLines') - businessLines: BusinessLineValue[] + businessLines: BusinessLineValue[], + @Query('refererId') + refererId: string ) { if (!role || role.length === 0) { throw new BadRequestException(); @@ -143,6 +145,7 @@ export class UserProfilesController { helps, departments, businessLines, + refererId, }); } diff --git a/src/user-profiles/user-profiles.service.ts b/src/user-profiles/user-profiles.service.ts index 5981ceb4..f5a5f50c 100644 --- a/src/user-profiles/user-profiles.service.ts +++ b/src/user-profiles/user-profiles.service.ts @@ -114,10 +114,19 @@ export class UserProfilesService { helps: HelpValue[]; departments: Department[]; businessLines: BusinessLineValue[]; + refererId: string; } ): Promise { - const { role, offset, limit, search, helps, departments, businessLines } = - query; + const { + role, + offset, + limit, + search, + helps, + departments, + businessLines, + refererId, + } = query; const searchOptions = search ? { [Op.or]: userProfileSearchQuery(search) } @@ -137,6 +146,12 @@ export class UserProfilesService { } : {}; + const refererIdOptions: WhereOptions = refererId + ? { + refererId, + } + : {}; + const helpsOptions: WhereOptions = helps?.length > 0 ? { @@ -167,6 +182,7 @@ export class UserProfilesService { role, lastConnection: { [Op.ne]: null }, ...searchOptions, + ...refererIdOptions, }, }, ], From 784ce21c735114b1aa4e2747d354f78488f2d3b7 Mon Sep 17 00:00:00 2001 From: Guillaume Cauchois Date: Tue, 19 Nov 2024 17:50:47 +0100 Subject: [PATCH 16/29] feat: create a dedicated referedCondidate endpoint --- src/user-profiles/user-profiles.controller.ts | 20 ++++-- src/user-profiles/user-profiles.service.ts | 69 ++++++++++++++----- 2 files changed, 67 insertions(+), 22 deletions(-) diff --git a/src/user-profiles/user-profiles.controller.ts b/src/user-profiles/user-profiles.controller.ts index 50a8c7bf..1e8af51e 100644 --- a/src/user-profiles/user-profiles.controller.ts +++ b/src/user-profiles/user-profiles.controller.ts @@ -121,9 +121,7 @@ export class UserProfilesController { @Query('departments') departments: Department[], @Query('businessLines') - businessLines: BusinessLineValue[], - @Query('refererId') - refererId: string + businessLines: BusinessLineValue[] ) { if (!role || role.length === 0) { throw new BadRequestException(); @@ -145,7 +143,21 @@ export class UserProfilesController { helps, departments, businessLines, - refererId, + }); + } + + @UserPermissions(Permissions.REFERER) + @Get('refered') + async findReferedCandidates( + @UserPayload('id', new ParseUUIDPipe()) userId: string, + @Query('limit', new ParseIntPipe()) + limit: number, + @Query('offset', new ParseIntPipe()) + offset: number + ) { + return this.userProfilesService.findAllReferedCandidates(userId, { + offset, + limit, }); } diff --git a/src/user-profiles/user-profiles.service.ts b/src/user-profiles/user-profiles.service.ts index f5a5f50c..e4d21311 100644 --- a/src/user-profiles/user-profiles.service.ts +++ b/src/user-profiles/user-profiles.service.ts @@ -114,19 +114,10 @@ export class UserProfilesService { helps: HelpValue[]; departments: Department[]; businessLines: BusinessLineValue[]; - refererId: string; } ): Promise { - const { - role, - offset, - limit, - search, - helps, - departments, - businessLines, - refererId, - } = query; + const { role, offset, limit, search, helps, departments, businessLines } = + query; const searchOptions = search ? { [Op.or]: userProfileSearchQuery(search) } @@ -146,12 +137,6 @@ export class UserProfilesService { } : {}; - const refererIdOptions: WhereOptions = refererId - ? { - refererId, - } - : {}; - const helpsOptions: WhereOptions = helps?.length > 0 ? { @@ -182,7 +167,6 @@ export class UserProfilesService { role, lastConnection: { [Op.ne]: null }, ...searchOptions, - ...refererIdOptions, }, }, ], @@ -226,6 +210,55 @@ export class UserProfilesService { ); } + async findAllReferedCandidates( + userId: string, + query: { + offset: number; + limit: number; + } + ): Promise { + const { offset, limit } = query; + + const profiles = await this.userProfileModel.findAll({ + attributes: UserProfilesAttributes, + order: sequelize.literal('"user.createdAt" DESC'), + include: [ + ...getUserProfileInclude(), + { + model: User, + as: 'user', + attributes: UserProfilesUserAttributes, + where: { + refererId: userId, + }, + }, + ], + limit, + offset, + }); + + return Promise.all( + profiles.map(async (profile): Promise => { + const lastSentMessage = await this.getLastContact( + userId, + profile.user.id + ); + const lastReceivedMessage = await this.getLastContact( + profile.user.id, + userId + ); + + const { user, ...restProfile }: UserProfile = profile.toJSON(); + return { + ...user, + ...restProfile, + lastSentMessage: lastSentMessage?.createdAt || null, + lastReceivedMessage: lastReceivedMessage?.createdAt || null, + }; + }) + ); + } + async findRecommendationsByUserId( userId: string ): Promise { From 4f05a676a7700f7c4298227d0accc3de6097dc23 Mon Sep 17 00:00:00 2001 From: Guillaume Cauchois Date: Fri, 22 Nov 2024 16:08:58 +0100 Subject: [PATCH 17/29] feat: admin view for referers --- src/users/users.controller.ts | 9 ++++ src/users/users.service.ts | 86 +++++++++++++++++++++++++++++++++++ src/users/users.utils.ts | 2 + 3 files changed, 97 insertions(+) diff --git a/src/users/users.controller.ts b/src/users/users.controller.ts index 687d59cc..fbe43ecc 100644 --- a/src/users/users.controller.ts +++ b/src/users/users.controller.ts @@ -101,6 +101,15 @@ export class UsersController { }); } + if (isRoleIncluded([UserRoles.REFERER], role)) { + return this.usersService.findAllRefererMembers({ + ...query, + limit, + offset, + search, + }); + } + throw new BadRequestException(); } diff --git a/src/users/users.service.ts b/src/users/users.service.ts index 6cb750fd..6763a7b1 100644 --- a/src/users/users.service.ts +++ b/src/users/users.service.ts @@ -289,6 +289,92 @@ export class UsersService { }); } + async findAllRefererMembers( + params: { + limit: number; + offset: number; + search: string; + } & FilterParams + ): Promise { + const { limit, offset, search, ...restParams } = params; + + const { replacements, filterOptions } = getCommonMembersFilterOptions({ + ...restParams, + role: [UserRoles.REFERER], + }); + + const referersIds: { userId: string }[] = + await this.userModel.sequelize.query( + ` + SELECT + "User"."id" as "userId" + + FROM "Users" as "User" + + LEFT OUTER JOIN "User_Candidats" AS "coaches" + ON "User"."id" = "coaches"."coachId" + LEFT OUTER JOIN "Users" AS "coaches->candidat" + ON "coaches"."candidatId" = "coaches->candidat"."id" + AND ("coaches->candidat"."deletedAt" IS NULL) + LEFT OUTER JOIN "Organizations" AS "coaches->candidat->organization" + ON "coaches->candidat"."OrganizationId" = "coaches->candidat->organization"."id" + LEFT OUTER JOIN "Organizations" AS "organization" + ON "User"."OrganizationId" = "organization"."id" + + WHERE "User"."deletedAt" IS NULL + AND ${filterOptions.join(' AND ')} ${ + search ? `AND ${userSearchQueryRaw(search, true)}` : '' + } + + GROUP BY "User"."id" + ORDER BY "User"."firstName" ASC + LIMIT ${limit} + OFFSET ${offset} + `, + { + type: QueryTypes.SELECT, + raw: true, + replacements, + } + ); + + return this.userModel.findAll({ + attributes: [...UserAttributes], + where: { + id: referersIds.map(({ userId }) => userId), + }, + order: [['firstName', 'ASC']], + include: [ + { + model: User, + as: 'referredCandidates', + attributes: [...UserAttributes], + include: [ + { + model: UserCandidat, + as: 'candidat', + attributes: [...UserCandidatAttributes], + paranoid: false, + include: [ + { + model: User, + as: 'candidat', + attributes: [...UserAttributes], + }, + ], + }, + ], + }, + { + model: Organization, + as: 'organization', + attributes: ['name', 'address', 'zone', 'id'], + required: false, + }, + ], + }); + } + async findAllUsers(search: string, role: UserRole[], organizationId: string) { const options: FindOptions = { attributes: [...UserAttributes], diff --git a/src/users/users.utils.ts b/src/users/users.utils.ts index 893ec2a7..5d25de86 100644 --- a/src/users/users.utils.ts +++ b/src/users/users.utils.ts @@ -163,6 +163,8 @@ export function getMemberOptions(filtersObj: FilterObject) { associatedUserOptionKey = '"candidat"."coachId"'; } else if (isRoleIncluded([UserRoles.COACH], rolesFilters)) { associatedUserOptionKey = '"coaches"."candidatId"'; + } else if (isRoleIncluded([UserRoles.REFERER], rolesFilters)) { + associatedUserOptionKey = '"referredCandidates"."candidatId"'; } else { return []; } From d1477db64c0c68b5c1969097d269a8a40b9e02e1 Mon Sep 17 00:00:00 2001 From: Guillaume Cauchois Date: Mon, 25 Nov 2024 14:20:24 +0100 Subject: [PATCH 18/29] fix: remove optionnal for mandatory fields --- src/users-creation/dto/create-user-refering.dto.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/users-creation/dto/create-user-refering.dto.ts b/src/users-creation/dto/create-user-refering.dto.ts index 20e7e24c..d834197e 100644 --- a/src/users-creation/dto/create-user-refering.dto.ts +++ b/src/users-creation/dto/create-user-refering.dto.ts @@ -27,17 +27,14 @@ export class CreateUserReferingDto extends PickType(User, [ ] as const) { @ApiProperty() @IsNumber() - @IsOptional() gender: Gender; @ApiProperty() @IsString() - @IsOptional() program: Program; @ApiProperty() @IsString() - @IsOptional() birthDate: Date; @ApiProperty() @@ -51,12 +48,10 @@ export class CreateUserReferingDto extends PickType(User, [ @ApiProperty() @IsArray() - @IsOptional() helpNeeds?: HelpNeed[]; @ApiProperty() @IsString() - @IsOptional() workingRight?: CandidateYesNoNSPPValue; @ApiProperty() From 46e18aecc8be976e612e6543f11de6e13b2a56ec Mon Sep 17 00:00:00 2001 From: Guillaume Cauchois Date: Mon, 25 Nov 2024 17:08:02 +0100 Subject: [PATCH 19/29] fix: tests --- tests/users/users.e2e-spec.ts | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/tests/users/users.e2e-spec.ts b/tests/users/users.e2e-spec.ts index 46c074de..b45cb2da 100644 --- a/tests/users/users.e2e-spec.ts +++ b/tests/users/users.e2e-spec.ts @@ -2390,14 +2390,33 @@ describe('Users', () => { ); }); - it('Should return 400, when try to list members with role referer', async () => { + it('Should return 200, and all the referers that match all the filters', async () => { + const organization = await organizationFactory.create({}, {}, true); + + const referers = await databaseHelper.createEntities( + userFactory, + 2, + { + firstName: 'XXX', + role: UserRoles.REFERER, + zone: AdminZones.LYON, + OrganizationId: organization.id, + } + ); + + const expectedCoachesIds = [...referers.map(({ id }) => id)]; + const response: APIResponse = await request(server) .get( - `${route}/members?limit=50&offset=0&role[]=${UserRoles.REFERER}&query=XXX&zone[]=${AdminZones.LYON}&associatedUser[]=true` + `${route}/members?limit=50&offset=0&role[]=${UserRoles.REFERER}&query=XXX&zone[]=${AdminZones.LYON}` ) .set('authorization', `Bearer ${loggedInAdmin.token}`); - expect(response.status).toBe(400); + expect(response.status).toBe(200); + expect(response.body.length).toBe(2); + expect(expectedCoachesIds).toEqual( + expect.arrayContaining(response.body.map(({ id }) => id)) + ); }); }); }); From c4d65c9c39bbffdf27b4997535d13d365b8d5357 Mon Sep 17 00:00:00 2001 From: Guillaume Cauchois Date: Tue, 26 Nov 2024 13:00:55 +0100 Subject: [PATCH 20/29] feat: remove organization countCoaches --- src/organizations/organizations.controller.ts | 4 +--- src/users/users.service.ts | 8 -------- tests/organizations/organizations.e2e-spec.ts | 6 +++--- 3 files changed, 4 insertions(+), 14 deletions(-) diff --git a/src/organizations/organizations.controller.ts b/src/organizations/organizations.controller.ts index 39f84b59..e56a5d88 100644 --- a/src/organizations/organizations.controller.ts +++ b/src/organizations/organizations.controller.ts @@ -55,17 +55,15 @@ export class OrganizationsController { return Promise.all( organizations.map(async (organization) => { - const { candidatesCount, coachesCount, referersCount } = + const { candidatesCount, referersCount } = await this.organizationsService.countAssociatedUsers(organization.id); return { ...(organization.toJSON() as Organization), candidatesCount, - coachesCount, referersCount, } as Organization & { candidatesCount: number; - coachesCount: number; referersCount: number; }; }) diff --git a/src/users/users.service.ts b/src/users/users.service.ts index 6763a7b1..830b6cc4 100644 --- a/src/users/users.service.ts +++ b/src/users/users.service.ts @@ -512,13 +512,6 @@ export class UsersService { }, }); - const { count: coachesCount } = await this.userModel.findAndCountAll({ - where: { - OrganizationId: organizationId, - role: UserRoles.COACH, - }, - }); - const { count: referersCount } = await this.userModel.findAndCountAll({ where: { OrganizationId: organizationId, @@ -528,7 +521,6 @@ export class UsersService { return { candidatesCount, - coachesCount, referersCount, }; } diff --git a/tests/organizations/organizations.e2e-spec.ts b/tests/organizations/organizations.e2e-spec.ts index 471c59e2..93ef85d7 100644 --- a/tests/organizations/organizations.e2e-spec.ts +++ b/tests/organizations/organizations.e2e-spec.ts @@ -237,7 +237,7 @@ describe('Organizations', () => { await databaseHelper.createEntities( userFactory, 7, - { role: UserRoles.COACH, OrganizationId: organization.id }, + { role: UserRoles.REFERER, OrganizationId: organization.id }, {}, true ); @@ -285,15 +285,15 @@ describe('Organizations', () => { expect.arrayContaining(response.body.map(({ id }) => id)) ); }); - it('Should return 200 and all organizations candidates and coaches count when admin gets organizations ', async () => { + it('Should return 200 and all organizations candidates and referers count when admin gets organizations ', async () => { const response: APIResponse = await request(server).get(`${route}/?limit=50&offset=0`); expect(response.status).toBe(200); expect(response.body).toEqual( expect.arrayContaining([ expect.objectContaining({ - coachesCount: 7, candidatesCount: 3, + refererCount: 7, }), ]) ); From b2c7b9ab327a642ef9589f6484abf3bcaef52b7c Mon Sep 17 00:00:00 2001 From: Guillaume Cauchois Date: Tue, 26 Nov 2024 13:01:57 +0100 Subject: [PATCH 21/29] fix: orienteur to prescripteur --- src/users/users.types.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/users/users.types.ts b/src/users/users.types.ts index d1ff1a28..ccc5fecb 100644 --- a/src/users/users.types.ts +++ b/src/users/users.types.ts @@ -14,14 +14,14 @@ import { export const UserRoles = { CANDIDATE: 'Candidat', COACH: 'Coach', - REFERER: 'Orienteur', + REFERER: 'Prescripteur', ADMIN: 'Admin', } as const; export type UserRole = (typeof UserRoles)[keyof typeof UserRoles]; export const Permissions = { - REFERER: 'Orienteur', + REFERER: 'Prescripteur', CANDIDATE: 'Candidat', COACH: 'Coach', RESTRICTED_COACH: 'Restricted_Coach', From a6b9f6f605ad36194b57ce268c91031303c84461 Mon Sep 17 00:00:00 2001 From: Guillaume Cauchois Date: Tue, 26 Nov 2024 14:18:45 +0100 Subject: [PATCH 22/29] fix: tests --- tests/organizations/organizations.e2e-spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/organizations/organizations.e2e-spec.ts b/tests/organizations/organizations.e2e-spec.ts index 93ef85d7..2fc993da 100644 --- a/tests/organizations/organizations.e2e-spec.ts +++ b/tests/organizations/organizations.e2e-spec.ts @@ -293,7 +293,7 @@ describe('Organizations', () => { expect.arrayContaining([ expect.objectContaining({ candidatesCount: 3, - refererCount: 7, + referersCount: 7, }), ]) ); From f4c761fe27e121690aa2243e61958bc42daa7f21 Mon Sep 17 00:00:00 2001 From: "Dorian P." Date: Tue, 26 Nov 2024 16:01:47 +0100 Subject: [PATCH 23/29] fix condition send mail - sending admin new referer notification email (#239) --- .../mailjet/mailjet.types.ts | 1 + src/mails/mails.service.ts | 17 ++++++++++++++--- .../users-creation.controller.ts | 19 ++++++++----------- src/users-creation/users-creation.service.ts | 8 +++++--- 4 files changed, 28 insertions(+), 17 deletions(-) diff --git a/src/external-services/mailjet/mailjet.types.ts b/src/external-services/mailjet/mailjet.types.ts index 714fa9d6..ba919039 100644 --- a/src/external-services/mailjet/mailjet.types.ts +++ b/src/external-services/mailjet/mailjet.types.ts @@ -97,6 +97,7 @@ export const MailjetTemplates = { ONBOARDING_J3_PROFILE_COMPLETION: 6129711, REFERER_ONBOARDING_CONFIRMATION: 6324339, REFERED_CANDIDATE_FINALIZE_ACCOUNT: 6324039, + ADMIN_NEW_REFERER_NOTIFICATION: 6328158, } as const; export type MailjetTemplateKey = keyof typeof MailjetTemplates; diff --git a/src/mails/mails.service.ts b/src/mails/mails.service.ts index 59d7cb57..fb8c21b8 100644 --- a/src/mails/mails.service.ts +++ b/src/mails/mails.service.ts @@ -79,9 +79,7 @@ export class MailsService { }); } - async sendWelcomeMail( - user: Pick - ) { + async sendWelcomeMail(user: User) { const { candidatesAdminMail } = getAdminMailsFromZone(user.zone); if (user.role === UserRoles.COACH) { @@ -866,6 +864,19 @@ export class MailsService { }, }); } + + async sendAdminNewRefererNotificationMail(referer: User) { + const adminFromZone = getAdminMailsFromZone(referer.zone); + await this.queuesService.addToWorkQueue(Jobs.SEND_MAIL, { + toEmail: adminFromZone.candidatesAdminMail, + templateId: MailjetTemplates.ADMIN_NEW_REFERER_NOTIFICATION, + variables: { + refererFirstName: referer.firstName, + refererLastName: referer.lastName, + refererProfileUrl: `${process.env.FRONTEND_URL}/backoffice/admin/membres/${referer.id}`, + }, + }); + } } const getRoleString = (user: User): string => { diff --git a/src/users-creation/users-creation.controller.ts b/src/users-creation/users-creation.controller.ts index 72fd68fe..620df3ad 100644 --- a/src/users-creation/users-creation.controller.ts +++ b/src/users-creation/users-creation.controller.ts @@ -231,17 +231,14 @@ export class UsersCreationController { gender: createUserRegistrationDto.gender, }); - if ( - createUserRegistrationDto.program === Programs.BOOST || - createUserRegistrationDto.role === UserRoles.REFERER - ) { - await this.usersCreationService.sendWelcomeMail({ - id: createdUser.id, - firstName: createdUser.firstName, - role: createdUser.role, - zone: createdUser.zone, - email: createdUser.email, - }); + if (createUserRegistrationDto.program === Programs.BOOST) { + await this.usersCreationService.sendWelcomeMail(createdUser); + } + + if (createUserRegistrationDto.role === UserRoles.REFERER) { + await this.usersCreationService.sendAdminNewRefererNotificationMail( + createdUser + ); } await this.usersCreationService.sendVerificationMail(createdUser); diff --git a/src/users-creation/users-creation.service.ts b/src/users-creation/users-creation.service.ts index e8487fb0..da26088e 100644 --- a/src/users-creation/users-creation.service.ts +++ b/src/users-creation/users-creation.service.ts @@ -72,9 +72,7 @@ export class UsersCreationService { return this.mailsService.sendNewAccountMail(user, token); } - async sendWelcomeMail( - user: Pick - ) { + async sendWelcomeMail(user: User) { return this.mailsService.sendWelcomeMail(user); } @@ -95,6 +93,10 @@ export class UsersCreationService { return this.usersService.sendMailsAfterMatching(candidateId); } + async sendAdminNewRefererNotificationMail(referer: User) { + return this.mailsService.sendAdminNewRefererNotificationMail(referer); + } + async updateUserCandidatByCandidateId( candidateId: string, updateUserCandidatDto: Partial From dab413cde14e81470413c0de255d1ddcc536da7d Mon Sep 17 00:00:00 2001 From: "Dorian P." Date: Tue, 26 Nov 2024 16:42:03 +0100 Subject: [PATCH 24/29] Created mailer method and using it in auth controller (#238) --- src/auth/auth.controller.ts | 5 +++++ src/auth/auth.service.ts | 10 ++++++++++ src/external-services/mailjet/mailjet.types.ts | 1 + src/mails/mails.service.ts | 16 ++++++++++++++++ 4 files changed, 32 insertions(+) diff --git a/src/auth/auth.controller.ts b/src/auth/auth.controller.ts index b0fa6095..09d689e7 100644 --- a/src/auth/auth.controller.ts +++ b/src/auth/auth.controller.ts @@ -285,6 +285,11 @@ export class AuthController { throw new NotFoundException(); } + await this.authService.sendRefererCandidateHasVerifiedAccountMail( + updatedUser.referer, + updatedUser + ); + return updatedUser.email; } } diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts index 4b9a0508..5f12e6dd 100644 --- a/src/auth/auth.service.ts +++ b/src/auth/auth.service.ts @@ -137,6 +137,16 @@ export class AuthService { return this.mailsService.sendVerificationMail(user, token); } + async sendRefererCandidateHasVerifiedAccountMail( + referer: User, + candidate: User + ) { + return this.mailsService.sendRefererCandidateHasVerifiedAccountMail( + referer, + candidate + ); + } + async generateVerificationToken(user: User) { return this.jwtService.sign( { sub: user.id }, diff --git a/src/external-services/mailjet/mailjet.types.ts b/src/external-services/mailjet/mailjet.types.ts index ba919039..6e3a3453 100644 --- a/src/external-services/mailjet/mailjet.types.ts +++ b/src/external-services/mailjet/mailjet.types.ts @@ -96,6 +96,7 @@ export const MailjetTemplates = { ONBOARDING_J1_BAO: 6129684, ONBOARDING_J3_PROFILE_COMPLETION: 6129711, REFERER_ONBOARDING_CONFIRMATION: 6324339, + REFERER_CANDIDATE_HAS_FINALIZED_ACCOUNT: 6482813, REFERED_CANDIDATE_FINALIZE_ACCOUNT: 6324039, ADMIN_NEW_REFERER_NOTIFICATION: 6328158, } as const; diff --git a/src/mails/mails.service.ts b/src/mails/mails.service.ts index fb8c21b8..01e12329 100644 --- a/src/mails/mails.service.ts +++ b/src/mails/mails.service.ts @@ -865,6 +865,22 @@ export class MailsService { }); } + async sendRefererCandidateHasVerifiedAccountMail( + referer: User, + candidate: User + ) { + await this.queuesService.addToWorkQueue(Jobs.SEND_MAIL, { + toEmail: referer.email, + templateId: MailjetTemplates.REFERER_CANDIDATE_HAS_FINALIZED_ACCOUNT, + variables: { + candidateFirstName: candidate.firstName, + candidateLastName: candidate.lastName, + refererFirstName: referer.firstName, + zone: candidate.zone, + }, + }); + } + async sendAdminNewRefererNotificationMail(referer: User) { const adminFromZone = getAdminMailsFromZone(referer.zone); await this.queuesService.addToWorkQueue(Jobs.SEND_MAIL, { From fb4479be5748e5b89d390369d9f70fc09ad2d12e Mon Sep 17 00:00:00 2001 From: Guillaume Cauchois Date: Wed, 27 Nov 2024 13:47:41 +0100 Subject: [PATCH 25/29] feat: add referer tests --- tests/users/users.e2e-spec.ts | 589 +++++++++++++++++++++++++++++++++- 1 file changed, 587 insertions(+), 2 deletions(-) diff --git a/tests/users/users.e2e-spec.ts b/tests/users/users.e2e-spec.ts index 46c074de..dab7aa20 100644 --- a/tests/users/users.e2e-spec.ts +++ b/tests/users/users.e2e-spec.ts @@ -1103,6 +1103,499 @@ describe('Users', () => { expect(response.status).toBe(409); }); }); + describe('/refering - Create user through refering', () => { + let loggedInReferer: LoggedUser; + let loggedInCandidate: LoggedUser; + let loggedInCoach: LoggedUser; + let organization: Organization; + + beforeEach(async () => { + organization = await organizationFactory.create({}, {}, true); + loggedInReferer = await usersHelper.createLoggedInUser({ + role: UserRoles.REFERER, + OrganizationId: organization.id, + }); + loggedInCandidate = await usersHelper.createLoggedInUser({ + role: UserRoles.CANDIDATE, + OrganizationId: organization.id, + }); + loggedInCoach = await usersHelper.createLoggedInUser({ + role: UserRoles.COACH, + OrganizationId: organization.id, + }); + }); + + it('Should return 200 and a created candidate if valid candidate data', async () => { + const user = await userFactory.create( + { role: UserRoles.CANDIDATE }, + {}, + false + ); + + const helpNeeds: { name: HelpValue }[] = [ + { name: 'cv' }, + { name: 'interview' }, + ]; + + const userValues = { + firstName: user.firstName, + lastName: user.lastName, + email: user.email, + phone: user.phone, + gender: user.gender, + }; + + const userProfileValues = { + helpNeeds: helpNeeds, + department: 'Paris (75)' as Department, + }; + + const userToSend = { + ...userValues, + ...userProfileValues, + campaign: '1234', + workingRight: CandidateYesNoNSPP.YES, + program: Programs.THREE_SIXTY, + birthDate: '1996-24-04', + nationality: Nationalities.EUROPEAN, + accommodation: CandidateAccommodations.INSERTION, + hasSocialWorker: YesNoJNSPR.YES, + resources: CandidateResources.AAH, + studiesLevel: StudiesLevels.BAC, + workingExperience: WorkingExperienceYears.BETWEEN_3_AND_10_YEARS, + jobSearchDuration: JobSearchDurations.BETWEEN_12_AND_24_MONTHS, + searchBusinessLines: [{ name: 'id' }] as BusinessLine[], + searchAmbitions: [{ name: 'développeur' }] as Ambition[], + }; + + const response: APIResponse< + UsersCreationController['createUserRefering'] + > = await request(server) + .post(`${route}/refering`) + .set('authorization', `Bearer ${loggedInReferer.token}`) + .send(userToSend); + expect(response.status).toBe(201); + expect(response.body).toEqual( + expect.objectContaining({ + ...userValues, + zone: getZoneFromDepartment(userProfileValues.department), + userProfile: expect.objectContaining({ + department: userProfileValues.department, + helpNeeds: expect.arrayContaining( + userProfileValues.helpNeeds.map((expectation) => + expect.objectContaining(expectation) + ) + ), + }), + }) + ); + }); + + it('Should return 200 and a created candidate if valid candidate data with minimum data', async () => { + const user = await userFactory.create( + { role: UserRoles.CANDIDATE }, + {}, + false + ); + + const helpNeeds: { name: HelpValue }[] = [{ name: 'cv' }]; + + const userValues = { + firstName: user.firstName, + lastName: user.lastName, + email: user.email, + phone: user.phone, + gender: user.gender, + }; + + const userProfileValues = { + helpNeeds: helpNeeds, + department: 'Paris (75)' as Department, + }; + + const userToSend = { + ...userValues, + ...userProfileValues, + program: Programs.THREE_SIXTY, + workingRight: CandidateYesNoNSPP.YES, + birthDate: '1996-24-04', + searchBusinessLines: [{ name: 'id' }] as BusinessLine[], + }; + + const response: APIResponse< + UsersCreationController['createUserRefering'] + > = await request(server) + .post(`${route}/refering`) + .set('authorization', `Bearer ${loggedInReferer.token}`) + .send(userToSend); + expect(response.status).toBe(201); + expect(response.body).toEqual( + expect.objectContaining({ + ...userValues, + zone: getZoneFromDepartment(userProfileValues.department), + userProfile: expect.objectContaining({ + department: userProfileValues.department, + helpNeeds: expect.arrayContaining( + userProfileValues.helpNeeds.map((expectation) => + expect.objectContaining(expectation) + ) + ), + }), + }) + ); + }); + + it('Should return 400 when has missing mandatory fields', async () => { + const user = await userFactory.create( + { role: UserRoles.CANDIDATE }, + {}, + false + ); + + const helpNeeds: { name: HelpValue }[] = []; + + const userValues = { + firstName: user.firstName, + lastName: user.lastName, + email: user.email, + }; + + const userProfileValues = { + helpNeeds: helpNeeds, + department: 'Paris (75)' as Department, + }; + + const userToSend = { + ...userValues, + ...userProfileValues, + campaign: '1234', + workingRight: CandidateYesNoNSPP.YES, + birthDate: '1996-24-04', + }; + + const response: APIResponse< + UsersCreationController['createUserRefering'] + > = await request(server) + .post(`${route}/refering`) + .set('authorization', `Bearer ${loggedInReferer.token}`) + .send(userToSend); + expect(response.status).toBe(400); + }); + + it('Should return 400 when has invalid email', async () => { + const user = await userFactory.create( + { role: UserRoles.CANDIDATE }, + {}, + false + ); + + const helpNeeds: { name: HelpValue }[] = [ + { name: 'cv' }, + { name: 'interview' }, + ]; + + const userValues = { + firstName: user.firstName, + lastName: user.lastName, + email: 'email.fr', // This is the incorrect data + phone: user.phone, + gender: user.gender, + }; + + const userProfileValues = { + helpNeeds: helpNeeds, + department: 'Paris (75)' as Department, + }; + + const userToSend = { + ...userValues, + ...userProfileValues, + campaign: '1234', + workingRight: CandidateYesNoNSPP.YES, + program: Programs.THREE_SIXTY, + birthDate: '1996-24-04', + nationality: Nationalities.EUROPEAN, + accommodation: CandidateAccommodations.INSERTION, + hasSocialWorker: YesNoJNSPR.YES, + resources: CandidateResources.AAH, + studiesLevel: StudiesLevels.BAC, + workingExperience: WorkingExperienceYears.BETWEEN_3_AND_10_YEARS, + jobSearchDuration: JobSearchDurations.BETWEEN_12_AND_24_MONTHS, + searchAmbitions: [{ name: 'développeur' }] as Ambition[], + }; + + const response: APIResponse< + UsersCreationController['createUserRefering'] + > = await request(server) + .post(`${route}/refering`) + .set('authorization', `Bearer ${loggedInReferer.token}`) + .send(userToSend); + expect(response.status).toBe(400); + }); + + it('Should return 400 when has invalid email', async () => { + const user = await userFactory.create( + { role: UserRoles.CANDIDATE }, + {}, + false + ); + + const helpNeeds: { name: HelpValue }[] = [ + { name: 'cv' }, + { name: 'interview' }, + ]; + + const userValues = { + firstName: user.firstName, + lastName: user.lastName, + email: 'email.fr', // This is the incorrect data + phone: user.phone, + gender: user.gender, + }; + + const userProfileValues = { + helpNeeds: helpNeeds, + department: 'Paris (75)' as Department, + }; + + const userToSend = { + ...userValues, + ...userProfileValues, + campaign: '1234', + workingRight: CandidateYesNoNSPP.YES, + program: Programs.THREE_SIXTY, + birthDate: '1996-24-04', + nationality: Nationalities.EUROPEAN, + accommodation: CandidateAccommodations.INSERTION, + hasSocialWorker: YesNoJNSPR.YES, + resources: CandidateResources.AAH, + studiesLevel: StudiesLevels.BAC, + workingExperience: WorkingExperienceYears.BETWEEN_3_AND_10_YEARS, + jobSearchDuration: JobSearchDurations.BETWEEN_12_AND_24_MONTHS, + searchAmbitions: [{ name: 'développeur' }] as Ambition[], + }; + + const response: APIResponse< + UsersCreationController['createUserRefering'] + > = await request(server) + .post(`${route}/refering`) + .set('authorization', `Bearer ${loggedInReferer.token}`) + .send(userToSend); + expect(response.status).toBe(400); + }); + + it('Should return 400 when has invalid phone', async () => { + const user = await userFactory.create( + { role: UserRoles.CANDIDATE }, + {}, + false + ); + + const helpNeeds: { name: HelpValue }[] = [ + { name: 'cv' }, + { name: 'interview' }, + ]; + + const userValues = { + firstName: user.firstName, + lastName: user.lastName, + email: user.email, + phone: '1234', // This is the incorrect data + gender: user.gender, + }; + + const userProfileValues = { + helpNeeds: helpNeeds, + department: 'Paris (75)' as Department, + }; + + const userToSend = { + ...userValues, + ...userProfileValues, + campaign: '1234', + workingRight: CandidateYesNoNSPP.YES, + program: Programs.THREE_SIXTY, + birthDate: '1996-24-04', + nationality: Nationalities.EUROPEAN, + accommodation: CandidateAccommodations.INSERTION, + hasSocialWorker: YesNoJNSPR.YES, + resources: CandidateResources.AAH, + studiesLevel: StudiesLevels.BAC, + workingExperience: WorkingExperienceYears.BETWEEN_3_AND_10_YEARS, + jobSearchDuration: JobSearchDurations.BETWEEN_12_AND_24_MONTHS, + searchAmbitions: [{ name: 'développeur' }] as Ambition[], + }; + + const response: APIResponse< + UsersCreationController['createUserRefering'] + > = await request(server) + .post(`${route}/refering`) + .set('authorization', `Bearer ${loggedInReferer.token}`) + .send(userToSend); + expect(response.status).toBe(400); + }); + + it('Should return 403 when a candidate referer another valid candidate', async () => { + const user = await userFactory.create( + { role: UserRoles.CANDIDATE }, + {}, + false + ); + + const helpNeeds: { name: HelpValue }[] = [ + { name: 'cv' }, + { name: 'interview' }, + ]; + + const userValues = { + firstName: user.firstName, + lastName: user.lastName, + email: user.email, + phone: user.phone, + gender: user.gender, + }; + + const userProfileValues = { + helpNeeds: helpNeeds, + department: 'Paris (75)' as Department, + }; + + const userToSend = { + ...userValues, + ...userProfileValues, + campaign: '1234', + workingRight: CandidateYesNoNSPP.YES, + program: Programs.THREE_SIXTY, + birthDate: '1996-24-04', + nationality: Nationalities.EUROPEAN, + accommodation: CandidateAccommodations.INSERTION, + hasSocialWorker: YesNoJNSPR.YES, + resources: CandidateResources.AAH, + studiesLevel: StudiesLevels.BAC, + workingExperience: WorkingExperienceYears.BETWEEN_3_AND_10_YEARS, + jobSearchDuration: JobSearchDurations.BETWEEN_12_AND_24_MONTHS, + searchBusinessLines: [{ name: 'id' }] as BusinessLine[], + searchAmbitions: [{ name: 'développeur' }] as Ambition[], + }; + + const response: APIResponse< + UsersCreationController['createUserRefering'] + > = await request(server) + .post(`${route}/refering`) + .set('authorization', `Bearer ${loggedInCandidate.token}`) + .send(userToSend); + expect(response.status).toBe(403); + }); + + it('Should return 403 when a candidate referer another valid coach', async () => { + const user = await userFactory.create( + { role: UserRoles.CANDIDATE }, + {}, + false + ); + + const helpNeeds: { name: HelpValue }[] = [ + { name: 'cv' }, + { name: 'interview' }, + ]; + + const userValues = { + firstName: user.firstName, + lastName: user.lastName, + email: user.email, + phone: user.phone, + gender: user.gender, + }; + + const userProfileValues = { + helpNeeds: helpNeeds, + department: 'Paris (75)' as Department, + }; + + const userToSend = { + ...userValues, + ...userProfileValues, + campaign: '1234', + workingRight: CandidateYesNoNSPP.YES, + program: Programs.THREE_SIXTY, + birthDate: '1996-24-04', + nationality: Nationalities.EUROPEAN, + accommodation: CandidateAccommodations.INSERTION, + hasSocialWorker: YesNoJNSPR.YES, + resources: CandidateResources.AAH, + studiesLevel: StudiesLevels.BAC, + workingExperience: WorkingExperienceYears.BETWEEN_3_AND_10_YEARS, + jobSearchDuration: JobSearchDurations.BETWEEN_12_AND_24_MONTHS, + searchBusinessLines: [{ name: 'id' }] as BusinessLine[], + searchAmbitions: [{ name: 'développeur' }] as Ambition[], + }; + + const response: APIResponse< + UsersCreationController['createUserRefering'] + > = await request(server) + .post(`${route}/refering`) + .set('authorization', `Bearer ${loggedInCoach.token}`) + .send(userToSend); + expect(response.status).toBe(403); + }); + + it('Should return 409 when the email already exist', async () => { + const existingUser = await userFactory.create({}, {}, true); + + const user = await userFactory.create( + { + role: UserRoles.CANDIDATE, + email: existingUser.email, + }, + {}, + false + ); + + const helpNeeds: { name: HelpValue }[] = [ + { name: 'cv' }, + { name: 'interview' }, + ]; + + const userValues = { + firstName: user.firstName, + lastName: user.lastName, + email: user.email, + phone: user.phone, + gender: user.gender, + }; + + const userProfileValues = { + helpNeeds: helpNeeds, + department: 'Paris (75)' as Department, + }; + + const userToSend = { + ...userValues, + ...userProfileValues, + campaign: '1234', + workingRight: CandidateYesNoNSPP.YES, + program: Programs.THREE_SIXTY, + birthDate: '1996-24-04', + nationality: Nationalities.EUROPEAN, + accommodation: CandidateAccommodations.INSERTION, + hasSocialWorker: YesNoJNSPR.YES, + resources: CandidateResources.AAH, + studiesLevel: StudiesLevels.BAC, + workingExperience: WorkingExperienceYears.BETWEEN_3_AND_10_YEARS, + jobSearchDuration: JobSearchDurations.BETWEEN_12_AND_24_MONTHS, + searchBusinessLines: [{ name: 'id' }] as BusinessLine[], + searchAmbitions: [{ name: 'développeur' }] as Ambition[], + }; + + const response: APIResponse< + UsersCreationController['createUserRefering'] + > = await request(server) + .post(`${route}/refering`) + .set('authorization', `Bearer ${loggedInReferer.token}`) + .send(userToSend); + expect(response.status).toBe(409); + }); + }); }); describe('R - Read 1 User', () => { describe('/:id - Get a user by id or email', () => { @@ -3846,6 +4339,18 @@ describe('Users', () => { .set('authorization', `Bearer ${loggedInCoach.token}`); expect(response.status).toBe(200); }); + it('Should return 200 if user is logged in as referer', async () => { + const loggedInReferer = await usersHelper.createLoggedInUser({ + role: UserRoles.REFERER, + }); + const response: APIResponse = + await request(server) + .get( + `${route}/profile?offset=0&limit=25&role[]=${UserRoles.CANDIDATE}` + ) + .set('authorization', `Bearer ${loggedInReferer.token}`); + expect(response.status).toBe(200); + }); it('Should return 400 if no offset or limit parameter', async () => { const loggedInCandidate = await usersHelper.createLoggedInUser({ role: UserRoles.CANDIDATE, @@ -4914,6 +5419,7 @@ describe('Users', () => { let loggedInAdmin: LoggedUser; let loggedInCandidate: LoggedUser; let loggedInCoach: LoggedUser; + let loggedInReferer: LoggedUser; beforeEach(async () => { loggedInAdmin = await usersHelper.createLoggedInUser({ @@ -4925,6 +5431,9 @@ describe('Users', () => { loggedInCoach = await usersHelper.createLoggedInUser({ role: UserRoles.COACH, }); + loggedInReferer = await usersHelper.createLoggedInUser({ + role: UserRoles.REFERER, + }); }); it('Should return 401 if user is not logged in', async () => { const updates = await userFactory.create({}, {}, false); @@ -4972,6 +5481,23 @@ describe('Users', () => { expect(response.body.phone).toEqual(updates.phone); expect(response.body.address).toEqual(updates.address); }); + it('Should return 200 and updated user when a referer update himself', async () => { + const updates = await userFactory.create({}, {}, false); + const response: APIResponse = + await request(server) + .put(`${route}/${loggedInReferer.user.id}`) + .set('authorization', `Bearer ${loggedInReferer.token}`) + .send({ + phone: updates.phone, + address: updates.address, + email: updates.email, + firstName: updates.firstName, + lastName: updates.lastName, + }); + expect(response.status).toBe(200); + expect(response.body.phone).toEqual(updates.phone); + expect(response.body.address).toEqual(updates.address); + }); it('Should return 400 when a candidate update himself with invalid phone', async () => { const response: APIResponse = await request(server) @@ -5513,6 +6039,7 @@ describe('Users', () => { let loggedInAdmin: LoggedUser; let loggedInCandidate: LoggedUser; let loggedInCoach: LoggedUser; + let loggedInReferer: LoggedUser; beforeEach(async () => { loggedInAdmin = await usersHelper.createLoggedInUser({ @@ -5524,6 +6051,9 @@ describe('Users', () => { const coach = await userFactory.create({ role: UserRoles.COACH, }); + const referer = await userFactory.create({ + role: UserRoles.REFERER, + }); await userCandidatsHelper.associateCoachAndCandidate(coach, candidat); loggedInCandidate = await usersHelper.createLoggedInUser( candidat, @@ -5535,6 +6065,11 @@ describe('Users', () => { {}, false ); + loggedInReferer = await usersHelper.createLoggedInUser( + referer, + {}, + false + ); }); it('Should return 401, if user not logged in', async () => { const response: APIResponse< @@ -5553,6 +6088,14 @@ describe('Users', () => { .set('authorization', `Bearer ${loggedInAdmin.token}`); expect(response.status).toBe(403); }); + it('Should return 403, if referer checks if note has been updated', async () => { + const response: APIResponse< + UsersController['checkNoteHasBeenModified'] + > = await request(server) + .get(`${route}/candidate/checkUpdate/${loggedInCandidate.user.id}`) + .set('authorization', `Bearer ${loggedInReferer.token}`); + expect(response.status).toBe(403); + }); it('Should return 200 and noteHasBeenModified, if coach checks if note has been updated', async () => { await userCandidatsHelper.setLastModifiedBy( loggedInCandidate.user.id, @@ -5641,6 +6184,7 @@ describe('Users', () => { let loggedInAdmin: LoggedUser; let loggedInCandidate: LoggedUser; let loggedInCoach: LoggedUser; + let loggedInReferer: LoggedUser; beforeEach(async () => { loggedInAdmin = await usersHelper.createLoggedInUser({ @@ -5652,6 +6196,9 @@ describe('Users', () => { loggedInCoach = await usersHelper.createLoggedInUser({ role: UserRoles.COACH, }); + loggedInReferer = await usersHelper.createLoggedInUser({ + role: UserRoles.REFERER, + }); }); it('Should return 401, if user not logged in', async () => { const response: APIResponse = @@ -5668,6 +6215,13 @@ describe('Users', () => { .set('authorization', `Bearer ${loggedInAdmin.token}`); expect(response.status).toBe(403); }); + it('Should return 403, if referer sets the note has been read', async () => { + const response: APIResponse = + await request(server) + .put(`${route}/candidate/read/${loggedInCandidate.user.id}`) + .set('authorization', `Bearer ${loggedInReferer.token}`); + expect(response.status).toBe(403); + }); it('Should return 403, if coach sets the note has been read on candidate not related', async () => { const response: APIResponse = await request(server) @@ -6132,6 +6686,7 @@ describe('Users', () => { let loggedInAdmin: LoggedUser; let loggedInCandidate: LoggedUser; let loggedInCoach: LoggedUser; + let loggedInReferer: LoggedUser; beforeEach(async () => { loggedInAdmin = await usersHelper.createLoggedInUser({ @@ -6143,6 +6698,9 @@ describe('Users', () => { loggedInCoach = await usersHelper.createLoggedInUser({ role: UserRoles.COACH, }); + loggedInReferer = await usersHelper.createLoggedInUser({ + role: UserRoles.REFERER, + }); }); it('Should return 200, and updated opportunities ids, if admin bulk updates some users', async () => { @@ -6193,7 +6751,7 @@ describe('Users', () => { ]) ); }); - it('Should return 403, if not logged in as candidate', async () => { + it('Should return 403, if logged in as candidate', async () => { const originalUsers = await databaseHelper.createEntities( userFactory, 5, @@ -6219,7 +6777,7 @@ describe('Users', () => { }); expect(responseCandidate.status).toBe(403); }); - it('Should return 403, if not logged in as coach', async () => { + it('Should return 403, if logged in as coach', async () => { const originalUsers = await databaseHelper.createEntities( userFactory, 5, @@ -6245,6 +6803,33 @@ describe('Users', () => { }); expect(responseCoach.status).toBe(403); }); + + it('Should return 403, if logged in as referer', async () => { + const originalUsers = await databaseHelper.createEntities( + userFactory, + 5, + { + role: UserRoles.CANDIDATE, + }, + { + userCandidat: { hidden: true }, + } + ); + const originalUsersIds = originalUsers.map(({ id }) => { + return id; + }); + const responseCoach: APIResponse = + await request(server) + .put(`${route}/candidate/bulk`) + .set('authorization', `Bearer ${loggedInReferer.token}`) + .send({ + attributes: { + hidden: true, + }, + ids: originalUsersIds, + }); + expect(responseCoach.status).toBe(403); + }); }); }); // TODO put in unit tests From e5e6163f4d1e096daed4aa4699905afcc715a0c7 Mon Sep 17 00:00:00 2001 From: Guillaume Cauchois Date: Wed, 27 Nov 2024 16:09:57 +0100 Subject: [PATCH 26/29] fix: merge conflict --- src/mails/mails.service.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/mails/mails.service.ts b/src/mails/mails.service.ts index 7b321471..d91915a5 100644 --- a/src/mails/mails.service.ts +++ b/src/mails/mails.service.ts @@ -79,7 +79,9 @@ export class MailsService { }); } - async sendWelcomeMail(user: User) { + async sendWelcomeMail( + user: Pick + ) { const { candidatesAdminMail } = getAdminMailsFromZone(user.zone); if (user.role === UserRoles.COACH) { From b90e0072cc5296c85bcc69a968efdcd8bc0c1671 Mon Sep 17 00:00:00 2001 From: Guillaume Cauchois Date: Wed, 27 Nov 2024 17:21:06 +0100 Subject: [PATCH 27/29] test: referer in messaging --- tests/messaging/messaging.e2e-spec.ts | 618 +++++++++++++++++++------- 1 file changed, 456 insertions(+), 162 deletions(-) diff --git a/tests/messaging/messaging.e2e-spec.ts b/tests/messaging/messaging.e2e-spec.ts index da3678d5..eb1fb422 100644 --- a/tests/messaging/messaging.e2e-spec.ts +++ b/tests/messaging/messaging.e2e-spec.ts @@ -31,8 +31,9 @@ describe('MESSAGING', () => { let conversationFactory: ConversationFactory; let userFactory: UserFactory; let loggedInCandidate: LoggedUser; + let loggedInCoach: LoggedUser; + let loggedInReferer: LoggedUser; let loggedInOtherCandidate: LoggedUser; - let coachs: User[]; beforeAll(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ @@ -67,13 +68,16 @@ describe('MESSAGING', () => { loggedInCandidate = await usersHelper.createLoggedInUser({ role: UserRoles.CANDIDATE, }); + loggedInCoach = await usersHelper.createLoggedInUser({ + role: UserRoles.COACH, + }); + loggedInReferer = await usersHelper.createLoggedInUser({ + role: UserRoles.REFERER, + }); loggedInOtherCandidate = await usersHelper.createLoggedInUser({ role: UserRoles.CANDIDATE, }); - - // Create the coach used for the conversations - coachs = await databaseHelper.createEntities(userFactory, NB_CONVERSATIONS); }); afterEach(async () => { @@ -85,13 +89,37 @@ describe('MESSAGING', () => { */ describe('CRUD Messages', () => { describe('C - Create Messages - /messaging/messages', () => { - it('should not create a conversation if a conversation between coach and candidate already exists', async () => { + it('should return 201 but not create a conversation if a conversation between coach and candidate already exists', async () => { + // Create the conversation + const conversation = await conversationFactory.create(); + + await messagingHelper.associationParticipantsToConversation( + conversation.id, + [loggedInCandidate.user.id, loggedInCoach.user.id] + ); + const nbConversationBefore = await messagingHelper.countConversation(); + + const response: APIResponse = + await request(server) + .post(`/messaging/messages`) + .send({ + content: 'Super message', + conversationId: conversation.id, + }) + .set('authorization', `Bearer ${loggedInCandidate.token}`); + const nbConversation = await messagingHelper.countConversation(); + expect(response.status).toBe(201); + expect(nbConversation).toBe(nbConversationBefore); + expect(nbConversation).toBe(1); + }); + + it('should return 201 but not create a conversation if a conversation between referer and candidate already exists', async () => { // Create the conversation const conversation = await conversationFactory.create(); await messagingHelper.associationParticipantsToConversation( conversation.id, - [loggedInCandidate.user.id, coachs[0].id] + [loggedInCandidate.user.id, loggedInReferer.user.id] ); const nbConversationBefore = await messagingHelper.countConversation(); @@ -109,13 +137,13 @@ describe('MESSAGING', () => { expect(nbConversation).toBe(1); }); - it('should create a message in the conversation', async () => { + it('should return 201 and create a message in the conversation between candidate and coach (sent by candidate)', async () => { // Create the conversation let conversation = await conversationFactory.create(); await messagingHelper.associationParticipantsToConversation( conversation.id, - [loggedInCandidate.user.id, coachs[0].id] + [loggedInCandidate.user.id, loggedInCoach.user.id] ); const response: APIResponse = @@ -132,13 +160,13 @@ describe('MESSAGING', () => { expect(conversation.messages.length).toBe(1); }); - it('should return 401 if the user is not a participant of the conversation', async () => { + it('should return 201 and create a message in the conversation between candidate and coach (sent by coach)', async () => { // Create the conversation - const conversation = await conversationFactory.create(); + let conversation = await conversationFactory.create(); await messagingHelper.associationParticipantsToConversation( conversation.id, - [loggedInOtherCandidate.user.id, coachs[0].id] + [loggedInCandidate.user.id, loggedInCoach.user.id] ); const response: APIResponse = @@ -148,48 +176,140 @@ describe('MESSAGING', () => { content: 'Super message', conversationId: conversation.id, }) - .set('authorization', `Bearer ${loggedInCandidate.token}`); + .set('authorization', `Bearer ${loggedInCoach.token}`); - expect(response.status).toBe(401); + conversation = await messagingHelper.findConversation(conversation.id); + expect(response.status).toBe(201); + expect(conversation.messages.length).toBe(1); }); - it('should return 400 if neither the participantIds nor the conversationId is provided', async () => { + it('should return 201 and create a message in the conversation between candidate and referer (sent by candidate)', async () => { + // Create the conversation + let conversation = await conversationFactory.create(); + + await messagingHelper.associationParticipantsToConversation( + conversation.id, + [loggedInCandidate.user.id, loggedInReferer.user.id] + ); + const response: APIResponse = await request(server) .post(`/messaging/messages`) .send({ content: 'Super message', + conversationId: conversation.id, }) .set('authorization', `Bearer ${loggedInCandidate.token}`); - expect(response.status).toBe(400); + + conversation = await messagingHelper.findConversation(conversation.id); + expect(response.status).toBe(201); + expect(conversation.messages.length).toBe(1); }); - it('should return 400 when the message is empty', async () => { + it('should return 201 and create a message in the conversation between candidate and referer (sent by referer)', async () => { + // Create the conversation + let conversation = await conversationFactory.create(); + + await messagingHelper.associationParticipantsToConversation( + conversation.id, + [loggedInCandidate.user.id, loggedInReferer.user.id] + ); + + const response: APIResponse = + await request(server) + .post(`/messaging/messages`) + .send({ + content: 'Super message', + conversationId: conversation.id, + }) + .set('authorization', `Bearer ${loggedInReferer.token}`); + + conversation = await messagingHelper.findConversation(conversation.id); + expect(response.status).toBe(201); + expect(conversation.messages.length).toBe(1); + }); + + it('should return 201 and create a message in the conversation between coach and referer (sent by coach)', async () => { + // Create the conversation + let conversation = await conversationFactory.create(); + + await messagingHelper.associationParticipantsToConversation( + conversation.id, + [loggedInCoach.user.id, loggedInReferer.user.id] + ); + + const response: APIResponse = + await request(server) + .post(`/messaging/messages`) + .send({ + content: 'Super message', + conversationId: conversation.id, + }) + .set('authorization', `Bearer ${loggedInCoach.token}`); + + conversation = await messagingHelper.findConversation(conversation.id); + expect(response.status).toBe(201); + expect(conversation.messages.length).toBe(1); + }); + + it('should return 201 and create a message in the conversation between coach and referer (sent by referer)', async () => { + // Create the conversation + let conversation = await conversationFactory.create(); + + await messagingHelper.associationParticipantsToConversation( + conversation.id, + [loggedInCoach.user.id, loggedInReferer.user.id] + ); + + const response: APIResponse = + await request(server) + .post(`/messaging/messages`) + .send({ + content: 'Super message', + conversationId: conversation.id, + }) + .set('authorization', `Bearer ${loggedInReferer.token}`); + + conversation = await messagingHelper.findConversation(conversation.id); + expect(response.status).toBe(201); + expect(conversation.messages.length).toBe(1); + }); + + it('should return 401 if the user is not a participant of the conversation', async () => { // Create the conversation const conversation = await conversationFactory.create(); await messagingHelper.associationParticipantsToConversation( conversation.id, - [loggedInCandidate.user.id, coachs[0].id] + [loggedInOtherCandidate.user.id, loggedInCoach.user.id] ); const response: APIResponse = await request(server) .post(`/messaging/messages`) .send({ + content: 'Super message', conversationId: conversation.id, - content: '', + }) + .set('authorization', `Bearer ${loggedInCandidate.token}`); + + expect(response.status).toBe(401); + }); + + it('should return 400 if neither the participantIds nor the conversationId is provided', async () => { + const response: APIResponse = + await request(server) + .post(`/messaging/messages`) + .send({ + content: 'Super message', }) .set('authorization', `Bearer ${loggedInCandidate.token}`); expect(response.status).toBe(400); }); - it('shoud return 201 when a message is sent by a coach', async () => { + it('should return 400 when the message is empty', async () => { // Create the conversation const conversation = await conversationFactory.create(); - const loggedInCoach = await usersHelper.createLoggedInUser({ - role: UserRoles.COACH, - }); await messagingHelper.associationParticipantsToConversation( conversation.id, @@ -200,11 +320,11 @@ describe('MESSAGING', () => { await request(server) .post(`/messaging/messages`) .send({ - content: 'Super message', conversationId: conversation.id, + content: '', }) - .set('authorization', `Bearer ${loggedInCoach.token}`); - expect(response.status).toBe(201); + .set('authorization', `Bearer ${loggedInCandidate.token}`); + expect(response.status).toBe(400); }); }); }); @@ -214,188 +334,322 @@ describe('MESSAGING', () => { */ describe('CRUD Conversations', () => { describe('C - Create conversations - /messaging/messages', () => { - it('should create a conversation if no conversation between coach and candidate doesn t exists', async () => { + it('should return 201 and create a conversation if no conversation between coach and candidate doesn t exists (initiated by Candidate)', async () => { const response: APIResponse = await request(server) .post(`/messaging/messages`) .send({ content: 'Super message', - participantIds: [coachs[0].id], + participantIds: [loggedInCoach.user.id], }) .set('authorization', `Bearer ${loggedInCandidate.token}`); const nbConversation = await messagingHelper.countConversation(); expect(response.status).toBe(201); expect(nbConversation).toBe(1); }); - }); - describe('R - Read conversations - /messaging/conversations', () => { - beforeEach(async () => { - // Create the conversations - const conversations = await databaseHelper.createEntities( - conversationFactory, - NB_CONVERSATIONS - ); - // And link the conversations to the coachs and the logged in candidate - const linkPromises = conversations.map((conversation, idx) => - messagingHelper.associationParticipantsToConversation( - conversation.id, - [loggedInCandidate.user.id, coachs[idx].id] - ) - ); - await Promise.all(linkPromises); - - // Add messages to the conversations - const messagePromises = conversations.map((conversation) => - messagingHelper.addMessagesToConversation( - 2, - conversation.id, - loggedInCandidate.user.id - ) - ); - - await Promise.all(messagePromises); + it('should return 201 and create a conversation if no conversation between coach and candidate doesn t exists (initiated by Coach)', async () => { + const response: APIResponse = + await request(server) + .post(`/messaging/messages`) + .send({ + content: 'Super message', + participantIds: [loggedInCandidate.user.id], + }) + .set('authorization', `Bearer ${loggedInCoach.token}`); + const nbConversation = await messagingHelper.countConversation(); + expect(response.status).toBe(201); + expect(nbConversation).toBe(1); }); - it('should return all conversations', async () => { - const response: APIResponse = + it('should return 201 and create a conversation if no conversation between coach and referer doesn t exists (initiated by Referer)', async () => { + const response: APIResponse = await request(server) - .get(`/messaging/conversations`) - .set('authorization', `Bearer ${loggedInCandidate.token}`); + .post(`/messaging/messages`) + .send({ + content: 'Super message', + participantIds: [loggedInCoach.user.id], + }) + .set('authorization', `Bearer ${loggedInReferer.token}`); + const nbConversation = await messagingHelper.countConversation(); + expect(response.status).toBe(201); + expect(nbConversation).toBe(1); + }); - expect(response.status).toBe(200); - expect(response.body.length).toBe(NB_CONVERSATIONS); + it('should return 201 and create a conversation if no conversation between coach and referer doesn t exists (initiated by Coach)', async () => { + const response: APIResponse = + await request(server) + .post(`/messaging/messages`) + .send({ + content: 'Super message', + participantIds: [loggedInReferer.user.id], + }) + .set('authorization', `Bearer ${loggedInCoach.token}`); + const nbConversation = await messagingHelper.countConversation(); + expect(response.status).toBe(201); + expect(nbConversation).toBe(1); }); - it('should return 401 if no token provided', async () => { - const response = await request(server).get(`/messaging/conversations`); + it('should return 201 and create a conversation if no conversation between candidate and referer doesn t exists (initiated by Candidate)', async () => { + const response: APIResponse = + await request(server) + .post(`/messaging/messages`) + .send({ + content: 'Super message', + participantIds: [loggedInCandidate.user.id], + }) + .set('authorization', `Bearer ${loggedInReferer.token}`); + const nbConversation = await messagingHelper.countConversation(); + expect(response.status).toBe(201); + expect(nbConversation).toBe(1); + }); - expect(response.status).toBe(401); + it('should return 201 and create a conversation if no conversation between candidate and referer doesn t exists (initiated by Referer)', async () => { + const response: APIResponse = + await request(server) + .post(`/messaging/messages`) + .send({ + content: 'Super message', + participantIds: [loggedInCandidate.user.id], + }) + .set('authorization', `Bearer ${loggedInReferer.token}`); + const nbConversation = await messagingHelper.countConversation(); + expect(response.status).toBe(201); + expect(nbConversation).toBe(1); }); - it('should not return conversation from other users', async () => { - const response: APIResponse = + it('should return 201 and create a conversation if no conversation between candidate and referer doesn t exists (initiated by Candidate)', async () => { + const response: APIResponse = await request(server) - .get(`/messaging/conversations`) - .set('authorization', `Bearer ${loggedInOtherCandidate.token}`); + .post(`/messaging/messages`) + .send({ + content: 'Super message', + participantIds: [loggedInReferer.user.id], + }) + .set('authorization', `Bearer ${loggedInCandidate.token}`); + const nbConversation = await messagingHelper.countConversation(); + expect(response.status).toBe(201); + expect(nbConversation).toBe(1); + }); + }); - expect(response.status).toBe(200); - expect(response.body.length).toBe(0); + describe('R - Read conversations', () => { + let coachs: User[]; + beforeEach(async () => { + coachs = await databaseHelper.createEntities( + userFactory, + NB_CONVERSATIONS, + { + role: UserRoles.COACH, + } + ); }); - it('should return 401 if the user is not logged in', async () => { - const response = await request(server).get(`/messaging/conversations`); + describe('R - Read conversations - /messaging/conversations', () => { + it('should return 401 if no token provided', async () => { + const response = await request(server).get( + `/messaging/conversations` + ); + expect(response.status).toBe(401); + }); - expect(response.status).toBe(401); - }); - }); + describe('Contexte : 1 Candidate -> Coach (x NB_CONVERSATIONS)', () => { + beforeEach(async () => { + // Create the conversations + const conversations = await databaseHelper.createEntities( + conversationFactory, + NB_CONVERSATIONS + ); + // And link the conversations to the coachs and the logged in candidate + const linkPromises = conversations.map((conversation, idx) => + messagingHelper.associationParticipantsToConversation( + conversation.id, + [loggedInCandidate.user.id, coachs[idx].id] + ) + ); + await Promise.all(linkPromises); + + // Add messages to the conversations + const messagePromises = conversations.map((conversation) => + messagingHelper.addMessagesToConversation( + 2, + conversation.id, + loggedInCandidate.user.id + ) + ); + + await Promise.all(messagePromises); + }); - describe('R - Read conversation - /messaging/conversations/:id', () => { - it('should return the conversation (conversation without message)', async () => { - const conversation = await conversationFactory.create(); + it('should return 200 and all conversations when logged as candidate', async () => { + const response: APIResponse< + MessagingController['getConversations'] + > = await request(server) + .get(`/messaging/conversations`) + .set('authorization', `Bearer ${loggedInCandidate.token}`); - await messagingHelper.associationParticipantsToConversation( - conversation.id, - [loggedInCandidate.user.id, coachs[0].id] - ); + expect(response.status).toBe(200); + expect(response.body.length).toBe(NB_CONVERSATIONS); + }); - const response: APIResponse = - await request(server) - .get(`/messaging/conversations/${conversation.id}`) - .set('authorization', `Bearer ${loggedInCandidate.token}`); + it('should not return conversation from other users', async () => { + const response: APIResponse< + MessagingController['getConversations'] + > = await request(server) + .get(`/messaging/conversations`) + .set('authorization', `Bearer ${loggedInOtherCandidate.token}`); + + expect(response.status).toBe(200); + expect(response.body.length).toBe(0); + }); + }); - expect(response.status).toBe(200); - expect(response.body.id).toBe(conversation.id); - expect(response.body.messages.length).toBe(0); + describe('Contexte : 1 Candidate -> 1 Coach', () => { + it('should return 200 and all conversations when logged as coach', async () => { + const conversation = await conversationFactory.create(); + messagingHelper.associationParticipantsToConversation( + conversation.id, + [loggedInCandidate.user.id, loggedInCoach.user.id] + ); + + const response: APIResponse< + MessagingController['getConversations'] + > = await request(server) + .get(`/messaging/conversations`) + .set('authorization', `Bearer ${loggedInCoach.token}`); + + expect(response.status).toBe(200); + expect(response.body.length).toBe(1); + }); + }); + + describe('Contexte : 1 Candidate -> 1 Referer', () => { + it('should return 200 and all conversations when logged as referer', async () => { + const conversation = await conversationFactory.create(); + messagingHelper.associationParticipantsToConversation( + conversation.id, + [loggedInCandidate.user.id, loggedInReferer.user.id] + ); + + const response: APIResponse< + MessagingController['getConversations'] + > = await request(server) + .get(`/messaging/conversations`) + .set('authorization', `Bearer ${loggedInReferer.token}`); + + expect(response.status).toBe(200); + expect(response.body.length).toBe(1); + }); + }); }); - it('should return the conversation (conversation with messages)', async () => { - const conversation = await conversationFactory.create(); + describe('R - Read conversation - /messaging/conversations/:id', () => { + it('should return the conversation (conversation without message)', async () => { + const conversation = await conversationFactory.create(); - await messagingHelper.associationParticipantsToConversation( - conversation.id, - [loggedInCandidate.user.id, coachs[0].id] - ); + await messagingHelper.associationParticipantsToConversation( + conversation.id, + [loggedInCandidate.user.id, loggedInCoach.user.id] + ); - await messagingHelper.addMessagesToConversation( - 2, - conversation.id, - loggedInCandidate.user.id - ); + const response: APIResponse = + await request(server) + .get(`/messaging/conversations/${conversation.id}`) + .set('authorization', `Bearer ${loggedInCandidate.token}`); - const response: APIResponse = - await request(server) - .get(`/messaging/conversations/${conversation.id}`) - .set('authorization', `Bearer ${loggedInCandidate.token}`); + expect(response.status).toBe(200); + expect(response.body.id).toBe(conversation.id); + expect(response.body.messages.length).toBe(0); + }); - expect(response.status).toBe(200); - expect(response.body.id).toBe(conversation.id); - expect(response.body.messages.length).toBe(2); - }); + it('should return the conversation (conversation with messages)', async () => { + const conversation = await conversationFactory.create(); - it('should return the conversation only with the messages in the conversation', async () => { - const conversation = await conversationFactory.create(); - const secondConversation = await conversationFactory.create(); + await messagingHelper.associationParticipantsToConversation( + conversation.id, + [loggedInCandidate.user.id, loggedInCoach.user.id] + ); - await messagingHelper.associationParticipantsToConversation( - conversation.id, - [loggedInCandidate.user.id, coachs[0].id] - ); - await messagingHelper.associationParticipantsToConversation( - secondConversation.id, - [loggedInCandidate.user.id, coachs[1].id] - ); + await messagingHelper.addMessagesToConversation( + 2, + conversation.id, + loggedInCandidate.user.id + ); - await messagingHelper.addMessagesToConversation( - 2, - conversation.id, - loggedInCandidate.user.id - ); - await messagingHelper.addMessagesToConversation( - 5, - secondConversation.id, - coachs[1].id - ); + const response: APIResponse = + await request(server) + .get(`/messaging/conversations/${conversation.id}`) + .set('authorization', `Bearer ${loggedInCandidate.token}`); - const response: APIResponse = - await request(server) - .get(`/messaging/conversations/${conversation.id}`) - .set('authorization', `Bearer ${loggedInCandidate.token}`); + expect(response.status).toBe(200); + expect(response.body.id).toBe(conversation.id); + expect(response.body.messages.length).toBe(2); + }); - expect(response.status).toBe(200); - expect(response.body.id).toBe(conversation.id); - expect(response.body.messages.length).toBe(2); - }); + it('should return the conversation only with the messages in the conversation', async () => { + const conversation = await conversationFactory.create(); + const secondConversation = await conversationFactory.create(); - it('should return 401 if the user is not a participant of the conversation', async () => { - const conversation = await conversationFactory.create(); + await messagingHelper.associationParticipantsToConversation( + conversation.id, + [loggedInCandidate.user.id, coachs[0].id] + ); + await messagingHelper.associationParticipantsToConversation( + secondConversation.id, + [loggedInCandidate.user.id, coachs[1].id] + ); + + await messagingHelper.addMessagesToConversation( + 2, + conversation.id, + loggedInCandidate.user.id + ); + await messagingHelper.addMessagesToConversation( + 5, + secondConversation.id, + coachs[1].id + ); + + const response: APIResponse = + await request(server) + .get(`/messaging/conversations/${conversation.id}`) + .set('authorization', `Bearer ${loggedInCandidate.token}`); + + expect(response.status).toBe(200); + expect(response.body.id).toBe(conversation.id); + expect(response.body.messages.length).toBe(2); + }); - await messagingHelper.associationParticipantsToConversation( - conversation.id, - [loggedInOtherCandidate.user.id, coachs[0].id] - ); + it('should return 401 if the user is not a participant of the conversation', async () => { + const conversation = await conversationFactory.create(); - const response: APIResponse = - await request(server) - .get(`/messaging/conversations/${conversation.id}`) - .set('authorization', `Bearer ${loggedInCandidate.token}`); + await messagingHelper.associationParticipantsToConversation( + conversation.id, + [loggedInOtherCandidate.user.id, loggedInCoach.user.id] + ); - expect(response.status).toBe(401); - }); + const response: APIResponse = + await request(server) + .get(`/messaging/conversations/${conversation.id}`) + .set('authorization', `Bearer ${loggedInCandidate.token}`); - it('should return 401 if the user is not logged in', async () => { - const conversation = await conversationFactory.create(); + expect(response.status).toBe(401); + }); - await messagingHelper.associationParticipantsToConversation( - conversation.id, - [loggedInCandidate.user.id, coachs[0].id] - ); + it('should return 401 if the user is not logged in', async () => { + const conversation = await conversationFactory.create(); - const response = await request(server).get( - `/messaging/conversations/${conversation.id}` - ); + await messagingHelper.associationParticipantsToConversation( + conversation.id, + [loggedInCandidate.user.id, loggedInCoach.user.id] + ); - expect(response.status).toBe(401); + const response = await request(server).get( + `/messaging/conversations/${conversation.id}` + ); + + expect(response.status).toBe(401); + }); }); }); }); @@ -404,12 +658,12 @@ describe('MESSAGING', () => { * CRUD - Report */ describe('CRUD - Report', () => { - it('should report a conversation', async () => { + it('should 201 when a candidate report a conversation', async () => { const conversation = await conversationFactory.create(); await messagingHelper.associationParticipantsToConversation( conversation.id, - [loggedInCandidate.user.id, coachs[0].id] + [loggedInCandidate.user.id, loggedInCoach.user.id] ); const response: APIResponse = @@ -424,12 +678,52 @@ describe('MESSAGING', () => { expect(response.status).toBe(201); }); + it('should 201 when a coach report a conversation', async () => { + const conversation = await conversationFactory.create(); + + await messagingHelper.associationParticipantsToConversation( + conversation.id, + [loggedInCandidate.user.id, loggedInCoach.user.id] + ); + + const response: APIResponse = + await request(server) + .post(`/messaging/conversations/${conversation.id}/report`) + .set('authorization', `Bearer ${loggedInCoach.token}`) + .send({ + reason: 'SPAM', + comment: 'Cette personne fait du spam', + }); + + expect(response.status).toBe(201); + }); + + it('should 201 when a referer report a conversation', async () => { + const conversation = await conversationFactory.create(); + + await messagingHelper.associationParticipantsToConversation( + conversation.id, + [loggedInCandidate.user.id, loggedInReferer.user.id] + ); + + const response: APIResponse = + await request(server) + .post(`/messaging/conversations/${conversation.id}/report`) + .set('authorization', `Bearer ${loggedInReferer.token}`) + .send({ + reason: 'SPAM', + comment: 'Cette personne fait du spam', + }); + + expect(response.status).toBe(201); + }); + it('should return 401 if the user is not a participant of the conversation', async () => { const conversation = await conversationFactory.create(); await messagingHelper.associationParticipantsToConversation( conversation.id, - [loggedInOtherCandidate.user.id, coachs[0].id] + [loggedInOtherCandidate.user.id, loggedInCoach.user.id] ); const response: APIResponse = @@ -449,7 +743,7 @@ describe('MESSAGING', () => { await messagingHelper.associationParticipantsToConversation( conversation.id, - [loggedInCandidate.user.id, coachs[0].id] + [loggedInCandidate.user.id, loggedInCoach.user.id] ); const response: APIResponse = From d6b52bdd292c262f13d5706964176e5d2732b087 Mon Sep 17 00:00:00 2001 From: Guillaume Cauchois Date: Fri, 29 Nov 2024 14:01:56 +0100 Subject: [PATCH 28/29] test: add test on referer get refered candidate list --- src/user-profiles/user-profiles.controller.ts | 1 + tests/users/users.e2e-spec.ts | 340 ++++++++++++++---- 2 files changed, 263 insertions(+), 78 deletions(-) diff --git a/src/user-profiles/user-profiles.controller.ts b/src/user-profiles/user-profiles.controller.ts index 1e8af51e..4abff9db 100644 --- a/src/user-profiles/user-profiles.controller.ts +++ b/src/user-profiles/user-profiles.controller.ts @@ -147,6 +147,7 @@ export class UserProfilesController { } @UserPermissions(Permissions.REFERER) + @UseGuards(UserPermissionsGuard) @Get('refered') async findReferedCandidates( @UserPayload('id', new ParseUUIDPipe()) userId: string, diff --git a/tests/users/users.e2e-spec.ts b/tests/users/users.e2e-spec.ts index 54733c1e..00bc3139 100644 --- a/tests/users/users.e2e-spec.ts +++ b/tests/users/users.e2e-spec.ts @@ -4304,91 +4304,93 @@ describe('Users', () => { }); }); describe('R - Read many Profiles', () => { - describe('/profile - Read all profiles', () => { - it('Should return 401 if user is not logged in', async () => { - const response: APIResponse = - await request(server).get( - `${route}/profile?offset=0&limit=25&role[]=${UserRoles.CANDIDATE}` - ); - expect(response.status).toBe(401); - }); - it('Should return 400 if no role parameter', async () => { - const loggedInCandidate = await usersHelper.createLoggedInUser({ - role: UserRoles.CANDIDATE, + describe('/profile', () => { + describe('/profile?limit=&offset= - Read all profiles', () => { + it('Should return 401 if user is not logged in', async () => { + const response: APIResponse = + await request(server).get( + `${route}/profile?offset=0&limit=25&role[]=${UserRoles.CANDIDATE}` + ); + expect(response.status).toBe(401); }); - const response: APIResponse = - await request(server) - .get(`${route}/profile`) - .set('authorization', `Bearer ${loggedInCandidate.token}`); - expect(response.status).toBe(400); - }); - it('Should return 200 if user is logged in as admin', async () => { - const loggedInAdmin = await usersHelper.createLoggedInUser({ - role: UserRoles.ADMIN, + it('Should return 400 if no role parameter', async () => { + const loggedInCandidate = await usersHelper.createLoggedInUser({ + role: UserRoles.CANDIDATE, + }); + const response: APIResponse = + await request(server) + .get(`${route}/profile`) + .set('authorization', `Bearer ${loggedInCandidate.token}`); + expect(response.status).toBe(400); }); - const response: APIResponse = - await request(server) - .get( - `${route}/profile?offset=0&limit=25&role[]=${UserRoles.CANDIDATE}` - ) - .set('authorization', `Bearer ${loggedInAdmin.token}`); - expect(response.status).toBe(200); - }); - it('Should return 200 if user is logged in as candidate', async () => { - const loggedInCandidate = await usersHelper.createLoggedInUser({ - role: UserRoles.CANDIDATE, + it('Should return 200 if user is logged in as admin', async () => { + const loggedInAdmin = await usersHelper.createLoggedInUser({ + role: UserRoles.ADMIN, + }); + const response: APIResponse = + await request(server) + .get( + `${route}/profile?offset=0&limit=25&role[]=${UserRoles.CANDIDATE}` + ) + .set('authorization', `Bearer ${loggedInAdmin.token}`); + expect(response.status).toBe(200); }); - const response: APIResponse = - await request(server) - .get( - `${route}/profile?offset=0&limit=25&role[]=${UserRoles.CANDIDATE}` - ) - .set('authorization', `Bearer ${loggedInCandidate.token}`); - expect(response.status).toBe(200); - }); - it('Should return 200 if user is logged in as coach', async () => { - const loggedInCoach = await usersHelper.createLoggedInUser({ - role: UserRoles.COACH, + it('Should return 200 if user is logged in as candidate', async () => { + const loggedInCandidate = await usersHelper.createLoggedInUser({ + role: UserRoles.CANDIDATE, + }); + const response: APIResponse = + await request(server) + .get( + `${route}/profile?offset=0&limit=25&role[]=${UserRoles.CANDIDATE}` + ) + .set('authorization', `Bearer ${loggedInCandidate.token}`); + expect(response.status).toBe(200); }); - const response: APIResponse = - await request(server) - .get( - `${route}/profile?offset=0&limit=25&role[]=${UserRoles.CANDIDATE}` - ) - .set('authorization', `Bearer ${loggedInCoach.token}`); - expect(response.status).toBe(200); - }); - it('Should return 200 if user is logged in as referer', async () => { - const loggedInReferer = await usersHelper.createLoggedInUser({ - role: UserRoles.REFERER, + it('Should return 200 if user is logged in as coach', async () => { + const loggedInCoach = await usersHelper.createLoggedInUser({ + role: UserRoles.COACH, + }); + const response: APIResponse = + await request(server) + .get( + `${route}/profile?offset=0&limit=25&role[]=${UserRoles.CANDIDATE}` + ) + .set('authorization', `Bearer ${loggedInCoach.token}`); + expect(response.status).toBe(200); }); - const response: APIResponse = - await request(server) - .get( - `${route}/profile?offset=0&limit=25&role[]=${UserRoles.CANDIDATE}` - ) - .set('authorization', `Bearer ${loggedInReferer.token}`); - expect(response.status).toBe(200); - }); - it('Should return 400 if no offset or limit parameter', async () => { - const loggedInCandidate = await usersHelper.createLoggedInUser({ - role: UserRoles.CANDIDATE, + it('Should return 200 if user is logged in as referer', async () => { + const loggedInReferer = await usersHelper.createLoggedInUser({ + role: UserRoles.REFERER, + }); + const response: APIResponse = + await request(server) + .get( + `${route}/profile?offset=0&limit=25&role[]=${UserRoles.CANDIDATE}` + ) + .set('authorization', `Bearer ${loggedInReferer.token}`); + expect(response.status).toBe(200); }); - const response: APIResponse = - await request(server) - .get(`${route}/profile?role[]=${UserRoles.CANDIDATE}`) - .set('authorization', `Bearer ${loggedInCandidate.token}`); - expect(response.status).toBe(400); - }); - it('Should return 400 if no role parameter', async () => { - const loggedInCandidate = await usersHelper.createLoggedInUser({ - role: UserRoles.CANDIDATE, + it('Should return 400 if no offset or limit parameter', async () => { + const loggedInCandidate = await usersHelper.createLoggedInUser({ + role: UserRoles.CANDIDATE, + }); + const response: APIResponse = + await request(server) + .get(`${route}/profile?role[]=${UserRoles.CANDIDATE}`) + .set('authorization', `Bearer ${loggedInCandidate.token}`); + expect(response.status).toBe(400); + }); + it('Should return 400 if no role parameter', async () => { + const loggedInCandidate = await usersHelper.createLoggedInUser({ + role: UserRoles.CANDIDATE, + }); + const response: APIResponse = + await request(server) + .get(`${route}/profile?offset=0&limit=25`) + .set('authorization', `Bearer ${loggedInCandidate.token}`); + expect(response.status).toBe(400); }); - const response: APIResponse = - await request(server) - .get(`${route}/profile?offset=0&limit=25`) - .set('authorization', `Bearer ${loggedInCandidate.token}`); - expect(response.status).toBe(400); }); describe('/profile?limit=&offset=&role[]= - Get paginated and creation date sorted users filtered by role', () => { let loggedInCandidate: LoggedUser; @@ -5432,6 +5434,188 @@ describe('Users', () => { }); }); }); + + describe('/profile/refered', () => { + describe('/profile/refered?limit=&offset= - Read only candidates that current user refered', () => { + let loggedInReferer: LoggedUser; + beforeEach(async () => { + loggedInReferer = await usersHelper.createLoggedInUser({ + role: UserRoles.REFERER, + }); + }); + + it('Should return 401 if user is not logged in', async () => { + const response: APIResponse< + UserProfilesController['findReferedCandidates'] + > = await request(server).get( + `${route}/profile/refered?limit=25&offset=0` + ); + expect(response.status).toBe(401); + }); + + it('Should return 200 and empty list if user is logged in as referer but has no candidate refered', async () => { + const loggedInReferer = await usersHelper.createLoggedInUser({ + role: UserRoles.REFERER, + }); + const response: APIResponse< + UserProfilesController['findReferedCandidates'] + > = await request(server) + .get(`${route}/profile/refered?limit=25&offset=0`) + .set('authorization', `Bearer ${loggedInReferer.token}`); + expect(response.status).toBe(200); + expect(response.body).toEqual([]); + }); + + it('Should return 200 and sorted list of candidates refered by current user', async () => { + // Initialize referableCandidates + const referedCandidates = await databaseHelper.createEntities( + userFactory, + 3, + { + role: UserRoles.CANDIDATE, + zone: AdminZones.LILLE, + refererId: loggedInReferer.user.id, + }, + {} + ); + + const response: APIResponse< + UserProfilesController['findReferedCandidates'] + > = await request(server) + .get(`${route}/profile/refered?limit=25&offset=0`) + .set('authorization', `Bearer ${loggedInReferer.token}`); + expect(response.status).toBe(200); + expect(response.body.length).toBe(referedCandidates.length); + expect(response.body).toEqual( + referedCandidates + .sort((userA, userB) => + moment(userB.createdAt).diff(userA.createdAt) + ) + .map((user) => + expect.objectContaining( + userProfilesHelper.mapUserProfileFromUser(user) + ) + ) + ); + }); + + it('Should return 200 and sorted list of candidates refered by current user with limit = 1 and offset = 1', async () => { + // Initialize referableCandidates + const referedCandidates = await databaseHelper.createEntities( + userFactory, + 3, + { + role: UserRoles.CANDIDATE, + zone: AdminZones.LILLE, + refererId: loggedInReferer.user.id, + }, + {} + ); + + const response: APIResponse< + UserProfilesController['findReferedCandidates'] + > = await request(server) + .get(`${route}/profile/refered?limit=1&offset=1`) + .set('authorization', `Bearer ${loggedInReferer.token}`); + const sortedReferedCandidates = referedCandidates.sort( + (userA, userB) => moment(userB.createdAt).diff(userA.createdAt) + ); + expect(response.status).toBe(200); + expect(response.body.length).toBe(1); + expect(response.body).toEqual( + [sortedReferedCandidates[1]].map((user) => + expect.objectContaining( + userProfilesHelper.mapUserProfileFromUser(user) + ) + ) + ); + }); + + it('Should return 200 and only own refered candidates', async () => { + // Candidates of referer + const referedCandidates = await databaseHelper.createEntities( + userFactory, + 3, + { + role: UserRoles.CANDIDATE, + zone: AdminZones.LILLE, + refererId: loggedInReferer.user.id, + }, + {} + ); + + // Create other referer and his candidates + const otherLoggedInReferer = await usersHelper.createLoggedInUser({ + role: UserRoles.REFERER, + }); + await databaseHelper.createEntities( + userFactory, + 5, + { + role: UserRoles.CANDIDATE, + zone: AdminZones.PARIS, + refererId: otherLoggedInReferer.user.id, + }, + {} + ); + + const response: APIResponse< + UserProfilesController['findReferedCandidates'] + > = await request(server) + .get(`${route}/profile/refered?limit=25&offset=0`) + .set('authorization', `Bearer ${loggedInReferer.token}`); + expect(response.status).toBe(200); + expect(response.body.length).toBe(referedCandidates.length); + expect(response.body).toEqual( + referedCandidates + .sort((userA, userB) => + moment(userB.createdAt).diff(userA.createdAt) + ) + .map((user) => + expect.objectContaining( + userProfilesHelper.mapUserProfileFromUser(user) + ) + ) + ); + }); + + it('Should return 403 if user is logged in as candidate', async () => { + const loggedInCandidate = await usersHelper.createLoggedInUser({ + role: UserRoles.CANDIDATE, + }); + const response: APIResponse< + UserProfilesController['findReferedCandidates'] + > = await request(server) + .get(`${route}/profile/refered?limit=25&offset=0`) + .set('authorization', `Bearer ${loggedInCandidate.token}`); + expect(response.status).toBe(403); + }); + + it('Should return 403 if user is logged in as coach', async () => { + const loggedInCoach = await usersHelper.createLoggedInUser({ + role: UserRoles.COACH, + }); + const response: APIResponse< + UserProfilesController['findReferedCandidates'] + > = await request(server) + .get(`${route}/profile/refered?limit=25&offset=0`) + .set('authorization', `Bearer ${loggedInCoach.token}`); + expect(response.status).toBe(403); + }); + + it('Should return 403 if user is logged in as admin', async () => { + const loggedInAdmin = await usersHelper.createLoggedInUser({ + role: UserRoles.ADMIN, + }); + const response: APIResponse< + UserProfilesController['findReferedCandidates'] + > = await request(server) + .get(`${route}/profile/refered?limit=25&offset=0`) + .set('authorization', `Bearer ${loggedInAdmin.token}`); + expect(response.status).toBe(403); + }); + }); + }); }); describe('U - Update 1 User', () => { describe('/:id - Update user', () => { From a418ad2f611098031f99b51e0f5b4e688947b6c8 Mon Sep 17 00:00:00 2001 From: Guillaume Cauchois Date: Mon, 2 Dec 2024 16:17:57 +0100 Subject: [PATCH 29/29] fix: tests --- tests/users/users.e2e-spec.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/users/users.e2e-spec.ts b/tests/users/users.e2e-spec.ts index 00bc3139..d3ac731b 100644 --- a/tests/users/users.e2e-spec.ts +++ b/tests/users/users.e2e-spec.ts @@ -407,6 +407,7 @@ describe('Users', () => { whatsappZoneCoachQR: coachWhatsappZoneCoachQR, OrganizationId: coachOrganizationId, referredCandidates: coachReferredCandidates, + referer: coachReferer, ...coach } = await userFactory.create({ role: UserRoles.COACH }, {}, true); @@ -519,6 +520,7 @@ describe('Users', () => { whatsappZoneCoachQR: candidateWhatappZoneCoachQR, refererId: candidateRefererId, referredCandidates: candidateReferredCandidates, + referer: candidateReferer, ...candidate } = await userFactory.create( { role: UserRoles.CANDIDATE }, @@ -6056,6 +6058,7 @@ describe('Users', () => { whatsappZoneCoachUrl: coachWhatsappZoneCoachUrl, whatsappZoneCoachQR: coachWhatsappZoneCoachQR, refererId: coachRefererId, + referer: coachReferer, referredCandidates: coachReferredCandidates, ...restCoach } = loggedInCoach.user; @@ -6092,6 +6095,7 @@ describe('Users', () => { whatsappZoneCoachUrl, whatsappZoneCoachQR, refererId, + referer, referredCandidates, ...restCandidate } = loggedInCandidate.user;