mirror of
https://github.com/djdevin/recflare.git
synced 2026-09-08 22:51:30 -07:00
add warning table (maybe this is just a notification, later)
This commit is contained in:
@@ -1,11 +1,12 @@
|
||||
import { Hono } from 'hono'
|
||||
import { describeRoute } from 'hono-openapi'
|
||||
|
||||
import { authedId, unauthorized } from '../http'
|
||||
import { authedId, authedRoles, unauthorized } from '../http'
|
||||
import {
|
||||
AUTHED,
|
||||
BareBoolean,
|
||||
CreateReportRequest,
|
||||
CreateWarningRequest,
|
||||
DeviceIdRequest,
|
||||
form,
|
||||
json,
|
||||
@@ -15,15 +16,24 @@ import {
|
||||
UNAUTHORIZED_RESPONSE,
|
||||
} from '../openapi'
|
||||
import { createReport } from '../reports-db'
|
||||
import { createWarning } from '../warnings-db'
|
||||
|
||||
import type { Context } from 'hono'
|
||||
import type { App } from '../context'
|
||||
|
||||
/**
|
||||
* Read one field of the report submission. The client posts it form-encoded, but the
|
||||
* Roles allowed to hand down a warning — the operator-granted elevated roles the auth
|
||||
* worker stamps from an account's isModerator/isDeveloper flags (see the admin CLI's
|
||||
* `grant-moderator` / `grant-developer`). Same set the `notify` / `www` workers gate
|
||||
* their admin surfaces on: a warning is a moderation action, but staff hold both.
|
||||
*/
|
||||
const MODERATOR_ROLES = new Set(['moderator', 'developer'])
|
||||
|
||||
/**
|
||||
* Read one field of a submitted form. The client posts these form-encoded, but the
|
||||
* same names also arrive as a query string on some builds, so both are accepted.
|
||||
*/
|
||||
function reportField(
|
||||
function formField(
|
||||
body: Record<string, unknown>,
|
||||
c: Context<App>,
|
||||
name: string
|
||||
@@ -136,23 +146,81 @@ export const moderationRoutes = new Hono<App>({ strict: false })
|
||||
if (reporterId === null) return unauthorized(c)
|
||||
|
||||
const body = await c.req.parseBody().catch(() => ({}) as Record<string, unknown>)
|
||||
const reportedPlayerId = asInt(reportField(body, c, 'PlayerIdReported'))
|
||||
const reportedPlayerId = asInt(formField(body, c, 'PlayerIdReported'))
|
||||
if (reportedPlayerId === null) {
|
||||
return c.json({ success: false, error: 'PlayerIdReported is required' }, 400)
|
||||
}
|
||||
|
||||
// 0 / -1 are the client's "no room" values — store null rather than a bogus id.
|
||||
const roomId = asInt(reportField(body, c, 'RoomId'))
|
||||
const roomId = asInt(formField(body, c, 'RoomId'))
|
||||
|
||||
await createReport(c.env.DB, {
|
||||
reporterPlayerId: reporterId,
|
||||
reportedPlayerId,
|
||||
reportCategory: asInt(reportField(body, c, 'ReportCategory')) ?? 0,
|
||||
details: reportField(body, c, 'Details') ?? null,
|
||||
heightReporter: asFloat(reportField(body, c, 'HeightReporter')),
|
||||
heightReported: asFloat(reportField(body, c, 'HeightReported')),
|
||||
reportCategory: asInt(formField(body, c, 'ReportCategory')) ?? 0,
|
||||
details: formField(body, c, 'Details') ?? null,
|
||||
heightReporter: asFloat(formField(body, c, 'HeightReporter')),
|
||||
heightReported: asFloat(formField(body, c, 'HeightReported')),
|
||||
roomId: roomId !== null && roomId > 0 ? roomId : null,
|
||||
roomInstanceType: reportField(body, c, 'RoomInstanceType') ?? null,
|
||||
roomInstanceType: formField(body, c, 'RoomInstanceType') ?? null,
|
||||
})
|
||||
|
||||
return c.json({ success: true, error: '' })
|
||||
}
|
||||
)
|
||||
|
||||
// A warning handed down by a moderator — the staff-side counterpart to a report.
|
||||
// Gated on the `moderator` role in the token, not just a valid one.
|
||||
.post(
|
||||
'/api/playerwarnings',
|
||||
describeRoute({
|
||||
tags: ['Moderation'],
|
||||
summary: 'Issue a player warning',
|
||||
description:
|
||||
'Records a moderator-issued warning in the `warning` table — an append-only log ' +
|
||||
'like `report`; nothing dispatches the warning to the player or acts on the rows ' +
|
||||
'yet.\n\n' +
|
||||
'**Staff only.** The token must carry the `moderator` or `developer` role (granted ' +
|
||||
'per account by the operator, see the admin CLI’s `grant-moderator` / ' +
|
||||
'`grant-developer`); a valid token with neither gets a 403. The acting moderator ' +
|
||||
'is the caller, NOT a body field.\n\n' +
|
||||
'Only `WarnedPlayerId` is required; the rest are stored as NULL when absent. ' +
|
||||
'`ReportCategory` is stored verbatim — the enum is not mapped here. ' +
|
||||
'`DisplayReason` is what the warned player would be shown; `ModeratorNote` is ' +
|
||||
'internal and never surfaced to them.\n\n' +
|
||||
'Answers the same `{ success, error }` envelope as the report write, with `error` ' +
|
||||
'an empty string rather than null — including on the rejected branches, so there ' +
|
||||
'is only one shape to parse.',
|
||||
security: AUTHED,
|
||||
requestBody: form(CreateWarningRequest, 'The warning'),
|
||||
responses: {
|
||||
200: json(ReportCreateResponse, '`{ success: true, error: "" }`'),
|
||||
400: json(ReportCreateResponse, 'No `WarnedPlayerId` in the request'),
|
||||
401: UNAUTHORIZED_RESPONSE,
|
||||
403: json(ReportCreateResponse, 'A valid token with neither staff role'),
|
||||
},
|
||||
}),
|
||||
async (c) => {
|
||||
const moderatorId = await authedId(c)
|
||||
if (moderatorId === null) return unauthorized(c)
|
||||
|
||||
const roles = await authedRoles(c)
|
||||
if (!roles?.some((role) => MODERATOR_ROLES.has(role))) {
|
||||
return c.json({ success: false, error: 'Forbidden' }, 403)
|
||||
}
|
||||
|
||||
const body = await c.req.parseBody().catch(() => ({}) as Record<string, unknown>)
|
||||
const warnedPlayerId = asInt(formField(body, c, 'WarnedPlayerId'))
|
||||
if (warnedPlayerId === null) {
|
||||
return c.json({ success: false, error: 'WarnedPlayerId is required' }, 400)
|
||||
}
|
||||
|
||||
await createWarning(c.env.DB, {
|
||||
moderatorPlayerId: moderatorId,
|
||||
warnedPlayerId,
|
||||
reportCategory: asInt(formField(body, c, 'ReportCategory')) ?? 0,
|
||||
displayReason: formField(body, c, 'DisplayReason') ?? null,
|
||||
moderatorNote: formField(body, c, 'ModeratorNote') ?? null,
|
||||
})
|
||||
|
||||
return c.json({ success: true, error: '' })
|
||||
|
||||
Reference in New Issue
Block a user