A FastAPI application with a server-rendered Jinja2 UI for tracking job applications. You register or sign in (email/password or Google), upload a screenshot of a listing, and the app extracts structured fields with an OpenAI vision model, saves the row, and lets you mark jobs applied, saved, or rejected from the browser.
Product notes live in reqs.md. PostgreSQL table layout is summarized in db_tables.md; SQL migrations under supabase/migrations/ track schema changes (for example Google columns on users and location on jobs).
- FastAPI — HTTP API and
GET /dashboard - SQLAlchemy + psycopg2 — PostgreSQL (Supabase-friendly)
- Jinja2 +
static/— HTML UI and styles - passlib/bcrypt, python-jose — password hashing and JWT session cookie
- Pillow — validate, resize, and re-encode uploads before vision calls
- OpenAI SDK — vision extraction from normalized images
- Python 3.11+
- PostgreSQL with
usersandjobstables compatible with db_tables.md and any appliedsupabase/migrations/(optionallocationonjobs, Google-related columns onusers) OPENAI_API_KEYfor extraction- Optional: Google OAuth client for “Continue with Google”
Shell examples use bash; on Windows, use the equivalent PowerShell commands and .venv\Scripts\activate.
From the repo root:
python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
pip install -r requirements.txtIf psycopg2-binary fails to build on macOS, install Xcode Command Line Tools:
xcode-select --installcp .env.example .env| Variable | Purpose |
|---|---|
DATABASE_URL |
SQLAlchemy PostgreSQL URL (preferred; Supabase “URI” from the dashboard) |
DB_URL |
Legacy alias for DATABASE_URL if older .env files use it |
SECRET_KEY |
Signs JWT session cookies — e.g. openssl rand -hex 32 |
OPENAI_API_KEY |
Required for /jobs/from-image extraction |
OPENAI_VISION_MODEL |
Optional; defaults to gpt-4.1. Must accept images and structured JSON output. |
GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, GOOGLE_REDIRECT_URI |
Optional; all three must be set to enable Google sign-in |
Do not commit .env or real secrets.
Verified Google emails can link to an existing email/password account (google_sub is stored on that user). Google-only accounts have a nullable password.
- Google Cloud Console: OAuth consent screen + OAuth 2.0 Client ID (Web application).
- Authorized redirect URI must match exactly what you put in
.env(for local dev:http://127.0.0.1:8000/auth/google/callback). - Set
GOOGLE_REDIRECT_URIto that same URL; use HTTPS and your production host in production.
Flow: browser hits GET /auth/google, then Google redirects to GET /auth/google/callback.
uvicorn main:app --reloadOpen http://127.0.0.1:8000. Unauthenticated requests to / redirect to /auth/login.
| Path | Description |
|---|---|
/ |
Pipeline dashboard (authenticated) |
/auth/register, /auth/login |
Registration and login forms |
/auth/logout |
POST — clears session cookie |
/static/... |
CSS and other static assets |
| Path | Description |
|---|---|
/docs, /redoc |
OpenAPI |
/health |
{ "ok": true } or 503 if the database ping fails |
Auth
| Method | Path | Notes |
|---|---|---|
GET |
/auth/me |
Current user |
Jobs
| Method | Path | Notes |
|---|---|---|
POST |
/jobs/from-image |
multipart/form-data: required image (PNG, JPEG, or WebP); optional url (stored only, never fetched) |
GET |
/jobs |
List current user’s jobs, newest first |
PATCH |
/jobs/{id}/apply |
Status Applied, sets date_applied to today |
PATCH |
/jobs/{id}/unapply |
From Applied back to Saved; clears date_applied |
PATCH |
/jobs/{id}/reject |
Status Rejected |
DELETE |
/jobs/{id} |
Remove row |
POST /jobs/scrape is deprecated and returns 410 Gone (URL scraping disabled; use image upload).
- Upload limit after read: 8 MB; types PNG, JPEG, WebP (see
app/services/images.py). - The server normalizes the image (decode, resize bound, re-encode), then calls the vision model.
- Extracted fields: title, company, date_posted (relative phrases may be normalized to a date where possible), and location when visible (city/region/country or “Remote”).
pytest/healthreturns 503 — CheckDATABASE_URL, network/firewall, Supabase SSL/session vs pooler port, and that tables exist.- Vision errors — Confirm
OPENAI_API_KEYandOPENAI_VISION_MODEL; try a clear listing screenshot within type/size limits. - Google sign-in unavailable — All three Google env vars must be non-empty; mis-matched redirect URI is a common mistake.