feat: migrate legacy long-lived database records

This commit is contained in:
2026-07-27 01:12:00 +00:00
parent d8220a18b3
commit 89013272e1
39 changed files with 2495 additions and 124 deletions
+1
View File
@@ -16,6 +16,7 @@
"prisma:generate:game": "prisma generate --schema prisma/game.prisma",
"prisma:generate:gateway": "prisma generate --schema prisma/gateway.prisma",
"prisma:migrate:deploy:game": "prisma migrate deploy --schema prisma/game.prisma",
"prisma:migrate:deploy:gateway": "PRISMA_SCHEMA=prisma/gateway.prisma prisma migrate deploy --schema prisma/gateway.prisma --config prisma.gateway.config.ts",
"prisma:migrate:status:game": "prisma migrate status --schema prisma/game.prisma",
"prisma:db:push:game": "prisma db push --schema prisma/game.prisma",
"prisma:db:push:gateway": "prisma db push --schema prisma/gateway.prisma"
+18
View File
@@ -0,0 +1,18 @@
import 'dotenv/config';
import { defineConfig } from 'prisma/config';
const databaseUrl = process.env.GATEWAY_DATABASE_URL ?? process.env.DATABASE_URL;
if (!databaseUrl) {
throw new Error('GATEWAY_DATABASE_URL or DATABASE_URL is required for gateway migrations.');
}
export default defineConfig({
schema: 'prisma/gateway.prisma',
migrations: {
path: 'prisma/gateway-migrations',
},
datasource: {
url: databaseUrl,
},
});
+34 -10
View File
@@ -277,10 +277,11 @@ model OldNation {
id Int @id @default(autoincrement())
serverId String @map("server_id")
nation Int @default(0)
sourceId Int @default(0) @map("source_id")
data Json @default(dbgenerated("'{}'::jsonb"))
date DateTime @default(now())
@@unique([serverId, nation])
@@unique([serverId, nation, sourceId])
@@index([serverId, nation], name: "by_server")
@@map("ng_old_nations")
}
@@ -303,6 +304,7 @@ model OldGeneral {
model Emperor {
id Int @id @default(autoincrement()) @map("no")
legacyId Int? @unique @map("legacy_id")
serverId String? @map("server_id")
phase String? @default("")
nationCount String? @map("nation_count")
@@ -438,16 +440,19 @@ model DiplomacyLetter {
}
model YearbookHistory {
id Int @id @default(autoincrement())
profileName String @map("profile_name")
year Int
month Int
map Json
nations Json
hash String @default("")
createdAt DateTime @default(now()) @map("created_at")
id Int @id @default(autoincrement())
profileName String @map("profile_name")
sourceId Int @default(0) @map("source_id")
year Int
month Int
map Json
nations Json
globalHistory Json @default(dbgenerated("'[]'::jsonb")) @map("global_history")
globalAction Json @default(dbgenerated("'[]'::jsonb")) @map("global_action")
hash String @default("")
createdAt DateTime @default(now()) @map("created_at")
@@unique([profileName, year, month])
@@unique([profileName, year, month, sourceId])
@@index([profileName, year, month])
@@map("yearbook_history")
}
@@ -553,7 +558,10 @@ model NationBet {
model InheritanceLog {
id Int @id @default(autoincrement())
legacyId Int? @unique @map("legacy_id")
userId String @map("user_id")
serverId String? @map("server_id")
logType String @default("inheritPoint") @map("log_type")
year Int
month Int
text String
@@ -565,6 +573,7 @@ model InheritanceLog {
model InheritanceResult {
id Int @id @default(autoincrement())
legacyId Int? @unique @map("legacy_id")
serverId String @map("server_id")
owner String @map("owner")
generalId Int @map("general_id")
@@ -624,6 +633,21 @@ model InheritanceUserState {
@@map("inheritance_user_state")
}
model LegacyGameStorage {
id Int @id @default(autoincrement())
sourceId Int @map("source_id")
namespace String
key String
value Json
scope String
migratedAt DateTime @default(now()) @map("migrated_at")
@@unique([sourceId])
@@unique([namespace, key])
@@index([scope, namespace])
@@map("legacy_game_storage")
}
model BoardPost {
id Int @id @default(autoincrement())
nationId Int @map("nation_id")
@@ -0,0 +1,125 @@
DO $$
BEGIN
CREATE TYPE "OAuthType" AS ENUM ('NONE', 'KAKAO');
EXCEPTION
WHEN duplicate_object THEN NULL;
END
$$;
DO $$
BEGIN
CREATE TYPE "GatewayProfileStatus" AS ENUM (
'RESERVED', 'PREOPEN', 'RUNNING', 'PAUSED', 'COMPLETED', 'STOPPED', 'DISABLED'
);
EXCEPTION
WHEN duplicate_object THEN NULL;
END
$$;
DO $$
BEGIN
CREATE TYPE "GatewayBuildStatus" AS ENUM ('IDLE', 'QUEUED', 'RUNNING', 'FAILED', 'SUCCEEDED');
EXCEPTION
WHEN duplicate_object THEN NULL;
END
$$;
DO $$
BEGIN
CREATE TYPE "GatewayOperationType" AS ENUM ('RESET', 'START', 'STOP');
EXCEPTION
WHEN duplicate_object THEN NULL;
END
$$;
DO $$
BEGIN
CREATE TYPE "GatewayOperationStatus" AS ENUM ('QUEUED', 'RUNNING', 'SUCCEEDED', 'FAILED', 'CANCELLED');
EXCEPTION
WHEN duplicate_object THEN NULL;
END
$$;
DO $$
BEGIN
CREATE TYPE "GatewaySourceMode" AS ENUM ('BRANCH', 'COMMIT');
EXCEPTION
WHEN duplicate_object THEN NULL;
END
$$;
CREATE TABLE IF NOT EXISTS "app_user" (
"id" TEXT PRIMARY KEY,
"login_id" TEXT NOT NULL,
"display_name" TEXT NOT NULL,
"password_hash" TEXT NOT NULL,
"password_salt" TEXT NOT NULL,
"roles" JSONB NOT NULL DEFAULT '[]'::jsonb,
"sanctions" JSONB NOT NULL DEFAULT '{}'::jsonb,
"oauth_type" "OAuthType" NOT NULL DEFAULT 'NONE',
"oauth_id" TEXT,
"email" TEXT,
"oauth_info" JSONB NOT NULL DEFAULT '{}'::jsonb,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
"last_login_at" TIMESTAMP(3)
);
CREATE UNIQUE INDEX IF NOT EXISTS "app_user_login_id_key" ON "app_user" ("login_id");
CREATE UNIQUE INDEX IF NOT EXISTS "app_user_oauth_id_key" ON "app_user" ("oauth_id");
CREATE UNIQUE INDEX IF NOT EXISTS "app_user_email_key" ON "app_user" ("email");
CREATE TABLE IF NOT EXISTS "gateway_profile" (
"profile_name" TEXT PRIMARY KEY,
"profile" TEXT NOT NULL,
"scenario" TEXT NOT NULL,
"api_port" INTEGER NOT NULL,
"status" "GatewayProfileStatus" NOT NULL,
"build_status" "GatewayBuildStatus" NOT NULL DEFAULT 'IDLE',
"build_commit_sha" TEXT,
"build_workspace" TEXT,
"build_last_used_at" TIMESTAMP(3),
"preopen_at" TIMESTAMP(3),
"open_at" TIMESTAMP(3),
"scheduled_start_at" TIMESTAMP(3),
"build_requested_at" TIMESTAMP(3),
"build_started_at" TIMESTAMP(3),
"build_completed_at" TIMESTAMP(3),
"build_error" TEXT,
"last_error" TEXT,
"meta" JSONB NOT NULL DEFAULT '{}'::jsonb,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL
);
CREATE UNIQUE INDEX IF NOT EXISTS "gateway_profile_profile_scenario_key"
ON "gateway_profile" ("profile", "scenario");
CREATE TABLE IF NOT EXISTS "gateway_operation" (
"id" TEXT PRIMARY KEY,
"profile_name" TEXT NOT NULL REFERENCES "gateway_profile" ("profile_name") ON DELETE CASCADE,
"type" "GatewayOperationType" NOT NULL,
"status" "GatewayOperationStatus" NOT NULL DEFAULT 'QUEUED',
"source_mode" "GatewaySourceMode",
"source_ref" TEXT,
"resolved_commit_sha" TEXT,
"payload" JSONB NOT NULL DEFAULT '{}'::jsonb,
"reason" TEXT,
"requested_by" TEXT NOT NULL,
"scheduled_at" TIMESTAMP(3),
"started_at" TIMESTAMP(3),
"completed_at" TIMESTAMP(3),
"error" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL
);
CREATE INDEX IF NOT EXISTS "gateway_operation_status_scheduled_at_created_at_idx"
ON "gateway_operation" ("status", "scheduled_at", "created_at");
CREATE INDEX IF NOT EXISTS "gateway_operation_profile_name_created_at_idx"
ON "gateway_operation" ("profile_name", "created_at");
CREATE TABLE IF NOT EXISTS "system" (
"no" INTEGER PRIMARY KEY DEFAULT 1,
"notice" TEXT NOT NULL DEFAULT ''
);
@@ -0,0 +1,39 @@
ALTER TABLE "app_user"
ADD COLUMN IF NOT EXISTS "legacy_data" JSONB NOT NULL DEFAULT '{}'::jsonb;
ALTER TABLE "system"
ADD COLUMN IF NOT EXISTS "registration_enabled" BOOLEAN NOT NULL DEFAULT FALSE,
ADD COLUMN IF NOT EXISTS "login_enabled" BOOLEAN NOT NULL DEFAULT FALSE,
ADD COLUMN IF NOT EXISTS "created_at" TIMESTAMP(3),
ADD COLUMN IF NOT EXISTS "updated_at" TIMESTAMP(3);
CREATE TABLE IF NOT EXISTS "legacy_member_log" (
"id" BIGINT PRIMARY KEY,
"member_no" INTEGER NOT NULL,
"user_id" TEXT NOT NULL,
"date" TIMESTAMP(3) NOT NULL,
"action_type" TEXT NOT NULL,
"action" JSONB
);
CREATE INDEX IF NOT EXISTS "legacy_member_log_user_date"
ON "legacy_member_log" ("user_id", "date");
CREATE INDEX IF NOT EXISTS "legacy_member_log_member_date"
ON "legacy_member_log" ("member_no", "date");
CREATE TABLE IF NOT EXISTS "legacy_banned_member" (
"no" INTEGER PRIMARY KEY,
"hashed_email" TEXT NOT NULL UNIQUE,
"info" TEXT
);
CREATE TABLE IF NOT EXISTS "legacy_root_key_value" (
"id" SERIAL PRIMARY KEY,
"source_table" TEXT NOT NULL,
"namespace" TEXT NOT NULL,
"key" TEXT NOT NULL,
"value" JSONB NOT NULL,
"migrated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "legacy_root_key_value_source_namespace_key"
UNIQUE ("source_table", "namespace", "key")
);
+114 -76
View File
@@ -32,106 +32,144 @@ enum GatewayBuildStatus {
}
enum GatewayOperationType {
RESET
START
STOP
RESET
START
STOP
}
enum GatewayOperationStatus {
QUEUED
RUNNING
SUCCEEDED
FAILED
CANCELLED
QUEUED
RUNNING
SUCCEEDED
FAILED
CANCELLED
}
enum GatewaySourceMode {
BRANCH
COMMIT
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")
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")
picture String @default("default.jpg")
imageServer Int @default(0) @map("image_server")
iconUpdatedAt DateTime? @map("icon_updated_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")
kakaoGraceStartedAt DateTime @default(now()) @map("kakao_grace_started_at")
deleteAfter DateTime? @map("delete_after")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
lastLoginAt DateTime? @map("last_login_at")
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")
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")
picture String @default("default.jpg")
imageServer Int @default(0) @map("image_server")
iconUpdatedAt DateTime? @map("icon_updated_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")
kakaoGraceStartedAt DateTime @default(now()) @map("kakao_grace_started_at")
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")
@@map("app_user")
}
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
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[]
profileName String @id @map("profile_name")
profile String
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[]
@@unique([profile, scenario])
@@map("gateway_profile")
}
model GatewayOperation {
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?
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
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?
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@index([status, scheduledAt, createdAt])
@@index([profileName, createdAt])
@@map("gateway_operation")
@@index([status, scheduledAt, createdAt])
@@index([profileName, createdAt])
@@map("gateway_operation")
}
model SystemSetting {
id Int @id @default(1) @map("no")
notice String @default("") @map("notice")
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")
}
@@ -0,0 +1,48 @@
ALTER TABLE "ng_old_nations"
ADD COLUMN IF NOT EXISTS "source_id" INTEGER NOT NULL DEFAULT 0;
DROP INDEX IF EXISTS "ng_old_nations_server_id_nation";
CREATE UNIQUE INDEX IF NOT EXISTS "ng_old_nations_server_nation_source"
ON "ng_old_nations" ("server_id", "nation", "source_id");
ALTER TABLE "yearbook_history"
ADD COLUMN IF NOT EXISTS "source_id" INTEGER NOT NULL DEFAULT 0,
ADD COLUMN IF NOT EXISTS "global_history" JSONB NOT NULL DEFAULT '[]'::jsonb,
ADD COLUMN IF NOT EXISTS "global_action" JSONB NOT NULL DEFAULT '[]'::jsonb;
DROP INDEX IF EXISTS "yearbook_history_profile_year_month_key";
CREATE UNIQUE INDEX IF NOT EXISTS "yearbook_history_profile_year_month_source"
ON "yearbook_history" ("profile_name", "year", "month", "source_id");
ALTER TABLE "inheritance_log"
ADD COLUMN IF NOT EXISTS "legacy_id" INTEGER,
ADD COLUMN IF NOT EXISTS "server_id" TEXT,
ADD COLUMN IF NOT EXISTS "log_type" TEXT NOT NULL DEFAULT 'inheritPoint';
CREATE UNIQUE INDEX IF NOT EXISTS "inheritance_log_legacy_id_key"
ON "inheritance_log" ("legacy_id");
ALTER TABLE "emperior"
ADD COLUMN IF NOT EXISTS "legacy_id" INTEGER;
CREATE UNIQUE INDEX IF NOT EXISTS "emperior_legacy_id_key"
ON "emperior" ("legacy_id");
ALTER TABLE "inheritance_result"
ADD COLUMN IF NOT EXISTS "legacy_id" INTEGER;
CREATE UNIQUE INDEX IF NOT EXISTS "inheritance_result_legacy_id_key"
ON "inheritance_result" ("legacy_id");
CREATE TABLE IF NOT EXISTS "legacy_game_storage" (
"id" SERIAL PRIMARY KEY,
"source_id" INTEGER NOT NULL,
"namespace" TEXT NOT NULL,
"key" TEXT NOT NULL,
"value" JSONB NOT NULL,
"scope" TEXT NOT NULL,
"migrated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "legacy_game_storage_source_id_key" UNIQUE ("source_id"),
CONSTRAINT "legacy_game_storage_namespace_key" UNIQUE ("namespace", "key")
);
CREATE INDEX IF NOT EXISTS "legacy_game_storage_scope_namespace"
ON "legacy_game_storage" ("scope", "namespace");