Skip to content

fix: resume pipeline (PDF parse, DOCX preview, downloads), inline job runner, Google OAuth, applicant emails#89

Merged
RikepilB merged 8 commits into
mainfrom
fix/resume-pipeline-and-notifications
Jun 11, 2026
Merged

fix: resume pipeline (PDF parse, DOCX preview, downloads), inline job runner, Google OAuth, applicant emails#89
RikepilB merged 8 commits into
mainfrom
fix/resume-pipeline-and-notifications

Conversation

@RikepilB

Copy link
Copy Markdown
Owner

Summary

Fixes the full resume pipeline plus auth and notification delivery:

  • PDF parsingcreateRequire + require("pdf-parse") broke under the Next bundler (PDF resumes failed while DOCX worked). Replaced with a memoized dynamic import() left external via serverExternalPackages (now also lists pdfjs-dist, @napi-rs/canvas). Committed PDF/DOCX fixtures so extraction tests always run in CI.
  • LLM upgrade — default OpenRouter model is now google/gemini-2.5-flash with gemini-2.5-flash-lite → openrouter/auto fallbacks (slugs verified against the live model list); tightened the parse prompt.
  • Resume serving — shared disk→DB resolution (src/lib/resume/storage-read.ts), proper Content-Disposition (inline for embeddable types, attachment with the real filename otherwise, ?download=1 to force), clearer 404 explaining dev/prod storage mismatches.
  • DOCX inline preview — new authenticated route /api/resumes/preview/[...objectName] converts DOCX → sanitized HTML (mammoth + sanitize-html allowlist, CSP default-src 'none'), rendered in a sandboxed iframe on the applicant detail page. PDFs keep the native viewer.
  • Inline job runner — new dispatcher (src/server/jobs/) with JOB_RUNNER=worker|inline. Inline (default on Vercel) runs resume parsing and email sends post-response via next/server after()no worker processes needed on Vercel. Worker mode keeps the existing pg-boss path. Admin fan-out sends sequentially to respect Resend rate limits.
  • Google OAuth — root cause: AUTH_GOOGLE_* set to empty strings while real credentials sat in legacy GOOGLE_CLIENT_*. Dev diagnostics now warn on conflicting pairs. Google sign-ins are upserted with an organization attached (Prisma adapter created them org-less, breaking org-scoped routes); roles never downgraded; optional AUTH_ALLOWED_EMAIL_DOMAIN gate.
  • Docs — SETUP.md: job runner modes, OAuth troubleshooting runbook, Resend sender requirements.

Verification

  • lint → typecheck → test (192 passed) → build all green.
  • E2E smoke against pnpm build && pnpm start with JOB_RUNNER=inline (no workers running):
    • PDF application → parsingStatus=COMPLETED, name/skills extracted by gemini-2.5-flash, match score computed — the previously broken path.
    • DOCX application → parse completed; preview route returns 401 logged-out.
    • EmailLog rows written for applicant confirmation + all 5 admin notifications (delivery blocked only by Resend's unverified-domain testing limitation, as documented).

Flagged risks / follow-ups

  • /api/resumes/* serves files unauthenticated (pre-existing; URLs are UUID-unguessable). Follow-up candidate.
  • Inline LLM call bounded by function maxDuration (60s on apply page); Retry button is the recovery path if cut.
  • Google sign-up policy: any Google account lands as RECRUITER with an org attached — set AUTH_ALLOWED_EMAIL_DOMAIN to restrict.
  • Production email delivery requires a verified Resend domain in EMAIL_FROM.

🤖 Generated with Claude Code

RikepilB added 8 commits June 11, 2026 09:52
…undling

The createRequire(import.meta.url) + require("pdf-parse") combo broke module
resolution inside the Next runtime (inline parse paths), so PDF resumes failed
to parse while DOCX (ESM mammoth) worked. A memoized dynamic import stays
external thanks to serverExternalPackages, which now also lists pdf-parse's
transitive deps (pdfjs-dist, @napi-rs/canvas). Adds a committed PDF fixture so
the extraction test always runs in CI.
…heap fallbacks

Replaces openrouter/owl-alpha default and free/auto fallbacks with
google/gemini-2.5-flash -> google/gemini-2.5-flash-lite -> openrouter/auto
(slugs verified against the live OpenRouter model list). Tightens the resume
parse prompt for stricter, deduplicated output.
…sition and clearer 404

Extracts the duplicated local-disk/ResumeFile resolution into
src/lib/resume/storage-read.ts, reused by the serving route and the parser.
The serving route now sends Content-Disposition (inline for embeddable types,
attachment otherwise, ?download=1 to force) with RFC 5987 filenames, and the
404 explains the dev-vs-prod storage mismatch.
Word resumes now render inline: a new authenticated route
/api/resumes/preview/<objectName> converts DOCX to sanitized HTML
(mammoth + sanitize-html allowlist, CSP default-src 'none') and the admin
applicant page embeds it in a sandboxed iframe. PDFs keep the native iframe.
Adds getResumePreviewKind() and a committed DOCX fixture.
…very when no worker runs

Adds a job dispatcher (src/server/jobs) with two modes via JOB_RUNNER:
'worker' enqueues to pg-boss as before; 'inline' (default on Vercel) runs
parsing and email sends after the response with next/server after(), so
serverless deployments work without worker processes. The email dispatch
switch is extracted to a shared module used by both the worker and the
inline path. Apply page gets maxDuration=60 for the inline LLM call.
…s with an organization

Root cause of the broken Google login: AUTH_GOOGLE_ID/SECRET were set to
empty strings while the real credentials lived in the legacy GOOGLE_CLIENT_*
vars, making it easy to edit the wrong pair. The dev diagnostic now warns
when both conventions are set with different values.

Google sign-ins are also upserted into the User table with an organization
attached (the Prisma adapter creates users without one, which broke
org-scoped routes like parse-retry). Roles are never downgraded and existing
org memberships are never reassigned. Adds an optional
AUTH_ALLOWED_EMAIL_DOMAIN gate, unset by default.
…te limits

Parallel after() callbacks fired one Resend request per admin simultaneously
and tripped the requests-per-second limit (observed in smoke testing). The
inline fan-out now sends sequentially inside a single after() task.
@vercel

vercel Bot commented Jun 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
scoutlane Ready Ready Preview, Comment Jun 11, 2026 3:45pm

@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7f75ff22-e8ae-44c0-a7e1-76717cfcdc10

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/resume-pipeline-and-notifications

Comment @coderabbitai help to get the list of available commands and usage tips.

@RikepilB RikepilB merged commit b7bc079 into main Jun 11, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant