From 7a8f6f26138ec78a32101934d62422b0d7b23d22 Mon Sep 17 00:00:00 2001 From: Jonas Tranberg Date: Sat, 25 May 2024 20:54:24 +0200 Subject: [PATCH] api work --- src/api/endpoints/authentication.ts | 18 +++--- src/api/endpoints/game.ts | 64 +++++-------------- src/api/endpoints/stats.ts | 17 ++--- src/api/models/card.ts | 7 ++ src/api/models/game.ts | 21 ++++++ src/api/models/location.ts | 5 ++ src/api/models/player.ts | 6 ++ src/models/card.ts | 12 ++-- src/models/chug.ts | 3 +- src/models/game.ts | 24 +++++++ src/models/location.ts | 7 ++ src/stores/game.mapper.ts | 2 +- src/stores/game.ts | 34 ++++++++-- src/views/Login/Continue/index.tsx | 6 +- src/views/Login/New/components/PlayerItem.tsx | 9 ++- src/views/Login/New/contexts/newGame.tsx | 2 + 16 files changed, 152 insertions(+), 85 deletions(-) create mode 100644 src/api/models/card.ts create mode 100644 src/api/models/game.ts create mode 100644 src/api/models/location.ts create mode 100644 src/api/models/player.ts create mode 100644 src/models/game.ts create mode 100644 src/models/location.ts diff --git a/src/api/endpoints/authentication.ts b/src/api/endpoints/authentication.ts index 9fbcb0a..010b47b 100644 --- a/src/api/endpoints/authentication.ts +++ b/src/api/endpoints/authentication.ts @@ -1,11 +1,11 @@ import client from "../client"; -export interface ILoginRequest { +export interface LoginRequest { username: string; password: string; } -export interface ILoginResponse { +export interface LoginResponse { token: string; id: number; image: string; @@ -14,13 +14,13 @@ export interface ILoginResponse { export async function login( username: string, password: string, -): Promise { - const data: ILoginRequest = { +): Promise { + const data: LoginRequest = { username: username, password: password, }; - const response = await client.post("/api-token-auth/", data, { + const response = await client.post("/api-token-auth/", data, { headers: { Authorization: "", // Skip auth interceptor }, @@ -29,7 +29,7 @@ export async function login( return response.data; } -export interface ICreateUserResponse { +export interface CreateUserResponse { token: string; id: number; image: string; @@ -38,13 +38,13 @@ export interface ICreateUserResponse { export async function createUser( username: string, password: string, -): Promise { - const data: ILoginRequest = { +): Promise { + const data: LoginRequest = { username: username, password: password, }; - const response = await client.post("/api/users/", data, { + const response = await client.post("/api/users/", data, { headers: { Authorization: "", // Skip auth interceptor }, diff --git a/src/api/endpoints/game.ts b/src/api/endpoints/game.ts index d6f370c..f9acecd 100644 --- a/src/api/endpoints/game.ts +++ b/src/api/endpoints/game.ts @@ -1,6 +1,7 @@ import client from "../client"; +import { Game } from "../models/game"; -export interface IPostStartRequest { +export interface PostStartRequest { tokens: string[]; official: boolean; } @@ -8,8 +9,8 @@ export interface IPostStartRequest { export async function postStart( tokens: string[], official: boolean, -): Promise { - const data: IPostStartRequest = { +): Promise { + const data: PostStartRequest = { tokens: tokens, official: official, }; @@ -17,49 +18,18 @@ export async function postStart( return (await client.post("/api/games/", data)).data; } -export interface IGameState { - id: number; - start_datetime: string; - end_datetime?: string; - description: string; - official: boolean; - dnf: boolean; - shuffle_indices: number[]; - cards: ICard[]; - players: IPlayer[]; - sips_per_beer: number; - has_ended: boolean; - description_html: string; - location: ILocation; - image?: string; - token: string; -} - -export interface IPlayer { - id: number; - username: string; - is_superuser: boolean; - image_url: string; -} - -export interface ILocation { - latitude: number; - longitude: number; - accuracy: number; -} - -export interface ICard { - value: number; - suit: string; - start_delta_ms?: number; - chug_start_start_delta_ms?: number; - chug_end_start_delta_ms?: number; -} - -export async function postUpdate(gameState: IGameState): Promise { +export async function postUpdate( + token: string, + gameState: Game, +): Promise { return await client.post( `/api/games/${gameState.id}/update_state/`, gameState, + { + headers: { + Authorization: `GameToken ${token}`, + }, + }, ); } @@ -78,7 +48,7 @@ export async function deletePhoto(gameId: number): Promise { return await client.delete(`/api/games/${gameId}/delete_image/`); } -export interface IResumableGame { +export interface ResumableGame { id: number; start_datetime: string; players: { @@ -90,8 +60,8 @@ export interface IResumableGame { export async function getResumableGames( token: string, -): Promise { - const response = await client.get("/api/games/resumable/", { +): Promise { + const response = await client.get("/api/games/resumable/", { headers: { Authorization: `Token ${token}`, }, @@ -103,7 +73,7 @@ export async function getResumableGames( export async function postResumeGame( token: string, gameId: number, -): Promise { +): Promise { const response = await client.post( `/api/games/${gameId}/resume/`, {}, diff --git a/src/api/endpoints/stats.ts b/src/api/endpoints/stats.ts index 9a7ad0d..955042c 100644 --- a/src/api/endpoints/stats.ts +++ b/src/api/endpoints/stats.ts @@ -1,6 +1,6 @@ import client from "../client"; -export interface IRankedCardResponse { +export interface RankedCardResponse { user_id: number; user_username: string; user_image: string; @@ -8,13 +8,12 @@ export interface IRankedCardResponse { ranking_value: string; } -export async function getRankedCards(): Promise { - const response = - await client.get("/api/ranked_cards/"); +export async function getRankedCards(): Promise { + const response = await client.get("/api/ranked_cards/"); return response.data; } -export interface IUserStatsResponse { +export interface UserStatsResponse { season_number: number; total_games: number; total_time_played_seconds: number; @@ -29,11 +28,7 @@ export interface IUserStatsResponse { average_chug_time_seconds: number; } -export async function getUserStats( - userId: number, -): Promise { - const response = await client.get( - `/api/stats/${userId}/`, - ); +export async function getUserStats(userId: number): Promise { + const response = await client.get(`/api/stats/${userId}/`); return response.data; } diff --git a/src/api/models/card.ts b/src/api/models/card.ts new file mode 100644 index 0000000..e6f92ef --- /dev/null +++ b/src/api/models/card.ts @@ -0,0 +1,7 @@ +export interface Card { + value: number; + suit: string; + start_delta_ms?: number; + chug_start_start_delta_ms?: number; + chug_end_start_delta_ms?: number; +} diff --git a/src/api/models/game.ts b/src/api/models/game.ts new file mode 100644 index 0000000..77b300d --- /dev/null +++ b/src/api/models/game.ts @@ -0,0 +1,21 @@ +import { Card } from "./card"; + +export interface Game { + start_datetime: string; + official: boolean; + player_names: string[]; + cards: Card[]; + + has_ended: boolean; + description?: string; + dnf: boolean; + + dnf_player_ids: number[]; + + id: number; + player_ids: number[]; + token: string; + shuffle_indices: number[]; + + location?: Location; +} diff --git a/src/api/models/location.ts b/src/api/models/location.ts new file mode 100644 index 0000000..72b3a91 --- /dev/null +++ b/src/api/models/location.ts @@ -0,0 +1,5 @@ +export interface Location { + latitude: number; + longitude: number; + accuracy: number; +} diff --git a/src/api/models/player.ts b/src/api/models/player.ts new file mode 100644 index 0000000..df3e09b --- /dev/null +++ b/src/api/models/player.ts @@ -0,0 +1,6 @@ +export interface Player { + id: number; + username: string; + is_superuser: boolean; + image_url: string; +} diff --git a/src/models/card.ts b/src/models/card.ts index 2bae65d..adfd55b 100644 --- a/src/models/card.ts +++ b/src/models/card.ts @@ -7,6 +7,10 @@ type CardSuit = (typeof CardSuits)[number]; interface Card { value: CardValue; suit: CardSuit; + + start_delta_ms?: number; + chug_start_start_delta_ms?: number; + chug_end_start_delta_ms?: number; } const getCardASCIISymbol = (card: Card): string => { @@ -71,11 +75,11 @@ const getCardImageURI = (card: Card): string => { }; export { + CardSuits, + CardValues, getCardASCIISymbol, - getCardSuitColor, getCardImageURI, + getCardSuitColor, getCardSuitName, - CardValues, - CardSuits, }; -export type { Card, CardValue, CardSuit }; +export type { Card, CardSuit, CardValue }; diff --git a/src/models/chug.ts b/src/models/chug.ts index 69d4f27..89bc721 100644 --- a/src/models/chug.ts +++ b/src/models/chug.ts @@ -1,5 +1,6 @@ interface Chug { - duration: number; + start_start_delta_ms: number; + end_start_delta_ms: number; } export type { Chug }; diff --git a/src/models/game.ts b/src/models/game.ts new file mode 100644 index 0000000..12fc822 --- /dev/null +++ b/src/models/game.ts @@ -0,0 +1,24 @@ +import { Card } from "./card"; +import { Location } from "./location"; + +interface Game { + start_datetime: string; + official: boolean; + player_names: string[]; + cards: Card[]; + + has_ended: boolean; + description?: string; + dnf: boolean; + + dnf_player_ids: number[]; + + id?: number; + player_ids?: number[]; + token?: string; + shuffle_indices?: number[]; + + location?: Location; +} + +export type { Game }; diff --git a/src/models/location.ts b/src/models/location.ts new file mode 100644 index 0000000..cf4cad9 --- /dev/null +++ b/src/models/location.ts @@ -0,0 +1,7 @@ +interface Location { + latitude: number; + longitude: number; + accuracy: number; +} + +export type { Location }; diff --git a/src/stores/game.mapper.ts b/src/stores/game.mapper.ts index 396819c..bc9a610 100644 --- a/src/stores/game.mapper.ts +++ b/src/stores/game.mapper.ts @@ -45,4 +45,4 @@ const mapToLocal = (state: IGameState): GameState => { }; }; -export { mapToRemote, mapToLocal }; +export { mapToLocal, mapToRemote }; diff --git a/src/stores/game.ts b/src/stores/game.ts index e1453cb..1d62ad4 100644 --- a/src/stores/game.ts +++ b/src/stores/game.ts @@ -2,7 +2,6 @@ import { create } from "zustand"; import { persist } from "zustand/middleware"; import * as GameAPI from "../api/endpoints/game"; import { Card, CardValues } from "../models/card"; -import { Chug } from "../models/chug"; import { Player } from "../models/player"; import { GenerateDeck, @@ -33,7 +32,6 @@ interface GameState { players: Player[]; - chugs: Chug[]; draws: Card[]; } @@ -68,7 +66,6 @@ const initialState: GameState = { players: [], - chugs: [], draws: [], }; @@ -98,7 +95,7 @@ const useGame = create()( let id; let token; - let shuffleIndices; + let shuffleIndices: number[]; let gameStartTimestamp = Date.now(); let turnStartTimestamp = Date.now(); @@ -115,8 +112,6 @@ const useGame = create()( shuffleIndices = resp.shuffle_indices; } - const deck = GenerateDeck(shuffleIndices, players.length); - set({ id: id, offline: options.offline, @@ -127,7 +122,6 @@ const useGame = create()( sipsInABeer: options.sipsInABeer, numberOfRounds: options.numberOfRounds, players: players, - chugs: [], draws: [], }); @@ -139,6 +133,7 @@ const useGame = create()( const state = useGame.getState(); + // TODO: this can be optimized const deck = GenerateDeck(state.shuffleIndices, state.players.length); if (deck.length === 0) { @@ -151,6 +146,8 @@ const useGame = create()( state.draws.length, ); + card.start_delta_ms = Date.now() - state.gameStartTimestamp; + const draws = [...state.draws, card]; const turnStartTimestamp = Date.now(); @@ -169,6 +166,29 @@ const useGame = create()( set(update); + if (state.offline) { + return card; + } + + GameAPI.postUpdate(state.token as string, { + id: state.id as number, + token: state.token as string, + start_datetime: new Date(state.gameStartTimestamp).toISOString(), + + player_names: state.players.map((player) => player.username), + player_ids: state.players.map((player) => player.id as number), + + official: !state.offline, + shuffle_indices: state.shuffleIndices, + has_ended: done, + + cards: [...state.draws, card], + + // TODO: Implement + dnf_player_ids: [], + dnf: false, + }); + return card; }, diff --git a/src/views/Login/Continue/index.tsx b/src/views/Login/Continue/index.tsx index 0232b8a..ea9ebd0 100644 --- a/src/views/Login/Continue/index.tsx +++ b/src/views/Login/Continue/index.tsx @@ -30,9 +30,9 @@ const ContinueGameView: FunctionComponent = () => { })); const [player, setPlayer] = useState(null); - const [resumableGames, setResumableGames] = useState< - GameAPI.IResumableGame[] - >([]); + const [resumableGames, setResumableGames] = useState( + [], + ); const [selectedGame, setSelectedGame] = useState( null, ); diff --git a/src/views/Login/New/components/PlayerItem.tsx b/src/views/Login/New/components/PlayerItem.tsx index e339a7a..6fff999 100644 --- a/src/views/Login/New/components/PlayerItem.tsx +++ b/src/views/Login/New/components/PlayerItem.tsx @@ -32,13 +32,17 @@ const PlayerItem: FunctionComponent = (props) => { const isOffline = newGame.offline; const login = async () => { + if (newGame.offline) { + return; + } + if (!player.username || !player.password) { return; } const isPlayerWithSameUsernameLoggedIn = newGame.players.some( (p) => - p.username.toLowerCase() === player.username.toLowerCase() && p.ready, + p.username?.toLowerCase() === player.username?.toLowerCase() && p.ready, ); if (isPlayerWithSameUsernameLoggedIn) { @@ -55,6 +59,7 @@ const PlayerItem: FunctionComponent = (props) => { newGame.setPlayer(props.index, { ...player, + id: resp.id, token: resp.token, avatar: resp.image, ready: true, @@ -133,7 +138,7 @@ const PlayerItem: FunctionComponent = (props) => { onChange={(e) => updateUsername(e.target.value)} disabled={(player.ready && !isOffline) || disabled} onKeyDown={(e) => { - if (e.key === "Enter" && isOffline) { + if (e.key === "Enter") { login(); } }} diff --git a/src/views/Login/New/contexts/newGame.tsx b/src/views/Login/New/contexts/newGame.tsx index a3da054..67784d9 100644 --- a/src/views/Login/New/contexts/newGame.tsx +++ b/src/views/Login/New/contexts/newGame.tsx @@ -7,6 +7,8 @@ import React, { } from "react"; interface Player { + id?: number; + username: string; ready: boolean;