fix: 투표 UTC wall과 migration 시간대 경계를 고정한다
This commit is contained in:
@@ -97,12 +97,12 @@ model ReadModelOutbox {
|
||||
id BigInt @id @default(autoincrement())
|
||||
payload Json
|
||||
attempts Int @default(0)
|
||||
availableAt DateTime @default(dbgenerated("CURRENT_TIMESTAMP AT TIME ZONE 'UTC'")) @map("available_at") @db.Timestamp(3)
|
||||
availableAt DateTime @default(dbgenerated("(CURRENT_TIMESTAMP AT TIME ZONE 'UTC')")) @map("available_at") @db.Timestamp(3)
|
||||
lockedAt DateTime? @map("locked_at") @db.Timestamp(3)
|
||||
lockOwner String? @map("lock_owner")
|
||||
deliveredAt DateTime? @map("delivered_at") @db.Timestamp(3)
|
||||
lastError String? @map("last_error")
|
||||
createdAt DateTime @default(dbgenerated("CURRENT_TIMESTAMP AT TIME ZONE 'UTC'")) @map("created_at") @db.Timestamp(3)
|
||||
createdAt DateTime @default(dbgenerated("(CURRENT_TIMESTAMP AT TIME ZONE 'UTC')")) @map("created_at") @db.Timestamp(3)
|
||||
|
||||
@@index([deliveredAt, availableAt, id], map: "read_model_outbox_delivered_at_available_at_id_idx")
|
||||
@@map("read_model_outbox")
|
||||
@@ -116,12 +116,12 @@ model WebPushOutbox {
|
||||
year Int?
|
||||
month Int?
|
||||
attempts Int @default(0)
|
||||
availableAt DateTime @default(dbgenerated("CURRENT_TIMESTAMP AT TIME ZONE 'UTC'")) @map("available_at") @db.Timestamp(3)
|
||||
availableAt DateTime @default(dbgenerated("(CURRENT_TIMESTAMP AT TIME ZONE 'UTC')")) @map("available_at") @db.Timestamp(3)
|
||||
lockedAt DateTime? @map("locked_at") @db.Timestamp(3)
|
||||
lockOwner String? @map("lock_owner")
|
||||
deliveredAt DateTime? @map("delivered_at") @db.Timestamp(3)
|
||||
lastError String? @map("last_error")
|
||||
createdAt DateTime @default(dbgenerated("CURRENT_TIMESTAMP AT TIME ZONE 'UTC'")) @map("created_at") @db.Timestamp(3)
|
||||
createdAt DateTime @default(dbgenerated("(CURRENT_TIMESTAMP AT TIME ZONE 'UTC')")) @map("created_at") @db.Timestamp(3)
|
||||
|
||||
@@index([deliveredAt, availableAt, id], map: "web_push_outbox_delivered_at_available_at_id_idx")
|
||||
@@map("web_push_outbox")
|
||||
@@ -901,8 +901,8 @@ model VotePoll {
|
||||
endAt DateTime? @map("end_at")
|
||||
endTick BigInt? @map("end_tick")
|
||||
closedAt DateTime? @map("closed_at")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
createdAt DateTime @default(dbgenerated("(CURRENT_TIMESTAMP AT TIME ZONE 'UTC')")) @map("created_at") @db.Timestamp(3)
|
||||
updatedAt DateTime @default(dbgenerated("(CURRENT_TIMESTAMP AT TIME ZONE 'UTC')")) @updatedAt @map("updated_at") @db.Timestamp(3)
|
||||
|
||||
votes Vote[]
|
||||
comments VoteComment[]
|
||||
@@ -916,7 +916,7 @@ model Vote {
|
||||
generalId Int @map("general_id")
|
||||
nationId Int @map("nation_id")
|
||||
selection Json
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
createdAt DateTime @default(dbgenerated("(CURRENT_TIMESTAMP AT TIME ZONE 'UTC')")) @map("created_at") @db.Timestamp(3)
|
||||
|
||||
poll VotePoll @relation(fields: [voteId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@ -933,7 +933,7 @@ model VoteComment {
|
||||
generalName String @map("general_name")
|
||||
nationName String @map("nation_name")
|
||||
text String
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
createdAt DateTime @default(dbgenerated("(CURRENT_TIMESTAMP AT TIME ZONE 'UTC')")) @map("created_at") @db.Timestamp(3)
|
||||
|
||||
poll VotePoll @relation(fields: [voteId], references: [id], onDelete: Cascade)
|
||||
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
-- Vote mutations use raw SQL to preserve their locked transaction and Ref
|
||||
-- ordering. Game connections retain the Ref-compatible Seoul session, so make
|
||||
-- timestamp-omitting insert fallbacks safe for older and future writers.
|
||||
-- Historical rows are intentionally preserved because
|
||||
-- Prisma-seeded UTC values and DB-default Seoul values have no durable
|
||||
-- provenance marker that would allow a safe blanket rewrite.
|
||||
ALTER TABLE "vote_poll"
|
||||
ALTER COLUMN "created_at" SET DEFAULT (CURRENT_TIMESTAMP AT TIME ZONE 'UTC'),
|
||||
ALTER COLUMN "updated_at" SET DEFAULT (CURRENT_TIMESTAMP AT TIME ZONE 'UTC');
|
||||
|
||||
ALTER TABLE "vote"
|
||||
ALTER COLUMN "created_at" SET DEFAULT (CURRENT_TIMESTAMP AT TIME ZONE 'UTC');
|
||||
|
||||
ALTER TABLE "vote_comment"
|
||||
ALTER COLUMN "created_at" SET DEFAULT (CURRENT_TIMESTAMP AT TIME ZONE 'UTC');
|
||||
@@ -35,6 +35,11 @@ chain을 적용하고 두 번째 실행은 `No pending migrations to apply`여
|
||||
- `read_model_revision_meta.id=1`의 `coverage_version=0`
|
||||
- `diplomacy`, `event`, `log_entry`, `error_log`
|
||||
- auction, board, vote, yearbook, archive와 inheritance table
|
||||
- `vote_poll.created_at/updated_at`, `vote.created_at`, `vote_comment.created_at`의
|
||||
신규 raw-SQL fallback은 KST game session에서도 UTC wall 값을 기록한다. 현재
|
||||
writer는 JavaScript `Date`를 명시하고, 이전 writer 형태의 column 생략도 새
|
||||
default로 안전해야 한다. 기존 vote timestamp는 Prisma UTC와 raw KST 출처를
|
||||
구분할 표식이 없어 소급 이동하지 않는다.
|
||||
- `nation.chief_general_id`
|
||||
- `city.trade` nullable, `city.trust` REAL
|
||||
- `auction_bid.meta` JSONB NOT NULL
|
||||
@@ -62,6 +67,26 @@ WebPush `created_at` 보존, 두 pending outbox의 requeue·lease 해제, delive
|
||||
보존, target checksum과 이전 DML shape 호환성을 확인합니다. 이전 binary를 별도
|
||||
build해 실행하거나 down migration을 제공한다는 뜻은 아닙니다.
|
||||
|
||||
운영 release-controller의 게임 migration 명령은 profile runtime URL을 바꾸지
|
||||
않고 migration 연결에만 `options=-c TimeZone=Asia/Seoul`을 추가합니다. 이미
|
||||
명시된 `DATABASE_URL` option/query, `PGOPTIONS` 또는 `PGTZ`가 다른 timezone을
|
||||
요구하면 마지막 옵션으로 덮지 않고 migration 시작 전에 실패합니다. 그 다음
|
||||
변경하지 않은 원본 profile URL로 `current_setting('TimeZone')`을 조회해 기존
|
||||
writer session이 실제로 `Asia/Seoul`인지 확인한 뒤에만, KST option을 고정한
|
||||
별도 URL로 Prisma migration을 실행합니다. 이는 이미 배포된 migration checksum을
|
||||
보존하면서 legacy game wall-clock provenance가 다른 과거 시각을 0700 migration이
|
||||
잘못 재해석하는 일을 막습니다.
|
||||
|
||||
실제 fail-closed 경계는 timezone option이 없는 일회성 UTC-default role URL을
|
||||
`PROFILE_MIGRATION_UTC_DATABASE_URL`로 주입하고 다음처럼 재현합니다. 이 URL은
|
||||
격리 DB의 disposable role만 사용하며 문서·로그에 값을 남기지 않습니다.
|
||||
marker는 `external_fixture`로 등록되어 일반 조건부 runner가 일회성 role을 만들거나
|
||||
이 테스트를 실행하지 않으며, 위 URL을 준비한 명시적 실행에서만 활성화됩니다.
|
||||
|
||||
```sh
|
||||
pnpm --filter @sammo-ts/gateway-api test profileMigrationTimezone.integration.test.ts
|
||||
```
|
||||
|
||||
## NPC selection 중복 owner preflight
|
||||
|
||||
`20260731000000_add_npc_selection_token`은 `general.user_id` 중복을 발견하면
|
||||
|
||||
Reference in New Issue
Block a user