Auth Domain (Complete): - auth_user: Better Auth compatible with password, 2FA, SSO support - auth_session: session management with IP and user agent tracking - auth_account: OAuth provider accounts - auth_oauth_client + tokens: OAuth client configuration and token management - auth_user_type: role definitions (admin, customer, internal) - auth_domain: multi-tenant domain grouping - auth_password_history + reset: password security and recovery - auth_permission_group + rule + category: RBAC system with granular permissions - auth_security_question + answer: account recovery mechanism Quest Domain (Partial - Core User/Company): - quest_user: portal-specific user data (sub-user flag, API tokens) - quest_company: customer/company info (Epicor integration, invoicing flags) - quest_user_company: many-to-many with active company tracking Migration applied successfully to PostgreSQL 16 database. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
373 lines
12 KiB
Text
373 lines
12 KiB
Text
// Prisma schema for Vorteq Quest Portal
|
|
// PostgreSQL 16 database
|
|
|
|
generator client {
|
|
provider = "prisma-client-js"
|
|
}
|
|
|
|
datasource db {
|
|
provider = "postgresql"
|
|
url = env("DATABASE_URL")
|
|
}
|
|
|
|
// =============================================================================
|
|
// AUTH DOMAIN - User Authentication & Authorization
|
|
// =============================================================================
|
|
|
|
// Core user authentication table (Better Auth compatible)
|
|
model auth_user {
|
|
id String @id @default(cuid())
|
|
email String @unique
|
|
email_verified Boolean @default(false)
|
|
name String?
|
|
image String?
|
|
created_at DateTime @default(now())
|
|
updated_at DateTime @updatedAt
|
|
|
|
// Password authentication
|
|
password_hash String?
|
|
|
|
// Account security
|
|
deactivated Boolean @default(false)
|
|
deactivated_at DateTime?
|
|
deactivation_reason String?
|
|
|
|
// Login tracking
|
|
login_count Int @default(0)
|
|
failed_login_count Int @default(0)
|
|
last_login_at DateTime?
|
|
last_login_ip String?
|
|
last_failed_login_at DateTime?
|
|
|
|
// Two-factor authentication
|
|
two_factor_enabled Boolean @default(false)
|
|
two_factor_secret String?
|
|
two_factor_backup_codes String?
|
|
|
|
// SSO fields
|
|
sso_provider String?
|
|
sso_id String?
|
|
|
|
// Relationships
|
|
auth_user_type_id String
|
|
auth_user_type auth_user_type @relation(fields: [auth_user_type_id], references: [id])
|
|
auth_domain_id String?
|
|
auth_domain auth_domain? @relation(fields: [auth_domain_id], references: [id])
|
|
|
|
sessions auth_session[]
|
|
accounts auth_account[]
|
|
password_history auth_password_history[]
|
|
password_resets auth_password_reset[]
|
|
security_answers auth_security_answer[]
|
|
quest_user quest_user?
|
|
|
|
@@index([email])
|
|
@@index([auth_user_type_id])
|
|
@@index([auth_domain_id])
|
|
@@map("auth_user")
|
|
}
|
|
|
|
// User session table (Better Auth compatible)
|
|
model auth_session {
|
|
id String @id @default(cuid())
|
|
user_id String
|
|
token String @unique
|
|
expires_at DateTime
|
|
ip_address String?
|
|
user_agent String?
|
|
created_at DateTime @default(now())
|
|
updated_at DateTime @updatedAt
|
|
|
|
user auth_user @relation(fields: [user_id], references: [id], onDelete: Cascade)
|
|
|
|
@@index([user_id])
|
|
@@index([token])
|
|
@@index([expires_at])
|
|
@@map("auth_session")
|
|
}
|
|
|
|
// OAuth/Social provider accounts (Better Auth compatible)
|
|
model auth_account {
|
|
id String @id @default(cuid())
|
|
user_id String
|
|
account_id String
|
|
provider_id String
|
|
access_token String?
|
|
refresh_token String?
|
|
id_token String?
|
|
access_token_expires_at DateTime?
|
|
refresh_token_expires_at DateTime?
|
|
scope String?
|
|
password String?
|
|
created_at DateTime @default(now())
|
|
updated_at DateTime @updatedAt
|
|
|
|
user auth_user @relation(fields: [user_id], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([provider_id, account_id])
|
|
@@index([user_id])
|
|
@@map("auth_account")
|
|
}
|
|
|
|
// OAuth client configuration
|
|
model auth_oauth_client {
|
|
id String @id @default(cuid())
|
|
client_id String @unique
|
|
client_secret String
|
|
redirect_uris String[] // Array of allowed redirect URIs
|
|
grant_types String[] // Array of allowed grant types
|
|
name String
|
|
created_at DateTime @default(now())
|
|
updated_at DateTime @updatedAt
|
|
revoked Boolean @default(false)
|
|
|
|
access_tokens auth_oauth_access_token[]
|
|
refresh_tokens auth_oauth_refresh_token[]
|
|
|
|
@@map("auth_oauth_client")
|
|
}
|
|
|
|
// OAuth access tokens
|
|
model auth_oauth_access_token {
|
|
id String @id @default(cuid())
|
|
access_token String @unique
|
|
client_id String
|
|
user_id String?
|
|
expires_at DateTime
|
|
scopes String[] // Array of scopes
|
|
created_at DateTime @default(now())
|
|
|
|
client auth_oauth_client @relation(fields: [client_id], references: [client_id], onDelete: Cascade)
|
|
|
|
@@index([access_token])
|
|
@@index([expires_at])
|
|
@@map("auth_oauth_access_token")
|
|
}
|
|
|
|
// OAuth refresh tokens
|
|
model auth_oauth_refresh_token {
|
|
id String @id @default(cuid())
|
|
refresh_token String @unique
|
|
access_token_id String?
|
|
client_id String
|
|
user_id String?
|
|
expires_at DateTime
|
|
scopes String[] // Array of scopes
|
|
created_at DateTime @default(now())
|
|
revoked Boolean @default(false)
|
|
|
|
client auth_oauth_client @relation(fields: [client_id], references: [client_id], onDelete: Cascade)
|
|
|
|
@@index([refresh_token])
|
|
@@index([expires_at])
|
|
@@map("auth_oauth_refresh_token")
|
|
}
|
|
|
|
// User type/role definitions
|
|
model auth_user_type {
|
|
id String @id @default(cuid())
|
|
name String @unique
|
|
description String?
|
|
is_admin Boolean @default(false)
|
|
is_customer Boolean @default(false)
|
|
is_internal Boolean @default(false)
|
|
created_at DateTime @default(now())
|
|
updated_at DateTime @updatedAt
|
|
|
|
users auth_user[]
|
|
|
|
@@map("auth_user_type")
|
|
}
|
|
|
|
// Domain/company grouping for multi-tenant scenarios
|
|
model auth_domain {
|
|
id String @id @default(cuid())
|
|
name String @unique
|
|
description String?
|
|
is_active Boolean @default(true)
|
|
created_at DateTime @default(now())
|
|
updated_at DateTime @updatedAt
|
|
|
|
users auth_user[]
|
|
|
|
@@map("auth_domain")
|
|
}
|
|
|
|
// Password history for preventing reuse
|
|
model auth_password_history {
|
|
id String @id @default(cuid())
|
|
user_id String
|
|
password_hash String
|
|
created_at DateTime @default(now())
|
|
|
|
user auth_user @relation(fields: [user_id], references: [id], onDelete: Cascade)
|
|
|
|
@@index([user_id])
|
|
@@map("auth_password_history")
|
|
}
|
|
|
|
// Password reset tokens
|
|
model auth_password_reset {
|
|
id String @id @default(cuid())
|
|
user_id String
|
|
token String @unique
|
|
expires_at DateTime
|
|
used_at DateTime?
|
|
ip_address String?
|
|
min_time_bypass Boolean @default(false)
|
|
created_at DateTime @default(now())
|
|
|
|
user auth_user @relation(fields: [user_id], references: [id], onDelete: Cascade)
|
|
|
|
@@index([token])
|
|
@@index([user_id])
|
|
@@index([expires_at])
|
|
@@map("auth_password_reset")
|
|
}
|
|
|
|
// Permission groups (roles)
|
|
model auth_permission_group {
|
|
id String @id @default(cuid())
|
|
name String @unique
|
|
description String?
|
|
is_active Boolean @default(true)
|
|
created_at DateTime @default(now())
|
|
updated_at DateTime @updatedAt
|
|
|
|
rules auth_permission_group_rule[]
|
|
|
|
@@map("auth_permission_group")
|
|
}
|
|
|
|
// Permission rules (individual permissions)
|
|
model auth_permission_rule {
|
|
id String @id @default(cuid())
|
|
name String @unique
|
|
description String?
|
|
auth_permission_rule_category_id String?
|
|
category auth_permission_rule_category? @relation(fields: [auth_permission_rule_category_id], references: [id])
|
|
created_at DateTime @default(now())
|
|
updated_at DateTime @updatedAt
|
|
|
|
groups auth_permission_group_rule[]
|
|
|
|
@@index([auth_permission_rule_category_id])
|
|
@@map("auth_permission_rule")
|
|
}
|
|
|
|
// Permission rule categories for organization
|
|
model auth_permission_rule_category {
|
|
id String @id @default(cuid())
|
|
name String @unique
|
|
description String?
|
|
created_at DateTime @default(now())
|
|
updated_at DateTime @updatedAt
|
|
|
|
rules auth_permission_rule[]
|
|
|
|
@@map("auth_permission_rule_category")
|
|
}
|
|
|
|
// Junction table: permission groups to rules (many-to-many)
|
|
model auth_permission_group_rule {
|
|
id String @id @default(cuid())
|
|
auth_permission_group_id String
|
|
auth_permission_rule_id String
|
|
created_at DateTime @default(now())
|
|
|
|
group auth_permission_group @relation(fields: [auth_permission_group_id], references: [id], onDelete: Cascade)
|
|
rule auth_permission_rule @relation(fields: [auth_permission_rule_id], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([auth_permission_group_id, auth_permission_rule_id])
|
|
@@index([auth_permission_group_id])
|
|
@@index([auth_permission_rule_id])
|
|
@@map("auth_permission_group_rule")
|
|
}
|
|
|
|
// Security questions for account recovery
|
|
model auth_security_question {
|
|
id String @id @default(cuid())
|
|
question String @unique
|
|
is_active Boolean @default(true)
|
|
sort_order Int @default(0)
|
|
created_at DateTime @default(now())
|
|
updated_at DateTime @updatedAt
|
|
|
|
answers auth_security_answer[]
|
|
|
|
@@map("auth_security_question")
|
|
}
|
|
|
|
// User security question answers
|
|
model auth_security_answer {
|
|
id String @id @default(cuid())
|
|
user_id String
|
|
auth_security_question_id String
|
|
answer_hash String
|
|
created_at DateTime @default(now())
|
|
updated_at DateTime @updatedAt
|
|
|
|
user auth_user @relation(fields: [user_id], references: [id], onDelete: Cascade)
|
|
question auth_security_question @relation(fields: [auth_security_question_id], references: [id])
|
|
|
|
@@unique([user_id, auth_security_question_id])
|
|
@@index([user_id])
|
|
@@map("auth_security_answer")
|
|
}
|
|
|
|
// =============================================================================
|
|
// QUEST DOMAIN - Portal-specific user and company data
|
|
// =============================================================================
|
|
|
|
// Quest-specific user data (extends auth_user)
|
|
model quest_user {
|
|
id String @id @default(cuid())
|
|
auth_user_id String @unique
|
|
is_sub_user Boolean @default(false)
|
|
api_token String? @unique
|
|
api_token_expires_at DateTime?
|
|
created_at DateTime @default(now())
|
|
updated_at DateTime @updatedAt
|
|
|
|
auth_user auth_user @relation(fields: [auth_user_id], references: [id], onDelete: Cascade)
|
|
companies quest_user_company[]
|
|
|
|
@@index([auth_user_id])
|
|
@@map("quest_user")
|
|
}
|
|
|
|
// Company/customer information
|
|
model quest_company {
|
|
id String @id @default(cuid())
|
|
epicor_cust_id String @unique
|
|
display_name String
|
|
can_access_invoices Boolean @default(false)
|
|
invoicing_email String?
|
|
order_ack_email String?
|
|
receives_so_emails Boolean @default(false)
|
|
is_active Boolean @default(true)
|
|
created_at DateTime @default(now())
|
|
updated_at DateTime @updatedAt
|
|
|
|
users quest_user_company[]
|
|
|
|
@@index([epicor_cust_id])
|
|
@@map("quest_company")
|
|
}
|
|
|
|
// Junction table: users to companies (many-to-many with active company tracking)
|
|
model quest_user_company {
|
|
id String @id @default(cuid())
|
|
quest_user_id String
|
|
quest_company_id String
|
|
is_active_company Boolean @default(false)
|
|
created_at DateTime @default(now())
|
|
|
|
user quest_user @relation(fields: [quest_user_id], references: [id], onDelete: Cascade)
|
|
company quest_company @relation(fields: [quest_company_id], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([quest_user_id, quest_company_id])
|
|
@@index([quest_user_id])
|
|
@@index([quest_company_id])
|
|
@@map("quest_user_company")
|
|
}
|