Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(lbac-2409): migration recruteurslba to jobs_partners #1785

Open
wants to merge 30 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
f75c114
feat: refactor algo import
kevbarns Jan 22, 2025
756b06c
Merge branch 'main' into feat(lbac-2409)-update-algo-check
kevbarns Jan 23, 2025
3826d04
fix: missing bucket resolution
kevbarns Jan 23, 2025
3de1313
feat: refactor & optimise
kevbarns Jan 24, 2025
39ab551
Merge branch 'main' into feat(lbac-2409)-update-algo-check
kevbarns Feb 3, 2025
94de913
refactor: bing bang
kevbarns Feb 4, 2025
c5d6a3d
Merge branch 'main' into feat(lbac-2409)-update-algo-check
kevbarns Feb 5, 2025
86f2113
feat: mapper to computed
kevbarns Feb 5, 2025
a18f8d8
chore: clean up
kevbarns Feb 6, 2025
0a9cdd6
Merge branch 'main' into feat(lbac-2409)-update-algo-check
kevbarns Feb 12, 2025
46eb0d1
fix: get recruteurs_lba from jobs_partners + application
kevbarns Feb 12, 2025
34090df
fix: typing
kevbarns Feb 12, 2025
19ac115
feat: update services & tests
kevbarns Feb 13, 2025
4addf65
Merge branch 'main' into feat(lbac-2409)-update-algo-check
kevbarns Feb 13, 2025
b9422cb
feat: update jobOpportunity service & test
kevbarns Feb 13, 2025
30ef75d
feat: migrate remaining call to jobs_parters & tests
kevbarns Feb 13, 2025
30f4158
Merge branch 'main' into feat(lbac-2409)-update-algo-check
kevbarns Feb 13, 2025
c1cdb70
fix: typecheck blankComputedJobPartner update
kevbarns Feb 13, 2025
8190bb7
fix: application.controller.v2.test.ts
kevbarns Feb 13, 2025
9eb08eb
fix: openapi
kevbarns Feb 13, 2025
09d081c
fix: missing query params
kevbarns Feb 13, 2025
ee15399
Update server/src/http/controllers/unsubscribeRecruteurLba.controller.ts
kevbarns Feb 13, 2025
a6a5aa5
fix: updated_at
kevbarns Feb 13, 2025
aea41f4
fix: get whitelisted field projection from zod model
kevbarns Feb 13, 2025
908482a
fix: model
kevbarns Feb 13, 2025
1409a9a
fix: jobOpportunity
kevbarns Feb 13, 2025
c2fb402
fix: typing
kevbarns Feb 19, 2025
f54af9e
fix: pr comment
kevbarns Feb 19, 2025
bbe5320
Merge branch 'main' into feat(lbac-2409)-update-algo-check
kevbarns Feb 19, 2025
3fa2b8d
fix: route & test
kevbarns Feb 19, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions .talismanrc
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ fileignoreconfig:
- filename: server/src/http/controllers/__snapshots__/etablissementRecruteur.controller.test.ts.snap
checksum: e8a35e591adb1447d1b6e796b063f435ac389e9ffe619c68185f348beae71215
- filename: server/src/http/controllers/application.controller.test.ts
checksum: 00cac67c8e138098126ae98702e2bdc3e1092e1bbf188d9a437197e9ca94e16a
checksum: 7ec327fbd60ed13519fb0869dea25434c05b0bc601a07761f7e74c175d7712a7
- filename: server/src/http/controllers/v2/applications.controller.v2.test.ts
checksum: 51358a158fbd04caebd42a56120fab182928b368206aa2892a008d14e177b0ac
- filename: server/src/http/controllers/appointments/appointments.controller.ts
checksum: dc77c04efc26dcd8ca4816f392ab24a32a92cb45ca4b94adb593a08e7a2d231e
- filename: server/src/http/controllers/etablissementRecruteur.controller.test.ts
Expand All @@ -44,7 +46,7 @@ fileignoreconfig:
- filename: server/src/http/controllers/v2/jobs.controller.v2.ts
checksum: da8baa573172fa97c21f9123e04032718249dcec1194a51d7a1f1757070594ce
- filename: server/src/http/controllers/v3/jobs/jobs.controller.v3.test.ts
checksum: 4b24ff09463f476fd0b64bef104da6e17153ffdfff2d628c96b16d3ea33779f7
checksum: f945b1e0dd925b704b0ac17e31d6ecd33417de0eb3dd2a9177bd82e9e4dd6131
- filename: server/src/http/routes/appointmentRequest.controller.ts
checksum: d2770daa97ae332eec0b66497fdb717229895583ac3bfd48af1a830b36504968
- filename: server/src/http/routes/auth/password.controller.ts
Expand All @@ -64,7 +66,7 @@ fileignoreconfig:
- filename: server/src/jobs/offrePartenaire/__snapshots__/importRHAlternance.test.ts.snap
checksum: 41e5eadcdaa0cb1eb95e4cb3859f4b7b1d176bbb95ee8451d46a6b2c9021c3fd
- filename: server/src/jobs/offrePartenaire/detectDuplicateJobPartners.test.ts
checksum: 82d624da5034d7e482cb7b2a99a134796e3a2b3c3a2a89a10da2e49242854c79
checksum: 42830bef5459bba7625d1c52c2eac844911c3471f483f38499002e61dfd7cbb5
- filename: server/src/jobs/offrePartenaire/importFromUrlInXml.ts
checksum: 18bd149922151536ad0e239b082309697cee6046b33512d5eecc54b2d25b465a
- filename: server/src/jobs/offrePartenaire/importHelloWork.test.input.xml
Expand All @@ -84,7 +86,7 @@ fileignoreconfig:
- filename: server/src/services/__snapshots__/partnerJob.service.test.ts.snap
checksum: 6f190e19172481706181e0a13ed7de5f0f6c699f86d35d9b08071ece445920b1
- filename: server/src/services/application.service.test.ts
checksum: d11d73a73ed489475d6a2f7dd693787fc71a7dfd9150bb88cec4f88c92c19d02
checksum: 2b61fd4ed86f14a39b8c86531b13690bc586954829dd0c8df277516a500dca2d
- filename: server/src/services/application.service.ts
checksum: 38021ae663db3a146c848a8d691c0c1bc9bb262a80a6f2dedf9fcdb372eef3ac
- filename: server/src/services/eligibleTrainingsForAppointment.service.ts
Expand All @@ -102,7 +104,7 @@ fileignoreconfig:
- filename: server/src/services/partnerJob.service.test.ts
checksum: b488a3b67b801c3fa5b012d2ba2b120b21af4f459b966c6629e6e681ead97913
- filename: server/src/services/recruteurLba.service.test.ts
checksum: 38c947c87cfb508fec88cbce878f89725cbb6679e833a66bdb7fca60e3685a0a
checksum: 704c302541b044c957effcb9c3d49246beaece7eefe002e09fb5883636c632a7
- filename: server/src/services/referrers.service.ts
checksum: 966b0ece2b18b5a6066df531524c97ba0ad38266e782a334004e8c127b41ade6
- filename: server/src/services/trafficSource.service.test.ts
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
},
"dependencies": {
"husky": "^8.0.3",
"stream-json": "^1.9.1",
"type-fest": "^4.18.2"
},
"devDependencies": {
Expand Down
10 changes: 0 additions & 10 deletions server/src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -299,16 +299,6 @@ program
.option("-q, --queued", "Run job asynchronously", false)
.action(createJobAction("brevo:blocked:sync"))

program
.command("update-companies")
.description("Met à jour la liste des sociétés bonnes alternances")
.option("-use-algo-file, [UseAlgoFile]", "télécharge et traite le fichier issu de l'algo", false)
.option("-clear-mongo, [ClearMongo]", "vide la collection des bonnes alternances", false)
.option("-force-recreate, [ForceRecreate]", "pour forcer la recréation", false)
.option("-source-file, [SourceFile]", "fichier source alternatif")
.option("-q, --queued", "Run job asynchronously", false)
.action(createJobAction("companies:update"))

program
.command("update-geo-locations")
.description("Procède à la géolocalisation de masse des sociétés dans le fichier des bonnes alternances")
Expand Down
15 changes: 10 additions & 5 deletions server/src/common/utils/awsUtils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Readable } from "node:stream"

import { DeleteObjectCommand, GetObjectCommand, HeadObjectCommand, PutObjectCommand, PutObjectRequest, S3Client, S3ClientConfig } from "@aws-sdk/client-s3"
import { Upload } from "@aws-sdk/lib-storage"
import { getSignedUrl } from "@aws-sdk/s3-request-presigner"
Expand Down Expand Up @@ -28,7 +30,8 @@ function getBucketName(bucket: Bucket) {
export async function s3ReadAsStream(bucket: Bucket, key: string) {
try {
const object = await s3Client.send(new GetObjectCommand({ Bucket: getBucketName(bucket), Key: key }))
return object.Body?.transformToWebStream()
const webStream = object.Body?.transformToWebStream()
return Readable.fromWeb(webStream as any)
} catch (error: any) {
const newError = internal(`Error reading S3 file stream`, { key: key, bucket: getBucketName(bucket) })
newError.cause = error.message
Expand Down Expand Up @@ -86,22 +89,24 @@ export async function s3Delete(bucket: Bucket, fileKey: string) {
}

export const s3SignedUrl = async (bucket: Bucket, key: string, options: RequestPresigningArguments = {}) => {
const bucketName = getBucketName(bucket)
try {
const url = getSignedUrl(s3Client, new GetObjectCommand({ Bucket: bucket, Key: key }), options)
const url = getSignedUrl(s3Client, new GetObjectCommand({ Bucket: bucketName, Key: key }), options)
return url
} catch (error: any) {
const newError = internal(`error getting s3 file url`, { key, bucket })
const newError = internal(`error getting s3 file url`, { key, bucketName })
newError.cause = error.message
throw newError
}
}

export const getS3FileLastUpdate = async (bucket: Bucket, key: string): Promise<Date | null> => {
const bucketName = getBucketName(bucket)
try {
const headResponse = await s3Client.send(new HeadObjectCommand({ Bucket: bucket, Key: key }))
const headResponse = await s3Client.send(new HeadObjectCommand({ Bucket: bucketName, Key: key }))
return headResponse.LastModified ? new Date(headResponse.LastModified) : null
} catch (error: any) {
const newError = internal(`error getting s3 head object`, { key, bucket })
const newError = internal(`error getting s3 head object`, { key, bucketName })
newError.cause = error.message
throw newError
}
Expand Down
8 changes: 4 additions & 4 deletions server/src/common/utils/mongodbUtils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { captureException } from "@sentry/node"
import { isEqual } from "lodash-es"
import { Collection, CollectionInfo, MongoClient, MongoServerError } from "mongodb" // to uncomment when migrated to V7
import { Collection, CollectionInfo, Db, IndexDescriptionInfo, MongoClient, MongoServerError } from "mongodb"
import { IModelDescriptor } from "shared/models/common"
import { CollectionName, IDocument, modelDescriptors } from "shared/models/models"
import { zodToMongoSchema } from "zod-mongodb-schema"
Expand Down Expand Up @@ -63,19 +63,19 @@ export const closeMongodbConnection = async () => {
return mongodbClient?.close()
}

export const getDatabase = () => {
export const getDatabase = (): Db => {
return ensureInitialization().db()
}

export const getDbCollection = <K extends CollectionName>(name: K): Collection<IDocument<K>> => {
return ensureInitialization().db().collection(name)
}

export const getCollectionList = () => {
export const getCollectionList = (): Promise<(CollectionInfo | Pick<CollectionInfo, "name" | "type">)[]> => {
return ensureInitialization().db().listCollections().toArray()
}

export const getDbCollectionIndexes = async (name: CollectionName) => {
export const getDbCollectionIndexes = async (name: CollectionName): Promise<IndexDescriptionInfo[]> => {
return await ensureInitialization().db().collection(name).indexes()
}

Expand Down
55 changes: 25 additions & 30 deletions server/src/http/controllers/application.controller.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { omit } from "lodash-es"
import { ObjectId } from "mongodb"
import { generateLbaCompanyFixture } from "shared/fixtures/recruteurLba.fixture"
import { OPCOS_LABEL } from "shared/constants"
import { LBA_ITEM_TYPE } from "shared/constants/lbaitem"
import { generateJobsPartnersOfferPrivate } from "shared/fixtures/jobPartners.fixture"
import { beforeEach, describe, expect, it, vi } from "vitest"

import { s3WriteString } from "@/common/utils/awsUtils"
Expand All @@ -24,36 +26,29 @@ useMongo()

describe("POST /v1/application", () => {
const httpClient = useServer()
const recruteur = generateLbaCompanyFixture({
siret: "11000001500013",
raison_sociale: "ASSEMBLEE NATIONALE",
enseigne: "ASSEMBLEE NATIONALE",
naf_code: "8411Z",
naf_label: "Administration publique générale",
rome_codes: ["G1203", "I1203", "M1602", "M1607", "K2303", "K1802", "K1707", "K1206", "I1101", "M1501", "K1404", "K1202", "M1601"],
street_number: "126",
street_name: "RUE DE L UNIVERSITE",
insee_city_code: "75107",
zip_code: "75007",
city: "Paris",
geo_coordinates: "48.860825,2.318606",
geopoint: {
const recruteur = generateJobsPartnersOfferPrivate({
partner_label: LBA_ITEM_TYPE.RECRUTEURS_LBA,
workplace_siret: "11000001500013",
workplace_legal_name: "ASSEMBLEE NATIONALE",
workplace_brand: "ASSEMBLEE NATIONALE",
workplace_naf_code: "8411Z",
workplace_naf_label: "Administration publique générale",
offer_rome_codes: ["G1203", "I1203", "M1602", "M1607", "K2303", "K1802", "K1707", "K1206", "I1101", "M1501", "K1404", "K1202", "M1601"],
workplace_address_label: "126 RUE DE L UNIVERSITE 75107 Paris",
workplace_geopoint: {
coordinates: [2.318606, 48.860825],
type: "Point",
},
email: "[email protected]",
phone: null,
company_size: "1000-1999",
website: null,
opco: "Opco Mobilités",
opco_short_name: "MOBILITE",
opco_url: "https://www.opcomobilites.fr/",
apply_email: "[email protected]",
apply_phone: null,
workplace_size: "1000-1999",
workplace_website: null,
workplace_opco: OPCOS_LABEL.MOBILITE,
created_at: new Date("2024-07-04T23:24:58.995Z"),
last_update_at: new Date("2024-07-04T23:24:58.995Z"),
})

beforeEach(async () => {
await getDbCollection("recruteurslba").insertOne(recruteur)
await getDbCollection("jobs_partners").insertOne(recruteur)
})

it("should create an application with minimal fileds used", async () => {
Expand All @@ -67,8 +62,8 @@ describe("POST /v1/application", () => {
applicant_first_name: "Jean",
applicant_last_name: "Dupont",
applicant_phone: "0101010101",
company_siret: recruteur.siret,
company_name: recruteur.enseigne,
company_siret: recruteur.workplace_siret,
company_name: recruteur.workplace_legal_name,
caller: "Open Data 42",
}

Expand Down Expand Up @@ -102,21 +97,21 @@ describe("POST /v1/application", () => {
applicant_attachment_name: body.applicant_file_name,
applicant_message_to_company: "",
caller: body.caller,
company_address: "126 RUE DE L UNIVERSITE, 75007 Paris",
company_email: recruteur.email,
company_address: "126 RUE DE L UNIVERSITE 75107 Paris",
company_email: recruteur.apply_email,
company_feedback: null,
company_feedback_send_status: null,
company_feedback_reasons: null,
company_naf: "Administration publique générale",
company_name: "ASSEMBLEE NATIONALE",
company_phone: null,
company_recruitment_intention: null,
company_siret: recruteur.workplace_siret,
company_recruitment_intention_date: null,
company_siret: recruteur.siret,
created_at: expect.any(Date),
job_id: recruteur._id.toString(),
job_origin: "recruteurs_lba",
job_title: "ASSEMBLEE NATIONALE",
job_title: "Une super offre d'alternance",
last_update_at: expect.any(Date),
scan_status: "WAITING_FOR_SCAN",
to_applicant_message_id: null,
Expand Down
60 changes: 35 additions & 25 deletions server/src/http/controllers/unsubscribeRecruteurLba.controller.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { ObjectId } from "mongodb"
import { IUnsubscribedLbaCompany, zRoutes } from "shared"
import { UNSUBSCRIBE_EMAIL_ERRORS } from "shared/constants"
import { IJobsPartnersRecruteurAlgoPrivate, JOBPARTNERS_LABEL } from "shared/models/jobsPartners.model"
import { IUnsubscribeQueryResponse } from "shared/models/unsubscribedRecruteurLba.model"

import { asyncForEach } from "@/common/utils/asyncUtils"
import { getStaticFilePath } from "@/common/utils/getStaticFilePath"
import { getDbCollection } from "@/common/utils/mongodbUtils"
import { obfuscateLbaCompanyApplications } from "@/services/application.service"
import { buildLbaCompanyAddress } from "@/services/recruteurLba.service"

import config from "../../config"
import mailer from "../../services/mailer.service"
Expand Down Expand Up @@ -36,55 +36,65 @@ export default function (server: Server) {
const reason = req.body.reason
const sirets = req.body.sirets

const criteria: { email: string; siret?: { $in: string[] } } = { email }
if (sirets) {
if (sirets.length) {
criteria.siret = { $in: sirets }
} else {
return res.status(400).send({ result: UNSUBSCRIBE_EMAIL_ERRORS.WRONG_PARAMETERS })
}
const query: { apply_email: string; workplace_siret?: { $in: string[] }; partner_label: string } = { apply_email: email, partner_label: JOBPARTNERS_LABEL.RECRUTEURS_LBA }
if (sirets && sirets.length) {
query.workplace_siret = { $in: sirets }
} else {
return res.status(400).send({ result: UNSUBSCRIBE_EMAIL_ERRORS.WRONG_PARAMETERS })
}

const lbaCompaniesToUnsubscribe = await getDbCollection("recruteurslba").find(criteria).limit(ARBITRARY_COMPANY_LIMIT).toArray()
const lbaCompaniesToUnsubscribe = (await getDbCollection("jobs_partners").find(query).limit(ARBITRARY_COMPANY_LIMIT).toArray()) as IJobsPartnersRecruteurAlgoPrivate[]

if (!lbaCompaniesToUnsubscribe.length) {
result = { result: UNSUBSCRIBE_EMAIL_ERRORS.NON_RECONNU }
} else if (lbaCompaniesToUnsubscribe.length > 1 && !sirets) {
const companies = lbaCompaniesToUnsubscribe.map((company) => {
return { enseigne: company.enseigne, siret: company.siret, address: buildLbaCompanyAddress(company) }
return { enseigne: company.workplace_legal_name, siret: company.workplace_siret, address: company.workplace_address_label }
})
result = { result: UNSUBSCRIBE_EMAIL_ERRORS.ETABLISSEMENTS_MULTIPLES, companies }
} else {
const now = new Date()
await asyncForEach(lbaCompaniesToUnsubscribe, async (company) => {
const { siret, raison_sociale, enseigne, naf_code, naf_label, rome_codes, insee_city_code, zip_code, city, company_size, created_at, last_update_at } = company
const {
workplace_siret,
workplace_legal_name,
workplace_brand,
workplace_naf_code,
workplace_naf_label,
offer_rome_codes,
workplace_address_zipcode,
workplace_address_city,
workplace_size,
created_at,
updated_at,
} = company
const unsubscribedLbaCompany: IUnsubscribedLbaCompany = {
_id: new ObjectId(),
siret,
raison_sociale,
enseigne,
naf_code,
naf_label,
rome_codes,
insee_city_code,
zip_code,
city,
company_size,
siret: workplace_siret!,
raison_sociale: workplace_legal_name,
enseigne: workplace_brand!,
naf_code: workplace_naf_code!,
naf_label: workplace_naf_label!,
rome_codes: offer_rome_codes,
insee_city_code: workplace_address_zipcode,
zip_code: workplace_address_zipcode,
city: workplace_address_city,
company_size: workplace_size,
created_at,
last_update_at,
last_update_at: updated_at,
unsubscribe_reason: reason,
unsubscribe_date: now,
}

await getDbCollection("unsubscribedrecruteurslba").insertOne(unsubscribedLbaCompany)

const lbaCompanyToUnsubscribe = await getDbCollection("recruteurslba").findOne({ siret })
const lbaCompanyToUnsubscribe = await getDbCollection("jobs_partners").findOne({ workplace_siret, partner_label: JOBPARTNERS_LABEL.RECRUTEURS_LBA })
if (lbaCompanyToUnsubscribe) {
await getDbCollection("recruteurslba").deleteOne({ _id: lbaCompanyToUnsubscribe._id })
await getDbCollection("jobs_partners").deleteOne({ _id: lbaCompanyToUnsubscribe._id })
}

if (reason === "OPPOSITION") {
await obfuscateLbaCompanyApplications(siret)
await obfuscateLbaCompanyApplications(workplace_siret!)
}
})

Expand Down
Loading
Loading