docs: consolidate repository guides and preserve audit history
This commit is contained in:
@@ -1,315 +1,52 @@
|
||||
# Job Tracker
|
||||
|
||||
Job Tracker is a self-hosted career workspace built with a Next.js/React frontend and an ASP.NET Core API. SQLite is the local default; MariaDB/MySQL is the supported server-database option.
|
||||
Job Tracker (Jobjakt) is a self-hosted workspace for managing job applications, companies, correspondence, follow-ups, attachments, career profiles, CVs, and optional local/private AI assistance.
|
||||
|
||||
## Features (high level)
|
||||
## Stack
|
||||
|
||||
- Track job applications (status, applied date, notes, tags, follow-up dates, deadlines, salary, links, etc.)
|
||||
- Share opt-in public CV links with recruiter-friendly PDF download
|
||||
- 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
|
||||
- Quick-capture bookmarklet (Settings) + installable PWA with a mobile share-target: both open `/?add=<page url>` to pre-fill Add Job from any posting
|
||||
- Note: no offline service-worker cache is bundled by design (the app is deployed frequently; an aggressive cache would risk serving stale builds). The manifest provides installability and share-to-capture without it.
|
||||
- Optional local AI service for short/full descriptions
|
||||
- Optional Google sign-in (Google ID tokens) to protect the API
|
||||
- Standard Problem Details responses with trace IDs for unhandled API errors
|
||||
- Next.js 16 / React 19 / TypeScript / MUI frontend
|
||||
- ASP.NET Core 9 / EF Core 9 API
|
||||
- SQLite by default; MariaDB/MySQL for server deployments
|
||||
- FastAPI document/AI service with optional Ollama, Gemini, or Groq CV routing
|
||||
- Docker Compose + nginx; Gitea Actions deployment
|
||||
|
||||
## Architecture
|
||||
## Quick start
|
||||
|
||||
- `job-tracker-ui/`: statically exported Next.js 16 / React 19 app (runs on `http://localhost:3000` in dev)
|
||||
- `JobTrackerApi/`: ASP.NET Core API (defaults to `http://localhost:5202`)
|
||||
- Database: SQLite defaults to `JobTrackerApi/jobtracker.db`; MariaDB/MySQL is selected with `Database:Provider`
|
||||
- Attachments: stored on disk under `DataRoot/Attachments/<jobId>/...`
|
||||
- Optional local AI service: `tools/summarizer/` (FastAPI) used by the API via `Ai:BaseUrl`
|
||||
Prerequisites: .NET SDK 9, Node.js 24/npm, and Docker Compose for the full stack.
|
||||
|
||||
## Quickstart (Docker)
|
||||
For Docker, copy `.env.example` to `.env`, fill required values, then run:
|
||||
|
||||
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
|
||||
```powershell
|
||||
docker compose -f docker-compose.yml -f docker-compose.dev.yml 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)
|
||||
Open `http://localhost:3000`. The API is available at `http://localhost:5202` in local process development.
|
||||
|
||||
## Local development
|
||||
For local processes:
|
||||
|
||||
### Prereqs
|
||||
|
||||
- .NET SDK `9.x` (API targets `net9.0`)
|
||||
- Node.js 24 and npm (matching CI)
|
||||
- (Optional) Python 3.12 if running the AI service without Docker
|
||||
|
||||
### 1) Run the API
|
||||
|
||||
```bash
|
||||
```powershell
|
||||
cd JobTrackerApi
|
||||
dotnet restore --locked-mode
|
||||
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
|
||||
cd ..\job-tracker-ui
|
||||
npm ci
|
||||
npm run dev
|
||||
```
|
||||
|
||||
The UI defaults to calling `http://localhost:5202/api` when running on localhost (see `job-tracker-ui/src/api.ts`).
|
||||
## Quality checks
|
||||
|
||||
### 3) Run the browser smoke suite
|
||||
|
||||
```bash
|
||||
```powershell
|
||||
dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --no-restore
|
||||
cd job-tracker-ui
|
||||
npx playwright install chromium
|
||||
npm run test:e2e
|
||||
npm run lint
|
||||
npm test -- --runInBand
|
||||
npm run typecheck
|
||||
npm run build
|
||||
```
|
||||
|
||||
The suite uses `http://localhost:3300`, starts isolated API/SQLite and Next.js processes, then covers login, saved-job creation,
|
||||
Career Workspace, and anonymous public-CV/PDF access. It is also a required CI gate.
|
||||
Run browser tests with `npx playwright install chromium` once, followed by `npm run test:e2e` from `job-tracker-ui/`.
|
||||
|
||||
### 4) (Optional) Run the AI service
|
||||
## Documentation
|
||||
|
||||
The API calls a local FastAPI service to generate summaries. If it’s 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 -f docker-compose.yml -f docker-compose.dev.yml 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`)
|
||||
- `CvExports:RetainDays`: generated PDF retention in days (default `30`, clamped to `1`–`365`)
|
||||
- `Cors:Origins`: list of allowed origins (defaults to `http://localhost:3000`; wildcard origins are rejected because requests use credentials)
|
||||
- `Ai:BaseUrl`: AI service base URL (default `http://127.0.0.1:8001`)
|
||||
- `Workers:RulesEnabled`, `Workers:FollowUpRemindersEnabled`, `Workers:DailyExportEnabled`, `Workers:JobEnrichmentEnabled`: explicit worker kill switches, all default `false`; do not enable before the prerequisites in `docs/architecture/background-workers.md`
|
||||
- `Exports:DailyEnabled`: legacy daily-export setting; both it and `Workers:DailyExportEnabled` must be true
|
||||
- `Exports:DailyFolder`: export destination (relative to `Data:Root` if not absolute)
|
||||
- `Exports:DailyHourLocal`: local hour (0–23) when the daily export runs
|
||||
- `Backups:Enabled`: enable/disable the automated SQLite backup job (default `true`)
|
||||
- `Backups:HourLocal`: local hour (0–23) when the daily database backup runs (default `3`)
|
||||
- `Backups:RetainCount`: how many backup files to keep in `<Data:Root>/backups` (default `14`)
|
||||
- Backups use SQLite `VACUUM INTO` (consistent snapshot, safe with WAL). A catch-up backup runs at startup when none exists from the last 24 h. For MySQL/MariaDB configure external backups instead (see `deploy/MARIADB.md`).
|
||||
- `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)
|
||||
- `Stripe:SecretKey`, `Stripe:PricePremium`, `Stripe:WebhookSecret`: enable hosted Premium checkout,
|
||||
the customer portal, and signed subscription webhooks when all are configured
|
||||
- `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`: the single external origin used for email links, OAuth callbacks, billing redirects, secure cookies, and production Host validation (Production requires HTTPS; local development defaults to `http://localhost:3000`)
|
||||
- `Auth:MicrosoftTenant`: Microsoft application sign-in policy (`common`, `organizations`, `consumers`, or one tenant GUID); required in Production when Microsoft sign-in is enabled and distinct from Graph's `Microsoft:TenantId`
|
||||
- `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
|
||||
|
||||
- `NEXT_PUBLIC_API_BASE_URL`: override the API base URL (example: `http://localhost:5202/api`)
|
||||
|
||||
The complete environment template is `.env.example`. Production requirements and restore/rollback procedures are maintained in `deploy/README.md`; current component boundaries and supported providers are documented in `docs/architecture/current.md`.
|
||||
|
||||
## 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 it’s 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. The status is normalized against the canonical pipeline (casing + known synonyms like `Interviewing`→`Interview`); unrecognized values are preserved as custom statuses.
|
||||
- `GET /api/jobapplications/pipeline`
|
||||
- Returns the canonical ordered pipeline stages (`Applied, Waiting, Interview, Offer, Rejected, Ghosted`) with display order and category (`Active`/`Success`/`Closed`). The UI renders board columns and status dropdowns from this single source of truth.
|
||||
- `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.
|
||||
- `GET /api/jobapplications/{id}/match-score`
|
||||
- Deterministic CV↔job keyword-coverage score (0–100) with matched/missing keywords and per-CV-section coverage. Missing skills also seed a checklist-backed job-specific learning path, so users can mark them learned or dismiss them. No AI calls: results are instant and reproducible. Requires profile CV text/structure and a job description. (The AI narrative equivalent is `GET /api/jobapplications/{id}/candidate-fit`.)
|
||||
- `GET /api/jobapplications/{id}/status-suggestion`
|
||||
- Deterministic status suggestion derived from the job's most recent inbound message (interview invite / offer / rejection). Returns a forward-only suggestion (`hasSuggestion`, `suggestedStatus`, `signal`, …) or `hasSuggestion: false`. Applying it is a normal `PATCH .../status` — always user-confirmed.
|
||||
- `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`).
|
||||
|
||||
## Project status and planned work
|
||||
|
||||
This README documents released behaviour, not the roadmap. Validated findings and implementation status live in `docs/audits/audit-remediation-backlog.md` and `docs/work-programmes/master-progress.md`; archived phase documents are historical evidence and may describe superseded behaviour.
|
||||
Start at [docs/README.md](docs/README.md) for architecture, configuration, frontend/backend guides, AI, database, security, infrastructure, testing, and remaining work. Production/external blockers remain in [BLOCKERS.md](BLOCKERS.md).
|
||||
|
||||
Reference in New Issue
Block a user