Add function to fetch player information (#2010)

## Description:

This pull request introduces the fetchPlayerById() function together
with its associated schema components.

It represents one part of a series of split pull requests related to the
PlayerInfoModal (Player Profile). Subsequent pull requests will address
UI implementation and additional features.

(origin pr:https://github.com/openfrontio/OpenFrontIO/pull/1758)

## Please complete the following:

- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced

## Please put your Discord username so you can be contacted if a bug or
regression is found:

aotumuri

---------

Co-authored-by: evanpelle <evanpelle@gmail.com>
This commit is contained in:
Aotumuri
2025-09-15 07:15:50 +09:00
committed by GitHub
parent 835c8d97ce
commit a4127960b7
3 changed files with 81 additions and 2 deletions
+41
View File
@@ -1,6 +1,8 @@
import { decodeJwt } from "jose";
import { z } from "zod";
import {
PlayerProfile,
PlayerProfileSchema,
RefreshResponseSchema,
TokenPayload,
TokenPayloadSchema,
@@ -268,3 +270,42 @@ export async function getUserMe(): Promise<UserMeResponse | false> {
return false;
}
}
export async function fetchPlayerById(
playerId: string,
): Promise<PlayerProfile | false> {
try {
const base = getApiBase();
const token = getToken();
if (!token) return false;
const url = `${base}/player/${encodeURIComponent(playerId)}`;
const res = await fetch(url, {
headers: {
Accept: "application/json",
Authorization: `Bearer ${token}`,
},
});
if (res.status !== 200) {
console.warn(
"fetchPlayerById: unexpected status",
res.status,
res.statusText,
);
return false;
}
const json = await res.json();
const parsed = PlayerProfileSchema.safeParse(json);
if (!parsed.success) {
console.warn("fetchPlayerById: Zod validation failed", parsed.error);
return false;
}
return parsed.data;
} catch (err) {
console.warn("fetchPlayerById: request failed", err);
return false;
}
}
+39 -1
View File
@@ -1,5 +1,7 @@
import { z } from "zod";
import { base64urlToUuid } from "./Base64";
import { BigIntStringSchema, PlayerStatsSchema } from "./StatsSchemas";
import { Difficulty, GameMapType, GameMode, GameType } from "./game/Game";
export const RefreshResponseSchema = z.object({
token: z.string(),
@@ -37,8 +39,8 @@ export const DiscordUserSchema = z.object({
username: z.string(),
global_name: z.string().nullable(),
discriminator: z.string(),
locale: z.string().optional(),
});
export type DiscordUser = z.infer<typeof DiscordUserSchema>;
export const UserMeResponseSchema = z.object({
user: z.object({
@@ -52,3 +54,39 @@ export const UserMeResponseSchema = z.object({
}),
});
export type UserMeResponse = z.infer<typeof UserMeResponseSchema>;
export const PlayerStatsLeafSchema = z.object({
wins: BigIntStringSchema,
losses: BigIntStringSchema,
total: BigIntStringSchema,
stats: PlayerStatsSchema,
});
export type PlayerStatsLeaf = z.infer<typeof PlayerStatsLeafSchema>;
export const PlayerStatsTreeSchema = z.partialRecord(
z.enum(GameType),
z.partialRecord(
z.enum(GameMode),
z.partialRecord(z.enum(Difficulty), PlayerStatsLeafSchema),
),
);
export type PlayerStatsTree = z.infer<typeof PlayerStatsTreeSchema>;
export const PlayerGameSchema = z.object({
gameId: z.string(),
start: z.iso.datetime(),
mode: z.enum(GameMode),
type: z.enum(GameType),
map: z.enum(GameMapType),
difficulty: z.enum(Difficulty),
clientId: z.string().optional(),
});
export type PlayerGame = z.infer<typeof PlayerGameSchema>;
export const PlayerProfileSchema = z.object({
createdAt: z.iso.datetime(),
user: DiscordUserSchema.optional(),
games: PlayerGameSchema.array(),
stats: PlayerStatsTreeSchema,
});
export type PlayerProfile = z.infer<typeof PlayerProfileSchema>;
+1 -1
View File
@@ -88,7 +88,7 @@ export const OTHER_INDEX_CAPTURE = 2; // Structures captured
export const OTHER_INDEX_LOST = 3; // Structures/warships destroyed/captured by others
export const OTHER_INDEX_UPGRADE = 4; // Structures upgraded
const BigIntStringSchema = z.preprocess((val) => {
export const BigIntStringSchema = z.preprocess((val) => {
if (typeof val === "string" && /^-?\d+$/.test(val)) return BigInt(val);
if (typeof val === "bigint") return val;
return val;