Files
core2026/packages/infra/prisma/gateway.prisma
T
Hide_D 10cd1ed6d4 feat: 관리자 계정 식별자와 카카오 영구 교체 지원
관리자 승인과 사용자 OAuth 증명을 분리하고 기존 Kakao stable ID를 영구 폐기한다. 로그인 ID와 닉네임 변경은 현재 장수에 revision 기반으로 투영하며 과거 기록은 당시 이름으로 보존한다.
2026-08-24 23:25:49 +00:00

492 lines
20 KiB
Plaintext

generator client {
provider = "prisma-client-js"
output = "./generated/gateway"
engineType = "binary"
}
datasource db {
provider = "postgresql"
}
enum OAuthType {
NONE
KAKAO
}
enum GatewayProfileStatus {
RESERVED
PREOPEN
RUNNING
PAUSED
COMPLETED
CANCELLED
STOPPED
DISABLED
}
enum GatewayBuildStatus {
IDLE
QUEUED
RUNNING
FAILED
SUCCEEDED
}
enum GatewayOperationType {
RESET
DEPLOY
CANCEL_GAME
START
STOP
}
enum GatewayReleaseOperationType {
DEPLOY
ROLLBACK
}
enum GatewayOperationStatus {
QUEUED
RUNNING
SUCCEEDED
FAILED
CANCELLED
}
enum GatewayRuntimeActionStatus {
REQUESTED
PARTIAL
APPLIED
FAILED
IGNORED
}
enum AdminAuditOutcome {
STARTED
SUCCEEDED
FAILED
}
enum SpecialAccountAccessKind {
TESTER
RECOVERY
OTHER
}
enum GatewaySourceMode {
BRANCH
COMMIT
}
model AppUser {
id String @id @default(uuid())
loginId String @unique @map("login_id")
displayName String @unique @map("display_name")
passwordHash String @map("password_hash")
passwordSalt String @map("password_salt")
passwordResetRequired Boolean @default(false) @map("password_reset_required")
roles Json @default(dbgenerated("'[]'::jsonb"))
sanctions Json @default(dbgenerated("'{}'::jsonb"))
oauthType OAuthType @default(NONE) @map("oauth_type")
oauthId String? @unique @map("oauth_id")
email String? @unique
oauthInfo Json @default(dbgenerated("'{}'::jsonb")) @map("oauth_info")
identityRevision DateTime @default(now()) @map("identity_revision")
authRevision Int @default(0) @map("auth_revision")
sessionRevokedBefore DateTime? @map("session_revoked_before")
kakaoReplacementApprovedUntil DateTime? @map("kakao_replacement_approved_until")
kakaoReplacementApprovedByUserId String? @map("kakao_replacement_approved_by_user_id")
kakaoReplacementReason String? @map("kakao_replacement_reason")
picture String @default("default.jpg")
imageServer Int @default(0) @map("image_server")
iconUpdatedAt DateTime? @map("icon_updated_at")
iconRevision DateTime? @map("icon_revision")
profileIconResetAt DateTime? @map("profile_icon_reset_at")
iconRetiredAt DateTime? @map("icon_retired_at")
thirdPartyUse Boolean @default(true) @map("third_party_use")
termsAcceptedAt DateTime? @map("terms_accepted_at")
privacyAcceptedAt DateTime? @map("privacy_accepted_at")
kakaoVerifiedAt DateTime? @map("kakao_verified_at")
kakaoTalkVerifiedUntil DateTime? @map("kakao_talk_verified_until")
kakaoGraceStartedAt DateTime @default(now()) @map("kakao_grace_started_at")
kakaoGraceUntil DateTime? @map("kakao_grace_until")
deleteAfter DateTime? @map("delete_after")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
lastLoginAt DateTime? @map("last_login_at")
legacyData Json @default(dbgenerated("'{}'::jsonb")) @map("legacy_data")
icons UserIcon[]
specialAccessGrants SpecialAccountAccessGrant[]
webPushSubscriptions WebPushSubscription[]
webPushPreferences WebPushPreference[]
webPushNotifications WebPushNotification[]
@@map("app_user")
}
model RetiredKakaoIdentity {
id String @id @default(uuid())
oauthId String @unique @map("oauth_id")
formerUserId String @map("former_user_id")
approvedByUserId String @map("approved_by_user_id")
reason String
retiredAt DateTime @default(now()) @map("retired_at")
@@index([formerUserId, retiredAt])
@@map("retired_kakao_identity")
}
model SpecialAccountAccessGrant {
id String @id @default(uuid())
userId String @map("user_id")
user AppUser @relation(fields: [userId], references: [id], onDelete: Cascade)
kind SpecialAccountAccessKind
profiles String[] @default([])
allowsGeneralCreation Boolean @default(true) @map("allows_general_creation")
expiresAt DateTime? @map("expires_at")
reason String
grantedByUserId String @map("granted_by_user_id")
revokedAt DateTime? @map("revoked_at")
revokedByUserId String? @map("revoked_by_user_id")
revokedReason String? @map("revoked_reason")
createdAt DateTime @default(now()) @map("created_at")
@@index([userId, revokedAt, expiresAt])
@@index([kind, createdAt])
@@map("special_account_access_grant")
}
model AdminAuditEvent {
id String @id @default(uuid())
correlationId String @map("correlation_id")
actorUserId String @map("actor_user_id")
actorUsername String @map("actor_username")
credentialKind String @default("SESSION") @map("credential_kind")
capability String?
scope String?
action String
targetType String? @map("target_type")
targetId String? @map("target_id")
profileName String? @map("profile_name")
reason String?
outcome AdminAuditOutcome
summary Json @default(dbgenerated("'{}'::jsonb"))
errorCode String? @map("error_code")
errorMessage String? @map("error_message")
createdAt DateTime @default(now()) @map("created_at")
@@index([correlationId, createdAt])
@@index([actorUserId, createdAt])
@@index([targetType, targetId, createdAt])
@@index([profileName, createdAt])
@@index([action, createdAt])
@@map("admin_audit_event")
}
model UserIcon {
id String @id @default(uuid())
userId String @map("user_id")
user AppUser @relation(fields: [userId], references: [id], onDelete: Cascade)
picture String @unique
imageServer Int @default(1) @map("image_server")
createdAt DateTime @default(now()) @map("created_at")
retiredAt DateTime? @map("retired_at")
@@index([userId, retiredAt, createdAt])
@@map("user_icon")
}
model LegacyMemberLog {
id BigInt @id
memberNo Int @map("member_no")
userId String @map("user_id")
date DateTime
actionType String @map("action_type")
action Json?
@@index([userId, date])
@@index([memberNo, date])
@@map("legacy_member_log")
}
model LegacyBannedMember {
id Int @id @map("no")
hashedEmail String @unique @map("hashed_email")
info String?
@@map("legacy_banned_member")
}
model LegacyRootKeyValue {
id Int @id @default(autoincrement())
sourceTable String @map("source_table")
namespace String
key String
value Json
migratedAt DateTime @default(now()) @map("migrated_at")
@@unique([sourceTable, namespace, key])
@@map("legacy_root_key_value")
}
model GatewayProfile {
profileName String @id @map("profile_name")
profile String
instanceKey String @map("instance_key")
currentScenario String? @map("current_scenario")
/// Legacy compatibility mirror. The database trigger keeps this synchronized
/// with currentScenario while the previous Gateway release remains rollbackable.
scenario String
apiPort Int @map("api_port")
status GatewayProfileStatus
buildStatus GatewayBuildStatus @default(IDLE) @map("build_status")
buildCommitSha String? @map("build_commit_sha")
buildWorkspace String? @map("build_workspace")
buildLastUsedAt DateTime? @map("build_last_used_at")
preopenAt DateTime? @map("preopen_at")
openAt DateTime? @map("open_at")
scheduledStartAt DateTime? @map("scheduled_start_at")
buildRequestedAt DateTime? @map("build_requested_at")
buildStartedAt DateTime? @map("build_started_at")
buildCompletedAt DateTime? @map("build_completed_at")
buildError String? @map("build_error")
lastError String? @map("last_error")
meta Json @default(dbgenerated("'{}'::jsonb"))
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
operations GatewayOperation[]
runtimeActions GatewayRuntimeAction[]
@@unique([profile, instanceKey])
@@map("gateway_profile")
}
model WebPushSubscription {
id String @id @default(uuid())
userId String @map("user_id")
user AppUser @relation(fields: [userId], references: [id], onDelete: Cascade)
endpoint String @unique @db.Text
p256dh String @db.Text
auth String @db.Text
expirationTime DateTime? @map("expiration_time")
userAgent String? @map("user_agent") @db.Text
disabledAt DateTime? @map("disabled_at")
lastSeenAt DateTime @default(now()) @map("last_seen_at")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
deliveries WebPushDelivery[]
@@index([userId, disabledAt, updatedAt])
@@map("web_push_subscription")
}
model WebPushPreference {
id String @id @default(uuid())
userId String @map("user_id")
user AppUser @relation(fields: [userId], references: [id], onDelete: Cascade)
profileName String @map("profile_name")
eventType String @map("event_type")
enabled Boolean @default(false)
targetYear Int? @map("target_year")
targetMonth Int? @map("target_month")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@unique([userId, profileName, eventType])
@@index([profileName, eventType, enabled])
@@map("web_push_preference")
}
model WebPushEventReceipt {
eventId String @id @map("event_id")
profileName String @map("profile_name")
eventType String @map("event_type")
createdAt DateTime @default(now()) @map("created_at")
@@index([createdAt])
@@map("web_push_event_receipt")
}
model WebPushNotification {
id String @id @default(uuid())
dedupeKey String @unique @map("dedupe_key")
userId String @map("user_id")
user AppUser @relation(fields: [userId], references: [id], onDelete: Cascade)
profileName String @map("profile_name")
eventType String @map("event_type")
title String
body String
url String
tag String
createdAt DateTime @default(now()) @map("created_at")
deliveries WebPushDelivery[]
@@index([userId, createdAt])
@@map("web_push_notification")
}
model WebPushDelivery {
id BigInt @id @default(autoincrement())
notificationId String @map("notification_id")
notification WebPushNotification @relation(fields: [notificationId], references: [id], onDelete: Cascade)
subscriptionId String @map("subscription_id")
subscription WebPushSubscription @relation(fields: [subscriptionId], references: [id], onDelete: Cascade)
status String @default("PENDING")
attempts Int @default(0)
availableAt DateTime @default(now()) @map("available_at")
lockedAt DateTime? @map("locked_at")
lockOwner String? @map("lock_owner")
deliveredAt DateTime? @map("delivered_at")
lastError String? @map("last_error")
createdAt DateTime @default(now()) @map("created_at")
@@unique([notificationId, subscriptionId])
@@index([status, availableAt, id])
@@map("web_push_delivery")
}
model WebPushProfileCursor {
profileName String @id @map("profile_name")
status String
preopenAt DateTime? @map("preopen_at")
openAt DateTime? @map("open_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@map("web_push_profile_cursor")
}
model GatewayRuntimeAction {
id String @id @default(uuid())
profileName String @map("profile_name")
profile GatewayProfile @relation(fields: [profileName], references: [profileName], onDelete: Cascade)
action String
payload Json @default(dbgenerated("'{}'::jsonb"))
durationMinutes Int? @map("duration_minutes")
scheduledAt DateTime? @map("scheduled_at")
reason String?
requestedBy String @map("requested_by")
status GatewayRuntimeActionStatus @default(REQUESTED)
detail String?
handler String?
handledAt DateTime? @map("handled_at")
attempts Int @default(0)
nextAttemptAt DateTime? @map("next_attempt_at")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@index([profileName, status, createdAt])
@@index([profileName, createdAt])
@@map("gateway_runtime_action")
}
model GatewayOperation {
/// Partial unique indexes in the gateway migration chain permit one future
/// scheduled RESET beside one immediate queued operation. RUNNING work and
/// each queue lane remain unique per profile.
id String @id @default(uuid())
profileName String @map("profile_name")
profile GatewayProfile @relation(fields: [profileName], references: [profileName], onDelete: Cascade)
type GatewayOperationType
status GatewayOperationStatus @default(QUEUED)
sourceMode GatewaySourceMode? @map("source_mode")
sourceRef String? @map("source_ref")
resolvedCommitSha String? @map("resolved_commit_sha")
payload Json @default(dbgenerated("'{}'::jsonb"))
reason String?
requestedBy String @map("requested_by")
scheduledAt DateTime? @map("scheduled_at")
startedAt DateTime? @map("started_at")
completedAt DateTime? @map("completed_at")
error String?
leaseOwner String? @map("lease_owner")
leaseUntil DateTime? @map("lease_until")
heartbeatAt DateTime? @map("heartbeat_at")
attempts Int @default(0)
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
logs GatewayOperationLog[]
@@index([status, scheduledAt, createdAt])
@@index([status, leaseUntil, createdAt])
@@index([profileName, createdAt])
@@map("gateway_operation")
}
model GatewayOperationLog {
id BigInt @id @default(autoincrement())
operationId String @map("operation_id")
level String
phase String
message String @db.Text
createdAt DateTime @default(now()) @map("created_at")
operation GatewayOperation @relation(fields: [operationId], references: [id], onDelete: Cascade)
@@index([operationId, id])
@@map("gateway_operation_log")
}
model GatewayReleaseState {
id String @id @default("gateway")
activeCommitSha String? @map("active_commit_sha")
activeWorkspace String? @map("active_workspace")
previousCommitSha String? @map("previous_commit_sha")
previousWorkspace String? @map("previous_workspace")
lastSuccessfulAt DateTime? @map("last_successful_at") @db.Timestamptz(3)
lastError String? @map("last_error")
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3)
@@map("gateway_release_state")
}
model GatewayReleaseOperation {
/// A partial unique index in the gateway migration chain permits only one
/// QUEUED or RUNNING control-plane release at a time.
id String @id @default(uuid())
type GatewayReleaseOperationType
status GatewayOperationStatus @default(QUEUED)
sourceMode GatewaySourceMode? @map("source_mode")
sourceRef String? @map("source_ref")
resolvedCommitSha String? @map("resolved_commit_sha")
payload Json @default(dbgenerated("'{}'::jsonb"))
reason String?
requestedBy String @map("requested_by")
startedAt DateTime? @map("started_at") @db.Timestamptz(3)
completedAt DateTime? @map("completed_at") @db.Timestamptz(3)
error String?
leaseOwner String? @map("lease_owner")
leaseUntil DateTime? @map("lease_until") @db.Timestamptz(6)
heartbeatAt DateTime? @map("heartbeat_at") @db.Timestamptz(6)
attempts Int @default(0)
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3)
logs GatewayReleaseLog[]
@@index([status, leaseUntil, createdAt])
@@index([createdAt])
@@map("gateway_release_operation")
}
model GatewayReleaseLog {
id BigInt @id @default(autoincrement())
operationId String @map("operation_id")
level String
phase String
message String @db.Text
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
operation GatewayReleaseOperation @relation(fields: [operationId], references: [id], onDelete: Cascade)
@@index([operationId, id])
@@map("gateway_release_log")
}
model SystemSetting {
id Int @id @default(1) @map("no")
registrationEnabled Boolean @default(false) @map("registration_enabled")
loginEnabled Boolean @default(false) @map("login_enabled")
notice String @default("") @map("notice")
createdAt DateTime? @map("created_at")
updatedAt DateTime? @map("updated_at")
@@map("system")
}