diff --git a/backend/controllers/followups.ts b/backend/controllers/followups.ts index 1fc869ce0c..b203fd4e75 100644 --- a/backend/controllers/followups.ts +++ b/backend/controllers/followups.ts @@ -10,10 +10,10 @@ import { SurveyType } from "../../lib/enums/survey.js" import { FollowupFactory } from "../lib/followup-factory.js" import { FetchSurvey } from "../../lib/types/survey.d.js" import Request from "../types/express.d.js" -import { phoneNumberValidation } from "../../lib/phone-number.js" import config from "../config/index.js" import { sendSimulationResultsEmail } from "../lib/messaging/email/email-service.js" import { sendSimulationResultsSms } from "../lib/messaging/sms/sms-service.js" +import { ErrorType, ErrorStatus, ErrorName } from "../../lib/enums/error.js" export function followup( req: Request, @@ -39,23 +39,6 @@ export function followup( }) } -async function sendFollowupNotifications(followup: Followup, res: Response) { - const { email, phone } = followup - if (phone) { - if ( - phoneNumberValidation(phone, config.smsService.internationalDiallingCodes) - ) { - await sendSimulationResultsSms(followup) - } else { - return res.status(422).send("Unsupported phone number format") - } - } - if (email) { - await sendSimulationResultsEmail(followup) - } - return res.send({ result: "OK" }) -} - async function createSimulationRecapUrl(req: Request, res: Response) { const followup = await FollowupFactory.create(req.simulation) await followup.addSurveyIfMissing( @@ -69,6 +52,7 @@ async function createSimulationRecapUrl(req: Request, res: Response) { export async function persist(req: Request, res: Response) { const { surveyOptin, email, phone } = req.body const simulation = req.simulation + try { if (email || phone) { const followup = await FollowupFactory.createWithResults( @@ -77,17 +61,27 @@ export async function persist(req: Request, res: Response) { email, phone ) - return sendFollowupNotifications(followup, res) - } else { - return createSimulationRecapUrl(req, res) + if (email) await sendSimulationResultsEmail(followup) + if (phone) await sendSimulationResultsSms(followup) + return res.send({ result: "OK" }) } + + return createSimulationRecapUrl(req, res) } catch (error: any) { Sentry.captureException(error) - if (error.name === "ValidationError") { - return res.status(403).send(error.message) - } else { - return res.status(500).send(`Error while persisting followup`) + + let status: number = ErrorStatus.InternalServerError + + if ( + error.name === ErrorName.ValidationError || + error.message === ErrorType.UnsupportedPhoneNumberFormat + ) { + status = ErrorStatus.UnprocessableEntity } + + return res + .status(status) + .send(error.message || ErrorType.PersistingFollowup) } } @@ -117,7 +111,7 @@ export function showFollowup(req: Request, res: Response) { }) .catch((error: Error) => { console.error("error", error) - return res.sendStatus(400) + return res.sendStatus(ErrorStatus.BadRequest) }) } @@ -142,12 +136,13 @@ export function showSurveyResults(req: Request, res: Response) { export function showSurveyResultByEmail(req: Request, res: Response) { Followups.findByEmail(req.params.email) .then((followups: Followup[]) => { - if (!followups || !followups.length) return res.sendStatus(404) + if (!followups || !followups.length) + return res.sendStatus(ErrorStatus.NotFound) res.send(followups) }) .catch((error: Error) => { console.error("error", error) - return res.sendStatus(400) + return res.sendStatus(ErrorStatus.BadRequest) }) } @@ -160,7 +155,7 @@ export async function followupByAccessToken( const followup: Followup | null = await Followups.findOne({ accessToken, }) - if (!followup) return res.sendStatus(404) + if (!followup) return res.sendStatus(ErrorStatus.NotFound) req.followup = followup next() } @@ -216,7 +211,7 @@ async function getRedirectUrl(req: Request) { case SurveyType.TousABordNotification: return "https://www.tadao.fr/713-Demandeur-d-emploi.html" default: - throw new Error(`Unknown survey type: ${surveyType}`) + throw new Error(`${ErrorType.UnknownSurveyType} : ${surveyType}`) } } @@ -227,7 +222,6 @@ export async function logSurveyLinkClick(req: Request, res: Response) { res.redirect(redirectUrl) } catch (error) { Sentry.captureException(error) - console.error("error", error) - return res.sendStatus(404) + return res.sendStatus(ErrorStatus.NotFound) } } diff --git a/backend/lib/messaging/email/email-service.ts b/backend/lib/messaging/email/email-service.ts index cc8232c25c..d28767bd5f 100644 --- a/backend/lib/messaging/email/email-service.ts +++ b/backend/lib/messaging/email/email-service.ts @@ -7,13 +7,14 @@ import { EmailType } from "../../../../lib/enums/messaging.js" import { SurveyType } from "../../../../lib/enums/survey.js" import { Survey } from "../../../../lib/types/survey.js" import { Followup } from "../../../../lib/types/followup.js" +import { ErrorType } from "../../../../lib/enums/error.js" import dayjs from "dayjs" export async function sendSimulationResultsEmail( followup: Followup ): Promise { if (!followup.email) { - throw new Error("Missing followup email") + throw new Error(ErrorType.MissingFollowupEmail) } const render: any = await emailRender(EmailType.SimulationResults, followup) const sendEmailSmtpResponse = await sendEmailSmtp({ diff --git a/backend/lib/messaging/sms/sms-service.ts b/backend/lib/messaging/sms/sms-service.ts index b9e10becf1..20f447755f 100644 --- a/backend/lib/messaging/sms/sms-service.ts +++ b/backend/lib/messaging/sms/sms-service.ts @@ -1,10 +1,14 @@ import axios from "axios" import config from "../../../config/index.js" -import { phoneNumberFormatting } from "./../../../../lib/phone-number.js" +import { + phoneNumberFormatting, + phoneNumberValidation, +} from "./../../../../lib/phone-number.js" import { SmsType } from "../../../../lib/enums/messaging.js" import { Followup } from "../../../../lib/types/followup.js" import { Survey } from "../../../../lib/types/survey.d.js" import { SurveyType } from "../../../../lib/enums/survey.js" +import { ErrorType } from "../../../../lib/enums/error.js" import dayjs from "dayjs" import Sentry from "@sentry/node" @@ -69,7 +73,16 @@ export async function sendSimulationResultsSms( ): Promise { try { if (!followup.phone) { - throw new Error("Missing followup phone") + throw new Error(ErrorType.MissingFollowupPhone) + } + + if ( + !phoneNumberValidation( + followup.phone, + config.smsService.internationalDiallingCodes + ) + ) { + throw new Error(ErrorType.UnsupportedPhoneNumberFormat) } const { username, password } = await getSMSConfig() @@ -90,10 +103,14 @@ export async function sendSimulationResultsSms( followup.smsSentAt = dayjs().toDate() followup.smsMessageId = data.messageIds[0] return await followup.save() - } catch (err) { - Sentry.captureException(err) - followup.smsError = JSON.stringify(err, null, 2) - throw err + } catch (error: any) { + // Avoid sending invalid destination address error to sentry + if (!error?.message?.includes("Invalid destination address")) { + Sentry.captureException(error) + } + followup.smsError = error?.message + await followup.save() + throw error } } diff --git a/lib/enums/error.ts b/lib/enums/error.ts new file mode 100644 index 0000000000..89ab511b6c --- /dev/null +++ b/lib/enums/error.ts @@ -0,0 +1,20 @@ +export enum ErrorType { + UnsupportedPhoneNumberFormat = "Unsupported phone number format", + PersistingFollowup = "Persisting followup error", + MissingFollowupPhone = "Missing followup phone", + MissingFollowupEmail = "Missing followup email", + UnknownSurveyType = "Unknown survey type", +} + +export enum ErrorStatus { + BadRequest = 400, + Unauthorized = 401, + Forbidden = 403, + UnprocessableEntity = 422, + NotFound = 404, + InternalServerError = 500, +} + +export enum ErrorName { + ValidationError = "ValidationError", +} diff --git a/src/components/modals/errors-email-and-sms-modal.vue b/src/components/modals/errors-email-and-sms-modal.vue index d46b10e460..c5217afefb 100644 --- a/src/components/modals/errors-email-and-sms-modal.vue +++ b/src/components/modals/errors-email-and-sms-modal.vue @@ -25,6 +25,16 @@ const recapEmailState = computed(() => store.recapEmailState) }}

+
+

+ Une erreur s'est produite dans l'envoi du récapitulatif par SMS : + l'adresse de destination est invalide. Veuillez réessayer avec un + numéro valide ou utiliser l'envoi par email seulement. +

+
{ ABTestingService.getValues().CTA_EmailRecontact ) } - } catch (error) { - console.error(error) - Sentry.captureException(error) + } catch (error: any) { + if (!error?.response?.data?.includes("Invalid destination address")) { + Sentry.captureException(error) + } else { + store.setFormRecapPhoneState("invalid-address") + } } } @@ -181,7 +184,6 @@ const sendRecapByEmail = async (surveyOptin) => { store.setModalState(undefined) await postFollowup(surveyOptin, emailValue.value) } catch (error) { - Sentry.captureException(error) store.setFormRecapEmailState("error") throw error }