Patchpass V1
This commit is contained in:
@@ -0,0 +1,182 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "UserRole" AS ENUM ('ADMIN', 'USER');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "RequestState" AS ENUM ('PENDING', 'CHANGES_REQUESTED', 'APPROVED', 'REJECTED', 'EXPIRED', 'CONSUMED', 'CANCELLED');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "NotificationType" AS ENUM ('REQUEST_CREATED', 'REQUEST_UPDATED', 'REQUEST_APPROVED', 'REQUEST_REJECTED', 'CHANGES_REQUESTED', 'REQUEST_CONSUMED', 'REQUEST_CANCELLED', 'REQUEST_EXPIRED', 'AGENT_DISABLED', 'ADMIN_ACTION', 'SETTINGS_CHANGED');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "GlobalSettingType" AS ENUM ('registration_enabled', 'requests_enabled');
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "GlobalSetting" (
|
||||
"type" "GlobalSettingType" NOT NULL,
|
||||
"value" TEXT NOT NULL,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "GlobalSetting_pkey" PRIMARY KEY ("type")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "User" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"username" TEXT NOT NULL,
|
||||
"displayName" TEXT NOT NULL,
|
||||
"password" TEXT NOT NULL,
|
||||
"role" "UserRole" NOT NULL DEFAULT 'USER',
|
||||
"totpSecret" TEXT,
|
||||
"totpEnabled" BOOLEAN NOT NULL DEFAULT false,
|
||||
"disabled" BOOLEAN NOT NULL DEFAULT false,
|
||||
"autoDeleteEnabled" BOOLEAN NOT NULL DEFAULT false,
|
||||
"autoDeleteDays" INTEGER NOT NULL DEFAULT 30,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Session" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"hash" TEXT NOT NULL,
|
||||
"userId" INTEGER NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "Session_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Agent" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"description" TEXT,
|
||||
"website" TEXT,
|
||||
"iconUrl" TEXT,
|
||||
"apiKey" TEXT NOT NULL,
|
||||
"disabled" BOOLEAN NOT NULL DEFAULT false,
|
||||
"maxPendingRequests" INTEGER NOT NULL DEFAULT 5,
|
||||
"ownerId" INTEGER NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Agent_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "ChangeRequest" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"publicId" TEXT NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
"description" TEXT,
|
||||
"changes" JSONB NOT NULL,
|
||||
"rawChanges" JSONB NOT NULL,
|
||||
"metadata" JSONB,
|
||||
"contentHash" TEXT NOT NULL,
|
||||
"state" "RequestState" NOT NULL DEFAULT 'PENDING',
|
||||
"expiresAt" TIMESTAMP(3) NOT NULL,
|
||||
"comment" TEXT,
|
||||
"decidedAt" TIMESTAMP(3),
|
||||
"approverId" INTEGER,
|
||||
"signature" TEXT,
|
||||
"receiptIssuedAt" TIMESTAMP(3),
|
||||
"consumedAt" TIMESTAMP(3),
|
||||
"cancelledAt" TIMESTAMP(3),
|
||||
"updateCount" INTEGER NOT NULL DEFAULT 0,
|
||||
"resubmitted" BOOLEAN NOT NULL DEFAULT false,
|
||||
"lastAgentUpdateAt" TIMESTAMP(3),
|
||||
"agentId" INTEGER NOT NULL,
|
||||
"userId" INTEGER NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "ChangeRequest_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Notification" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"type" "NotificationType" NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
"message" TEXT NOT NULL,
|
||||
"requestPublicId" TEXT,
|
||||
"read" BOOLEAN NOT NULL DEFAULT false,
|
||||
"userId" INTEGER NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "Notification_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "AuditLog" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"action" TEXT NOT NULL,
|
||||
"detail" TEXT,
|
||||
"targetType" TEXT,
|
||||
"targetId" TEXT,
|
||||
"actorId" INTEGER,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "AuditLog_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "User_username_key" ON "User"("username");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Session_hash_key" ON "Session"("hash");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Session_userId_idx" ON "Session"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Agent_apiKey_key" ON "Agent"("apiKey");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Agent_ownerId_idx" ON "Agent"("ownerId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "ChangeRequest_publicId_key" ON "ChangeRequest"("publicId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "ChangeRequest_userId_state_idx" ON "ChangeRequest"("userId", "state");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "ChangeRequest_agentId_state_idx" ON "ChangeRequest"("agentId", "state");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "ChangeRequest_createdAt_idx" ON "ChangeRequest"("createdAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Notification_userId_read_idx" ON "Notification"("userId", "read");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Notification_userId_createdAt_idx" ON "Notification"("userId", "createdAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "AuditLog_createdAt_idx" ON "AuditLog"("createdAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "AuditLog_actorId_idx" ON "AuditLog"("actorId");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Session" ADD CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Agent" ADD CONSTRAINT "Agent_ownerId_fkey" FOREIGN KEY ("ownerId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "ChangeRequest" ADD CONSTRAINT "ChangeRequest_approverId_fkey" FOREIGN KEY ("approverId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "ChangeRequest" ADD CONSTRAINT "ChangeRequest_agentId_fkey" FOREIGN KEY ("agentId") REFERENCES "Agent"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "ChangeRequest" ADD CONSTRAINT "ChangeRequest_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Notification" ADD CONSTRAINT "Notification_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "AuditLog" ADD CONSTRAINT "AuditLog_actorId_fkey" FOREIGN KEY ("actorId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
@@ -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"
|
||||
@@ -0,0 +1,214 @@
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Enums
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
enum UserRole {
|
||||
ADMIN
|
||||
USER
|
||||
}
|
||||
|
||||
enum RequestState {
|
||||
PENDING
|
||||
CHANGES_REQUESTED
|
||||
APPROVED
|
||||
REJECTED
|
||||
EXPIRED
|
||||
CONSUMED
|
||||
CANCELLED
|
||||
}
|
||||
|
||||
enum NotificationType {
|
||||
REQUEST_CREATED
|
||||
REQUEST_UPDATED
|
||||
REQUEST_APPROVED
|
||||
REQUEST_REJECTED
|
||||
CHANGES_REQUESTED
|
||||
REQUEST_CONSUMED
|
||||
REQUEST_CANCELLED
|
||||
REQUEST_EXPIRED
|
||||
AGENT_DISABLED
|
||||
ADMIN_ACTION
|
||||
SETTINGS_CHANGED
|
||||
}
|
||||
|
||||
enum GlobalSettingType {
|
||||
registration_enabled
|
||||
requests_enabled
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Models
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// A global (admin-only) platform setting, keyed by type.
|
||||
model GlobalSetting {
|
||||
type GlobalSettingType @id
|
||||
value String
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
/// A human account. The first registered user becomes ADMIN.
|
||||
model User {
|
||||
id Int @id @default(autoincrement())
|
||||
/// Normalized (lowercased) username — enforces case-insensitive uniqueness.
|
||||
username String @unique
|
||||
/// Original-cased display handle shown in the UI.
|
||||
displayName String
|
||||
password String
|
||||
role UserRole @default(USER)
|
||||
|
||||
// Optional TOTP two-factor auth
|
||||
totpSecret String?
|
||||
totpEnabled Boolean @default(false)
|
||||
|
||||
// Admin can disable a human account entirely
|
||||
disabled Boolean @default(false)
|
||||
|
||||
// Auto-delete of old change requests (opt-in, min 7 days, default 30)
|
||||
autoDeleteEnabled Boolean @default(false)
|
||||
autoDeleteDays Int @default(30)
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
sessions Session[]
|
||||
agents Agent[]
|
||||
changeRequests ChangeRequest[] @relation("Owner")
|
||||
decisions ChangeRequest[] @relation("Approver")
|
||||
notifications Notification[]
|
||||
auditLogs AuditLog[] @relation("AuditActor")
|
||||
}
|
||||
|
||||
/// A browser session for a human account (cookie-based auth).
|
||||
model Session {
|
||||
id Int @id @default(autoincrement())
|
||||
hash String @unique
|
||||
userId Int
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([userId])
|
||||
}
|
||||
|
||||
/// An AGENT account. Fully managed by a human owner. Holds exactly one API key.
|
||||
model Agent {
|
||||
id Int @id @default(autoincrement())
|
||||
name String
|
||||
description String?
|
||||
website String?
|
||||
iconUrl String?
|
||||
/// The single API key the agent authenticates with.
|
||||
apiKey String @unique
|
||||
disabled Boolean @default(false)
|
||||
/// Max simultaneous pending requests (1..10, human-configurable, default 5).
|
||||
maxPendingRequests Int @default(5)
|
||||
|
||||
ownerId Int
|
||||
owner User @relation(fields: [ownerId], references: [id], onDelete: Cascade)
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
changeRequests ChangeRequest[]
|
||||
|
||||
@@index([ownerId])
|
||||
}
|
||||
|
||||
/// A structured change request submitted by an agent for human approval.
|
||||
model ChangeRequest {
|
||||
id Int @id @default(autoincrement())
|
||||
/// Public UUID (v4) exposed to agents as request_id.
|
||||
publicId String @unique @default(uuid())
|
||||
|
||||
title String
|
||||
description String?
|
||||
/// Normalized changes array (diffs normalized, canonical ordering).
|
||||
changes Json
|
||||
/// Original changes exactly as submitted (audit trail).
|
||||
rawChanges Json
|
||||
metadata Json?
|
||||
/// SHA-256 hash over the canonical (title + description + normalized changes).
|
||||
contentHash String
|
||||
|
||||
state RequestState @default(PENDING)
|
||||
|
||||
expiresAt DateTime
|
||||
|
||||
// Decision fields
|
||||
comment String?
|
||||
decidedAt DateTime?
|
||||
approverId Int?
|
||||
approver User? @relation("Approver", fields: [approverId], references: [id], onDelete: SetNull)
|
||||
/// HMAC-SHA256 signature of the approval receipt (platform-signed decision).
|
||||
signature String?
|
||||
receiptIssuedAt DateTime?
|
||||
|
||||
consumedAt DateTime?
|
||||
cancelledAt DateTime?
|
||||
|
||||
// Update tracking for the CHANGES_REQUESTED → resubmit loop
|
||||
updateCount Int @default(0)
|
||||
/// True after an agent resubmits following a CHANGES_REQUESTED decision.
|
||||
resubmitted Boolean @default(false)
|
||||
lastAgentUpdateAt DateTime?
|
||||
|
||||
agentId Int
|
||||
agent Agent @relation(fields: [agentId], references: [id], onDelete: Cascade)
|
||||
|
||||
// The human who owns/reviews this request (the agent's owner at creation time).
|
||||
userId Int
|
||||
user User @relation("Owner", fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([userId, state])
|
||||
@@index([agentId, state])
|
||||
@@index([createdAt])
|
||||
}
|
||||
|
||||
/// An in-app notification for a human account.
|
||||
model Notification {
|
||||
id Int @id @default(autoincrement())
|
||||
type NotificationType
|
||||
title String
|
||||
message String
|
||||
/// Optional link to a change request (publicId).
|
||||
requestPublicId String?
|
||||
read Boolean @default(false)
|
||||
|
||||
userId Int
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([userId, read])
|
||||
@@index([userId, createdAt])
|
||||
}
|
||||
|
||||
/// An audit log entry. Records privileged/admin actions and payload views.
|
||||
model AuditLog {
|
||||
id Int @id @default(autoincrement())
|
||||
action String
|
||||
/// Human-readable detail / JSON string of context.
|
||||
detail String?
|
||||
/// Optional target references.
|
||||
targetType String?
|
||||
targetId String?
|
||||
|
||||
actorId Int?
|
||||
actor User? @relation("AuditActor", fields: [actorId], references: [id], onDelete: SetNull)
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([createdAt])
|
||||
@@index([actorId])
|
||||
}
|
||||
Reference in New Issue
Block a user