f0f178d77e
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>
69 lines
4.3 KiB
Markdown
69 lines
4.3 KiB
Markdown
# Data Model Review — Job Tracker
|
|
|
|
**Companion to:** [SYSTEM_AUDIT_REPORT.md](SYSTEM_AUDIT_REPORT.md)
|
|
|
|
## 1. Entities (verified in `Models/`)
|
|
|
|
`JobApplication`, `Company`, `Correspondence`, `Attachment`, `JobEvent`, `TailoredCvDraft`(+`Json`),
|
|
`StructuredCvProfile`(+`Json`), `CvExtraction`, `GmailConnection`, `GmailReviewDecision`, `RuleSettings`,
|
|
`UserRuleSettings`, `HumanLanguageCatalog`, `SystemEmailSettings`, `ApplicationUser`.
|
|
|
|
## 2. `JobApplication` — the god entity `[Design flaw]`
|
|
|
|
~40 columns spanning **eight** distinct concerns on one row:
|
|
|
|
1. Identity/ownership (`Id`, `OwnerUserId`)
|
|
2. Core role (`JobTitle`, `CompanyId`, `Status`, `DateApplied`, `Location`)
|
|
3. Salary — **both** free-text (`Salary`) *and* structured (`SalaryMin/Max/Currency/Period`)
|
|
4. Workflow (`NextAction`, `FollowUpAt`, `FeedbackRequestedAt`, `RecruiterMessageDraft`)
|
|
5. **Denormalised attachment flags** (`HasResume`, `HasCoverLetter`, `HasPortfolio`, `HasOtherAttachment`)
|
|
6. Soft delete (`IsDeleted`, `DeletedAt`)
|
|
7. Imported content (`Description`, `TranslatedDescription`, `DescriptionLanguage`, `Tags`, `Deadline`, `ShortSummary`)
|
|
8. Tailored CV — **both** inline (`TailoredCvText`, `TailoredCvUpdatedAt`) *and* related (`TailoredCvDraft`)
|
|
|
|
### 2.1 Denormalisation hazard `[Bug risk — High]`
|
|
`HasResume/HasCoverLetter/HasPortfolio/HasOtherAttachment` duplicate information already derivable from the
|
|
`Attachments` collection. Any code path that adds/removes an attachment without updating the boolean (or
|
|
vice-versa) produces a **silent inconsistency** that the attachment-checklist UI will display wrong. These
|
|
booleans should be **computed projections**, not stored state. If kept for query performance, they must be
|
|
maintained in one place (a domain method) — verify no controller mutates them independently.
|
|
|
|
### 2.2 Dual tailored-CV source of truth `[Design flaw — High]`
|
|
`TailoredCvText` (string on `JobApplication`) vs `TailoredCvDraft`/`TailoredCvDraftJson` (related entities).
|
|
Two writable representations of "the tailored CV for this job" with no documented precedence. This is a
|
|
classic bug incubator: read one, write the other, and the workspace shows stale content.
|
|
|
|
### 2.3 CV "versioning" is not modelled `[Design flaw — Medium]`
|
|
Product step 8 promises *"CV version is linked to job."* The schema stores a **single current** tailored
|
|
text per job, not a **version history**. There is no `CvVersion` table with immutable snapshots. The
|
|
promised capability is only partially real. If versioning matters (it should, for A/B and audit), model it
|
|
explicitly: `CvVersion(id, ownerUserId, sourceProfileId, jobApplicationId?, content, createdAt, label)`.
|
|
|
|
## 3. Relationships
|
|
|
|
- `JobApplication *→1 Company` (FK `CompanyId`) — fine.
|
|
- `JobApplication 1→* Correspondence / Attachment / JobEvent` — fine, but **FK columns are unindexed**
|
|
(`Correspondence.JobApplicationId`, `JobEvent.JobApplicationId`) → N+1 and slow joins.
|
|
- `Correspondence.ExternalThreadId` powers Gmail continuity (D007/D008) — good, but unindexed.
|
|
|
|
## 4. Indexing `[Performance — High]`
|
|
Only `OwnerUserId` is indexed. Every list/board/reminders/analytics query filters on `IsDeleted`
|
|
(unindexed), reminders/background jobs filter on `FollowUpAt` (unindexed), and detail loads join on the
|
|
unindexed child FKs. **Add:** `IsDeleted`, `(IsDeleted, Status)`, `FollowUpAt`,
|
|
`Correspondence.JobApplicationId`, `JobEvent.JobApplicationId`. SQLite- and MySQL-safe.
|
|
|
|
## 5. Tags/JSON-as-string `[Design flaw — Low]`
|
|
`Tags` is a JSON-array string; `TailoredCvDraftJson`/`StructuredCvProfileJson` are JSON blobs. Workable
|
|
with EF value converters, but unqueryable. Acceptable given SQLite; revisit if moving fully to MySQL/Postgres
|
|
(use native JSON columns).
|
|
|
|
## 6. Recommended target schema (incremental)
|
|
1. Split `JobApplication` into `JobApplication` (core+workflow) + `JobImportContent` (description/translation/
|
|
summary/tags) — a 1:1 owned entity — so wide read paths don't drag import blobs.
|
|
2. Make attachment booleans computed (drop stored columns after a migration + backfill check).
|
|
3. Pick **one** tailored-CV representation (`TailoredCvDraft`) and deprecate `TailoredCvText`.
|
|
4. Introduce `CvVersion` for real versioning.
|
|
5. Add the five hot-path indexes (do this first — highest value, lowest risk).
|
|
|
|
All five are additive/behaviour-preserving migrations guarded by the existing test suite.
|