First Commit

This commit is contained in:
cesnimda
2026-03-21 11:55:27 +01:00
commit 2e8a29b4d0
1757 changed files with 166084 additions and 0 deletions
+244
View File
@@ -0,0 +1,244 @@
# 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 summarizer 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 summarizer service: `tools/summarizer/` (FastAPI) used by the API via `Summarizer:BaseUrl`
## Quickstart (Docker)
This runs: frontend (nginx), backend API, and the summarizer service.
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 summarizer 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`).
### 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 summarizer
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
docker compose up --build summarizer
```
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)
- `Summarizer:BaseUrl`: summarizer 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: `Job Tracker`)
### 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.