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")
}
