feat(F-003): implement auth domain Prisma schema and migration
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>
This commit is contained in:
parent
7cd434aa82
commit
e643af1fe6
4 changed files with 813 additions and 19 deletions
28
TASKS.md
28
TASKS.md
|
|
@ -31,21 +31,21 @@
|
|||
- **Deps:** F-001 | **Est:** 1 hr (remaining) | **Status:** ✅ Complete
|
||||
|
||||
### F-003: Prisma Schema - Auth Domain
|
||||
- [ ] `auth_user` table (email, password hash, deactivated, verified, login counts, 2FA, SSO fields)
|
||||
- [ ] `auth_domain` table
|
||||
- [ ] `auth_user_type` table
|
||||
- [ ] `auth_oauth_client` + `auth_oauth_access_token` + `auth_oauth_refresh_token`
|
||||
- [ ] `auth_password_history` + `auth_password_reset`
|
||||
- [ ] `auth_permission_group` + `auth_permission_rule` + junction table
|
||||
- [ ] `auth_permission_rule_category`
|
||||
- [ ] `auth_security_question` + `auth_security_answer`
|
||||
- [ ] Run initial migration, verify schema
|
||||
- **Deps:** F-001 | **Est:** 4 hrs
|
||||
- [x] `auth_user` table (email, password hash, deactivated, verified, login counts, 2FA, SSO fields)
|
||||
- [x] `auth_domain` table
|
||||
- [x] `auth_user_type` table
|
||||
- [x] `auth_oauth_client` + `auth_oauth_access_token` + `auth_oauth_refresh_token`
|
||||
- [x] `auth_password_history` + `auth_password_reset`
|
||||
- [x] `auth_permission_group` + `auth_permission_rule` + junction table
|
||||
- [x] `auth_permission_rule_category`
|
||||
- [x] `auth_security_question` + `auth_security_answer`
|
||||
- [x] Run initial migration, verify schema
|
||||
- **Deps:** F-001 | **Est:** 4 hrs | **Status:** ✅ Complete
|
||||
|
||||
### F-004: Prisma Schema - Quest Domain
|
||||
- [ ] `quest_company` (EpicorCustID, display name, invoicing flags)
|
||||
- [ ] `quest_user` (FK to auth_user, IsSubUser, API token)
|
||||
- [ ] `quest_user_company` junction table
|
||||
- [x] `quest_company` (EpicorCustID, display name, invoicing flags)
|
||||
- [x] `quest_user` (FK to auth_user, IsSubUser, API token)
|
||||
- [x] `quest_user_company` junction table
|
||||
- [ ] `quest_account_request`
|
||||
- [ ] `quest_notification` + `quest_user_notification_alert_read`
|
||||
- [ ] `quest_email_event` + `quest_user_email_event` junction
|
||||
|
|
@ -53,7 +53,7 @@
|
|||
- [ ] `quest_plant` + `quest_inventory_type` + `quest_inventory_plant`
|
||||
- [ ] `quest_processed_order_acknowledgement_email`
|
||||
- [ ] Run migration, verify
|
||||
- **Deps:** F-003 | **Est:** 3 hrs
|
||||
- **Deps:** F-003 | **Est:** 3 hrs | **Status:** 🔨 In Progress
|
||||
|
||||
### F-005: Prisma Schema - Remaining Domains
|
||||
- [ ] `ship_request` + `ship_request_detail`
|
||||
|
|
|
|||
|
|
@ -0,0 +1,437 @@
|
|||
-- CreateTable
|
||||
CREATE TABLE "auth_user" (
|
||||
"id" TEXT NOT NULL,
|
||||
"email" TEXT NOT NULL,
|
||||
"email_verified" BOOLEAN NOT NULL DEFAULT false,
|
||||
"name" TEXT,
|
||||
"image" TEXT,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
"password_hash" TEXT,
|
||||
"deactivated" BOOLEAN NOT NULL DEFAULT false,
|
||||
"deactivated_at" TIMESTAMP(3),
|
||||
"deactivation_reason" TEXT,
|
||||
"login_count" INTEGER NOT NULL DEFAULT 0,
|
||||
"failed_login_count" INTEGER NOT NULL DEFAULT 0,
|
||||
"last_login_at" TIMESTAMP(3),
|
||||
"last_login_ip" TEXT,
|
||||
"last_failed_login_at" TIMESTAMP(3),
|
||||
"two_factor_enabled" BOOLEAN NOT NULL DEFAULT false,
|
||||
"two_factor_secret" TEXT,
|
||||
"two_factor_backup_codes" TEXT,
|
||||
"sso_provider" TEXT,
|
||||
"sso_id" TEXT,
|
||||
"auth_user_type_id" TEXT NOT NULL,
|
||||
"auth_domain_id" TEXT,
|
||||
|
||||
CONSTRAINT "auth_user_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "auth_session" (
|
||||
"id" TEXT NOT NULL,
|
||||
"user_id" TEXT NOT NULL,
|
||||
"token" TEXT NOT NULL,
|
||||
"expires_at" TIMESTAMP(3) NOT NULL,
|
||||
"ip_address" TEXT,
|
||||
"user_agent" TEXT,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "auth_session_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "auth_account" (
|
||||
"id" TEXT NOT NULL,
|
||||
"user_id" TEXT NOT NULL,
|
||||
"account_id" TEXT NOT NULL,
|
||||
"provider_id" TEXT NOT NULL,
|
||||
"access_token" TEXT,
|
||||
"refresh_token" TEXT,
|
||||
"id_token" TEXT,
|
||||
"access_token_expires_at" TIMESTAMP(3),
|
||||
"refresh_token_expires_at" TIMESTAMP(3),
|
||||
"scope" TEXT,
|
||||
"password" TEXT,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "auth_account_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "auth_oauth_client" (
|
||||
"id" TEXT NOT NULL,
|
||||
"client_id" TEXT NOT NULL,
|
||||
"client_secret" TEXT NOT NULL,
|
||||
"redirect_uris" TEXT[],
|
||||
"grant_types" TEXT[],
|
||||
"name" TEXT NOT NULL,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
"revoked" BOOLEAN NOT NULL DEFAULT false,
|
||||
|
||||
CONSTRAINT "auth_oauth_client_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "auth_oauth_access_token" (
|
||||
"id" TEXT NOT NULL,
|
||||
"access_token" TEXT NOT NULL,
|
||||
"client_id" TEXT NOT NULL,
|
||||
"user_id" TEXT,
|
||||
"expires_at" TIMESTAMP(3) NOT NULL,
|
||||
"scopes" TEXT[],
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "auth_oauth_access_token_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "auth_oauth_refresh_token" (
|
||||
"id" TEXT NOT NULL,
|
||||
"refresh_token" TEXT NOT NULL,
|
||||
"access_token_id" TEXT,
|
||||
"client_id" TEXT NOT NULL,
|
||||
"user_id" TEXT,
|
||||
"expires_at" TIMESTAMP(3) NOT NULL,
|
||||
"scopes" TEXT[],
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"revoked" BOOLEAN NOT NULL DEFAULT false,
|
||||
|
||||
CONSTRAINT "auth_oauth_refresh_token_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "auth_user_type" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"description" TEXT,
|
||||
"is_admin" BOOLEAN NOT NULL DEFAULT false,
|
||||
"is_customer" BOOLEAN NOT NULL DEFAULT false,
|
||||
"is_internal" BOOLEAN NOT NULL DEFAULT false,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "auth_user_type_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "auth_domain" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"description" TEXT,
|
||||
"is_active" BOOLEAN NOT NULL DEFAULT true,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "auth_domain_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "auth_password_history" (
|
||||
"id" TEXT NOT NULL,
|
||||
"user_id" TEXT NOT NULL,
|
||||
"password_hash" TEXT NOT NULL,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "auth_password_history_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "auth_password_reset" (
|
||||
"id" TEXT NOT NULL,
|
||||
"user_id" TEXT NOT NULL,
|
||||
"token" TEXT NOT NULL,
|
||||
"expires_at" TIMESTAMP(3) NOT NULL,
|
||||
"used_at" TIMESTAMP(3),
|
||||
"ip_address" TEXT,
|
||||
"min_time_bypass" BOOLEAN NOT NULL DEFAULT false,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "auth_password_reset_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "auth_permission_group" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"description" TEXT,
|
||||
"is_active" BOOLEAN NOT NULL DEFAULT true,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "auth_permission_group_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "auth_permission_rule" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"description" TEXT,
|
||||
"auth_permission_rule_category_id" TEXT,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "auth_permission_rule_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "auth_permission_rule_category" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"description" TEXT,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "auth_permission_rule_category_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "auth_permission_group_rule" (
|
||||
"id" TEXT NOT NULL,
|
||||
"auth_permission_group_id" TEXT NOT NULL,
|
||||
"auth_permission_rule_id" TEXT NOT NULL,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "auth_permission_group_rule_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "auth_security_question" (
|
||||
"id" TEXT NOT NULL,
|
||||
"question" TEXT NOT NULL,
|
||||
"is_active" BOOLEAN NOT NULL DEFAULT true,
|
||||
"sort_order" INTEGER NOT NULL DEFAULT 0,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "auth_security_question_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "auth_security_answer" (
|
||||
"id" TEXT NOT NULL,
|
||||
"user_id" TEXT NOT NULL,
|
||||
"auth_security_question_id" TEXT NOT NULL,
|
||||
"answer_hash" TEXT NOT NULL,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "auth_security_answer_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "quest_user" (
|
||||
"id" TEXT NOT NULL,
|
||||
"auth_user_id" TEXT NOT NULL,
|
||||
"is_sub_user" BOOLEAN NOT NULL DEFAULT false,
|
||||
"api_token" TEXT,
|
||||
"api_token_expires_at" TIMESTAMP(3),
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "quest_user_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "quest_company" (
|
||||
"id" TEXT NOT NULL,
|
||||
"epicor_cust_id" TEXT NOT NULL,
|
||||
"display_name" TEXT NOT NULL,
|
||||
"can_access_invoices" BOOLEAN NOT NULL DEFAULT false,
|
||||
"invoicing_email" TEXT,
|
||||
"order_ack_email" TEXT,
|
||||
"receives_so_emails" BOOLEAN NOT NULL DEFAULT false,
|
||||
"is_active" BOOLEAN NOT NULL DEFAULT true,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "quest_company_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "quest_user_company" (
|
||||
"id" TEXT NOT NULL,
|
||||
"quest_user_id" TEXT NOT NULL,
|
||||
"quest_company_id" TEXT NOT NULL,
|
||||
"is_active_company" BOOLEAN NOT NULL DEFAULT false,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "quest_user_company_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "auth_user_email_key" ON "auth_user"("email");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "auth_user_email_idx" ON "auth_user"("email");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "auth_user_auth_user_type_id_idx" ON "auth_user"("auth_user_type_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "auth_user_auth_domain_id_idx" ON "auth_user"("auth_domain_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "auth_session_token_key" ON "auth_session"("token");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "auth_session_user_id_idx" ON "auth_session"("user_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "auth_session_token_idx" ON "auth_session"("token");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "auth_session_expires_at_idx" ON "auth_session"("expires_at");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "auth_account_user_id_idx" ON "auth_account"("user_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "auth_account_provider_id_account_id_key" ON "auth_account"("provider_id", "account_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "auth_oauth_client_client_id_key" ON "auth_oauth_client"("client_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "auth_oauth_access_token_access_token_key" ON "auth_oauth_access_token"("access_token");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "auth_oauth_access_token_access_token_idx" ON "auth_oauth_access_token"("access_token");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "auth_oauth_access_token_expires_at_idx" ON "auth_oauth_access_token"("expires_at");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "auth_oauth_refresh_token_refresh_token_key" ON "auth_oauth_refresh_token"("refresh_token");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "auth_oauth_refresh_token_refresh_token_idx" ON "auth_oauth_refresh_token"("refresh_token");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "auth_oauth_refresh_token_expires_at_idx" ON "auth_oauth_refresh_token"("expires_at");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "auth_user_type_name_key" ON "auth_user_type"("name");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "auth_domain_name_key" ON "auth_domain"("name");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "auth_password_history_user_id_idx" ON "auth_password_history"("user_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "auth_password_reset_token_key" ON "auth_password_reset"("token");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "auth_password_reset_token_idx" ON "auth_password_reset"("token");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "auth_password_reset_user_id_idx" ON "auth_password_reset"("user_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "auth_password_reset_expires_at_idx" ON "auth_password_reset"("expires_at");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "auth_permission_group_name_key" ON "auth_permission_group"("name");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "auth_permission_rule_name_key" ON "auth_permission_rule"("name");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "auth_permission_rule_auth_permission_rule_category_id_idx" ON "auth_permission_rule"("auth_permission_rule_category_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "auth_permission_rule_category_name_key" ON "auth_permission_rule_category"("name");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "auth_permission_group_rule_auth_permission_group_id_idx" ON "auth_permission_group_rule"("auth_permission_group_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "auth_permission_group_rule_auth_permission_rule_id_idx" ON "auth_permission_group_rule"("auth_permission_rule_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "auth_permission_group_rule_auth_permission_group_id_auth_pe_key" ON "auth_permission_group_rule"("auth_permission_group_id", "auth_permission_rule_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "auth_security_question_question_key" ON "auth_security_question"("question");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "auth_security_answer_user_id_idx" ON "auth_security_answer"("user_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "auth_security_answer_user_id_auth_security_question_id_key" ON "auth_security_answer"("user_id", "auth_security_question_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "quest_user_auth_user_id_key" ON "quest_user"("auth_user_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "quest_user_api_token_key" ON "quest_user"("api_token");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "quest_user_auth_user_id_idx" ON "quest_user"("auth_user_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "quest_company_epicor_cust_id_key" ON "quest_company"("epicor_cust_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "quest_company_epicor_cust_id_idx" ON "quest_company"("epicor_cust_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "quest_user_company_quest_user_id_idx" ON "quest_user_company"("quest_user_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "quest_user_company_quest_company_id_idx" ON "quest_user_company"("quest_company_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "quest_user_company_quest_user_id_quest_company_id_key" ON "quest_user_company"("quest_user_id", "quest_company_id");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "auth_user" ADD CONSTRAINT "auth_user_auth_user_type_id_fkey" FOREIGN KEY ("auth_user_type_id") REFERENCES "auth_user_type"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "auth_user" ADD CONSTRAINT "auth_user_auth_domain_id_fkey" FOREIGN KEY ("auth_domain_id") REFERENCES "auth_domain"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "auth_session" ADD CONSTRAINT "auth_session_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "auth_user"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "auth_account" ADD CONSTRAINT "auth_account_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "auth_user"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "auth_oauth_access_token" ADD CONSTRAINT "auth_oauth_access_token_client_id_fkey" FOREIGN KEY ("client_id") REFERENCES "auth_oauth_client"("client_id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "auth_oauth_refresh_token" ADD CONSTRAINT "auth_oauth_refresh_token_client_id_fkey" FOREIGN KEY ("client_id") REFERENCES "auth_oauth_client"("client_id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "auth_password_history" ADD CONSTRAINT "auth_password_history_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "auth_user"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "auth_password_reset" ADD CONSTRAINT "auth_password_reset_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "auth_user"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "auth_permission_rule" ADD CONSTRAINT "auth_permission_rule_auth_permission_rule_category_id_fkey" FOREIGN KEY ("auth_permission_rule_category_id") REFERENCES "auth_permission_rule_category"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "auth_permission_group_rule" ADD CONSTRAINT "auth_permission_group_rule_auth_permission_group_id_fkey" FOREIGN KEY ("auth_permission_group_id") REFERENCES "auth_permission_group"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "auth_permission_group_rule" ADD CONSTRAINT "auth_permission_group_rule_auth_permission_rule_id_fkey" FOREIGN KEY ("auth_permission_rule_id") REFERENCES "auth_permission_rule"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "auth_security_answer" ADD CONSTRAINT "auth_security_answer_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "auth_user"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "auth_security_answer" ADD CONSTRAINT "auth_security_answer_auth_security_question_id_fkey" FOREIGN KEY ("auth_security_question_id") REFERENCES "auth_security_question"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "quest_user" ADD CONSTRAINT "quest_user_auth_user_id_fkey" FOREIGN KEY ("auth_user_id") REFERENCES "auth_user"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "quest_user_company" ADD CONSTRAINT "quest_user_company_quest_user_id_fkey" FOREIGN KEY ("quest_user_id") REFERENCES "quest_user"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "quest_user_company" ADD CONSTRAINT "quest_user_company_quest_company_id_fkey" FOREIGN KEY ("quest_company_id") REFERENCES "quest_company"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
3
prisma/migrations/migration_lock.toml
Normal file
3
prisma/migrations/migration_lock.toml
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Please do not edit this file manually
|
||||
# It should be added in your version-control system (e.g., Git)
|
||||
provider = "postgresql"
|
||||
|
|
@ -10,10 +10,364 @@ datasource db {
|
|||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
// Placeholder - full schema will be created in F-003, F-004, F-005
|
||||
// This minimal schema allows Prisma client to be generated
|
||||
// =============================================================================
|
||||
// AUTH DOMAIN - User Authentication & Authorization
|
||||
// =============================================================================
|
||||
|
||||
model placeholder {
|
||||
// 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")
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue