관리자 승인과 사용자 OAuth 증명을 분리하고 기존 Kakao stable ID를 영구 폐기한다. 로그인 ID와 닉네임 변경은 현재 장수에 revision 기반으로 투영하며 과거 기록은 당시 이름으로 보존한다.
492 lines
20 KiB
Plaintext
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")
|
|
}
|