← Back to projects

Case study

Job Application Assistant

A private, phone-first web app that finds remote developer jobs every day, ranks them, flags scams, and helps me prepare honest, tailored applications. It never applies on its own.

Open the live demo

The source code is private because the real app holds my personal data. The demo is the same codebase running on sample data.

Role: Solo: design, build, security, testing, and deployment

Next.js 16React 19TypeScriptPostgreSQL (Neon)Drizzle ORMZodTailwind CSSVitestPlaywrightGitHub ActionsVercel

The problem

  • Looking for remote work from the Philippines means checking many job boards, and most listings turn out to be US-only, senior, or onsite.
  • Jobs shared on Facebook groups and as poster images are hard to track, and some are scams asking for fees or IDs.
  • Tailoring a CV for every role takes time, and it's easy to drift into claiming skills I don't have.
  • After applying, follow-ups and interview details get lost across email and chat.

Quick tour (silent, sample data)

Screens (demo, sample data)

Ranked jobs with salary and schedule labels
Why a job scored what it did
Scam warnings on a sample post
Tracker with follow-up reminders
Answer bank for form questions

What it does

Daily job feed

A scheduled job pulls from 8 official job APIs and RSS feeds (no scraping, no logging into job sites), removes duplicates, drops jobs that aren't open to the Philippines, and scores the rest from 0 to 100 with a visible breakdown.

Salary and schedule labels

Parses salaries in any currency and period (hourly, monthly, annual), converts them to monthly PHP, and marks each job as target, negotiable, below range, or unknown. Detects weekend work and rotating shifts.

Manual import with scam checks

Paste a Facebook post or upload a poster screenshot. OCR runs inside the app, fields are pre-filled for review, and posts that ask for fees, IDs, or Telegram-only contact get a visible warning.

Company check

Checks the company website, domain age via public RDAP, and whether the contact email matches the domain, then builds search links I open myself. A helper verdict, never a guarantee.

Honest tailoring

Builds a prompt with my contact details masked as placeholders, I run it in my own Claude Project, and paste the result back. A validator flags any skill, employer, date, or number that isn't in my master resume before I can export to Word or PDF.

Tracker and answer bank

Pipeline from applied to offer, follow-up reminders after 7 days, a ghosted suggestion after 30, calendar files for interviews, and saved answers to common form questions with one-tap copy.

Architecture

A request

Phone or laptop

Installable web app, built for 360px screens first

Proxy (every request)

Deny by default, strict CSP with nonces, CSRF check, security headers

Sign-in

GitHub OAuth (PKCE, my numeric ID only, 2FA required), then an app password hashed with argon2id

Next.js App Router

Server components, server actions, and route handlers; Zod validates every input

Neon Postgres via Drizzle

Personal fields and files encrypted with AES-256-GCM before they reach the database

The daily fetch

Vercel Cron, once a day

8 job APIs and RSS feeds (allowlisted domains, timeouts, size limits)

Validate, sanitize, dedupe

Hard filters and scoring

Saved for the morning

Security decisions

Encrypted at rest

AES-256-GCM with a random IV per value and the column name bound in as associated data, so values can't be swapped between rows. Uploaded files are encrypted the same way.

Deny by default

Every route is private unless listed as public, and every handler re-checks the session. A test fails the build if a new route isn't covered.

Two-step login

GitHub sign-in restricted to one numeric user ID with 2FA enforced, followed by a rate-limited app password. Sessions are hashed server-side with idle and absolute timeouts.

PII masking

Name, email, phone, and links are replaced with placeholders before any prompt leaves the app, and put back locally afterwards.

Untrusted input

Job text is stripped of HTML and never followed as instructions. Uploads are checked by magic bytes, re-encoded, and stripped of metadata.

Safe demo

The public demo runs the same code with sample data. It refuses every change in one place, and refuses to start if it's pointed at a database holding real data.

How it was built

  • Built in phases, each on its own branch with a pull request, a written review checklist, and a release tag.
  • CI on every push: lint, type check, unit and integration tests, build, dependency audit, and secret scanning.
  • 500+ unit and integration tests, plus 320+ end-to-end tests on three phone sizes and desktop, with accessibility checks.
  • 23 architecture decision records explaining choices like the encryption scheme and hosting.
  • Batched the scoring updates so the daily run went from about 146 seconds to 7, well inside the hosting time limit.
Try the demo