Add tournament and multiplayer community chat moderation
All checks were successful
Deploy MineGeo to Dokploy / deploy (push) Successful in 12m17s
All checks were successful
Deploy MineGeo to Dokploy / deploy (push) Successful in 12m17s
This commit is contained in:
@@ -93,6 +93,64 @@ export async function multiplayerApi(
|
||||
409,
|
||||
);
|
||||
const action = path[2] ?? "view";
|
||||
if (
|
||||
action === "community" &&
|
||||
path.length === 3 &&
|
||||
["GET", "POST"].includes(request.method)
|
||||
) {
|
||||
if (!savedSecret)
|
||||
throw new ServiceError("Room membership permission required", 403);
|
||||
const input =
|
||||
request.method === "POST" ? await jsonBody(request) : { action: "view" };
|
||||
const schema = z.discriminatedUnion("action", [
|
||||
z.object({ action: z.literal("view") }).strict(),
|
||||
z
|
||||
.object({
|
||||
action: z.literal("slow"),
|
||||
seconds: z.number().int().min(0).max(300),
|
||||
})
|
||||
.strict(),
|
||||
...(["admin", "unadmin", "kick", "ban"] as const).map((action) =>
|
||||
z
|
||||
.object({ action: z.literal(action), playerId: z.string().uuid() })
|
||||
.strict(),
|
||||
),
|
||||
z
|
||||
.object({ action: z.literal("unban"), banId: z.string().uuid() })
|
||||
.strict(),
|
||||
z
|
||||
.object({
|
||||
action: z.literal("tag"),
|
||||
playerId: 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 = schema.parse(input);
|
||||
if (request.method === "POST") {
|
||||
if (body.action === "view") throw new ServiceError("Not found", 404);
|
||||
await limit(`multiplayer:moderation:${accessHash}`, 30, 60);
|
||||
}
|
||||
return respond(
|
||||
required(
|
||||
await client.rpc("party_community", {
|
||||
p_room: id,
|
||||
p_access_hash: accessHash,
|
||||
p_action: body.action,
|
||||
p_payload: body,
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
if (action === "discord" && path.length === 3 && request.method === "POST") {
|
||||
const view = required(
|
||||
await client.rpc("multiplayer_action", {
|
||||
|
||||
@@ -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 =
|
||||
|
||||
Reference in New Issue
Block a user