[www] show moderated image

This commit is contained in:
Devin Zuczek
2026-10-14 15:21:37 -04:00
parent 6f8e1182e8
commit e7ee9ed444
2 changed files with 46 additions and 5 deletions
+20 -2
View File
@@ -1,7 +1,7 @@
import { useCallback, useEffect, useState } from 'react'
import { KickReportCategory } from '../../../notify/src/notification-payloads'
import { accountIdForUsername, call, isAdmin, useAction, usernamesFor } from './api'
import { accountIdForUsername, call, isAdmin, useAction, usernamesFor, where } from './api'
import type { PublicAccount } from './api'
@@ -95,6 +95,11 @@ interface ReportRow {
banned_at: string | null
chat_message_id: number | null
image_id: number | null
/**
* The reported photo's key on `img`, joined on by `www` (the `report` table stores only
* the id). Null for a report that isn't against a photo, or one whose photo is gone.
*/
image_name?: string | null
/**
* Who lifted the ban that once stood on this row, and when — null until one is lifted,
* and cleared again by a re-ban. A lift wipes the four ban columns, so this pair is the
@@ -925,7 +930,20 @@ function ReportTable({
<td>
<PlayerName id={report.reporter_player_id} names={names} />
</td>
<td>{reportKind(report)}</td>
<td>
{report.image_name ? (
<a
href={`${where().img}/${report.image_name}`}
target="_blank"
rel="noreferrer"
title={`Photo #${report.image_id}`}
>
{reportKind(report)}
</a>
) : (
reportKind(report)
)}
</td>
<td>{categoryLabel(report.report_category)}</td>
{/* The reporter's own words, untruncated in the title so a long
description is readable without leaving the table. */}
+26 -3
View File
@@ -5,6 +5,7 @@ import {
clearPasswordHash,
createGift,
getAccount,
getImageById,
getOnlinePlayerIds,
getPlayerIdsInRoom,
getPresences,
@@ -340,6 +341,22 @@ async function recordBanAudit(
/** The evasion arms in force, read from the same knob `api` and `match` read. */
const armsFor = (env: Env) => banEvasionMatch(env.BAN_EVASION_MATCH)
/**
* Reports with the reported photo's `image_name` alongside its `image_id`, so the panel can
* link to the picture — `img` serves by name, and the row only stores the id. Read whatever
* the photo's accessibility: one made private after it was reported is still evidence. A
* photo since deleted gets a null name and the panel shows the bare id.
*/
async function withImageNames(db: D1Database, reports: ReportRow[]) {
const ids = [...new Set(reports.flatMap((r) => (r.image_id == null ? [] : [r.image_id])))]
const images = await Promise.all(ids.map((id) => getImageById(db, id)))
const names = new Map(ids.map((id, i) => [id, images[i]?.ImageName ?? null]))
return reports.map((r) => ({
...r,
image_name: r.image_id == null ? null : (names.get(r.image_id) ?? null),
}))
}
// ---- Handlers ---------------------------------------------------------------
/**
@@ -361,7 +378,7 @@ export async function searchReportsHandler(c: Context<App>) {
skip: intQuery(c, 'skip') ?? 0,
take: Math.min(intQuery(c, 'take') ?? 50, MAX_TAKE),
})
return c.json(page)
return c.json({ ...page, reports: await withImageNames(c.env.DB, page.reports) })
}
/** One report by id — the detail behind a search row. */
@@ -370,7 +387,8 @@ export async function getReportHandler(c: Context<App>) {
if (Number.isNaN(id)) return c.json({ error: 'A numeric report id is required' }, 400)
const report = await getReportById(c.env.DB, id)
if (report === null) return c.json({ error: 'No such report' }, 404)
return c.json(report)
const [withName] = await withImageNames(c.env.DB, [report])
return c.json(withName)
}
/**
@@ -525,7 +543,12 @@ export async function playerHistoryHandler(c: Context<App>) {
getWarningsAgainst(c.env.DB, playerId),
getActiveBan(c.env.DB, playerId),
])
return c.json({ playerId, reports, warnings, activeBan })
return c.json({
playerId,
reports: await withImageNames(c.env.DB, reports),
warnings,
activeBan,
})
}
/**