feat: migrate legacy long-lived database records
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
# Legacy MariaDB long-lived data migration
|
||||
|
||||
## Scope and safety boundary
|
||||
|
||||
`tools/legacy-db-migration` is the only supported importer. It has no HTTP
|
||||
entrypoint, defaults to a read-only dry-run, and requires `--apply` for target
|
||||
writes. Database URLs are accepted only through environment variables.
|
||||
PostgreSQL advisory locks serialize an apply per profile, and every target row
|
||||
uses a stable legacy key with `ON CONFLICT`, so an interrupted run is
|
||||
repeatable.
|
||||
|
||||
The source of truth for eligibility is the checked ref schema, not every table
|
||||
that happens to exist in an old dump. An extra root `config` table found in the
|
||||
2026-07-27 dump is therefore retained only in the recovery dump.
|
||||
|
||||
### Gateway
|
||||
|
||||
| Legacy table | Target | Policy |
|
||||
| --------------- | ----------------------------- | ------------------------------------------------------------------------------- |
|
||||
| `member` | `app_user` plus `legacy_data` | Preserve identity, roles/ACL, sanctions, OAuth metadata, password hash and salt |
|
||||
| `member_log` | `legacy_member_log` | Preserve complete JSON action history |
|
||||
| `banned_member` | `legacy_banned_member` | Preserve hashed-email ban |
|
||||
| `storage` | `legacy_root_key_value` | Preserve raw namespace/key/JSON value |
|
||||
| `system` | `system` | Preserve registration/login switches and notice |
|
||||
| `login_token` | none | Exclude expired bearer tokens, IP addresses and obsolete PHP sessions |
|
||||
|
||||
Legacy member numbers map to deterministic UUIDs. Existing rows are updated by
|
||||
that UUID, so references such as `ng_old_generals.owner` remain stable even
|
||||
when an old account was deleted before the dump.
|
||||
|
||||
Kakao members retain `oauth_id`, email and metadata. Cutover sets
|
||||
`kakao_verified_at` and `kakao_grace_started_at` to the migration time, starting
|
||||
the current verification policy from the migration instead of treating an old
|
||||
Kakao login as unverified. Five source Kakao rows have no OAuth ID; their
|
||||
metadata is retained for audit, but an absent provider identifier is not
|
||||
invented.
|
||||
|
||||
Legacy password hashes remain usable when gateway-api has
|
||||
`GATEWAY_LEGACY_PASSWORD_GLOBAL_SALT`; a successful login upgrades the stored
|
||||
value to Argon2id. A test-only account can instead be reset with the CLI and a
|
||||
mode-0600 password file:
|
||||
|
||||
```sh
|
||||
GATEWAY_DATABASE_URL=... pnpm migrate:legacy -- \
|
||||
reset-password --login-id test-user --password-file /secure/password --apply
|
||||
```
|
||||
|
||||
The password is never accepted as an argument or printed.
|
||||
|
||||
### Game profiles
|
||||
|
||||
| Legacy table | Target | Policy |
|
||||
| ------------------------------- | ------------------------ | --------------------------------------------------------------------- |
|
||||
| `ng_games` | `ng_games` | Preserve completed season metadata |
|
||||
| `hall` | `hall` | Preserve hall-of-fame rows |
|
||||
| `ng_old_generals` | `ng_old_generals` | Preserve full JSON snapshots and owner |
|
||||
| `ng_old_nations` | `ng_old_nations` | Preserve all versions, including duplicate server/nation pairs |
|
||||
| `emperior` | `emperior` | Preserve dynasty detail and legacy key |
|
||||
| `inheritance_result` | `inheritance_result` | Preserve result JSON/string and legacy key |
|
||||
| `user_record` | `inheritance_log` | Preserve complete long-lived user record |
|
||||
| persistent `storage` namespaces | `legacy_game_storage` | Preserve raw `inheritance_*` and `user_*` rows before projection |
|
||||
| `storage:inheritance_point` | `inheritance_point` | Project the numeric first tuple item; retain the tuple in raw storage |
|
||||
| `storage:user` | `inheritance_user_state` | Project known current inheritance state; retain raw storage |
|
||||
| `ng_history` | `yearbook_history` | Preserve map, nation, global history and global action snapshots |
|
||||
|
||||
The source contains legitimate duplicate `(server_id, nation)` old-nation rows
|
||||
and `(server_id, year, month)` history rows. `source_id` is consequently part of
|
||||
the archive unique keys. Runtime-generated rows use `source_id = 0`; migrated
|
||||
rows use the legacy primary key. This avoids a lossy last-row-wins upsert.
|
||||
|
||||
Current-season actor/world/queue/lock/message/market/vote state is explicitly
|
||||
excluded. In particular, `general`, `city`, `nation`, their turn queues,
|
||||
`general_record`, `world_history`, `statistic`, `board`, `comment`,
|
||||
`diplomacy`, `ng_diplomacy`, `event`, `message`, `rank_data`, `troop`,
|
||||
`tournament`, `vote`, `vote_comment`, season-scoped/unknown `storage`,
|
||||
`ng_auction`, `ng_auction_bid`,
|
||||
`ng_betting`, `reserved_open`, `select_pool`, `select_npc_token` and `plock`
|
||||
must not be used to reconstruct a running season.
|
||||
|
||||
## Cutover procedure
|
||||
|
||||
1. Keep the original compressed dumps immutable and restore each source to a
|
||||
private MariaDB instance.
|
||||
2. Deploy the gateway and game Prisma migrations to empty staging databases.
|
||||
3. Run gateway and each non-empty profile without `--apply`; archive the JSON
|
||||
counts and excluded-table reasons.
|
||||
4. Compare source counts, malformed JSON checks and duplicate natural-key
|
||||
counts. Stop on unexplained drift.
|
||||
5. Put the affected target in maintenance mode, take a PostgreSQL backup, then
|
||||
run the same commands with `--apply`.
|
||||
6. Repeat each apply. Counts must remain unchanged.
|
||||
7. Verify Kakao migration timestamps, password-hash shapes, archive ownership,
|
||||
old-nation/history duplicate preservation and the `/past-plays` authenticated
|
||||
read path.
|
||||
8. Retain the MariaDB dumps as rollback evidence. Rollback restores the
|
||||
pre-cutover PostgreSQL backup; it does not reverse individual importer
|
||||
upserts.
|
||||
|
||||
The supplied dump inventory has data for `che`, `hwe` and `root`. The `kwe`
|
||||
directory is empty, so there is no KWE dataset to import or validate.
|
||||
Reference in New Issue
Block a user