608 lines
20 KiB
Text
608 lines
20 KiB
Text
datasource db {
|
|
provider = "postgresql"
|
|
url = env("DATABASE_URL")
|
|
}
|
|
|
|
generator client {
|
|
provider = "prisma-client-js"
|
|
}
|
|
|
|
model User {
|
|
id String @id @default(cuid())
|
|
email String @unique
|
|
passwordHash String
|
|
name String?
|
|
role String @default("SEEKER") // SEEKER, EMPLOYER, ADMIN
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
resumes Resume[]
|
|
interactions UserJobInteraction[]
|
|
profile UserProfile?
|
|
applications Application[]
|
|
jobAlerts JobAlert[]
|
|
savedJobs SavedJob[]
|
|
savedSearches SavedSearch[]
|
|
statusChanges ApplicationStatusHistory[]
|
|
authoredNotes CandidateNote[]
|
|
createdTags CandidateTag[]
|
|
notifications Notification[]
|
|
notificationPreference NotificationPreference?
|
|
companyReviews CompanyReview[]
|
|
companyId String?
|
|
company Company? @relation(fields: [companyId], references: [id], onDelete: SetNull)
|
|
postedJobs Job[] @relation("PostedJobs")
|
|
auditLogs AuditLog[] @relation("ActorAuditLogs")
|
|
emailVerified DateTime?
|
|
failedLoginAttempts Int @default(0)
|
|
lockedUntil DateTime?
|
|
passwordResetTokens PasswordResetToken[]
|
|
memberships OrganizationMembership[]
|
|
createdInvitations OrganizationInvitation[] @relation("InvitedBy")
|
|
companyClaims CompanyClaim[] @relation("Claimant")
|
|
analyses JobAnalysisCache[]
|
|
|
|
@@index([role])
|
|
@@index([companyId])
|
|
}
|
|
|
|
model UserProfile {
|
|
id String @id @default(cuid())
|
|
userId String @unique
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
headline String?
|
|
bio String?
|
|
phone String?
|
|
location String?
|
|
isPublic Boolean @default(true)
|
|
searchableToEmployers Boolean @default(false)
|
|
skills CandidateSkill[]
|
|
workHistory WorkExperience[]
|
|
education Education[]
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([location])
|
|
@@index([isPublic, searchableToEmployers])
|
|
}
|
|
|
|
model CandidateSkill {
|
|
id String @id @default(cuid())
|
|
profileId String
|
|
profile UserProfile @relation(fields: [profileId], references: [id], onDelete: Cascade)
|
|
name String
|
|
level String? // BEGINNER, INTERMEDIATE, EXPERT
|
|
|
|
@@index([profileId])
|
|
@@index([name])
|
|
}
|
|
|
|
model WorkExperience {
|
|
id String @id @default(cuid())
|
|
profileId String
|
|
profile UserProfile @relation(fields: [profileId], references: [id], onDelete: Cascade)
|
|
company String
|
|
title String
|
|
location String?
|
|
startDate DateTime
|
|
endDate DateTime?
|
|
isCurrent Boolean @default(false)
|
|
description String?
|
|
|
|
@@index([profileId])
|
|
}
|
|
|
|
model Education {
|
|
id String @id @default(cuid())
|
|
profileId String
|
|
profile UserProfile @relation(fields: [profileId], references: [id], onDelete: Cascade)
|
|
institution String
|
|
degree String
|
|
fieldOfStudy String?
|
|
startDate DateTime?
|
|
endDate DateTime?
|
|
|
|
@@index([profileId])
|
|
}
|
|
|
|
model Resume {
|
|
id String @id @default(cuid())
|
|
userId String
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
title String @default("My Resume")
|
|
data String @default("{}")
|
|
isActiveForMatching Boolean @default(false)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([userId])
|
|
}
|
|
|
|
model Job {
|
|
id String @id @default(cuid())
|
|
jobUrlHash String @unique
|
|
fingerprintHash String?
|
|
title String
|
|
company String
|
|
location String
|
|
isRemote Boolean @default(false)
|
|
department String?
|
|
experienceLevel String?
|
|
description String @db.Text
|
|
salaryMin Float?
|
|
salaryMax Float?
|
|
jobUrl String
|
|
source String
|
|
sourceId String?
|
|
moderationStatus String @default("APPROVED") // APPROVED, PENDING_REVIEW, REJECTED, FLAGGED
|
|
moderationReason String?
|
|
moderatedAt DateTime?
|
|
lifecycleStatus String @default("ACTIVE") // ACTIVE, STALE, EXPIRED, ARCHIVED
|
|
firstSeenAt DateTime @default(now())
|
|
lastSeenAt DateTime @default(now())
|
|
missingScanCount Int @default(0)
|
|
expiredAt DateTime?
|
|
rawPayloadJson String? @db.Text
|
|
datePosted DateTime?
|
|
companyId String?
|
|
companyRef Company? @relation(fields: [companyId], references: [id], onDelete: SetNull)
|
|
postedById String?
|
|
postedBy User? @relation("PostedJobs", fields: [postedById], references: [id], onDelete: SetNull)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
interactions UserJobInteraction[]
|
|
applications Application[]
|
|
savedBy SavedJob[]
|
|
|
|
@@index([companyId])
|
|
@@index([postedById])
|
|
@@index([location])
|
|
@@index([isRemote])
|
|
@@index([department])
|
|
@@index([experienceLevel])
|
|
@@index([datePosted])
|
|
@@index([source])
|
|
@@index([moderationStatus])
|
|
@@index([lifecycleStatus])
|
|
@@index([fingerprintHash])
|
|
@@index([sourceId])
|
|
}
|
|
|
|
model UserJobInteraction {
|
|
id String @id @default(cuid())
|
|
userId String
|
|
jobId String
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
job Job @relation(fields: [jobId], references: [id], onDelete: Cascade)
|
|
status String
|
|
notes String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@unique([userId, jobId])
|
|
@@index([userId])
|
|
@@index([jobId])
|
|
}
|
|
|
|
model Application {
|
|
id String @id @default(cuid())
|
|
jobId String
|
|
applicantId String
|
|
job Job @relation(fields: [jobId], references: [id], onDelete: Cascade)
|
|
applicant User @relation(fields: [applicantId], references: [id], onDelete: Cascade)
|
|
resumeId String?
|
|
coverLetter String?
|
|
status String @default("APPLIED")
|
|
answersJson String?
|
|
snapshotJson String?
|
|
employerNotes String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
statusHistory ApplicationStatusHistory[]
|
|
candidateNotes CandidateNote[]
|
|
candidateTags CandidateTag[]
|
|
|
|
@@unique([jobId, applicantId])
|
|
@@index([applicantId])
|
|
@@index([jobId])
|
|
@@index([status])
|
|
}
|
|
|
|
model JobAlert {
|
|
id String @id @default(cuid())
|
|
userId String
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
titleQuery String?
|
|
locationQuery String?
|
|
isRemoteOnly Boolean @default(false)
|
|
minSalary Float?
|
|
frequency String @default("DAILY")
|
|
isActive Boolean @default(true)
|
|
lastSentAt DateTime?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([userId])
|
|
}
|
|
|
|
model Company {
|
|
id String @id @default(cuid())
|
|
name String @unique
|
|
logoUrl String?
|
|
website String?
|
|
cultureInfo String?
|
|
location String?
|
|
description String?
|
|
verificationStatus String @default("UNCLAIMED") // UNCLAIMED, PENDING_VERIFICATION, VERIFIED, REJECTED, REVOKED
|
|
trustStatus String @default("UNVERIFIED") // NEW, UNVERIFIED, PENDING_REVIEW, VERIFIED, RESTRICTED, SUSPENDED
|
|
verifiedAt DateTime?
|
|
reviews CompanyReview[]
|
|
members User[]
|
|
orgMembers OrganizationMembership[]
|
|
invitations OrganizationInvitation[]
|
|
claims CompanyClaim[]
|
|
jobs Job[]
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @default(now()) @updatedAt
|
|
|
|
@@index([trustStatus])
|
|
@@index([verificationStatus])
|
|
}
|
|
|
|
model OrganizationMembership {
|
|
id String @id @default(cuid())
|
|
userId String
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
companyId String
|
|
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
|
|
role String @default("RECRUITER") // OWNER, ADMIN, RECRUITER, HIRING_MANAGER
|
|
status String @default("ACTIVE") // ACTIVE, SUSPENDED, INVITED
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@unique([userId, companyId])
|
|
@@index([companyId])
|
|
@@index([userId])
|
|
@@index([role])
|
|
@@map("organization_memberships")
|
|
}
|
|
|
|
model CompanyClaim {
|
|
id String @id @default(cuid())
|
|
companyId String
|
|
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
|
|
claimantId String
|
|
claimant User @relation("Claimant", fields: [claimantId], references: [id], onDelete: Cascade)
|
|
corporateEmail String
|
|
evidenceType String // DOMAIN_MATCH, DOCUMENT_SUBMISSION, MANUAL_REVIEW
|
|
evidenceData String? // Documentation, notes, domain details
|
|
status String @default("PENDING") // PENDING, UNDER_REVIEW, APPROVED, REJECTED, REVOKED
|
|
adminNotes String?
|
|
reviewedAt DateTime?
|
|
reviewedById String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([companyId])
|
|
@@index([claimantId])
|
|
@@index([status])
|
|
@@map("company_claims")
|
|
}
|
|
|
|
model OrganizationInvitation {
|
|
id String @id @default(cuid())
|
|
companyId String
|
|
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
|
|
email String
|
|
role String @default("RECRUITER") // ADMIN, RECRUITER, HIRING_MANAGER
|
|
token String @unique
|
|
tokenHash String? @unique
|
|
invitedById String
|
|
invitedBy User @relation("InvitedBy", fields: [invitedById], references: [id], onDelete: Cascade)
|
|
expiresAt DateTime
|
|
acceptedAt DateTime?
|
|
revokedAt DateTime?
|
|
createdAt DateTime @default(now())
|
|
|
|
@@index([companyId])
|
|
@@index([email])
|
|
@@index([token])
|
|
@@index([tokenHash])
|
|
@@map("organization_invitations")
|
|
}
|
|
|
|
model CompanyReview {
|
|
id String @id @default(cuid())
|
|
companyId String
|
|
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
|
|
authorId String?
|
|
author User? @relation(fields: [authorId], references: [id], onDelete: SetNull)
|
|
rating Int
|
|
title String
|
|
content String
|
|
status String @default("PENDING")
|
|
isApproved Boolean @default(false)
|
|
isReported Boolean @default(false)
|
|
reportReason String?
|
|
createdAt DateTime @default(now())
|
|
|
|
@@unique([companyId, authorId])
|
|
@@index([companyId])
|
|
@@index([status])
|
|
}
|
|
|
|
model FeaturedJob {
|
|
id String @id @default(cuid())
|
|
jobId String @unique
|
|
tier String @default("FEATURED")
|
|
expiresAt DateTime?
|
|
createdAt DateTime @default(now())
|
|
}
|
|
|
|
model SavedJob {
|
|
id String @id @default(cuid())
|
|
userId String
|
|
jobId String
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
job Job @relation(fields: [jobId], references: [id], onDelete: Cascade)
|
|
savedAt DateTime @default(now())
|
|
|
|
@@unique([userId, jobId])
|
|
@@index([userId])
|
|
@@index([jobId])
|
|
@@map("saved_jobs")
|
|
}
|
|
|
|
model SavedSearch {
|
|
id String @id @default(cuid())
|
|
userId String
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
name String?
|
|
keywords String?
|
|
location String?
|
|
department String?
|
|
isRemoteOnly Boolean @default(false)
|
|
minSalary Float?
|
|
alertFrequency String @default("DAILY")
|
|
isActive Boolean @default(true)
|
|
lastSentAt DateTime?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([userId])
|
|
@@index([isActive, alertFrequency])
|
|
@@index([createdAt])
|
|
@@map("saved_searches")
|
|
}
|
|
|
|
model ApplicationStatusHistory {
|
|
id String @id @default(cuid())
|
|
applicationId String
|
|
application Application @relation(fields: [applicationId], references: [id], onDelete: Cascade)
|
|
fromStatus String?
|
|
toStatus String
|
|
changedById String?
|
|
changedBy User? @relation(fields: [changedById], references: [id], onDelete: SetNull)
|
|
note String?
|
|
createdAt DateTime @default(now())
|
|
|
|
@@index([applicationId])
|
|
@@map("application_status_history")
|
|
}
|
|
|
|
model CandidateNote {
|
|
id String @id @default(cuid())
|
|
applicationId String
|
|
application Application @relation(fields: [applicationId], references: [id], onDelete: Cascade)
|
|
authorId String
|
|
author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
|
|
noteText String
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([applicationId])
|
|
@@map("candidate_notes")
|
|
}
|
|
|
|
model CandidateTag {
|
|
id String @id @default(cuid())
|
|
applicationId String
|
|
application Application @relation(fields: [applicationId], references: [id], onDelete: Cascade)
|
|
tag String
|
|
createdById String
|
|
createdBy User @relation(fields: [createdById], references: [id], onDelete: Cascade)
|
|
createdAt DateTime @default(now())
|
|
|
|
@@unique([applicationId, tag])
|
|
@@index([applicationId])
|
|
@@map("candidate_tags")
|
|
}
|
|
|
|
model Notification {
|
|
id String @id @default(cuid())
|
|
userId String
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
type String
|
|
title String
|
|
message String
|
|
link String?
|
|
isRead Boolean @default(false)
|
|
readAt DateTime?
|
|
createdAt DateTime @default(now())
|
|
|
|
@@index([userId])
|
|
@@index([userId, isRead])
|
|
@@map("notifications")
|
|
}
|
|
|
|
model NotificationPreference {
|
|
id String @id @default(cuid())
|
|
userId String @unique
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
emailStatusChange Boolean @default(true)
|
|
emailJobAlerts Boolean @default(true)
|
|
inAppStatusChange Boolean @default(true)
|
|
inAppJobAlerts Boolean @default(true)
|
|
digestFrequency String @default("IMMEDIATE")
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@map("notification_preferences")
|
|
}
|
|
|
|
model VerificationToken {
|
|
id String @id @default(cuid())
|
|
identifier String
|
|
token String @unique
|
|
expiresAt DateTime
|
|
createdAt DateTime @default(now())
|
|
|
|
@@unique([identifier, token])
|
|
@@map("verification_tokens")
|
|
}
|
|
|
|
model PasswordResetToken {
|
|
id String @id @default(cuid())
|
|
userId String
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
token String @unique
|
|
expiresAt DateTime
|
|
usedAt DateTime?
|
|
createdAt DateTime @default(now())
|
|
|
|
@@index([userId])
|
|
@@map("password_reset_tokens")
|
|
}
|
|
|
|
model AuditLog {
|
|
id String @id @default(cuid())
|
|
actorId String?
|
|
actor User? @relation("ActorAuditLogs", fields: [actorId], references: [id], onDelete: SetNull)
|
|
action String
|
|
targetId String?
|
|
details String?
|
|
ipAddress String?
|
|
createdAt DateTime @default(now())
|
|
|
|
@@index([actorId])
|
|
@@index([action])
|
|
@@index([createdAt])
|
|
@@map("audit_logs")
|
|
}
|
|
|
|
model JobAnalysisCache {
|
|
id String @id @default(cuid())
|
|
userId String
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
targetType String
|
|
targetId String
|
|
contentHash String
|
|
analysisVersion String @default("v1.0")
|
|
modelId String @default("intelligence-hybrid-v1")
|
|
score Int
|
|
headline String
|
|
strongMatches String
|
|
potentialGaps String
|
|
relevantExp String
|
|
explanation String @db.Text
|
|
processingMs Int @default(0)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@unique([userId, targetType, targetId, contentHash])
|
|
@@index([userId, targetType])
|
|
@@index([targetId])
|
|
@@index([contentHash])
|
|
@@map("job_analysis_cache")
|
|
}
|
|
|
|
model JobSource {
|
|
id String @id @default(cuid())
|
|
name String
|
|
type String // ATS, API, RSS, CAREER_PAGE, AGGREGATOR
|
|
provider String // greenhouse, lever, ashby, jobaps, worktable
|
|
baseUrl String
|
|
identifier String? // board slug or external id
|
|
enabled Boolean @default(true)
|
|
status String @default("ACTIVE") // ACTIVE, DEGRADED, FAILED, DISABLED, MAINTENANCE
|
|
scheduleTier String @default("STANDARD") // HIGH_PRIORITY (6h), STANDARD (24h), LOW_PRIORITY (3d)
|
|
rateLimitRpm Int @default(60)
|
|
lastRunAt DateTime?
|
|
lastSuccessAt DateTime?
|
|
lastFailureAt DateTime?
|
|
consecutiveFailures Int @default(0)
|
|
averageDurationMs Int @default(0)
|
|
jobsDiscoveredLastRun Int @default(0)
|
|
jobsCreatedLastRun Int @default(0)
|
|
jobsUpdatedLastRun Int @default(0)
|
|
jobsExpiredLastRun Int @default(0)
|
|
lastError String? @db.Text
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
executionLogs SourceExecutionLog[]
|
|
|
|
@@unique([provider, identifier])
|
|
@@index([status])
|
|
@@index([type])
|
|
@@index([scheduleTier])
|
|
@@map("job_sources")
|
|
}
|
|
|
|
model SourceExecutionLog {
|
|
id String @id @default(cuid())
|
|
sourceId String
|
|
source JobSource @relation(fields: [sourceId], references: [id], onDelete: Cascade)
|
|
status String // SUCCESS, DEGRADED, FAILED
|
|
startedAt DateTime @default(now())
|
|
completedAt DateTime?
|
|
durationMs Int @default(0)
|
|
discoveredCount Int @default(0)
|
|
createdCount Int @default(0)
|
|
updatedCount Int @default(0)
|
|
expiredCount Int @default(0)
|
|
duplicateCount Int @default(0)
|
|
errorMessage String? @db.Text
|
|
diagnosticMetadata String? @db.Text
|
|
createdAt DateTime @default(now())
|
|
|
|
@@index([sourceId])
|
|
@@index([status])
|
|
@@index([createdAt])
|
|
@@map("source_execution_logs")
|
|
}
|
|
|
|
model BetaFeedback {
|
|
id String @id @default(cuid())
|
|
userId String?
|
|
userEmail String?
|
|
category String // BUG, UI_PROBLEM, SEARCH_PROBLEM, JOB_DATA_PROBLEM, FEATURE_REQUEST, OTHER
|
|
description String @db.Text
|
|
expectedBehavior String? @db.Text
|
|
actualBehavior String? @db.Text
|
|
route String?
|
|
pageUrl String?
|
|
viewport String? // mobile, tablet, desktop
|
|
browserInfo String? @db.Text
|
|
createdAt DateTime @default(now())
|
|
|
|
@@index([category])
|
|
@@index([createdAt])
|
|
@@map("beta_feedback")
|
|
}
|
|
|
|
model BetaInvitation {
|
|
id String @id @default(cuid())
|
|
email String @unique
|
|
tokenHash String @unique
|
|
invitedBy String?
|
|
role String @default("BETA_TESTER") // BETA_TESTER, EMPLOYER, ADMIN
|
|
acceptedAt DateTime?
|
|
lastActiveAt DateTime?
|
|
expiresAt DateTime
|
|
createdAt DateTime @default(now())
|
|
|
|
@@index([email])
|
|
@@index([tokenHash])
|
|
@@map("beta_invitations")
|
|
}
|
|
|
|
|
|
|