Skip to content

Latest commit

Β 

History

494 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸš€ Hire Flow

Hire Flow Cover

TypeScript Next.js React Prisma PostgreSQL Tailwind AI Better Auth Pusher TanStack Query Zustand Zod

About

Hire Flow is a production-grade job board and applicant tracking system (ATS) supporting three distinct user roles β€” Admins, Recruiters, and Job Seekers β€” with real-time messaging, AI-powered resume assistance, and a public-facing job marketplace with SEO built in from the ground up.

Built with Next.js 16, React 19, Prisma 7, and Better Auth, this project demonstrates end-to-end product thinking: tenant isolation, app-wide rate limiting, optimistic concurrency, audit trails, CI quality gates, and agent-assisted development workflows.

Designed and built by Mohamed Hazeem β€” a full-stack engineer focused on production-grade architecture, scalable systems, AI Integration and pragmatic feature scoping.


🎯 The Problem & Solution

Hire Flow exists to give small teams and job seekers the substance of enterprise hiring software without the enterprise tax β€” fragmented tools, brittle integrations, and five-figure monthly bills.

  • Recruiters are stuck between spreadsheets and bloated ATS tools. Incumbents like Greenhouse and Lever charge per-seat and bury core workflows (pipeline, bulk actions, analytics, messaging) behind paywalls. Hire Flow delivers a full 7-stage pipeline with optimistic concurrency, bulk transitions, audit trails, and CSV export in one place β€” at a fraction of the cost.
  • Job seekers get ghosted and disorganized. Most boards offer apply-and-forget with no visibility. Hire Flow gives candidates a structured resume builder, AI-powered enhancement, one-click apply with frozen resume snapshots, and an application tracker with a shared status timeline β€” so they always know where they stand.
  • Admins lack a single source of truth. Platform owners need oversight without building from scratch. Hire Flow ships user/recruiter management, ban/unban, session revocation, team invites, and platform-wide analytics behind role-guarded endpoints.
  • Communication is fragmented across email, calendar, and chat. Hire Flow centralizes real-time, rate-limited messaging (Pusher-backed private channels) for every role pair β€” admin↔user and recruiter↔applicant β€” keeping context in one thread instead of lost in inboxes.
  • Transparency and trust are afterthoughts in legacy tools. By surfacing resume snapshots, unified status timelines, and a dual-gate public job marketplace with SEO baked in, Hire Flow keeps candidates, recruiters, and admins aligned on the same facts.

The result: ~90% of the value of enterprise ATS tooling for ~10% of the operational cost, with zero vendor lock-in.

πŸ“Έ App Screenshots

Dashboard
Job Search
Applicant Pipeline
Analytics
Messaging
Resume Builder
AI Enhancement
Notifications
Public Jobs

🧱 Tech Stack

Category Technology Version
Framework Next.js 16.2.7
UI Library React 19.2.4
Language TypeScript ^5
ORM Prisma (+ @prisma/adapter-pg) ^7.8.0
Database Driver pg ^8.21.0
Auth Better Auth (+ Prisma adapter) ^1.6.15
Validation Zod ^4.4.3
Data Fetching TanStack React Query ^5.101.0
Client State Zustand ^5.0.14
Forms React Hook Form (+ Hookform Resolvers) ^7.78.0 / ^5.4.0
Realtime Pusher / pusher-js ^5.3.4 / ^8.5.0
Email Resend + React Email ^6.12.4 / ^6.6.1
Charts Recharts ^3.8.1
Animation Motion ^12.40.0
PDF/DOC Parsing pdf-parse, mammoth, react-pdf ^2.4.5 / ^1.12.0 / ^10.4.1
Styling Tailwind CSS v4 + shadcn ^4 / ^4.11.0
Date Handling date-fns ^4.4.0
File Storage Vercel Blob (provider abstraction) ^2.6.1
Tooling ESLint 9, Prettier, Vitest, Stryker Mutator, tsx, cross-env β€”

✨ Features

πŸ›‘οΈ Admin

  • User & recruiter management with ban/unban and role assignment
  • Session inspection and bulk session revocation
  • Admin team management with invite-based onboarding
  • Job oversight dashboard with platform-wide analytics
  • Real-time admin messaging (Pusher-backed private channels)
  • Read-only applicant detail view with resume fallback chain

🏒 Recruiter

  • Company profile CRUD + team invite management + company logo upload (cloud storage)
  • Full job posting lifecycle (draft β†’ active β†’ archived)
  • 7-status applicant pipeline with optimistic concurrency control
  • Bulk actions: mass status transitions, bulk rejection with shared reasoning, one-time action constraints
  • Applicant detail view: profile, timeline, resume, in-thread messaging, status controls
  • Analytics suite: conversion funnels, trend charts, filterable by date range/status/type/location; status filter supports multi-select with colored dot indicators
  • CSV export: RFC 4180-compliant, streamed via cursor-batched queries, 50K row cap
  • Real-time, rate-limited direct messaging with applicants
  • App-wide rate limiting β€” configurable sliding-window limiter with OpenTelemetry metrics, batched cleanup, per-endpoint fail strategy, and admin metrics endpoint
  • Role-aware notifications & activity feed with unread badges

πŸ‘€ Job Seeker (User)

  • Rich profile builder: experience, social links, skills, salary expectations
  • In-app resume builder (structured JSON, not a generic text editor) + file upload (PDF/DOC/DOCX ≀10MB) with automatic file cleanup on delete
  • AI-powered resume enhancement β€” multi-provider (Anthropic/OpenAI/Gemini), suggestion-only with per-suggestion copy, ATS scoring, rate-limited to 5/day
  • One-click apply with resume snapshotting (frozen at apply time, survives resume deletion)
  • Application tracking: filterable list, status timeline, withdraw flow, in-thread recruiter messaging
  • Bookmark/save jobs with graceful handling of jobs that later go inactive

🌍 Public

  • Full-text job search with advanced filtering (work mode, employment type, experience, industry)
  • Dual-gate job visibility β€” jobs only appear when both recruiter- and admin-level flags allow it
  • Job detail pages with view tracking (deduplicated per session), company preview cards, and related content panels (company jobs + similar jobs)
  • Animated home page: hero search, category strip, featured jobs/companies, testimonials
  • Career resources hub (resume tips, interview checklist, salary FAQ)
  • Dedicated pages: About, Careers, Contact, Employers, Pricing, Press, Become an Employer
  • SEO-complete: dynamic sitemap.xml, robots.txt, and JSON-LD JobPosting structured data
  • Social links in footer (LinkedIn, Twitter/X, GitHub) with mailto: fallback to contact email

⏭️ Strategic Omissions (By Design)

Hire Flow intentionally avoids legacy enterprise bloat to keep infrastructure costs under $50/month and the user experience fast.

Feature Status Rationale
Email forwarding (IMAP/SMTP sync) ❌ Not built Syncing external inboxes requires complex webhooks and $5/user/month Google API fees. All communication stays native in-app (Pusher real-time), ensuring recruiters never miss context and candidates have a single source of truth.
Google / Outlook Calendar sync ❌ Not built Two-way calendar APIs are brittle and require OAuth token refresh cycles. We store interviewDate and meetingLink as data fields β€” recruiters paste their own meeting links, keeping scheduling simple and bug-free.
External job board syndication ❌ Not built Posting to LinkedIn / Indeed requires paid API access and strict content policy compliance. Hire Flow is a standalone marketplace β€” we drive traffic directly to your job posts, not through aggregators.
OCR for scanned/ handwritten images ❌ Not built Text extraction from digital PDFs and DOCX (via pdf-parse/mammoth) is already wired into the AI enhancement pipeline β€” scanned image OCR requires a separate ML layer that adds cost for minimal real-world gain.

The trade-off: You get ~90% of the value of enterprise ATS tools (Greenhouse, Lever) for ~10% of the operational cost, with zero vendor lock-in.


πŸ€– AI-Powered Features

Hire Flow ships a production-grade AI layer that goes beyond a single API call β€” it's an architected multi-provider system built for reliability, cost control, and user experience.

Multi-Provider Abstraction

A single AI_PROVIDER env var (anthropic | openai | google) switches between Claude, GPT, and Gemini under the hood β€” no code changes, no provider lock-in. If no API key is configured, every AI surface degrades gracefully with a user-facing message rather than crashing the page.

Resume Enhancement Engine

Job seekers get an interactive AI assistant that:

  • Analyses their resume against the job market and suggests targeted improvements
  • Scores ATS compatibility (keyword density, section completeness, formatting)
  • Returns actionable suggestions with per-suggestion copy, never a blind rewrite
  • Snapshots the resume at enhancement time so the original is preserved

Rate-Limited Per-User Quota

Each user gets 5 AI enhancements per day, enforced by an atomic PostgreSQL UPDATE … WHERE used < $3 RETURNING query β€” quota races are impossible even under concurrent requests. The quota survives server restarts (DB-backed, not in-memory).

Graceful Degradation

Every AI feature checks for key presence at runtime. If no provider key is configured, the UI shows "AI features temporarily unavailable" β€” the rest of the app works perfectly. This pattern is enforced consistently across resume enhancement, ATS scoring, and any future AI surface.


πŸ›οΈ Architecture & Design Decisions

Resume Snapshotting β€” At the moment a user applies, their resume (file URL or builder JSON) is frozen directly onto the Application row. This guarantees recruiters always see exactly what was submitted, even if the source resume is later edited or soft-deleted.

Unified Status Timeline β€” Both the recruiter and applicant surfaces write to a shared ApplicationStatusChange table starting from the very first applied transition. A single StatusTimeline component renders identically on both sides, eliminating duplicated timeline logic and drift.

Multi-Provider AI Layer β€” lib/ai-client.ts abstracts over Anthropic, OpenAI, and Google via a single AI_PROVIDER env var, with graceful degradation ("AI features temporarily unavailable") if no key is configured β€” so the app never hard-fails on a missing third-party dependency.

Rate Limiting β€” A unified lib/rate-limiting/ module provides configurable sliding-window rate limiting across all API routes, with OpenTelemetry metrics, batched cleanup, per-endpoint fail strategy, and an admin metrics endpoint. Wired into all admin, recruiter, and user API routes β€” 36 handlers across 29 endpoint keys β€” via withRateLimit. Rate limits scale by role via configurable multipliers (anonymous Γ—0.3, user Γ—1, recruiter Γ—2, admin Γ—5, super_admin Γ—10).

Isolated Public Route Group β€” The (public) route group ships its own navbar/footer shell, kept fully separate from (auth) and (roles) groups. This avoids double-chrome bugs and lets crawlers hit marketing/job pages without any auth machinery in the render path.

Dual-Gate Job Visibility β€” Every public-facing job query filters on both status: "active" (recruiter-controlled) and isActive: true (admin kill-switch). Missing either check would leak archived or platform-deactivated postings β€” this pattern is enforced consistently across listings, sitemap generation, and featured jobs.

Cloud Storage Provider Abstraction β€” File uploads (resumes, logos) are abstracted behind a provider registry (lib/upload.ts). The UPLOAD_PROVIDER env var selects between local (dev) and vercel-blob (production). Both saveUpload and deleteUpload dispatch to the correct implementation β€” file cleanup on resume delete or logo change automatically removes the remote file, preventing storage bloat. Switching providers requires no code changes.

Middleware-Driven Redirects β€” Rather than a single "role home" constant scattered across components, redirect logic lives centrally in proxy.ts middleware and shared auth hooks, keeping role-based routing consistent and easy to audit.

Null-Guarded Structured Data β€” JSON-LD JobPosting markup is injected via Next.js generateMetadata, with every field (salary, location, employment type) individually null-checked so no fabricated data ever reaches search engines.

CI Quality Gates β€” A 6-job GitHub Actions workflow enforces typecheck, lint, format, unit/integration/contract tests with coverage, E2E, performance with baseline regression, and mutation testing (currently disabled in CI). Vitest v3 global setup returns teardown for perf result publishing. Baseline regression compares PF1–PF5 against benchmark/baseline.json; mutation testing (Stryker, currently disabled) uses vitest-runner and break: null threshold.


🚦 CI Quality Gates

Hire Flow enforces 6 quality gates in .github/workflows/test.yml:

Gate Tool When Purpose
Typecheck tsc --noEmit Every PR Fastest gate; catches type errors ESLint misses
Lint ESLint 9 Every PR Code quality, unused vars, react-hooks rules
Format Prettier Every PR Consistent formatting across 600+ files
Tests + Coverage Vitest (default + dom + contract) Every PR Unit, integration, contract tests with coverage ratchet
E2E Playwright Every PR Full role-based journeys with real auth
Performance Vitest perf + baseline compare Master push PF1–PF5 budgets + regression against benchmark/baseline.json
Mutation Stryker Mutator Master push (commented out) Surviving mutants across lib/rate-limiting/ οΏ½ disabled until Stryker issues are resolved

Supporting config: .prettierrc, .prettierignore, vitest.config.ts (4 projects: default, dom, contract, perf), stryker.config.json (vitest-runner, break: null), scripts/compare-benchmark.ts, scripts/update-baseline.ts, lib/test/perf-teardown.ts.

Performance results flow: perf tests call publishBenchmark() β†’ vitest v3 globalSetup returns globalTeardown β†’ writeBenchmarkResults(sha) writes benchmark/results/${sha}.json β†’ CI compare-benchmark.ts reads baseline + results β†’ fails on hard limit or >20% regression.


πŸ“‘ Core API Endpoints

All routes use Zod validation, centralized error handling (lib/api-error.ts), and requireRole([...]) guards. All admin, recruiter, and user API routes apply app-wide rate limiting via withRateLimit β€” limits scale by role multiplier. Error responses follow { error: string, code?: string }.

Admin / Super Admin

Method Endpoint Purpose Rate Limited
GET /api/admin/dashboard Platform-wide stats (users, jobs, applications) βœ…
GET /api/admin/users List users (paginated, filterable) βœ…
GET /api/admin/users/[id] User detail βœ…
DELETE /api/admin/users/[id] Remove user βœ…
POST /api/admin/users/[id]/ban Ban user + revoke all sessions βœ…
POST /api/admin/users/[id]/unban Unban user βœ…
POST /api/admin/users/[id]/role Change user role βœ…
GET /api/admin/users/[id]/sessions List active sessions βœ…
DELETE /api/admin/users/[id]/sessions Revoke all sessions βœ…
GET /api/admin/users/[id]/applications List user's applications βœ…
GET /api/admin/jobs List all jobs (paginated, filterable) βœ…
DELETE /api/admin/jobs/[id] Remove job βœ…
PATCH /api/admin/jobs/[id] Toggle isActive kill-switch βœ…
GET /api/admin/invite List pending invites & team members βœ…
DELETE /api/admin/invite/[id] Cancel pending invite βœ…
POST /api/admin/invite/accept Accept invite with token βœ…
DELETE /api/admin/team/[id] Remove team member βœ…
GET /api/admin/threads List message threads βœ…
GET /api/admin/messages/[threadId] Get thread messages βœ…
POST /api/admin/messages/[threadId] Send message βœ…
DELETE /api/admin/messages/[threadId] Delete thread βœ…
DELETE /api/admin/messages/[threadId]/[messageId] Delete message βœ…
GET /api/admin/messages/search Search users & recruiters βœ…
GET /api/admin/applications/[applicationId]/detail Read-only applicant detail (profile, timeline, resume) βœ…

Recruiter

Method Endpoint Purpose Rate Limited
GET /api/recruiter/jobs List company jobs (paginated, filterable) βœ…
POST /api/recruiter/jobs Create job posting βœ…
GET /api/recruiter/jobs/[id] Job detail βœ…
PATCH /api/recruiter/jobs/[id] Update job fields βœ…
DELETE /api/recruiter/jobs/[id] Soft-delete (active/archived) or hard-delete (draft) βœ…
PATCH /api/recruiter/jobs/[id]/toggle Toggle status (draft ↔ active ↔ archived) βœ…
GET /api/recruiter/jobs/[id]/applicants List applicants (paginated, filterable) βœ…
GET /api/recruiter/jobs/[id]/analytics Per-job pipeline funnel & trends βœ…
GET /api/recruiter/jobs/[id]/applicants/export Stream RFC 4180 CSV of filtered applicants βœ…
GET /api/recruiter/applications/[applicationId]/detail Full applicant detail (profile, timeline, resume) βœ…
GET /api/recruiter/applications/[applicationId]/profile Applicant profile for messaging βœ…
PATCH /api/recruiter/applications/[applicationId]/status Transition applicant pipeline status βœ…
POST /api/recruiter/applications/[applicationId]/revert Revert to previous status from audit trail βœ…
POST /api/recruiter/applications/bulk/status Atomic bulk status transition βœ…
GET /api/recruiter/analytics Cross-job analytics (date range, filters) βœ…
GET /api/recruiter/threads List message threads βœ…
GET /api/recruiter/messages/[threadId] Get thread messages βœ…
POST /api/recruiter/messages/[threadId] Send message βœ…
DELETE /api/recruiter/messages/[threadId] Delete thread βœ…
DELETE /api/recruiter/messages/[threadId]/[messageId] Delete message βœ…
GET /api/recruiter/messages/search Search applicants βœ…
GET /api/recruiter/invite List pending invites βœ…
DELETE /api/recruiter/invite/[id] Cancel pending invite βœ…
POST /api/recruiter/invite/accept Accept invite with token βœ…
DELETE /api/recruiter/team/[id] Remove team member βœ…

Job Seeker (User)

Method Endpoint Purpose Rate Limited
GET /api/user/profile Get own profile βœ…
GET /api/user/resumes List own non-deleted resumes βœ…
POST /api/user/resumes Upload resume (PDF/DOC/DOCX, ≀10MB) βœ…
PATCH /api/user/resumes/[id] Set resume as primary ❌
DELETE /api/user/resumes/[id] Soft-delete resume + remove from storage ❌
PATCH /api/user/resumes/[id]/builder-data Update builder-structured JSON resume ❌
POST /api/user/resumes/[id]/ai-enhance AI resume suggestions (rate-limited, 5/day) βœ…
GET /api/user/applications List own applications (filterable, searchable) βœ…
GET /api/user/applications/stats Application counts (total, active, interviews, offers) ❌
GET /api/user/applications/[id] Application detail with timeline ❌
DELETE /api/user/applications/[id] Withdraw (applied / reviewing only) ❌
GET /api/user/bookmarks List bookmarked jobs βœ…
POST /api/user/bookmarks Toggle bookmark (create / delete) βœ…
GET /api/user/bookmarks/[jobId] Check bookmark status βœ…

Public

Method Endpoint Purpose Rate Limited
GET /api/jobs Public job listing (search, filters, pagination) ❌
GET /api/jobs/[id] Job detail (JSON-LD enriched) ❌
POST /api/jobs/[id]/view Increment view count (rate-limited, session-dedupped) βœ…
POST /api/jobs/[id]/apply Submit application (auth required, rate-limited) βœ…
GET /api/jobs/[id]/related Related jobs from same company & similar roles ❌

Shared / Infrastructure

Method Endpoint Role Purpose Rate Limited
POST /api/upload authenticated Upload a file (resume, logo, attachment) ❌
DELETE /api/upload authenticated Delete a file by URL or filename ❌
GET /api/files/download authenticated Stream file download (auth-guarded proxy) ❌
GET /api/notifications authenticated List notifications βœ…
PATCH /api/notifications authenticated Mark notifications as read ❌
DELETE /api/notifications authenticated Delete notification ❌
POST /api/pusher/auth authenticated Pusher private channel authentication ❌
GET /api/users/[id] any role Resolve user by ID (name, avatar) ❌

πŸ“ˆ Project Scale

  • 5 completed phases (Foundation β†’ Admin β†’ Recruiter β†’ User β†’ Public), fully sequenced and documented
  • 3 distinct role-based dashboards + 1 public marketplace
  • 7-stage applicant status pipeline with full audit trail
  • Cloud storage provider abstraction (local dev ↔ Vercel Blob production)
  • App-wide rate limiting β€” lib/rate-limiting/ module with config, middleware, rate-limiter, repository, metrics, telemetry, cleanup, request-context; wired into 36 API route handlers across 29 endpoint keys (admin, recruiter, user, public); 3 role-multiplier tests added for recruiter/admin/super_admin
  • CI quality gates β€” ESLint 9, Prettier, Vitest 4-project config (default/dom/contract/perf), Stryker mutation testing (currently disabled), GitHub Actions 6-job workflow
  • 700+ TypeScript/React files across API routes, feature modules, shared components, rate-limiting, and test infrastructure
  • Real-time messaging across 3 role pairs (admin↔user, recruiter↔applicant) via Pusher private channels

🏁 Getting Started

Prerequisites

  • Node.js 20+
  • PostgreSQL database (local or hosted, e.g. Neon/Supabase)
  • (Optional) Pusher app credentials for realtime features
  • (Optional) An API key from Anthropic, OpenAI, or Google for AI resume suggestions
  • (Production) A Vercel Blob BLOB_READ_WRITE_TOKEN when using UPLOAD_PROVIDER=vercel-blob

1. Clone & install

git clone https://github.com/Mohamedhazeem/hire-flow.git
cd hire-flow
npm install

2. Configure environment variables

Create a .env file in the project root:

# App
NODE_ENV=development
ALLOW_SEED=false                          # gate for running the seed script
DATABASE_URL=""                           # PostgreSQL connection string

# Auth (Better Auth)
BETTER_AUTH_SECRET=                       # random secret for session signing
BETTER_AUTH_URL=http://localhost:3000
NEXT_PUBLIC_APP_URL=http://localhost:3000
NEXT_PUBLIC_ENABLE_TEMP_MAIL_CHECK=false  # block disposable email domains on signup

# OAuth
GOOGLE_CLIENT_ID=""
GOOGLE_CLIENT_SECRET=""

# Transactional Email (Resend)
RESEND_API_KEY=""                         # use onboarding@resend.dev while testing
EMAIL_FROM="HireFlow <onboarding@resend.dev>"

# Bootstrap
PROMOTE_TO_SUPER_ADMINS="admin@hireflow.dev"   # comma-separated emails auto-eligible for promotion

# Realtime (Pusher)
PUSHER_APP_ID=000000
PUSHER_KEY=dummy-key
PUSHER_SECRET=dummy-secret
PUSHER_CLUSTER=us2
NEXT_PUBLIC_PUSHER_KEY=dummy-key
NEXT_PUBLIC_PUSHER_CLUSTER=us2

# AI Provider (optional β€” defaults to 'anthropic')
# Supported values: anthropic | openai | google
AI_PROVIDER=anthropic

# Anthropic (Claude)
ANTHROPIC_API_KEY=sk-ant-xxxxxxxxxxxx
ANTHROPIC_MODEL=claude-sonnet-4-20250514

# OpenAI (GPT) β€” uncomment and fill to use instead
# OPENAI_API_KEY=sk-xxxxxxxxxxxx
# OPENAI_MODEL=gpt-4o

# Google (Gemini) β€” uncomment and fill to use instead
# GEMINI_API_KEY=xxxxxxxxxxxx
# GEMINI_MODEL=gemini-2.0-flash

# Upload Storage Provider: "local" (dev) or "vercel-blob" (production)
UPLOAD_PROVIDER=local

# Vercel Blob β€” required when UPLOAD_PROVIDER=vercel-blob
# Get this token from: Vercel Dashboard β†’ Storage β†’ Blob β†’ Tokens
BLOB_READ_WRITE_TOKEN=

# Vercel Blob CDN host β€” used for image remotePatterns in next.config.ts
NEXT_PUBLIC_BLOB_CDN_HOST=public.blob.vercel-storage.com

# Social links (optional β€” unset values fall back to mailto:CONTACT_EMAIL)
NEXT_PUBLIC_LINKEDIN_URL=
NEXT_PUBLIC_TWITTER_URL=
NEXT_PUBLIC_GITHUB_URL=
NEXT_PUBLIC_CONTACT_EMAIL=

3. Set up the database

npx prisma migrate dev
npm run seed

4. Promote a super admin

The seed script creates demo users. To promote your own signed-up email to Super Admin, add it to PROMOTE_TO_SUPER_ADMINS in .env before running:

npm run promote

Use npm run demote to revoke super admin status.

5. Run the dev server

npm run dev

Visit http://localhost:3000. Sign up as a job seeker directly, or promote your account to explore the Admin and Recruiter dashboards.


πŸ“‚ Project Structure

hire-flow/
β”œβ”€β”€ app/
β”‚   β”œβ”€β”€ (public)/                # Marketing shell: home, jobs, resources, privacy, terms, about, careers, contact, pricing, press, employers, become-employer
β”‚   β”œβ”€β”€ (auth)/                  # Login, register, verify-email, reset-password, unauthorised, admin-invite, recruiter-invite
β”‚   β”œβ”€β”€ (roles)/
β”‚   β”‚   β”œβ”€β”€ admin/                # Admin dashboard, users, jobs, team, messages
β”‚   β”‚   β”œβ”€β”€ recruiter/            # Recruiter dashboard, jobs, applicants, analytics
β”‚   β”‚   └── user/                 # Job seeker dashboard, profile, resumes, applications
β”‚   β”œβ”€β”€ api/                      # REST route handlers (admin/recruiter/user/jobs/files)
β”‚   β”œβ”€β”€ features/                 # Feature-colocated components, hooks, queries, schemas
β”‚   β”‚   β”œβ”€β”€ admin/
β”‚   β”‚   β”œβ”€β”€ auth/
β”‚   β”‚   β”œβ”€β”€ recruiter/
β”‚   β”‚   β”œβ”€β”€ shared/
β”‚   β”‚   β”œβ”€β”€ user/
β”‚   β”‚   β”œβ”€β”€ jobs/
β”‚   β”‚   β”œβ”€β”€ landing/
β”‚   β”‚   β”œβ”€β”€ public/
β”‚   β”‚   └── notifications/
β”‚   β”œβ”€β”€ sitemap.ts
β”‚   β”œβ”€β”€ robots.ts
β”‚   β”œβ”€β”€ layout.tsx
β”‚   └── providers.tsx
β”œβ”€β”€ components/
β”‚   β”œβ”€β”€ chat/                    # Chat header, thread list, message input
β”‚   β”œβ”€β”€ layout/                  # Page header, role layout client
β”‚   β”œβ”€β”€ shared/                  # StatusTimeline, ConfirmActionButton, AvatarFallback…
β”‚   └── ui/                      # Shared shadcn primitives (button, table, dialog, data-table…)
β”œβ”€β”€ lib/                          # Core utilities
β”‚   β”œβ”€β”€ api/                     # api-client, api-error, api-response, api-wrapper
β”‚   β”œβ”€β”€ handlers/                # Message & invite handlers
β”‚   β”œβ”€β”€ pusher/                  # Pusher server/client setup
β”‚   β”œβ”€β”€ repositories/            # Application, job, message repositories
β”‚   β”œβ”€β”€ rate-limiting/           # App-wide rate limiting (config, middleware, limiter, metrics, cleanup)
β”‚   β”œβ”€β”€ services/                # Application, job, notification services
β”‚   β”œβ”€β”€ upload.ts                # Provider registry: local + Vercel Blob implementations
β”‚   └── test/                    # Factories, fixtures, mocks, reset-db, perf helpers, teardown
β”œβ”€β”€ stores/                       # Zustand: ui-store, chat-store
β”œβ”€β”€ features/
β”‚   └── messages/                 # Presence store & realtime messaging
β”œβ”€β”€ utils/                        # env, format-string, etc.
β”œβ”€β”€ scripts/                      # promote-super-admin, demote-super-admin, benchmark compare/update
β”œβ”€β”€ e2e/                          # Playwright spec files
β”œβ”€β”€ prisma/
β”‚   β”œβ”€β”€ schema.prisma
β”‚   β”œβ”€β”€ seed.ts
β”‚   └── scripts/
β”œβ”€β”€ proxy.ts                      # Auth & role-redirect middleware
└── manifest.md                   # Living build log & architecture record

πŸ“„ API Documentation (OpenAPI)

A modular OpenAPI 3.0.3 specification is maintained under docs/, reverse-engineered from the live route handlers, Zod schemas, and Prisma models.

docs/
β”œβ”€β”€ openapi.yaml                      # Root: info, servers, tags, security, global component refs, path refs (dev/localhost)
β”œβ”€β”€ openapi.production.yaml           # Production variant: same spec with production Vercel server URL (gitignored)
β”œβ”€β”€ paths/                            # One file per API group (admin, recruiter, user, jobs, users, auth, notifications, pusher, files, upload)
β”œβ”€β”€ schemas/                          # Granular schemas by group + shared
β”‚   β”œβ”€β”€ admin/
β”‚   β”œβ”€β”€ recruiter/
β”‚   β”œβ”€β”€ user/
β”‚   β”œβ”€β”€ jobs/
β”‚   β”œβ”€β”€ users/
β”‚   β”œβ”€β”€ auth/
β”‚   β”œβ”€β”€ notifications/
β”‚   β”œβ”€β”€ pusher/
β”‚   β”œβ”€β”€ upload/
β”‚   └── shared/
β”‚       β”œβ”€β”€ ErrorResponse.yaml
β”‚       β”œβ”€β”€ ValidationErrorResponse.yaml
β”‚       └── enums/                    # Role, JobStatus, ApplicationStatus, WorkMode, EmploymentType, NotificationType
β”œβ”€β”€ parameters/shared-params.yaml
β”œβ”€β”€ requestBodies/shared-requestBodies.yaml
β”œβ”€β”€ responses/shared-responses.yaml
β”œβ”€β”€ headers/shared-headers.yaml
└── security/securitySchemes.yaml

Spec characteristics

  • 85 operations across 10 API groups with globally unique operationIds.
  • Responses follow the app's envelope: { success: boolean, data: <T> }; errors return { success: false, message } (422 validation errors additionally return { error, details }).
  • Authentication modeled as Better Auth cookie session (sessionAuth); public endpoints explicitly omit the security requirement.
  • Shared components (error responses, enums, pagination params, rate-limit headers) are centralized under docs/responses/, docs/parameters/, docs/headers/, and docs/security/.
  • List endpoints embed pagination metadata directly in data β€” offset mode: {page, pageSize, total, totalPages, hasNextPage, hasPrevPage}; cursor mode: {nextCursor, hasNextPage}.
  • All 513 cross-file $refs resolve; 0 orphan schemas.

Serving the spec

Load docs/openapi.yaml into Swagger UI, Redoc, or any OpenAPI 3.0.3 tooling for local development β€” its servers entry points to http://localhost:3000.

For production deployments, use docs/openapi.production.yaml instead. It is identical to the root spec except the servers entry points to the production Vercel URL. This file is gitignored to avoid exposing live infrastructure endpoints in the open source repository.


🧩 Key Patterns

  • Server Components by default β€” client boundaries ("use client") only where interactivity is required
  • REST route handlers for mutations, Server Actions for plain forms β€” keeps complex validation/authorization logic out of form submission plumbing
  • Zod everywhere β€” every write path runs schema.safeParse() before touching the database; errors flow through a centralized lib/api-error.ts
  • TanStack Query for server state, Zustand strictly for UI state β€” sidebars/modals never touch API data, and vice versa
  • Pusher channel convention β€” private-thread-[id] for conversations, private-user-[id] for personal notifications
  • Prisma singleton + @prisma/adapter-pg β€” connection reuse across serverless invocations
  • Shared notification utility β€” lib/notifications.ts performs the DB write and Pusher trigger atomically from a single call site
  • Rate limiting as infrastructure, not an afterthought β€” unified lib/rate-limiting/ module with configurable sliding-window limiter, OpenTelemetry metrics, batched cleanup, per-endpoint fail strategy, role-aware multipliers (Γ—0.3 anonymous β†’ Γ—10 super_admin), and admin metrics endpoint
  • Upload provider abstraction β€” UPLOAD_PROVIDER env var selects local or vercel-blob; the provider registry in lib/upload.ts dispatches saveUpload/deleteUpload to the correct implementation with zero code changes when switching
  • CI quality gates as code β€” typecheck, lint, format, contract tests, performance baseline regression, and mutation testing (currently disabled) are all defined in .github/workflows/test.yml with supporting scripts in scripts/ and config in vitest.config.ts/stryker.config.json

☁️ Deployment

Deployed on Vercel.

Build command (from package.json):

npx prisma migrate deploy && next build

Required environment variables in production: DATABASE_URL, BETTER_AUTH_SECRET, BETTER_AUTH_URL, NEXT_PUBLIC_APP_URL, GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, RESEND_API_KEY, EMAIL_FROM, PROMOTE_TO_SUPER_ADMINS, PUSHER_APP_ID, PUSHER_KEY, PUSHER_SECRET, PUSHER_CLUSTER, NEXT_PUBLIC_PUSHER_KEY, NEXT_PUBLIC_PUSHER_CLUSTER, AI_PROVIDER, and the corresponding AI provider key/model (e.g. ANTHROPIC_API_KEY, ANTHROPIC_MODEL), UPLOAD_PROVIDER, BLOB_READ_WRITE_TOKEN, NEXT_PUBLIC_BLOB_CDN_HOST, NEXT_PUBLIC_LINKEDIN_URL, NEXT_PUBLIC_TWITTER_URL, NEXT_PUBLIC_GITHUB_URL, and NEXT_PUBLIC_CONTACT_EMAIL.


πŸ§ͺ Testing

Hire Flow uses a layered testing strategy matched to each layer of the stack:

Layer Tool What it covers
Unit Vitest Pure logic: rate limiter, CSV builder, pagination, Zod schemas, AI client fallback
Contract Vitest (contract project) Shared behavioral guarantees for repository implementations (fake + Prisma)
Integration Vitest + local Postgres test DB API route handlers called directly, tenant isolation, transactions, audit trails
Performance Vitest (perf project) + local Postgres Scale budgets: analytics (PF1), applicant listing (PF2), CSV export (PF3), public job FTS search (PF4), resume AI-enhance quota race (PF5)
Component React Testing Library Data table selection, bulk action logic, forms, AI suggestions panel
Mutation Stryker Mutator + Vitest (disabled) Surviving mutants across lib/rate-limiting/ and shared APIs
End-to-End Playwright Full role-based journeys (anonymous β†’ user β†’ recruiter β†’ admin) with real Better Auth sessions

External services (Pusher, AI providers, Resend, Vercel Blob) are mocked at the module level β€” tests never make real network calls or incur API costs.

Test infrastructure (Phases 0–7)

Tests are built incrementally per the testing strategy. Suites are colocated next to source as *.test.ts (unit), *.test.ts (integration, real Postgres), *.perf.test.ts (perf project), *.dom.test.tsx (component), and *.contract.test.ts (contract).

Phase Focus Covers
0 Test infrastructure vitest.config.ts (react + tsconfig-paths plugins, default/dom/contract/perf projects), lib/test/test-db.ts (isolated Prisma client), lib/test/reset-db.ts (RESTART IDENTITY CASCADE truncation in dependency order), lib/test/factories.ts (createTestUser/Company/Job/Application/Resume/Thread), lib/test/auth-fixtures.ts (mockSession), lib/test/mocks.ts (mockPusherTrigger/mockAiClient/mockResend), global-setup.ts returns vitest v3 teardown for perf result publishing
1 Input validation & schema hardening SQL-injection rejection for raw analytics query params (Zod UUID/ISO dates); edge cases for profile/resume/application-submit/job/auth/admin schemas; mass-assignment (over-posting) prevention on role/patch/apply routes
2 Unit tests: pure logic lib/rate-limiter.ts, csv-builder.ts (RFC 4180 escaping), lib/pagination.ts, api-error/api-response, lib/routes.ts, lib/job-categories.ts, rate-limit-message.ts, ai-client.ts; Zod schema tests; require-role.ts, validator.ts, presence-store.ts, applicant-table-utils.ts
3 Auth & authorization Session/token security (expired/malformed/missing β†’ 401, cross-role β†’ 403); IDOR protection for every resource (application, job, resume, profile, thread, message, notification, bookmark, admin actions); middleware redirect matrix
4 Integration: API routes + real DB All 17 priority route groups (tenant isolation, public-job gate, apply, status/bulk/revert, resume CRUD, ai-enhance rate limit, messages, bookmarks, export, ban/sessions, withdraw, files/download, analytics, notifications, role PATCH, upload/download with cloud storage); file upload/download edges with local and Vercel Blob providers, notification delivery, search/FTS sanitization, pagination boundaries, audit-trail integrity, error-shape/info-leak, concurrent race conditions
5 Component tests (RTL) data-table, applicants-table, bulk-reject-dialog, status-timeline, resume-builder-form, ai-suggestions-panel, job-search-bar, save-job-button, account-popover, apply-modal, chat components, no-company-prompt
6 End-to-end (Playwright) 10 role-based journeys (anonymous apply redirect, user apply, recruiter pipeline, bulk reject, admin ban, messaging roundtrip, AI enhance, CSV export, cross-role access, IDOR deep links) across anonymous/user/recruiter/admin storage states
7 CI quality gates Typecheck (tsc --noEmit), ESLint, Prettier format check, contract tests (*.contract.test.ts), performance baseline regression (benchmark/baseline.json + compare-benchmark.ts), mutation testing (Stryker with vitest-runner) οΏ½ currently disabled in CI

Performance & stability tests (Phase 7)

The perf Vitest project runs tests matching *.perf.test.ts against a real Postgres hireflow_test database (no data-layer mocks) with a 300s timeout. Helpers live in lib/test/perf.ts (measure, assertWithin, assertMemoryWithin, publishBenchmark, writeBenchmarkResults) and seed factories in lib/test/factories/seed-factories.ts. Vitest v3 globalSetup returns a teardown function that publishes results to benchmark/results/${sha}.json.

Test File What it asserts
PF1 app/features/recruiter/queries/analytics-queries.perf.test.ts Analytics aggregation over a large dataset within budget
PF2 app/features/recruiter/queries/application-queries.perf.test.ts Applicant listing/pagination at scale
PF3 app/features/recruiter/queries/export-queries.perf.test.ts CSV export throughput
PF4 app/features/jobs/queries/public-job-queries.perf.test.ts Public job FTS search (search:"engineer") ≀ 1000ms using GIN indexes
PF5 app/api/user/resumes/[id]/ai-enhance/route.perf.test.ts Atomic daily quota (limit 5) survives concurrent requests β€” exactly 5 succeed
RL1–RL5 lib/test/unit/rate-limit.test.ts Rate limiter: restart persistence, concurrent windows, max:0 guard, etc.

Supporting infra: Application indexes (@@index([appliedAt]), @@index([jobId, appliedAt])) and GIN FTS indexes on Job.title/Job.description; a ResumeEnhancementQuota table backing PF5's atomic UPDATE … WHERE used < $3 RETURNING.

Contract tests (Phase 7)

The contract Vitest project runs *.contract.test.ts files to enforce shared behavioral guarantees across implementation variants:

Test File What it asserts
Repo lib/test/unit/rate-limiting/contract/repository-contract.ts FakeRepository and PrismaRateLimitRepository behave identically
Prisma lib/test/unit/rate-limiting/repository.prisma.contract.test.ts Prisma-backed repository satisfies the contract against a real Postgres DB

Mutation testing (Phase 7)

Stryker Mutator (currently disabled) runs on master push using vitest-runner against the default and contract projects. Thresholds: high: 80, low: 60, break: null. Mutants are generated from lib/rate-limiting/**/*.ts excluding tests, types, config, and metrics. Reports are written to reports/mutation/.

Running tests

vitest.config.ts defines four projects: default (30s timeout β€” unit + integration), dom (5s timeout β€” component/RTL tests), contract (5s timeout β€” *.contract.test.ts), and perf (300s timeout β€” *.perf.test.ts against a real Postgres test DB). By default npm run test runs all three; use --project to target a specific one.

# One-time: create local test database
createdb hireflow_test

# Add to .env.test
DATABASE_URL_TEST="postgresql://<user>:<password>@localhost:5432/hireflow_test"

# All projects (default + dom + contract + perf)
npm run test
npm run test:watch
npm run test:coverage

# Component/DOM tests only
npx vitest run --project dom

# Contract tests only
npx vitest run --project contract

# Performance/safety project only (real Postgres, long timeout)
npx vitest run --project perf

# Specific combination
npx vitest run --project default --project perf

# Apply migrations to the test database before the first run (global-setup also does this)
npx prisma migrate deploy --schema prisma/schema.prisma

# End-to-end (spins up a production build automatically)
npm run test:e2e
npm run test:e2e:ui   # interactive mode

CI quality gates are defined in .github/workflows/test.yml: typecheck, lint, format check, unit/integration/contract with coverage, E2E, performance with baseline regression (scripts/compare-benchmark.ts). Mutation testing (Stryker) is currently disabled in CI. Performance results are published to benchmark/results/${sha}.json by lib/test/perf-teardown.ts via vitest v3 global setup teardown.

Coverage thresholds are enforced via vitest.config.ts (currently lines: 22, functions: 54, statements: 22, branches: 67) and ratchet upward as suites mature.


πŸ“„ License

This project is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International (CC BY-NC-ND 4.0).

  • View and Study β€” free to use for personal learning, education, and non-commercial portfolio reference.
  • Attribution β€” you must give appropriate credit to Mohamed Hazeem, provide a link to the original repository, and indicate if changes were made.
  • NonCommercial β€” you may not use the material for commercial purposes.
  • NoDerivatives β€” you may not distribute modified versions of the material.
  • No Misattribution β€” you must not claim authorship of the original work or represent modified material as the original creation of Mohamed Hazeem.

For commercial licensing inquiries, contact a.mohamedhazeem@gmail.com.

πŸ“¬ Contact

Built by Mohamed Hazeem β€” reach out via a.mohamedhazeem@gmail.com or GitHub.

About

Hire Flow is a production-grade job board and applicant tracking system (ATS) supporting three distinct user roles - Admins, Recruiters, and Job Seekers - with real-time messaging, AI-powered resume assistance, and a public-facing job marketplace with SEO built in from the ground up.

Topics

Resources

Stars

3 stars

Watchers

0 watching

Forks

Contributors

Languages