AI-powered job application platform aggregating 8 job sources with Gmail OAuth sending
JobHunt is a job application platform I designed and built to consolidate the entire job search workflow into one place. It pulls listings from 8 job boards simultaneously, deduplicates them, and lets you filter by job type (remote/hybrid/onsite), how recently posted, and keywords. You manage applications through 6 stages: Draft, Applied, Interview, Offer, Rejected, Withdrawn.
The AI layer is powered by Gemini 2.5 Flash Lite. From a job listing, you can: tailor your CV to the role, generate a cover letter in three styles (formal, casual, or creative), and generate 10 interview Q&As across technical, behavioural, company, and situational categories with hints for each. You can also analyze your CV to get a score and improvement suggestions, analyze your LinkedIn profile to get a rewritten headline and About section, and rebuild CV text that came out garbled from a badly structured PDF.
Every piece of AI output is editable before it goes anywhere. That was a deliberate decision because AI writing is a first draft, not a finished product. When you are ready to apply, you connect your Gmail via OAuth2 and send the email directly from your own address with the CV as an attachment. The platform tracks the Gmail message ID for reconciliation.
Auth uses WebAuthn/Passkeys for biometric login (Face ID, fingerprint) alongside email/password. The WebAuthn challenge is stored in the database rather than in-memory, which makes it work correctly on Vercel serverless where in-memory state does not persist between requests. CV management supports multiple CVs per user with custom labels, an active CV selector, PDF and .docx upload with a custom parser, and AI-powered CV rebuilding for garbled extractions.
Next.js 16 App Router with React 19 and TypeScript. Supabase handles the PostgreSQL database with Row Level Security enforcing user data isolation at the database level. All user data is scoped by user_id and RLS policies mean a query that accidentally omits the user filter returns no data rather than someone else's data.
The job aggregation runs via API route on demand and via Vercel cron in the background. Each source is fetched in parallel and results are upserted by (user_id, source, external_id) unique constraint, so re-fetching never creates duplicates. The normalisation layer maps each source's response shape to a consistent internal Job type.
The AI features use Gemini 2.5 Flash Lite specifically because it is on the free tier. Each feature (tailor CV, cover letter, interview prep, CV analysis, LinkedIn analysis, CV rebuild) is a separate function in src/lib/ai/ with its own prompt and output validation. Rate limiting uses Vercel KV with an in-memory fallback for local development.
The PDF parser reads the raw PDF binary and extracts text streams by decompressing FlateDecode-encoded content with Node.js zlib. This works on Vercel serverless without the browser-global dependencies that pdfjs-dist requires. The .docx parser uses Mammoth which has no such constraint.
Gmail OAuth stores the access token and refresh token in the gmail_connections table. Before sending an email, the route checks token expiry and refreshes automatically if needed. The sent email's Gmail message ID is stored on the application record for tracking.