Add tournament and multiplayer community chat moderation
All checks were successful
Deploy MineGeo to Dokploy / deploy (push) Successful in 12m17s

This commit is contained in:
2026-10-27 12:54:15 +05:00
parent b8a430d4c2
commit 9c314ecb4e
19 changed files with 1070 additions and 16 deletions

View File

@@ -13,7 +13,11 @@ import {
panoramaPath,
viewpointSchema,
} from "@/shared/protocol";
import { roomGuessSchema, roomRoundSchema } from "@/shared/multiplayer";
import {
chatMessageSchema,
roomGuessSchema,
roomRoundSchema,
} from "@/shared/multiplayer";
import { ServiceError, checkServiceError } from "./service-error";
type Dependencies = {
@@ -82,6 +86,75 @@ export async function tournamentApi(
throw new ServiceError("Not found", 404);
const id = z.string().uuid().parse(path[1]);
const action = path[2] ?? "view";
if (["community", "chat"].includes(action)) {
if (!["GET", "POST"].includes(request.method))
throw new ServiceError("Not found", 404);
const account = await deps.account();
if (!account)
throw new ServiceError("Sign in to join or play a tournament", 401);
if (action === "chat") {
const body =
request.method === "POST"
? chatMessageSchema.strict().parse(await deps.jsonBody(request))
: null;
if (body) await deps.limit(`tournaments:chat:${account.id}`, 60, 60);
return respond(
unwrap(
await db.rpc("tournament_chat", {
p_tournament: id,
p_account: account.id,
p_text: body?.text ?? null,
p_client_id: body?.clientId ?? null,
}),
),
);
}
const schema = z.discriminatedUnion("action", [
z
.object({
action: z.literal("slow"),
seconds: z.number().int().min(0).max(300),
})
.strict(),
...(["admin", "unadmin", "kick", "ban", "unban"] as const).map((action) =>
z
.object({ action: z.literal(action), accountId: z.string().uuid() })
.strict(),
),
z
.object({
action: z.literal("tag"),
accountId: z.string().uuid(),
tag: z
.string()
.trim()
.max(32)
.refine(
(value) =>
!/[\u0000-\u001f\u007f-\u009f\u202a-\u202e\u2066-\u2069]/u.test(
value,
),
),
})
.strict(),
]);
const body =
request.method === "POST"
? schema.parse(await deps.jsonBody(request))
: { action: "view" as const };
if (request.method === "POST")
await deps.limit(`tournaments:moderation:${account.id}`, 30, 60);
return respond(
unwrap(
await db.rpc("tournament_community", {
p_tournament: id,
p_account: account.id,
p_action: body.action,
p_payload: body,
}),
),
);
}
const read =
request.method === "GET" && (action === "view" || action === "media");
const write =