feat: complete release readiness work
- consolidate API ownership and remove dead vendor code - add Stripe billing, learning paths, and public CV hardening - add migration, recovery, security, audit, and browser gates
This commit is contained in:
@@ -51,7 +51,7 @@ master profile text ────────────┘ │
|
||||
|
||||
## History model
|
||||
|
||||
`AiInteraction` (`Models/AiInteraction.cs`) is **append-only** — one row per generation, never
|
||||
`AiInteraction` (`JobTrackerApi/Models/AiInteraction.cs`) is **append-only** — one row per generation, never
|
||||
overwritten. This is deliberately distinct from `AiWorkspaceNote` (a one-row-per-type *cache* for
|
||||
candidate-fit/focus-plan). History gives the user restore/reuse (re-surface a past result), compare
|
||||
(view two side by side), copy, and delete. `ResultJson` is `{ text, meta? }`; `Provider` records which
|
||||
|
||||
@@ -93,6 +93,11 @@ Seeding is idempotent per `(JobApplicationId, SystemKey)` — enforced by a uniq
|
||||
never duplicates. Custom items have a `NULL` `SystemKey`; both SQLite and MariaDB treat NULLs as
|
||||
distinct in a unique index, so a user can add as many as they like.
|
||||
|
||||
The deterministic match-score endpoint also synchronises `learning:{hash}` system items from its
|
||||
current missing skills. They form the first job-specific learning path without another table or an
|
||||
external course catalogue. Manual learned/dismissed decisions persist; only recommendations that
|
||||
were auto-completed because a gap disappeared reopen when the same gap returns.
|
||||
|
||||
Deleting a **system** item dismisses it (a hard delete would be undone by the next seed); deleting a
|
||||
**custom** item removes the row. Dismissed items leave the progress denominator entirely.
|
||||
|
||||
@@ -378,6 +383,6 @@ belongs to the AI cache — a reminder that the two systems are genuinely differ
|
||||
6. ✅ Completion and product readiness — ownership audit, security review, full local verification
|
||||
(Phase 5.6). See `docs/phase-5-completion-report.md`.
|
||||
|
||||
**Phase 5 is feature-complete locally.** It is not deployed: CI is red for a documented environmental
|
||||
reason and deployment is gated on it. See the completion report's *Remaining risks*.
|
||||
**Phase 5 is feature-complete locally.** Current release and deployment dependencies are tracked in
|
||||
`BLOCKERS.md`; the completion report is a historical snapshot.
|
||||
7. Cover letter workflow. 8. Documents. 9. Interview preparation. 10. Dashboard improvements.
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
> ## Implementation status (2026-07-18) — SHIPPED
|
||||
>
|
||||
> The model below is implemented on `main` and verified against the live database:
|
||||
> - **Entities** (`Models/CareerEntities.cs`): `CareerExperience`, `CareerEducation`, `CareerSkill`,
|
||||
> - **Entities** (`JobTrackerApi/Models/CareerEntities.cs`): `CareerExperience`, `CareerEducation`, `CareerSkill`,
|
||||
> `CareerProject`, `CareerCertification`, `CareerLanguage` — relational children of `CareerProfile`;
|
||||
> long tail in `CareerProfile.LongTailJson`. Migration `AddCareerProfileRelationalChildren`
|
||||
> (applied cleanly on the real dev DB and the running container).
|
||||
@@ -40,7 +40,7 @@ The Career Workspace foundation (`992f89e`) stores the whole profile as **one JS
|
||||
`CareerProfileService.SaveVersionAsync` (dual-write). One row per user.
|
||||
- `CareerProfileVersion` — append-only history: one row per save, with a `Source` discriminator.
|
||||
|
||||
The blob shape (`Models/StructuredCvProfile.cs`) already has structured items for **Jobs, Education,
|
||||
The blob shape (`JobTrackerApi/Models/StructuredCvProfile.cs`) already has structured items for **Jobs, Education,
|
||||
Certifications, Projects, Languages**, plus `Skills`/`Summary`/`Interests` (string lists) and
|
||||
`OtherSections` (title + items). `CareerProfileService` already assigns **stable item IDs** to
|
||||
Jobs/Education/Certifications/Projects and normalizes free-text dates to `YYYY-MM`.
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
# Jobjakt — Current Architecture
|
||||
|
||||
> **This document describes the system as it actually is.** Every claim was verified against code.
|
||||
> Last verified: 2026-07-17 (Phase 0). Supersedes the archived `docs/_archive/SYSTEM_OVERVIEW.md` (2026-07-02).
|
||||
> Last verified: 2026-07-31. Supersedes the archived `docs/_archive/SYSTEM_OVERVIEW.md` (2026-07-02).
|
||||
>
|
||||
> **Rule:** if this document and the code disagree, the code wins — and this document is a bug. Fix it.
|
||||
> Do not trust other files under `docs/` over this one; most are stubs.
|
||||
> Topic documentation is maintained alongside this file; dated completion and review reports remain
|
||||
> historical snapshots. When documentation conflicts, prefer this file and the code.
|
||||
|
||||
---
|
||||
|
||||
@@ -69,22 +70,19 @@ flowchart LR
|
||||
BG --> DB
|
||||
```
|
||||
|
||||
### Solution layout (unusual — read this first)
|
||||
### Solution layout
|
||||
|
||||
| Project | Role |
|
||||
|---|---|
|
||||
| `JobTrackerApi/` | Web **host only**: `Program.cs`, appsettings, migrations, Dockerfile. Its csproj **excludes** `Controllers/**` and `Services/**` from its own compilation. |
|
||||
| `JobTrackerBackend/` | "Transitional shared-backend" **library** that link-compiles, via `<Compile Include>`, files physically located in `../Data`, `../Models`, `../JobTrackerApi/Controllers`, `../JobTrackerApi/Services`. Exists so tests can reference controllers/services without the web host. |
|
||||
| `JobTrackerApi/` | ASP.NET Core host plus its controllers, services, EF models, `JobTrackerContext`, migrations, and Dockerfile. |
|
||||
| `JobTrackerApi.Tests/` | xUnit, 36 test files incl. authorization + hostile-fixture suites. |
|
||||
| `Models/`, `Data/` (repo root) | The *real* EF models and `JobTrackerContext`. |
|
||||
| `job-tracker-ui/` | React SPA inside a Next.js shell. |
|
||||
| `tools/summarizer/` | FastAPI AI service (own Dockerfile, pytest tests). |
|
||||
| `tools/hostile-fixture-db/` | Security test fixture generator. |
|
||||
| `deploy/`, `.gitea/workflows/` | Prod deploy script + CI/CD. |
|
||||
|
||||
**Source lives in one place and compiles from another.** Any tool assuming csproj-adjacent source will mislead you. Check `JobTrackerBackend.csproj` before adding files or projects.
|
||||
|
||||
> Corrected 2026-07-17: the dead root `Controller/` (singular) folder described in the archived overview **no longer exists** — removed in `519c32e`.
|
||||
> Corrected 2026-07-31: the transitional `JobTrackerBackend` link-compilation project and root
|
||||
> `Models/`/`Data/` directories were retired. Source now compiles from the project that owns it.
|
||||
|
||||
---
|
||||
|
||||
@@ -94,7 +92,7 @@ flowchart LR
|
||||
|
||||
**Frontend:** **Next.js 16** + React 19 + **TypeScript 5.9** + MUI 7 (+ x-data-grid, x-date-pickers, lab), axios, react-router-dom 6, @tanstack/react-table, i18n EN + NB (custom provider), Jest/RTL.
|
||||
|
||||
> Corrected 2026-07-17: the archived overview said "CRA/react-scripts 5, TypeScript 4.9". The CRA→Next.js migration has happened. See §4 for what that migration did and did not do.
|
||||
> Corrected 2026-07-31: the CRA migration is complete; direct Jest/Babel configuration replaced `react-scripts`.
|
||||
|
||||
**AI:** FastAPI + transformers (`sshleifer/distilbart-cnn-12-6`) for summaries; pytesseract/PyMuPDF/pypdf/python-docx for extraction/OCR; **one** generation provider selected by the `AI_PROVIDER` env var ∈ {`ollama` (default, `qwen2.5:7b`), `gemini`, `groq`}; TTL cache.
|
||||
|
||||
@@ -104,17 +102,16 @@ flowchart LR
|
||||
|
||||
## 4. Frontend architecture
|
||||
|
||||
**Three toolchains coexist. This is the single most confusing thing about the frontend.**
|
||||
**Two routing layers coexist intentionally:**
|
||||
|
||||
1. **Next.js 16 App Router** (`app/layout.tsx`, `app/page.tsx`) — a thin shell that mounts a client-side app. The CRA→Next migration was a **CSR lift-and-shift**: no SSR, no server components, no Next routing, no data fetching. Next is effectively a build tool here. Static export → nginx.
|
||||
2. **react-router-dom v6** — does the actual routing, in **two different patterns inside one file** (`src/App.tsx`): `createBrowserRouter` for public routes (`/`, `/login`, `/forgot-password`, `/reset-password`, `/verify-email`) and a nested `<Routes>` inside a catch-all `Shell` for authenticated routes.
|
||||
3. **react-scripts 5.0.1** — still a dependency, used **only** as the test runner (`"test": "react-scripts test"`).
|
||||
|
||||
Known consequence: a **dev-only 404 on deep links** follows directly from the Next shell + client router combination.
|
||||
Development leaves static-export mode disabled so deep links reach the client router; production exports one shell and nginx falls back to `index.html` for unknown paths.
|
||||
|
||||
**Routes** (`src/App.tsx`): public — `/`, `/login`, `/forgot-password`, `/reset-password`, `/verify-email`. Authenticated — `/dashboard`, `/jobs`, `/reminders`, `/kanban`, `/companies`, `/correspondence`, `/correspondence/review`, `/profile`, `/career`, `/trash`, `/settings`, `/settings/connected-accounts`, `/admin/{audit,users,system}`.
|
||||
|
||||
> **No `/register` route exists.** Sign-up is folded into `LoginPage.tsx`, and the endpoint is disabled by default (§5).
|
||||
> `/register` reuses the hardened auth form. Submission remains disabled until registration is enabled by production configuration (§5).
|
||||
|
||||
**State management: none.** No Redux/Zustand/React Query. Local `useState` + `axios` per component, with a hand-rolled `refreshToken` counter threaded through props. Two workspace-cache hooks exist (`components/job-workspace/useWorkspaceTabCache.ts`, `useJobWorkspaceBaseData.ts`). This is the root cause of the oversized components below.
|
||||
|
||||
@@ -138,7 +135,7 @@ the reference model for how account identity and the master career profile relat
|
||||
| `/career` | `views/CareerWorkspacePage.tsx` → `views/CareerProfilePage.tsx` (~1293 lines) | The **master career profile** — the single editable source of truth |
|
||||
|
||||
`CareerWorkspacePage` is a thin shell (heading + source-of-truth notice) around `CareerProfilePage`.
|
||||
The CV Builder is **not** built yet (Phase 4); `CareerProfilePage` is where it will live.
|
||||
The CV Builder is a separate routed workspace and consumes the Career Profile as its source of truth.
|
||||
|
||||
### Request flow
|
||||
|
||||
@@ -209,7 +206,7 @@ null-leaves).
|
||||
- **Sessions:** `UserSession` entity + `SessionsController` — list/revoke active sessions.
|
||||
- Password policy: min 8, digit + lowercase. Reset via emailed token (SMTP required).
|
||||
- **Registration is disabled by default** — `AuthController.cs:135` reads `Auth:AllowRegistration` defaulting to `false` and returns HTTP 403. There is no CAPTCHA anywhere.
|
||||
- **Rate limiting (3 fixed-window policies):** `auth-login` 10/window, `auth-email` 5/window, `auth-2fa-challenge` 5/window. **AI and other expensive endpoints are unthrottled.**
|
||||
- **Rate limiting:** login, auth email, 2FA challenge, and anonymous public-CV PDF export have dedicated fixed-window policies. AI usage is bounded by per-account monthly generation and token ceilings rather than request-window throttling.
|
||||
|
||||
---
|
||||
|
||||
@@ -246,12 +243,12 @@ erDiagram
|
||||
- Salary is **structured**: `SalaryMin`, `SalaryMax`, `SalaryCurrency`, `SalaryPeriod` (plus a legacy free-text `Salary`).
|
||||
- `Tags` is a **JSON array in a string column** — not queryable; `/tags` and `/tag-trends` must scan.
|
||||
- Denormalized `HasResume`/`HasCoverLetter`/`HasPortfolio`/`HasOtherAttachment` flags duplicate `Attachments`; kept honest by `AttachmentFlagsRecomputeTests`.
|
||||
- CV text is stored **three times** (`CvExtractionRun.RawExtractedText`, `.NormalizedText`, `.StructuredProfileJson`) plus twice on the user. Deliberate audit trail, but **no retention policy**.
|
||||
- CV extraction runs retain the newest 20 completed runs per user; expired runs and unreferenced upload artifacts are pruned while the current artifact is preserved.
|
||||
- `Status` is free-text at the DB level; canonicalized only in the application layer by `JobPipeline.Normalize` — deliberately, so custom user values are never destroyed.
|
||||
- Indexes: `OwnerUserId` on Company/Job/JobApplication/GmailConnection; composites `(OwnerUserId, UploadedAtUtc)`, `(OwnerUserId, StartedAtUtc)`; unique `(OwnerUserId, JobApplicationId)` on draft, unique `(OwnerUserId, GmailAddress)`. EF auto-indexes FKs by convention. **Genuinely missing:** owner-prefixed composites `(OwnerUserId, IsDeleted, Status)` and `(OwnerUserId, FollowUpAt)`.
|
||||
- SQLite at `DataRoot/jobtracker.db` (WAL); migrations applied at startup by `StartupInitializationExtensions` (1356 lines — also seeds admin, creates Identity tables where `dotnet ef` is unavailable, ignores `PendingModelChangesWarning`).
|
||||
- Indexes include owner-prefixed list/board/reminder composites `(OwnerUserId, IsDeleted)`, `(OwnerUserId, IsDeleted, Status)`, and `(OwnerUserId, FollowUpAt)`, plus CV and provider-specific indexes.
|
||||
- SQLite lives at `DataRoot/jobtracker.db` (WAL); migrations and legacy-schema reconciliation run at startup through `StartupInitializationExtensions`.
|
||||
|
||||
> Corrected 2026-07-17: prior session notes recorded the EF model snapshot as **broken/empty**. It was **resynced** in `20260711181039_SyncModelSnapshot` — the snapshot now covers the full model and incremental `dotnet ef migrations add` works normally. Note `dotnet ef` still needs the `Design` package temporarily added to `JobTrackerApi` (the `MigrationsAssembly`), since it lives in `JobTrackerBackend` with `PrivateAssets=all`.
|
||||
> Corrected 2026-07-31: the model snapshot is current and the API directly carries the EF Design package and `JobTrackerContext`; no temporary project edit is required for `dotnet ef`.
|
||||
|
||||
---
|
||||
|
||||
@@ -294,10 +291,10 @@ erDiagram
|
||||
| `DailyExportHostedService` | Daily JSON export at a configured local hour |
|
||||
| `JobEnrichmentHostedService` | Backfills summaries/enrichment |
|
||||
| `SummarizerProbeHostedService` | Probes AI service readiness |
|
||||
| `CvProcessingHostedService` + `CvProcessingQueue` | In-memory queue for CV extraction |
|
||||
| `CvProcessingHostedService` + `CvProcessingQueue` | Process-local wake-up queue for CV extraction; queued/running database work is recovered at startup |
|
||||
| `DatabaseBackupHostedService` → `DatabaseBackupRunner` | Automated DB backup (`VACUUM INTO`, server-derived path) |
|
||||
|
||||
**All state is in-process** (`IMemoryCache`, in-memory queue) — single-instance assumption, no distributed locks, **queued CV jobs are lost on restart**.
|
||||
Caches and worker coordination are process-local. CV work itself is durable and recovered after restart, but the worker remains a deliberate single-instance design without database leasing.
|
||||
|
||||
---
|
||||
|
||||
@@ -361,15 +358,15 @@ Inbound: `GmailOAuthService` (655), `MicrosoftGraphOAuthService` (507), `ImapSer
|
||||
- **Backend:** xUnit integration-style via `TestHostFactory`. 36 test files. Notable: `JobApplicationsAuthorizationTests`, `OwnershipGuardTests`, `ImapServiceSsrfGuardTests`, `ProductionConfigTests`, `AttachmentFlagsRecomputeTests`, `CvCorpusHarnessTests`, `SqliteMigrationHelperTests`, `JobPipelineTests`, plus a `tools/hostile-fixture-db` project.
|
||||
- **Frontend:** ~20 Jest/RTL files — **all run in CI**.
|
||||
- **AI service:** pytest (`tools/summarizer/tests/`).
|
||||
- **Gaps:** no true end-to-end browser tests; no load/perf tests; **no dependency CVE scanning** (CI explicitly sets `npm_config_audit: 'false'`).
|
||||
- **Browser smoke:** Playwright drives login/session cookies, saved-job creation, Career Workspace routing, and anonymous public-CV rendering/PDF download against isolated API/SQLite and Next.js processes. CI installs Chromium and runs all four flows.
|
||||
- **Gap:** no load/performance suite. CI reports NuGet transitive vulnerabilities and production npm audit findings.
|
||||
|
||||
---
|
||||
|
||||
## 14. Logging & error handling
|
||||
|
||||
Console/debug logging; middleware logs every request (method, path, status, ms, traceId, sub claim). Unhandled exceptions logged then rethrown (500). Client errors POST to `/api/client-errors`. React `ErrorBoundary` + route error page.
|
||||
|
||||
No structured sink (Seq/OTLP), no in-app log rotation, no ProblemDetails standardization.
|
||||
Development uses simple console/debug logging; production emits structured JSON. Middleware records method, path, status, duration, trace ID, and subject. Unhandled errors return Problem Details with the same trace ID. Client errors POST to `/api/client-errors`; the frontend has an `ErrorBoundary` and route error page.
|
||||
Compose bounds each container's local logs to three 10 MB files. There is still no external sink (Seq/OTLP) or cross-host aggregation.
|
||||
|
||||
---
|
||||
|
||||
@@ -385,17 +382,17 @@ No structured sink (Seq/OTLP), no in-app log rotation, no ProblemDetails standar
|
||||
- 2FA + recovery codes + trusted devices + session revocation.
|
||||
- **AI sidecar: backend-only.** Unpublished, on a private two-member network, and token-authenticated (§16). Verified against the running stack, not just configured.
|
||||
|
||||
**Open findings** (detail in `docs/application-discovery-report.md` §12 and `docs/phase-0-foundation-report.md`):
|
||||
**Findings status** (detail in `docs/application-discovery-report.md` §12 and `docs/phase-0-foundation-report.md`):
|
||||
|
||||
| Sev | Finding | Status |
|
||||
|---|---|---|
|
||||
| Medium | DataProtection keys recoverable from git history (`519c32e`, `955cae6`) | **Open — rotation required, needs an operator** |
|
||||
| Medium | **CORS: `Cors:Origins="*"` triggers `SetIsOriginAllowed(_ => true)` + `AllowCredentials()`** (`Program.cs:96-102`) — reflected-origin with cookies = session theft from any site. Not currently active (compose never sets `Cors__Origins`, so it defaults to `localhost:3000`), but it is one config value away. | **Open — landmine** |
|
||||
| Medium | AI cost ceiling | Metering shipped in Phase 5; enforce quotas before open registration (Phase 7). |
|
||||
| Low | No CAPTCHA (rate limiting only) | Open — blocks public signup |
|
||||
| Low | Unbounded storage: attachments, CV artifacts, extraction runs, base64 avatars | Open |
|
||||
| Medium | Wildcard credentialed CORS configuration | **Closed — startup rejects it** |
|
||||
| Medium | AI cost ceiling | **Closed — monthly generation/token limits are enforced by plan** |
|
||||
| Low | Public-registration abuse control | Implemented with Turnstile; production keys/configuration still required |
|
||||
| Low | Unbounded storage | **Closed — attachment quotas, extraction/artifact pruning, file-backed avatars, and PDF-export retention are implemented** |
|
||||
| Low | Backup / DPAPI is Windows-oriented — verify behaviour on Linux prod | Unverified |
|
||||
| Low | No dependency CVE scanning in CI | Open |
|
||||
| Low | No dependency CVE scanning in CI | **Closed — NuGet and production npm audit reporting are in CI** |
|
||||
|
||||
---
|
||||
|
||||
@@ -417,20 +414,9 @@ Full record: `docs/phase-0-foundation-report.md`. What changed architecturally:
|
||||
|
||||
---
|
||||
|
||||
## 17. Known debt (ranked)
|
||||
## 17. Known debt
|
||||
|
||||
1. **`JobApplication` still carries opportunity columns** — the `Job` split is started but not completed. Reads/writes still use the legacy columns.
|
||||
2. **God controllers** — `JobApplicationsController` 2313/38 endpoints, `ProfileCvController` 2249, `StartupInitializationExtensions` 1356, `GmailController` 1023, `AuthController` 879.
|
||||
3. **Hardcoded CV templates** — dead end for the CV Builder.
|
||||
4. **Three frontend toolchains** — Next shell + react-router (×2 patterns) + react-scripts test runner.
|
||||
5. **No frontend data layer** — root cause of the 600–1400-line components.
|
||||
6. **`ProfilePage` (1368 lines) serves two routes** behind a boolean; the `careerView` prop it accepts is **never read** (the "CV Builder" tab is inert).
|
||||
7. **Denormalized `Has*` flags**; **`Tags` as a JSON string column**; **unbounded CV storage**; **base64 avatars in a DB column**.
|
||||
8. **`JobTrackerBackend` link-compilation** — self-described "transitional".
|
||||
9. **In-memory queue/cache** — single-instance coupling; restart loses queued CV jobs.
|
||||
10. **No OpenAPI in prod, no ProblemDetails, no structured logging sink.**
|
||||
11. **Root-level clutter**: `temp_job.json`, `temp_post_job.py`, `todo jobtracker.txt`, `test/`, `tmp/`, `vendor/`, `docs.7z`, `CV_Changes.md`, `SMART_GMAIL_PROGRESS.md`.
|
||||
12. **Norway-only import plugins** (Finn/NAV/Jobbnorge). Product decision 2026-07-17: **Norway first, but no hardcoding Norway** — market must become a data dimension, not an assumption.
|
||||
The live, prioritized ledger is `docs/architecture/technical-debt.md`. The principal remaining items are the legacy `JobApplication` opportunity columns, single-replica worker coordination, and optional cross-host log aggregation. Large orchestration files are refactored only when a cohesive behavior change provides a safe seam.
|
||||
|
||||
---
|
||||
|
||||
@@ -438,9 +424,8 @@ Full record: `docs/phase-0-foundation-report.md`. What changed architecturally:
|
||||
|
||||
Recorded nowhere else in active docs:
|
||||
|
||||
1. `JobTrackerBackend` link-compilation exists so tests can reach controllers without the web host — deliberate, self-described "transitional".
|
||||
2. `Status` is free-text and canonicalized in the application layer **specifically so custom user values are never destroyed** (`JobPipeline.cs` docstring). Deliberate; do not "fix" it with a DB enum.
|
||||
3. The CI frontend-test whitelist was removed after it "silently skipped new suites and let two regressions reach main."
|
||||
4. Ollama is intentionally not bundled by default so deploys reuse a shared instance.
|
||||
5. `AI_PROVIDER=gemini` exists to offload a weak local GPU in prod.
|
||||
6. `TailoredCvDraft` is a separate entity specifically to guarantee the master CV is never auto-modified.
|
||||
1. `Status` is free-text and canonicalized in the application layer **specifically so custom user values are never destroyed** (`JobPipeline.cs` docstring). Deliberate; do not "fix" it with a DB enum.
|
||||
2. The CI frontend-test whitelist was removed after it "silently skipped new suites and let two regressions reach main."
|
||||
3. Ollama is intentionally not bundled by default so deploys reuse a shared instance.
|
||||
4. `AI_PROVIDER=gemini` exists to offload a weak local GPU in prod.
|
||||
5. `TailoredCvDraft` is a separate entity specifically to guarantee the master CV is never auto-modified.
|
||||
|
||||
@@ -25,8 +25,8 @@ The master profile is never written by the builder. A variant references career
|
||||
|
||||
## Variant model
|
||||
|
||||
`CvVariant` (`Models/CvVariant.cs`) + `CvVariantVersion` (autosave history). The whole lens lives in one
|
||||
`SettingsJson` blob (`CvVariantSettings`, `Models/CvVariantSettings.cs`) because it is edited and saved
|
||||
`CvVariant` (`JobTrackerApi/Models/CvVariant.cs`) + `CvVariantVersion` (autosave history). The whole lens lives in one
|
||||
`SettingsJson` blob (`CvVariantSettings`, `JobTrackerApi/Models/CvVariantSettings.cs`) because it is edited and saved
|
||||
as a unit — never queried field-by-field. A variant stores:
|
||||
|
||||
- `ThemeId` + overrides: accent, heading/body font, density, page size, photo/icons/page-numbers.
|
||||
@@ -56,6 +56,11 @@ Anonymous (`/api/public-cv/{slug}`, `PublicCvController`): serves a public varia
|
||||
tenant filter — there is no current user). Unknown/private slug → 404
|
||||
(`CvBuilderTests.Private_variant_is_not_served_publicly`).
|
||||
|
||||
Recruiters can download the same public render as PDF through `GET /api/public-cv/{slug}/pdf`.
|
||||
The endpoint applies the identical public/private slug check; unknown, revoked, or private links return
|
||||
404 without invoking the exporter. Anonymous PDF generation is limited to three requests per minute
|
||||
per public link because it launches Chromium. `PublicCvPage` exposes it as a native **Download PDF** link.
|
||||
|
||||
## Builder workflow (frontend)
|
||||
|
||||
`/career/builder` lists variants (`CvBuilderPage`); the editor (`CvBuilderEditor`) is three tabs —
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
**A theme is data, not code.** There is exactly one renderer — `ThemedCvRenderer`
|
||||
(`JobTrackerApi/Services/ThemedCvRenderer.cs`) — with a single render path. Every visual difference
|
||||
between themes is expressed by the fields of a `CvTheme` record (`Models/CvTheme.cs`). Adding a theme
|
||||
between themes is expressed by the fields of a `CvTheme` record (`JobTrackerApi/Models/CvTheme.cs`). Adding a theme
|
||||
never touches the renderer.
|
||||
|
||||
This replaces the previous approach (`CvTemplateRenderer`, one hand-written HTML method per template),
|
||||
@@ -30,7 +30,7 @@ section keys move to the sidebar for the two-column layouts.
|
||||
|
||||
## Adding a theme
|
||||
|
||||
1. Append one `CvTheme { … }` to `CvThemeCatalog.Themes` (`Models/CvTheme.cs`). Only override the
|
||||
1. Append one `CvTheme { … }` to `CvThemeCatalog.Themes` (`JobTrackerApi/Models/CvTheme.cs`). Only override the
|
||||
fields that differ from the defaults.
|
||||
2. Nothing else. It appears in `GET /api/cv/themes`, the Customize tab picker, and renders.
|
||||
|
||||
|
||||
@@ -1,3 +1,16 @@
|
||||
# deployment
|
||||
|
||||
TODO: Complete documentation.
|
||||
Production runs the frontend/nginx, ASP.NET API, AI sidecar, and configured database through Docker
|
||||
Compose. The backend is not published directly; nginx proxies `/api`. `deploy/deploy.sh` validates
|
||||
configuration, takes and verifies a provider-appropriate backup before replacement, builds/restarts the
|
||||
stack, and performs health checks.
|
||||
|
||||
Production compose enables `Proxy:TrustForwardedHeaders` because nginx is the sole ingress, allowing
|
||||
HTTPS scheme and client-IP rate limits to use one trusted forwarded hop. The development override
|
||||
publishes the API directly and disables forwarded-header trust.
|
||||
|
||||
Each service rotates local Docker logs at 10 MB and retains three files. Add a central sink only if
|
||||
cross-host search or longer retention becomes necessary.
|
||||
|
||||
Use `deploy/README.md`, `deploy/first-production-deployment.md`, and
|
||||
`docs/deployment/backup-restore.md` as the operational runbooks.
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
# future
|
||||
|
||||
TODO: Complete documentation.
|
||||
Architecture evolves incrementally rather than through a rewrite. Current priorities are reducing large
|
||||
controller/component orchestration boundaries, replacing the transitional link-compiled backend project,
|
||||
adding durable background work if multi-instance deployment becomes necessary, and introducing browser
|
||||
end-to-end coverage for critical user journeys.
|
||||
|
||||
Job-specific learning recommendations reuse deterministic match gaps and checklist state. Portfolio
|
||||
content stays in public CVs. Broader course integrations or separate portfolio hosting remain out of
|
||||
scope until concrete requirements justify new storage or service boundaries. Active items are tracked
|
||||
in `docs/architecture/technical-debt.md` and `BLOCKERS.md`.
|
||||
|
||||
@@ -1,56 +1,56 @@
|
||||
# Technical Debt Management
|
||||
# Technical debt
|
||||
|
||||
## Purpose
|
||||
Last reconciled: 2026-07-31
|
||||
|
||||
Track known issues.
|
||||
This ledger contains verified engineering debt only. Product ideas belong in the roadmaps and
|
||||
operator/external dependencies belong in `BLOCKERS.md`.
|
||||
|
||||
---
|
||||
## Resolved in the 2026-07-31 debt pass
|
||||
|
||||
# Categories
|
||||
- Removed the 232 MB unused `vendor/saasable-ui-main` snapshot.
|
||||
- Retired the link-compiling `JobTrackerBackend` project. The API now owns its controllers,
|
||||
services, models, and `JobTrackerContext`; tests and tools reference the API directly.
|
||||
- Added standard Problem Details responses with trace IDs and structured JSON production logs.
|
||||
- Made trusted reverse-proxy headers explicit so rate limits see the real client address in the
|
||||
production nginx topology.
|
||||
- Made queued CV processing recover queued/running database work after restart and bypass the
|
||||
request-only tenant filter with explicit owner checks.
|
||||
- Pruned expired CV extraction runs and orphaned upload artifacts while preserving the current CV.
|
||||
- Added configurable PDF export retention (`CvExports:RetainDays`, default 30 days).
|
||||
- Corrected the remaining CRA-era environment-variable names after the Next.js migration.
|
||||
- Removed React Router and `act(...)` warnings from the frontend tests.
|
||||
- Replaced placeholder documentation stubs with concise, code-linked guidance.
|
||||
- Removed stale locals and reconciled old TODO claims against the implemented code.
|
||||
- Added a CI-gated Playwright smoke suite for login/session cookies, saved-job creation, Career
|
||||
Workspace routing, and anonymous public-CV rendering/PDF download.
|
||||
- Updated Axios and Next.js and overrode Next's vulnerable bundled Sharp/PostCSS versions; the only
|
||||
remaining npm findings are the non-applicable/mitigated React Router items in `BLOCKERS.md`.
|
||||
- Pinned SQLitePCLRaw 2.1.12 so the native SQLite runtime is no longer in the high-severity
|
||||
CVE-2025-6965 range; the NuGet transitive vulnerability audit is clean.
|
||||
- Made fresh SQLite startup reconcile schema-owned columns between historical EF migrations, so
|
||||
strict SQLite identifier handling no longer breaks the later table rebuild.
|
||||
- Updated Jest and Playwright, pinned the patched transitive brace-expansion package, and promoted
|
||||
the full npm high-severity audit to a blocking CI gate.
|
||||
- Corrected Chromium PDF export argument handling, bounded hung exports, and verified the returned
|
||||
public artifact is a real PDF in the browser smoke suite.
|
||||
- Removed unfinished Portfolio/Notes workspace navigation promises; existing project, attachment,
|
||||
and application-note surfaces remain authoritative, and stale section links fall back to Overview.
|
||||
|
||||
## Architecture
|
||||
## Remaining engineering debt
|
||||
|
||||
Examples:
|
||||
| Priority | Debt | Current decision / trigger |
|
||||
|---|---|---|
|
||||
| P1 | `JobApplication` still duplicates opportunity data now owned by `Job`. | Keep the compatibility dual-write until a production-data backfill and restore rehearsal prove every application has a valid `JobId`; then remove the legacy columns in one migration. |
|
||||
| P1 | Background workers assume one API instance. Restart recovery is durable, but there is no row lease for concurrent workers. | Add database leasing only before deploying more than one backend replica. |
|
||||
| P2 | Production log aggregation is still deployment-owned; Compose now bounds each container's local logs to 3 × 10 MB. | Add an OTLP/Seq sink only before multi-host operation or when incident-response needs exceed `docker logs`. |
|
||||
| P3 | `Tags` remains a JSON string and attachment-purpose booleans remain compatibility columns. | Normalize tags only when server-side tag querying becomes slow. Remove attachment flags only with an API/schema compatibility release; recomputation tests currently prevent drift. |
|
||||
| P3 | Checklist reads seed and synchronize system-generated items, so the GET is intentionally non-cacheable. | Split read/write paths only if workspace read volume or caching makes the current idempotent behavior measurably costly. |
|
||||
| P3 | Several orchestration files are large (`JobApplicationsController`, `StartupInitializationExtensions`, `JobDetailsDialog`). | File length alone is not a defect. Extract a cohesive slice only when the next behavior change touches it; avoid a standalone rewrite. |
|
||||
|
||||
- Large services.
|
||||
- Tight coupling.
|
||||
## Deliberate non-debt
|
||||
|
||||
---
|
||||
|
||||
## Frontend
|
||||
|
||||
Examples:
|
||||
|
||||
- Duplicate components.
|
||||
- Complex state.
|
||||
|
||||
---
|
||||
|
||||
## Backend
|
||||
|
||||
Examples:
|
||||
|
||||
- Large controllers.
|
||||
- Missing abstractions.
|
||||
|
||||
---
|
||||
|
||||
## Testing
|
||||
|
||||
Examples:
|
||||
|
||||
- Missing coverage.
|
||||
|
||||
---
|
||||
|
||||
# Rules
|
||||
|
||||
Do not fix everything immediately.
|
||||
|
||||
Prioritise:
|
||||
|
||||
Impact.
|
||||
|
||||
Risk.
|
||||
|
||||
User value.
|
||||
- Free-text pipeline status is intentional so custom values are not destroyed.
|
||||
- Next.js is the static build shell and React Router owns client navigation; changing routers has no
|
||||
demonstrated user benefit.
|
||||
- The CV queue is intentionally process-local for the current single-replica deployment.
|
||||
- User-local untracked files at the repository root were not removed or modified.
|
||||
|
||||
Reference in New Issue
Block a user