Track traffic by game month
This commit is contained in:
@@ -97,6 +97,8 @@ model WorldState {
|
||||
meta Json @default(dbgenerated("'{}'::jsonb"))
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
trafficPeriods TrafficPeriod[]
|
||||
|
||||
@@map("world_state")
|
||||
}
|
||||
|
||||
@@ -204,6 +206,38 @@ model GeneralAccessLog {
|
||||
@@map("general_access_log")
|
||||
}
|
||||
|
||||
model TrafficPeriod {
|
||||
id Int @id @default(autoincrement())
|
||||
worldStateId Int @map("world_state_id")
|
||||
year Int
|
||||
month Int
|
||||
startedAt DateTime @map("started_at")
|
||||
lastRefresh DateTime @map("last_refresh")
|
||||
refresh Int @default(0)
|
||||
online Int @default(0)
|
||||
|
||||
worldState WorldState @relation(fields: [worldStateId], references: [id], onDelete: Cascade)
|
||||
generals TrafficPeriodGeneral[]
|
||||
|
||||
@@unique([worldStateId, year, month])
|
||||
@@index([worldStateId, startedAt])
|
||||
@@map("traffic_period")
|
||||
}
|
||||
|
||||
model TrafficPeriodGeneral {
|
||||
periodId Int @map("period_id")
|
||||
generalId Int @map("general_id")
|
||||
userId String? @map("user_id")
|
||||
refresh Int @default(0)
|
||||
lastRefresh DateTime @map("last_refresh")
|
||||
|
||||
period TrafficPeriod @relation(fields: [periodId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@id([periodId, generalId])
|
||||
@@index([periodId, refresh, generalId])
|
||||
@@map("traffic_period_general")
|
||||
}
|
||||
|
||||
model MessageReadState {
|
||||
generalId Int @id @map("general_id")
|
||||
latestPrivateMessage Int @default(0) @map("latest_private_message")
|
||||
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
CREATE TABLE "traffic_period" (
|
||||
"id" SERIAL PRIMARY KEY,
|
||||
"world_state_id" INTEGER NOT NULL,
|
||||
"year" INTEGER NOT NULL,
|
||||
"month" INTEGER NOT NULL,
|
||||
"started_at" TIMESTAMP(3) NOT NULL,
|
||||
"last_refresh" TIMESTAMP(3) NOT NULL,
|
||||
"refresh" INTEGER NOT NULL DEFAULT 0,
|
||||
"online" INTEGER NOT NULL DEFAULT 0,
|
||||
CONSTRAINT "traffic_period_world_state_id_fkey"
|
||||
FOREIGN KEY ("world_state_id") REFERENCES "world_state"("id")
|
||||
ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
CONSTRAINT "traffic_period_month_check" CHECK ("month" BETWEEN 1 AND 12),
|
||||
CONSTRAINT "traffic_period_refresh_check" CHECK ("refresh" >= 0),
|
||||
CONSTRAINT "traffic_period_online_check" CHECK ("online" >= 0)
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "traffic_period_world_state_year_month_key"
|
||||
ON "traffic_period"("world_state_id", "year", "month");
|
||||
CREATE INDEX "traffic_period_world_state_started_at_idx"
|
||||
ON "traffic_period"("world_state_id", "started_at");
|
||||
|
||||
CREATE TABLE "traffic_period_general" (
|
||||
"period_id" INTEGER NOT NULL,
|
||||
"general_id" INTEGER NOT NULL,
|
||||
"user_id" TEXT,
|
||||
"refresh" INTEGER NOT NULL DEFAULT 0,
|
||||
"last_refresh" TIMESTAMP(3) NOT NULL,
|
||||
CONSTRAINT "traffic_period_general_pkey" PRIMARY KEY ("period_id", "general_id"),
|
||||
CONSTRAINT "traffic_period_general_period_id_fkey"
|
||||
FOREIGN KEY ("period_id") REFERENCES "traffic_period"("id")
|
||||
ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
CONSTRAINT "traffic_period_general_refresh_check" CHECK ("refresh" >= 0)
|
||||
);
|
||||
|
||||
CREATE INDEX "traffic_period_general_period_refresh_general_idx"
|
||||
ON "traffic_period_general"("period_id", "refresh", "general_id");
|
||||
|
||||
-- Preserve legacy completed-period snapshots. Invalid or missing legacy dates use
|
||||
-- the world row timestamp so one malformed JSON item cannot block deployment.
|
||||
WITH legacy_rows AS (
|
||||
SELECT
|
||||
world."id" AS "world_state_id",
|
||||
item,
|
||||
ordinality
|
||||
FROM "world_state" AS world
|
||||
CROSS JOIN LATERAL jsonb_array_elements(
|
||||
CASE
|
||||
WHEN jsonb_typeof(world."meta"->'recentTraffic') = 'array'
|
||||
THEN world."meta"->'recentTraffic'
|
||||
ELSE '[]'::jsonb
|
||||
END
|
||||
) WITH ORDINALITY AS legacy(item, ordinality)
|
||||
),
|
||||
normalized AS (
|
||||
SELECT
|
||||
legacy_rows."world_state_id",
|
||||
CASE
|
||||
WHEN pg_input_is_valid(legacy_rows.item->>'year', 'integer')
|
||||
THEN (legacy_rows.item->>'year')::INTEGER
|
||||
ELSE NULL
|
||||
END AS "year",
|
||||
CASE
|
||||
WHEN pg_input_is_valid(legacy_rows.item->>'month', 'integer')
|
||||
THEN (legacy_rows.item->>'month')::INTEGER
|
||||
ELSE NULL
|
||||
END AS "month",
|
||||
CASE
|
||||
WHEN pg_input_is_valid(legacy_rows.item->>'refresh', 'integer')
|
||||
THEN (legacy_rows.item->>'refresh')::INTEGER
|
||||
ELSE 0
|
||||
END AS "refresh",
|
||||
CASE
|
||||
WHEN pg_input_is_valid(legacy_rows.item->>'online', 'integer')
|
||||
THEN (legacy_rows.item->>'online')::INTEGER
|
||||
ELSE 0
|
||||
END AS "online",
|
||||
CASE
|
||||
WHEN pg_input_is_valid(
|
||||
legacy_rows.item->>'date',
|
||||
'timestamp without time zone'
|
||||
)
|
||||
THEN (legacy_rows.item->>'date')::TIMESTAMP
|
||||
ELSE world."updated_at"
|
||||
END AS "observed_at",
|
||||
legacy_rows.ordinality
|
||||
FROM legacy_rows
|
||||
JOIN "world_state" AS world ON world."id" = legacy_rows."world_state_id"
|
||||
)
|
||||
INSERT INTO "traffic_period" (
|
||||
"world_state_id",
|
||||
"year",
|
||||
"month",
|
||||
"started_at",
|
||||
"last_refresh",
|
||||
"refresh",
|
||||
"online"
|
||||
)
|
||||
SELECT DISTINCT ON ("world_state_id", "year", "month")
|
||||
"world_state_id",
|
||||
"year",
|
||||
"month",
|
||||
"observed_at",
|
||||
"observed_at",
|
||||
"refresh",
|
||||
"online"
|
||||
FROM normalized
|
||||
WHERE "year" IS NOT NULL
|
||||
AND "month" BETWEEN 1 AND 12
|
||||
ORDER BY "world_state_id", "year", "month", ordinality DESC
|
||||
ON CONFLICT ("world_state_id", "year", "month") DO NOTHING;
|
||||
@@ -33,6 +33,8 @@ pnpm --filter @sammo-ts/infra prisma:migrate:status:game
|
||||
- `nation.chief_general_id` 존재
|
||||
- `city.trade` nullable, `city.trust` REAL
|
||||
- `auction_bid.meta` JSONB, NOT NULL
|
||||
- `traffic_period`, `traffic_period_general` 존재 및
|
||||
`(world_state_id, year, month)`/`(period_id, general_id)` unique key
|
||||
|
||||
검증 뒤에는 이름을 확인한 임시 database와 role만 제거한다. 공유 개발
|
||||
database나 Compose volume을 삭제하지 않는다.
|
||||
|
||||
@@ -7,6 +7,8 @@ export interface DatabaseClient {
|
||||
worldState: GamePrisma.WorldStateDelegate;
|
||||
general: GamePrisma.GeneralDelegate;
|
||||
generalAccessLog: GamePrisma.GeneralAccessLogDelegate;
|
||||
trafficPeriod: GamePrisma.TrafficPeriodDelegate;
|
||||
trafficPeriodGeneral: GamePrisma.TrafficPeriodGeneralDelegate;
|
||||
messageReadState: GamePrisma.MessageReadStateDelegate;
|
||||
message: GamePrisma.MessageDelegate;
|
||||
city: GamePrisma.CityDelegate;
|
||||
|
||||
Reference in New Issue
Block a user