Files

289 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Job Tracker
Job Tracker is a simple, self-hosted app for tracking job applications with a React frontend and an ASP.NET Core API backed by SQLite.
## Features (high level)
- Track job applications (status, applied date, notes, tags, follow-up dates, deadlines, salary, links, etc.)
- Company management (location/source, recruiter details, pipeline stage, next contact date)
- Correspondence log per application (email/messages with subject/channel/date)
- Attachments per application (upload, list, download, rename, delete)
- Reminders + follow-up “needs attention” logic driven by configurable rules
- History/event trail per application (created, status changes, follow-up set, delete/restore)
- Export jobs to JSON/CSV + daily scheduled JSON export
- Optional “job import” preview from supported job sites (plugins) + optional translation to English
- Optional local AI service for short/full descriptions
- Optional Google sign-in (Google ID tokens) to protect the API
## Architecture
- `job-tracker-ui/`: React app (runs on `http://localhost:3000` in dev)
- `JobTrackerApi/`: ASP.NET Core API (defaults to `http://localhost:5202`)
- SQLite DB file: defaults to `JobTrackerApi/jobtracker.db` unless `Data:Root` / connection string overrides it
- Attachments: stored on disk under `DataRoot/Attachments/<jobId>/...`
- Optional local AI service: `tools/summarizer/` (FastAPI) used by the API via `Ai:BaseUrl`
## Quickstart (Docker)
This runs: frontend (nginx), backend API, the local AI service, and an Ollama container for hybrid CV block classification.
1) Create a `.env` file next to `docker-compose.yml` (you can start from `.env.example`).
```bash
docker compose up --build
```
- UI: `http://localhost:3000`
- API: available via the UI container reverse proxy at `http://localhost:3000/api/...`
- Persistent data: stored in the `jobtracker_data` Docker volume (mounted at `/data` in the API container)
## Local development
### Prereqs
- .NET SDK `9.x` (API targets `net9.0`)
- Node.js (for the UI)
- (Optional) Python 3.x if running the AI service without Docker
### 1) Run the API
```bash
cd JobTrackerApi
dotnet restore
dotnet run
```
By default the API listens on `http://localhost:5202` (see `JobTrackerApi/Properties/launchSettings.json`).
### Local preflight before browser/UAT
Run the backend first from `JobTrackerApi/`, then use the preflight gate from the repo root:
```bash
bash scripts/s06-preflight.sh
```
The preflight assumes the dev pairing used by `job-tracker-ui/src/api.ts` and `JobTrackerApi/appsettings.Development.json`:
- UI origin: `http://localhost:3000`
- API base: `http://localhost:5202/api`
It first checks the anonymous `GET /api/auth/config` endpoint, then probes `GET /api/admin/system` for DB/Gmail/AI readiness.
If auth is enabled and `/api/admin/system` returns `401/403`, export an admin bearer token first and rerun:
```bash
export AUTH_TOKEN="<admin bearer token>"
bash scripts/s06-preflight.sh
```
To obtain a local admin token in dev, log in against the API with the seeded admin email/password from `JobTrackerApi/appsettings.Development.json` (or your environment override) via `POST /api/auth/login`, then export only the returned access token. The script never prints token values. Use `API_BASE` if your API is not on the default dev port.
### Seed acceptance-ready data
After preflight passes and you have a bearer token, seed one deterministic acceptance fixture for the `/jobs` → workspace → follow-up → dashboard/reminders rerun:
```bash
export AUTH_TOKEN="<bearer token>"
bash scripts/s06-acceptance-data.sh
```
The script reuses `scripts/s06-preflight.sh`, creates or reuses the acceptance company/job, saves tailored package material, ensures one deterministic recruiter-thread correspondence entry, schedules follow-up readiness, and prints the seeded ids/readiness summary without echoing the token.
If the placeholder development password no longer matches the local DB, use the real account for this environment or a bearer token from an already-authenticated local browser session.
### 2) Run the UI
```bash
cd job-tracker-ui
npm install
npm start
```
The UI defaults to calling `http://localhost:5202/api` when running on localhost (see `job-tracker-ui/src/api.ts`).
### 3) (Optional) Run the AI service
The API calls a local FastAPI service to generate summaries. If its not running, the app still works (summary generation may be empty / best-effort).
With Docker (recommended):
```bash
# One command for local Ollama startup + pull + AI-service restart
OLLAMA_MODEL=qwen2.5:7b ./scripts/start-ollama-cv.sh
# Then start the rest of the app if needed
docker compose up --build -d backend frontend
```
The first Ollama startup is usually quick, but the first model pull and first generation can take a while. After the model is cached in the `ollama_data` volume, later restarts are much faster.
Or run directly from `tools/summarizer/` (see `tools/summarizer/README.md`).
## Configuration
### API settings (appsettings / env vars)
Common keys:
- `ConnectionStrings:JobTracker`: overrides SQLite location (otherwise uses `DataRoot/jobtracker.db`)
- `Data:Root`: folder for the SQLite DB + exports (defaults to API content root)
- `Data:AttachmentsRoot`: override attachments folder (defaults to `<Data:Root>/Attachments`)
- `Cors:Origins`: list of allowed origins (defaults to `http://localhost:3000`; use `"*"` to allow all)
- `Ai:BaseUrl`: AI service base URL (default `http://127.0.0.1:8001`)
- `Exports:DailyEnabled`: enable/disable daily export background job
- `Exports:DailyFolder`: export destination (relative to `Data:Root` if not absolute)
- `Exports:DailyHourLocal`: local hour (023) when the daily export runs
- `Auth:GoogleClientId`: if set, enables JWT bearer validation for Google ID tokens
- `Auth:JwtKey`: secret used to sign local JWTs for username/password login (set via env var `Auth__JwtKey`)
- `Auth:JwtIssuer`: JWT issuer (default `JobTrackerApi`)
- `Auth:JwtAudience`: JWT audience (default `job-tracker-ui`)
- `Auth:JwtExpiresMinutes`: access token lifetime in minutes (default `720`)
- `Auth:AdminEmail` / `Auth:AdminPassword`: optional seed admin user (created on startup if missing)
- `Auth:AllowRegistration`: allow self-service registration via `POST /api/auth/register` (default `false`)
- `Auth:Require`: if `true`, all endpoints require auth (except endpoints explicitly marked anonymous)
- `Translation:Provider`: `none` (default) or `libretranslate`
- `Translation:LibreTranslate:BaseUrl`: base URL for LibreTranslate (only if provider enabled)
- `Translation:LibreTranslate:ApiKey`: optional API key for LibreTranslate
- `App:PublicBaseUrl`: public base URL used when generating links in emails (example: `https://jobs.cesnimda.uk`)
- `Email:Enabled`: enable SMTP sending (`true`/`false`)
- `Email:SmtpHost`: SMTP host (for Gmail: `smtp.gmail.com`)
- `Email:SmtpPort`: SMTP port (for Gmail: `587`)
- `Email:SmtpUser`: SMTP username (often your Gmail address)
- `Email:SmtpPassword`: SMTP password (for Gmail: use an App Password)
- `Email:From`: from address (default: `Email:SmtpUser`)
- `Email:FromName`: from name (default: `Jobbjakt`)
- `Email:FollowUpReminders:Enabled`: enable scheduled follow-up reminder emails
- `Email:FollowUpReminders:UpcomingDays`: how far ahead reminder emails look for upcoming follow-up dates (default `2`)
### UI settings
- `REACT_APP_API_BASE_URL`: override the API base URL (example: `http://localhost:5202/api`)
## API endpoint reference
Base URL in local dev: `http://localhost:5202` (all routes are under `/api/...`).
Authentication:
- If `Auth:GoogleClientId` is configured, most endpoints require `Authorization: Bearer <google_id_token>`.
- If its not configured, endpoints are effectively anonymous.
### Job applications (`/api/jobapplications`)
- `GET /api/jobapplications`
- Query: `page`, `pageSize` (15/20/25), `q`, `status`, `companyId`, `location`, `needsFollowUp`, `includeDeleted`, `deletedOnly`, `sortBy`, `sortDir`
- Returns a paged list of `JobApplicationDto` (includes computed follow-up flags and short summary).
- `GET /api/jobapplications/{id}`
- Returns a single `JobApplicationDto` (includes computed follow-up flags and a “full” summary on demand).
- `GET /api/jobapplications/board?includeDeleted=false`
- Returns all job applications (intended for a board/overview view).
- `GET /api/jobapplications/reminders?upcomingDays=7`
- Returns jobs that need follow-up / are in key statuses and have upcoming follow-up dates.
- `POST /api/jobapplications`
- Body: `CreateJobApplicationRequest` (job title, company id, status, notes/description, follow-up fields, tags, attachments flags, etc.)
- Creates a job application and a `JobEvent` of type `Created`.
- `PUT /api/jobapplications/{id}`
- Body: `UpdateJobApplicationRequest`
- Updates an application; records a `StatusChanged` event if the status changed.
- `PATCH /api/jobapplications/{id}/status`
- Body: `{ "status": "..." }`
- Updates only status; records `StatusChanged` if it changed.
- `PATCH /api/jobapplications/{id}/followup`
- Body: `{ "followUpAt": "2026-03-13T12:00:00Z" }` (or `null`)
- Sets/clears follow-up date; records a `FollowUpSet` event.
- `GET /api/jobapplications/{id}/history`
- Returns `JobEvent` history for the job.
- `GET /api/jobapplications/{id}/timeline`
- Returns a unified timeline combining job events, correspondence, and attachments.
- `GET /api/jobapplications/stats`
- Returns totals, counts by status, applied-last-30-days, and average days since applied.
- `DELETE /api/jobapplications/{id}`
- Soft-deletes an application (`IsDeleted=true`); records a `Deleted` event.
- `POST /api/jobapplications/{id}/restore`
- Restores a soft-deleted application; records a `Restored` event.
### Companies (`/api/companies`)
- `GET /api/companies`: list companies
- `GET /api/companies/{id}`: company by id
- `POST /api/companies`: create (idempotent by name; returns existing if already present)
- Body: `{ "name": "...", "location": "...?", "source": "...?" }`
- `PUT /api/companies/{id}`: update
- Body includes recruiter details and `pipelineStage`, `lastContactedAt`, `nextContactAt`
### Correspondence (`/api/correspondence`)
- `GET /api/correspondence/{jobId}`: list messages for a job (ordered by date)
- `POST /api/correspondence`: create message
- Body: `{ "jobApplicationId": 1, "from": "...", "content": "...", "subject": "...?", "channel": "...?", "date": "..."? }`
### Attachments (`/api/attachments`)
- `GET /api/attachments/{jobId}`: list attachments for a job
- `GET /api/attachments/download/{id}`: download an attachment by attachment id
- `POST /api/attachments`: upload files (multipart/form-data)
- Form fields: `jobId` (int), `files` (one or more)
- `PATCH /api/attachments/{id}`: rename attachment
- Body: `{ "fileName": "NewName.pdf" }`
- `DELETE /api/attachments/{id}`: delete attachment record + best-effort delete file on disk
### Rules (`/api/rules`)
- `GET /api/rules`: get rule settings (auto-creates defaults on first request)
- `PUT /api/rules`: update rule settings (values are clamped to sane bounds)
### Export (`/api/export`)
- `GET /api/export/jobs?format=json|csv&includeDeleted=false`
- Downloads a file (`job-tracker-export-YYYY-MM-DD.json` or `.csv`).
### Backup (`/api/backup`)
- `POST /api/backup/encrypted`
- Returns an encrypted backup file (`.jtbackup`).
- Note: only implemented on Windows in this build (uses ASP.NET Data Protection / DPAPI).
### Job import (`/api/jobimport`)
- `POST /api/jobimport/preview`
- Body: `{ "url": "https://..." }`
- Returns a parsed preview payload (`JobImportResult`), if a matching plugin can parse it.
### Client error reporting (`/api/client-errors`)
- `POST /api/client-errors`
- Body: `{ errorId?, message?, stack?, componentStack?, url?, userAgent?, at? }`
- Logs frontend errors into the API logs (best-effort).
### Authentication (`/api/auth`)
- `GET /api/auth/config`: returns auth feature flags for the UI
- `POST /api/auth/login`: local email/password login (returns a signed JWT)
- `POST /api/auth/register`: local registration (only if enabled via `Auth:AllowRegistration=true`)
- `GET /api/auth/me`: returns current user/claims summary for the UI
- `POST /api/auth/request-password-reset`: sends reset email (requires SMTP enabled)
- `POST /api/auth/reset-password`: resets password using emailed token
### Users (`/api/users`) (admin-only)
- `GET /api/users`: list users + roles
- `POST /api/users`: create a user (and optionally roles)
- `PUT /api/users/{id}/roles`: replace roles for a user
- `DELETE /api/users/{id}`: delete user
- `POST /api/users/{id}/send-password-reset`: send a reset email to the user
## Notes for contributors
- The API applies EF Core migrations on startup for the configured SQLite database.
- The background rules engine may automatically transition jobs to `Ghosted` based on rule settings.
- In Docker, the UI proxies `/api/*` to the backend service (see `job-tracker-ui/nginx.conf`).
## Ideas to improve the app (next steps)
- Add first-class “timeline” view combining `JobEvent` history + correspondence + attachments into a single chronological stream (this also naturally supports the “Applied → Interview → Reply → …” flow you mentioned).
- Define a canonical pipeline/status model (enum + ordering) and drive UI badges/board columns from it; allow custom pipelines per user.
- Add Swagger/OpenAPI for the controllers (so endpoint docs stay in sync) + include example requests/responses.
- Add validation + problem-details responses consistently (and keep request/response DTOs stable and versioned).
- Add search improvements (full-text search, filter by tags, filter by date ranges, saved views).
- Add notifications (email/desktop) for follow-ups and upcoming deadlines.