diff options
| author | nsfisis <nsfisis@gmail.com> | 2025-03-08 10:13:05 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2025-03-08 10:13:05 +0900 |
| commit | 8dbdf96e674c1e26d7c98af8d0608f30bc1bf166 (patch) | |
| tree | 7c82476f6bbbc71d72ab7e71e39559eca197fd95 /frontend/app/api | |
| parent | 54316868c3bec1ff9b04643dfe6c13cf56bf3246 (diff) | |
| parent | 1e6df136d8202c8adf65948527f4c3e7583b338c (diff) | |
| download | phperkaigi-2025-albatross-8dbdf96e674c1e26d7c98af8d0608f30bc1bf166.tar.gz phperkaigi-2025-albatross-8dbdf96e674c1e26d7c98af8d0608f30bc1bf166.tar.zst phperkaigi-2025-albatross-8dbdf96e674c1e26d7c98af8d0608f30bc1bf166.zip | |
Merge branch 'phperkaigi-2025-ws-to-polling' into phperkaigi-2025
Diffstat (limited to 'frontend/app/api')
| -rw-r--r-- | frontend/app/api/client.ts | 120 | ||||
| -rw-r--r-- | frontend/app/api/schema.d.ts | 499 |
2 files changed, 619 insertions, 0 deletions
diff --git a/frontend/app/api/client.ts b/frontend/app/api/client.ts new file mode 100644 index 0000000..3d8b5dd --- /dev/null +++ b/frontend/app/api/client.ts @@ -0,0 +1,120 @@ +import createClient from "openapi-fetch"; +import { createContext } from "react"; +import type { paths } from "./schema"; + +const apiClient = createClient<paths>({ + baseUrl: + process.env.NODE_ENV === "development" + ? "http://localhost:8003/phperkaigi/2025/code-battle/api/" + : "http://api-server/phperkaigi/2025/code-battle/api/", +}); + +export async function apiPostLogin(username: string, password: string) { + const { data, error } = await apiClient.POST("/login", { + body: { + username, + password, + }, + }); + if (error) throw new Error(error.message); + return data; +} + +export async function apiGetGames(token: string) { + const { data, error } = await apiClient.GET("/games", { + params: { + header: { Authorization: `Bearer ${token}` }, + }, + }); + if (error) throw new Error(error.message); + return data; +} + +export async function apiGetGame(token: string, gameId: number) { + const { data, error } = await apiClient.GET("/games/{game_id}", { + params: { + header: { Authorization: `Bearer ${token}` }, + path: { game_id: gameId }, + }, + }); + if (error) throw new Error(error.message); + return data; +} + +export async function apiGetGamePlayLatestState(token: string, gameId: number) { + const { data, error } = await apiClient.GET( + "/games/{game_id}/play/latest_state", + { + params: { + header: { Authorization: `Bearer ${token}` }, + path: { game_id: gameId }, + }, + }, + ); + if (error) throw new Error(error.message); + return data; +} + +export async function apiPostGamePlayCode( + token: string, + gameId: number, + code: string, +) { + const { error } = await apiClient.POST("/games/{game_id}/play/code", { + params: { + header: { Authorization: `Bearer ${token}` }, + path: { game_id: gameId }, + }, + body: { code }, + }); + if (error) throw new Error(error.message); +} + +export async function apiPostGamePlaySubmit( + token: string, + gameId: number, + code: string, +) { + const { data, error } = await apiClient.POST("/games/{game_id}/play/submit", { + params: { + header: { Authorization: `Bearer ${token}` }, + path: { game_id: gameId }, + }, + body: { code }, + }); + if (error) throw new Error(error.message); + return data; +} + +export async function apiGetGameWatchRanking(token: string, gameId: number) { + const { data, error } = await apiClient.GET( + "/games/{game_id}/watch/ranking", + { + params: { + header: { Authorization: `Bearer ${token}` }, + path: { game_id: gameId }, + }, + }, + ); + if (error) throw new Error(error.message); + return data; +} + +export async function apiGetGameWatchLatestStates( + token: string, + gameId: number, +) { + const { data, error } = await apiClient.GET( + "/games/{game_id}/watch/latest_states", + { + params: { + header: { Authorization: `Bearer ${token}` }, + path: { game_id: gameId }, + }, + }, + ); + if (error) throw new Error(error.message); + return data; +} + +export const ApiAuthTokenContext = createContext<string>(""); diff --git a/frontend/app/api/schema.d.ts b/frontend/app/api/schema.d.ts new file mode 100644 index 0000000..cec5661 --- /dev/null +++ b/frontend/app/api/schema.d.ts @@ -0,0 +1,499 @@ +/** + * This file was auto-generated by openapi-typescript. + * Do not make direct changes to the file. + */ + +export interface paths { + "/login": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** User login */ + post: operations["postLogin"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/games": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List games */ + get: operations["getGames"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/games/{game_id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get a game */ + get: operations["getGame"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/games/{game_id}/play/latest_state": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get the latest execution result for player */ + get: operations["getGamePlayLatestState"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/games/{game_id}/play/code": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Post the latest code */ + post: operations["postGamePlayCode"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/games/{game_id}/play/submit": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Submit the answer */ + post: operations["postGamePlaySubmit"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/games/{game_id}/watch/ranking": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get the latest player ranking */ + get: operations["getGameWatchRanking"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/games/{game_id}/watch/latest_states": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get all the latest game states of the main players */ + get: operations["getGameWatchLatestStates"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; +} +export type webhooks = Record<string, never>; +export interface components { + schemas: { + Error: { + /** @example Invalid request */ + message: string; + }; + User: { + /** @example 123 */ + user_id: number; + /** @example john */ + username: string; + /** @example John Doe */ + display_name: string; + /** @example /images/john.jpg */ + icon_path?: string; + /** @example false */ + is_admin: boolean; + }; + Game: { + /** @example 1 */ + game_id: number; + /** + * @example 1v1 + * @enum {string} + */ + game_type: "1v1" | "multiplayer"; + /** @example true */ + is_public: boolean; + /** @example Game 1 */ + display_name: string; + /** @example 360 */ + duration_seconds: number; + /** @example 946684800 */ + started_at?: number; + problem: components["schemas"]["Problem"]; + main_players: components["schemas"]["User"][]; + }; + Problem: { + /** @example 1 */ + problem_id: number; + /** @example Problem 1 */ + title: string; + /** @example This is a problem */ + description: string; + /** @example echo 'hello world'; */ + sample_code: string; + }; + /** + * @example success + * @enum {string} + */ + ExecutionStatus: "none" | "running" | "success" | "wrong_answer" | "timeout" | "runtime_error" | "internal_error"; + LatestGameState: { + /** @example echo 'hello world'; */ + code: string; + /** @example 100 */ + score: number | null; + status: components["schemas"]["ExecutionStatus"]; + }; + RankingEntry: { + player: components["schemas"]["User"]; + /** @example 100 */ + score: number; + }; + }; + responses: { + /** @description Bad request */ + BadRequest: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Unauthorized */ + Unauthorized: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Forbidden */ + Forbidden: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + /** @description Not found */ + NotFound: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Error"]; + }; + }; + }; + parameters: { + header_authorization: string; + path_game_id: number; + }; + requestBodies: never; + headers: never; + pathItems: never; +} +export type $defs = Record<string, never>; +export interface operations { + postLogin: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** @example john */ + username: string; + /** @example password123 */ + password: string; + }; + }; + }; + responses: { + /** @description Successfully authenticated */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example xxxxx.xxxxx.xxxxx */ + token: string; + }; + }; + }; + 401: components["responses"]["Unauthorized"]; + }; + }; + getGames: { + parameters: { + query?: never; + header: { + Authorization: components["parameters"]["header_authorization"]; + }; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description List of games */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + games: components["schemas"]["Game"][]; + }; + }; + }; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + }; + }; + getGame: { + parameters: { + query?: never; + header: { + Authorization: components["parameters"]["header_authorization"]; + }; + path: { + game_id: components["parameters"]["path_game_id"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description A game */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + game: components["schemas"]["Game"]; + }; + }; + }; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 404: components["responses"]["NotFound"]; + }; + }; + getGamePlayLatestState: { + parameters: { + query?: never; + header: { + Authorization: components["parameters"]["header_authorization"]; + }; + path: { + game_id: components["parameters"]["path_game_id"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Your latest game state */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + state: components["schemas"]["LatestGameState"]; + }; + }; + }; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 404: components["responses"]["NotFound"]; + }; + }; + postGamePlayCode: { + parameters: { + query?: never; + header: { + Authorization: components["parameters"]["header_authorization"]; + }; + path: { + game_id: components["parameters"]["path_game_id"]; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** @example echo 'hello world'; */ + code: string; + }; + }; + }; + responses: { + /** @description Successfully updated */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 404: components["responses"]["NotFound"]; + }; + }; + postGamePlaySubmit: { + parameters: { + query?: never; + header: { + Authorization: components["parameters"]["header_authorization"]; + }; + path: { + game_id: components["parameters"]["path_game_id"]; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** @example echo 'hello world'; */ + code: string; + }; + }; + }; + responses: { + /** @description Successfully submitted */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 404: components["responses"]["NotFound"]; + }; + }; + getGameWatchRanking: { + parameters: { + query?: never; + header: { + Authorization: components["parameters"]["header_authorization"]; + }; + path: { + game_id: components["parameters"]["path_game_id"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Player ranking */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ranking: components["schemas"]["RankingEntry"][]; + }; + }; + }; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 404: components["responses"]["NotFound"]; + }; + }; + getGameWatchLatestStates: { + parameters: { + query?: never; + header: { + Authorization: components["parameters"]["header_authorization"]; + }; + path: { + game_id: components["parameters"]["path_game_id"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description All the latest game states of the main players */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + states: { + [key: string]: components["schemas"]["LatestGameState"]; + }; + }; + }; + }; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 404: components["responses"]["NotFound"]; + }; + }; +} |
