Skip to content

yb175/CVPilot

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

53 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

CVPilot β€” AI-Powered Resume-to-Job Matching Platform

TypeScript React Node.js PostgreSQL Prisma License: Proprietary

A precision tool for ambitious job seekers. Use neural-matching to bypass the noise and land high-stakes opportunities.

πŸ“‹ Table of Contents


🎯 Project Overview

CVPilot is an intelligent resume-to-job matching platform that helps job seekers discover and apply to the most relevant opportunities. Using advanced AI and natural language processing, CVPilot:

  • Analyzes your resume to extract skills, experience, and career profile
  • Matches you with live job listings from the Active Jobs database
  • Scores jobs intelligently (0-100%) based on skill alignment and requirements
  • Generates ATS optimization tips tailored to specific job descriptions
  • Tracks preferences (seniority level, location type) for personalized recommendations

Use Case Flow

User Signs Up (Clerk Auth)
        ↓
Upload Resume (PDF/DOCX)
        ↓
AI Parses Resume (Google Gemini)
        ↓
Set Job Preferences (Location, Seniority)
        ↓
Browse Jobs (Active Jobs API)
        ↓
AI Matches Resume vs Job (LLM Scoring)
        ↓
View Ranked Results (Score + Confidence)
        ↓
Get ATS Optimization Tips
        ↓
Apply with Confidence

✨ Key Features

For Job Seekers

Feature Description Status
Smart Resume Upload Upload PDF/DOCX files; AI extracts structure automatically βœ… Production
AI Resume Parsing Google Gemini extracts skills, experience, seniority level βœ… Production
Job Matching LLM-based scoring matches your profile vs job requirements βœ… Production (V2)
Match Confidence See not just scores but confidence levels (0-1) βœ… Production
Preference Management Set seniority & location preferences for personalized jobs βœ… Production
ATS Optimization Get resume tweaks to pass ATS scanners for specific jobs βœ… Production
Job Tracking View your matched jobs and scores in one dashboard βœ… Production

For Developers

Feature Description
Type-Safe APIs Full TypeScript throughout frontend & backend
Clean Architecture Layered design (controller β†’ service β†’ DB)
Comprehensive Docs API docs, setup guides, architecture explanations
Testing Framework Unit tests for services, integration tests for routes
Error Handling Centralized error middleware with correlation IDs
Structured Logging JSON-formatted logs with context (userId, duration, etc.)
CI/CD Ready Pipeline-friendly build steps and database migrations

πŸ› οΈ Tech Stack

Frontend

React 19                    - Component library with hooks, server components
TypeScript 5.9              - Static type checking for frontend code
Vite 8                      - Ultra-fast build tool with HMR
Tailwind CSS 3.4            - Utility-first styling framework
React Router DOM 7          - Client-side routing and navigation
Clerk @clerk/react          - Authentication (sign-up, login, sessions)
pdfjs-dist 5.6              - In-browser PDF rendering
Mammoth 1.12                - Word document (.docx) conversion

Backend

Node.js 18+                 - JavaScript runtime
Express 5.2                 - Web framework and HTTP routing
TypeScript 6                - Type-safe backend code
PostgreSQL 13+              - Relational database
Prisma 7.7                  - Type-safe ORM with migrations
@clerk/express 2.1          - Backend auth middleware for Clerk
@google/generative-ai 0.21  - Google Gemini API for AI features
Cloudinary 2.9              - CDN file storage for resumes
Zod 4.3                     - Runtime input validation
TSX 4.21                    - TypeScript execution for dev

DevOps & Tools

pnpm 8+                     - Fast, disk-efficient package manager
Prisma CLI                  - Database schema & migration tools
tsx watch                   - Hot-reload for backend development
ESLint + Prettier           - Code quality & formatting
Docker                      - Container deployment (optional)
PM2                         - Production process manager (optional)

πŸ—οΈ Architecture

High-Level Diagram

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                        FRONTEND (React)                         β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”          β”‚
β”‚  β”‚ Landing Page β”‚  β”‚ Profile Page β”‚  β”‚  Job Page    β”‚          β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜          β”‚
β”‚         ↓                ↓                     ↓                 β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”           β”‚
β”‚  β”‚   Clerk Authentication (OAuth/Email/SSO)       β”‚           β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜           β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                              ↕ (HTTP/JSON)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                     BACKEND (Express.js)                        β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚  API Routes: /resume, /jobs, /auth, /preferences         β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚  Controllers: HTTP adapters (validation, responses)      β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚  Services: Business logic (matching, parsing, storage)   β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚  Middleware: Auth (Clerk), validation (Zod), errors      β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€οΏ½οΏ½β”€β”˜  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
      ↓                      ↓                        ↓
   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
   β”‚ PostgreSQL  β”‚  β”‚  Cloudinary CDN  β”‚  β”‚  Google Gemini    β”‚
   β”‚  (Prisma)   β”‚  β”‚  (File Storage)  β”‚  β”‚  (AI Scoring)     β”‚
   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
      ↓
   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
   β”‚  Active Jobs API (Job Listings)     β”‚
   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Layered Backend Architecture

REQUEST FLOW:
─────────────────────────────────────────────────────────────

HTTP Request
    ↓
MIDDLEWARE LAYER
  β”œβ”€ Body parsing (JSON)
  β”œβ”€ CORS validation
  β”œβ”€ Clerk JWT verification
  β”œβ”€ Input validation (Zod)
  └─ Error handling
    ↓
ROUTE LAYER
  └─ Route matching & HTTP method handling
    ↓
CONTROLLER LAYER
  β”œβ”€ Extract request parameters
  β”œβ”€ Call service functions
  └─ Format HTTP response
    ↓
SERVICE LAYER (Business Logic)
  β”œβ”€ Orchestration (multi-step workflows)
  β”œβ”€ Domain rules & validation
  β”œβ”€ LLM calls (Gemini API)
  β”œβ”€ Job matching algorithms
  └─ Transaction management
    ↓
LIB LAYER (Singletons)
  β”œβ”€ Prisma client instance
  β”œβ”€ Logger factory
  β”œβ”€ Cloud storage client
  └─ Configuration loaders
    ↓
DATABASE & EXTERNAL SERVICES
  β”œβ”€ PostgreSQL (Prisma ORM)
  β”œβ”€ Cloudinary (file storage)
  β”œβ”€ Google Gemini (AI)
  └─ Active Jobs API (job listings)
    ↓
SERVICE LAYER (responses flow back)
    ↓
CONTROLLER LAYER (transforms)
    ↓
HTTP RESPONSE (JSON)

Backend Folder Structure

backend/
β”œβ”€β”€ server.ts                    # Express app entry point
β”œβ”€β”€ package.json                 # Dependencies
β”œβ”€β”€ tsconfig.json                # TypeScript config
β”‚
β”œβ”€β”€ config/                      # Configuration
β”‚   └── environment validation
β”‚
β”œβ”€β”€ route/                       # Route definitions
β”‚   β”œβ”€β”€ auth.route.ts
β”‚   β”œβ”€β”€ resume.route.ts
β”‚   β”œβ”€β”€ jobs.route.ts
β”‚   └── preferences.route.ts
β”‚
β”œβ”€β”€ controller/                  # HTTP handlers
β”‚   β”œβ”€β”€ authController.ts
β”‚   β”œβ”€β”€ resumeController.ts
β”‚   β”œβ”€β”€ jobsController.ts
β”‚   └── preferencesController.ts
β”‚
β”œβ”€β”€ middleware/                  # Express middleware
β”‚   β”œβ”€β”€ auth.middleware.ts       # Clerk validation
β”‚   β”œβ”€β”€ validation.middleware.ts # Zod schemas
β”‚   β”œβ”€β”€ errorHandler.middleware.ts
β”‚   └── upload.middleware.ts     # Multer for files
β”‚
β”œβ”€β”€ service/                     # Business logic
β”‚   β”œβ”€β”€ resume/
β”‚   β”‚   β”œβ”€β”€ resumeService.ts
β”‚   β”‚   β”œβ”€β”€ fileDownloader.ts
β”‚   β”‚   └── parser.ts
β”‚   β”‚
β”‚   β”œβ”€β”€ jobs/
β”‚   β”‚   β”œβ”€β”€ jobsService.ts
β”‚   β”‚   β”œβ”€β”€ llmJobMatcherV2.ts   # AI scoring
β”‚   β”‚   β”œβ”€β”€ providers/
β”‚   β”‚   β”‚   └── activeJobs.provider.ts
β”‚   β”‚   β”œβ”€β”€ matchCache.ts        # In-memory cache
β”‚   β”‚   └── jobMatch.service.ts
β”‚   β”‚
β”‚   β”œβ”€β”€ preferences/
β”‚   β”‚   └── preferencesService.ts
β”‚   β”‚
β”‚   β”œβ”€β”€ user/
β”‚   β”‚   └── userService.ts
β”‚   β”‚
β”‚   └── geminiClient.ts          # Google AI setup
β”‚
β”œβ”€β”€ lib/                         # Shared utilities
β”‚   β”œβ”€β”€ prisma.ts                # Singleton Prisma
β”‚   β”œβ”€β”€ logger.ts                # Logging utility
β”‚   β”œβ”€β”€ cloudinary.ts            # File storage
β”‚   └── validators.ts            # Zod schemas
β”‚
β”œβ”€β”€ prisma/                      # Database
β”‚   β”œβ”€β”€ schema.prisma            # DB schema
β”‚   β”œβ”€β”€ migrations/              # Version-controlled migrations
β”‚   └── seed.ts                  # Seed data
β”‚
β”œβ”€β”€ scripts/                     # Developer helpers
β”‚   └── fix-prisma-imports.mjs
β”‚
β”œβ”€β”€ tests/                       # Test suite
β”‚   β”œβ”€β”€ jobs.test.ts
β”‚   β”œβ”€β”€ greenhouse.provider.test.ts
β”‚   └── active-jobs.real-api.test.ts
β”‚
β”œβ”€β”€ docs/                        # Documentation
β”‚   β”œβ”€β”€ IMPLEMENTATION_CHECKLIST.md
β”‚   β”œβ”€β”€ V2_MIGRATION_GUIDE.md
β”‚   β”œβ”€β”€ JOB_MATCHING_ROOT_CAUSE_ANALYSIS.md
β”‚   └── EXECUTIVE_SUMMARY.md
β”‚
└── README.md                    # Backend setup & architecture
   DOCUMENTATION.md             # Complete API reference

Key Design Patterns

1. Separation of Concerns

  • Controllers handle HTTP only (request validation, response formatting)
  • Services contain business logic (no HTTP knowledge)
  • Middleware handles cross-cutting concerns (auth, logging, errors)
  • Lib provides singletons (Prisma, logger, config)

2. Error Handling Strategy

// ❌ BAD: Errors leak stack traces to client
res.status(500).json({ error: err.stack });

// βœ… GOOD: User-friendly messages with logging
logger.error("OPERATION_FAILED", {
  userId,
  error: err.message,
  stack: err.stack,
  requestId: req.id,
});
res.status(500).json({
  error: "Operation failed",
  requestId: req.id,  // For support debugging
});

3. Input Validation with Zod

import { z } from "zod";

const createPreferencesSchema = z.object({
  seniority: z.enum(["INTERN", "FULLTIME"]),
  locationPreferences: z
    .array(z.enum(["ONSITE", "HYBRID", "REMOTE"]))
    .min(1),
});

// TypeScript knows the shape after validation
const validated = createPreferencesSchema.parse(req.body);
// βœ… validated is strictly typed

4. Caching Strategy

// In-memory LRU cache for job matches
const cache = new MatchCache(maxEntries: 10000);

export async function matchJobs(userId, resume) {
  const cacheKey = `${userId}:${resumeHash}`;
  
  // Check cache first
  const cached = cache.get(userId, jobId);
  if (cached) return cached;  // Cache hit!
  
  // Cache miss: run expensive LLM pipeline
  const result = await runLLMMatching(resume, jobs);
  
  // Store in cache
  cache.set(userId, jobId, result);
  
  return result;
}

πŸš€ Getting Started

Prerequisites

Before you begin, ensure you have:

  • Node.js v18 or higher (Download)
  • pnpm v8 or higher (npm install -g pnpm)
  • PostgreSQL 13+ (Download)
  • Git for version control

1. Clone the Repository

git clone https://github.com/yb175/CVPilot.git
cd CVPilot

2. Install Dependencies

# Install both backend & frontend dependencies (monorepo)
pnpm install

3. Backend Setup

cd backend

# Copy environment template
cp .env.sample .env

# ⚠️ IMPORTANT: Edit .env with your credentials
# Required variables:
# - DATABASE_URL=postgresql://user:password@localhost:5432/cvpilot
# - CLERK_SECRET_KEY=sk_test_...
# - CLERK_PUBLISHABLE_KEY=pk_test_...
# - GOOGLE_GEMINI_API_KEY=AIzaSy...
# - CLOUDINARY_CLOUD_NAME=...
# - CLOUDINARY_API_KEY=...
# - CLOUDINARY_API_SECRET=...
# - RAPIDAPI_KEY=...

# Generate Prisma client
pnpm prisma generate

# Apply database migrations
pnpm prisma migrate dev --name init

# Verify database setup
pnpm prisma studio

cd ..

4. Frontend Setup

cd frontend

# Copy environment template
cp .env.sample .env

# ⚠️ IMPORTANT: Edit .env with your credentials
# Required variables:
# - VITE_CLERK_PUBLISHABLE_KEY=pk_test_...
# - VITE_API_URL=http://localhost:3000

cd ..

5. Start Development Servers

Open two terminal windows:

Terminal 1: Backend Server

cd backend
pnpm run dev

# Output:
# [Server] Gemini client initialized
# Server started on port 3000

Terminal 2: Frontend Dev Server

cd frontend
pnpm run dev

# Output:
# VITE v8.0.1 ready in 256 ms
# ➜ Local: http://localhost:5173/
# ➜ press h to show help

6. Access the Application

7. Verify Everything Works

# Test API health check
curl http://localhost:3000/health

# Expected output: OK

πŸ“ Project Structure

Root Level

CVPilot/
β”œβ”€β”€ backend/                     # Node.js API server
β”‚   β”œβ”€β”€ README.md               # Backend-specific docs
β”‚   β”œβ”€β”€ DOCUMENTATION.md        # Complete API reference
β”‚   β”œβ”€β”€ server.ts               # Express app entry
β”‚   β”œβ”€β”€ package.json
β”‚   β”œβ”€β”€ tsconfig.json
β”‚   β”œβ”€β”€ src/
β”‚   β”‚   β”œβ”€β”€ controller/
β”‚   β”‚   β”œβ”€β”€ service/
β”‚   β”‚   β”œβ”€β”€ route/
β”‚   β”‚   β”œβ”€β”€ middleware/
β”‚   β”‚   β”œβ”€β”€ lib/
β”‚   β”‚   └── tests/
β”‚   β”œβ”€β”€ prisma/
β”‚   β”‚   β”œβ”€β”€ schema.prisma
β”‚   β”‚   └── migrations/
β”‚   └── docs/
β”‚
β”œβ”€β”€ frontend/                    # React SPA
β”‚   β”œβ”€β”€ README.md               # Frontend-specific docs
β”‚   β”œβ”€β”€ src/
β”‚   β”‚   β”œβ”€β”€ main.tsx            # App entry point
β”‚   β”‚   β”œβ”€β”€ App.tsx             # Root component
β”‚   β”‚   β”œβ”€β”€ pages/              # Route pages
β”‚   β”‚   β”œβ”€β”€ components/         # Reusable components
β”‚   β”‚   β”œβ”€β”€ services/           # API utilities
β”‚   β”‚   β”œβ”€β”€ lib/                # Helpers
β”‚   β”‚   β”œβ”€β”€ styles/             # Global CSS
β”‚   β”‚   └── types/              # TypeScript types
β”‚   β”œβ”€β”€ index.html
β”‚   β”œβ”€β”€ package.json
β”‚   β”œβ”€β”€ tsconfig.json
β”‚   └── vite.config.ts
β”‚
β”œβ”€β”€ package.json                # Root monorepo config
β”œβ”€β”€ pnpm-workspace.yaml         # pnpm monorepo setup
β”œβ”€β”€ README.md                   # This file
β”œβ”€β”€ LICENSE                     # Proprietary license
└── .gitignore

Key Directories Explained

backend/src/

  • controllers/ β€” HTTP request handlers (thin adapters)
  • services/ β€” Business logic & orchestration
  • routes/ β€” API endpoint definitions
  • middleware/ β€” Auth, validation, error handling
  • lib/ β€” Shared utilities (Prisma, logger, config)
  • tests/ β€” Unit and integration tests

frontend/src/

  • pages/ β€” Full-page components (routed)
  • components/ β€” Reusable UI components
  • services/ β€” API calls & data fetching
  • lib/ β€” Helper functions, utilities
  • types/ β€” TypeScript interfaces

πŸ”§ Development Workflow

Making Changes

Backend Changes

cd backend

# Make code changes in src/

# TypeScript will compile automatically (tsx watch)
# Errors show in terminal immediately

# Tests (manual check)
pnpm run test

# Build for production
pnpm build
# Output: dist/server.js

Frontend Changes

cd frontend

# Make code changes in src/

# Vite auto-reloads (HMR)
# Changes appear instantly in browser
# Type errors show in terminal

# Build for production
pnpm build
# Output: dist/ (static files)

Database Changes

cd backend

# 1. Edit schema.prisma
# 2. Create migration
pnpm prisma migrate dev --name <migration_name>

# Example:
# pnpm prisma migrate dev --name add_user_verified_field

# This creates:
# - prisma/migrations/<timestamp>_<name>/migration.sql
# - Updates generated Prisma client

# 3. Commit to Git
git add prisma/migrations/
git commit -m "feat: add user verified field"

Running Tests

cd backend

# Run all tests
pnpm run test:all

# Run specific test suite
pnpm run test              # Job service tests
pnpm run test:provider     # ATS provider tests
pnpm run test:active-jobs  # Real API tests

Code Quality Checks

# Type checking (both packages)
pnpm run type-check

# Linting (when configured)
pnpm run lint

# Formatting (when configured)
pnpm run format

🀝 Contributing

We welcome contributions from the community! Here's how to get involved:

Contribution Types

  1. Bug Fixes β€” Fix existing issues and improve stability
  2. Features β€” Add new functionality aligned with roadmap
  3. Documentation β€” Improve READMEs, docs, and comments
  4. Performance β€” Optimize latency, database queries, caching
  5. Tests β€” Increase code coverage and reliability

Before You Start

Step-by-Step Guide

1. Fork & Clone

# Fork the repo on GitHub
# Then clone your fork
git clone https://github.com/YOUR_USERNAME/CVPilot.git
cd CVPilot

# Add upstream remote for syncing
git remote add upstream https://github.com/yb175/CVPilot.git

2. Create a Feature Branch

# Sync with latest upstream
git fetch upstream
git checkout upstream/main

# Create feature branch
git checkout -b feature/my-feature-name

# Example branch names:
# - feature/resume-caching
# - fix/job-matching-score-bug
# - docs/api-reference
# - perf/optimize-db-queries

3. Make Your Changes

Follow the architecture patterns:

// Example: Adding a new service feature

// 1. Define Zod schema for inputs
// backend/lib/validators.ts
export const matchJobsSchema = z.object({
  resumeHash: z.string(),
  filters: z.object({
    minScore: z.number().min(0).max(100),
  }).optional(),
});

// 2. Create service function
// backend/service/jobs/matchingService.ts
export async function matchJobsImproved(
  userId: number,
  resume: ParsedResume,
  filters?: MatchFilters
) {
  // Validate inputs
  const validated = matchJobsSchema.parse({ resumeHash, filters });
  
  // Core logic
  const scores = await runLLMMatching(resume);
  
  // Filter
  const filtered = scores.filter(s => s.score >= (filters?.minScore ?? 0));
  
  // Persist
  await persistMatchResults(userId, filtered);
  
  return filtered;
}

// 3. Create controller handler
// backend/controller/jobsController.ts
export async function matchJobsHandler(req: Request, res: Response) {
  const clerkUserId = req.auth?.userId;
  const filters = req.body.filters;
  
  const resume = await getResume(clerkUserId);
  const results = await matchJobsImproved(userId, resume, filters);
  
  res.json({
    success: true,
    count: results.length,
    results,
  });
}

// 4. Wire up route
// backend/route/jobsRoute.ts
router.post("/match", matchJobsHandler);

// 5. Add tests
// backend/tests/matching.test.ts
async function testMatchingImproved() {
  const results = await matchJobsImproved(userId, resume, { minScore: 80 });
  console.assert(results.every(r => r.score >= 80), "All scores >= 80");
}

4. Write/Update Tests

// backend/tests/my-feature.test.ts

import prisma from "../../lib/prisma.js";
import { myNewFunction } from "../../service/myNewService.js";

async function testMyFeature() {
  console.log("πŸ§ͺ Testing My Feature...\n");

  try {
    // Setup
    const testUser = await prisma.user.create({
      data: {
        clerkId: `test_${Date.now()}`,
        email: `test_${Date.now()}@example.com`,
      },
    });

    // Test
    const result = await myNewFunction(testUser.id);
    console.assert(result !== null, "Result should not be null");
    console.assert(result.success === true, "Result should indicate success");

    // Cleanup
    await prisma.user.delete({ where: { id: testUser.id } });

    console.log("βœ… Test passed!");
  } catch (error) {
    console.error("❌ Test failed:", error);
  } finally {
    await prisma.$disconnect();
  }
}

testMyFeature();

Run tests before committing:

pnpm run test:all

5. Commit with Clear Messages

# Use conventional commit format
git add .
git commit -m "feat(jobs): add minimum score filter to matching

- Add matchJobsImproved service function
- Support optional minScore filter
- Add validation with Zod schema
- Include tests for new functionality
- Resolves #123"

# Examples:
# feat(resume): add resume caching to reduce latency
# fix(matching): filter out jobs with missing IDs
# docs(api): update job matching endpoint documentation
# perf(db): add index to job_matches table
# test(service): add unit tests for resume parser

6. Push & Create Pull Request

# Push your feature branch
git push origin feature/my-feature-name

# Create PR on GitHub
# Title: Clear, descriptive
# Description: Explain what changed and why
# Link related issues: "Closes #123"

PR Description Template:

## Description
Brief explanation of what this PR does.

## Changes
- Change 1
- Change 2
- Change 3

## Testing
How to test these changes:
1. Step 1
2. Step 2

## Related Issues
Closes #123
Fixes #456

## Checklist
- [ ] Code follows project architecture patterns
- [ ] Tests added/updated
- [ ] Documentation updated
- [ ] No breaking changes
- [ ] TypeScript compiles without errors

7. Code Review

  • Respond to feedback promptly
  • Make requested changes in follow-up commits
  • Once approved, maintainers will merge

Coding Standards

TypeScript

// βœ… DO: Use strict typing
async function getUserWithResume(userId: number): Promise<UserWithResume> {
  return await prisma.user.findUnique({
    where: { id: userId },
    include: { resume: true },
  });
}

// ❌ DON'T: Avoid 'any'
async function getUser(userId: any) {
  return await prisma.user.findUnique({
    where: { id: userId },
  });
}

// βœ… DO: Use enums for fixed values
enum Seniority {
  INTERN = "INTERN",
  FULLTIME = "FULLTIME",
}

// ❌ DON'T: Use string literals everywhere
function setSeniority(seniority: "INTERN" | "FULLTIME") {
  // ...
}

Error Handling

// βœ… DO: Handle and log errors appropriately
try {
  const result = await runExpensiveOperation();
  return result;
} catch (error) {
  logger.error("OPERATION_FAILED", {
    userId,
    error: error instanceof Error ? error.message : String(error),
  });
  throw new HttpError(500, "Operation failed");
}

// ❌ DON'T: Let errors propagate silently
try {
  await runExpensiveOperation();
} catch (error) {
  // Silently swallow error
}

Naming Conventions

// βœ… DO: Use clear, descriptive names
const jobMatchScore = 87;
const getUserJobMatches = (userId: number) => {};
const MAXIMUM_CACHE_ENTRIES = 10000;

// ❌ DON'T: Use single-letter or cryptic names
const jms = 87;
const gu = (u: number) => {};
const MAX_CACHE = 10000;

Project-Specific Guidelines

Backend Changes

  • Place business logic in service layer
  • Keep controllers thin (validation + response formatting)
  • Use Prisma for all database access (no raw SQL)
  • Add tests for service functions
  • Update DOCUMENTATION.md if API changes

Frontend Changes

  • Use React hooks (no class components)
  • Keep components small and focused
  • Use TypeScript for all prop types
  • Use Tailwind for styling (no custom CSS)
  • Test with Clerk auth before committing

Database Changes

  • Always create migrations (never modify production DB directly)
  • Migrations must be idempotent (safe to run multiple times)
  • Test migrations locally before pushing
  • Commit migration files to version control

πŸ› Troubleshooting

Common Setup Issues

Issue: DATABASE_URL is not set

Solution:

cd backend
cp .env.sample .env
# Edit .env and add your DATABASE_URL

Issue: Port 3000 already in use

Solution:

# Change port in backend/.env
PORT=3001

# Or kill existing process on port 3000
lsof -i :3000
kill -9 <PID>

Issue: Prisma client not generated

Solution:

cd backend
pnpm prisma generate

Issue: Migrations failed

Solution:

cd backend

# Reset database (dev only!)
pnpm prisma migrate reset

# Then reapply migrations
pnpm prisma migrate dev

Development Issues

Issue: Frontend can't reach backend API

Solution:

# Check backend is running
curl http://localhost:3000/health

# If not, start backend:
cd backend && pnpm run dev

# Verify FRONTEND_URL in backend/.env
FRONTEND_URL=http://localhost:5173

Issue: Changes not reflecting after save

Solution:

# Backend: Restart tsx watch
# Terminal 1: Ctrl+C
# Terminal 1: pnpm run dev

# Frontend: Clear browser cache
# Hard refresh: Cmd+Shift+R (Mac) or Ctrl+Shift+R (Windows)

Testing Issues

Issue: Tests fail with "Database error"

Solution:

cd backend

# Ensure test database exists
createdb cvpilot_test

# Or use docker
docker run -d \
  --name postgres-test \
  -e POSTGRES_PASSWORD=postgres \
  -p 5433:5432 \
  postgres:15

# Set DATABASE_URL to test DB
export DATABASE_URL="postgresql://postgres:postgres@localhost:5433/cvpilot_test"

# Run tests
pnpm run test

Performance Issues

Issue: Job matching takes >5 seconds

Causes & Solutions:

  1. No caching β†’ Implement Redis cache or in-memory LRU
  2. Cold Gemini API β†’ First call is slower; subsequent calls cached
  3. Large job list β†’ Filter jobs before LLM scoring (use Active Jobs filters)
  4. Slow DB β†’ Check database indexes on job_matches table
# Check database query performance
cd backend
pnpm prisma studio

# Or view logs
DEBUG=prisma:* pnpm run dev

πŸ“š Additional Resources

Documentation

External Resources

Project Links


πŸ“‹ Development Checklist

Before submitting a PR, ensure:

Code Quality

  • TypeScript compiles without errors (pnpm run build)
  • No type assertions with any
  • No unused imports or variables
  • Code follows architecture patterns
  • Error handling is comprehensive
  • Logging includes context (userId, duration, etc.)

Testing

  • Unit tests added for service functions
  • Integration tests added for API endpoints
  • All tests pass (pnpm run test:all)
  • Test coverage for happy & error paths

Documentation

  • Code comments explain complex logic
  • Function JSDoc comments added
  • README updated (if needed)
  • API docs updated (if endpoints changed)
  • Commit messages follow conventional format

Database

  • Migrations created (if schema changed)
  • Migrations tested locally
  • Migration files committed to Git
  • Rollback plan documented (if applicable)

Security

  • No secrets in code or logs
  • Input validation with Zod
  • SQL injection impossible (using Prisma)
  • Auth checks in place
  • CORS properly configured

Performance

  • No N+1 queries (check Prisma includes)
  • API responses under 2 seconds
  • Database queries optimized
  • Unnecessary API calls removed
  • Caching strategy documented

πŸ“„ License

This project is proprietary and confidential. All rights reserved.

Unauthorized copying, modification, distribution, or use of this project is strictly prohibited.

For licensing inquiries, contact: backend@cvpilot.dev


πŸ™ Acknowledgments

CVPilot was built with ❀️ using:

  • Google Gemini API β€” Advanced AI for resume parsing and job matching
  • Clerk β€” Secure authentication infrastructure
  • Cloudinary β€” Reliable file storage and CDN
  • Prisma β€” Type-safe database access
  • React & Vite β€” Modern, fast frontend development
  • Express & Node.js β€” Lightweight, scalable backend

πŸ“ž Support

Have questions or need help?


🌟 Star Us!

If CVPilot helped you, please consider giving us a ⭐ on GitHub!


Made with ❀️ by the CVPilot Team

Last Updated: April 2026 | Version 1.1.0

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages