// 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") }