feat(extractors): expand catalog, smoke coverage, and sourcing docs
CI / Linting (Biome) (push) Failing after 40s
CI / Tests (push) Successful in 5m54s
CI / Type Check (adzuna-extractor) (push) Successful in 1m8s
CI / Type Check (gradcracker-extractor) (push) Successful in 1m11s
CI / Type Check (hiringcafe-extractor) (push) Successful in 1m8s
CI / Type Check (orchestrator) (push) Successful in 1m23s
CI / Type Check (startupjobs-extractor) (push) Successful in 1m6s
CI / Type Check (ukvisajobs-extractor) (push) Successful in 1m7s
CI / Documentation (push) Successful in 1m54s

Adds Arc.dev, BC T-Net, Eluta, iCIMS tenants, QAJobsBoard, and SmartRecruiters
manifests with registry/settings/UI wiring; registers full extractor list in
smoke-extractors and documents supplementary board access paths. Aligns Careerjet
v4 with the url query parameter and fixes strict typing in QAJobsBoard.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-15 22:36:23 -04:00
co-authored by Cursor
parent 67508d56ea
commit c840f289e1
50 changed files with 2926 additions and 101 deletions
+3 -1
View File
@@ -503,7 +503,9 @@ async function fetchApi<T>(
): Promise<T> {
const method = (options?.method || "GET").toUpperCase();
const activeCreds = getActiveBasicAuthCredentials();
let authHeader = activeCreds ? encodeBasicAuthHeaderValue(activeCreds) : undefined;
let authHeader = activeCreds
? encodeBasicAuthHeaderValue(activeCreds)
: undefined;
let authAttempt = 0;
let usernameHint = activeCreds?.username;
@@ -210,6 +210,11 @@ export const getEnabledSources = (
const hasGreenhouseCompanies =
(settings.greenhouseCompanies?.value ?? []).length > 0;
const hasWorkdayTenants = (settings.workdayTenants?.value ?? []).length > 0;
const hasSmartrecruitersCompanies =
(settings.smartrecruitersCompanies?.value ?? []).length > 0;
const hasElutaRssLocations =
(settings.elutaRssLocations?.value ?? []).length > 0;
const hasIcimsTenants = (settings.icimsTenants?.value ?? []).length > 0;
for (const source of orderedSources) {
if (source === "gradcracker") {
@@ -272,6 +277,22 @@ export const getEnabledSources = (
if (hasWorkdayTenants) enabled.push(source);
continue;
}
if (source === "smartrecruiters") {
if (hasSmartrecruitersCompanies) enabled.push(source);
continue;
}
if (source === "icims") {
if (hasIcimsTenants) enabled.push(source);
continue;
}
if (source === "bctenet") {
enabled.push(source);
continue;
}
if (source === "eluta") {
if (hasElutaRssLocations) enabled.push(source);
continue;
}
if (
source === "indeed" ||
source === "linkedin" ||
@@ -286,7 +307,9 @@ export const getEnabledSources = (
source === "arbeitnow" ||
source === "himalayas" ||
source === "weworkremotely" ||
source === "fourdayweek"
source === "fourdayweek" ||
source === "qajobsboard" ||
source === "arcdev"
) {
enabled.push(source);
}
@@ -10,9 +10,9 @@ import { asyncRoute, fail, ok } from "@infra/http";
import { logger } from "@infra/logger";
import { getRequestId } from "@infra/request-context";
import { isDemoMode, sendDemoBlocked } from "@server/config/demo";
import { getJobOwnerProfileId } from "@server/infra/request-context";
import { DEFAULT_JOB_OWNER_PROFILE_ID } from "@server/infra/job-owner-context";
import { parseBasicAuthUsername } from "@server/infra/basic-auth-credentials";
import { DEFAULT_JOB_OWNER_PROFILE_ID } from "@server/infra/job-owner-context";
import { getJobOwnerProfileId } from "@server/infra/request-context";
import * as profilesRepo from "@server/repositories/profiles";
import { getSetting } from "@server/repositories/settings";
import { setBackupSettings } from "@server/services/backup/index";
@@ -30,11 +30,11 @@ import {
} from "@server/services/rxresume";
import { getEffectiveSettings } from "@server/services/settings";
import { applySettingsUpdates } from "@server/services/settings-update";
import { jobSearchProfileSchema } from "@shared/settings-registry";
import {
type UpdateSettingsInput,
updateSettingsSchema,
} from "@shared/settings-schema";
import { jobSearchProfileSchema } from "@shared/settings-registry";
import { type Request, type Response, Router } from "express";
export const settingsRouter = Router();
@@ -251,9 +251,12 @@ settingsRouter.patch(
) {
const parsed = jobSearchProfileSchema.safeParse(input.jobSearchProfile);
if (parsed.success) {
const username = parseBasicAuthUsername(req.headers.authorization)?.trim();
const dataWithOwner =
username ? { ...parsed.data, basicAuthUser: username } : parsed.data;
const username = parseBasicAuthUsername(
req.headers.authorization,
)?.trim();
const dataWithOwner = username
? { ...parsed.data, basicAuthUser: username }
: parsed.data;
await profilesRepo.updateProfile(ownerId, { data: dataWithOwner });
}
}
@@ -272,6 +272,12 @@ export const DEMO_SOURCE_BASE_URLS: Record<JobSource, string> = {
lever: "https://jobs.lever.co",
greenhouse: "https://boards.greenhouse.io",
workday: "https://workday.com",
smartrecruiters: "https://www.smartrecruiters.com",
icims: "https://www.icims.com",
bctenet: "https://www.bctechnology.com",
eluta: "https://www.eluta.ca",
qajobsboard: "https://www.qajobsboard.com",
arcdev: "https://arc.dev",
manual: "https://example.com",
};
@@ -12,13 +12,13 @@ import {
normalizeCountryKey,
} from "@shared/location-support.js";
import { resolveBlockedCompanyKeywordsFromStoredString } from "@shared/resolve-blocked-company-keywords.js";
import { jobSearchProfileSchema } from "@shared/settings-registry.js";
import {
inferCountryKeyFromSearchGeography,
matchesRequestedCity,
resolveSearchCities,
shouldApplyStrictCityFilter,
} from "@shared/search-cities.js";
import { jobSearchProfileSchema } from "@shared/settings-registry.js";
import type { CreateJobInput, PipelineConfig } from "@shared/types";
import { type CrawlSource, progressHelpers, updateProgress } from "../progress";
@@ -106,19 +106,14 @@ const ROLE_TOKEN_STOPWORDS = new Set([
]);
function normalizeText(value: string | null | undefined): string {
return (value ?? "")
.toLowerCase()
.replace(/\s+/g, " ")
.trim();
return (value ?? "").toLowerCase().replace(/\s+/g, " ").trim();
}
function buildRoleMatchers(phrases: string[]): {
phraseMatchers: string[];
tokenMatchers: string[];
} {
const phraseMatchers = phrases
.map((p) => normalizeText(p))
.filter(Boolean);
const phraseMatchers = phrases.map((p) => normalizeText(p)).filter(Boolean);
const tokenSet = new Set<string>();
for (const phrase of phraseMatchers) {
@@ -164,7 +159,10 @@ function filterJobsBySearchProfile(args: {
const body = normalizeText(job.jobDescription);
const haystack = `${title}\n${body}`;
if (dealBreakersLower.length > 0 && matchesAny(haystack, dealBreakersLower)) {
if (
dealBreakersLower.length > 0 &&
matchesAny(haystack, dealBreakersLower)
) {
return false;
}