feat: implement enterprise RBAC, profile identity, system management, and audit stabilization
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "User"
|
||||
ADD COLUMN "avatar_url" TEXT,
|
||||
ADD COLUMN "profile_metadata" JSONB NOT NULL DEFAULT '{}',
|
||||
ADD COLUMN "must_change_password" BOOLEAN NOT NULL DEFAULT false,
|
||||
ADD COLUMN "mfa_enabled" BOOLEAN NOT NULL DEFAULT false,
|
||||
ADD COLUMN "mfa_secret" TEXT,
|
||||
ADD COLUMN "mfa_recovery_codes" JSONB NOT NULL DEFAULT '[]',
|
||||
ADD COLUMN "password_changed_at" TIMESTAMP(3);
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Tenant"
|
||||
ADD COLUMN "trial_starts_at" TIMESTAMP(3),
|
||||
ADD COLUMN "trial_ends_at" TIMESTAMP(3),
|
||||
ADD COLUMN "trial_grace_ends_at" TIMESTAMP(3),
|
||||
ADD COLUMN "trial_days" INTEGER,
|
||||
ADD COLUMN "trial_locked" BOOLEAN NOT NULL DEFAULT false;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "AuthSession" (
|
||||
"id" TEXT NOT NULL,
|
||||
"user_id" TEXT NOT NULL,
|
||||
"refresh_token_hash" TEXT NOT NULL,
|
||||
"ip_address" TEXT,
|
||||
"user_agent" TEXT,
|
||||
"issued_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"expires_at" TIMESTAMP(3) NOT NULL,
|
||||
"last_used_at" TIMESTAMP(3),
|
||||
"revoked_at" TIMESTAMP(3),
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "AuthSession_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "PasswordResetToken" (
|
||||
"id" TEXT NOT NULL,
|
||||
"user_id" TEXT NOT NULL,
|
||||
"token_hash" TEXT NOT NULL,
|
||||
"expires_at" TIMESTAMP(3) NOT NULL,
|
||||
"used_at" TIMESTAMP(3),
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "PasswordResetToken_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "CmsPage" (
|
||||
"id" TEXT NOT NULL,
|
||||
"slug" TEXT NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
"section" TEXT NOT NULL DEFAULT 'general',
|
||||
"content" JSONB NOT NULL DEFAULT '{}',
|
||||
"is_published" BOOLEAN NOT NULL DEFAULT false,
|
||||
"created_by" TEXT,
|
||||
"updated_by" TEXT,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "CmsPage_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "SiteNavigationItem" (
|
||||
"id" TEXT NOT NULL,
|
||||
"label" TEXT NOT NULL,
|
||||
"href" TEXT NOT NULL,
|
||||
"position" TEXT NOT NULL DEFAULT 'header',
|
||||
"sort_order" INTEGER NOT NULL DEFAULT 100,
|
||||
"is_enabled" BOOLEAN NOT NULL DEFAULT true,
|
||||
"metadata" JSONB NOT NULL DEFAULT '{}',
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "SiteNavigationItem_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "AuthSession_refresh_token_hash_key" ON "AuthSession"("refresh_token_hash");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "AuthSession_user_id_revoked_at_idx" ON "AuthSession"("user_id", "revoked_at");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "AuthSession_expires_at_idx" ON "AuthSession"("expires_at");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "PasswordResetToken_token_hash_key" ON "PasswordResetToken"("token_hash");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "PasswordResetToken_user_id_expires_at_idx" ON "PasswordResetToken"("user_id", "expires_at");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "CmsPage_slug_key" ON "CmsPage"("slug");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "CmsPage_section_is_published_idx" ON "CmsPage"("section", "is_published");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "SiteNavigationItem_position_sort_order_idx" ON "SiteNavigationItem"("position", "sort_order");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Tenant_trial_ends_at_idx" ON "Tenant"("trial_ends_at");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "AuthSession" ADD CONSTRAINT "AuthSession_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "PasswordResetToken" ADD CONSTRAINT "PasswordResetToken_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@@ -302,14 +302,23 @@ model User {
|
||||
email String @unique
|
||||
password_hash String
|
||||
full_name String?
|
||||
avatar_url String?
|
||||
profile_metadata Json @default("{}")
|
||||
role Role @default(VIEWER)
|
||||
tenant_id String?
|
||||
is_active Boolean @default(true)
|
||||
must_change_password Boolean @default(false)
|
||||
mfa_enabled Boolean @default(false)
|
||||
mfa_secret String?
|
||||
mfa_recovery_codes Json @default("[]")
|
||||
password_changed_at DateTime?
|
||||
last_login_at DateTime?
|
||||
created_at DateTime @default(now())
|
||||
updated_at DateTime @updatedAt
|
||||
|
||||
tenant Tenant? @relation(fields: [tenant_id], references: [id], onDelete: SetNull)
|
||||
auth_sessions AuthSession[]
|
||||
password_reset_tokens PasswordResetToken[]
|
||||
|
||||
@@index([tenant_id])
|
||||
}
|
||||
@@ -319,6 +328,11 @@ model Tenant {
|
||||
name String
|
||||
slug String @unique
|
||||
status TenantStatus @default(ACTIVE)
|
||||
trial_starts_at DateTime?
|
||||
trial_ends_at DateTime?
|
||||
trial_grace_ends_at DateTime?
|
||||
trial_days Int?
|
||||
trial_locked Boolean @default(false)
|
||||
plan String @default("starter")
|
||||
owner_email String
|
||||
member_emails Json @default("[]")
|
||||
@@ -351,9 +365,42 @@ model Tenant {
|
||||
monitoring_alert_events MonitoringAlertEvent[]
|
||||
|
||||
@@index([status])
|
||||
@@index([trial_ends_at])
|
||||
@@index([owner_email])
|
||||
}
|
||||
|
||||
model AuthSession {
|
||||
id String @id @default(cuid())
|
||||
user_id String
|
||||
refresh_token_hash String @unique
|
||||
ip_address String?
|
||||
user_agent String?
|
||||
issued_at DateTime @default(now())
|
||||
expires_at DateTime
|
||||
last_used_at DateTime?
|
||||
revoked_at DateTime?
|
||||
created_at DateTime @default(now())
|
||||
updated_at DateTime @updatedAt
|
||||
|
||||
user User @relation(fields: [user_id], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([user_id, revoked_at])
|
||||
@@index([expires_at])
|
||||
}
|
||||
|
||||
model PasswordResetToken {
|
||||
id String @id @default(cuid())
|
||||
user_id String
|
||||
token_hash String @unique
|
||||
expires_at DateTime
|
||||
used_at DateTime?
|
||||
created_at DateTime @default(now())
|
||||
|
||||
user User @relation(fields: [user_id], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([user_id, expires_at])
|
||||
}
|
||||
|
||||
model ProxmoxNode {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
@@ -1203,3 +1250,32 @@ model Setting {
|
||||
created_at DateTime @default(now())
|
||||
updated_at DateTime @updatedAt
|
||||
}
|
||||
|
||||
model CmsPage {
|
||||
id String @id @default(cuid())
|
||||
slug String @unique
|
||||
title String
|
||||
section String @default("general")
|
||||
content Json @default("{}")
|
||||
is_published Boolean @default(false)
|
||||
created_by String?
|
||||
updated_by String?
|
||||
created_at DateTime @default(now())
|
||||
updated_at DateTime @updatedAt
|
||||
|
||||
@@index([section, is_published])
|
||||
}
|
||||
|
||||
model SiteNavigationItem {
|
||||
id String @id @default(cuid())
|
||||
label String
|
||||
href String
|
||||
position String @default("header")
|
||||
sort_order Int @default(100)
|
||||
is_enabled Boolean @default(true)
|
||||
metadata Json @default("{}")
|
||||
created_at DateTime @default(now())
|
||||
updated_at DateTime @updatedAt
|
||||
|
||||
@@index([position, sort_order])
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user