From 2669f0d81a253196b2cd2a52413079b8567882ae Mon Sep 17 00:00:00 2001 From: TheGreatAion Date: Sun, 31 May 2026 21:43:48 -0400 Subject: [PATCH] add partial job update endpoint --- apps/api/src/controllers/jobController.js | 17 ++++-- apps/api/src/routes/jobRoutes.js | 3 +- apps/api/src/services/jobService.js | 10 ++++ apps/api/src/tests/job-update.test.js | 64 +++++++++++++++++++++++ 4 files changed, 90 insertions(+), 4 deletions(-) create mode 100644 apps/api/src/tests/job-update.test.js diff --git a/apps/api/src/controllers/jobController.js b/apps/api/src/controllers/jobController.js index e067c23073..e3413499f9 100644 --- a/apps/api/src/controllers/jobController.js +++ b/apps/api/src/controllers/jobController.js @@ -1,6 +1,6 @@ -import { ok } from "../utils/response.js"; -import { createJobSchema } from "../validators/job.js"; -import { createJob, listJobs } from "../services/jobService.js"; +import { fail, ok } from "../utils/response.js"; +import { createJobSchema, updateJobSchema } from "../validators/job.js"; +import { createJob, listJobs, updateJob } from "../services/jobService.js"; export async function getJobs(req, res) { return ok(res, await listJobs()); @@ -10,3 +10,14 @@ export async function postJob(req, res) { const payload = createJobSchema.parse(req.body); return ok(res, await createJob(payload), 201); } + +export async function patchJob(req, res) { + const payload = updateJobSchema.parse(req.body); + const job = await updateJob(req.params.id, payload); + + if (!job) { + return fail(res, "Job not found", 404); + } + + return ok(res, job); +} diff --git a/apps/api/src/routes/jobRoutes.js b/apps/api/src/routes/jobRoutes.js index f8ca6d3fc9..4ac9186926 100644 --- a/apps/api/src/routes/jobRoutes.js +++ b/apps/api/src/routes/jobRoutes.js @@ -1,7 +1,8 @@ import { Router } from "express"; -import { getJobs, postJob } from "../controllers/jobController.js"; +import { getJobs, patchJob, postJob } from "../controllers/jobController.js"; export const jobRoutes = Router(); jobRoutes.get("/", getJobs); jobRoutes.post("/", postJob); +jobRoutes.patch("/:id", patchJob); diff --git a/apps/api/src/services/jobService.js b/apps/api/src/services/jobService.js index b677c13eca..5c85674450 100644 --- a/apps/api/src/services/jobService.js +++ b/apps/api/src/services/jobService.js @@ -9,3 +9,13 @@ export async function createJob(payload) { jobs.push(job); return job; } + +export async function updateJob(id, payload) { + const index = jobs.findIndex((job) => job.id === id); + if (index === -1) { + return null; + } + + jobs[index] = { ...jobs[index], ...payload, id }; + return jobs[index]; +} diff --git a/apps/api/src/tests/job-update.test.js b/apps/api/src/tests/job-update.test.js new file mode 100644 index 0000000000..377d853a1b --- /dev/null +++ b/apps/api/src/tests/job-update.test.js @@ -0,0 +1,64 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { createApp } from "../app.js"; + +async function startServer(t) { + const server = createApp().listen(0); + + await new Promise((resolve, reject) => { + server.once("listening", resolve); + server.once("error", reject); + }); + + t.after(() => new Promise((resolve, reject) => { + server.close((error) => (error ? reject(error) : resolve())); + })); + + return `http://127.0.0.1:${server.address().port}`; +} + +test("PATCH /api/jobs/:id applies partial updates and preserves the job id", async (t) => { + const baseUrl = await startServer(t); + const createdResponse = await fetch(`${baseUrl}/api/jobs`, { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ + title: "Original job title", + description: "Original job description", + budgetMin: 100, + budgetMax: 200, + categoryId: "category-1" + }) + }); + const created = await createdResponse.json(); + + const response = await fetch(`${baseUrl}/api/jobs/${created.data.id}`, { + method: "PATCH", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ + id: "client-controlled-id", + title: "Updated job title" + }) + }); + const payload = await response.json(); + + assert.equal(response.status, 200); + assert.equal(payload.data.id, created.data.id); + assert.equal(payload.data.title, "Updated job title"); + assert.equal(payload.data.description, "Original job description"); +}); + +test("PATCH /api/jobs/:id returns 404 for an unknown job", async (t) => { + const baseUrl = await startServer(t); + const response = await fetch(`${baseUrl}/api/jobs/job-missing`, { + method: "PATCH", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ title: "Updated job title" }) + }); + + assert.equal(response.status, 404); + assert.deepEqual(await response.json(), { + success: false, + message: "Job not found" + }); +});