From bde0c8849d6b3d5e94c53ba4708be9bd25cfc3f1 Mon Sep 17 00:00:00 2001 From: Kevin Barnoin Date: Thu, 14 Nov 2024 17:27:13 +0100 Subject: [PATCH] feat(lbac-2243): safe parse jobs before return (#1647) * feat: safe parse jobs before return * fix: label --- .vscode/settings.json | 5 ++-- .../JobOpportunityRequestContext.ts | 8 +++--- .../jobOpportunity/jobOpportunity.service.ts | 27 +++++++++++++++++-- 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 37a59ada12..0bf044c33e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -53,10 +53,10 @@ "**/.hg": true, "**/CVS": true, "**/.DS_Store": true, - // "**/node_modules": true, + "**/node_modules": true, ".next": true, "**/*/lib": true, - // "**/*/dist": true, + "**/*/dist": true, "**/*/build": true, "**/.webpack": true }, @@ -68,6 +68,7 @@ "**/.webpack": true }, "search.exclude": { + "**/node_modules": true, "**/.webpack": true, "**/.yarn/**": true, "**/*/.next": true, diff --git a/server/src/services/jobs/jobOpportunity/JobOpportunityRequestContext.ts b/server/src/services/jobs/jobOpportunity/JobOpportunityRequestContext.ts index 4ccbf93184..d336956a6b 100644 --- a/server/src/services/jobs/jobOpportunity/JobOpportunityRequestContext.ts +++ b/server/src/services/jobs/jobOpportunity/JobOpportunityRequestContext.ts @@ -3,6 +3,8 @@ import { IRouteSchema } from "shared/routes/common.routes" // Messages are not constants but the codes are export const IJobOpportunityWarningMap = { FRANCE_TRAVAIL_API_ERROR: "Unable to retrieve job offers from France Travail API", + JOB_OFFER_FORMATING_ERROR: "Some job offers are invalid and have been excluded due to unexpected errors.", + RECRUITERS_FORMATING_ERROR: "Some recruiters are invalid and have been excluded due to unexpected errors.", } as const satisfies Record export type IJobOpportunityWarningCode = keyof typeof IJobOpportunityWarningMap @@ -16,7 +18,7 @@ export class JobOpportunityRequestContext { caller: string - #warnings: IJobOpportunityWarningCode[] = [] + #warnings: Set = new Set() constructor(route: Pick, caller: string) { this.route = route @@ -24,10 +26,10 @@ export class JobOpportunityRequestContext { } addWarning(code: IJobOpportunityWarningCode) { - this.#warnings.push(code) + this.#warnings.add(code) } getWarnings(): Array<{ code: string; message: string }> { - return this.#warnings.map((code) => ({ code, message: IJobOpportunityWarningMap[code] })) + return Array.from(this.#warnings).map((code) => ({ code, message: IJobOpportunityWarningMap[code] })) } } diff --git a/server/src/services/jobs/jobOpportunity/jobOpportunity.service.ts b/server/src/services/jobs/jobOpportunity/jobOpportunity.service.ts index e53a20aa49..24ceba0ed3 100644 --- a/server/src/services/jobs/jobOpportunity/jobOpportunity.service.ts +++ b/server/src/services/jobs/jobOpportunity/jobOpportunity.service.ts @@ -12,6 +12,7 @@ import { IJobsPartnersWritableApi, INiveauDiplomeEuropeen, JOBPARTNERS_LABEL, + ZJobsPartnersOfferApi, ZJobsPartnersRecruiterApi, } from "shared/models/jobsPartners.model" import { zOpcoLabel } from "shared/models/opco.model" @@ -22,6 +23,7 @@ import { sentryCaptureException } from "@/common/utils/sentryUtils" import { getRomeFromRomeo } from "@/services/cache.service" import { getEntrepriseDataFromSiret, getGeoPoint, getOpcoData } from "@/services/etablissement.service" +import { logger } from "../../../common/logger" import { IApiError } from "../../../common/utils/errorManager" import { getDbCollection } from "../../../common/utils/mongodbUtils" import { trackApiCall } from "../../../common/utils/sendTrackingEvent" @@ -524,9 +526,30 @@ export async function findJobsOpportunities(payload: IJobOpportunityGetQuery, co findFranceTravailOpportunities(resolvedQuery, context), ]) + const jobs = [...offreEmploiLba, ...franceTravail, ...offreEmploiPartenaire].filter((job) => { + const parsedJob = ZJobsPartnersOfferApi.safeParse(job) + if (!parsedJob.success) { + const error = internal("jobOpportunity.service.ts-findJobsOpportunities: invalid job offer", { job, error: parsedJob.error.format() }) + logger.error(error) + context.addWarning("JOB_OFFER_FORMATING_ERROR") + sentryCaptureException(error) + } + return parsedJob.success + }) + const recruiters = convertLbaCompanyToJobPartnerRecruiterApi(recruterLba).filter((recruteur) => { + const parsedRecruiter = ZJobsPartnersRecruiterApi.safeParse(recruteur) + if (!parsedRecruiter.success) { + const error = internal("jobOpportunity.service.ts-findJobsOpportunities: invalid recruiter", { recruteur, error: parsedRecruiter.error.format() }) + logger.error(error) + context.addWarning("RECRUITERS_FORMATING_ERROR") + sentryCaptureException(error) + } + return parsedRecruiter.success + }) + return { - jobs: [...offreEmploiLba, ...franceTravail, ...offreEmploiPartenaire], - recruiters: convertLbaCompanyToJobPartnerRecruiterApi(recruterLba), + jobs, + recruiters, warnings: context.getWarnings(), } }