My Jobs
@@ -186,156 +410,588 @@ const isEmpty = computed(() => jobs.value.length === 0)
-
-
-
-
-
- Needs your attention
-
-
- {{ jobsNeedingAttention.length }} job{{ jobsNeedingAttention.length === 1 ? '' : 's' }}
-
+
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ opt.label }}
+
+
-
-
-
- {{ j.pipeline.new }} new application{{ j.pipeline.new === 1 ? '' : 's' }} to review
-
-
+
+
+
+
-
- Review in Pipeline
-
+ {{ opt.label }}
+
+
+
+
+
+
+
+
+
+ {{ opt.label }}
+
+
+
+
+
+
+
+
+
+ {{ opt.label }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
No jobs match your search
+
Try a different keyword or clear your filters.
-
-
-
-
-
- All jobs
-
-
- {{ otherJobs.length }} job{{ otherJobs.length === 1 ? '' : 's' }}
-
+
+
+
+
+
+
+ |
+
+ Title
+
+
+
+
+ |
+
+
+ Status
+
+
+
+
+ |
+
+
+ Type
+
+
+
+
+ |
+
+
+ Location
+
+
+
+
+ |
+
+
+ New
+
+
+
+
+ |
+
+
+ Active
+
+
+
+
+ |
+
+
+ Created
+
+
+
+
+ |
+
+
+
+
+ |
+
+
+ {{ j.title }}
+
+
+ {{ j.pipeline.new }} new
+
+
+ |
+
+
+ {{ j.status }}
+
+ |
+
+ {{ typeLabels[j.type] ?? j.type }}
+ |
+
+
+
+ {{ j.location }}
+
+ —
+ |
+
+
+ {{ j.pipeline.new }}
+
+ 0
+ |
+
+
+ {{ totalActive(j.pipeline) }}
+
+ 0
+ |
+
+ {{ new Date(j.createdAt).toLocaleDateString() }}
+ |
+
+
+
-
-
-
+
+
+
+
+
-
-
-
+
+
+
{{ j.title }}
-
+
{{ j.status }}
-
+
+
+
{{ typeLabels[j.type] ?? j.type }}
-
+
{{ j.location }}
-
- Not published yet
-
-
-
+
+
-
+
{{ getStageCount(j.pipeline, stage.key) }}
-
+
+
+
+
+ {{ j.pipeline.new }} new application{{ j.pipeline.new === 1 ? '' : 's' }}
+
+
+
+ Review
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Needs your attention
+
+
+ {{ jobsNeedingAttention.length }} job{{ jobsNeedingAttention.length === 1 ? '' : 's' }}
+
+
+
+
+
+
+
+
+
+
+
+ {{ j.title }}
+
+
+ {{ j.status }}
+
+
+
+ {{ typeLabels[j.type] ?? j.type }}
+
+
+ {{ j.location }}
+
+
+
+
+
+
+
+
+
+ {{ getStageCount(j.pipeline, stage.key) }}
+
+
+ {{ stage.label }}
+
+
+
+
+
+
+
+
+ {{ j.pipeline.new }} new application{{ j.pipeline.new === 1 ? '' : 's' }} to review
+
+
+
+ Review in Pipeline
+
+
+
-
+
+
+
+
+
+
+ All jobs
+
+
+ {{ otherJobs.length }} job{{ otherJobs.length === 1 ? '' : 's' }}
+
+
+
+
+
+
+
+
+ {{ j.title }}
+
+
+ {{ j.status }}
+
+
+
+ {{ typeLabels[j.type] ?? j.type }}
+
+
+ {{ j.location }}
+
+
+ Not published yet
+
+
+
+
+
+
+
+ {{ getStageCount(j.pipeline, stage.key) }}
+
+
+ {{ stage.label }}
+
+
+
+
+
+
+
-
- {{ total }} job{{ total === 1 ? '' : 's' }} total
+
+
+ Showing {{ filteredJobs.length }} of {{ total }} job{{ total === 1 ? '' : 's' }}
+
+
+ {{ total }} job{{ total === 1 ? '' : 's' }} total
+
diff --git a/app/pages/dashboard/timeline.vue b/app/pages/dashboard/timeline.vue
index 5649876..3bbaf3f 100644
--- a/app/pages/dashboard/timeline.vue
+++ b/app/pages/dashboard/timeline.vue
@@ -589,7 +589,7 @@ function getEventDescription(item: TimelineItem): string {
-
+
{
throw createError({ statusCode: 404, statusMessage: 'Not found' })
}
- return result
+ const properties = await loadPropertyEntriesForEntity({
+ organizationId: orgId,
+ entityType: 'application',
+ entityId: result.id,
+ jobId: result.jobId,
+ })
+
+ return { ...result, properties }
})
diff --git a/server/api/applications/[id]/properties/[propId].put.ts b/server/api/applications/[id]/properties/[propId].put.ts
new file mode 100644
index 0000000..46337b6
--- /dev/null
+++ b/server/api/applications/[id]/properties/[propId].put.ts
@@ -0,0 +1,84 @@
+import { and, eq } from 'drizzle-orm'
+import { z } from 'zod'
+import { application, propertyDefinition, propertyValue } from '../../../../database/schema'
+import {
+ setPropertyValueSchema,
+ validateValueForType,
+ type PropertyType,
+} from '../../../../utils/schemas/property'
+
+const paramsSchema = z.object({ id: z.string().min(1), propId: z.string().min(1) })
+
+/**
+ * PUT /api/applications/:id/properties/:propId
+ * Set a property value for an application. Body: { value: any }.
+ * Pass `null` to clear the value (the `value` key is required).
+ *
+ * Requires `application: ['update']` — same gate as editing notes/status.
+ */
+export default defineEventHandler(async (event) => {
+ const session = await requirePermission(event, { application: ['update'] })
+ const orgId = session.session.activeOrganizationId
+
+ const { id, propId } = await getValidatedRouterParams(event, paramsSchema.parse)
+ const { value } = await readValidatedBody(event, setPropertyValueSchema.parse)
+
+ // Verify entity belongs to org
+ const app = await db.query.application.findFirst({
+ where: and(eq(application.id, id), eq(application.organizationId, orgId)),
+ columns: { id: true, jobId: true },
+ })
+ if (!app) throw createError({ statusCode: 404, statusMessage: 'Application not found' })
+
+ // Verify property belongs to org and is valid in this application's context
+ const def = await db.query.propertyDefinition.findFirst({
+ where: and(
+ eq(propertyDefinition.id, propId),
+ eq(propertyDefinition.organizationId, orgId),
+ ),
+ })
+ if (!def) throw createError({ statusCode: 404, statusMessage: 'Property not found' })
+ if (def.entityType !== 'application') {
+ throw createError({ statusCode: 422, statusMessage: 'Property is not an application property' })
+ }
+ if (def.jobId && def.jobId !== app.jobId) {
+ throw createError({
+ statusCode: 422,
+ statusMessage: 'Property is scoped to a different job',
+ })
+ }
+
+ const normalized = validateValueForType(def.type as PropertyType, value, def.config)
+
+ if (normalized === null) {
+ await db
+ .delete(propertyValue)
+ .where(
+ and(
+ eq(propertyValue.organizationId, orgId),
+ eq(propertyValue.propertyDefinitionId, propId),
+ eq(propertyValue.entityId, id),
+ eq(propertyValue.entityType, 'application'),
+ ),
+ )
+ return { value: null }
+ }
+
+ // Upsert
+ const [row] = await db
+ .insert(propertyValue)
+ .values({
+ organizationId: orgId,
+ propertyDefinitionId: propId,
+ entityType: 'application',
+ entityId: id,
+ value: normalized as never,
+ })
+ .onConflictDoUpdate({
+ target: [propertyValue.propertyDefinitionId, propertyValue.entityId],
+ set: { value: normalized as never, updatedAt: new Date() },
+ })
+ .returning({ value: propertyValue.value })
+
+ return { value: row?.value ?? normalized }
+})
diff --git a/server/api/applications/index.get.ts b/server/api/applications/index.get.ts
index 57f5b45..594fbc8 100644
--- a/server/api/applications/index.get.ts
+++ b/server/api/applications/index.get.ts
@@ -1,11 +1,17 @@
-import { eq, and, desc, sql } from 'drizzle-orm'
+import { eq, and, desc, inArray } from 'drizzle-orm'
import { application, candidate, job } from '../../database/schema'
import { applicationQuerySchema } from '../../utils/schemas/application'
+import { propertyFiltersArraySchema } from '../../utils/schemas/property'
+import {
+ entityIdsMatchingFilters,
+ loadPropertyEntriesForEntities,
+ type PropertyFilter,
+} from '../../utils/properties'
/**
* GET /api/applications
* List applications for the current organization.
- * Filterable by jobId, candidateId, and status. Paginated.
+ * Filterable by jobId, candidateId, status, and custom property filters. Paginated.
*/
export default defineEventHandler(async (event) => {
const session = await requirePermission(event, { application: ['read'] })
@@ -26,6 +32,33 @@ export default defineEventHandler(async (event) => {
conditions.push(eq(application.status, query.status))
}
+ // ── Custom property filters ──
+ let propertyFilters: PropertyFilter[] = []
+ if (query.propertyFilters) {
+ let raw: unknown
+ try {
+ raw = JSON.parse(query.propertyFilters)
+ } catch {
+ throw createError({ statusCode: 400, statusMessage: 'Invalid propertyFilters' })
+ }
+ const result = propertyFiltersArraySchema.safeParse(raw)
+ if (!result.success) {
+ throw createError({ statusCode: 400, statusMessage: 'Invalid propertyFilters' })
+ }
+ propertyFilters = result.data as PropertyFilter[]
+ }
+ if (propertyFilters.length > 0) {
+ const matching = await entityIdsMatchingFilters({
+ organizationId: orgId,
+ entityType: 'application',
+ filters: propertyFilters,
+ })
+ if (!matching || matching.size === 0) {
+ return { data: [], total: 0, page: query.page, limit: query.limit }
+ }
+ conditions.push(inArray(application.id, [...matching]))
+ }
+
const where = and(...conditions)
const [data, total] = await Promise.all([
@@ -55,5 +88,22 @@ export default defineEventHandler(async (event) => {
db.$count(application, where),
])
- return { data, total, page: query.page, limit: query.limit }
+ // Bulk-attach properties for the current page (org-global + per-job)
+ const ids = data.map((a) => a.id)
+ const jobIds = [...new Set(data.map((a) => a.jobId))]
+ const entityJobIds = new Map(data.map((a) => [a.id, a.jobId] as const))
+ const propertyMap = await loadPropertyEntriesForEntities({
+ organizationId: orgId,
+ entityType: 'application',
+ entityIds: ids,
+ jobIds,
+ entityJobIds,
+ })
+ const enriched = data.map((a) => ({
+ ...a,
+ properties: propertyMap.get(a.id) ?? [],
+ }))
+
+ return { data: enriched, total, page: query.page, limit: query.limit }
})
+
diff --git a/server/api/candidates/[id].get.ts b/server/api/candidates/[id].get.ts
index 2b720f1..a6cf1a1 100644
--- a/server/api/candidates/[id].get.ts
+++ b/server/api/candidates/[id].get.ts
@@ -1,6 +1,7 @@
import { eq, and } from 'drizzle-orm'
import { candidate } from '../../database/schema'
import { candidateIdParamSchema } from '../../utils/schemas/candidate'
+import { loadPropertyEntriesForEntity } from '../../utils/properties'
export default defineEventHandler(async (event) => {
const session = await requirePermission(event, { candidate: ['read'] })
@@ -46,11 +47,19 @@ export default defineEventHandler(async (event) => {
// Replace heavy parsedContent with a lightweight `parsed` boolean
const { documents, ...rest } = result
+
+ const properties = await loadPropertyEntriesForEntity({
+ organizationId: orgId,
+ entityType: 'candidate',
+ entityId: result.id,
+ })
+
return {
...rest,
documents: documents.map(({ parsedContent, ...doc }) => ({
...doc,
parsed: parsedContent != null,
})),
+ properties,
}
})
diff --git a/server/api/candidates/[id]/properties/[propId].put.ts b/server/api/candidates/[id]/properties/[propId].put.ts
new file mode 100644
index 0000000..7c446f0
--- /dev/null
+++ b/server/api/candidates/[id]/properties/[propId].put.ts
@@ -0,0 +1,72 @@
+import { and, eq } from 'drizzle-orm'
+import { z } from 'zod'
+import { candidate, propertyDefinition, propertyValue } from '../../../../database/schema'
+import {
+ setPropertyValueSchema,
+ validateValueForType,
+ type PropertyType,
+} from '../../../../utils/schemas/property'
+
+const paramsSchema = z.object({ id: z.string().min(1), propId: z.string().min(1) })
+
+/**
+ * PUT /api/candidates/:id/properties/:propId
+ * Set a property value for a candidate. Body: { value: any }. Passing null clears.
+ */
+export default defineEventHandler(async (event) => {
+ const session = await requirePermission(event, { candidate: ['update'] })
+ const orgId = session.session.activeOrganizationId
+
+ const { id, propId } = await getValidatedRouterParams(event, paramsSchema.parse)
+ const { value } = await readValidatedBody(event, setPropertyValueSchema.parse)
+
+ const cand = await db.query.candidate.findFirst({
+ where: and(eq(candidate.id, id), eq(candidate.organizationId, orgId)),
+ columns: { id: true },
+ })
+ if (!cand) throw createError({ statusCode: 404, statusMessage: 'Candidate not found' })
+
+ const def = await db.query.propertyDefinition.findFirst({
+ where: and(
+ eq(propertyDefinition.id, propId),
+ eq(propertyDefinition.organizationId, orgId),
+ ),
+ })
+ if (!def) throw createError({ statusCode: 404, statusMessage: 'Property not found' })
+ if (def.entityType !== 'candidate') {
+ throw createError({ statusCode: 422, statusMessage: 'Property is not a candidate property' })
+ }
+
+ const normalized = validateValueForType(def.type as PropertyType, value, def.config)
+
+ if (normalized === null) {
+ await db
+ .delete(propertyValue)
+ .where(
+ and(
+ eq(propertyValue.organizationId, orgId),
+ eq(propertyValue.propertyDefinitionId, propId),
+ eq(propertyValue.entityId, id),
+ eq(propertyValue.entityType, 'candidate'),
+ ),
+ )
+ return { value: null }
+ }
+
+ const [row] = await db
+ .insert(propertyValue)
+ .values({
+ organizationId: orgId,
+ propertyDefinitionId: propId,
+ entityType: 'candidate',
+ entityId: id,
+ value: normalized as never,
+ })
+ .onConflictDoUpdate({
+ target: [propertyValue.propertyDefinitionId, propertyValue.entityId],
+ set: { value: normalized as never, updatedAt: new Date() },
+ })
+ .returning({ value: propertyValue.value })
+
+ return { value: row?.value ?? normalized }
+})
diff --git a/server/api/candidates/index.get.ts b/server/api/candidates/index.get.ts
index 8223b41..f8b43fd 100644
--- a/server/api/candidates/index.get.ts
+++ b/server/api/candidates/index.get.ts
@@ -1,6 +1,12 @@
-import { eq, and, or, ilike, desc, sql, gte, lte } from 'drizzle-orm'
+import { eq, and, or, ilike, desc, sql, gte, lte, inArray } from 'drizzle-orm'
import { candidate, application } from '../../database/schema'
import { candidateQuerySchema } from '../../utils/schemas/candidate'
+import { propertyFiltersArraySchema } from '../../utils/schemas/property'
+import {
+ entityIdsMatchingFilters,
+ loadPropertyEntriesForEntities,
+ type PropertyFilter,
+} from '../../utils/properties'
export default defineEventHandler(async (event) => {
const session = await requirePermission(event, { candidate: ['read'] })
@@ -36,6 +42,33 @@ export default defineEventHandler(async (event) => {
conditions.push(lte(candidate.dateOfBirth, query.dobTo))
}
+ // ── Custom property filters (intersection-based) ──
+ let propertyFilters: PropertyFilter[] = []
+ if (query.propertyFilters) {
+ let raw: unknown
+ try {
+ raw = JSON.parse(query.propertyFilters)
+ } catch {
+ throw createError({ statusCode: 400, statusMessage: 'Invalid propertyFilters' })
+ }
+ const result = propertyFiltersArraySchema.safeParse(raw)
+ if (!result.success) {
+ throw createError({ statusCode: 400, statusMessage: 'Invalid propertyFilters' })
+ }
+ propertyFilters = result.data as PropertyFilter[]
+ }
+ if (propertyFilters.length > 0) {
+ const matching = await entityIdsMatchingFilters({
+ organizationId: orgId,
+ entityType: 'candidate',
+ filters: propertyFilters,
+ })
+ if (!matching || matching.size === 0) {
+ return { data: [], total: 0, page: query.page, limit: query.limit }
+ }
+ conditions.push(inArray(candidate.id, [...matching]))
+ }
+
const where = and(...conditions)
const [data, total] = await Promise.all([
@@ -64,5 +97,14 @@ export default defineEventHandler(async (event) => {
db.$count(candidate, where),
])
- return { data, total, page: query.page, limit: query.limit }
+ // Bulk-attach properties for the current page
+ const ids = data.map((c) => c.id)
+ const propertyMap = await loadPropertyEntriesForEntities({
+ organizationId: orgId,
+ entityType: 'candidate',
+ entityIds: ids,
+ })
+ const enriched = data.map((c) => ({ ...c, properties: propertyMap.get(c.id) ?? [] }))
+
+ return { data: enriched, total, page: query.page, limit: query.limit }
})
diff --git a/server/api/jobs/index.get.ts b/server/api/jobs/index.get.ts
index 30adb99..7586c45 100644
--- a/server/api/jobs/index.get.ts
+++ b/server/api/jobs/index.get.ts
@@ -35,6 +35,8 @@ export default defineEventHandler(async (event) => {
location: true,
type: true,
status: true,
+ experienceLevel: true,
+ remoteStatus: true,
createdAt: true,
updatedAt: true,
},
diff --git a/server/api/properties/[id].delete.ts b/server/api/properties/[id].delete.ts
new file mode 100644
index 0000000..a3829f1
--- /dev/null
+++ b/server/api/properties/[id].delete.ts
@@ -0,0 +1,33 @@
+import { and, eq } from 'drizzle-orm'
+import { propertyDefinition } from '../../database/schema'
+import { propertyIdParamSchema } from '../../utils/schemas/property'
+
+/**
+ * DELETE /api/properties/:id
+ * Removes a property definition. All values cascade-delete via the FK.
+ */
+export default defineEventHandler(async (event) => {
+ const session = await requirePermission(event, { organization: ['update'] })
+ const orgId = session.session.activeOrganizationId
+
+ const { id } = await getValidatedRouterParams(event, propertyIdParamSchema.parse)
+
+ const [deleted] = await db
+ .delete(propertyDefinition)
+ .where(and(eq(propertyDefinition.id, id), eq(propertyDefinition.organizationId, orgId)))
+ .returning({ id: propertyDefinition.id })
+
+ if (!deleted) {
+ throw createError({ statusCode: 404, statusMessage: 'Property not found' })
+ }
+
+ recordActivity({
+ organizationId: orgId,
+ actorId: session.user.id,
+ action: 'deleted',
+ resourceType: 'property',
+ resourceId: id,
+ })
+
+ return { id }
+})
diff --git a/server/api/properties/[id].patch.ts b/server/api/properties/[id].patch.ts
new file mode 100644
index 0000000..a048ffd
--- /dev/null
+++ b/server/api/properties/[id].patch.ts
@@ -0,0 +1,39 @@
+import { and, eq } from 'drizzle-orm'
+import { propertyDefinition } from '../../database/schema'
+import {
+ propertyIdParamSchema,
+ updatePropertyDefinitionSchema,
+} from '../../utils/schemas/property'
+
+/**
+ * PATCH /api/properties/:id
+ * Update a property definition (name, description, config, displayOrder).
+ * Type and entityType are immutable.
+ */
+export default defineEventHandler(async (event) => {
+ const session = await requirePermission(event, { organization: ['update'] })
+ const orgId = session.session.activeOrganizationId
+
+ const { id } = await getValidatedRouterParams(event, propertyIdParamSchema.parse)
+ const body = await readValidatedBody(event, updatePropertyDefinitionSchema.parse)
+
+ const [updated] = await db
+ .update(propertyDefinition)
+ .set({ ...body, updatedAt: new Date() })
+ .where(and(eq(propertyDefinition.id, id), eq(propertyDefinition.organizationId, orgId)))
+ .returning()
+
+ if (!updated) {
+ throw createError({ statusCode: 404, statusMessage: 'Property not found' })
+ }
+
+ recordActivity({
+ organizationId: orgId,
+ actorId: session.user.id,
+ action: 'updated',
+ resourceType: 'property',
+ resourceId: id,
+ })
+
+ return updated
+})
diff --git a/server/api/properties/index.get.ts b/server/api/properties/index.get.ts
new file mode 100644
index 0000000..fcb42d9
--- /dev/null
+++ b/server/api/properties/index.get.ts
@@ -0,0 +1,59 @@
+import { eq, and } from 'drizzle-orm'
+import { propertyDefinition } from '../../database/schema'
+import { propertyListQuerySchema, type PropertyEntityType } from '../../utils/schemas/property'
+import { loadPropertyDefinitions } from '../../utils/properties'
+
+/**
+ * GET /api/properties
+ * Returns property definitions visible in the requested context.
+ *
+ * - ?entityType=candidate → org-global candidate defs
+ * - ?entityType=application → org-global application defs
+ * - ?entityType=application&jobId=X → org-global + per-job defs for X
+ * - ?entityType=application&jobId=X&jobOnly=1 → ONLY per-job defs for X (schema editor)
+ *
+ * Reading is gated on `application: ['read']` because property definitions
+ * are pure metadata and any user with pipeline access needs to render them.
+ */
+export default defineEventHandler(async (event) => {
+ const session = await requirePermission(event, { application: ['read'] })
+ const orgId = session.session.activeOrganizationId
+
+ const query = await getValidatedQuery(event, propertyListQuerySchema.parse)
+
+ if (query.jobOnly) {
+ if (!query.jobId || query.entityType !== 'application') {
+ throw createError({
+ statusCode: 400,
+ statusMessage: 'jobOnly requires entityType=application and a jobId',
+ })
+ }
+ const rows = await db
+ .select()
+ .from(propertyDefinition)
+ .where(
+ and(
+ eq(propertyDefinition.organizationId, orgId),
+ eq(propertyDefinition.entityType, 'application'),
+ eq(propertyDefinition.jobId, query.jobId),
+ ),
+ )
+ .orderBy(propertyDefinition.displayOrder, propertyDefinition.createdAt)
+ return rows
+ }
+
+ if (!query.entityType) {
+ // Return both candidate and application org-global defs
+ const [candidateDefs, applicationDefs] = await Promise.all([
+ loadPropertyDefinitions({ organizationId: orgId, entityType: 'candidate' }),
+ loadPropertyDefinitions({ organizationId: orgId, entityType: 'application' }),
+ ])
+ return { candidate: candidateDefs, application: applicationDefs }
+ }
+
+ return loadPropertyDefinitions({
+ organizationId: orgId,
+ entityType: query.entityType as PropertyEntityType,
+ jobId: query.jobId ?? null,
+ })
+})
diff --git a/server/api/properties/index.post.ts b/server/api/properties/index.post.ts
new file mode 100644
index 0000000..c4af1e4
--- /dev/null
+++ b/server/api/properties/index.post.ts
@@ -0,0 +1,75 @@
+import { and, desc, eq, isNull } from 'drizzle-orm'
+import { job, propertyDefinition } from '../../database/schema'
+import { createPropertyDefinitionSchema } from '../../utils/schemas/property'
+
+/**
+ * POST /api/properties
+ * Create a new property definition.
+ *
+ * Requires `organization: ['update']` — only owners/admins can edit the schema.
+ */
+export default defineEventHandler(async (event) => {
+ const session = await requirePermission(event, { organization: ['update'] })
+ const orgId = session.session.activeOrganizationId
+
+ const body = await readValidatedBody(event, createPropertyDefinitionSchema.parse)
+
+ // Candidate properties are always org-global
+ if (body.entityType === 'candidate' && body.jobId) {
+ throw createError({
+ statusCode: 422,
+ statusMessage: 'Candidate properties cannot be scoped to a job',
+ })
+ }
+
+ // If jobId provided, verify it belongs to this org
+ if (body.jobId) {
+ const j = await db.query.job.findFirst({
+ where: and(eq(job.id, body.jobId), eq(job.organizationId, orgId)),
+ columns: { id: true },
+ })
+ if (!j) {
+ throw createError({ statusCode: 404, statusMessage: 'Job not found' })
+ }
+ }
+
+ // Compute displayOrder = max + 1 within this scope
+ const last = await db
+ .select({ displayOrder: propertyDefinition.displayOrder })
+ .from(propertyDefinition)
+ .where(
+ and(
+ eq(propertyDefinition.organizationId, orgId),
+ eq(propertyDefinition.entityType, body.entityType),
+ body.jobId
+ ? eq(propertyDefinition.jobId, body.jobId)
+ : isNull(propertyDefinition.jobId),
+ ),
+ )
+ .orderBy(desc(propertyDefinition.displayOrder))
+ .limit(1)
+
+ const nextOrder = (last[0]?.displayOrder ?? -1) + 1
+
+ const [created] = await db.insert(propertyDefinition).values({
+ organizationId: orgId,
+ entityType: body.entityType,
+ type: body.type,
+ name: body.name,
+ description: body.description ?? null,
+ jobId: body.jobId ?? null,
+ config: (body.config ?? null) as Record | null,
+ displayOrder: nextOrder,
+ }).returning()
+
+ recordActivity({
+ organizationId: orgId,
+ actorId: session.user.id,
+ action: 'created',
+ resourceType: 'property',
+ resourceId: created!.id,
+ metadata: { entityType: body.entityType, type: body.type, jobId: body.jobId ?? null },
+ })
+
+ return created
+})
diff --git a/server/api/properties/reorder.post.ts b/server/api/properties/reorder.post.ts
new file mode 100644
index 0000000..7f6154c
--- /dev/null
+++ b/server/api/properties/reorder.post.ts
@@ -0,0 +1,63 @@
+import { and, eq, inArray } from 'drizzle-orm'
+import { propertyDefinition } from '../../database/schema'
+import { reorderPropertiesSchema } from '../../utils/schemas/property'
+
+/**
+ * POST /api/properties/reorder
+ * Body: { ids: string[] } — applied left-to-right as displayOrder=0..N.
+ * All ids must belong to the same scope (entityType + jobId); we enforce
+ * that here so partial/mixed lists can't rewrite displayOrder across
+ * unrelated definitions.
+ */
+export default defineEventHandler(async (event) => {
+ const session = await requirePermission(event, { organization: ['update'] })
+ const orgId = session.session.activeOrganizationId
+
+ const { ids } = await readValidatedBody(event, reorderPropertiesSchema.parse)
+ if (ids.length === 0) return { ok: true }
+
+ // Verify all ids belong to this org and share a single (entityType, jobId) scope
+ const owned = await db
+ .select({
+ id: propertyDefinition.id,
+ entityType: propertyDefinition.entityType,
+ jobId: propertyDefinition.jobId,
+ })
+ .from(propertyDefinition)
+ .where(
+ and(
+ eq(propertyDefinition.organizationId, orgId),
+ inArray(propertyDefinition.id, ids),
+ ),
+ )
+ if (owned.length !== ids.length) {
+ throw createError({ statusCode: 404, statusMessage: 'One or more properties not found' })
+ }
+
+ const firstScope = owned[0]!
+ const sameScope = owned.every(
+ (r) => r.entityType === firstScope.entityType && r.jobId === firstScope.jobId,
+ )
+ if (!sameScope) {
+ throw createError({
+ statusCode: 400,
+ statusMessage: 'All ids must belong to the same property scope',
+ })
+ }
+
+ await db.transaction(async (tx) => {
+ for (let i = 0; i < ids.length; i++) {
+ await tx
+ .update(propertyDefinition)
+ .set({ displayOrder: i, updatedAt: new Date() })
+ .where(
+ and(
+ eq(propertyDefinition.id, ids[i]!),
+ eq(propertyDefinition.organizationId, orgId),
+ ),
+ )
+ }
+ })
+
+ return { ok: true }
+})
diff --git a/server/database/migrations/0028_custom_properties.sql b/server/database/migrations/0028_custom_properties.sql
new file mode 100644
index 0000000..26a008f
--- /dev/null
+++ b/server/database/migrations/0028_custom_properties.sql
@@ -0,0 +1,42 @@
+-- Migration 0028: Custom Properties (Notion-style "database properties")
+-- Adds two tables: property_definition (schema) and property_value (per-entity values).
+-- Both are tenant-scoped via organization_id with cascade deletes.
+
+CREATE TYPE "public"."property_entity_type" AS ENUM('candidate', 'application');--> statement-breakpoint
+CREATE TYPE "public"."property_type" AS ENUM('text', 'long_text', 'number', 'select', 'multi_select', 'date', 'checkbox', 'url', 'email', 'person', 'file');--> statement-breakpoint
+CREATE TABLE "property_definition" (
+ "id" text PRIMARY KEY NOT NULL,
+ "organization_id" text NOT NULL,
+ "job_id" text,
+ "entity_type" "property_entity_type" NOT NULL,
+ "type" "property_type" NOT NULL,
+ "name" text NOT NULL,
+ "description" text,
+ "display_order" integer DEFAULT 0 NOT NULL,
+ "config" jsonb,
+ "created_at" timestamp DEFAULT now() NOT NULL,
+ "updated_at" timestamp DEFAULT now() NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE "property_value" (
+ "id" text PRIMARY KEY NOT NULL,
+ "organization_id" text NOT NULL,
+ "property_definition_id" text NOT NULL,
+ "entity_type" "property_entity_type" NOT NULL,
+ "entity_id" text NOT NULL,
+ "value" jsonb,
+ "created_at" timestamp DEFAULT now() NOT NULL,
+ "updated_at" timestamp DEFAULT now() NOT NULL
+);
+--> statement-breakpoint
+ALTER TABLE "property_definition" ADD CONSTRAINT "property_definition_organization_id_organization_id_fk" FOREIGN KEY ("organization_id") REFERENCES "public"."organization"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "property_definition" ADD CONSTRAINT "property_definition_job_id_job_id_fk" FOREIGN KEY ("job_id") REFERENCES "public"."job"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "property_value" ADD CONSTRAINT "property_value_organization_id_organization_id_fk" FOREIGN KEY ("organization_id") REFERENCES "public"."organization"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "property_value" ADD CONSTRAINT "property_value_property_definition_id_property_definition_id_fk" FOREIGN KEY ("property_definition_id") REFERENCES "public"."property_definition"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+CREATE INDEX "property_definition_org_idx" ON "property_definition" USING btree ("organization_id");--> statement-breakpoint
+CREATE INDEX "property_definition_org_entity_idx" ON "property_definition" USING btree ("organization_id","entity_type");--> statement-breakpoint
+CREATE INDEX "property_definition_job_idx" ON "property_definition" USING btree ("job_id");--> statement-breakpoint
+CREATE INDEX "property_value_org_idx" ON "property_value" USING btree ("organization_id");--> statement-breakpoint
+CREATE INDEX "property_value_entity_idx" ON "property_value" USING btree ("entity_type","entity_id");--> statement-breakpoint
+CREATE INDEX "property_value_definition_idx" ON "property_value" USING btree ("property_definition_id");--> statement-breakpoint
+CREATE UNIQUE INDEX "property_value_def_entity_idx" ON "property_value" USING btree ("property_definition_id","entity_id");
diff --git a/server/database/migrations/meta/0028_snapshot.json b/server/database/migrations/meta/0028_snapshot.json
new file mode 100644
index 0000000..a943d31
--- /dev/null
+++ b/server/database/migrations/meta/0028_snapshot.json
@@ -0,0 +1,5372 @@
+{
+ "id": "4ffd7fcb-d7e0-4e49-8211-fa5b7305aff9",
+ "prevId": "08656045-ca21-4f0a-b890-3e9408b45724",
+ "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
+ },
+ "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 757aba4..2fec9f7 100644
--- a/server/database/migrations/meta/_journal.json
+++ b/server/database/migrations/meta/_journal.json
@@ -197,6 +197,13 @@
"when": 1777300000000,
"tag": "0027_chatbot_agent_default_unique",
"breakpoints": true
+ },
+ {
+ "idx": 28,
+ "version": "7",
+ "when": 1777463601249,
+ "tag": "0028_custom_properties",
+ "breakpoints": true
}
]
}
\ No newline at end of file
diff --git a/server/database/schema/app.ts b/server/database/schema/app.ts
index 020da9b..f450351 100644
--- a/server/database/schema/app.ts
+++ b/server/database/schema/app.ts
@@ -27,6 +27,11 @@ export const questionTypeEnum = pgEnum('question_type', [
'short_text', 'long_text', 'single_select', 'multi_select',
'number', 'date', 'url', 'checkbox', 'file_upload',
])
+export const propertyEntityTypeEnum = pgEnum('property_entity_type', ['candidate', 'application'])
+export const propertyTypeEnum = pgEnum('property_type', [
+ 'text', 'long_text', 'number', 'select', 'multi_select',
+ 'date', 'checkbox', 'url', 'email', 'person', 'file',
+])
export const genderEnum = pgEnum('gender', ['male', 'female', 'other', 'prefer_not_to_say'])
export const experienceLevelEnum = pgEnum('experience_level', ['junior', 'mid', 'senior', 'lead'])
export const nameDisplayFormatEnum = pgEnum('name_display_format', ['first_last', 'last_first'])
@@ -182,6 +187,69 @@ export const questionResponse = pgTable('question_response', {
index('question_response_question_id_idx').on(t.questionId),
]))
+// ─────────────────────────────────────────────
+// Custom Properties (Notion-style "database properties")
+// ─────────────────────────────────────────────
+//
+// Two-table design:
+// - propertyDefinition: schema. Org-global when jobId IS NULL; per-job otherwise.
+// entityType=candidate is always org-global (jobId must be NULL).
+// entityType=application can be org-global OR per-job.
+// - propertyValue: values, polymorphic to candidate.id or application.id.
+//
+// `value` is jsonb shaped by the property type:
+// text/long_text/url/email/person → string
+// number → number
+// select → string (one option id)
+// multi_select → string[] (option ids)
+// date → string (ISO YYYY-MM-DD)
+// checkbox → boolean
+// file → { documentId: string }
+//
+// `config` jsonb:
+// select / multi_select → { options: [{ id, label, color }] }
+// number → { format?: 'plain' | 'percent' | 'currency', currency?: string }
+// others → null
+//
+// Per-job overrides are NOT supported (additive only): per-job props are merged
+// after org-global ones, ordered by displayOrder.
+
+export const propertyDefinition = pgTable('property_definition', {
+ id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()),
+ organizationId: text('organization_id').notNull().references(() => organization.id, { onDelete: 'cascade' }),
+ /** NULL = org-global. Non-null = per-job (only valid when entityType='application'). */
+ jobId: text('job_id').references(() => job.id, { onDelete: 'cascade' }),
+ entityType: propertyEntityTypeEnum('entity_type').notNull(),
+ type: propertyTypeEnum('type').notNull(),
+ name: text('name').notNull(),
+ description: text('description'),
+ displayOrder: integer('display_order').notNull().default(0),
+ config: jsonb('config').$type | null>(),
+ createdAt: timestamp('created_at').notNull().defaultNow(),
+ updatedAt: timestamp('updated_at').notNull().defaultNow(),
+}, (t) => ([
+ index('property_definition_org_idx').on(t.organizationId),
+ index('property_definition_org_entity_idx').on(t.organizationId, t.entityType),
+ index('property_definition_job_idx').on(t.jobId),
+]))
+
+export const propertyValue = pgTable('property_value', {
+ id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()),
+ organizationId: text('organization_id').notNull().references(() => organization.id, { onDelete: 'cascade' }),
+ propertyDefinitionId: text('property_definition_id').notNull().references(() => propertyDefinition.id, { onDelete: 'cascade' }),
+ entityType: propertyEntityTypeEnum('entity_type').notNull(),
+ /** candidate.id when entityType='candidate', application.id when 'application' */
+ entityId: text('entity_id').notNull(),
+ value: jsonb('value'),
+ createdAt: timestamp('created_at').notNull().defaultNow(),
+ updatedAt: timestamp('updated_at').notNull().defaultNow(),
+}, (t) => ([
+ index('property_value_org_idx').on(t.organizationId),
+ index('property_value_entity_idx').on(t.entityType, t.entityId),
+ index('property_value_definition_idx').on(t.propertyDefinitionId),
+ uniqueIndex('property_value_def_entity_idx').on(t.propertyDefinitionId, t.entityId),
+]))
+
// ─────────────────────────────────────────────
// Organization Localization Settings
// ─────────────────────────────────────────────
@@ -678,6 +746,17 @@ export const questionResponseRelations = relations(questionResponse, ({ one }) =
question: one(jobQuestion, { fields: [questionResponse.questionId], references: [jobQuestion.id] }),
}))
+export const propertyDefinitionRelations = relations(propertyDefinition, ({ one, many }) => ({
+ organization: one(organization, { fields: [propertyDefinition.organizationId], references: [organization.id] }),
+ job: one(job, { fields: [propertyDefinition.jobId], references: [job.id] }),
+ values: many(propertyValue),
+}))
+
+export const propertyValueRelations = relations(propertyValue, ({ one }) => ({
+ organization: one(organization, { fields: [propertyValue.organizationId], references: [organization.id] }),
+ definition: one(propertyDefinition, { fields: [propertyValue.propertyDefinitionId], references: [propertyDefinition.id] }),
+}))
+
export const commentRelations = relations(comment, ({ one }) => ({
organization: one(organization, { fields: [comment.organizationId], references: [organization.id] }),
author: one(user, { fields: [comment.authorId], references: [user.id] }),
diff --git a/server/utils/properties.ts b/server/utils/properties.ts
new file mode 100644
index 0000000..edf27f5
--- /dev/null
+++ b/server/utils/properties.ts
@@ -0,0 +1,310 @@
+import { and, eq, inArray, isNull, or, sql } from 'drizzle-orm'
+import { propertyDefinition, propertyValue } from '../database/schema'
+import type { PropertyEntityType, PropertyType } from './schemas/property'
+
+// ─────────────────────────────────────────────
+// Property loading & attachment helpers
+// ─────────────────────────────────────────────
+
+export type PropertyDefinitionRow = {
+ id: string
+ organizationId: string
+ jobId: string | null
+ entityType: PropertyEntityType
+ type: PropertyType
+ name: string
+ description: string | null
+ displayOrder: number
+ config: Record | null
+ createdAt: Date
+ updatedAt: Date
+}
+
+export type PropertyEntry = {
+ definition: PropertyDefinitionRow
+ value: unknown
+}
+
+/**
+ * Load all property definitions visible in a given context.
+ * - Candidate context: org-global candidate definitions only.
+ * - Application context: org-global application defs + per-job defs for jobId (additive).
+ *
+ * Ordered by (jobId NULLS FIRST, displayOrder, createdAt) so org-global props
+ * always render first.
+ */
+export async function loadPropertyDefinitions(opts: {
+ organizationId: string
+ entityType: PropertyEntityType
+ jobId?: string | null
+}): Promise {
+ const { organizationId, entityType, jobId } = opts
+
+ const where =
+ entityType === 'candidate'
+ ? and(
+ eq(propertyDefinition.organizationId, organizationId),
+ eq(propertyDefinition.entityType, 'candidate'),
+ isNull(propertyDefinition.jobId),
+ )
+ : and(
+ eq(propertyDefinition.organizationId, organizationId),
+ eq(propertyDefinition.entityType, 'application'),
+ jobId
+ ? or(isNull(propertyDefinition.jobId), eq(propertyDefinition.jobId, jobId))
+ : isNull(propertyDefinition.jobId),
+ )
+
+ const rows = await db
+ .select()
+ .from(propertyDefinition)
+ .where(where)
+ .orderBy(
+ sql`${propertyDefinition.jobId} NULLS FIRST`,
+ propertyDefinition.displayOrder,
+ propertyDefinition.createdAt,
+ )
+
+ return rows as unknown as PropertyDefinitionRow[]
+}
+
+/**
+ * Load values for a single entity, returning [{ definition, value }] entries
+ * in the same order as definitions (missing values surface as `value: null`).
+ */
+export async function loadPropertyEntriesForEntity(opts: {
+ organizationId: string
+ entityType: PropertyEntityType
+ entityId: string
+ jobId?: string | null
+}): Promise {
+ const definitions = await loadPropertyDefinitions(opts)
+ if (definitions.length === 0) return []
+
+ const values = await db
+ .select({
+ propertyDefinitionId: propertyValue.propertyDefinitionId,
+ value: propertyValue.value,
+ })
+ .from(propertyValue)
+ .where(
+ and(
+ eq(propertyValue.organizationId, opts.organizationId),
+ eq(propertyValue.entityType, opts.entityType),
+ eq(propertyValue.entityId, opts.entityId),
+ inArray(
+ propertyValue.propertyDefinitionId,
+ definitions.map((d) => d.id),
+ ),
+ ),
+ )
+
+ const valueMap = new Map(values.map((v) => [v.propertyDefinitionId, v.value]))
+ return definitions.map((definition) => ({
+ definition,
+ value: valueMap.get(definition.id) ?? null,
+ }))
+}
+
+/**
+ * Bulk load values for many entities. Returns a Map
+ * suitable for splicing into list endpoint responses.
+ */
+export async function loadPropertyEntriesForEntities(opts: {
+ organizationId: string
+ entityType: PropertyEntityType
+ entityIds: string[]
+ /**
+ * For application list responses we may want per-job props too. The
+ * caller passes the union of jobIds; we then load all defs whose jobId
+ * is null OR in that set.
+ */
+ jobIds?: string[]
+ /**
+ * Optional per-entity job-id map. When provided, each entity only
+ * receives org-global defs plus defs scoped to that entity's own jobId,
+ * so mixed-job lists don't leak unrelated job properties across rows.
+ */
+ entityJobIds?: Map
+}): Promise