Skip to content

Commit

Permalink
fix: retour recette smart apply (#1828)
Browse files Browse the repository at this point in the history
* fix: urls & link in new page from email

* fix: cancel/provided link, candidature button state

* fix: keep old routing

* Potential fix for code scanning alert no. 631: Unvalidated dynamic method call

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>

* fix: missing change

* fix: prettier

---------

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
  • Loading branch information
kevbarns and github-advanced-security[bot] authored Feb 6, 2025
1 parent d4f4f51 commit d1c879d
Show file tree
Hide file tree
Showing 13 changed files with 151 additions and 41 deletions.
44 changes: 44 additions & 0 deletions server/src/http/controllers/v2/jobs.controller.v2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,50 @@ export default (server: Server) => {
}
)

server.post(
"/v2/_private/jobs/provided/:id",
{
schema: zRoutes.post["/v2/_private/jobs/provided/:id"],
onRequest: server.auth(zRoutes.post["/v2/_private/jobs/provided/:id"]),
config,
},
async (req, res) => {
const { id } = req.params
const job = await getDbCollection("jobs_partners").findOne({ _id: id })
if (!job) {
throw badRequest("Job does not exist")
}

if (job.offer_status === JOB_STATUS_ENGLISH.POURVUE) {
throw badRequest("Job is already provided")
}
await getDbCollection("jobs_partners").findOneAndUpdate({ _id: id }, { $set: { offer_status: JOB_STATUS_ENGLISH.POURVUE } })
return res.status(200).send({})
}
)

server.post(
"/v2/_private/jobs/canceled/:id",
{
schema: zRoutes.post["/v2/_private/jobs/canceled/:id"],
onRequest: server.auth(zRoutes.post["/v2/_private/jobs/canceled/:id"]),
config,
},
async (req, res) => {
const { id } = req.params
const job = await getDbCollection("jobs_partners").findOne({ _id: id })
if (!job) {
throw badRequest("Job does not exists")
}

if (job.offer_status === JOB_STATUS_ENGLISH.ANNULEE) {
throw badRequest("Job is already canceled")
}
await getDbCollection("jobs_partners").findOneAndUpdate({ _id: id }, { $set: { offer_status: JOB_STATUS_ENGLISH.ANNULEE } })
return res.status(200).send({})
}
)

server.post(
"/v2/jobs/extend/:id",
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { internal } from "@hapi/boom"
import { groupBy } from "lodash-es"
import { ObjectId } from "mongodb"
import { LBA_ITEM_TYPE } from "shared/constants/lbaitem"
import { JOB_STATUS } from "shared/models"

import { getStaticFilePath } from "@/common/utils/getStaticFilePath"
Expand Down Expand Up @@ -82,8 +83,8 @@ export const recruiterOfferExpirationReminderJob = async (threshold: number /* n
job_type: job.job_type,
job_level_label: job.job_level_label,
job_start_date: dayjs(job.job_start_date).format("DD/MM/YYYY"),
supprimer: createCancelJobLink(userWithAccountToUserForToken(contactUser), job._id.toString()),
pourvue: createProvidedJobLink(userWithAccountToUserForToken(contactUser), job._id.toString()),
supprimer: createCancelJobLink(userWithAccountToUserForToken(contactUser), job._id.toString(), LBA_ITEM_TYPE.OFFRES_EMPLOI_LBA),
pourvue: createProvidedJobLink(userWithAccountToUserForToken(contactUser), job._id.toString(), LBA_ITEM_TYPE.OFFRES_EMPLOI_LBA),
})),
threshold,
connectionUrl: createAuthMagicLink(userWithAccountToUserForToken(contactUser)),
Expand Down
25 changes: 21 additions & 4 deletions server/src/services/appLinks.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ApplicationIntention } from "shared/constants/application"
import { LBA_ITEM_TYPE } from "shared/constants/lbaitem"
import { IJob } from "shared/models"
import { IUserWithAccount } from "shared/models/userWithAccount.model"
import { zRoutes } from "shared/routes"
Expand Down Expand Up @@ -95,7 +96,7 @@ export function createCfaUnsubscribeToken(email: string, siret: string) {
)
}

export function createCancelJobLink(user: UserForAccessToken, jobId: string, utmData: string | undefined = undefined) {
export function createCancelJobLink(user: UserForAccessToken, jobId: string, jobOrigin: LBA_ITEM_TYPE, utmData: string | undefined = undefined) {
const token = generateAccessToken(
user,
[
Expand All @@ -108,16 +109,25 @@ export function createCancelJobLink(user: UserForAccessToken, jobId: string, utm
querystring: undefined,
},
}),
generateScope({
schema: zRoutes.post["/v2/_private/jobs/canceled/:id"],
options: {
params: {
id: jobId,
},
querystring: undefined,
},
}),
],
{
expiresIn: "30d",
}
)

return `${config.publicUrl}/espace-pro/offre/${jobId}/cancel?${utmData ? utmData : ""}&token=${token}`
return `${config.publicUrl}/espace-pro/offre/${jobOrigin}/${jobId}/cancel?${utmData ? utmData : ""}&token=${token}`
}

export function createProvidedJobLink(user: UserForAccessToken, jobId: string, utmData: string | undefined = undefined) {
export function createProvidedJobLink(user: UserForAccessToken, jobId: string, jobOrigin: LBA_ITEM_TYPE, utmData: string | undefined = undefined) {
const token = generateAccessToken(
user,
[
Expand All @@ -128,13 +138,20 @@ export function createProvidedJobLink(user: UserForAccessToken, jobId: string, u
querystring: undefined,
},
}),
generateScope({
schema: zRoutes.post["/v2/_private/jobs/provided/:id"],
options: {
params: { id: jobId },
querystring: undefined,
},
}),
],
{
expiresIn: "30d",
}
)

return `${config.publicUrl}/espace-pro/offre/${jobId}/provided?${utmData ? utmData : ""}&token=${token}`
return `${config.publicUrl}/espace-pro/offre/${jobOrigin}/${jobId}/provided?${utmData ? utmData : ""}&token=${token}`
}

export function createViewDelegationLink(email: string, establishment_id: string, job_id: string, siret_formateur: string) {
Expand Down
8 changes: 3 additions & 5 deletions server/src/services/application.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -467,12 +467,10 @@ const buildRecruiterEmailUrls = async (application: IApplication, applicant: IAp
cancelJobUrl: "",
}

if (application.job_id && user) {
urls.jobProvidedUrl = createProvidedJobLink(userForToken, application.job_id, utmRecruiterData)
urls.cancelJobUrl = createCancelJobLink(userForToken, application.job_id, utmRecruiterData)
}
if (application.job_id) {
urls.jobUrl = `${config.publicUrl}${getDirectJobPath(application.job_origin, application.job_id)}${utmRecruiterData}`
urls.jobProvidedUrl = createProvidedJobLink(userForToken, application.job_id, application.job_origin, utmRecruiterData)
urls.cancelJobUrl = createCancelJobLink(userForToken, application.job_id, application.job_origin, utmRecruiterData)
}

return urls
Expand Down Expand Up @@ -1121,7 +1119,7 @@ export const processApplicationEmails = {
throw internal("Email entreprise destinataire rejeté.")
}
},
// get data from applicant

async sendCandidatEmail(application: IApplication, applicant: IApplicant) {
const { job_origin } = application
const { url: urlOfDetail, urlWithoutUtm: urlOfDetailNoUtm } = buildUrlsOfDetail(application)
Expand Down
6 changes: 3 additions & 3 deletions server/static/templates/mail-candidature-partenaire.mjml.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -207,19 +207,19 @@
<br/>
</mj-text>
<mj-text align="left" color="#161616" css-class="text" font-family="Arial, Helvetica, Verdana, sans-serif" font-size="14px" font-weight="400" line-height="24px" padding="0px 0px 12px 0px" text-decoration="underline">
<a href="<%= data.jobProvidedUrl %>" style="font-family: Arial, Helvetica, Verdana, sans-serif; font-weight: 400; text-decoration: underline; color: inherit;">
<a href="<%= data.jobProvidedUrl %>" style="font-family: Arial, Helvetica, Verdana, sans-serif; font-weight: 400; text-decoration: underline; color: inherit;" target="_blank">
✅ Indiquer que mon offre est pourvue
</a>
<br/>
</mj-text>
<mj-text align="left" color="#161616" css-class="text" font-family="Arial, Helvetica, Verdana, sans-serif" font-size="14px" font-weight="400" line-height="24px" padding="0px 0px 12px 0px" text-decoration="underline">
<a href="<%= data.cancelJobUrl %>" style="font-family: Arial, Helvetica, Verdana, sans-serif; font-weight: 400; text-decoration: underline; color: inherit;">
<a href="<%= data.cancelJobUrl %>" style="font-family: Arial, Helvetica, Verdana, sans-serif; font-weight: 400; text-decoration: underline; color: inherit;" target="_blank">
📝 Supprimer mon offre
</a>
<br/>
</mj-text>
<mj-text align="left" color="#161616" css-class="text" font-family="Arial, Helvetica, Verdana, sans-serif" font-size="14px" font-weight="400" line-height="24px" text-decoration="underline">
<a href="<%= data.faqUrl %>" style="font-family: Arial, Helvetica, Verdana, sans-serif; font-weight: 400; text-decoration: underline; color: inherit;">
<a href="<%= data.faqUrl %>" style="font-family: Arial, Helvetica, Verdana, sans-serif; font-weight: 400; text-decoration: underline; color: inherit;" target="_blank">
💬 Consulter la FAQ
</a>
<br/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@
<br/>
</mj-text>
<mj-text align="left" color="#161616" css-class="text" font-family="Marianne, sans-serif" font-size="14px" font-weight="400" line-height="24px" text-decoration="underline">
<span style="font-family: Arial, Helvetica, Verdana, sans-serif; font-weight: 400;">💬</span>&nbsp;<a href="<%= data.faqUrl %>" style="font-family: Arial, Helvetica, Verdana, sans-serif; font-weight: 400; text-decoration: underline; color: inherit;">Consulter la FAQ</a>
<span style="font-family: Arial, Helvetica, Verdana, sans-serif; font-weight: 400;">💬</span>&nbsp;<a href="<%= data.faqUrl %>" style="font-family: Arial, Helvetica, Verdana, sans-serif; font-weight: 400; text-decoration: underline; color: inherit;" target="_blank">Consulter la FAQ</a>
<br/>
</mj-text>
</mj-column>
Expand Down
6 changes: 3 additions & 3 deletions server/static/templates/mail-candidature.mjml.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -207,19 +207,19 @@
<br/>
</mj-text>
<mj-text align="left" color="#161616" css-class="text" font-family="Arial, Helvetica, Verdana, sans-serif" font-size="14px" font-weight="400" line-height="24px" padding="0px 0px 12px 0px" text-decoration="underline">
<a href="<%= data.jobProvidedUrl %>" style="font-family: Arial, Helvetica, Verdana, sans-serif; font-weight: 400; text-decoration: underline; color: inherit;">
<a href="<%= data.jobProvidedUrl %>" style="font-family: Arial, Helvetica, Verdana, sans-serif; font-weight: 400; text-decoration: underline; color: inherit;" target="_blank">
✅ Indiquer que mon offre est pourvue
</a>
<br/>
</mj-text>
<mj-text align="left" color="#161616" css-class="text" font-family="Arial, Helvetica, Verdana, sans-serif" font-size="14px" font-weight="400" line-height="24px" padding="0px 0px 12px 0px" text-decoration="underline">
<a href="<%= data.cancelJobUrl %>" style="font-family: Arial, Helvetica, Verdana, sans-serif; font-weight: 400; text-decoration: underline; color: inherit;">
<a href="<%= data.cancelJobUrl %>" style="font-family: Arial, Helvetica, Verdana, sans-serif; font-weight: 400; text-decoration: underline; color: inherit;" target="_blank">
📝 Supprimer mon offre
</a>
<br/>
</mj-text>
<mj-text align="left" color="#161616" css-class="text" font-family="Arial, Helvetica, Verdana, sans-serif" font-size="14px" font-weight="400" line-height="24px" text-decoration="underline">
<a href="<%= data.faqUrl %>" style="font-family: Arial, Helvetica, Verdana, sans-serif; font-weight: 400; text-decoration: underline; color: inherit;">
<a href="<%= data.faqUrl %>" style="font-family: Arial, Helvetica, Verdana, sans-serif; font-weight: 400; text-decoration: underline; color: inherit;" target="_blank">
💬 Consulter la FAQ
</a>
<br/>
Expand Down
2 changes: 1 addition & 1 deletion shared/constants/lbaitem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export const newItemTypeToOldItemType = (lbaItemType: LBA_ITEM_TYPE): LBA_ITEM_T
case LBA_ITEM_TYPE.RECRUTEURS_LBA:
return LBA_ITEM_TYPE_OLD.LBA
case LBA_ITEM_TYPE.OFFRES_EMPLOI_PARTENAIRES:
return LBA_ITEM_TYPE_OLD.PE
return LBA_ITEM_TYPE_OLD.PARTNER_JOB
case LBA_ITEM_TYPE.FORMATION:
throw new Error("not used")
default:
Expand Down
34 changes: 34 additions & 0 deletions shared/routes/v2/jobs.routes.v2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,40 @@ export const zJobsRoutesV2 = {
},
},
post: {
"/v2/_private/jobs/provided/:id": {
method: "post",
path: "/v2/_private/jobs/provided/:id",
params: z
.object({
id: zObjectId,
})
.strict(),
response: {
"200": z.object({}).strict(),
},
securityScheme: {
auth: "access-token",
access: null,
resources: {},
},
},
"/v2/_private/jobs/canceled/:id": {
method: "post",
path: "/v2/_private/jobs/canceled/:id",
params: z
.object({
id: zObjectId,
})
.strict(),
response: {
"200": z.object({}).strict(),
},
securityScheme: {
auth: "access-token",
access: null,
resources: {},
},
},
"/v2/jobs/provided/:id": {
method: "post",
path: "/v2/jobs/provided/:id",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import CandidatureLba from "../CandidatureLba/CandidatureLba"
import CandidatureParTelephone from "../CandidatureParTelephone"

export const PartnerJobPostuler = ({ job, isCollapsedHeader }: { job: ILbaItemPartnerJob; isCollapsedHeader: boolean }) => {
// KBA fix enum shared/models/lbaItem.model.ts
if (["Pourvue", "Annulée"].includes(job.job.status)) return null
if (job.contact?.email) {
return (
<Box my={4}>
Expand Down
6 changes: 6 additions & 0 deletions ui/next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,12 @@ const nextConfig = {
destination: "/recherche-formation",
permanent: true,
},
// KBA TODO WAR ROOM : to remove on 2025_03_10 : route without jobType will be obsolete
{
source: "/espace-pro/offre/:id/:option",
destination: "/espace-pro/offre/offres_emploi_lba/:id/:option",
permanent: true,
},
]
},
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,36 +7,40 @@ import { getDirectJobPath } from "shared/metier/lbaitemutils"
import Footer from "@/components/footer"
import Navigation from "@/components/navigation"

import { cancelOffre, fillOffre } from "../../../../utils/api"
import { cancelOffre, cancelPartnerJob, fillOffre, providedPartnerJob } from "../../../../../utils/api"

export default function MailActionsOnOffre() {
const router = useRouter()
const { jobId, option, token } = router.query
const { jobId, option, token, jobType } = router.query as { jobId: string; option: string; token: string; jobType: LBA_ITEM_TYPE }
const [result, setResult] = useState("")

const error = () => {
setResult("Une erreur s'est produite. Merci de contacter le support de La bonne alternance")
}

useEffect(() => {
if (jobId && option) {
if (option === "cancel") {
cancelOffre(jobId, token)
.then(() => {
setResult("ok")
})
.catch(() => error())
}
if (!jobId || !option || !jobType) return

const jobActions = {
[LBA_ITEM_TYPE.OFFRES_EMPLOI_LBA]: {
cancel: cancelOffre,
provided: fillOffre,
},
[LBA_ITEM_TYPE.OFFRES_EMPLOI_PARTENAIRES]: {
cancel: cancelPartnerJob,
provided: providedPartnerJob,
},
}

if (option === "provided") {
fillOffre(jobId, token)
.then(() => {
setResult("ok")
})
.catch(() => error())
}
const action = jobActions[jobType]?.[option]
if (action && typeof action === "function") {
action(jobId, token)
.then(() => setResult("ok"))
.catch((error) => {
console.log(error)
setResult("Une erreur s'est produite. Merci de contacter le support de La bonne alternance")
return
})
} else {
setResult("Unsupported action.")
}
}, [jobId, option])
}, [jobId, option, jobType])

const cssParameters = {
background: "#fff1e5",
Expand Down
4 changes: 4 additions & 0 deletions ui/utils/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ export const createOffreByToken = (establishment_id: string, newOffre: IJobCreat
apiPost("/formulaire/:establishment_id/offre/by-token", { params: { establishment_id }, body: newOffre, headers: { authorization: `Bearer ${token}` } })
export const patchOffreDelegation = (jobId: string, siret: string) =>
apiPatch(`/formulaire/offre/:jobId/delegation`, { params: { jobId }, querystring: { siret_formateur: siret } }).catch(errorHandler)
// need a function to cancel partner jobs : add the job_origin from the application in the url - refactor ui/pages/espace-pro/offre/[jobId]/[option].tsx needed
export const cancelPartnerJob = (id, token) => apiPost("/v2/_private/jobs/canceled/:id", { params: { id }, headers: { authorization: `Bearer ${token}` } })
export const providedPartnerJob = (id, token) => apiPost("/v2/_private/jobs/provided/:id", { params: { id }, headers: { authorization: `Bearer ${token}` } })
// once offres_emploi_lba are definitly stored in jobs partners, we can move this call to /jobs/:jobId/cancel
export const cancelOffre = (jobId, token) => apiPut(`/formulaire/offre/:jobId/cancel`, { params: { jobId }, headers: { authorization: `Bearer ${token}` } })
export const cancelOffreFromAdmin = (jobId: string, data: IRoutes["put"]["/formulaire/offre/f/:jobId/cancel"]["body"]["_input"]) =>
apiPut("/formulaire/offre/f/:jobId/cancel", { params: { jobId }, body: data })
Expand Down

0 comments on commit d1c879d

Please sign in to comment.