diff --git a/app/features/api-public/routes/tournament.$id.teams.$teamId.update-member-ign.ts b/app/features/api-public/routes/tournament.$id.teams.$teamId.update-member-ign.ts new file mode 100644 index 000000000..99f4610e8 --- /dev/null +++ b/app/features/api-public/routes/tournament.$id.teams.$teamId.update-member-ign.ts @@ -0,0 +1,49 @@ +import type { ActionFunctionArgs } from "react-router"; +import { z } from "zod"; +import { action as adminAction } from "~/features/tournament/actions/to.$id.admin.server"; +import { IN_GAME_NAME_REGEXP } from "~/features/user-page/user-page-constants"; +import { parseBody, parseParams } from "~/utils/remix.server"; +import { id } from "~/utils/zod"; +import { wrapActionForApi } from "../api-action-wrapper.server"; + +const paramsSchema = z.object({ + id, + teamId: id, +}); + +const bodySchema = z.object({ + userId: id, + inGameName: z.string().regex(IN_GAME_NAME_REGEXP), +}); + +export const action = async (args: ActionFunctionArgs) => { + const { id: tournamentId } = parseParams({ + params: args.params, + schema: paramsSchema, + }); + const { userId, inGameName } = await parseBody({ + request: args.request, + schema: bodySchema, + }); + + const hashIndex = inGameName.lastIndexOf("#"); + const inGameNameText = inGameName.slice(0, hashIndex); + const inGameNameDiscriminator = inGameName.slice(hashIndex + 1); + + const internalRequest = new Request(args.request.url, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + _action: "UPDATE_IN_GAME_NAME", + memberId: userId, + inGameNameText, + inGameNameDiscriminator, + }), + }); + + return wrapActionForApi(adminAction, { + ...args, + params: { id: String(tournamentId) }, + request: internalRequest, + }); +}; diff --git a/app/features/api-public/schema.ts b/app/features/api-public/schema.ts index dc9a47966..623260cf6 100644 --- a/app/features/api-public/schema.ts +++ b/app/features/api-public/schema.ts @@ -526,3 +526,11 @@ export interface TournamentStartingBracketsBody { export interface TournamentTeamMemberBody { userId: number; } + +/** POST /api/tournament/{id}/teams/{tournamentTeamId}/update-member-ign */ + +/** @lintignore */ +export interface TournamentUpdateMemberIgnBody { + userId: number; + inGameName: string; +} diff --git a/app/routes.ts b/app/routes.ts index bc1f17fd8..955159058 100644 --- a/app/routes.ts +++ b/app/routes.ts @@ -323,6 +323,10 @@ export default [ "/tournament/:id/teams/:teamId/remove-member", "features/api-public/routes/tournament.$id.teams.$teamId.remove-member.ts", ), + route( + "/tournament/:id/teams/:teamId/update-member-ign", + "features/api-public/routes/tournament.$id.teams.$teamId.update-member-ign.ts", + ), ]), ]), diff --git a/e2e/api-public.spec.ts b/e2e/api-public.spec.ts index d837add1b..91435e1af 100644 --- a/e2e/api-public.spec.ts +++ b/e2e/api-public.spec.ts @@ -317,6 +317,27 @@ test.describe("Public API - Write endpoints", () => { expect(response.status()).toBe(200); }); + test("updates member IGN via API", async ({ page }) => { + await seed(page); + await impersonate(page, ADMIN_ID); + + const token = await generateWriteToken(page); + + const response = await page.request.fetch( + `/api/tournament/${ITZ_TOURNAMENT_ID}/teams/${ITZ_TEAM_ID}/update-member-ign`, + { + method: "POST", + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + data: { userId: ADMIN_ID, inGameName: "NewName#9999" }, + }, + ); + + expect(response.status()).toBe(200); + }); + test("returns 400 when user is not the organizer of this tournament", async ({ page, }) => {