add duid endpoint

This commit is contained in:
Devin Zuczek
2026-07-13 17:35:35 -04:00
parent 06c9f8f409
commit 08f25fb584
3 changed files with 68 additions and 0 deletions
+22
View File
@@ -1,5 +1,9 @@
import { Hono } from 'hono'
import { setDeviceId } from '@repo/domain'
import { authedId, unauthorized } from '../http'
import type { App } from '../context'
// ---- Player reporting ------------------------------------------------------
@@ -23,3 +27,21 @@ export const moderationRoutes = new Hono<App>({ strict: false })
)
.get('/api/PlayerReporting/v1/voteToKickReasons', (c) => c.json([])) // TODO: hydrate from JSON/vtkreasons.json
.post('/api/PlayerReporting/v1/hile', (c) => c.json(false))
// The client reporting its device id (form-encoded `oldDeviceId`, `newDeviceId`,
// `platform`), rotating from the id it thinks we hold to the current one. We don't
// reconcile the two: the client is the only source for either, so a mismatch tells
// us nothing and last write wins. `platform` is ignored — the account already
// records the platform its login is linked to. Auth-gated; the client ignores the
// response body, and the real service answers with an empty array.
.post('/api/PlayerReporting/v1/deviceId', async (c) => {
const id = await authedId(c)
if (id === null) return unauthorized(c)
const body = await c.req.parseBody().catch(() => ({}) as Record<string, unknown>)
const newDeviceId = body.newDeviceId
if (typeof newDeviceId !== 'string' || newDeviceId === '') {
return c.json({ error: 'newDeviceId is required' }, 400)
}
await setDeviceId(c.env.DB, id, newDeviceId)
return c.json([])
})
+31
View File
@@ -222,6 +222,37 @@ describe('public endpoints', () => {
})
})
test('POST /api/PlayerReporting/v1/deviceId stores the new device id', async () => {
const res = await exports.default.fetch(`${ORIGIN}/api/PlayerReporting/v1/deviceId`, {
method: 'POST',
headers: {
...(await bearer()),
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
oldDeviceId: '491e8b9',
newDeviceId: '491e8b9566cb1b593367c72860e978b3d5765326',
platform: '0',
}),
})
expect(res.status).toBe(200)
expect(await res.json()).toEqual([])
const row = await env.DB.prepare(
"SELECT json_extract(data, '$.deviceId') AS deviceId FROM account WHERE account_id = 42"
).first<{ deviceId: string | null }>()
expect(row?.deviceId).toBe('491e8b9566cb1b593367c72860e978b3d5765326')
})
test('POST /api/PlayerReporting/v1/deviceId 401s without a bearer token', async () => {
const res = await exports.default.fetch(`${ORIGIN}/api/PlayerReporting/v1/deviceId`, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({ newDeviceId: 'abc' }),
})
expect(res.status).toBe(401)
})
test('POST /api/playerReputation/v2/bulk returns a reputation per id', async () => {
const res = await exports.default.fetch(`${ORIGIN}/api/playerReputation/v2/bulk`, {
method: 'POST',