mirror of
https://github.com/djdevin/recflare.git
synced 2026-10-27 16:08:58 -07:00
[playersettings] improve migration
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
/**
|
||||
* Default player settings seeded on a player's first read, ported verbatim from
|
||||
* the reference's `PlayerSettingsController.GetPlayerSettings`. Ordered; written to the player’s row the
|
||||
* first time a player has no stored settings.
|
||||
* first time a player has no stored settings. Mirrored as `SEEDED_DEFAULTS` in
|
||||
* packages/tools/src/cmd/admin.cmd.ts (the KV import treats these values as the seed) — keep in sync.
|
||||
*/
|
||||
export const DEFAULT_SETTINGS: Array<{ Key: string; Value: string }> = [
|
||||
{ Key: 'Recroom.OOBE', Value: '77' },
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useWorkersLogger } from 'workers-tagged-logger'
|
||||
|
||||
import {
|
||||
mergePlayerSettings,
|
||||
PlayerSettingsTooLargeError,
|
||||
putPlayerSettingsIfChanged,
|
||||
readPlayerSettings,
|
||||
writePlayerSettings,
|
||||
@@ -202,13 +203,16 @@ const app = new Hono<App>()
|
||||
'player’s other settings alone. A JSON body is also accepted, as one object or an',
|
||||
'array, in either `key`/`value` or `Key`/`Value` casing; entries with an empty key are',
|
||||
'dropped. An unparseable or empty body is a no-op 200, not a 400, and so is a PUT whose',
|
||||
'values are already stored — nothing is written. Empty body on success.',
|
||||
'values are already stored — nothing is written. A PUT that would make the stored map',
|
||||
'larger than 64 KiB of JSON is refused with a 413 and changes nothing. Empty body on',
|
||||
'success.',
|
||||
].join(' '),
|
||||
security: AUTHED,
|
||||
requestBody: formOrJson(SettingFormWrite, SettingJsonWrite, 'The setting(s) to write'),
|
||||
responses: {
|
||||
200: { description: 'Applied, or nothing parseable to apply (empty body)' },
|
||||
401: UNAUTHORIZED_RESPONSE,
|
||||
413: { description: 'The stored map would exceed 64 KiB; nothing was written' },
|
||||
},
|
||||
}),
|
||||
async (c) => {
|
||||
@@ -221,7 +225,12 @@ const app = new Hono<App>()
|
||||
const patch: Record<string, string> = {}
|
||||
for (const { key, value } of incoming) patch[key] = value
|
||||
|
||||
await mergePlayerSettings(c.env.DB, id, patch)
|
||||
try {
|
||||
await mergePlayerSettings(c.env.DB, id, patch)
|
||||
} catch (err) {
|
||||
if (err instanceof PlayerSettingsTooLargeError) return c.body(null, 413)
|
||||
throw err
|
||||
}
|
||||
return c.body(null, 200)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -182,6 +182,29 @@ describe('playersettings endpoints', () => {
|
||||
)
|
||||
})
|
||||
|
||||
// The map lives on the D1 every worker shares, so a player can't grow it without bound.
|
||||
it('PUT /playersettings refuses a write that would push the map past the cap', async () => {
|
||||
await seedRaw(11, JSON.stringify({ A: '1' }))
|
||||
const big = Array.from({ length: 70 }, (_, i) => ({ key: `K${i}`, value: 'x'.repeat(1024) }))
|
||||
|
||||
const res = await SELF.fetch(`${ORIGIN}/playersettings`, {
|
||||
method: 'PUT',
|
||||
headers: { ...(await bearer('11')), 'content-type': 'application/json' },
|
||||
body: JSON.stringify(big),
|
||||
})
|
||||
expect(res.status).toBe(413)
|
||||
expect(await stored(11)).toEqual({ A: '1' })
|
||||
|
||||
// Under the cap, the same kind of write still lands.
|
||||
const ok = await SELF.fetch(`${ORIGIN}/playersettings`, {
|
||||
method: 'PUT',
|
||||
headers: { ...(await bearer('11')), 'content-type': 'application/json' },
|
||||
body: JSON.stringify(big.slice(0, 10)),
|
||||
})
|
||||
expect(ok.status).toBe(200)
|
||||
expect(Object.keys((await stored(11)) ?? {})).toHaveLength(11)
|
||||
})
|
||||
|
||||
it('PUT /playersettings 200s with no parseable settings', async () => {
|
||||
const res = await SELF.fetch(`${ORIGIN}/playersettings`, putForm({}, await bearer('9')))
|
||||
expect(res.status).toBe(200)
|
||||
|
||||
Reference in New Issue
Block a user