Commit Graph

59 Commits

Author SHA1 Message Date
cesnimda 3081d99355 feat(auth): Microsoft OAuth sign-in/link + self-serve signup via Google/Microsoft
CI and Deploy / test (pull_request) Successful in 2m9s
CI and Deploy / deploy (pull_request) Has been skipped
Wave 7. Mirrors the existing Google ID-token-exchange pattern (Program.cs
smart-scheme dispatch, JWT bearer scheme, AuthController exchange/link/
unlink endpoints, ApplicationUser fields, reconciler columns) for
Microsoft Entra ID + personal accounts via the multi-tenant "common"
endpoint.

Google/Microsoft sign-in previously only worked for accounts already
linked to an existing local user -- there was no way to actually sign
up via OAuth. Both exchange endpoints now create a new user when no
match is found and Auth:AllowRegistration is true, same gate as
email/password registration.

Frontend: new MicrosoftAuthCard (MSAL popup flow -- Microsoft has no
vanilla-JS equivalent to Google's Identity Services script) wired into
the login page's provider tabs and the profile page's account-linking
section. REACT_APP_MICROSOFT_CLIENT_ID env var, Auth:MicrosoftClientId
config gate on the backend.
2026-07-12 00:12:23 +02:00
cesnimda b4fd5e2f96 fix(jobs): derive attachment checklist flags from actual Attachments
CI and Deploy / test (pull_request) Successful in 2m4s
CI and Deploy / deploy (pull_request) Has been skipped
Backlog item 4 (Wave 3, first sub-item). HasResume/HasCoverLetter/HasPortfolio/
HasOtherAttachment were manually-editable checkboxes in EditJobDialog,
completely independent of whether a file was actually attached -- classic
drift: mark 'resume ready' by hand, later delete the resume attachment, flag
stays stuck true forever. User confirmed (asked directly, since removing the
manual-override capability is a product decision, not purely technical):
make them fully computed from Attachments, no manual override.

- AttachmentsController.RecomputeAttachmentFlagsAsync: the single place these
  four fields get written now, called after every attachment mutation
  (upload, delete, Purpose change) that could affect them. Deliberately kept
  as persisted columns (not [NotMapped] computed properties reading the
  Attachments navigation collection) -- ~15 query sites build JobApplication
  DTOs without .Include(Attachments), so a live-computed property would
  silently return false everywhere instead of throwing, the worst kind of
  bug. Recomputing at the one write funnel avoids touching any read path.
- Removed HasResume/etc from CreateJobApplicationRequest/
  UpdateJobApplicationRequest -- no longer client-settable.
- EditJobDialog: removed the manual checkboxes, kept the (now genuinely
  accurate) read-only status chips.
- AddJobModal: stopped sending has*-flags at job-creation time; the
  follow-up attachment upload call now sets them correctly via the same
  recompute path.

Caught a real bug while testing this: the Purpose-change path recomputed
before saving the Purpose change, so a fresh query missed the pending edit
and the flags never updated. Fixed by committing the mutation before
recomputing.

3 new backend tests (purpose-change sets flag, delete clears flag,
non-primary purpose counts as "other"). 172/172 backend, 25/25 frontend
suites (57 tests) green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 21:10:39 +02:00
cesnimda cb2715c323 feat(email): add Correspondence.Provider discriminator
CI and Deploy / test (pull_request) Successful in 2m2s
CI and Deploy / deploy (pull_request) Has been skipped
b4 of the multi-provider email roadmap. The manual/free-text correspondence
entry path already existed (CorrespondenceController.Create) -- this slice
was narrower than the roadmap wording suggests: tag every Correspondence row
with which provider it came from (gmail | manual today; microsoft | imap
once those providers grow an import-into-Correspondence path of their own),
not build a new endpoint.

- Correspondence.Provider (nullable string), reconciled via the existing
  EnsureColumn pattern (SQLite + MySQL).
- Idempotent backfill: rows with an ExternalThreadId (historically only
  ever written by Gmail import) get 'gmail'; everything else gets 'manual'.
- GmailController.ImportSingleMessageAsync now tags Provider = "gmail".
- CorrespondenceController.Create now tags Provider = "manual".
- Both write sites use a fixed literal, not request input -- no injection
  surface introduced. Backfill SQL is static, no interpolation.

148/148 green (147 existing + 1 new CorrespondenceControllerTests; the
GmailController import test gained a Provider assertion in place).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 19:47:52 +02:00
cesnimda a8e2f4dc4a feat(email): add ImapProvider (generic IMAP for unsupported providers)
CI and Deploy / test (pull_request) Successful in 2m2s
CI and Deploy / deploy (pull_request) Has been skipped
b3 of the multi-provider email roadmap. Adds ImapConnection model + table
(reconciler pattern, SQLite+MySQL), ImapService (MailKit-backed IMAP client),
ImapProvider implementing the existing IEmailProvider contract unchanged,
and ImapController for credential-based connect (no OAuth — user supplies
host/username/password directly, verified by a live connect before storage).

Scope, documented inline with ponytail: comments:
- INBOX only, no multi-folder support.
- Thread grouping approximates the References/In-Reply-To chain root rather
  than the IMAP THREAD extension, which not every server implements.
- External message ids are IMAP UIDs, scoped to the connection's current
  UIDVALIDITY.

Security: ran the security-audit skill against this diff (credential
handling + arbitrary-host connect is exactly the class of change the
standing security gate exists for). Found and fixed a real SSRF: the
connect endpoint let an authenticated user point the server at an
arbitrary host:port with no internal-range check, and connect-vs-auth
failure was distinguishable to the caller -- together a working oracle to
fingerprint internal services (loopback/RFC1918/link-local/cloud metadata)
from the server's network position. Fixed with EnsureHostIsExternalAsync
(DNS-resolve + reject internal ranges, re-checked on every reconnect to
close the DNS-rebinding gap) and a single generic failure message that no
longer distinguishes connect vs auth failure. 7 regression tests added.

Dependency: MailKit 4.17.0 (MIT license) on JobTrackerBackend.csproj --
stdlib has no IMAP client; hand-rolling IMAP4rev1 (TLS, SASL, MIME parsing)
would be a large, security-sensitive protocol implementation nobody asked
for, so this is the correct dependency, not a stdlib substitute.

168/168 green (161 existing + 7 new SSRF regression tests; the earlier
14 IMAP feature tests are included in the 161).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 19:40:50 +02:00
cesnimda cacad5cc94 feat(email): add MicrosoftGraphProvider (Outlook/365 via Graph OAuth)
CI and Deploy / test (pull_request) Successful in 2m1s
CI and Deploy / deploy (pull_request) Has been skipped
b2 of the multi-provider email roadmap. Mirrors the Gmail provider's shape
end-to-end so the two stay structurally interchangeable:

- MicrosoftGraphConnection model + table (reconciler pattern, SQLite+MySQL,
  same shape as GmailConnection: encrypted refresh/access token, sync state).
- MicrosoftGraphOAuthService: auth-code + offline-access flow against
  login.microsoftonline.com, encrypted token storage via IDataProtector,
  message search/thread/detail fetch against Microsoft Graph (conversationId
  stands in for Gmail's threadId), attachment listing.
- MicrosoftGraphProvider implements IEmailProvider — no contract changes;
  the existing seam was already provider-neutral.
- MicrosoftGraphController: connect-url/oauth/callback/status/disconnect,
  mirrors GmailController's OAuth surface exactly (including the popup
  postMessage handshake). Job-matching/review endpoints stay Gmail-only for
  now, per the roadmap — generalising those needs the frontend provider
  picker work, not this slice.
- Registered in DI + IEmailProviderRegistry (multi-registration of
  IEmailProvider, resolved by ProviderKey).
- Config: Microsoft:ClientId/ClientSecret/TenantId/RedirectUri, wired through
  docker-compose.yml + .env.example alongside the existing Google:Gmail* keys.
- Tests: MicrosoftGraphControllerTests (OAuth lifecycle) +
  MicrosoftGraphProviderTests (DTO mapping onto the neutral contract).
  147/147 green (135 existing + 12 new).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 18:08:11 +02:00
cesnimda bd51c245d3 test(security): lock tenant isolation on match-score and status-suggestion
Cross-user access to the new endpoints returns NotFound (carried by the
JobTrackerContext global query filters). Regression guard for the class
of tenant-leak bugs found in the M013-M015 assessments.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 03:55:12 +02:00
cesnimda ae3505b877 feat: deterministic email-driven status suggestions
New EmailStatusClassifier scans a message subject/body for outcome
signals (interview invite, offer, rejection) and suggests a canonical
pipeline status. Priority-ordered so a rejection that mentions the prior
interview still classifies as Rejected. Deterministic - no AI - so it is
instant, reproducible, and safe.

- GET /api/jobapplications/{id}/status-suggestion reads the job's latest
  inbound correspondence (incl. Gmail imports) and suggests a forward
  status move, suppressed when already in/past that stage
- always human-confirmed via the existing PATCH .../status
- 7 classifier unit tests + 2 endpoint integration tests; backend green (133)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 03:48:32 +02:00
cesnimda 45cbc8b1ab feat: time-in-stage analytics + pipeline-driven funnel
- New pure StageAnalytics.TimeInStage: median days jobs have spent in
  each active pipeline stage (entry time from the last StatusChanged
  event into that stage, else applied date). Closed/success stages
  excluded since 'how long stuck' only applies to actionable stages.
- analytics-overview now derives the funnel from JobPipeline (includes
  the previously-omitted Waiting stage, normalizes legacy spellings) and
  returns TimeInStage.
- 4 unit tests; full backend suite green (124).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 03:40:41 +02:00
cesnimda bb736d1183 feat: canonical job pipeline as single source of truth
New JobPipeline: ordered canonical stages (Applied, Waiting, Interview,
Offer, Rejected, Ghosted) with category grouping and a Normalize() that
canonicalizes casing and known synonyms (Interviewing->Interview,
declined->Rejected, ...) while preserving unknown custom statuses.

- normalize status on every write path (Create/Update/PATCH status) so
  the stored value stays canonical without destroying custom values
- GET /api/jobapplications/pipeline exposes the ordered stages so the UI
  renders from one source instead of duplicated hardcoded lists
- 14 unit tests; full backend suite green (120)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 03:31:35 +02:00
cesnimda 209528c8b5 feat(ui): instant match-score panel on the Candidate Fit tab
Adds a MatchScoreCard at the top of the Candidate Fit tab that loads the
deterministic /match-score endpoint independently of the slow AI
narrative, so users see a reproducible score, matched/missing keyword
chips, and per-section coverage immediately.

- MatchScore types + cached, attachment-independent load effect
- graceful 'not enough signal' state
- EN/NB translations
- frontend panel test (matched/missing/section + degraded state)
- backend integration tests for GetMatchScore (happy path + missing CV)
- README endpoint reference

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 03:24:53 +02:00
cesnimda 3fad43a9e2 feat: deterministic CV-to-job match score endpoint
New JobCvMatchService: a pure, AI-free keyword-coverage scorer that
returns a stable, reproducible 0-100 match score plus matched/missing
keyword lists and per-CV-section coverage. Unlike candidate-fit (AI
narrative), it makes no model calls, so results are instant and
identical for identical inputs - the Jobscan-style differentiator.

- GET /api/jobapplications/{id}/match-score
- keywords = curated SkillTagger tags (high weight) + salient posting
  terms (title terms boosted); word-boundary matching avoids false hits
- section coverage shows where CV evidence is concentrated
- fix(SkillTagger): punctuation-tolerant C#/.NET patterns; the old \b
  boundaries silently missed 'C#,' and '.NET,' everywhere they are used
- 7 unit tests on the pure scorer; full backend suite green (104)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 03:16:32 +02:00
cesnimda 83e6430a24 feat: structured salary fields (min/max/currency/period)
Adds SalaryMin/SalaryMax/SalaryCurrency/SalaryPeriod alongside the
existing free-text Salary field (kept for back-compat and display).

- JobApplication model + idempotent column bridging for SQLite and MySQL
- Create/Update DTOs with NormalizeSalary (clamps negatives, swaps
  inverted min/max, uppercases currency, whitelists period)
- JobApplicationDto exposes the fields; CSV export gains 4 columns
- UI: add/edit dialogs get min/max/currency/period inputs; job table
  renders a formatted range via shared salary.ts formatter (falls back
  to free-text when structured values are absent)
- EN/NB translations; backend + full frontend suites green

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 22:15:37 +02:00
cesnimda 999d6e05e7 feat: automated daily SQLite database backups with retention
New DatabaseBackupHostedService + SqliteDatabaseBackupRunner:
- daily VACUUM INTO snapshot to <Data:Root>/backups (safe with WAL)
- catch-up backup at startup when none exists from the last 24h
- retention pruning (Backups:RetainCount, default 14)
- warns and stays idle on MySQL/MariaDB where external backups apply

Production previously had no automated database backup on Linux
(the /api/backup endpoint is Windows-DPAPI-only).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 21:49:58 +02:00
cesnimda 54abc9f546 Use Ollama rewrite path for CV generation 2026-04-11 22:26:03 +02:00
cesnimda 591c9b8a64 Clamp AI summarize lengths for CV rewrite 2026-04-11 21:55:51 +02:00
cesnimda 534534b333 Harden CV rewrite diagnostics and preview PDFs 2026-04-11 21:36:45 +02:00
cesnimda 811963749e Fix cross-user job history leak 2026-04-11 17:05:52 +02:00
cesnimda 09e96ce381 Fail closed on malformed local auth 2026-04-11 16:29:53 +02:00
cesnimda 6a223a4b70 Harden job import SSRF validation 2026-04-11 16:26:14 +02:00
cesnimda 27fd70a2d7 refactor, security updates, cv extraction upgrades 2026-04-11 01:34:32 +02:00
cesnimda 269dcb3487 Handle disconnected Gmail and bound CV rewrite prompts 2026-04-09 22:07:36 +02:00
cesnimda 5cd34f17bb Complete Gmail correspondence workflow 2026-04-02 12:29:24 +02:00
cesnimda b87e673d38 feat: add gmail review actions 2026-04-01 21:54:05 +02:00
cesnimda 69e78d8951 refactor: extract gmail matching service 2026-04-01 16:59:29 +02:00
cesnimda f48136f04c feat: enrich gmail correspondence metadata 2026-04-01 16:27:34 +02:00
cesnimda e5bcf9d5ea feat: harden gmail sync foundation 2026-04-01 16:09:29 +02:00
cesnimda 9191e4cc5b fix: harden admin system fallback and benchmark review 2026-04-01 13:38:22 +02:00
cesnimda 0d65835857 feat: add cv benchmark workflow and admin visibility 2026-04-01 12:25:45 +02:00
cesnimda 0551a525a8 feat: add server-backed profile CV builder pipeline 2026-04-01 12:25:35 +02:00
cesnimda f22c6791a7 Improve CV rewrite flow and parser accuracy 2026-04-01 11:30:37 +02:00
cesnimda f402213526 Extend CV classifier contract and provenance UI 2026-04-01 11:06:55 +02:00
cesnimda b283f8b9d2 Improve classifier fallback for flat CV parsing 2026-04-01 11:00:53 +02:00
cesnimda 517c42250d Refactor repeated test host setup 2026-04-01 10:48:28 +02:00
cesnimda 18d1de45cb Refactor backend project and tighten CV test coverage 2026-04-01 10:42:55 +02:00
cesnimda 44000f96f2 Improve CV parsing and profile editor flow 2026-03-29 14:29:18 +02:00
cesnimda 839a2ed80d Add CV template preview and PDF export pipeline 2026-03-29 00:43:54 +01:00
cesnimda 2392b135c2 Add CV extraction review surfaces 2026-03-28 23:51:03 +01:00
cesnimda 107c181506 Add canonical CV artifact pipeline 2026-03-28 23:32:54 +01:00
cesnimda d8ab312f59 Improve structured CV fallback extraction 2026-03-28 22:56:55 +01:00
cesnimda 4103f84f85 Fix account and backup admin settings flows 2026-03-28 15:30:07 +01:00
cesnimda 8f8a34ad9c Add typed structured CV extraction 2026-03-28 15:01:32 +01:00
cesnimda 19a4da9382 Auto-link verified Google sign-ins by email 2026-03-28 14:24:10 +01:00
cesnimda 9f949ee9df Harden password reset and email send flows 2026-03-28 14:17:12 +01:00
cesnimda 9adbde3f5e feat(S05/T01): Unified workflow trust signals across the API, table, da…
- JobTrackerApi/Controllers/JobApplicationsController.cs
- JobTrackerApi.Tests/JobApplicationsWorkflowSignalsTests.cs
- job-tracker-ui/src/jobWorkflowSignals.ts
- job-tracker-ui/src/components/JobTable.tsx
- job-tracker-ui/src/components/DashboardView.tsx
- job-tracker-ui/src/components/RemindersView.tsx
- job-tracker-ui/src/workflow-trust-signals.test.tsx
2026-03-24 14:28:01 +01:00
cesnimda ac1832ea04 chore(M001/S03): auto-commit after complete-slice 2026-03-24 12:40:09 +01:00
cesnimda 13d4e29336 chore(M001/S01): auto-commit after complete-slice 2026-03-24 12:27:04 +01:00
cesnimda 955cae6d4b test(S01/T01): Added a job-scoped Gmail matching contract with backend-…
- JobTrackerApi/Controllers/GmailController.cs
- JobTrackerApi/Services/GmailOAuthService.cs
- JobTrackerApi.Tests/GmailControllerTests.cs
- .gsd/milestones/M001/slices/S01/S01-PLAN.md
- .gsd/KNOWLEDGE.md
2026-03-24 12:07:25 +01:00
cesnimda 0cacb4e51b Implement S03 follow-up draft context loop 2026-03-24 11:05:41 +01:00
cesnimda b5b430947b Complete S02 application package drafting loop 2026-03-24 10:36:05 +01:00
cesnimda 3e5f796326 Complete S01 Gmail matching and import workflow 2026-03-24 10:06:50 +01:00