feat: 정식 사용자의 오픈 요청과 동의 기반 예약을 영속화한다

This commit is contained in:
2026-10-23 00:18:45 +09:00
parent 856187b315
commit 236f1bc4ed
24 changed files with 2333 additions and 142 deletions
@@ -0,0 +1,28 @@
CREATE TABLE "gateway_open_request" (
"id" TEXT PRIMARY KEY,
"profile_name" TEXT NOT NULL REFERENCES "gateway_profile"("profile_name") ON DELETE CASCADE,
"requested_by" TEXT NOT NULL REFERENCES "app_user"("id") ON DELETE CASCADE,
"status" TEXT NOT NULL DEFAULT 'PENDING',
"install" JSONB NOT NULL,
"announcement" JSONB NOT NULL,
"season_key" TEXT NOT NULL,
"expires_at" TIMESTAMP(3) NOT NULL,
"preopen_at" TIMESTAMP(3) NOT NULL,
"open_at" TIMESTAMP(3) NOT NULL,
"operation_id" TEXT UNIQUE REFERENCES "gateway_operation"("id") ON DELETE RESTRICT,
"decided_by" TEXT,
"decided_at" TIMESTAMP(3),
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "gateway_open_request_status_check" CHECK ("status" IN ('PENDING','RESERVED','EXPIRED','WITHDRAWN','REJECTED','SUPERSEDED')),
CONSTRAINT "gateway_open_request_dates_check" CHECK ("created_at" < "expires_at" AND "expires_at" <= "preopen_at" AND "preopen_at" < "open_at"),
CONSTRAINT "gateway_open_request_decision_check" CHECK (("status" = 'RESERVED') = ("operation_id" IS NOT NULL))
);
CREATE INDEX "gateway_open_request_profile_name_status_expires_at_idx" ON "gateway_open_request"("profile_name", "status", "expires_at");
CREATE UNIQUE INDEX "gateway_open_request_one_pending_per_user" ON "gateway_open_request"("profile_name", "requested_by") WHERE "status" = 'PENDING';
CREATE TABLE "gateway_open_endorsement" (
"request_id" TEXT NOT NULL REFERENCES "gateway_open_request"("id") ON DELETE CASCADE,
"user_id" TEXT NOT NULL REFERENCES "app_user"("id") ON DELETE CASCADE,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY ("request_id", "user_id")
);
+41
View File
@@ -115,6 +115,8 @@ model AppUser {
updatedAt DateTime @updatedAt @map("updated_at")
lastLoginAt DateTime? @map("last_login_at")
legacyData Json @default(dbgenerated("'{}'::jsonb")) @map("legacy_data")
openRequests GatewayOpenRequest[]
openEndorsements GatewayOpenEndorsement[]
icons UserIcon[]
specialAccessGrants SpecialAccountAccessGrant[]
webPushSubscriptions WebPushSubscription[]
@@ -254,6 +256,7 @@ model GatewayProfile {
meta Json @default(dbgenerated("'{}'::jsonb"))
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
openRequests GatewayOpenRequest[]
operations GatewayOperation[]
runtimeActions GatewayRuntimeAction[]
@@ -407,6 +410,7 @@ model GatewayOperation {
bulkRelease GatewayBulkRelease? @relation(fields: [bulkReleaseId], references: [id], onDelete: SetNull)
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
openRequest GatewayOpenRequest?
logs GatewayOperationLog[]
@@index([status, scheduledAt, createdAt])
@@ -513,3 +517,40 @@ model SystemSetting {
@@map("system")
}
// Immutable proposal options; approval and operation creation commit atomically.
model GatewayOpenRequest {
id String @id @default(uuid())
profileName String @map("profile_name")
profile GatewayProfile @relation(fields: [profileName], references: [profileName], onDelete: Cascade)
requestedBy String @map("requested_by")
requester AppUser @relation(fields: [requestedBy], references: [id], onDelete: Cascade)
status String @default("PENDING")
install Json
announcement Json
seasonKey String @map("season_key")
expiresAt DateTime @map("expires_at")
preopenAt DateTime @map("preopen_at")
openAt DateTime @map("open_at")
operationId String? @unique @map("operation_id")
operation GatewayOperation? @relation(fields: [operationId], references: [id], onDelete: Restrict)
decidedBy String? @map("decided_by")
decidedAt DateTime? @map("decided_at")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
endorsements GatewayOpenEndorsement[]
@@index([profileName, status, expiresAt])
@@map("gateway_open_request")
}
model GatewayOpenEndorsement {
requestId String @map("request_id")
request GatewayOpenRequest @relation(fields: [requestId], references: [id], onDelete: Cascade)
userId String @map("user_id")
user AppUser @relation(fields: [userId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now()) @map("created_at")
@@id([requestId, userId])
@@map("gateway_open_endorsement")
}