Skip to content

Resume import — one shared service (WING-503)

Both surfaces that ingest a resume file — onboarding (frontend/src/app/features/onboarding/) and profile (frontend/src/app/features/profile/) — now call the same ResumeImportService (frontend/src/app/core/resume-import/resume-import.service.ts) instead of carrying their own copies of the upload/extract/apply pipeline.

Why

The two implementations had drifted: onboarding's copy broke on desktop while profile's kept working, which is exactly the failure mode duplicated behavior invites (see the fleet's one-implementation-per-behavior rule). WING-503 collapses them into one service so a fix lands in both surfaces at once.

Shape

  • ResumeImportService.import(file) — validates the file client-side (extension, size), posts it to the edition-appropriate extract endpoint, and returns the extracted text + proposal.
  • proposal-apply.ts (frontend/src/app/core/proposal-apply.ts) — applies the returned proposal to the profile store; shared by both callers.
  • Callers own only their UI: progress, error display, and where the user lands afterwards.

Endpoints and auth

  • Desktop (Electron, loopback API): POST /api/profile/extract-resume-docx — launch token mandatory, even from the trusted dev origin ([RequireLocalToken(allowDevOrigin: false)]; see security.md § 3). The Electron preload exposes the token and the Angular interceptor attaches it.
  • Cloud (pre-session onboarding): POST /api/tenant/extract-resume-docx — deliberately public pre-session, rate-limited (tenant-onboard) and size-capped; see the allowlist rationale in TenantSessionMiddleware.ExactMatchPublicPaths.

Tests

  • resume-import.service.spec.ts — service behavior, both success and rejection paths.
  • onboarding.spec.ts / profile.spec.ts — each caller renders the shared service's states.
  • Backend contract: ApiSmokeTests.ResumeDocxExtract_TrustedOriginWithoutLaunchToken_IsUnauthorized pins the desktop route's 401.

Plain-language sibling: resume-import