A job discovery and application workspace built with FastAPI, PostgreSQL/pgvector, and TypeScript with jQuery. The existing animated violet interface now reads persisted application data.
flowchart LR
Browser[TypeScript / jQuery / Chart.js / SortableJS] --> Web[nginx]
Web --> API[FastAPI / JWT]
API --> DB[(PostgreSQL / pgvector)]
Worker[Single scheduler and durable SQL worker] --> DB
Worker --> Sources[Configured official job APIs]
Worker --> SMTP[SMTP / optional Mailpit]
Worker --> Model[Local embedding model]
API --> Ollama[Optional Ollama]
API -->|Authenticated SSE| Browser
The API persists work items; one worker processes ingestion, enrichment, matching, alerts, digests and insight snapshots. Redis is unnecessary for the current SQL queue. SQLite supports local development with lexical matching/search fallbacks; PostgreSQL enables vector similarity, full-text search and trigram indexes.
- Copy
.env.exampleto.env. Replace credential placeholders locally; never commit.env. - Configure a PostgreSQL database as below and generate a unique JWT secret. Set a real contact in
INGESTION_USER_AGENT. - Run
docker compose up --build.
Open http://localhost:8080. The API container applies migrations and seeds only taxonomy and curated learning resources. No jobs or accounts are invented at startup. Register a candidate or recruiter through the UI. Source administration requires an administrator account provisioned by the database owner; public registration cannot create administrators.
For local SMTP, set SMTP_HOST=mailpit, SMTP_PORT=1025, SMTP_STARTTLS=false, and run docker compose --profile mail up --build. Mailpit's inbox is http://localhost:8025. Users must opt into email digests in Settings.
For a disposable local Postgres database, set DEV_DB_PASSWORD and use DATABASE_URL=postgresql+psycopg://skillmatch:YOUR_LOCAL_PASSWORD@dev-db:5432/skillmatch, then run docker compose --profile dev up --build. The API waits for database availability. The optional database uses the official pgvector PostgreSQL image; its entrypoint initializes storage before running PostgreSQL as its database user. API, worker, nginx and Mailpit run without root privileges.
Production requires ENVIRONMENT=production, a strong JWT_SECRET, PostgreSQL, COOKIE_SECURE=true, HTTPS termination and explicit CORS_ORIGINS. The example contains no usable credentials. Production rejects demo mode.
Create a project and database in the Neon console, then copy the connection string into your private .env, using the SQLAlchemy driver prefix postgresql+psycopg:// and retaining sslmode=require. Use a database role permitted to create extensions for migration, and a direct connection when applying migrations.
In Neon's SQL editor run:
CREATE EXTENSION IF NOT EXISTS vector;
CREATE EXTENSION IF NOT EXISTS pg_trgm;The migrations also enable these extensions when permitted. See Neon PostgreSQL extensions and pgvector documentation. The schema and embedding model use 384 dimensions; changing models requires a deliberate migration and re-embedding.
Python 3.10+ and Node 20+ are used. The local environment requires 4 concurrently running services:
cd backend
..\.venv\Scripts\python.exe -m uvicorn app.main:app --host 127.0.0.1 --port 8000 --reloadcd backend
..\.venv\Scripts\python.exe -m app.workercd frontend
npm run devOllama must be running and serving models locally:
# Start Ollama server if not already running as a Windows service
ollama serve
# Ensure required local models are pulled
ollama pull all-minilm:l6
ollama pull llama3:latestTo re-embed all jobs, resumes, and recalculate match scores with the active vector backend:
cd backend
..\.venv\Scripts\python.exe -m app.cli re-embedOr use the Re-embed everything button in the Administration dashboard panel.
- Public job browsing with cursor pagination, filters, source attribution, original apply links, detail pages and similar jobs.
- Latest-resume match explanations, ATS-style text checks, per-job suggestions, and database-backed skill-gap resources.
- Saved jobs and a persisted application board with drag and drop, keyboard-accessible status selectors, notes and timelines. Dashboard applications/interviews count these records.
- Saved searches, authenticated SSE notifications, unread/read state and optional SMTP digests.
- Market charts based on stored jobs and daily snapshots. Salary charts separate currency and pay period; missing salary stays unknown.
- Ctrl+K and header search across jobs, companies, skills and pages.
- Per-user theme and preferences. Matching considers available location/remote evidence; role and salary preferences also appear in explanations. Missing evidence is disclosed rather than treated as a perfect match.
- Recruiter native jobs, ranked discoverable candidates and application pipeline. Before application, candidate discovery exposes name and score, not resume/contact information.
- Administrator source configuration, scheduling controls and ingestion run history.
Optional writing assistance requires OLLAMA_ENABLED=true and a reachable OLLAMA_URL with the configured model installed. It drafts text; users review it before use. All other features work without Ollama. ATS checks inspect extracted text and are not a guarantee of compatibility with a particular employer's ATS.
The curated Together AI (Greenhouse), Palantir (Lever), and Cohere (Ashby) engineering boards are enabled in backend/sources.yaml. General feeds remain opt-in. Configure additional permitted company boards/credentials using the administration UI or YAML. Existing database settings take precedence over YAML initialization. Deployment owners must check the source's current terms and permissions before enabling it. The adapters use official endpoints; arbitrary scrape URLs are not accepted.
| Source | API / terms | Configuration |
|---|---|---|
| Greenhouse | Job Board API, terms | Authorized company board slug |
| Lever | Postings API and license, terms | Company slug and region |
| Ashby | Public postings API, terms | Company board slug; unlisted roles excluded |
| Remotive | Public API and usage terms | Attribution and original link; public browsing remains ungated |
| Arbeitnow | API, terms | Public feed |
| Remote OK | API including legal metadata | Preserve source credit and original link |
| Adzuna | API, API terms | Account keys, country and query; respect account quotas |
| Hacker News | Official API and license, guidelines | Optional explicit hiring-thread ID |
Remotive requires source attribution and prohibits requiring signup just to see listings. Adzuna has account-level quotas and permitted-use conditions. Configure cadence to your permitted quota; source retries and page caps do not replace account-level license compliance. Browser tests use isolated mock data. The workspace database has separately received real public feed imports.
The worker imports the Together AI, Palantir and Cohere boards every 12 hours. Their source config uses role_scope: cse to select software/SDE/SWE, AI/ML/GenAI, data, platform/cloud, DevOps/SRE and security engineering roles. Selection uses titles plus technical description evidence for generic engineering titles, and excludes sales/recruiting roles. It is a deterministic relevance filter, not a guarantee that every technical role will be recognized. Set role_scope: all to import every publicly listed role from an authorized board.
Jobs retain employer, actual location, source attribution and original application links. These sources contain onsite/hybrid roles as well as remote roles; location eligibility must still be checked. No country or experience-level restriction is imposed. Ashby salary summaries are used only when one explicit salary component is available; equity and bonuses are not treated as salary.
New boards use the existing source table and worker; no migration or API key is needed for these public board endpoints. After updating code, restart the API and the single worker. Adzuna remains disabled until its credentials are configured. Employer/source usage terms still apply to any redistribution.
The adapter uses https://remotive.com/api/remote-jobs; no API key is required for this public feed. In the administrator Ingestion page, enable the existing Remotive source with a 720-minute interval and run the worker. Source settings already stored in the database take precedence over the initial YAML defaults.
The first live import was completed for this workspace and Remotive was enabled in its configured database. Keep python -m app.worker running from backend for subsequent scheduled imports. The feed is delayed by 24 hours and its returned listing count can differ from the paid website's catalog. Job cards and details retain Remotive source links and public browsing does not require registration. The importer enforces a six-hour minimum between attempts and makes no immediate HTTP retries for Remotive failures.
| UI data | Origin |
|---|---|
| Jobs, companies, salaries and locations | Native recruiter records or configured API ingestion |
| Match scores and missing skills | Latest uploaded resume and persisted job skills/embeddings |
| Application/interview counts, notes, dates | Application records and event timeline |
| Notifications | Saved-search matches, with persisted read state |
| Insight charts | Stored live/native jobs and actual snapshots; no fabricated history |
| Learning resources | Curated database table seeded on setup |
| Marketing copy | Static presentation text |
| Demo jobs | Only explicit python -m app.seed --demo; marked is_demo, hidden by default |
No frontend demo arrays remain. A fresh database can legitimately show empty states. In development only, set DEMO_MODE=true to expose explicit demo rows. Historic synthetic rows are marked by migration rather than deleted. Test fixtures are isolated from the application database. Unknown salary, dates or evidence are displayed as unknown, not estimated.
.venv/Scripts/python.exe -m ruff check backend scripts
.venv/Scripts/python.exe -m pytest backend/tests -q
cd frontend
npm test
npm run build
npx playwright install chromium
npm run test:e2eBrowser tests start an isolated temporary SQLite API and worker on port 8012 and Vite on 5182. Windows uses installed Chrome; CI uses Chromium. They do not reuse or modify your development database. Tests cover the API client, mocked source adapters, deduplication, matching, authorization, migrations and real browser workflows. GitHub Actions runs Ruff, backend/frontend/browser tests and both Docker image builds.
From backend, run python -m alembic upgrade head. Latest migration: c6f4a1b209d3 (five query indexes). Previous migrations are preserved. Back up existing databases before deployment. Run only one worker scheduler in this version. PostgreSQL/vector execution, live provider credentials, real SMTP delivery and model availability require verification in the deployment environment; SQLite tests do not prove those integrations.
Each report links a complete source snapshot, changed-file list and run commands:
- Stage 1: audit and matching
- Stage 2: ingestion and Docker
- Stage 3: backend features
- Stage 4: frontend integration
- Stage 5: tests, CI and documentation
- Stage 6: engineering hardening
Older source exports are historical snapshots; the working files and latest stage snapshots describe the current implementation.