aboutsummaryrefslogtreecommitdiffhomepage
path: root/frontend/app/api
diff options
context:
space:
mode:
Diffstat (limited to 'frontend/app/api')
-rw-r--r--frontend/app/api/client.ts120
-rw-r--r--frontend/app/api/schema.d.ts499
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"];
+ };
+ };
+}