Files
jobtrackingapp/docs/remaster/ARCHITECTURE_REVIEW.md
T
cesnimda f0f178d77e docs(remaster): full-system audit + rebuild-vs-refactor decision
Deep, code-grounded audit of Job Tracker producing the mission deliverables
under docs/remaster/: system audit, bug report, architecture/data-model/AI/UX
reviews, remaster proposal, migration plan, competitor research, and the gated
REBUILD_DECISION.

Verdict: Incremental Refactor (no full rebuild). Evidence: no Critical defects;
hardened cookie/CSRF auth (token never in JS storage), real SSRF defence,
enforced multi-tenancy via global query filters, decoupled provider-swappable
AI service, 135 backend tests. Debt is localised (god controllers/entity,
missing hot-path indexes, prompt-injection hardening, CRA build debt) and
reachable by in-place, test-guarded refactors.

Also harden .gitignore: exclude agent tooling (.claude/, .bg-shell/, .agent.md)
and restore/broaden the runtime-secrets block (**/keys/, **/backups/, exports,
CV artifacts) so nested DataProtection keys can't be committed accidentally.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 10:04:03 +02:00

5.3 KiB

Architecture Review — Job Tracker

Companion to: SYSTEM_AUDIT_REPORT.md

1. Current topology (as built, verified)

React 19 / TS / MUI 7 (CRA)  ──HTTP(cookie+CSRF)──▶  ASP.NET Core API (net9.0, EF Core 9)
job-tracker-ui/                                       JobTrackerApi/  (+ JobTrackerBackend link-compile)
                                                          │
                                    ┌─────────────────────┼───────────────────────┐
                                    ▼                     ▼                         ▼
                            EF Core / SQLite|MySQL   Hosted services         HttpClient ──▶ FastAPI AI svc
                            (global query filters)   (reminders, rules,                    tools/summarizer/
                                                      enrichment, export,                   Ollama | distilbart
                                                      backup)                               (provider-swappable)
                                                                            ──▶ Gmail API (OAuth), LibreTranslate

2. What is genuinely good (keep)

  • AI service decoupling [strength]. The .NET side (SummarizerService, CvAiClassifier, CvAiNormalizer) only speaks HTTP to the FastAPI service. Swapping Ollama→Gemini/Groq is a change in one Python file with zero .NET edits. This is textbook boundary placement.
  • Multi-tenancy via global query filters on OwnerUserId in Data/JobTrackerContext.cs. Centralised, hard to bypass accidentally, covered by JobApplicationsAuthorizationTests.
  • SSRF-safe ingestion (JobImport/JobImportService.cs:133-210): scheme allowlist, loopback/private/ CGNAT/link-local/IPv6-ULA blocklist after DNS resolution, redirect-averse fetch, 4 MB cap.
  • Deterministic domain servicesJobCvMatchService, JobPipeline, StageAnalytics, EmailStatusClassifier are small, pure, unit-testable. This is the model the controllers should follow.
  • Provider-agnostic persistence — SQLite default, Pomelo MySQL/MariaDB for prod.

3. Architectural weaknesses

3.1 God controllers [Architectural weakness] — highest impact

JobApplicationsController = 3,271 lines, ProfileCvController = 2,265, GmailController = 1,179. These are transaction scripts: they hold orchestration, validation, AI-context assembly, persistence, and DTO shaping inline. Consequences: untestable in isolation, merge-conflict magnets, duplicated RulesEngine.GetSettings calls, and read paths that load whole tables then filter in memory. Fix: extract cohesive services (JobStatsService, AnalyticsService, CvContextBuilder, GmailImportService, GmailThreadRefresher) + DTO files. The 135 integration tests make this safe.

3.2 Build-layout footgun [Architectural weakness]

Controllers/services compile through a separate JobTrackerBackend library that globs ../JobTrackerApi/Controllers/**/*.cs and ../Services/**/*.cs, not through JobTrackerApi.csproj. New files "just compile" from the right folder — invisible magic that will confuse every new contributor. Fix: document loudly (done in CLAUDE/README) or collapse the split; not urgent.

3.3 Polling background services, no event bus [Design flaw, low severity]

Reminders/rules/enrichment run on timers. Fine for a single node and a personal/low-tenant load; would need an outbox/queue if this becomes real multi-tenant SaaS. Not a problem today.

3.4 Frontend build platform [Architectural weakness]

CRA / react-scripts 5 is EOL-ish and carries transitive-vuln debt (.gsd D019 remediated only the direct axios finding and explicitly deferred the framework migration). And .gsd/OVERRIDES.md records an active directive "use next.js" (2026-04-10) that was never executed. So the shipped stack contradicts the last recorded frontend decision. Resolve intentionally (Vite for least churn, or Next.js per the override if SSR/SEO for a public product matters).

3.5 Scraper-plugin fragility [Architectural weakness]

HTML-structure-coupled plugins against adversarial targets (LinkedIn/Indeed) will rot. No plugin-health metric, so failures are silent (fall back to universal parser or manual). Fix: health telemetry + lean on the already-solid manual fallback; treat scraping as best-effort, not a guarantee.

4. Service-boundary map (target)

Concern Today Target owner
Job CRUD JobApplicationsController thin controller → JobApplicationService
Stats/analytics inline in controller (load-all) AnalyticsService (server-side aggregation)
CV context assembly inline in JobApplicationsController/ProfileCvController CvContextBuilder
Gmail import/refresh GmailController (N+1) GmailImportService + GmailThreadRefresher
Rule settings repeated RulesEngine.GetSettings cache in IMemoryCache (already registered)

5. Verdict

The skeleton is correct; the muscle is in the wrong place (controllers). This is the signature of a system that grew feature-first, not of one that is architecturally unsound. Refactor, do not rebuild.