The Problem

Job seekers waste hours aggregating listings, tailoring resumes, and manually tracking responses. The lack of a unified, automatable pipeline forces repetitive copy‑paste and makes it easy to miss interview invitations or visa‑related constraints.

What This Does

job-ops is a self‑hosted suite of six loosely coupled projects that together provide:

  • Orchestrator – a React front‑end (orchestrator/src/client/**) and Express API (orchestrator/src/server/**) that surface search results, score fits, and store application state.
  • Extractors – TypeScript scrapers for individual boards (extractors/adzuna/src/main.ts, extractors/gradcracker/src/main.ts, etc.). Each exporter implements a main() entry point that returns normalized job objects.
  • Shared – type definitions and small utilities (shared/src/types/**, shared/src/location-support.ts).
  • Docs‑site – a static Docusaurus site (docs-site/src/components/HomepageFeatures/index.tsx) that ships the user guide.
  • Docker – Dockerfile and docker‑compose.yml wrap the whole stack, exposing the UI on port 3005 and the API on 3000.

Key files: orchestrator/src/client/api/client.ts (central API wrapper), orchestrator/src/server/services/llm/codex/client.ts (LLM calls), shared/src/types/design-resume.ts (resume schema), and the extractor manifest.ts files (common CLI flags).

How It Is Wired

Execution starts at the orchestrator UI (orchestrator/src/client/App.tsx) which imports the API client (orchestrator/src/client/api/client.ts). That client is the hub: 126 functions, 7 classes, and 41 downstream calls, including a model inference (codex/client.ts) and file I/O (readMetadata).

  • API flow – UI actions invoke cn (a utility in orchestrator/src/lib/utils.ts) which builds request payloads and calls fetchApi. fetchApi is referenced 82 times across the code base, making it the next‑largest blast radius.
  • Server side – Express routes (orchestrator/src/server/api/routes/**) forward to service modules. The most connected service is orchestrator/src/server/services/llm/types (17 inbound, 0 outbound imports).
  • Extractor pipeline – each extractor’s main() (e.g., extractors/ukvisajobs/src/main.ts) is invoked by the orchestrator’s job‑search scheduler (not fully mapped in the static analysis). The run function in extractors/adzuna/src/main.ts reaches 91 internal functions before returning a list of jobs.
  • External effects –
  • Filesystem: initialize → readMetadata reads metadata.json.
  • Network: start (test) performs a session.request.
  • LLM inference: orchestrator/src/server/services/llm/codex/client.ts calls the configured model endpoint.

Circular imports involve orchestrator/src/client/components/index.ts, shared/src/location-support.ts, and shared/src/types/settings.ts; breaking this cycle would reduce coupling. Deeply nested components (ConversionAnalytics.tsx, SettingsPage.tsx) exhibit indentation depth 8, indicating complex render logic.

How To Use It

# Clone the upstream repo
git clone https://github.com/moses-y/job-ops.git
cd job-ops

# Build and start all services (Docker Compose uses the provided Dockerfile)
docker compose up -d
  • Configuration – copy .env.example to .env and fill in required keys (CODex_API_KEY, OPENAI_API_KEY, etc.) as documented in the README.
  • Running the UI – open http://localhost:3005 in a browser; the React app will load the Express API at http://localhost:3000.
  • Running an extractor manually – from the repo root: ``bash npx ts-node extractors/adzuna/src/main.ts ` (or compile with npm run build` in the extractor folder and execute the generated JS.)

Real‑World Use

A recruiter‑tracking service can embed JobOps by calling the API endpoint POST /api/jobs/search (implemented in orchestrator/src/server/api/routes/search.ts). The response includes a scored list of jobs and a generated resume PDF URL, which the service can forward to candidates automatically.

Code Health & Issues

Static analysis summary – 98 measured findings: 24 high, 74 medium, 0 low. Notable categories: oversized files, deep nesting, hub modules, import cycles, and duplicated manifest code.

Audit findings – 10 items (2 high, 6 medium, 2 low):

  • HIGH – Pin GitHub Action versions to commit SHAs (.github/workflows/*).
  • HIGH – Replace wildcard CORS (cors()) with an explicit allow‑list (orchestrator/src/server/app.ts).
  • MEDIUM – Declare least‑privilege GITHUB_TOKEN permissions (.github/workflows/ci.yml).
  • MEDIUM – Add Dependabot/Renovate (.github/dependabot.yml).
  • MEDIUM – Pin Docker base image by digest (Dockerfile).
  • MEDIUM – Add a dependency‑vulnerability scan step to CI.
  • MEDIUM – Set persist-credentials: false on checkout.
  • MEDIUM – Run container as non‑root (USER directive).
  • LOW – Set timeout-minutes on workflow jobs.
  • LOW – Add convention files (.editorconfig, .gitattributes, formatter config).

All tests, CI, licence, and lockfile are present; no committed secrets were detected.

The Bottom Line

JobOps delivers a functional, containerized job‑search pipeline with a polished UI and extensible scrapers. The codebase works but suffers from large, tightly coupled modules, deep nesting, and several high‑severity security hygiene gaps. It is suitable for teams comfortable refactoring TypeScript and adding CI hardening, but requires immediate fixes to CORS, action pinning, and Docker user handling before production use.