Skip to content
Closed
Changes from 1 commit
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
38 changes: 37 additions & 1 deletion backend/controllers/jobController.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,33 @@
const ADZUNA_COUNTRY = process.env.ADZUNA_COUNTRY || "in";
const CACHE_TTL_MS = 24 * 60 * 60 * 1000;

// Valid country codes for Adzuna API (Issue #1301)
const VALID_ADZUNA_COUNTRIES = [
"au", // Australia
"at", // Austria
"be", // Belgium
"br", // Brazil
"ca", // Canada
"de", // Germany
"es", // Spain
"fr", // France
"gb", // United Kingdom
"in", // India
"it", // Italy
"mx", // Mexico
Comment on lines +16 to +23
"nl", // Netherlands
"nz", // New Zealand
"pl", // Poland
"ru", // Russia
"sg", // Singapore
"us", // United States
"za", // South Africa
];

const isValidCountry = (country) => {
return VALID_ADZUNA_COUNTRIES.includes(country.toLowerCase());
};
Comment on lines +33 to +35

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- controller excerpt ---'
cat -n backend/controllers/jobController.js | sed -n '1,120p'

printf '%s\n' '--- Express dependency/config references ---'
rg -n '"express"|express\(|query parser|queryParser|req\.query|isValidCountry|VALID_ADZUNA_COUNTRIES' \
  package.json package-lock.json yarn.lock pnpm-lock.yaml backend 2>/dev/null | head -250

printf '%s\n' '--- relevant tracked files ---'
git ls-files | rg '(^|/)(package(-lock)?\.json|yarn\.lock|pnpm-lock\.yaml|jobController\.js|app|server|routes?)'

Repository: Canopus-Labs/PrepPilot

Length of output: 8341


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- server configuration ---'
cat -n backend/server.js | sed -n '1,100p'

printf '%s\n' '--- job routes ---'
cat -n backend/routes/jobRoutes.js | sed -n '1,100p'

printf '%s\n' '--- parser configuration and job endpoint tests ---'
rg -n -i 'query\s*parser|set\(["'\'']query|job|country' backend --glob '!package-lock.json' | head -250

printf '%s\n' '--- standalone Node querystring behavior ---'
node - <<'JS'
const querystring = require('node:querystring');

for (const input of [
  'country=us',
  'country=us&country=ca',
  'country[]=us&country[]=ca',
  'country=',
]) {
  const parsed = querystring.parse(input);
  const country = parsed.country;
  let result;
  try {
    result = {
      parsed,
      typeofCountry: typeof country,
      isArray: Array.isArray(country),
      lowercased: country.toLowerCase(),
    };
  } catch (error) {
    result = {
      parsed,
      typeofCountry: typeof country,
      isArray: Array.isArray(country),
      error: error.message,
    };
  }
  console.log(input, JSON.stringify(result));
}
JS

Repository: Canopus-Labs/PrepPilot

Length of output: 10803


🌐 Web query:

Express 5.1 application query parser default simple repeated query parameters arrays official documentation

💡 Result:

In Express 5.1, the default setting for the query parser application setting is 'simple' [1][2]. This parser uses the Node.js built-in querystring module rather than the qs library that was used as the default in previous versions [1][2]. Because of this change, the 'simple' parser does not automatically parse repeated query parameters or bracketed parameters (e.g., items[]) into arrays [3][4]. If you require array parsing for repeated query parameters, you must change the query parser setting [1]. To restore behavior similar to Express 4 (where qs handles array parsing), you can set the parser to 'extended' [4][2]: app.set('query parser', 'extended'); The 'extended' parser uses the qs library, which automatically parses repeated query parameters and bracketed syntax into arrays [4][5]. Note that when using the 'extended' parser, there is a default arrayLimit of 1000, which ensures that repeated keys are correctly returned as arrays rather than objects for up to 1000 items [6][5]. If you need further customization (such as specific qs options), you can also provide a custom function to the query parser setting [7]: const qs = require('qs'); app.set('query parser', (str) => qs.parse(str, { /* custom options */ }));

Citations:


Reject non-string country values before lowercasing.

With Express 5.1’s default parser, ?country=us&country=ca produces an array. .toLowerCase() then throws, and getJobs returns HTTP 500 instead of HTTP 400. Add a typeof country === "string" guard and a regression test.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/controllers/jobController.js` around lines 33 - 35, The
isValidCountry helper currently lowercases non-string values and can throw for
array query parameters. Add a typeof country === "string" guard before calling
toLowerCase, preserving validation for valid strings, and add a regression test
through getJobs that verifies repeated country parameters return HTTP 400 rather
than 500.


// The Jobs feature is optional: without Adzuna credentials it stays dormant
// instead of crashing the server or spamming failed API calls.
const isAdzunaConfigured = () => Boolean(ADZUNA_APP_ID && ADZUNA_API_KEY);
Expand Down Expand Up @@ -53,7 +80,16 @@
.select("role");

const role = req.query.role || latestSession?.role || "software engineer";
const country = req.query.country || ADZUNA_COUNTRY;
const countryParam = req.query.country || ADZUNA_COUNTRY;

// Validate country parameter (Issue #1301)
if (!isValidCountry(countryParam)) {
return res.status(400).json({
message: `Invalid country code. Valid codes are: ${VALID_ADZUNA_COUNTRIES.join(", ")}`,
});
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

const country = countryParam.toLowerCase();
Comment on lines +103 to +109

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Use the normalized default country in refreshJobCache.

If ADZUNA_COUNTRY=US, getJobs uses a role|us cache key, but refreshJobCache at Lines 123-124 uses role|US. The scheduled refresh then writes a cache entry that getJobs does not read. Normalize the default once and pass that value to both the cache key and fetchFromAdzuna().

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/controllers/jobController.js` around lines 83 - 92, Update
refreshJobCache to normalize the resolved country once, then reuse that
lowercase value for both the cache key and fetchFromAdzuna(). Ensure the default
ADZUNA_COUNTRY follows the same normalization as the country used by getJobs,
including when it is configured as uppercase.

const cacheKey = `${role.toLowerCase()}|${country}`;

const cached = await JobCache.findOne({ cacheKey });
Expand Down
Loading