-
+
+
+
Add custom criterion
+
+
+
-
- {{ criterion.weight }}
-
-
-
-
Max score: {{ criterion.maxScore }}
-
Key: {{ criterion.key }}
+
+
+
-
-
-
-
-
-
-
-
-
Add custom criterion
-
-
- Description
+
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+ Automatically score every new applicant
+
+
+ When a candidate applies, AI will automatically analyze their resume against these criteria and assign a score. Requires an AI provider configured in settings plus a resume upload.
+
+
+ Configure an AI provider to enable automatic scoring.
+
+
+
-
-
-
-
-
-
-
-
-
Scoring criteria are optional. You can skip this step and add them later from job settings.
-
@@ -1586,18 +1393,22 @@ const questionTypeLabels: Record
= {
-
Distribute to job boards
+ Share & track
Create tracked links for each platform. This lets you see exactly where your applicants come from.
-
-
-
Job boards
+
+
+
{{ group.label }}
-
-
-
Direct outreach
-
-
-
-
-
-
-
- {{ ch.name }}
- {{ ch.description }}
-
-
-
-
-
-
-
- Creating...
-
-
-
-
-
-
-
-
- Clicks and applications from this link will be tracked
-
-
-
-
-
-
-
-
-
Social media
-
-
-
-
-
-
-
- {{ ch.name }}
- {{ ch.description }}
-
-
-
-
-
-
-
- Creating...
-
-
-
-
-
-
-
-
- Clicks and applications from this link will be tracked
-
-
-
-
-
-
Custom job board
@@ -2050,67 +1735,15 @@ const questionTypeLabels: Record = {
-
-
diff --git a/app/pages/dashboard/jobs/preview.vue b/app/pages/dashboard/jobs/preview.vue
new file mode 100644
index 00000000..277eab9a
--- /dev/null
+++ b/app/pages/dashboard/jobs/preview.vue
@@ -0,0 +1,172 @@
+
+
+
+
+
+
+
+
+
+
+
Draft applicant preview
+
Only you can see this page. Changes appear after reopening or refreshing the preview.
+
+
+
+
+
+
+
+
+
+
+
+
No draft preview found
+
+ Return to the job builder, add a job title, and click “View preview” again.
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/pages/jobs/[slug]/apply.vue b/app/pages/jobs/[slug]/apply.vue
index 9050f133..91a39ff1 100644
--- a/app/pages/jobs/[slug]/apply.vue
+++ b/app/pages/jobs/[slug]/apply.vue
@@ -1,5 +1,5 @@
@@ -298,259 +291,22 @@ const typeLabels: Record = {
Back to job details
-
-
-
-
-
-
-
-
-
-
- {{ job.organizationName }}
-
-
-
- {{ typeLabels[job.type] ?? job.type }}
-
-
-
- {{ job.location }}
-
-
-
-
- {{ job.title }}
-
-
-
-
-
-
-
+
-
-
-
-
Your application
-
Fields marked with * are required.
-
-
-
-
-
-
- {{ submitError }}
-
-
-
-
-
+ delete errors[key]"
+ @submit="handleSubmit"
+ />
diff --git a/e2e/critical-flows/candidate-application.spec.ts b/e2e/critical-flows/candidate-application.spec.ts
index 9a24ceaf..237f8bd0 100644
--- a/e2e/critical-flows/candidate-application.spec.ts
+++ b/e2e/critical-flows/candidate-application.spec.ts
@@ -1,4 +1,4 @@
-import { test, expect } from '../fixtures'
+import { test, expect, declineAnalyticsConsent } from '../fixtures'
/**
* Critical flow: Candidate applies to a published job that contains every
@@ -202,6 +202,7 @@ test.describe('Candidate Application Flow — All Custom Question Field Types',
// ── Candidate flow: fresh unauthenticated context ─────────────────────────
const candidateContext = await browser.newContext()
+ await declineAnalyticsConsent(candidateContext)
const candidatePage = await candidateContext.newPage()
// Unique identity per run + retry — static emails cause a 409 "already
@@ -513,6 +514,7 @@ test.describe('Candidate Application — Required Cover Letter Validation', () =
// ── Candidate flow ────────────────────────────────────────────────────────
const candidateContext = await browser.newContext()
+ await declineAnalyticsConsent(candidateContext)
const candidatePage = await candidateContext.newPage()
await candidatePage.goto(applicationLink)
diff --git a/e2e/critical-flows/job-creation.spec.ts b/e2e/critical-flows/job-creation.spec.ts
index 0bf67a54..b18b52fa 100644
--- a/e2e/critical-flows/job-creation.spec.ts
+++ b/e2e/critical-flows/job-creation.spec.ts
@@ -16,41 +16,159 @@ import { test, expect } from '../fixtures'
const JOB_TITLE = 'Senior QA Engineer'
const JOB_DESCRIPTION = 'We are looking for a senior QA engineer to lead our testing efforts.'
const JOB_LOCATION = 'Remote'
+const QUESTION_LABEL = 'Which testing framework do you know best?'
+const UPDATED_QUESTION_LABEL = 'Which browser testing framework do you know best?'
test.describe('Job Creation Flow', () => {
- test('recruiter can create and publish a job', async ({ authenticatedPage, testAccount }) => {
+ test('wizard rejects malformed drafts, invalid questions, and duplicate submissions', async ({ authenticatedPage }) => {
const page = authenticatedPage
+ await page.evaluate(() => {
+ localStorage.setItem('reqcore-job-draft', JSON.stringify({
+ form: { title: ' ' },
+ applicationForm: { questions: 'not-an-array' },
+ scoringCriteria: [{ key: '__invalid__' }],
+ currentStep: 99,
+ }))
+ })
+ await page.goto('/dashboard/jobs/new')
+ await page.waitForLoadState('networkidle')
+
+ const title = page.getByLabel('Job title')
+ await expect(title).toBeVisible()
+ await expect(title).toHaveValue('')
+ await title.fill(' ')
+ await expect(page.locator('form').getByRole('button', { name: 'Save & continue' })).toBeDisabled()
+
+ await title.fill('Robustness Test Engineer')
+ const continueButton = page.locator('form').getByRole('button', { name: 'Save & continue' })
+ await expect(continueButton).toBeEnabled()
+ await continueButton.click()
+
+ await page.getByRole('button', { name: 'Add a question', exact: true }).click()
+ await page.getByLabel('Question').fill('Preferred test framework?')
+ await page.getByLabel('Field Type').selectOption('single_select')
+ await page.getByPlaceholder('Option 1').fill('Playwright')
+ await page.getByRole('button', { name: 'Add option' }).click()
+ await page.getByPlaceholder('Option 2').fill(' playwright ')
+ await page.getByRole('button', { name: 'Add Question', exact: true }).click()
+ await expect(page.getByText('Options must be unique')).toBeVisible()
+
+ await page.getByPlaceholder('Option 2').fill('Cypress')
+ await page.getByRole('button', { name: 'Add Question', exact: true }).click()
+ await page.locator('form').getByRole('button', { name: 'Save & continue' }).click()
+ await page.locator('form').getByRole('button', { name: 'Save & continue' }).click()
+
+ let createRequests = 0
+ page.on('request', (request) => {
+ if (request.method() === 'POST' && new URL(request.url()).pathname === '/api/jobs') createRequests++
+ })
+ await page.locator('form button').filter({ hasText: /^Save as draft/ }).click()
+
+ await page.locator('form').evaluate((form: HTMLFormElement) => {
+ form.requestSubmit()
+ form.requestSubmit()
+ })
+
+ await page.waitForURL(/\/dashboard\/jobs(?:\?|$)/)
+ expect(createRequests).toBe(1)
+ })
+
+ test('recruiter can configure the application form and publish a job', async ({ authenticatedPage }) => {
+ const page = authenticatedPage
+ const dismissFeedbackSurvey = async () => {
+ await page.getByRole('button', { name: 'No thanks' }).click({ timeout: 2_000 }).catch(() => {})
+ }
+
// ── Navigate to Create Job ───────────────────────────
await page.goto('/dashboard/jobs/new')
await page.waitForLoadState('networkidle')
- await expect(page.getByRole('heading', { name: 'New Job' })).toBeVisible()
+ await dismissFeedbackSurvey()
// ── Step 1: Fill in job details ──────────────────────
// Wait for the form to be fully hydrated before interacting
await page.getByLabel('Job title').waitFor({ state: 'visible', timeout: 15_000 })
await page.getByLabel('Job title').fill(JOB_TITLE)
- await page.locator('textarea').first().fill(JOB_DESCRIPTION)
+ await page.getByLabel('Description').fill(JOB_DESCRIPTION)
await page.getByLabel('Location').fill(JOB_LOCATION)
- // Click through to step 3 and submit (scope to form to avoid header duplicate button)
+ // The persistent candidate preview should update as job details are entered.
+ const preview = page.getByRole('complementary')
+ await expect(preview.getByRole('heading', { name: JOB_TITLE })).toBeVisible()
+ await expect(preview.getByText(JOB_LOCATION)).toBeVisible()
+
await page.locator('form').getByRole('button', { name: 'Save & continue' }).waitFor({ state: 'attached', timeout: 10_000 })
await expect(page.locator('form').getByRole('button', { name: 'Save & continue' })).toBeEnabled({ timeout: 10_000 })
await page.locator('form').getByRole('button', { name: 'Save & continue' }).click()
- // Step 2: Application form — skip (defaults are fine)
+ // ── Step 2: Configure the application form ───────────
+ await expect(page.getByText('Customize your application form')).toBeVisible()
+
+ const resumeRequirement = page.getByRole('radiogroup', { name: 'Resume requirement' })
+ await resumeRequirement.getByRole('radio', { name: 'Off' }).click()
+ await expect(resumeRequirement.getByRole('radio', { name: 'Off' })).toBeChecked()
+ await expect(preview.getByText('Resume / CV', { exact: false })).toHaveCount(0)
+
+ const coverLetterRequirement = page.getByRole('radiogroup', { name: 'Cover letter requirement' })
+ await coverLetterRequirement.getByRole('radio', { name: 'Required' }).click()
+ await expect(coverLetterRequirement.getByRole('radio', { name: 'Required' })).toBeChecked()
+ await expect(preview.getByLabel('Cover Letter')).toBeVisible()
+
+ // Add a required single-select question and verify it appears in the preview.
+ await page.getByRole('button', { name: 'Add a question', exact: true }).click()
+ await page.getByLabel('Question').fill(QUESTION_LABEL)
+ await page.getByLabel('Field Type').selectOption('single_select')
+ await page.getByPlaceholder('Option 1').fill('Playwright')
+ await page.getByRole('button', { name: 'Add option' }).click()
+ await page.getByPlaceholder('Option 2').fill('Cypress')
+ await page.getByLabel('Required', { exact: true }).check()
+ await page.getByRole('button', { name: 'Add Question', exact: true }).click()
+
+ await expect(page.getByText('1 question added')).toBeVisible()
+ const previewQuestion = preview.getByLabel(QUESTION_LABEL)
+ await expect(previewQuestion).toBeVisible()
+ await expect(previewQuestion.getByRole('option', { name: 'Playwright' })).toHaveCount(1)
+ await expect(previewQuestion.getByRole('option', { name: 'Cypress' })).toHaveCount(1)
+
+ // Editing must update both the builder row and the candidate preview.
+ await page.getByTitle('Edit').click()
+ await page.getByLabel('Question').fill(UPDATED_QUESTION_LABEL)
+ await page.getByRole('button', { name: 'Update', exact: true }).click()
+ await expect(preview.getByLabel(UPDATED_QUESTION_LABEL)).toBeVisible()
+ await expect(preview.getByLabel(QUESTION_LABEL)).toHaveCount(0)
+
+ // The full draft preview opens in a new tab and uses the applicant-facing form.
+ const previewPagePromise = page.waitForEvent('popup')
+ await page.getByRole('button', { name: 'View preview' }).click()
+ const previewPage = await previewPagePromise
+ await previewPage.waitForLoadState('domcontentloaded')
+ await expect(previewPage.getByText('Draft applicant preview')).toBeVisible()
+ await expect(previewPage.getByRole('heading', { name: JOB_TITLE })).toBeVisible()
+ await expect(previewPage.getByText(JOB_DESCRIPTION)).toBeVisible()
+ await expect(previewPage.getByLabel('Cover Letter')).toBeVisible()
+ await expect(previewPage.getByText('Resume / CV', { exact: false })).toHaveCount(0)
+ await expect(previewPage.getByLabel(UPDATED_QUESTION_LABEL)).toBeVisible()
+ await previewPage.close()
+
+ // Device switching is part of the new persistent preview.
+ const previewDevice = preview.getByRole('radiogroup', { name: 'Preview device' })
+ await previewDevice.getByRole('radio', { name: 'Mobile' }).click()
+ await expect(previewDevice.getByRole('radio', { name: 'Mobile' })).toBeChecked()
+
await page.locator('form').getByRole('button', { name: 'Save & continue' }).waitFor({ state: 'attached', timeout: 10_000 })
await expect(page.locator('form').getByRole('button', { name: 'Save & continue' })).toBeEnabled({ timeout: 10_000 })
await page.locator('form').getByRole('button', { name: 'Save & continue' }).click()
// Step 3: Scoring criteria — skip (defaults are fine)
await page.locator('form').getByRole('button', { name: 'Save & continue' }).waitFor({ state: 'visible', timeout: 10_000 })
+ await dismissFeedbackSurvey()
await page.locator('form').getByRole('button', { name: 'Save & continue' }).click()
// Step 4: Publish the job
await expect(page.getByRole('heading', { name: /Ready to go\?/i })).toBeVisible({ timeout: 10_000 })
const publishButton = page.locator('form').getByRole('button', { name: /Publish & copy link/i })
await publishButton.waitFor({ state: 'visible', timeout: 10_000 })
+ await dismissFeedbackSurvey()
await publishButton.click()
// ── Verify the success state ("Your job is live!") ───
@@ -70,5 +188,14 @@ test.describe('Job Creation Flow', () => {
// Verify the "Apply" link/button is present (use .first() because the page has two apply links)
await expect(page.getByRole('link', { name: /apply/i }).first()).toBeVisible()
+
+ // The published candidate form must match the builder configuration.
+ await page.goto(`/jobs/${jobSlug}/apply`)
+ await expect(page.getByLabel('Cover Letter')).toBeVisible()
+ await expect(page.getByText('Resume / CV', { exact: false })).toHaveCount(0)
+ const publishedQuestion = page.getByLabel(UPDATED_QUESTION_LABEL)
+ await expect(publishedQuestion).toBeVisible()
+ await expect(publishedQuestion.getByRole('option', { name: 'Playwright' })).toHaveCount(1)
+ await expect(publishedQuestion.getByRole('option', { name: 'Cypress' })).toHaveCount(1)
})
})
diff --git a/e2e/critical-flows/resume-upload.spec.ts b/e2e/critical-flows/resume-upload.spec.ts
index ee59561e..96bbc37c 100644
--- a/e2e/critical-flows/resume-upload.spec.ts
+++ b/e2e/critical-flows/resume-upload.spec.ts
@@ -1,5 +1,5 @@
import { type Browser } from '@playwright/test'
-import { test, expect } from '../fixtures'
+import { test, expect, declineAnalyticsConsent } from '../fixtures'
import {
VALID_FILE_CONFIGS,
INVALID_FILE_CONFIGS,
@@ -50,6 +50,7 @@ test.describe('Resume Upload — All File Formats', () => {
// We need a fresh authenticated page to set up the job.
// Re-use the same signup logic from fixtures.ts.
const context = await browser.newContext()
+ await declineAnalyticsConsent(context)
const page = await context.newPage()
const id = `${Date.now()}-setup`
@@ -90,15 +91,32 @@ test.describe('Resume Upload — All File Formats', () => {
resp => resp.url().includes('/api/auth/sign-in') && resp.status() === 200,
{ timeout: 30_000 },
),
- page.getByRole('button', { name: 'Sign in' }).click(),
+ page.getByRole('button', { name: 'Sign in', exact: true }).click(),
])
- await page.waitForURL('**/onboarding/**', { waitUntil: 'commit', timeout: 30_000 })
+ await page.goto('/onboarding/create-org')
}
await page.getByLabel('Organization name').waitFor({ state: 'visible', timeout: 30_000 })
await page.getByLabel('Organization name').fill(account.orgName)
await page.getByRole('button', { name: 'Create organization' }).click()
- await page.waitForURL('**/dashboard**', { waitUntil: 'commit' })
+ await page.waitForURL(
+ url => url.pathname.includes('/dashboard') || url.pathname.includes('/auth/sign-in'),
+ { waitUntil: 'commit', timeout: 30_000 },
+ )
+
+ if (page.url().includes('/auth/sign-in')) {
+ await page.getByLabel('Email').fill(account.email)
+ await page.getByLabel('Password').fill(account.password)
+ await Promise.all([
+ page.waitForResponse(
+ resp => resp.url().includes('/api/auth/sign-in') && resp.status() === 200,
+ { timeout: 30_000 },
+ ),
+ page.getByRole('button', { name: 'Sign in', exact: true }).click(),
+ ])
+ await page.goto('/dashboard')
+ }
+ await page.waitForLoadState('networkidle')
// ── Create job ─────────────────────────────────────────────────────────
@@ -204,6 +222,7 @@ async function assertUploadResult(
const candidate = applicant(idx)
const ctx = await browser.newContext()
+ await declineAnalyticsConsent(ctx)
const page = await ctx.newPage()
await page.goto(applicationLink)
diff --git a/e2e/critical-flows/source-tracking.spec.ts b/e2e/critical-flows/source-tracking.spec.ts
index 77ce00a5..171a41fb 100644
--- a/e2e/critical-flows/source-tracking.spec.ts
+++ b/e2e/critical-flows/source-tracking.spec.ts
@@ -1,4 +1,4 @@
-import { test, expect } from '../fixtures'
+import { test, expect, declineAnalyticsConsent } from '../fixtures'
/**
* Critical flow: Source tracking query parameters (?ref=, utm_*) propagate
@@ -67,6 +67,7 @@ test.describe('Source Tracking — Query Parameter Propagation', () => {
// ── Step 2: Candidate navigates from job listing with tracking params ──────
const candidateContext = await browser.newContext()
+ await declineAnalyticsConsent(candidateContext)
const candidatePage = await candidateContext.newPage()
const REF_CODE = 'TRACK_E2E_123'
diff --git a/e2e/fixtures.ts b/e2e/fixtures.ts
index e1c4928e..484bb285 100644
--- a/e2e/fixtures.ts
+++ b/e2e/fixtures.ts
@@ -1,4 +1,4 @@
-import { test as base, type Page } from '@playwright/test'
+import { test as base, type BrowserContext, type Page } from '@playwright/test'
/**
* Shared test fixtures for Reqcore E2E tests.
@@ -31,6 +31,14 @@ type Fixtures = {
authenticatedPage: Page
}
+export async function declineAnalyticsConsent(context: BrowserContext) {
+ await context.addCookies([{
+ name: 'reqcore-consent',
+ value: 'denied',
+ url: process.env.PLAYWRIGHT_BASE_URL || 'http://localhost:3333',
+ }])
+}
+
export const test = base.extend
({
testAccount: [
// eslint-disable-next-line no-empty-pattern
@@ -42,6 +50,8 @@ export const test = base.extend({
],
authenticatedPage: async ({ page, testAccount }, use) => {
+ await declineAnalyticsConsent(page.context())
+
// Sign up
await page.goto('/auth/sign-up')
await page.waitForLoadState('networkidle')
@@ -78,12 +88,12 @@ export const test = base.extend({
resp => resp.url().includes('/api/auth/sign-in') && resp.status() === 200,
{ timeout: 30_000 },
),
- page.getByRole('button', { name: 'Sign in' }).click(),
+ page.getByRole('button', { name: 'Sign in', exact: true }).click(),
])
- // Sign-in navigates to /dashboard, then require-org middleware
- // redirects to /onboarding/create-org (user has no org yet)
- await page.waitForURL('**/onboarding/**', { waitUntil: 'commit', timeout: 30_000 })
+ // The API response confirms the session cookie is set. Navigate directly
+ // instead of depending on the sign-in page's client redirect timing.
+ await page.goto('/onboarding/create-org')
}
// Wait for the org-creation form to render (loading spinner may show first)
@@ -91,8 +101,43 @@ export const test = base.extend({
await page.getByLabel('Organization name').fill(testAccount.orgName)
await page.getByRole('button', { name: 'Create organization' }).click()
- // Wait for redirect to dashboard (use 'commit' for SPA navigation)
- await page.waitForURL('**/dashboard**', { waitUntil: 'commit' })
+ // Creating the org performs a hard navigation. Under load, the freshly
+ // updated session can race that navigation and land on sign-in instead.
+ await page.waitForURL(
+ url => url.pathname.includes('/dashboard') || url.pathname.includes('/auth/sign-in'),
+ { waitUntil: 'commit', timeout: 30_000 },
+ )
+
+ if (page.url().includes('/auth/sign-in')) {
+ await page.getByLabel('Email').fill(testAccount.email)
+ await page.getByLabel('Password').fill(testAccount.password)
+ await Promise.all([
+ page.waitForResponse(
+ resp => resp.url().includes('/api/auth/sign-in') && resp.status() === 200,
+ { timeout: 30_000 },
+ ),
+ page.getByRole('button', { name: 'Sign in', exact: true }).click(),
+ ])
+ await page.goto('/dashboard')
+ }
+
+ // Do not hand the page to the test while dashboard auth middleware is
+ // still settling; otherwise the test's first navigation can race a
+ // delayed redirect back to sign-in.
+ await page.waitForLoadState('networkidle')
+ if (page.url().includes('/auth/sign-in')) {
+ await page.getByLabel('Email').fill(testAccount.email)
+ await page.getByLabel('Password').fill(testAccount.password)
+ await Promise.all([
+ page.waitForResponse(
+ resp => resp.url().includes('/api/auth/sign-in') && resp.status() === 200,
+ { timeout: 30_000 },
+ ),
+ page.getByRole('button', { name: 'Sign in', exact: true }).click(),
+ ])
+ await page.goto('/dashboard')
+ await page.waitForLoadState('networkidle')
+ }
await use(page)
},
diff --git a/i18n.config.ts b/i18n/i18n.config.ts
similarity index 69%
rename from i18n.config.ts
rename to i18n/i18n.config.ts
index 1dd9feed..ec85f564 100644
--- a/i18n.config.ts
+++ b/i18n/i18n.config.ts
@@ -1,7 +1,7 @@
export default defineI18nConfig(() => ({
legacy: false,
- locale: 'en',
- fallbackLocale: 'en',
+ locale: "en",
+ fallbackLocale: "en",
missingWarn: false,
fallbackWarn: false,
-}))
+}));
diff --git a/package-lock.json b/package-lock.json
index 0fef0509..1437ccc2 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -31,7 +31,7 @@
"googleapis": "^171.4.0",
"lucide-vue-next": "^0.577.0",
"mammoth": "^1.12.0",
- "nodemailer": "^8.0.7",
+ "nodemailer": "^9.0.1",
"nuxt": "^4.4.8",
"pdf-parse": "^2.4.5",
"postgres": "^3.4.8",
@@ -13476,9 +13476,9 @@
}
},
"node_modules/nodemailer": {
- "version": "8.0.11",
- "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-8.0.11.tgz",
- "integrity": "sha512-nrO/pDAUKl+wXX+lx16tDLbnm0fW6sK/x8mgohaCpg+CdCEl482bD4tCuAZk2DyliruiNTIZxRCoWkDqJEnAiA==",
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-9.0.1.tgz",
+ "integrity": "sha512-Gwv8SQewT616ZM/URn0H54b8PWo/Wum7md3EW2aWy1lO27+WZCX+Xyak3J+NlmHUjDh5ME+uesJUDRbR3Ye8Bw==",
"license": "MIT-0",
"engines": {
"node": ">=6.0.0"
diff --git a/package.json b/package.json
index 988825c9..b41de9d1 100644
--- a/package.json
+++ b/package.json
@@ -47,7 +47,7 @@
"googleapis": "^171.4.0",
"lucide-vue-next": "^0.577.0",
"mammoth": "^1.12.0",
- "nodemailer": "^8.0.7",
+ "nodemailer": "^9.0.1",
"nuxt": "^4.4.8",
"pdf-parse": "^2.4.5",
"postgres": "^3.4.8",
@@ -94,7 +94,7 @@
"fast-xml-builder": "1.2.0",
"kysely": "0.28.17",
"simple-git": "3.36.0",
- "protobufjs": "8.6.3",
+ "protobufjs": "8.6.4",
"form-data": "4.0.6"
}
}
diff --git a/server/api/jobs/[id].get.ts b/server/api/jobs/[id].get.ts
index 95b6e0ec..eed37b02 100644
--- a/server/api/jobs/[id].get.ts
+++ b/server/api/jobs/[id].get.ts
@@ -25,6 +25,7 @@ export default defineEventHandler(async (event) => {
salaryNegotiable: true,
remoteStatus: true,
validThrough: true,
+ phoneRequirement: true,
requireResume: true,
requireCoverLetter: true,
autoScoreOnApply: true,
diff --git a/server/api/jobs/[id].patch.ts b/server/api/jobs/[id].patch.ts
index 1cda5169..c7321354 100644
--- a/server/api/jobs/[id].patch.ts
+++ b/server/api/jobs/[id].patch.ts
@@ -55,6 +55,7 @@ export default defineEventHandler(async (event) => {
salaryNegotiable: job.salaryNegotiable,
remoteStatus: job.remoteStatus,
validThrough: job.validThrough,
+ phoneRequirement: job.phoneRequirement,
requireResume: job.requireResume,
requireCoverLetter: job.requireCoverLetter,
autoScoreOnApply: job.autoScoreOnApply,
diff --git a/server/api/jobs/[id]/questions/[questionId].patch.ts b/server/api/jobs/[id]/questions/[questionId].patch.ts
index f2d76ff7..f331f24f 100644
--- a/server/api/jobs/[id]/questions/[questionId].patch.ts
+++ b/server/api/jobs/[id]/questions/[questionId].patch.ts
@@ -1,6 +1,6 @@
import { eq, and } from 'drizzle-orm'
import { jobQuestion } from '../../../../database/schema'
-import { questionIdParamSchema, updateQuestionSchema } from '../../../../utils/schemas/jobQuestion'
+import { questionIdParamSchema, questionStateSchema, updateQuestionSchema } from '../../../../utils/schemas/jobQuestion'
export default defineEventHandler(async (event) => {
const session = await requirePermission(event, { job: ['update'] })
@@ -9,6 +9,33 @@ export default defineEventHandler(async (event) => {
const { id: jobId, questionId } = await getValidatedRouterParams(event, questionIdParamSchema.parse)
const body = await readValidatedBody(event, updateQuestionSchema.parse)
+ const existing = await db.query.jobQuestion.findFirst({
+ where: and(
+ eq(jobQuestion.id, questionId),
+ eq(jobQuestion.jobId, jobId),
+ eq(jobQuestion.organizationId, orgId),
+ ),
+ columns: {
+ type: true,
+ options: true,
+ },
+ })
+
+ if (!existing) {
+ throw createError({ statusCode: 404, statusMessage: 'Question not found' })
+ }
+
+ const mergedState = questionStateSchema.safeParse({
+ type: body.type ?? existing.type,
+ options: body.options === undefined ? existing.options : body.options,
+ })
+ if (!mergedState.success) {
+ throw createError({
+ statusCode: 400,
+ statusMessage: mergedState.error.issues[0]?.message ?? 'Invalid question',
+ })
+ }
+
const [updated] = await db.update(jobQuestion)
.set({
...body,
diff --git a/server/api/jobs/index.post.ts b/server/api/jobs/index.post.ts
index bdba394e..7e58ec3b 100644
--- a/server/api/jobs/index.post.ts
+++ b/server/api/jobs/index.post.ts
@@ -1,56 +1,94 @@
-import { job } from '../../database/schema'
-import { createJobSchema } from '../../utils/schemas/job'
+import { job, jobQuestion, scoringCriterion } from '../../database/schema'
+import { createJobWizardSchema } from '../../utils/schemas/job'
export default defineEventHandler(async (event) => {
const session = await requirePermission(event, { job: ['create'] })
const orgId = session.session.activeOrganizationId
- const body = await readValidatedBody(event, createJobSchema.parse)
+ const body = await readValidatedBody(event, createJobWizardSchema.parse)
// Generate a deterministic ID upfront so we can build the slug
const jobId = crypto.randomUUID()
const slug = generateJobSlug(body.title, jobId, body.slug)
- const [created] = await db.insert(job).values({
- id: jobId,
- organizationId: orgId,
- title: body.title,
- slug,
- description: body.description,
- location: body.location,
- type: body.type,
- salaryMin: body.salaryMin,
- salaryMax: body.salaryMax,
- salaryCurrency: body.salaryCurrency,
- salaryUnit: body.salaryUnit,
- salaryNegotiable: body.salaryNegotiable,
- remoteStatus: body.remoteStatus,
- validThrough: body.validThrough,
- requireResume: body.requireResume,
- requireCoverLetter: body.requireCoverLetter,
- autoScoreOnApply: body.autoScoreOnApply,
- experienceLevel: body.experienceLevel,
- }).returning({
- id: job.id,
- title: job.title,
- slug: job.slug,
- description: job.description,
- location: job.location,
- type: job.type,
- status: job.status,
- salaryMin: job.salaryMin,
- salaryMax: job.salaryMax,
- salaryCurrency: job.salaryCurrency,
- salaryUnit: job.salaryUnit,
- salaryNegotiable: job.salaryNegotiable,
- remoteStatus: job.remoteStatus,
- validThrough: job.validThrough,
- requireResume: job.requireResume,
- requireCoverLetter: job.requireCoverLetter,
- autoScoreOnApply: job.autoScoreOnApply,
- experienceLevel: job.experienceLevel,
- createdAt: job.createdAt,
- updatedAt: job.updatedAt,
+ const created = await db.transaction(async (tx) => {
+ const [createdJob] = await tx.insert(job).values({
+ id: jobId,
+ organizationId: orgId,
+ title: body.title,
+ slug,
+ description: body.description,
+ location: body.location,
+ type: body.type,
+ status: body.status,
+ salaryMin: body.salaryMin,
+ salaryMax: body.salaryMax,
+ salaryCurrency: body.salaryCurrency,
+ salaryUnit: body.salaryUnit,
+ salaryNegotiable: body.salaryNegotiable,
+ remoteStatus: body.remoteStatus,
+ validThrough: body.validThrough,
+ phoneRequirement: body.phoneRequirement,
+ requireResume: body.requireResume,
+ requireCoverLetter: body.requireCoverLetter,
+ autoScoreOnApply: body.autoScoreOnApply,
+ experienceLevel: body.experienceLevel,
+ }).returning({
+ id: job.id,
+ title: job.title,
+ slug: job.slug,
+ description: job.description,
+ location: job.location,
+ type: job.type,
+ status: job.status,
+ salaryMin: job.salaryMin,
+ salaryMax: job.salaryMax,
+ salaryCurrency: job.salaryCurrency,
+ salaryUnit: job.salaryUnit,
+ salaryNegotiable: job.salaryNegotiable,
+ remoteStatus: job.remoteStatus,
+ validThrough: job.validThrough,
+ phoneRequirement: job.phoneRequirement,
+ requireResume: job.requireResume,
+ requireCoverLetter: job.requireCoverLetter,
+ autoScoreOnApply: job.autoScoreOnApply,
+ experienceLevel: job.experienceLevel,
+ createdAt: job.createdAt,
+ updatedAt: job.updatedAt,
+ })
+
+ if (!createdJob) {
+ throw createError({ statusCode: 500, statusMessage: 'Failed to create job' })
+ }
+
+ if (body.questions.length) {
+ await tx.insert(jobQuestion).values(body.questions.map((question, index) => ({
+ organizationId: orgId,
+ jobId,
+ type: question.type,
+ label: question.label,
+ description: question.description,
+ required: question.required,
+ options: question.options,
+ displayOrder: index,
+ })))
+ }
+
+ if (body.criteria.length) {
+ await tx.insert(scoringCriterion).values(body.criteria.map((criterion, index) => ({
+ organizationId: orgId,
+ jobId,
+ key: criterion.key,
+ name: criterion.name,
+ description: criterion.description ?? null,
+ category: criterion.category,
+ maxScore: criterion.maxScore,
+ weight: criterion.weight,
+ displayOrder: index,
+ })))
+ }
+
+ return createdJob
})
if (!created) {
diff --git a/server/api/public/jobs/[slug].get.ts b/server/api/public/jobs/[slug].get.ts
index 8c1dea05..1264f918 100644
--- a/server/api/public/jobs/[slug].get.ts
+++ b/server/api/public/jobs/[slug].get.ts
@@ -28,6 +28,7 @@ export default defineEventHandler(async (event) => {
salaryNegotiable: true,
remoteStatus: true,
validThrough: true,
+ phoneRequirement: true,
requireResume: true,
requireCoverLetter: true,
createdAt: true,
diff --git a/server/api/public/jobs/[slug]/apply.post.ts b/server/api/public/jobs/[slug]/apply.post.ts
index 370f4dd3..c9d31b31 100644
--- a/server/api/public/jobs/[slug]/apply.post.ts
+++ b/server/api/public/jobs/[slug]/apply.post.ts
@@ -178,13 +178,27 @@ export default defineEventHandler(async (event) => {
const existingJob = await db.query.job.findFirst({
where: and(eq(job.slug, slug), eq(job.status, 'open')),
- columns: { id: true, organizationId: true, requireResume: true, requireCoverLetter: true, autoScoreOnApply: true },
+ columns: {
+ id: true,
+ organizationId: true,
+ phoneRequirement: true,
+ requireResume: true,
+ requireCoverLetter: true,
+ autoScoreOnApply: true,
+ },
})
if (!existingJob) {
throw createError({ statusCode: 404, statusMessage: 'Job not found or not accepting applications' })
}
+ if (existingJob.phoneRequirement === 'required' && !phone?.trim()) {
+ throw createError({ statusCode: 422, statusMessage: 'Phone number is required for this position' })
+ }
+ if (existingJob.phoneRequirement === 'hidden') {
+ phone = undefined
+ }
+
// Validate required resume
if (existingJob.requireResume && !resumeUpload) {
throw createError({ statusCode: 422, statusMessage: 'Resume/CV is required for this position' })
diff --git a/server/database/migrations/0029_wakeful_secret_warriors.sql b/server/database/migrations/0029_wakeful_secret_warriors.sql
new file mode 100644
index 00000000..de18e3c5
--- /dev/null
+++ b/server/database/migrations/0029_wakeful_secret_warriors.sql
@@ -0,0 +1 @@
+ALTER TABLE "job" ADD COLUMN "phone_requirement" text DEFAULT 'optional' NOT NULL;
\ No newline at end of file
diff --git a/server/database/migrations/meta/0029_snapshot.json b/server/database/migrations/meta/0029_snapshot.json
new file mode 100644
index 00000000..b067c7d8
--- /dev/null
+++ b/server/database/migrations/meta/0029_snapshot.json
@@ -0,0 +1,5379 @@
+{
+ "id": "4b18b04e-8187-4af5-8dee-05ae600f5f38",
+ "prevId": "4ffd7fcb-d7e0-4e49-8211-fa5b7305aff9",
+ "version": "7",
+ "dialect": "postgresql",
+ "tables": {
+ "public.account": {
+ "name": "account",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "account_id": {
+ "name": "account_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provider_id": {
+ "name": "provider_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "access_token": {
+ "name": "access_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token": {
+ "name": "refresh_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "id_token": {
+ "name": "id_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "access_token_expires_at": {
+ "name": "access_token_expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token_expires_at": {
+ "name": "refresh_token_expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "account_user_id_idx": {
+ "name": "account_user_id_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "account_user_id_user_id_fk": {
+ "name": "account_user_id_user_id_fk",
+ "tableFrom": "account",
+ "tableTo": "user",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.invitation": {
+ "name": "invitation",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "inviter_id": {
+ "name": "inviter_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role": {
+ "name": "role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'pending'"
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "invitation_organization_id_idx": {
+ "name": "invitation_organization_id_idx",
+ "columns": [
+ {
+ "expression": "organization_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "invitation_email_idx": {
+ "name": "invitation_email_idx",
+ "columns": [
+ {
+ "expression": "email",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "invitation_inviter_id_user_id_fk": {
+ "name": "invitation_inviter_id_user_id_fk",
+ "tableFrom": "invitation",
+ "tableTo": "user",
+ "columnsFrom": [
+ "inviter_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "invitation_organization_id_organization_id_fk": {
+ "name": "invitation_organization_id_organization_id_fk",
+ "tableFrom": "invitation",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organization_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.member": {
+ "name": "member",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role": {
+ "name": "role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'member'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "member_user_id_idx": {
+ "name": "member_user_id_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "member_organization_id_idx": {
+ "name": "member_organization_id_idx",
+ "columns": [
+ {
+ "expression": "organization_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "member_user_org_unique_idx": {
+ "name": "member_user_org_unique_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "organization_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "member_user_id_user_id_fk": {
+ "name": "member_user_id_user_id_fk",
+ "tableFrom": "member",
+ "tableTo": "user",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "member_organization_id_organization_id_fk": {
+ "name": "member_organization_id_organization_id_fk",
+ "tableFrom": "member",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organization_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.organization": {
+ "name": "organization",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "slug": {
+ "name": "slug",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "logo": {
+ "name": "logo",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "organization_slug_unique": {
+ "name": "organization_slug_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "slug"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.rate_limit": {
+ "name": "rate_limit",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "count": {
+ "name": "count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "last_request": {
+ "name": "last_request",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "rate_limit_key_idx": {
+ "name": "rate_limit_key_idx",
+ "columns": [
+ {
+ "expression": "key",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.session": {
+ "name": "session",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ip_address": {
+ "name": "ip_address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_agent": {
+ "name": "user_agent",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "active_organization_id": {
+ "name": "active_organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "session_user_id_idx": {
+ "name": "session_user_id_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "session_user_id_user_id_fk": {
+ "name": "session_user_id_user_id_fk",
+ "tableFrom": "session",
+ "tableTo": "user",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "session_token_unique": {
+ "name": "session_token_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "token"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.user": {
+ "name": "user",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "email_verified": {
+ "name": "email_verified",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "image": {
+ "name": "image",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "user_email_unique": {
+ "name": "user_email_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "email"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.verification": {
+ "name": "verification",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "identifier": {
+ "name": "identifier",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "verification_identifier_idx": {
+ "name": "verification_identifier_idx",
+ "columns": [
+ {
+ "expression": "identifier",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.activity_log": {
+ "name": "activity_log",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "actor_id": {
+ "name": "actor_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "action": {
+ "name": "action",
+ "type": "activity_action",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "resource_type": {
+ "name": "resource_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "resource_id": {
+ "name": "resource_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "activity_log_organization_id_idx": {
+ "name": "activity_log_organization_id_idx",
+ "columns": [
+ {
+ "expression": "organization_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "activity_log_actor_id_idx": {
+ "name": "activity_log_actor_id_idx",
+ "columns": [
+ {
+ "expression": "actor_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "activity_log_resource_idx": {
+ "name": "activity_log_resource_idx",
+ "columns": [
+ {
+ "expression": "resource_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "resource_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "activity_log_created_at_idx": {
+ "name": "activity_log_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "activity_log_organization_id_organization_id_fk": {
+ "name": "activity_log_organization_id_organization_id_fk",
+ "tableFrom": "activity_log",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organization_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "activity_log_actor_id_user_id_fk": {
+ "name": "activity_log_actor_id_user_id_fk",
+ "tableFrom": "activity_log",
+ "tableTo": "user",
+ "columnsFrom": [
+ "actor_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.ai_config": {
+ "name": "ai_config",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'Default'"
+ },
+ "provider": {
+ "name": "provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'openai'"
+ },
+ "model": {
+ "name": "model",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'gpt-4o-mini'"
+ },
+ "api_key_encrypted": {
+ "name": "api_key_encrypted",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "base_url": {
+ "name": "base_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "max_tokens": {
+ "name": "max_tokens",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 4096
+ },
+ "input_price_per_1m": {
+ "name": "input_price_per_1m",
+ "type": "numeric(10, 4)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "output_price_per_1m": {
+ "name": "output_price_per_1m",
+ "type": "numeric(10, 4)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_default_chatbot": {
+ "name": "is_default_chatbot",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "is_default_analysis": {
+ "name": "is_default_analysis",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "ai_config_organization_id_idx": {
+ "name": "ai_config_organization_id_idx",
+ "columns": [
+ {
+ "expression": "organization_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "ai_config_default_chatbot_idx": {
+ "name": "ai_config_default_chatbot_idx",
+ "columns": [
+ {
+ "expression": "organization_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"ai_config\".\"is_default_chatbot\" = true",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "ai_config_default_analysis_idx": {
+ "name": "ai_config_default_analysis_idx",
+ "columns": [
+ {
+ "expression": "organization_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"ai_config\".\"is_default_analysis\" = true",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "ai_config_organization_id_organization_id_fk": {
+ "name": "ai_config_organization_id_organization_id_fk",
+ "tableFrom": "ai_config",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organization_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.analysis_run": {
+ "name": "analysis_run",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "application_id": {
+ "name": "application_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "analysis_run_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'completed'"
+ },
+ "provider": {
+ "name": "provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "model": {
+ "name": "model",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "criteria_snapshot": {
+ "name": "criteria_snapshot",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "composite_score": {
+ "name": "composite_score",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "prompt_tokens": {
+ "name": "prompt_tokens",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "completion_tokens": {
+ "name": "completion_tokens",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "raw_response": {
+ "name": "raw_response",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "error_message": {
+ "name": "error_message",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "scored_by_id": {
+ "name": "scored_by_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "analysis_run_organization_id_idx": {
+ "name": "analysis_run_organization_id_idx",
+ "columns": [
+ {
+ "expression": "organization_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "analysis_run_application_id_idx": {
+ "name": "analysis_run_application_id_idx",
+ "columns": [
+ {
+ "expression": "application_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "analysis_run_created_at_idx": {
+ "name": "analysis_run_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "analysis_run_organization_id_organization_id_fk": {
+ "name": "analysis_run_organization_id_organization_id_fk",
+ "tableFrom": "analysis_run",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organization_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "analysis_run_application_id_application_id_fk": {
+ "name": "analysis_run_application_id_application_id_fk",
+ "tableFrom": "analysis_run",
+ "tableTo": "application",
+ "columnsFrom": [
+ "application_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "analysis_run_scored_by_id_user_id_fk": {
+ "name": "analysis_run_scored_by_id_user_id_fk",
+ "tableFrom": "analysis_run",
+ "tableTo": "user",
+ "columnsFrom": [
+ "scored_by_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.application": {
+ "name": "application",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "candidate_id": {
+ "name": "candidate_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "job_id": {
+ "name": "job_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "application_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'new'"
+ },
+ "score": {
+ "name": "score",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "notes": {
+ "name": "notes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cover_letter_text": {
+ "name": "cover_letter_text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "application_organization_id_idx": {
+ "name": "application_organization_id_idx",
+ "columns": [
+ {
+ "expression": "organization_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "application_candidate_id_idx": {
+ "name": "application_candidate_id_idx",
+ "columns": [
+ {
+ "expression": "candidate_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "application_job_id_idx": {
+ "name": "application_job_id_idx",
+ "columns": [
+ {
+ "expression": "job_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "application_org_candidate_job_idx": {
+ "name": "application_org_candidate_job_idx",
+ "columns": [
+ {
+ "expression": "organization_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "candidate_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "job_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "application_organization_id_organization_id_fk": {
+ "name": "application_organization_id_organization_id_fk",
+ "tableFrom": "application",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organization_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "application_candidate_id_candidate_id_fk": {
+ "name": "application_candidate_id_candidate_id_fk",
+ "tableFrom": "application",
+ "tableTo": "candidate",
+ "columnsFrom": [
+ "candidate_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "application_job_id_job_id_fk": {
+ "name": "application_job_id_job_id_fk",
+ "tableFrom": "application",
+ "tableTo": "job",
+ "columnsFrom": [
+ "job_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.application_source": {
+ "name": "application_source",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "application_id": {
+ "name": "application_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel": {
+ "name": "channel",
+ "type": "source_channel",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'direct'"
+ },
+ "tracking_link_id": {
+ "name": "tracking_link_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "utm_source": {
+ "name": "utm_source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "utm_medium": {
+ "name": "utm_medium",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "utm_campaign": {
+ "name": "utm_campaign",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "utm_term": {
+ "name": "utm_term",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "utm_content": {
+ "name": "utm_content",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "referrer_domain": {
+ "name": "referrer_domain",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "application_source_organization_id_idx": {
+ "name": "application_source_organization_id_idx",
+ "columns": [
+ {
+ "expression": "organization_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "application_source_application_id_idx": {
+ "name": "application_source_application_id_idx",
+ "columns": [
+ {
+ "expression": "application_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "application_source_channel_idx": {
+ "name": "application_source_channel_idx",
+ "columns": [
+ {
+ "expression": "channel",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "application_source_tracking_link_id_idx": {
+ "name": "application_source_tracking_link_id_idx",
+ "columns": [
+ {
+ "expression": "tracking_link_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "application_source_application_idx": {
+ "name": "application_source_application_idx",
+ "columns": [
+ {
+ "expression": "application_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "application_source_organization_id_organization_id_fk": {
+ "name": "application_source_organization_id_organization_id_fk",
+ "tableFrom": "application_source",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organization_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "application_source_application_id_application_id_fk": {
+ "name": "application_source_application_id_application_id_fk",
+ "tableFrom": "application_source",
+ "tableTo": "application",
+ "columnsFrom": [
+ "application_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "application_source_tracking_link_id_tracking_link_id_fk": {
+ "name": "application_source_tracking_link_id_tracking_link_id_fk",
+ "tableFrom": "application_source",
+ "tableTo": "tracking_link",
+ "columnsFrom": [
+ "tracking_link_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.calendar_integration": {
+ "name": "calendar_integration",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provider": {
+ "name": "provider",
+ "type": "calendar_provider",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'google'"
+ },
+ "access_token_encrypted": {
+ "name": "access_token_encrypted",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "refresh_token_encrypted": {
+ "name": "refresh_token_encrypted",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "calendar_id": {
+ "name": "calendar_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'primary'"
+ },
+ "account_email": {
+ "name": "account_email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "webhook_channel_id": {
+ "name": "webhook_channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "webhook_resource_id": {
+ "name": "webhook_resource_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "webhook_expiration": {
+ "name": "webhook_expiration",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sync_token": {
+ "name": "sync_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "calendar_integration_user_provider_idx": {
+ "name": "calendar_integration_user_provider_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "provider",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "calendar_integration_webhook_channel_idx": {
+ "name": "calendar_integration_webhook_channel_idx",
+ "columns": [
+ {
+ "expression": "webhook_channel_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "calendar_integration_user_id_user_id_fk": {
+ "name": "calendar_integration_user_id_user_id_fk",
+ "tableFrom": "calendar_integration",
+ "tableTo": "user",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.candidate": {
+ "name": "candidate",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "first_name": {
+ "name": "first_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "last_name": {
+ "name": "last_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "display_name": {
+ "name": "display_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "phone": {
+ "name": "phone",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gender": {
+ "name": "gender",
+ "type": "gender",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "date_of_birth": {
+ "name": "date_of_birth",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "quick_notes": {
+ "name": "quick_notes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "candidate_organization_id_idx": {
+ "name": "candidate_organization_id_idx",
+ "columns": [
+ {
+ "expression": "organization_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "candidate_gender_idx": {
+ "name": "candidate_gender_idx",
+ "columns": [
+ {
+ "expression": "organization_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "gender",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "candidate_org_email_idx": {
+ "name": "candidate_org_email_idx",
+ "columns": [
+ {
+ "expression": "organization_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "email",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "candidate_organization_id_organization_id_fk": {
+ "name": "candidate_organization_id_organization_id_fk",
+ "tableFrom": "candidate",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organization_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.chatbot_agent": {
+ "name": "chatbot_agent",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "icon": {
+ "name": "icon",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "system_prompt": {
+ "name": "system_prompt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "temperature": {
+ "name": "temperature",
+ "type": "numeric(3, 2)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_default": {
+ "name": "is_default",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "chatbot_agent_org_user_idx": {
+ "name": "chatbot_agent_org_user_idx",
+ "columns": [
+ {
+ "expression": "organization_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "chatbot_agent_default_per_user_idx": {
+ "name": "chatbot_agent_default_per_user_idx",
+ "columns": [
+ {
+ "expression": "organization_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"chatbot_agent\".\"is_default\" = true",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "chatbot_agent_organization_id_organization_id_fk": {
+ "name": "chatbot_agent_organization_id_organization_id_fk",
+ "tableFrom": "chatbot_agent",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organization_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "chatbot_agent_user_id_user_id_fk": {
+ "name": "chatbot_agent_user_id_user_id_fk",
+ "tableFrom": "chatbot_agent",
+ "tableTo": "user",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.chatbot_conversation": {
+ "name": "chatbot_conversation",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "folder_id": {
+ "name": "folder_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ai_config_id": {
+ "name": "ai_config_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'New chat'"
+ },
+ "scope": {
+ "name": "scope",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thinking": {
+ "name": "thinking",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "pinned": {
+ "name": "pinned",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "last_message_preview": {
+ "name": "last_message_preview",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_message_at": {
+ "name": "last_message_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "chatbot_conversation_org_user_idx": {
+ "name": "chatbot_conversation_org_user_idx",
+ "columns": [
+ {
+ "expression": "organization_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "chatbot_conversation_folder_idx": {
+ "name": "chatbot_conversation_folder_idx",
+ "columns": [
+ {
+ "expression": "folder_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "chatbot_conversation_last_message_at_idx": {
+ "name": "chatbot_conversation_last_message_at_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "last_message_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "chatbot_conversation_organization_id_organization_id_fk": {
+ "name": "chatbot_conversation_organization_id_organization_id_fk",
+ "tableFrom": "chatbot_conversation",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organization_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "chatbot_conversation_user_id_user_id_fk": {
+ "name": "chatbot_conversation_user_id_user_id_fk",
+ "tableFrom": "chatbot_conversation",
+ "tableTo": "user",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "chatbot_conversation_folder_id_chatbot_folder_id_fk": {
+ "name": "chatbot_conversation_folder_id_chatbot_folder_id_fk",
+ "tableFrom": "chatbot_conversation",
+ "tableTo": "chatbot_folder",
+ "columnsFrom": [
+ "folder_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "chatbot_conversation_agent_id_chatbot_agent_id_fk": {
+ "name": "chatbot_conversation_agent_id_chatbot_agent_id_fk",
+ "tableFrom": "chatbot_conversation",
+ "tableTo": "chatbot_agent",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "chatbot_conversation_ai_config_id_ai_config_id_fk": {
+ "name": "chatbot_conversation_ai_config_id_ai_config_id_fk",
+ "tableFrom": "chatbot_conversation",
+ "tableTo": "ai_config",
+ "columnsFrom": [
+ "ai_config_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.chatbot_folder": {
+ "name": "chatbot_folder",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "icon": {
+ "name": "icon",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "position": {
+ "name": "position",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "chatbot_folder_org_user_idx": {
+ "name": "chatbot_folder_org_user_idx",
+ "columns": [
+ {
+ "expression": "organization_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "chatbot_folder_organization_id_organization_id_fk": {
+ "name": "chatbot_folder_organization_id_organization_id_fk",
+ "tableFrom": "chatbot_folder",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organization_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "chatbot_folder_user_id_user_id_fk": {
+ "name": "chatbot_folder_user_id_user_id_fk",
+ "tableFrom": "chatbot_folder",
+ "tableTo": "user",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.chatbot_message": {
+ "name": "chatbot_message",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "conversation_id": {
+ "name": "conversation_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role": {
+ "name": "role",
+ "type": "chatbot_message_role",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "content": {
+ "name": "content",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "reasoning": {
+ "name": "reasoning",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tool_calls": {
+ "name": "tool_calls",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sources": {
+ "name": "sources",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "attachments": {
+ "name": "attachments",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "chatbot_message_conversation_idx": {
+ "name": "chatbot_message_conversation_idx",
+ "columns": [
+ {
+ "expression": "conversation_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "chatbot_message_conversation_id_chatbot_conversation_id_fk": {
+ "name": "chatbot_message_conversation_id_chatbot_conversation_id_fk",
+ "tableFrom": "chatbot_message",
+ "tableTo": "chatbot_conversation",
+ "columnsFrom": [
+ "conversation_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "chatbot_message_organization_id_organization_id_fk": {
+ "name": "chatbot_message_organization_id_organization_id_fk",
+ "tableFrom": "chatbot_message",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organization_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "chatbot_message_user_id_user_id_fk": {
+ "name": "chatbot_message_user_id_user_id_fk",
+ "tableFrom": "chatbot_message",
+ "tableTo": "user",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.comment": {
+ "name": "comment",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "author_id": {
+ "name": "author_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "target_type": {
+ "name": "target_type",
+ "type": "comment_target",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "target_id": {
+ "name": "target_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "body": {
+ "name": "body",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "comment_organization_id_idx": {
+ "name": "comment_organization_id_idx",
+ "columns": [
+ {
+ "expression": "organization_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "comment_target_idx": {
+ "name": "comment_target_idx",
+ "columns": [
+ {
+ "expression": "target_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "target_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "comment_author_id_idx": {
+ "name": "comment_author_id_idx",
+ "columns": [
+ {
+ "expression": "author_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "comment_organization_id_organization_id_fk": {
+ "name": "comment_organization_id_organization_id_fk",
+ "tableFrom": "comment",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organization_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "comment_author_id_user_id_fk": {
+ "name": "comment_author_id_user_id_fk",
+ "tableFrom": "comment",
+ "tableTo": "user",
+ "columnsFrom": [
+ "author_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.criterion_score": {
+ "name": "criterion_score",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "application_id": {
+ "name": "application_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "criterion_key": {
+ "name": "criterion_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "max_score": {
+ "name": "max_score",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "applicant_score": {
+ "name": "applicant_score",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "confidence": {
+ "name": "confidence",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "evidence": {
+ "name": "evidence",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "strengths": {
+ "name": "strengths",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gaps": {
+ "name": "gaps",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "criterion_score_organization_id_idx": {
+ "name": "criterion_score_organization_id_idx",
+ "columns": [
+ {
+ "expression": "organization_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "criterion_score_application_id_idx": {
+ "name": "criterion_score_application_id_idx",
+ "columns": [
+ {
+ "expression": "application_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "criterion_score_app_criterion_idx": {
+ "name": "criterion_score_app_criterion_idx",
+ "columns": [
+ {
+ "expression": "application_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "criterion_key",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "criterion_score_organization_id_organization_id_fk": {
+ "name": "criterion_score_organization_id_organization_id_fk",
+ "tableFrom": "criterion_score",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organization_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "criterion_score_application_id_application_id_fk": {
+ "name": "criterion_score_application_id_application_id_fk",
+ "tableFrom": "criterion_score",
+ "tableTo": "application",
+ "columnsFrom": [
+ "application_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.document": {
+ "name": "document",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "candidate_id": {
+ "name": "candidate_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "document_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'resume'"
+ },
+ "storage_key": {
+ "name": "storage_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "original_filename": {
+ "name": "original_filename",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "mime_type": {
+ "name": "mime_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "size_bytes": {
+ "name": "size_bytes",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "parsed_content": {
+ "name": "parsed_content",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "document_organization_id_idx": {
+ "name": "document_organization_id_idx",
+ "columns": [
+ {
+ "expression": "organization_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "document_candidate_id_idx": {
+ "name": "document_candidate_id_idx",
+ "columns": [
+ {
+ "expression": "candidate_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "document_organization_id_organization_id_fk": {
+ "name": "document_organization_id_organization_id_fk",
+ "tableFrom": "document",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organization_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "document_candidate_id_candidate_id_fk": {
+ "name": "document_candidate_id_candidate_id_fk",
+ "tableFrom": "document",
+ "tableTo": "candidate",
+ "columnsFrom": [
+ "candidate_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "document_storage_key_unique": {
+ "name": "document_storage_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "storage_key"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.email_template": {
+ "name": "email_template",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "subject": {
+ "name": "subject",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "body": {
+ "name": "body",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_by_id": {
+ "name": "created_by_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "email_template_organization_id_idx": {
+ "name": "email_template_organization_id_idx",
+ "columns": [
+ {
+ "expression": "organization_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "email_template_created_by_id_idx": {
+ "name": "email_template_created_by_id_idx",
+ "columns": [
+ {
+ "expression": "created_by_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "email_template_organization_id_organization_id_fk": {
+ "name": "email_template_organization_id_organization_id_fk",
+ "tableFrom": "email_template",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organization_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "email_template_created_by_id_user_id_fk": {
+ "name": "email_template_created_by_id_user_id_fk",
+ "tableFrom": "email_template",
+ "tableTo": "user",
+ "columnsFrom": [
+ "created_by_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.interview": {
+ "name": "interview",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "application_id": {
+ "name": "application_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "interview_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'video'"
+ },
+ "status": {
+ "name": "status",
+ "type": "interview_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'scheduled'"
+ },
+ "scheduled_at": {
+ "name": "scheduled_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "duration": {
+ "name": "duration",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 60
+ },
+ "location": {
+ "name": "location",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "notes": {
+ "name": "notes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "interviewers": {
+ "name": "interviewers",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_by_id": {
+ "name": "created_by_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "invitation_sent_at": {
+ "name": "invitation_sent_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "candidate_response": {
+ "name": "candidate_response",
+ "type": "candidate_response",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'pending'"
+ },
+ "candidate_responded_at": {
+ "name": "candidate_responded_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "google_calendar_event_id": {
+ "name": "google_calendar_event_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "google_calendar_event_link": {
+ "name": "google_calendar_event_link",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "timezone": {
+ "name": "timezone",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'UTC'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "interview_organization_id_idx": {
+ "name": "interview_organization_id_idx",
+ "columns": [
+ {
+ "expression": "organization_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "interview_application_id_idx": {
+ "name": "interview_application_id_idx",
+ "columns": [
+ {
+ "expression": "application_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "interview_scheduled_at_idx": {
+ "name": "interview_scheduled_at_idx",
+ "columns": [
+ {
+ "expression": "scheduled_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "interview_status_idx": {
+ "name": "interview_status_idx",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "interview_created_by_id_idx": {
+ "name": "interview_created_by_id_idx",
+ "columns": [
+ {
+ "expression": "created_by_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "interview_organization_id_organization_id_fk": {
+ "name": "interview_organization_id_organization_id_fk",
+ "tableFrom": "interview",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organization_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "interview_application_id_application_id_fk": {
+ "name": "interview_application_id_application_id_fk",
+ "tableFrom": "interview",
+ "tableTo": "application",
+ "columnsFrom": [
+ "application_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "interview_created_by_id_user_id_fk": {
+ "name": "interview_created_by_id_user_id_fk",
+ "tableFrom": "interview",
+ "tableTo": "user",
+ "columnsFrom": [
+ "created_by_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.invite_link": {
+ "name": "invite_link",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_by_id": {
+ "name": "created_by_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role": {
+ "name": "role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'member'"
+ },
+ "max_uses": {
+ "name": "max_uses",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "use_count": {
+ "name": "use_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "revoked_at": {
+ "name": "revoked_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "invite_link_organization_id_idx": {
+ "name": "invite_link_organization_id_idx",
+ "columns": [
+ {
+ "expression": "organization_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "invite_link_token_idx": {
+ "name": "invite_link_token_idx",
+ "columns": [
+ {
+ "expression": "token",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "invite_link_organization_id_organization_id_fk": {
+ "name": "invite_link_organization_id_organization_id_fk",
+ "tableFrom": "invite_link",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organization_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "invite_link_created_by_id_user_id_fk": {
+ "name": "invite_link_created_by_id_user_id_fk",
+ "tableFrom": "invite_link",
+ "tableTo": "user",
+ "columnsFrom": [
+ "created_by_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "invite_link_token_unique": {
+ "name": "invite_link_token_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "token"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.job": {
+ "name": "job",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "slug": {
+ "name": "slug",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "location": {
+ "name": "location",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "type": {
+ "name": "type",
+ "type": "job_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'full_time'"
+ },
+ "status": {
+ "name": "status",
+ "type": "job_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'draft'"
+ },
+ "salary_min": {
+ "name": "salary_min",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "salary_max": {
+ "name": "salary_max",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "salary_currency": {
+ "name": "salary_currency",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "salary_unit": {
+ "name": "salary_unit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "salary_negotiable": {
+ "name": "salary_negotiable",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "remote_status": {
+ "name": "remote_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "valid_through": {
+ "name": "valid_through",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "experience_level": {
+ "name": "experience_level",
+ "type": "experience_level",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "phone_requirement": {
+ "name": "phone_requirement",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'optional'"
+ },
+ "require_resume": {
+ "name": "require_resume",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "require_cover_letter": {
+ "name": "require_cover_letter",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "auto_score_on_apply": {
+ "name": "auto_score_on_apply",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "job_organization_id_idx": {
+ "name": "job_organization_id_idx",
+ "columns": [
+ {
+ "expression": "organization_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "job_organization_id_organization_id_fk": {
+ "name": "job_organization_id_organization_id_fk",
+ "tableFrom": "job",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organization_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "job_slug_unique": {
+ "name": "job_slug_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "slug"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.job_question": {
+ "name": "job_question",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "job_id": {
+ "name": "job_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "question_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'short_text'"
+ },
+ "label": {
+ "name": "label",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "required": {
+ "name": "required",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "options": {
+ "name": "options",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "display_order": {
+ "name": "display_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "job_question_organization_id_idx": {
+ "name": "job_question_organization_id_idx",
+ "columns": [
+ {
+ "expression": "organization_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "job_question_job_id_idx": {
+ "name": "job_question_job_id_idx",
+ "columns": [
+ {
+ "expression": "job_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "job_question_organization_id_organization_id_fk": {
+ "name": "job_question_organization_id_organization_id_fk",
+ "tableFrom": "job_question",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organization_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "job_question_job_id_job_id_fk": {
+ "name": "job_question_job_id_job_id_fk",
+ "tableFrom": "job_question",
+ "tableTo": "job",
+ "columnsFrom": [
+ "job_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.join_request": {
+ "name": "join_request",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "message": {
+ "name": "message",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "join_request_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'pending'"
+ },
+ "reviewed_by_id": {
+ "name": "reviewed_by_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "reviewed_at": {
+ "name": "reviewed_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "join_request_organization_id_idx": {
+ "name": "join_request_organization_id_idx",
+ "columns": [
+ {
+ "expression": "organization_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "join_request_user_id_idx": {
+ "name": "join_request_user_id_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "join_request_status_idx": {
+ "name": "join_request_status_idx",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "join_request_user_id_user_id_fk": {
+ "name": "join_request_user_id_user_id_fk",
+ "tableFrom": "join_request",
+ "tableTo": "user",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "join_request_organization_id_organization_id_fk": {
+ "name": "join_request_organization_id_organization_id_fk",
+ "tableFrom": "join_request",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organization_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "join_request_reviewed_by_id_user_id_fk": {
+ "name": "join_request_reviewed_by_id_user_id_fk",
+ "tableFrom": "join_request",
+ "tableTo": "user",
+ "columnsFrom": [
+ "reviewed_by_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.org_settings": {
+ "name": "org_settings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name_display_format": {
+ "name": "name_display_format",
+ "type": "name_display_format",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'first_last'"
+ },
+ "date_format": {
+ "name": "date_format",
+ "type": "date_format",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'mdy'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "org_settings_organization_id_idx": {
+ "name": "org_settings_organization_id_idx",
+ "columns": [
+ {
+ "expression": "organization_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "org_settings_organization_id_organization_id_fk": {
+ "name": "org_settings_organization_id_organization_id_fk",
+ "tableFrom": "org_settings",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organization_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.property_definition": {
+ "name": "property_definition",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "job_id": {
+ "name": "job_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "entity_type": {
+ "name": "entity_type",
+ "type": "property_entity_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "property_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "display_order": {
+ "name": "display_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "config": {
+ "name": "config",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "property_definition_org_idx": {
+ "name": "property_definition_org_idx",
+ "columns": [
+ {
+ "expression": "organization_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "property_definition_org_entity_idx": {
+ "name": "property_definition_org_entity_idx",
+ "columns": [
+ {
+ "expression": "organization_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "entity_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "property_definition_job_idx": {
+ "name": "property_definition_job_idx",
+ "columns": [
+ {
+ "expression": "job_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "property_definition_organization_id_organization_id_fk": {
+ "name": "property_definition_organization_id_organization_id_fk",
+ "tableFrom": "property_definition",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organization_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "property_definition_job_id_job_id_fk": {
+ "name": "property_definition_job_id_job_id_fk",
+ "tableFrom": "property_definition",
+ "tableTo": "job",
+ "columnsFrom": [
+ "job_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.property_value": {
+ "name": "property_value",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "property_definition_id": {
+ "name": "property_definition_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "entity_type": {
+ "name": "entity_type",
+ "type": "property_entity_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "entity_id": {
+ "name": "entity_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "property_value_org_idx": {
+ "name": "property_value_org_idx",
+ "columns": [
+ {
+ "expression": "organization_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "property_value_entity_idx": {
+ "name": "property_value_entity_idx",
+ "columns": [
+ {
+ "expression": "entity_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "entity_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "property_value_definition_idx": {
+ "name": "property_value_definition_idx",
+ "columns": [
+ {
+ "expression": "property_definition_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "property_value_def_entity_idx": {
+ "name": "property_value_def_entity_idx",
+ "columns": [
+ {
+ "expression": "property_definition_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "entity_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "property_value_organization_id_organization_id_fk": {
+ "name": "property_value_organization_id_organization_id_fk",
+ "tableFrom": "property_value",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organization_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "property_value_property_definition_id_property_definition_id_fk": {
+ "name": "property_value_property_definition_id_property_definition_id_fk",
+ "tableFrom": "property_value",
+ "tableTo": "property_definition",
+ "columnsFrom": [
+ "property_definition_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.question_response": {
+ "name": "question_response",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "application_id": {
+ "name": "application_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "question_id": {
+ "name": "question_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "question_response_organization_id_idx": {
+ "name": "question_response_organization_id_idx",
+ "columns": [
+ {
+ "expression": "organization_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "question_response_application_id_idx": {
+ "name": "question_response_application_id_idx",
+ "columns": [
+ {
+ "expression": "application_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "question_response_question_id_idx": {
+ "name": "question_response_question_id_idx",
+ "columns": [
+ {
+ "expression": "question_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "question_response_organization_id_organization_id_fk": {
+ "name": "question_response_organization_id_organization_id_fk",
+ "tableFrom": "question_response",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organization_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "question_response_application_id_application_id_fk": {
+ "name": "question_response_application_id_application_id_fk",
+ "tableFrom": "question_response",
+ "tableTo": "application",
+ "columnsFrom": [
+ "application_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "question_response_question_id_job_question_id_fk": {
+ "name": "question_response_question_id_job_question_id_fk",
+ "tableFrom": "question_response",
+ "tableTo": "job_question",
+ "columnsFrom": [
+ "question_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.scoring_criterion": {
+ "name": "scoring_criterion",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "job_id": {
+ "name": "job_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "category": {
+ "name": "category",
+ "type": "criterion_category",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'custom'"
+ },
+ "max_score": {
+ "name": "max_score",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 10
+ },
+ "weight": {
+ "name": "weight",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 50
+ },
+ "display_order": {
+ "name": "display_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "scoring_criterion_organization_id_idx": {
+ "name": "scoring_criterion_organization_id_idx",
+ "columns": [
+ {
+ "expression": "organization_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scoring_criterion_job_id_idx": {
+ "name": "scoring_criterion_job_id_idx",
+ "columns": [
+ {
+ "expression": "job_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scoring_criterion_job_key_idx": {
+ "name": "scoring_criterion_job_key_idx",
+ "columns": [
+ {
+ "expression": "job_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "key",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "scoring_criterion_organization_id_organization_id_fk": {
+ "name": "scoring_criterion_organization_id_organization_id_fk",
+ "tableFrom": "scoring_criterion",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organization_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "scoring_criterion_job_id_job_id_fk": {
+ "name": "scoring_criterion_job_id_job_id_fk",
+ "tableFrom": "scoring_criterion",
+ "tableTo": "job",
+ "columnsFrom": [
+ "job_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.tracking_link": {
+ "name": "tracking_link",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "job_id": {
+ "name": "job_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "channel": {
+ "name": "channel",
+ "type": "source_channel",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'custom'"
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "code": {
+ "name": "code",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "utm_source": {
+ "name": "utm_source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "utm_medium": {
+ "name": "utm_medium",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "utm_campaign": {
+ "name": "utm_campaign",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "utm_term": {
+ "name": "utm_term",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "utm_content": {
+ "name": "utm_content",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "click_count": {
+ "name": "click_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "application_count": {
+ "name": "application_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "is_active": {
+ "name": "is_active",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "created_by_id": {
+ "name": "created_by_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "tracking_link_organization_id_idx": {
+ "name": "tracking_link_organization_id_idx",
+ "columns": [
+ {
+ "expression": "organization_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "tracking_link_job_id_idx": {
+ "name": "tracking_link_job_id_idx",
+ "columns": [
+ {
+ "expression": "job_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "tracking_link_code_idx": {
+ "name": "tracking_link_code_idx",
+ "columns": [
+ {
+ "expression": "code",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "tracking_link_channel_idx": {
+ "name": "tracking_link_channel_idx",
+ "columns": [
+ {
+ "expression": "channel",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "tracking_link_organization_id_organization_id_fk": {
+ "name": "tracking_link_organization_id_organization_id_fk",
+ "tableFrom": "tracking_link",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organization_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "tracking_link_job_id_job_id_fk": {
+ "name": "tracking_link_job_id_job_id_fk",
+ "tableFrom": "tracking_link",
+ "tableTo": "job",
+ "columnsFrom": [
+ "job_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "tracking_link_created_by_id_user_id_fk": {
+ "name": "tracking_link_created_by_id_user_id_fk",
+ "tableFrom": "tracking_link",
+ "tableTo": "user",
+ "columnsFrom": [
+ "created_by_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "tracking_link_code_unique": {
+ "name": "tracking_link_code_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "code"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.sso_provider": {
+ "name": "sso_provider",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "issuer": {
+ "name": "issuer",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "domain": {
+ "name": "domain",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "oidc_config": {
+ "name": "oidc_config",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "saml_config": {
+ "name": "saml_config",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provider_id": {
+ "name": "provider_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "sso_provider_domain_idx": {
+ "name": "sso_provider_domain_idx",
+ "columns": [
+ {
+ "expression": "domain",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "sso_provider_provider_id_idx": {
+ "name": "sso_provider_provider_id_idx",
+ "columns": [
+ {
+ "expression": "provider_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "sso_provider_organization_id_idx": {
+ "name": "sso_provider_organization_id_idx",
+ "columns": [
+ {
+ "expression": "organization_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "sso_provider_user_id_user_id_fk": {
+ "name": "sso_provider_user_id_user_id_fk",
+ "tableFrom": "sso_provider",
+ "tableTo": "user",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "sso_provider_organization_id_organization_id_fk": {
+ "name": "sso_provider_organization_id_organization_id_fk",
+ "tableFrom": "sso_provider",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organization_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ }
+ },
+ "enums": {
+ "public.activity_action": {
+ "name": "activity_action",
+ "schema": "public",
+ "values": [
+ "created",
+ "updated",
+ "deleted",
+ "status_changed",
+ "comment_added",
+ "member_invited",
+ "member_removed",
+ "member_role_changed",
+ "scored"
+ ]
+ },
+ "public.analysis_run_status": {
+ "name": "analysis_run_status",
+ "schema": "public",
+ "values": [
+ "completed",
+ "failed",
+ "partial"
+ ]
+ },
+ "public.application_status": {
+ "name": "application_status",
+ "schema": "public",
+ "values": [
+ "new",
+ "screening",
+ "interview",
+ "offer",
+ "hired",
+ "rejected"
+ ]
+ },
+ "public.calendar_provider": {
+ "name": "calendar_provider",
+ "schema": "public",
+ "values": [
+ "google"
+ ]
+ },
+ "public.candidate_response": {
+ "name": "candidate_response",
+ "schema": "public",
+ "values": [
+ "pending",
+ "accepted",
+ "declined",
+ "tentative"
+ ]
+ },
+ "public.chatbot_message_role": {
+ "name": "chatbot_message_role",
+ "schema": "public",
+ "values": [
+ "user",
+ "assistant"
+ ]
+ },
+ "public.comment_target": {
+ "name": "comment_target",
+ "schema": "public",
+ "values": [
+ "candidate",
+ "application",
+ "job"
+ ]
+ },
+ "public.criterion_category": {
+ "name": "criterion_category",
+ "schema": "public",
+ "values": [
+ "technical",
+ "experience",
+ "soft_skills",
+ "education",
+ "culture",
+ "custom"
+ ]
+ },
+ "public.date_format": {
+ "name": "date_format",
+ "schema": "public",
+ "values": [
+ "mdy",
+ "dmy",
+ "ymd"
+ ]
+ },
+ "public.document_type": {
+ "name": "document_type",
+ "schema": "public",
+ "values": [
+ "resume",
+ "cover_letter",
+ "other"
+ ]
+ },
+ "public.experience_level": {
+ "name": "experience_level",
+ "schema": "public",
+ "values": [
+ "junior",
+ "mid",
+ "senior",
+ "lead"
+ ]
+ },
+ "public.gender": {
+ "name": "gender",
+ "schema": "public",
+ "values": [
+ "male",
+ "female",
+ "other",
+ "prefer_not_to_say"
+ ]
+ },
+ "public.interview_status": {
+ "name": "interview_status",
+ "schema": "public",
+ "values": [
+ "scheduled",
+ "completed",
+ "cancelled",
+ "no_show"
+ ]
+ },
+ "public.interview_type": {
+ "name": "interview_type",
+ "schema": "public",
+ "values": [
+ "phone",
+ "video",
+ "in_person",
+ "panel",
+ "technical",
+ "take_home"
+ ]
+ },
+ "public.job_status": {
+ "name": "job_status",
+ "schema": "public",
+ "values": [
+ "draft",
+ "open",
+ "closed",
+ "archived"
+ ]
+ },
+ "public.job_type": {
+ "name": "job_type",
+ "schema": "public",
+ "values": [
+ "full_time",
+ "part_time",
+ "contract",
+ "internship"
+ ]
+ },
+ "public.join_request_status": {
+ "name": "join_request_status",
+ "schema": "public",
+ "values": [
+ "pending",
+ "approved",
+ "rejected"
+ ]
+ },
+ "public.name_display_format": {
+ "name": "name_display_format",
+ "schema": "public",
+ "values": [
+ "first_last",
+ "last_first"
+ ]
+ },
+ "public.property_entity_type": {
+ "name": "property_entity_type",
+ "schema": "public",
+ "values": [
+ "candidate",
+ "application"
+ ]
+ },
+ "public.property_type": {
+ "name": "property_type",
+ "schema": "public",
+ "values": [
+ "text",
+ "long_text",
+ "number",
+ "select",
+ "multi_select",
+ "date",
+ "checkbox",
+ "url",
+ "email",
+ "person",
+ "file"
+ ]
+ },
+ "public.question_type": {
+ "name": "question_type",
+ "schema": "public",
+ "values": [
+ "short_text",
+ "long_text",
+ "single_select",
+ "multi_select",
+ "number",
+ "date",
+ "url",
+ "checkbox",
+ "file_upload"
+ ]
+ },
+ "public.source_channel": {
+ "name": "source_channel",
+ "schema": "public",
+ "values": [
+ "linkedin",
+ "indeed",
+ "glassdoor",
+ "ziprecruiter",
+ "monster",
+ "handshake",
+ "angellist",
+ "wellfound",
+ "dice",
+ "stackoverflow",
+ "weworkremotely",
+ "remoteok",
+ "builtin",
+ "hired",
+ "lever",
+ "greenhouse_board",
+ "google_jobs",
+ "facebook",
+ "twitter",
+ "instagram",
+ "tiktok",
+ "reddit",
+ "referral",
+ "career_site",
+ "email",
+ "event",
+ "agency",
+ "direct",
+ "other",
+ "custom"
+ ]
+ }
+ },
+ "schemas": {},
+ "sequences": {},
+ "roles": {},
+ "policies": {},
+ "views": {},
+ "_meta": {
+ "columns": {},
+ "schemas": {},
+ "tables": {}
+ }
+}
\ No newline at end of file
diff --git a/server/database/migrations/meta/_journal.json b/server/database/migrations/meta/_journal.json
index 2fec9f78..894d47d7 100644
--- a/server/database/migrations/meta/_journal.json
+++ b/server/database/migrations/meta/_journal.json
@@ -204,6 +204,13 @@
"when": 1777463601249,
"tag": "0028_custom_properties",
"breakpoints": true
+ },
+ {
+ "idx": 29,
+ "version": "7",
+ "when": 1781773504170,
+ "tag": "0029_wakeful_secret_warriors",
+ "breakpoints": true
}
]
}
\ No newline at end of file
diff --git a/server/database/schema/app.ts b/server/database/schema/app.ts
index f450351d..7637328f 100644
--- a/server/database/schema/app.ts
+++ b/server/database/schema/app.ts
@@ -64,6 +64,7 @@ export const job = pgTable('job', {
/** Experience level required for this role */
experienceLevel: experienceLevelEnum('experience_level'),
// ── Application form settings ──
+ phoneRequirement: text('phone_requirement').$type<'hidden' | 'optional' | 'required'>().notNull().default('optional'),
requireResume: boolean('require_resume').notNull().default(false),
requireCoverLetter: boolean('require_cover_letter').notNull().default(false),
// ── AI scoring settings ──
diff --git a/server/utils/auth.ts b/server/utils/auth.ts
index 6639ccd4..26cbe66e 100644
--- a/server/utils/auth.ts
+++ b/server/utils/auth.ts
@@ -247,7 +247,9 @@ function getAuth(): Auth {
// Disabled in CI/test (GITHUB_ACTIONS or NODE_ENV !== 'production')
// to prevent E2E test flakiness.
rateLimit: {
- enabled: !process.env.CI && !process.env.GITHUB_ACTIONS,
+ enabled: process.env.NODE_ENV === "production"
+ && !process.env.CI
+ && !process.env.GITHUB_ACTIONS,
window: 60,
max: 100, // 100 requests per minute per IP — stops bots, not humans
storage: "database",
diff --git a/server/utils/schemas/job.ts b/server/utils/schemas/job.ts
index 537ceef8..a0304d7a 100644
--- a/server/utils/schemas/job.ts
+++ b/server/utils/schemas/job.ts
@@ -1,6 +1,8 @@
import { z } from 'zod'
+import { createQuestionSchema } from './jobQuestion'
+import { createCriterionSchema } from './scoring'
-export { JOB_STATUS_TRANSITIONS } from '~~/shared/status-transitions'
+export { JOB_STATUS_TRANSITIONS } from '../../../shared/status-transitions'
// ─────────────────────────────────────────────
// Job validation schemas — shared across API routes
@@ -8,9 +10,9 @@ export { JOB_STATUS_TRANSITIONS } from '~~/shared/status-transitions'
/** Schema for creating a new job */
export const createJobSchema = z.object({
- title: z.string().min(1, 'Title is required').max(200),
- description: z.string().optional(),
- location: z.string().optional(),
+ title: z.string().trim().min(1, 'Title is required').max(200),
+ description: z.string().trim().max(100_000).optional(),
+ location: z.string().trim().max(500).optional(),
type: z.enum(['full_time', 'part_time', 'contract', 'internship']).default('full_time'),
/** Optional custom slug — if omitted, generated from title */
slug: z.string().max(80).optional(),
@@ -27,6 +29,8 @@ export const createJobSchema = z.object({
validThrough: z.coerce.date().nullable().optional(),
/** Whether the application form requires a resume/CV upload */
requireResume: z.boolean().optional().default(false),
+ /** Whether phone is hidden, optional, or required on the application form */
+ phoneRequirement: z.enum(['hidden', 'optional', 'required']).optional().default('optional'),
/** Whether the application form asks for a cover letter upload */
requireCoverLetter: z.boolean().optional().default(false),
/** Whether to automatically run AI scoring when a candidate applies */
@@ -35,11 +39,39 @@ export const createJobSchema = z.object({
experienceLevel: z.enum(['junior', 'mid', 'senior', 'lead']).optional(),
})
+/**
+ * Atomic payload used by the create-job wizard. Keeping related records in the
+ * same transaction prevents retries from leaving duplicate or partially-built
+ * jobs behind.
+ */
+export const createJobWizardSchema = createJobSchema.extend({
+ status: z.enum(['draft', 'open']).optional().default('draft'),
+ questions: z.array(createQuestionSchema).max(50).optional().default([]),
+ criteria: z.array(createCriterionSchema).max(20).optional().default([]),
+}).superRefine((data, ctx) => {
+ const criterionKeys = data.criteria.map(criterion => criterion.key)
+ if (new Set(criterionKeys).size !== criterionKeys.length) {
+ ctx.addIssue({
+ code: 'custom',
+ message: 'Scoring criterion keys must be unique',
+ path: ['criteria'],
+ })
+ }
+
+ if (data.autoScoreOnApply && data.criteria.length === 0) {
+ ctx.addIssue({
+ code: 'custom',
+ message: 'At least one scoring criterion is required when automatic scoring is enabled',
+ path: ['criteria'],
+ })
+ }
+})
+
/** Schema for updating an existing job (all fields optional, no defaults — PATCH semantics) */
export const updateJobSchema = z.object({
- title: z.string().min(1, 'Title is required').max(200).optional(),
- description: z.string().nullable().optional(),
- location: z.string().nullable().optional(),
+ title: z.string().trim().min(1, 'Title is required').max(200).optional(),
+ description: z.string().trim().max(100_000).nullable().optional(),
+ location: z.string().trim().max(500).nullable().optional(),
type: z.enum(['full_time', 'part_time', 'contract', 'internship']).optional(),
slug: z.string().max(80).optional(),
/** Pass null to explicitly clear a salary field */
@@ -52,6 +84,7 @@ export const updateJobSchema = z.object({
/** Pass null to explicitly clear the expiry date */
validThrough: z.coerce.date().nullable().optional(),
requireResume: z.boolean().optional(),
+ phoneRequirement: z.enum(['hidden', 'optional', 'required']).optional(),
requireCoverLetter: z.boolean().optional(),
/** Whether to automatically run AI scoring when a candidate applies */
autoScoreOnApply: z.boolean().optional(),
diff --git a/server/utils/schemas/jobQuestion.ts b/server/utils/schemas/jobQuestion.ts
index ee290374..539a7a69 100644
--- a/server/utils/schemas/jobQuestion.ts
+++ b/server/utils/schemas/jobQuestion.ts
@@ -6,35 +6,57 @@ import { z } from 'zod'
const questionTypes = ['short_text', 'long_text', 'single_select', 'multi_select', 'number', 'date', 'url', 'checkbox', 'file_upload'] as const
+function validateSelectOptions(
+ data: { type: typeof questionTypes[number], options?: string[] | null },
+ ctx: z.RefinementCtx,
+) {
+ if (data.type !== 'single_select' && data.type !== 'multi_select') return
+
+ if (!data.options?.length) {
+ ctx.addIssue({
+ code: 'custom',
+ message: 'Options are required for select-type questions',
+ path: ['options'],
+ })
+ return
+ }
+
+ const normalized = data.options.map(option => option.toLocaleLowerCase())
+ if (new Set(normalized).size !== normalized.length) {
+ ctx.addIssue({
+ code: 'custom',
+ message: 'Options must be unique',
+ path: ['options'],
+ })
+ }
+}
+
/** Schema for creating a new custom question */
export const createQuestionSchema = z.object({
- label: z.string().min(1, 'Label is required').max(500),
+ label: z.string().trim().min(1, 'Label is required').max(500),
type: z.enum(questionTypes).default('short_text'),
- description: z.string().max(1000).optional(),
+ description: z.string().trim().max(1000).optional(),
required: z.boolean().default(false),
- options: z.array(z.string().min(1).max(200)).min(1).optional(),
+ options: z.array(z.string().trim().min(1).max(200)).min(1).max(50).optional(),
displayOrder: z.number().int().min(0).default(0),
-}).refine(
- (data) => {
- // options required for select types
- if (data.type === 'single_select' || data.type === 'multi_select') {
- return data.options && data.options.length >= 1
- }
- return true
- },
- { message: 'Options are required for select-type questions', path: ['options'] },
-)
+}).superRefine(validateSelectOptions)
/** Schema for updating an existing question (all fields optional) */
export const updateQuestionSchema = z.object({
- label: z.string().min(1).max(500).optional(),
+ label: z.string().trim().min(1).max(500).optional(),
type: z.enum(questionTypes).optional(),
- description: z.string().max(1000).nullish(),
+ description: z.string().trim().max(1000).nullish(),
required: z.boolean().optional(),
- options: z.array(z.string().min(1).max(200)).min(1).nullish(),
+ options: z.array(z.string().trim().min(1).max(200)).min(1).max(50).nullish(),
displayOrder: z.number().int().min(0).optional(),
})
+/** Validates invariants after an existing question and a partial update are merged. */
+export const questionStateSchema = z.object({
+ type: z.enum(questionTypes),
+ options: z.array(z.string().trim().min(1).max(200)).min(1).max(50).nullish(),
+}).superRefine(validateSelectOptions)
+
/** Schema for bulk reordering questions */
export const reorderQuestionsSchema = z.object({
order: z.array(
diff --git a/server/utils/schemas/scoring.ts b/server/utils/schemas/scoring.ts
index b2fbdc20..55c49aa2 100644
--- a/server/utils/schemas/scoring.ts
+++ b/server/utils/schemas/scoring.ts
@@ -49,10 +49,11 @@ const criterionCategoryValues = ['technical', 'experience', 'soft_skills', 'educ
export const createCriterionSchema = z.object({
key: z.string()
+ .trim()
.min(1).max(100)
.regex(/^[a-z][a-z0-9_]*$/, 'Key must be lowercase alphanumeric with underscores, starting with a letter'),
- name: z.string().min(1).max(200),
- description: z.string().max(1000).nullish(),
+ name: z.string().trim().min(1).max(200),
+ description: z.string().trim().max(1000).nullish(),
category: z.enum(criterionCategoryValues).optional().default('custom'),
maxScore: z.number().int().min(1).max(100).optional().default(10),
weight: z.number().int().min(0).max(100).optional().default(50),
diff --git a/tests/unit/job-creation-schema.test.ts b/tests/unit/job-creation-schema.test.ts
new file mode 100644
index 00000000..9c140dd9
--- /dev/null
+++ b/tests/unit/job-creation-schema.test.ts
@@ -0,0 +1,81 @@
+import { describe, expect, it } from 'vitest'
+import { createJobSchema, createJobWizardSchema } from '../../server/utils/schemas/job'
+import { createQuestionSchema, questionStateSchema } from '../../server/utils/schemas/jobQuestion'
+
+describe('job creation validation', () => {
+ it('rejects whitespace-only titles and trims valid text fields', () => {
+ expect(createJobSchema.safeParse({ title: ' ' }).success).toBe(false)
+
+ const result = createJobSchema.parse({
+ title: ' Senior QA Engineer ',
+ description: ' Build reliable systems. ',
+ location: ' Remote ',
+ })
+
+ expect(result.title).toBe('Senior QA Engineer')
+ expect(result.description).toBe('Build reliable systems.')
+ expect(result.location).toBe('Remote')
+ })
+
+ it('rejects duplicate select options after trimming and case folding', () => {
+ const result = createQuestionSchema.safeParse({
+ label: 'Preferred framework?',
+ type: 'single_select',
+ options: ['Playwright', ' playwright '],
+ })
+
+ expect(result.success).toBe(false)
+ })
+
+ it('rejects invalid select question state after applying a partial update', () => {
+ expect(questionStateSchema.safeParse({
+ type: 'single_select',
+ options: null,
+ }).success).toBe(false)
+
+ expect(questionStateSchema.safeParse({
+ type: 'multi_select',
+ options: ['Playwright', ' playwright '],
+ }).success).toBe(false)
+
+ expect(questionStateSchema.safeParse({
+ type: 'single_select',
+ options: ['Playwright'],
+ }).success).toBe(true)
+ })
+
+ it('rejects impossible automatic-scoring and duplicate-criterion payloads', () => {
+ expect(createJobWizardSchema.safeParse({
+ title: 'QA Engineer',
+ autoScoreOnApply: true,
+ criteria: [],
+ }).success).toBe(false)
+
+ const criterion = {
+ key: 'quality',
+ name: 'Quality',
+ category: 'technical' as const,
+ maxScore: 10,
+ weight: 50,
+ displayOrder: 0,
+ }
+ expect(createJobWizardSchema.safeParse({
+ title: 'QA Engineer',
+ criteria: [criterion, { ...criterion, name: 'Quality duplicate' }],
+ }).success).toBe(false)
+ })
+
+ it('caps nested records before they reach the transaction', () => {
+ const questions = Array.from({ length: 51 }, (_, index) => ({
+ label: `Question ${index}`,
+ type: 'short_text' as const,
+ required: false,
+ displayOrder: index,
+ }))
+
+ expect(createJobWizardSchema.safeParse({
+ title: 'QA Engineer',
+ questions,
+ }).success).toBe(false)
+ })
+})