Skip to content

Commit

Permalink
Merge branch 'release/v2.26.8'
Browse files Browse the repository at this point in the history
  • Loading branch information
DDZBX committed Dec 12, 2024
2 parents 695e305 + eee656f commit b2e31a5
Show file tree
Hide file tree
Showing 36 changed files with 3,180 additions and 3,157 deletions.
3 changes: 0 additions & 3 deletions .env.dist
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,6 @@ OFFER_NO_RESPONSE_DELAY=0.00069
OFFER_NO_RESPONSE_DELAY=0.00069
SEND_OFFERS_EMAIL_AFTER_CV_PUBLISH_DELAY=0.0000115

# Sentry
SENTRY_DSN=

# Urls
FRONT_URL=http://localhost:3001
AWS_LAMBDA_URL=http://localhost:9000/2015-03-31/functions/function/invocations
Expand Down
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
v18.20.3
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "linkedout-backend",
"version": "2.26.7",
"version": "2.26.8",
"license": "ISC",
"engines": {
"node": "18.x"
Expand Down Expand Up @@ -131,5 +131,6 @@
"ts-node": "^10.0.0",
"tsconfig-paths": "^3.10.1",
"typescript": "^4.3.5"
}
},
"packageManager": "[email protected]+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}
61 changes: 61 additions & 0 deletions src/auth/auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,4 +239,65 @@ export class AuthController {

return;
}

@Throttle(60, 60)
@Public()
@Post('finalize-refered-user')
async finalizeReferedUser(
@Body('token') token?: string,
@Body('password') password?: string
): Promise<string> {
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();
}

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);
await this.authService.sendRefererCandidateHasVerifiedAccountMail(
updatedUser
);

return updatedUser.email;
}
}
20 changes: 20 additions & 0 deletions src/auth/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,12 @@ export class AuthService {
return this.mailsService.sendVerificationMail(user, token);
}

async sendRefererCandidateHasVerifiedAccountMail(candidate: User) {
return this.mailsService.sendRefererCandidateHasVerifiedAccountMail(
candidate
);
}

async generateVerificationToken(user: User) {
return this.jwtService.sign(
{ sub: user.id },
Expand All @@ -146,4 +152,18 @@ export class AuthService {
}
);
}

async sendWelcomeMail(
user: Pick<User, 'id' | 'firstName' | 'role' | 'zone' | 'email'>
) {
return this.mailsService.sendWelcomeMail(user);
}

async sendOnboardingJ1BAOMail(user: User) {
return this.mailsService.sendOnboardingJ1BAOMail(user);
}

async sendOnboardingJ3ProfileCompletionMail(user: User) {
return this.mailsService.sendOnboardingJ3ProfileCompletionMail(user);
}
}
16 changes: 16 additions & 0 deletions src/db/migrations/20241008205334-add-referrer-to-user.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
'use strict';

/** @type {import('sequelize-cli').Migration} */
module.exports = {
async up(queryInterface, Sequelize) {
await queryInterface.addColumn('Users', 'refererId', {
type: Sequelize.UUID,
allowNull: true,
defaultValue: null,
});
},

async down(queryInterface, Sequelize) {
await queryInterface.removeColumn('Users', 'refererId');
},
};
3 changes: 3 additions & 0 deletions src/external-databases/external-databases.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
}
Expand Down
5 changes: 5 additions & 0 deletions src/external-services/mailjet/mailjet.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -94,6 +95,10 @@ export const MailjetTemplates = {
CONVERSATION_REPORTED_ADMIN: 6276909,
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;

export type MailjetTemplateKey = keyof typeof MailjetTemplates;
Expand Down
104 changes: 90 additions & 14 deletions src/mails/mails.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Injectable } from '@nestjs/common';
import { Injectable, NotFoundException } from '@nestjs/common';
import _ from 'lodash';
import { HeardAboutFilters } from 'src/contacts/contacts.types';
import { ContactUsFormDto } from 'src/contacts/dto';
Expand Down Expand Up @@ -29,15 +29,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,
Expand Down Expand Up @@ -110,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) {
Expand All @@ -120,6 +126,7 @@ export class MailsService {
firstName: user.firstName,
toEmail: user.email,
token,
zone: user.zone,
},
});
}
Expand Down Expand Up @@ -166,7 +173,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.REFERER) {
toEmail.cc = coach.email;
}
const { candidatesAdminMail } = getAdminMailsFromZone(candidate.zone);
Expand Down Expand Up @@ -196,7 +203,7 @@ export class MailsService {
const coach = getCoachFromCandidate(candidate);

const toEmail: CustomMailParams['toEmail'] =
coach && coach.role !== UserRoles.COACH_EXTERNAL
coach && coach.role !== UserRoles.REFERER
? { to: candidate.email, cc: coach.email }
: { to: candidate.email };

Expand All @@ -220,7 +227,7 @@ export class MailsService {
let candidate, coach: User;
let toEmail: string;
// if user is a a candidate then get the user as candidate
if (isRoleIncluded(CandidateUserRoles, submittingUser.role)) {
if (submittingUser.role === UserRoles.CANDIDATE) {
candidate = submittingUser;
coach = getCoachFromCandidate(candidate);
toEmail = getAdminMailsFromZone(submittingUser.zone).candidatesAdminMail;
Expand Down Expand Up @@ -251,7 +258,7 @@ export class MailsService {
};

const coach = getCoachFromCandidate(candidate);
if (coach && coach.role !== UserRoles.COACH_EXTERNAL) {
if (coach && coach.role !== UserRoles.REFERER) {
toEmail.cc = coach.email;
}

Expand All @@ -278,7 +285,7 @@ export class MailsService {
to: candidate.email,
};
const coach = getCoachFromCandidate(candidate);
if (coach && coach.role !== UserRoles.COACH_EXTERNAL) {
if (coach && coach.role !== UserRoles.REFERER) {
toEmail.cc = coach.email;
}
const { candidatesAdminMail } = getAdminMailsFromZone(candidate.zone);
Expand Down Expand Up @@ -815,12 +822,81 @@ 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.FRONT_URL}/login`,
zone: referer.zone,
},
});
}

async sendReferedCandidateFinalizeAccountMail(
referer: User,
candidate: User,
token: string
) {
await this.queuesService.addToWorkQueue(Jobs.SEND_MAIL, {
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,
finalizeAccountUrl: `${process.env.FRONT_URL}/finaliser-compte-oriente?token=${token}`,
zone: candidate.zone,
},
});
}

async sendRefererCandidateHasVerifiedAccountMail(candidate: User) {
if (candidate.referer === null) {
throw new NotFoundException();
}

await this.queuesService.addToWorkQueue(Jobs.SEND_MAIL, {
toEmail: candidate.referer.email,
templateId: MailjetTemplates.REFERER_CANDIDATE_HAS_FINALIZED_ACCOUNT,
variables: {
candidateFirstName: candidate.firstName,
candidateLastName: candidate.lastName,
refererFirstName: candidate.referer.firstName,
zone: candidate.zone,
loginUrl: `${process.env.FRONT_URL}/login`,
},
});
}

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 => {
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';
Expand Down
5 changes: 2 additions & 3 deletions src/messages/messages.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ import { ApiTags } from '@nestjs/swagger';
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,
Expand Down Expand Up @@ -47,7 +46,7 @@ export class MessagesController {
if (
(createMessageDto.senderPhone &&
!isValidPhone(createMessageDto.senderPhone)) ||
!isRoleIncluded(CandidateUserRoles, candidate.role)
candidate.role !== UserRoles.CANDIDATE
) {
throw new BadRequestException();
}
Expand Down
6 changes: 3 additions & 3 deletions src/opportunities/opportunities.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
Expand Down
Loading

0 comments on commit b2e31a5

Please sign in to comment.