Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions apps/api/src/middleware/asyncHandler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export function asyncHandler(handler) {
return (req, res, next) => {
Promise.resolve(handler(req, res, next)).catch(next);
};
}
12 changes: 11 additions & 1 deletion apps/api/src/middleware/errorHandler.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
import { ZodError } from "zod";

export function errorHandler(err, req, res, next) {
console.error("Unhandled API error:", err);
if (res.headersSent) {
return next(err);
}

if (err instanceof ZodError) {
return res.status(400).json({
success: false,
message: "Validation failed",
errors: err.issues
});
}

console.error("Unhandled API error:", err);
return res.status(500).json({
success: false,
message: "Unexpected server error"
Expand Down
3 changes: 2 additions & 1 deletion apps/api/src/routes/adminRoutes.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { Router } from "express";
import { metrics } from "../controllers/adminController.js";
import { asyncHandler } from "../middleware/asyncHandler.js";
import { authMiddleware } from "../middleware/auth.js";

export const adminRoutes = Router();

adminRoutes.use(authMiddleware);
adminRoutes.get("/metrics", metrics);
adminRoutes.get("/metrics", asyncHandler(metrics));
9 changes: 5 additions & 4 deletions apps/api/src/routes/authRoutes.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { Router } from "express";
import { login, oauthCallback, refresh, register } from "../controllers/authController.js";
import { asyncHandler } from "../middleware/asyncHandler.js";

export const authRoutes = Router();

authRoutes.post("/register", register);
authRoutes.post("/login", login);
authRoutes.get("/oauth/:provider/callback", oauthCallback);
authRoutes.post("/refresh", refresh);
authRoutes.post("/register", asyncHandler(register));
authRoutes.post("/login", asyncHandler(login));
authRoutes.get("/oauth/:provider/callback", asyncHandler(oauthCallback));
authRoutes.post("/refresh", asyncHandler(refresh));
5 changes: 3 additions & 2 deletions apps/api/src/routes/jobRoutes.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Router } from "express";
import { getJobs, postJob } from "../controllers/jobController.js";
import { asyncHandler } from "../middleware/asyncHandler.js";

export const jobRoutes = Router();

jobRoutes.get("/", getJobs);
jobRoutes.post("/", postJob);
jobRoutes.get("/", asyncHandler(getJobs));
jobRoutes.post("/", asyncHandler(postJob));
5 changes: 3 additions & 2 deletions apps/api/src/routes/messageRoutes.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Router } from "express";
import { getMessages, postMessage } from "../controllers/messageController.js";
import { asyncHandler } from "../middleware/asyncHandler.js";

export const messageRoutes = Router();

messageRoutes.get("/", getMessages);
messageRoutes.post("/", postMessage);
messageRoutes.get("/", asyncHandler(getMessages));
messageRoutes.post("/", asyncHandler(postMessage));
5 changes: 3 additions & 2 deletions apps/api/src/routes/notificationRoutes.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Router } from "express";
import { getNotifications, postNotification } from "../controllers/notificationController.js";
import { asyncHandler } from "../middleware/asyncHandler.js";

export const notificationRoutes = Router();

notificationRoutes.get("/", getNotifications);
notificationRoutes.post("/", postNotification);
notificationRoutes.get("/", asyncHandler(getNotifications));
notificationRoutes.post("/", asyncHandler(postNotification));
3 changes: 2 additions & 1 deletion apps/api/src/routes/paymentRoutes.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Router } from "express";
import { createPayment } from "../controllers/paymentController.js";
import { asyncHandler } from "../middleware/asyncHandler.js";

export const paymentRoutes = Router();

paymentRoutes.post("/", createPayment);
paymentRoutes.post("/", asyncHandler(createPayment));
5 changes: 3 additions & 2 deletions apps/api/src/routes/proposalRoutes.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Router } from "express";
import { getProposals, postProposal } from "../controllers/proposalController.js";
import { asyncHandler } from "../middleware/asyncHandler.js";

export const proposalRoutes = Router();

proposalRoutes.get("/", getProposals);
proposalRoutes.post("/", postProposal);
proposalRoutes.get("/", asyncHandler(getProposals));
proposalRoutes.post("/", asyncHandler(postProposal));
5 changes: 3 additions & 2 deletions apps/api/src/routes/reviewRoutes.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Router } from "express";
import { getReviews, postReview } from "../controllers/reviewController.js";
import { asyncHandler } from "../middleware/asyncHandler.js";

export const reviewRoutes = Router();

reviewRoutes.get("/", getReviews);
reviewRoutes.post("/", postReview);
reviewRoutes.get("/", asyncHandler(getReviews));
reviewRoutes.post("/", asyncHandler(postReview));
3 changes: 2 additions & 1 deletion apps/api/src/routes/searchRoutes.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Router } from "express";
import { search } from "../controllers/searchController.js";
import { asyncHandler } from "../middleware/asyncHandler.js";

export const searchRoutes = Router();

searchRoutes.get("/", search);
searchRoutes.get("/", asyncHandler(search));
3 changes: 2 additions & 1 deletion apps/api/src/routes/uploadRoutes.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { Router } from "express";
import multer from "multer";
import { uploadFile } from "../controllers/uploadController.js";
import { asyncHandler } from "../middleware/asyncHandler.js";

const upload = multer({ storage: multer.memoryStorage() });

export const uploadRoutes = Router();

uploadRoutes.post("/", upload.single("file"), uploadFile);
uploadRoutes.post("/", upload.single("file"), asyncHandler(uploadFile));
5 changes: 3 additions & 2 deletions apps/api/src/routes/userRoutes.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Router } from "express";
import { getUsers, postUser } from "../controllers/userController.js";
import { asyncHandler } from "../middleware/asyncHandler.js";

export const userRoutes = Router();

userRoutes.get("/", getUsers);
userRoutes.post("/", postUser);
userRoutes.get("/", asyncHandler(getUsers));
userRoutes.post("/", asyncHandler(postUser));
39 changes: 39 additions & 0 deletions apps/api/src/tests/error-handling.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import test from "node:test";
import assert from "node:assert/strict";
import { createApp } from "../app.js";

async function withServer(callback) {
const app = createApp();
const server = app.listen(0);

await new Promise((resolve, reject) => {
server.once("listening", resolve);
server.once("error", reject);
});

try {
const { port } = server.address();
return await callback(`http://127.0.0.1:${port}`);
} finally {
await new Promise((resolve, reject) => {
server.close((error) => (error ? reject(error) : resolve()));
});
}
}

test("POST /api/jobs returns validation errors instead of hanging", async () => {
await withServer(async (baseUrl) => {
const response = await fetch(`${baseUrl}/api/jobs`, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ title: "bad" })
});
const payload = await response.json();

assert.equal(response.status, 400);
assert.equal(payload.success, false);
assert.equal(payload.message, "Validation failed");
assert.ok(Array.isArray(payload.errors));
assert.ok(payload.errors.some((error) => error.path.includes("description")));
});
});
Loading