Yeti Jobs is backed by a normalized PostgreSQL schema that enforces data integrity at every layer — from enum-constrained columns and cascading foreign keys to a GIN-indexed tsvector column for full-text job search. The schema is split across 14 numbered SQL files and executed in order during migration, ensuring every dependency (extensions → enums → tables → indexes → triggers) is created before it is referenced.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/tech-dipesh/yeti-Jobs/llms.txt
Use this file to discover all available pages before exploring further.
Extensions
Before any tables or types are created, thepgcrypto extension is loaded. This enables gen_random_uuid(), which every table uses to generate uuid primary keys by default.
Enums
All enumerated types are created withDO $$ BEGIN ... EXCEPTION WHEN duplicate_object THEN null END $$ guards so that re-running the file against an existing database is safe.
role_type — User roles
role_type — User roles
| Value | Description |
|---|---|
guest | Default role for new sign-ups; can browse and apply to jobs |
admin | Platform administrator; can manage companies and users |
recruiter | Company employee; can create and manage job listings |
user_degree_type — Education levels
user_degree_type — Education levels
users table and user_educations table to categorise a user’s highest qualification.| Value | Description |
|---|---|
Basic | Default; general or unspecified education |
Matrix | Secondary school matriculation |
High School | High school diploma |
Undergraduation | Bachelor’s degree |
Postgraduation | Master’s degree or above |
location_type_option — Job work arrangement
location_type_option — Job work arrangement
| Value | Description |
|---|---|
Remote | Fully remote, no office attendance required |
Onsite | Full-time in-office; default for new job listings |
Hybrid | Mix of remote and in-office |
is_job_open — Job listing state
is_job_open — Job listing state
active to closed when they exceed 30 days.| Value | Description |
|---|---|
active | Listing is live and accepting applications |
closed | Listing is expired or manually closed |
application_status — Application pipeline stage
application_status — Application pipeline stage
| Value | Description |
|---|---|
applied | Default; application submitted, awaiting review |
shortlisted | Moved forward for further consideration |
hired | Candidate has been offered and accepted the role |
rejected | Application declined |
email_verification_type — Email token purpose
email_verification_type — Email token purpose
email_verified table.| Value | Description |
|---|---|
verify_mail | Token sent on sign-up to confirm the email address |
forget_password | Token sent during a password-reset request |
notifications_type — Notification categories
notifications_type — Notification categories
| Value | Description |
|---|---|
new_jobs | A new job has been posted |
application_status | A recruiter changed the status of an application |
job_alert | A saved search or alert triggered |
bookmark_reminder | Reminder about a bookmarked job |
company_follow | A user followed a company |
application_recieved | Recruiter received a new application |
profile_view | Someone viewed the user’s profile |
message_recieved | An in-platform message arrived |
resume_analysed | ATS scoring completed for a resume |
announcement | Platform-wide announcement |
Tables
companies
The root entity. Users with therecruiter role belong to a company, and jobs are always posted under a company. Many other tables cascade-delete when a company row is removed.
Column details — companies
Column details — companies
| Column | Type | Nullable | Default | Notes |
|---|---|---|---|---|
uid | uuid | NOT NULL | gen_random_uuid() | Primary key |
name | text | NOT NULL | — | Company display name; indexed with btree for fast lookups |
description | text | nullable | — | Free-text company bio |
website | text | nullable | — | Company website URL |
created_at | timestamptz | nullable | CURRENT_TIMESTAMP | Row creation timestamp |
founded_year | int2 | NOT NULL | 2026 | Year the company was founded |
location | text | nullable | — | Headquarters location |
logo_url | text | nullable | — | Supabase Storage public URL for the company logo |
users
Stores all platform users regardless of role. Acompany_id foreign key links recruiters to their employer; it is NULL for job-seeking guests.
Column details — users
Column details — users
| Column | Type | Nullable | Default | Notes |
|---|---|---|---|---|
uid | uuid | NOT NULL | gen_random_uuid() | Primary key |
fname | text | NOT NULL | — | First name |
lname | text | nullable | — | Last name |
education | user_degree_type | NOT NULL | 'Basic' | Highest qualification enum |
email | text | nullable | — | Unique (enforced by users_email_key index); used for login |
password | text | nullable | — | bcrypt-hashed password |
role | role_type | NOT NULL | 'guest' | Access control role |
company_id | uuid | nullable | — | FK → companies.uid; cascades on delete |
resume_url | text | nullable | — | Supabase Storage URL of uploaded resume PDF |
profile_pic_url | text | nullable | — | Supabase Storage URL of profile picture |
skills | text[] | nullable | — | Postgres text array of skill tags |
experience | int4 | nullable | — | Years of work experience |
jobs
The core listing table. Every job belongs to a company and is created by a user (recruiter). Thesearch_title column is a tsvector automatically maintained by the vector_search_update trigger.
Column details — jobs
Column details — jobs
| Column | Type | Nullable | Default | Notes |
|---|---|---|---|---|
uid | uuid | NOT NULL | gen_random_uuid() | Primary key |
title | text | NOT NULL | — | Job title; GIN-indexed via search_title tsvector |
description | text | nullable | — | Full job description |
salary | int8 | NOT NULL | — | Annual salary figure |
job_type | location_type_option | nullable | 'Onsite' | Work arrangement enum |
is_job_open | is_job_open | NOT NULL | 'active' | Listing state; cron job closes expired listings |
company_id | uuid | nullable | — | FK → companies.uid |
created_by | uuid | NOT NULL | — | FK → users.uid (the recruiter who posted the job) |
search_title | tsvector | nullable | — | Auto-populated by trigger; powers GIN full-text search |
skills | text[] | nullable | — | Required skill tags for the role |
total_job_views | int8 | NOT NULL | 0 | View counter incremented on each listing visit |
created_at | date | nullable | CURRENT_DATE | Posting date |
experience_years | int4 | nullable | — | Minimum years of experience required |
location | text | nullable | — | Office or city location for the role |
expired_at | date | nullable | CURRENT_DATE + 30 | Auto-close date; set 30 days from creation |
applications
Records a user’s application to a job. Recruiters updatestatus to move candidates through the hiring pipeline.
Column details — applications
Column details — applications
| Column | Type | Nullable | Default | Notes |
|---|---|---|---|---|
uid | uuid | NOT NULL | gen_random_uuid() | Primary key |
user_id | uuid | nullable | — | FK → users.uid (the applicant) |
job_id | uuid | nullable | — | FK → jobs.uid |
status | application_status | nullable | 'applied' | Current pipeline stage |
applied_at | timestamptz | nullable | CURRENT_TIMESTAMP | Submission timestamp |
cover_letter | text | nullable | — | Optional cover letter body |
notice_period | int4 | NOT NULL | 0 | Days notice required before starting |
expected_salary | int8 | NOT NULL | 0 | Candidate’s salary expectation |
why_hire | text | nullable | — | Candidate’s pitch statement |
saved_jobs
Bookmarks linking a user to a job they want to revisit. Denormalisescompany_id to avoid a join when listing bookmarks with company details.
Column details — saved_jobs
Column details — saved_jobs
| Column | Type | Nullable | Default | Notes |
|---|---|---|---|---|
uid | uuid | NOT NULL | gen_random_uuid() | Primary key |
user_id | uuid | nullable | — | FK → users.uid |
job_id | uuid | nullable | — | FK → jobs.uid; cascades on delete |
company_id | uuid | nullable | — | FK → companies.uid; denormalised for fast bookmark queries |
created_at | timestamptz | nullable | CURRENT_TIMESTAMP | Bookmark creation timestamp |
user_companies_follows
Join table between users and the companies they follow. A unique partial index on(user_id, company_id) prevents duplicate follow rows.
Column details — user_companies_follows
Column details — user_companies_follows
| Column | Type | Nullable | Default | Notes |
|---|---|---|---|---|
uid | uuid | NOT NULL | gen_random_uuid() | Primary key |
user_id | uuid | NOT NULL | — | FK → users.uid; cascades on delete |
company_id | uuid | NOT NULL | — | FK → companies.uid; cascades on delete |
created_at | timestamptz | nullable | CURRENT_TIMESTAMP | Follow creation timestamp |
email_verified
Stores short-lived numeric verification codes for both email confirmation and password reset flows. Each code expires 15 minutes after creation.Column details — email_verified
Column details — email_verified
| Column | Type | Nullable | Default | Notes |
|---|---|---|---|---|
uid | uuid | NOT NULL | gen_random_uuid() | Primary key |
user_id | uuid | nullable | — | FK → users.uid; cascades on delete |
verified_type | email_verification_type | nullable | 'verify_mail' | Token purpose: account confirmation or password reset |
created_at | timestamptz | nullable | now() | Token issue timestamp |
expired_at | timestamptz | nullable | now() + 15 min | Token expiry — 15-minute window |
verified_code | int4 | nullable | — | 4- or 6-digit numeric OTP sent by email |
is_verified | bool | nullable | false | Flipped to true once the code is consumed |
ats_scores
Stores AI-generated ATS (Applicant Tracking System) scoring results for a user’s resume. Thefeedback column holds structured JSON returned by the LLM.
Column details — ats_scores
Column details — ats_scores
| Column | Type | Nullable | Default | Notes |
|---|---|---|---|---|
uid | uuid | NOT NULL | gen_random_uuid() | Primary key |
user_id | uuid | nullable | — | FK → users.uid; cascades on delete |
created_at | timestamptz | nullable | CURRENT_TIMESTAMP | When the analysis was run |
score | int4 | nullable | — | Numeric ATS score (0–100) |
feedback | jsonb | nullable | — | Structured LLM feedback: strengths, gaps, suggestions |
user_educations
Stores a user’s detailed education history. A user may have multiple rows (one per degree or institution).Column details — user_educations
Column details — user_educations
| Column | Type | Nullable | Default | Notes |
|---|---|---|---|---|
uid | uuid | NOT NULL | gen_random_uuid() | Primary key |
user_id | uuid | NOT NULL | — | FK → users.uid; cascades on delete |
university_name | text | NOT NULL | — | Name of the institution |
degree | text | NOT NULL | — | Degree title or qualification name |
start_date | date | nullable | — | Enrolment start date |
end_date | date | nullable | — | Graduation or completion date |
grade | text | nullable | — | Final grade, GPA, or CGPA |
notifications
Stores all in-app notifications for users. Rows can be linked to a company, a job, or neither depending on the notification type. Theread_at column is NULL until the user opens the notification.
Column details — notifications
Column details — notifications
| Column | Type | Nullable | Default | Notes |
|---|---|---|---|---|
uuid | uuid | NOT NULL | — | Primary key |
users_id | uuid | nullable | — | FK → users.uid; cascades on delete; the notification recipient |
company_id | uuid | nullable | — | FK → companies.uid; cascades on delete; set when notification is company-related |
job_id | uuid | nullable | — | FK → jobs.uid; set when notification is job-related |
type | notifications_type | nullable | — | Category enum |
created_at | timestamp | nullable | current_timestamp | When the notification was generated |
read_at | timestamp | nullable | NULL | Populated when the user reads the notification |
Indexes
All indexes are created idempotently withIF NOT EXISTS. They are split across four concern areas.
Jobs indexes
Jobs indexes
| Index | Type | Purpose |
|---|---|---|
index_jobs_search_title | GIN | Full-text search on job titles |
idx_jobs_company_id | btree | Company → jobs foreign key lookup |
idx_jobs_created_by | btree | Recruiter → posted jobs lookup |
idx_jobs_is_open | btree | Filter active/closed listings |
idx_jobs_company_open | btree (composite) | Company dashboard active-job count |
Users and companies indexes
Users and companies indexes
Applications and saved_jobs indexes
Applications and saved_jobs indexes
Supporting table indexes
Supporting table indexes
Trigger — vector_search_update
The search_title column on the jobs table is a tsvector that must stay in sync with the title column. Rather than requiring the application layer to manage this, a BEFORE INSERT OR UPDATE trigger handles it automatically.
INSERT and UPDATE, so renaming a job title automatically refreshes the search index without any extra application code.