From cd65f06c5ff732a513b6c3dcb1b3d8c52d176604 Mon Sep 17 00:00:00 2001 From: glax Date: Mon, 26 May 2025 02:37:43 +0200 Subject: [PATCH] Add API code --- src/api/endpoints/Actions.tsx | 30 +++++++++++ src/api/endpoints/Data.tsx | 27 ++++++++++ src/api/endpoints/TimeTrack.tsx | 18 +++++++ src/api/fetchApi.tsx | 89 +++++++++++++++++++++++++++++++++ src/api/types/IGame.ts | 4 ++ src/api/types/IPlayer.ts | 7 +++ src/api/types/ITrackedTime.ts | 4 ++ 7 files changed, 179 insertions(+) create mode 100644 src/api/endpoints/Actions.tsx create mode 100644 src/api/endpoints/Data.tsx create mode 100644 src/api/endpoints/TimeTrack.tsx create mode 100644 src/api/fetchApi.tsx create mode 100644 src/api/types/IGame.ts create mode 100644 src/api/types/IPlayer.ts create mode 100644 src/api/types/ITrackedTime.ts diff --git a/src/api/endpoints/Actions.tsx b/src/api/endpoints/Actions.tsx new file mode 100644 index 0000000..6e34f3d --- /dev/null +++ b/src/api/endpoints/Actions.tsx @@ -0,0 +1,30 @@ +import {deleteData, postData, putData} from "../fetchApi.tsx"; +import type IPlayer from "../types/IPlayer.ts"; + +export function AddPlayer(apiUri: string, steamId: bigint){ + return putData(`${apiUri}/Actions/Player/${steamId}`) as Promise; +} + +export function DeletePlayer(apiUri: string, steamId: bigint){ + return deleteData(`${apiUri}/Actions/Player/${steamId}`) as Promise; +} + +export function UpdatePlayerData(apiUri: string, steamId: bigint){ + return postData(`${apiUri}/Actions/Update/Player/${steamId}/All`) as Promise; +} + +export function UpdatePlayerInfo(apiUri: string, steamId: bigint){ + return postData(`${apiUri}/Actions/Update/Player/${steamId}/Info`) as Promise; +} + +export function UpdatePlayerOwnedGames(apiUri: string, steamId: bigint){ + return postData(`${apiUri}/Actions/Update/Player/${steamId}/OwnedGames`) as Promise; +} + +export function UpdatePlayerTimeTracked(apiUri: string, steamId: bigint){ + return postData(`${apiUri}/Actions/Update/Player/${steamId}/TimeTracked`) as Promise; +} + +export function UpdateAll(apiUri: string){ + return postData(`${apiUri}/Actions/All`) as Promise; +} \ No newline at end of file diff --git a/src/api/endpoints/Data.tsx b/src/api/endpoints/Data.tsx new file mode 100644 index 0000000..13009ea --- /dev/null +++ b/src/api/endpoints/Data.tsx @@ -0,0 +1,27 @@ +import {getData} from "../fetchApi.tsx"; +import type IPlayer from "../types/IPlayer.ts"; +import type IGame from "../types/IGame.ts"; + +export function GetPlayers(apiUri: string){ + return getData(`${apiUri}/Data/Players`) as Promise; +} + +export function GetPlayer(apiUri: string, steamdId: bigint){ + return getData(`${apiUri}/Data/Player/${steamdId}`) as Promise; +} + +export function GetGamesOfPlayer(apiUri: string, steamdId: bigint){ + return getData(`${apiUri}/Data/Player/${steamdId}/Games`) as Promise; +} + +export function GetGames(apiUri: string){ + return getData(`${apiUri}/Data/Games`) as Promise; +} + +export function GetGame(apiUri: string, appId: bigint){ + return getData(`${apiUri}/Data/Game/${appId}`) as Promise; +} + +export function GetPlayersOfGame(apiUri: string, appId: bigint){ + return getData(`${apiUri}/Data/Game/${appId}/Players`) as Promise; +} \ No newline at end of file diff --git a/src/api/endpoints/TimeTrack.tsx b/src/api/endpoints/TimeTrack.tsx new file mode 100644 index 0000000..02dc2a3 --- /dev/null +++ b/src/api/endpoints/TimeTrack.tsx @@ -0,0 +1,18 @@ +import {getData} from "../fetchApi.tsx"; +import type ITrackedTime from "../types/ITrackedTime.ts"; + +export function GetTimelines(apiUri: string, steamId: bigint){ + return getData(`${apiUri}/TimeTrack/${steamId}`) as Promise>; +} + +export function GetTimelineGame(apiUri: string, steamId: bigint, appId: bigint){ + return getData(`${apiUri}/TimeTrack/${steamId}/${appId}`) as Promise; +} + +export function GetTotal(apiUri: string, steamId: bigint){ + return getData(`${apiUri}/TimeTrack/${steamId}/Total`) as unknown as Promise; +} + +export function GetTotalPerGame(apiUri: string, steamId: bigint){ + return getData(`${apiUri}/TimeTrack/${steamId}/Total/PerGame`) as Promise>; +} diff --git a/src/api/fetchApi.tsx b/src/api/fetchApi.tsx new file mode 100644 index 0000000..a0a5ec4 --- /dev/null +++ b/src/api/fetchApi.tsx @@ -0,0 +1,89 @@ +import {createContext} from "react"; + +export const ApiUriContext = createContext(""); + +export function getData(uri: string) : Promise { + return makeRequestWrapper("GET", uri, null); +} + +export function postData(uri: string, content?: object | string | number | boolean | null) : Promise { + return makeRequestWrapper("POST", uri, content); +} + +export function deleteData(uri: string) : Promise { + return makeRequestWrapper("DELETE", uri, null) as Promise; +} + +export function patchData(uri: string, content: object | string | number | boolean) : Promise { + return makeRequestWrapper("patch", uri, content); +} + +export function putData(uri: string, content?: object | string | number | boolean | null) : Promise { + return makeRequestWrapper("PUT", uri, content); +} + +function makeRequestWrapper(method: string, uri: string, content?: object | string | number | null | boolean) : Promise{ + return makeRequest(method, uri, content) + .then((result) => result as Promise) + .catch((e) => { + console.warn(e); + return Promise.resolve(undefined); + }); +} + +let currentlyRequestedEndpoints: string[] = []; +function makeRequest(method: string, uri: string, content?: object | string | number | null | boolean) : Promise { + const id = method + uri; + if(currentlyRequestedEndpoints.find(x => x == id) != undefined) + return Promise.reject(`Already requested: ${method} ${uri}`); + currentlyRequestedEndpoints.push(id); + return fetch(uri, + { + method: method, + headers : { + 'Content-Type': 'application/json', + 'Accept': 'application/json' + }, + body: content ? JSON.stringify(content) : null + }) + .then(function(response){ + if(!response.ok){ + if(response.status === 503){ + currentlyRequestedEndpoints.splice(currentlyRequestedEndpoints.indexOf(id), 1) + let retryHeaderVal = response.headers.get("Retry-After"); + let seconds = 10; + if(retryHeaderVal === null){ + return response.text().then(text => { + seconds = parseInt(text); + return new Promise(resolve => setTimeout(resolve, seconds * 1000)) + .then(() => { + return makeRequest(method, uri, content); + }); + }); + }else { + seconds = parseInt(retryHeaderVal); + return new Promise(resolve => setTimeout(resolve, seconds * 1000)) + .then(() => { + return makeRequest(method, uri, content); + }); + } + }else + throw new Error(response.statusText); + } + let json = response.json(); + return json.then((json) => json).catch(() => null); + }) + .catch(function(err : Error){ + console.error(`Error ${method}ing Data ${uri}\n${err}`); + return Promise.reject(); + }).finally(() => currentlyRequestedEndpoints.splice(currentlyRequestedEndpoints.indexOf(id), 1)); +} + +export function isValidUri(uri: string) : boolean{ + try { + new URL(uri); + return true; + } catch (err) { + return false; + } +} \ No newline at end of file diff --git a/src/api/types/IGame.ts b/src/api/types/IGame.ts new file mode 100644 index 0000000..2952c8b --- /dev/null +++ b/src/api/types/IGame.ts @@ -0,0 +1,4 @@ +export default interface IGame { + appId: bigint, + name: string +} \ No newline at end of file diff --git a/src/api/types/IPlayer.ts b/src/api/types/IPlayer.ts new file mode 100644 index 0000000..36b6374 --- /dev/null +++ b/src/api/types/IPlayer.ts @@ -0,0 +1,7 @@ +export default interface IPlayer { + steamId: bigint, + name: string, + profileUrl: string, + avatarUrl: string, + updatedAt: Date +} \ No newline at end of file diff --git a/src/api/types/ITrackedTime.ts b/src/api/types/ITrackedTime.ts new file mode 100644 index 0000000..eb25c8e --- /dev/null +++ b/src/api/types/ITrackedTime.ts @@ -0,0 +1,4 @@ +export default interface ITrackedTime { + timeStamp: Date, + timePlayed: bigint +} \ No newline at end of file