mirror of
https://github.com/djdevin/recflare.git
synced 2026-10-25 15:09:18 -07:00
[rooms] record invention room usage
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
-- Which inventions a room has in it — one row per (room, invention).
|
||||
--
|
||||
-- Generated from packages/domain/src/rooms-db.ts (SUBROOM_SCHEMA_DDL) — keep in sync.
|
||||
--
|
||||
-- A room save (`POST /rooms/:id/subrooms/:sid/data`) carries `InventionUsage`, a base64
|
||||
-- protobuf listing the inventions in the saved scene (see `decodeInventionUsage`). Each
|
||||
-- save re-derives the room's rows here from every one of its subrooms, so the table is the
|
||||
-- union across the room and an invention removed from it drops out on the next save.
|
||||
--
|
||||
-- Empty until rooms are next saved: the `InventionUsage` already stored on subrooms is not
|
||||
-- backfilled, since decoding it is not something SQL can do.
|
||||
|
||||
CREATE TABLE IF NOT EXISTS room_invention (
|
||||
room_id INTEGER NOT NULL,
|
||||
invention_id INTEGER NOT NULL,
|
||||
PRIMARY KEY (room_id, invention_id)
|
||||
);
|
||||
|
||||
-- The reverse lookup: which rooms use this invention.
|
||||
CREATE INDEX IF NOT EXISTS idx_room_invention_invention ON room_invention (invention_id);
|
||||
@@ -10,7 +10,9 @@ import { beforeAll, describe, expect, it } from 'vitest'
|
||||
|
||||
import {
|
||||
AUDIT_LOG_SCHEMA_DDL,
|
||||
countRoomsUsingInvention,
|
||||
createRoomInstance,
|
||||
decodeInventionUsage,
|
||||
getRoomInstance,
|
||||
MessageType,
|
||||
NOTIFICATION_SCHEMA_DDL,
|
||||
@@ -22,6 +24,7 @@ import {
|
||||
SUBROOM_SCHEMA_DDL,
|
||||
} from '@repo/domain'
|
||||
|
||||
import { SCHEMA_DDL as INVENTION_SCHEMA_DDL } from '../../../../api/src/inventions-db'
|
||||
import { SCHEMA_DDL as ROOM_VOTE_SCHEMA_DDL } from '../../../../api/src/votes-db'
|
||||
import { NotificationType } from '../../../../notify/src/notification-types'
|
||||
import importRooms from '../../../static/ImportRooms.json'
|
||||
@@ -119,6 +122,8 @@ beforeAll(async () => {
|
||||
// Vote-to-kick ballots (owned by `api`) and game invites (owned by `match`) — the cron
|
||||
// here sweeps the stale ones.
|
||||
for (const stmt of ROOM_VOTE_SCHEMA_DDL) await env.DB.prepare(stmt).run()
|
||||
// Inventions (owned by the api worker) — a room save writes each one's room count.
|
||||
for (const stmt of INVENTION_SCHEMA_DDL) await env.DB.prepare(stmt).run()
|
||||
for (const stmt of ROOM_INVITE_SCHEMA_DDL) await env.DB.prepare(stmt).run()
|
||||
// Seed each room and split its subrooms into the subroom table (mirrors 0007's backfill).
|
||||
for (const r of importRooms) await seedRoomWithSubRooms(env.DB, r as Record<string, unknown>)
|
||||
@@ -3625,6 +3630,61 @@ describe('rooms endpoints', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('a room save records which inventions the room uses in room_invention', async () => {
|
||||
const save = async (inventionUsage: string) => {
|
||||
const res = await SELF.fetch(`${ORIGIN}/rooms/2/subrooms/2/data`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', ...(await bearer('1')) },
|
||||
body: JSON.stringify({ InventionUsage: inventionUsage, AutoPublish: true }),
|
||||
})
|
||||
expect(((await res.json()) as { success: boolean }).success).toBe(true)
|
||||
}
|
||||
const rows = async (): Promise<number[]> =>
|
||||
(
|
||||
await env.DB.prepare(
|
||||
'SELECT invention_id FROM room_invention WHERE room_id = 2 ORDER BY invention_id'
|
||||
).all<{ invention_id: number }>()
|
||||
).results.map((r) => r.invention_id)
|
||||
|
||||
// The invention records the counts are written onto (the `api` worker's table).
|
||||
for (const id of [257, 27941]) {
|
||||
await env.DB.prepare('INSERT INTO invention (data) VALUES (?1)')
|
||||
.bind(JSON.stringify({ InventionId: id, NumPlayersHaveUsedInRoom: 0 }))
|
||||
.run()
|
||||
}
|
||||
const usedIn = async (id: number): Promise<number> =>
|
||||
(
|
||||
await env.DB.prepare(
|
||||
"SELECT json_extract(data, '$.NumPlayersHaveUsedInRoom') AS n FROM invention WHERE id = ?1"
|
||||
)
|
||||
.bind(id)
|
||||
.first<{ n: number }>()
|
||||
)?.n ?? -1
|
||||
|
||||
// A real client value: `{ 1: 1, 2: [{ 1: 27941, 2: 1 }, { 1: 257, 2: 1 }] }`.
|
||||
expect(decodeInventionUsage('CAESBgil2gEQARIFCIECEAE=')).toEqual([27941, 257])
|
||||
await save('CAESBgil2gEQARIFCIECEAE=')
|
||||
expect(await rows()).toEqual([257, 27941])
|
||||
expect(await countRoomsUsingInvention(env.DB, 27941)).toBe(1)
|
||||
// The invention's own counter is the number of rooms it is in.
|
||||
expect([await usedIn(257), await usedIn(27941)]).toEqual([1, 1])
|
||||
|
||||
// Saving the same inventions again is not a second use.
|
||||
await save('CAESBgil2gEQARIFCIECEAE=')
|
||||
expect([await usedIn(257), await usedIn(27941)]).toEqual([1, 1])
|
||||
|
||||
// An invention taken out of the room drops out, and its counter comes back down.
|
||||
await save('CAESBQiBAhAB')
|
||||
expect(await rows()).toEqual([257])
|
||||
expect(await countRoomsUsingInvention(env.DB, 27941)).toBe(0)
|
||||
expect([await usedIn(257), await usedIn(27941)]).toEqual([1, 0])
|
||||
|
||||
// A value that doesn't decode records nothing and does not fail the save.
|
||||
await save('not base64 !!')
|
||||
expect(await rows()).toEqual([])
|
||||
expect(await usedIn(257)).toBe(0)
|
||||
})
|
||||
|
||||
it('a real client room-save body stages, and publish_save makes it live', async () => {
|
||||
const save = async (body: unknown) =>
|
||||
SELF.fetch(`${ORIGIN}/rooms/5/subrooms/5/data`, {
|
||||
|
||||
Reference in New Issue
Block a user