Skip to main content

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.

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.

Extensions

Before any tables or types are created, the pgcrypto extension is loaded. This enables gen_random_uuid(), which every table uses to generate uuid primary keys by default.
create extension if not exists "pgcrypto";

Enums

All enumerated types are created with DO $$ BEGIN ... EXCEPTION WHEN duplicate_object THEN null END $$ guards so that re-running the file against an existing database is safe.
Controls what actions a user can perform across the platform.
create type "role_type" as enum ('guest', 'admin', 'recruiter');
ValueDescription
guestDefault role for new sign-ups; can browse and apply to jobs
adminPlatform administrator; can manage companies and users
recruiterCompany employee; can create and manage job listings
Used on the users table and user_educations table to categorise a user’s highest qualification.
create type "user_degree_type" as enum (
  'Basic', 'Matrix', 'Undergraduation', 'Postgraduation', 'High School'
);
ValueDescription
BasicDefault; general or unspecified education
MatrixSecondary school matriculation
High SchoolHigh school diploma
UndergraduationBachelor’s degree
PostgraduationMaster’s degree or above
Defines where the job is physically performed.
create type "public"."location_type_option" as enum ('Remote', 'Onsite', 'Hybrid');
ValueDescription
RemoteFully remote, no office attendance required
OnsiteFull-time in-office; default for new job listings
HybridMix of remote and in-office
Tracks whether a job posting is accepting applications. The nightly cron job automatically flips listings from active to closed when they exceed 30 days.
create type "public"."is_job_open" as enum ('active', 'closed');
ValueDescription
activeListing is live and accepting applications
closedListing is expired or manually closed
Tracks where a candidate’s application sits in the recruiter’s hiring pipeline.
create type "public"."application_status" as enum (
  'applied', 'rejected', 'hired', 'shortlisted'
);
ValueDescription
appliedDefault; application submitted, awaiting review
shortlistedMoved forward for further consideration
hiredCandidate has been offered and accepted the role
rejectedApplication declined
Distinguishes between the two token flows that share the email_verified table.
create type "public"."email_verification_type" as enum (
  'verify_mail', 'forget_password'
);
ValueDescription
verify_mailToken sent on sign-up to confirm the email address
forget_passwordToken sent during a password-reset request
Categorises every in-app notification a user can receive.
create type notifications_type as enum (
  'new_jobs', 'application_status', 'job_alert', 'bookmark_reminder',
  'company_follow', 'application_recieved', 'profile_view',
  'message_recieved', 'resume_analysed', 'announcement'
);
ValueDescription
new_jobsA new job has been posted
application_statusA recruiter changed the status of an application
job_alertA saved search or alert triggered
bookmark_reminderReminder about a bookmarked job
company_followA user followed a company
application_recievedRecruiter received a new application
profile_viewSomeone viewed the user’s profile
message_recievedAn in-platform message arrived
resume_analysedATS scoring completed for a resume
announcementPlatform-wide announcement

Tables

companies

The root entity. Users with the recruiter role belong to a company, and jobs are always posted under a company. Many other tables cascade-delete when a company row is removed.
create TABLE if not exists companies (
    uid         uuid        not null default gen_random_uuid(),
    name        text        not null,
    description text,
    website     text,
    created_at  timestamptz default CURRENT_TIMESTAMP,
    founded_year int2       not null default 2026,
    location    text,
    logo_url    text,
    primary key (uid)
);
ColumnTypeNullableDefaultNotes
uiduuidNOT NULLgen_random_uuid()Primary key
nametextNOT NULLCompany display name; indexed with btree for fast lookups
descriptiontextnullableFree-text company bio
websitetextnullableCompany website URL
created_attimestamptznullableCURRENT_TIMESTAMPRow creation timestamp
founded_yearint2NOT NULL2026Year the company was founded
locationtextnullableHeadquarters location
logo_urltextnullableSupabase Storage public URL for the company logo

users

Stores all platform users regardless of role. A company_id foreign key links recruiters to their employer; it is NULL for job-seeking guests.
create TABLE if not exists users (
    uid             uuid             not null default gen_random_uuid(),
    fname           text             not null,
    lname           text,
    education       user_degree_type not null default 'Basic'::user_degree_type,
    email           text,
    password        text,
    role            role_type        not null default 'guest'::role_type,
    company_id      uuid,
    resume_url      text,
    profile_pic_url text,
    skills          _text,
    experience      int4,
    constraint users_company_id_fkey
        foreign key (company_id) references companies(uid) ON DELETE cascade,
    primary key (uid)
);
ColumnTypeNullableDefaultNotes
uiduuidNOT NULLgen_random_uuid()Primary key
fnametextNOT NULLFirst name
lnametextnullableLast name
educationuser_degree_typeNOT NULL'Basic'Highest qualification enum
emailtextnullableUnique (enforced by users_email_key index); used for login
passwordtextnullablebcrypt-hashed password
rolerole_typeNOT NULL'guest'Access control role
company_iduuidnullableFK → companies.uid; cascades on delete
resume_urltextnullableSupabase Storage URL of uploaded resume PDF
profile_pic_urltextnullableSupabase Storage URL of profile picture
skillstext[]nullablePostgres text array of skill tags
experienceint4nullableYears of work experience

jobs

The core listing table. Every job belongs to a company and is created by a user (recruiter). The search_title column is a tsvector automatically maintained by the vector_search_update trigger.
create TABLE if not exists jobs (
    uid              uuid                  not null default gen_random_uuid(),
    title            text                  not null,
    description      text,
    salary           int8                  not null,
    job_type         location_type_option  default 'Onsite'::location_type_option,
    is_job_open      is_job_open           not null default 'active'::is_job_open,
    company_id       uuid,
    created_by       uuid                  not null,
    search_title     tsvector,
    skills           _text,
    total_job_views  int8                  not null default '0'::bigint,
    created_at       date                  default CURRENT_DATE,
    experience_years int4,
    location         text,
    expired_at       date                  default (CURRENT_DATE + 30),
    constraint jobs_company_id_fkey
        foreign key (company_id) references companies(uid),
    constraint jobs_created_by_fkey
        foreign key (created_by) references users(uid),
    primary key (uid)
);
ColumnTypeNullableDefaultNotes
uiduuidNOT NULLgen_random_uuid()Primary key
titletextNOT NULLJob title; GIN-indexed via search_title tsvector
descriptiontextnullableFull job description
salaryint8NOT NULLAnnual salary figure
job_typelocation_type_optionnullable'Onsite'Work arrangement enum
is_job_openis_job_openNOT NULL'active'Listing state; cron job closes expired listings
company_iduuidnullableFK → companies.uid
created_byuuidNOT NULLFK → users.uid (the recruiter who posted the job)
search_titletsvectornullableAuto-populated by trigger; powers GIN full-text search
skillstext[]nullableRequired skill tags for the role
total_job_viewsint8NOT NULL0View counter incremented on each listing visit
created_atdatenullableCURRENT_DATEPosting date
experience_yearsint4nullableMinimum years of experience required
locationtextnullableOffice or city location for the role
expired_atdatenullableCURRENT_DATE + 30Auto-close date; set 30 days from creation

applications

Records a user’s application to a job. Recruiters update status to move candidates through the hiring pipeline.
create TABLE if not exists applications (
    user_id         uuid,
    job_id          uuid,
    status          application_status default 'applied'::application_status,
    applied_at      timestamptz        default CURRENT_TIMESTAMP,
    uid             uuid               not null default gen_random_uuid(),
    cover_letter    text,
    notice_period   int4               not null default 0,
    expected_salary int8               not null default 0,
    why_hire        text,
    constraint applications_job_id_fkey
        foreign key (job_id) references jobs(uid),
    constraint applications_user_id_fkey
        foreign key (user_id) references users(uid),
    primary key (uid)
);
ColumnTypeNullableDefaultNotes
uiduuidNOT NULLgen_random_uuid()Primary key
user_iduuidnullableFK → users.uid (the applicant)
job_iduuidnullableFK → jobs.uid
statusapplication_statusnullable'applied'Current pipeline stage
applied_attimestamptznullableCURRENT_TIMESTAMPSubmission timestamp
cover_lettertextnullableOptional cover letter body
notice_periodint4NOT NULL0Days notice required before starting
expected_salaryint8NOT NULL0Candidate’s salary expectation
why_hiretextnullableCandidate’s pitch statement

saved_jobs

Bookmarks linking a user to a job they want to revisit. Denormalises company_id to avoid a join when listing bookmarks with company details.
create TABLE if not exists saved_jobs (
    uid        uuid        not null default gen_random_uuid(),
    company_id uuid,
    job_id     uuid,
    created_at timestamptz default CURRENT_TIMESTAMP,
    user_id    uuid,
    constraint saved_jobs_company_id_fkey
        foreign key (company_id) references companies(uid),
    constraint saved_jobs_job_id_fkey
        foreign key (job_id) references jobs(uid) on delete cascade,
    constraint saved_jobs_user_id_fkey
        foreign key (user_id) references users(uid),
    primary key (uid)
);
ColumnTypeNullableDefaultNotes
uiduuidNOT NULLgen_random_uuid()Primary key
user_iduuidnullableFK → users.uid
job_iduuidnullableFK → jobs.uid; cascades on delete
company_iduuidnullableFK → companies.uid; denormalised for fast bookmark queries
created_attimestamptznullableCURRENT_TIMESTAMPBookmark 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.
create TABLE if not exists user_companies_follows (
    uid        uuid        not null default gen_random_uuid() primary key,
    user_id    uuid        not null,
    company_id uuid        not null,
    created_at timestamptz default CURRENT_TIMESTAMP,
    constraint user_companies_follows_company_id_fkey
        foreign key (company_id) references companies(uid) on delete cascade,
    constraint user_companies_follows_user_id_fkey
        foreign key (user_id) references users(uid) on delete cascade
);
ColumnTypeNullableDefaultNotes
uiduuidNOT NULLgen_random_uuid()Primary key
user_iduuidNOT NULLFK → users.uid; cascades on delete
company_iduuidNOT NULLFK → companies.uid; cascades on delete
created_attimestamptznullableCURRENT_TIMESTAMPFollow 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.
create TABLE if not exists email_verified (
    uid           uuid                    not null default gen_random_uuid(),
    user_id       uuid,
    verified_type email_verification_type default 'verify_mail'::email_verification_type,
    created_at    timestamptz             default now(),
    expired_at    timestamptz             default (now() + '00:15:00'::interval),
    verified_code int4,
    is_verified   bool                    default false,
    constraint email_verified_user_id_fkey
        foreign key (user_id) references users(uid) on delete cascade,
    primary key (uid)
);
ColumnTypeNullableDefaultNotes
uiduuidNOT NULLgen_random_uuid()Primary key
user_iduuidnullableFK → users.uid; cascades on delete
verified_typeemail_verification_typenullable'verify_mail'Token purpose: account confirmation or password reset
created_attimestamptznullablenow()Token issue timestamp
expired_attimestamptznullablenow() + 15 minToken expiry — 15-minute window
verified_codeint4nullable4- or 6-digit numeric OTP sent by email
is_verifiedboolnullablefalseFlipped to true once the code is consumed

ats_scores

Stores AI-generated ATS (Applicant Tracking System) scoring results for a user’s resume. The feedback column holds structured JSON returned by the LLM.
create TABLE if not exists ats_scores (
    uid        uuid        not null default gen_random_uuid(),
    user_id    uuid,
    created_at timestamptz default CURRENT_TIMESTAMP,
    score      int4,
    feedback   jsonb,
    constraint ats_scores_user_id_fkey
        foreign key (user_id) references users(uid) on delete cascade,
    primary key (uid)
);
ColumnTypeNullableDefaultNotes
uiduuidNOT NULLgen_random_uuid()Primary key
user_iduuidnullableFK → users.uid; cascades on delete
created_attimestamptznullableCURRENT_TIMESTAMPWhen the analysis was run
scoreint4nullableNumeric ATS score (0–100)
feedbackjsonbnullableStructured 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).
create table if not exists user_educations (
    uid             uuid primary key default gen_random_uuid(),
    user_id         uuid not null references users(uid) on delete cascade,
    university_name text not null,
    degree          text not null,
    start_date      date,
    end_date        date,
    grade           text
);
ColumnTypeNullableDefaultNotes
uiduuidNOT NULLgen_random_uuid()Primary key
user_iduuidNOT NULLFK → users.uid; cascades on delete
university_nametextNOT NULLName of the institution
degreetextNOT NULLDegree title or qualification name
start_datedatenullableEnrolment start date
end_datedatenullableGraduation or completion date
gradetextnullableFinal 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. The read_at column is NULL until the user opens the notification.
create table notifications if not exists (
    uuid       uuid primary key,
    users_id   uuid references users(uid) on delete cascade,
    company_id uuid references companies(uid) on delete cascade,
    job_id     uuid references jobs(uid),
    type       notifications_type,
    created_at timestamp default current_timestamp,
    read_at    timestamp
);
ColumnTypeNullableDefaultNotes
uuiduuidNOT NULLPrimary key
users_iduuidnullableFK → users.uid; cascades on delete; the notification recipient
company_iduuidnullableFK → companies.uid; cascades on delete; set when notification is company-related
job_iduuidnullableFK → jobs.uid; set when notification is job-related
typenotifications_typenullableCategory enum
created_attimestampnullablecurrent_timestampWhen the notification was generated
read_attimestampnullableNULLPopulated when the user reads the notification

Indexes

All indexes are created idempotently with IF NOT EXISTS. They are split across four concern areas.
-- GIN index for full-text search on the tsvector column
create index if not exists index_jobs_search_title
    on jobs using gin (search_title);

-- FK lookup indexes
create index if not exists idx_jobs_company_id on jobs(company_id);
create index if not exists idx_jobs_created_by on jobs(created_by);

-- Filter index for open/closed queries
create index if not exists idx_jobs_is_open on jobs(is_job_open);

-- Composite index: company dashboard open-jobs query
create index if not exists idx_jobs_company_open on jobs(company_id, is_job_open);
IndexTypePurpose
index_jobs_search_titleGINFull-text search on job titles
idx_jobs_company_idbtreeCompany → jobs foreign key lookup
idx_jobs_created_bybtreeRecruiter → posted jobs lookup
idx_jobs_is_openbtreeFilter active/closed listings
idx_jobs_company_openbtree (composite)Company dashboard active-job count
-- Unique email constraint (also enforces uniqueness)
create unique index if not exists users_email_key
    on users using btree(email);

-- Fast email lookup for login
create index if not exists idx_users_email on users using btree (email);

-- Company name search
create index if not exists idx_companies_name on companies using btree (name);

-- Prevents duplicate follows (one row per user+company pair)
create unique index if not exists unique_user_companies
    on user_companies_follows using btree (user_id, company_id);
create index if not exists idx_applications_user_id on applications(user_id);
create index if not exists idx_applications_job_id  on applications(job_id);
create index if not exists idx_applications_status  on applications(status);

create index if not exists idx_saved_jobs_user_id on saved_jobs(user_id);
create index if not exists idx_saved_jobs_job_id  on saved_jobs(job_id);
-- Email verification lookups
create index if not exists idx_email_verified_code_user_id
    on email_verified using btree(user_id);
create index if not exists idx_verified_code_user_id
    on email_verified(verified_code);

-- Education and ATS score lookups
create index if not exists idx_user_educations_user_id on user_educations(user_id);
create index if not exists idx_ats_scores_user_id      on ats_scores(user_id);

-- Notifications: ordered by recency and read status
create index idx_notifications_user_created
    on notifications (users_id, created_at desc);
create index idx_notifications_user_read
    on notifications (users_id, read_at);

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.
create or replace function public.update_search_job_title()
  returns trigger
  language plpgsql
as $function$
  begin
    new.search_title =
      to_tsvector('english', new.title);
    return new;
  end
$function$;

do $$ begin
  if not exists (
    select 1 from pg_trigger where tgname = 'vector_search_update'
  ) then
    create trigger vector_search_update
      before insert or update on public.jobs
      for each row
      execute function update_search_job_title();
  end if;
end $$;
The trigger fires on both INSERT and UPDATE, so renaming a job title automatically refreshes the search index without any extra application code.

Entity Relationships

The diagram below shows every table and their foreign key relationships.
Foreign keys that reference companies(uid) with ON DELETE CASCADE include users, user_companies_follows, and notifications. Deleting a company row therefore cascades through the entire ownership graph — jobs, applications, follows, and notifications are all removed automatically.

Build docs developers (and LLMs) love