misc fixes to clubs/moderation/room schema

This commit is contained in:
Devin Zuczek
2026-07-12 22:44:09 -04:00
parent b463a79df2
commit d56dd99315
5 changed files with 99 additions and 14 deletions
+12
View File
@@ -24,6 +24,18 @@ export const gameplayRoutes = new Hono<App>({ strict: false })
.post('/api/objectives/v1/updateobjective', (c) => c.body(null, 200))
.get('/api/communityboard/v2/current', (c) => c.json({})) // TODO: hydrate from JSON/communityboard.json
.get('/api/playerevents/v1/all', (c) => c.json({ Created: [], Responses: [] }))
// Player events for a set of clubs (`?id=1&id=2`) — the events shelf on a club's
// page. A bare array: the client deserializes this one as a list, and chokes on the
// `{ ContinuationToken, Events }` envelope the single-club form uses. No
// player-event storage yet, so the feed is empty.
.get('/api/playerevents/v1/clubs', (c) => c.json([]))
// The same feed for a single club (`/club/1`) — the form the reference serves,
// which *does* wrap the events with a paging cursor (empty = no next page).
.get('/api/playerevents/v1/club/:clubId{[0-9]+}', (c) =>
c.json({ ContinuationToken: '', Events: [] })
)
.get('/api/announcement/v1/get', (c) => c.json([])) // TODO: hydrate from JSON/announcements.json
// GameSight attribution/analytics event sink. Accept and ack without persisting.
+10 -4
View File
@@ -4,15 +4,21 @@ import type { App } from '../context'
// ---- Player reporting ------------------------------------------------------
export const moderationRoutes = new Hono<App>({ strict: false })
// Whether the caller is currently blocked (banned / timed out / host-kicked). No
// ban storage yet, so this is always the "not blocked" answer. `ReportCategory` is
// -1 (no category) rather than 0, which is a real category; `Message` is null, not
// an empty string — the client distinguishes "no message" from a blank one.
.get('/api/PlayerReporting/v1/moderationBlockDetails', (c) =>
c.json({
ReportCategory: 0,
ReportCategory: -1,
Duration: 0,
GameSessionId: 0,
IsHostKick: false,
Message: '',
PlayerIdReporter: null,
IsBan: false,
IsHostKick: false,
IsVoiceModAutoban: false,
Message: null,
PlayerIdReporter: null,
TimeoutStartedAt: null,
})
)
.get('/api/PlayerReporting/v1/voteToKickReasons', (c) => c.json([])) // TODO: hydrate from JSON/vtkreasons.json
+11 -3
View File
@@ -4,18 +4,26 @@ import { parseFormIds, queryIds } from '../http'
import type { App } from '../context'
/** Default reputation for an account — the fallback used with no DB. */
/**
* Default reputation for an account — the fallback used with no DB. Nobody has
* earned cheers yet, so every counter is 0 and everyone has their full cheer credit.
* `SelectedCheer` is an int (0 = none selected), not null, and `IsCheerful` is true:
* the client reads it to decide whether the player may hand out cheers at all.
*/
function defaultReputation(id: number) {
return {
AccountId: id,
IsCheerful: true,
Noteriety: 0,
SelectedCheer: 0,
CheerCredit: 20,
CheerGeneral: 0,
CheerHelpful: 0,
CheerCreative: 0,
CheerGreatHost: 0,
CheerSportsman: 0,
CheerCredit: 20,
SelectedCheer: null,
SubscriberCount: 0,
SubscribedCount: 0,
}
}
+53 -3
View File
@@ -154,11 +154,61 @@ describe('public endpoints', () => {
})
test('GET /api/playerReputation/v2/bulk?id= returns a reputation per id', async () => {
const res = await exports.default.fetch(`${ORIGIN}/api/playerReputation/v2/bulk?id=1&id=2`)
const res = await exports.default.fetch(`${ORIGIN}/api/playerReputation/v2/bulk?id=1380`)
expect(res.status).toBe(200)
const reps = (await res.json()) as Array<{ AccountId: number; CheerCredit: number }>
// The full reputation shape the client expects, field for field.
expect(await res.json()).toEqual([
{
AccountId: 1380,
IsCheerful: true,
Noteriety: 0,
SelectedCheer: 0,
CheerCredit: 20,
CheerGeneral: 0,
CheerHelpful: 0,
CheerCreative: 0,
CheerGreatHost: 0,
CheerSportsman: 0,
SubscriberCount: 0,
SubscribedCount: 0,
},
])
const many = await exports.default.fetch(`${ORIGIN}/api/playerReputation/v2/bulk?id=1&id=2`)
const reps = (await many.json()) as Array<{ AccountId: number }>
expect(reps.map((r) => r.AccountId)).toEqual([1, 2])
expect(reps[0]).toMatchObject({ CheerCredit: 20 })
})
test('GET /api/playerevents/v1/clubs returns an empty event list', async () => {
// The client deserializes this as a bare array — an envelope here fails with
// "expected:'[', actual:'{'". No player-event storage yet → empty.
const res = await exports.default.fetch(`${ORIGIN}/api/playerevents/v1/clubs?id=1&id=2`)
expect(res.status).toBe(200)
expect(await res.json()).toEqual([])
// The single-club form does wrap its events with a paging cursor.
const one = await exports.default.fetch(`${ORIGIN}/api/playerevents/v1/club/1`)
expect(one.status).toBe(200)
expect(await one.json()).toEqual({ ContinuationToken: '', Events: [] })
})
test('GET /api/PlayerReporting/v1/moderationBlockDetails reports "not blocked"', async () => {
const res = await exports.default.fetch(
`${ORIGIN}/api/PlayerReporting/v1/moderationBlockDetails`
)
expect(res.status).toBe(200)
// ReportCategory -1 = no category (0 is a real one), and Message is null.
expect(await res.json()).toEqual({
ReportCategory: -1,
Duration: 0,
GameSessionId: 0,
IsBan: false,
IsHostKick: false,
IsVoiceModAutoban: false,
Message: null,
PlayerIdReporter: null,
TimeoutStartedAt: null,
})
})
test('POST /api/playerReputation/v2/bulk returns a reputation per id', async () => {