Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/nayalsaurav/resume-analyzer/llms.txt

Use this file to discover all available pages before exploring further.

Resume Check Karo persists every resume submission in a single PostgreSQL table managed by Prisma ORM. The schema is intentionally minimal: one Resume model captures everything needed to display a user’s history — the job context they provided, the CDN URLs of their uploaded files, and the full AI-generated feedback object stored as a JSON column. This flat design avoids join complexity while remaining fully queryable through Prisma’s type-safe client.

Prisma Schema

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

model Resume {
  id             String   @id @default(uuid())
  userId         String
  companyName    String
  jobName        String
  jobTitle       String
  jobDescription String
  analysis       Json?
  pdfUrl         String
  imageUrl       String
  createdAt      DateTime @default(now())
  updatedAt      DateTime @updatedAt

  @@index([userId])
}

Field Reference

FieldTypeDescription
idString (UUID)Primary key, auto-generated UUID via @default(uuid()). Globally unique and safe to expose in URLs.
userIdStringThe Clerk user ID of the authenticated user who submitted the resume. Indexed for fast per-user lookups.
companyNameStringTarget company name entered by the user at submission time.
jobNameStringInternal job listing name or reference, entered by the user.
jobTitleStringThe formal job title the user is applying for (e.g. “Senior Software Engineer”).
jobDescriptionStringFull job description text provided by the user. Injected verbatim into the Gemini AI prompt.
analysisJson?Nullable JSON column. Stores the full Feedback object returned by Gemini AI after analysis.
pdfUrlStringImageKit CDN URL of the uploaded PDF resume file.
imageUrlStringImageKit CDN URL of the generated resume preview image (used in the dashboard card thumbnail).
createdAtDateTimeTimestamp of record creation, set automatically by @default(now()).
updatedAtDateTimeTimestamp of the most recent mutation, updated automatically by @updatedAt.

The analysis JSON Field

The analysis column stores the complete Feedback object exactly as returned by Google Gemini and parsed from JSON. Because Prisma maps the Json type to unknown on the TypeScript client, the server action casts it to the Feedback interface before passing it to the UI layer.
export interface FeedbackTip {
  type: "good" | "improve";
  tip: string;
  explanation?: string; // present on toneAndStyle, content, structure, skills; absent on ATS
}

export interface FeedbackSection {
  score: number; // 0–100
  tips: FeedbackTip[];
}

export interface Feedback {
  overallScore: number;
  ATS: FeedbackSection;
  toneAndStyle: FeedbackSection;
  content: FeedbackSection;
  skills: FeedbackSection;
  structure: FeedbackSection;
}
A fully populated analysis value looks like this at rest in the database:
{
  "overallScore": 74,
  "ATS": {
    "score": 80,
    "tips": [
      { "type": "good", "tip": "Strong use of action verbs throughout." },
      { "type": "improve", "tip": "Add more keywords from the job description to improve ATS match." }
    ]
  },
  "toneAndStyle": {
    "score": 70,
    "tips": [
      {
        "type": "improve",
        "tip": "Avoid passive voice in the experience section.",
        "explanation": "Passive constructions reduce impact and can lower recruiter engagement."
      }
    ]
  },
  "content": { "score": 75, "tips": [] },
  "structure": { "score": 72, "tips": [] },
  "skills": { "score": 68, "tips": [] }
}
The analysis field is nullable (Json?). This means a Resume record can be created before the AI call completes or if the AI call fails. The UI checks for a null analysis and renders an appropriate empty state rather than crashing.

The userId Index

@@index([userId])
Every page in the dashboard filters resumes by the authenticated user’s Clerk ID. Without an index, PostgreSQL would perform a full sequential scan of the Resume table on every page load — fine with ten rows, unacceptable at scale. The @@index([userId]) directive creates a B-tree index on the userId column so Prisma queries like the one below resolve in sub-millisecond time regardless of total table size:
const resumes = await prisma.resume.findMany({
  where: { userId: clerkUserId },
  orderBy: { createdAt: "desc" },
});
Clerk user IDs are stable string identifiers (e.g. user_2abc...). They never change for a given account, so the index remains valid even if a user updates their email or other profile details.

Running Migrations

After cloning the repository, apply the schema to your PostgreSQL database and regenerate the Prisma client:
1

Apply the initial migration

npx prisma migrate dev --name init
This creates the Resume table, its columns, and the userId index in your development database. Prisma also auto-generates the typed client after a successful migration.
2

Regenerate the Prisma client (schema changes only)

If you modify prisma/schema.prisma without adding a migration (e.g. after pulling changes), regenerate the client manually:
npx prisma generate
3

Inspect the database (optional)

Open Prisma Studio to browse records visually:
npx prisma studio
Always run npx prisma migrate dev (not db push) in development so migration history is tracked in prisma/migrations/. Use npx prisma migrate deploy in production CI/CD pipelines — it applies pending migrations without prompting interactively.

Querying Patterns

The server action and dashboard page use three primary query patterns against the Resume model:
await prisma.resume.create({
  data: {
    userId,
    companyName,
    jobName,
    jobTitle,
    jobDescription,
    analysis: feedback as object, // Feedback cast to Prisma Json
    pdfUrl,
    imageUrl,
  },
});

Build docs developers (and LLMs) love