From b8b43e1a4f455a0cf6aa6444a2107de8896dd5b6 Mon Sep 17 00:00:00 2001 From: Bruce Berrios <58147810+Bruception@users.noreply.github.com> Date: Fri, 3 Jun 2022 08:31:51 -0400 Subject: [PATCH] Re-write ape (#3064) bruception * Re-write ape * apeClient -> httpClient --- frontend/src/ts/ape/adapters/axios-adapter.ts | 107 ++++++++++++ frontend/src/ts/ape/endpoints/ape-keys.ts | 31 ++-- frontend/src/ts/ape/endpoints/configs.ts | 19 +-- frontend/src/ts/ape/endpoints/index.ts | 32 ++-- frontend/src/ts/ape/endpoints/leaderboards.ts | 34 ++-- frontend/src/ts/ape/endpoints/presets.ts | 26 +-- frontend/src/ts/ape/endpoints/psas.ts | 14 +- frontend/src/ts/ape/endpoints/quotes.ts | 49 +++--- frontend/src/ts/ape/endpoints/results.ts | 31 ++-- frontend/src/ts/ape/endpoints/users.ts | 139 +++++++-------- frontend/src/ts/ape/index.ts | 123 ++------------ frontend/src/ts/ape/types/ape.d.ts | 160 ++---------------- frontend/src/ts/db.ts | 2 +- 13 files changed, 308 insertions(+), 459 deletions(-) create mode 100644 frontend/src/ts/ape/adapters/axios-adapter.ts diff --git a/frontend/src/ts/ape/adapters/axios-adapter.ts b/frontend/src/ts/ape/adapters/axios-adapter.ts new file mode 100644 index 000000000..6b34327a2 --- /dev/null +++ b/frontend/src/ts/ape/adapters/axios-adapter.ts @@ -0,0 +1,107 @@ +import { Auth } from "../../firebase"; +import { getIdToken } from "firebase/auth"; +import axios, { AxiosRequestConfig, AxiosResponse } from "axios"; + +type AxiosClientMethod = ( + endpoint: string, + config: AxiosRequestConfig +) => Promise; + +type AxiosClientDataMethod = ( + endpoint: string, + data: any, + config: AxiosRequestConfig +) => Promise; + +type AxiosClientMethods = AxiosClientMethod & AxiosClientDataMethod; + +async function adaptRequestOptions( + options: Ape.RequestOptions +): Promise { + const currentUser = Auth.currentUser; + const idToken = currentUser && (await getIdToken(currentUser)); + + return { + params: options.searchQuery, + data: options.payload, + headers: { + ...options.headers, + Accept: "application/json", + "Content-Type": "application/json", + ...(idToken && { Authorization: `Bearer ${idToken}` }), + }, + }; +} + +function apeifyClientMethod( + clientMethod: AxiosClientMethods, + methodType: Ape.HttpMethodTypes +): Ape.HttpClientMethod { + return async ( + endpoint: string, + options: Ape.RequestOptions = {} + ): Ape.EndpointData => { + let errorMessage = ""; + + try { + const requestOptions: AxiosRequestConfig = await adaptRequestOptions( + options + ); + + let response; + if (methodType === "get" || methodType === "delete") { + response = await clientMethod(endpoint, requestOptions); + } else { + response = await clientMethod( + endpoint, + requestOptions.data, + requestOptions + ); + } + + const { message, data } = response.data as Ape.ApiResponse; + + return { + status: response.status, + message, + data, + }; + } catch (error) { + console.error(error); + + const typedError = error as Error; + errorMessage = typedError.message; + + if (axios.isAxiosError(typedError)) { + return { + status: typedError.response?.status ?? 500, + message: typedError.message, + ...typedError.response?.data, + }; + } + } + + return { + status: 500, + message: errorMessage, + }; + }; +} + +export function buildHttpClient( + baseURL: string, + timeout: number +): Ape.HttpClient { + const axiosClient = axios.create({ + baseURL, + timeout, + }); + + return { + get: apeifyClientMethod(axiosClient.get, "get"), + post: apeifyClientMethod(axiosClient.post, "post"), + put: apeifyClientMethod(axiosClient.put, "put"), + patch: apeifyClientMethod(axiosClient.patch, "patch"), + delete: apeifyClientMethod(axiosClient.delete, "delete"), + }; +} diff --git a/frontend/src/ts/ape/endpoints/ape-keys.ts b/frontend/src/ts/ape/endpoints/ape-keys.ts index 660d5a7bd..4c1f936b1 100644 --- a/frontend/src/ts/ape/endpoints/ape-keys.ts +++ b/frontend/src/ts/ape/endpoints/ape-keys.ts @@ -1,33 +1,28 @@ const BASE_PATH = "/ape-keys"; -export default function getApeKeysEndpoints( - apeClient: Ape.Client -): Ape.Endpoints["apeKeys"] { - async function get(): Ape.EndpointData { - return await apeClient.get(BASE_PATH); +export default class ApeKeys { + constructor(private httpClient: Ape.HttpClient) { + this.httpClient = httpClient; } - async function generate(name: string, enabled: boolean): Ape.EndpointData { + async get(): Ape.EndpointData { + return await this.httpClient.get(BASE_PATH); + } + + async generate(name: string, enabled: boolean): Ape.EndpointData { const payload = { name, enabled }; - return await apeClient.post(BASE_PATH, { payload }); + return await this.httpClient.post(BASE_PATH, { payload }); } - async function update( + async update( apeKeyId: string, updates: { name?: string; enabled?: boolean } ): Ape.EndpointData { const payload = { ...updates }; - return await apeClient.patch(`${BASE_PATH}/${apeKeyId}`, { payload }); + return await this.httpClient.patch(`${BASE_PATH}/${apeKeyId}`, { payload }); } - async function _delete(apeKeyId: string): Ape.EndpointData { - return await apeClient.delete(`${BASE_PATH}/${apeKeyId}`); + async delete(apeKeyId: string): Ape.EndpointData { + return await this.httpClient.delete(`${BASE_PATH}/${apeKeyId}`); } - - return { - get, - generate, - update, - delete: _delete, - }; } diff --git a/frontend/src/ts/ape/endpoints/configs.ts b/frontend/src/ts/ape/endpoints/configs.ts index d9ebe013e..82bac636a 100644 --- a/frontend/src/ts/ape/endpoints/configs.ts +++ b/frontend/src/ts/ape/endpoints/configs.ts @@ -1,18 +1,15 @@ const BASE_PATH = "/configs"; -export default function getConfigsEndpoints( - apeClient: Ape.Client -): Ape.Endpoints["configs"] { - async function get(): Ape.EndpointData { - return await apeClient.get(BASE_PATH); +export default class Configs { + constructor(private httpClient: Ape.HttpClient) { + this.httpClient = httpClient; } - async function save(config: MonkeyTypes.Config): Ape.EndpointData { - return await apeClient.patch(BASE_PATH, { payload: { config } }); + async get(): Ape.EndpointData { + return await this.httpClient.get(BASE_PATH); } - return { - get, - save, - }; + async save(config: MonkeyTypes.Config): Ape.EndpointData { + return await this.httpClient.patch(BASE_PATH, { payload: { config } }); + } } diff --git a/frontend/src/ts/ape/endpoints/index.ts b/frontend/src/ts/ape/endpoints/index.ts index b0fc7b873..e121e8c6a 100644 --- a/frontend/src/ts/ape/endpoints/index.ts +++ b/frontend/src/ts/ape/endpoints/index.ts @@ -1,19 +1,19 @@ -import getConfigsEndpoints from "./configs"; -import getLeaderboardsEndpoints from "./leaderboards"; -import getPresetsEndpoints from "./presets"; -import getPsasEndpoints from "./psas"; -import getQuotesEndpoints from "./quotes"; -import getResultsEndpoints from "./results"; -import getUsersEndpoints from "./users"; -import getApeKeysEndpoints from "./ape-keys"; +import Configs from "./configs"; +import Leaderboards from "./leaderboards"; +import Presets from "./presets"; +import Psas from "./psas"; +import Quotes from "./quotes"; +import Results from "./results"; +import Users from "./users"; +import ApeKeys from "./ape-keys"; export default { - getConfigsEndpoints, - getLeaderboardsEndpoints, - getPresetsEndpoints, - getPsasEndpoints, - getQuotesEndpoints, - getResultsEndpoints, - getUsersEndpoints, - getApeKeysEndpoints, + Configs, + Leaderboards, + Presets, + Psas, + Quotes, + Results, + Users, + ApeKeys, }; diff --git a/frontend/src/ts/ape/endpoints/leaderboards.ts b/frontend/src/ts/ape/endpoints/leaderboards.ts index 90df571d5..cb0e9cbba 100644 --- a/frontend/src/ts/ape/endpoints/leaderboards.ts +++ b/frontend/src/ts/ape/endpoints/leaderboards.ts @@ -1,11 +1,23 @@ const BASE_PATH = "/leaderboards"; -export default function getLeaderboardsEndpoints( - apeClient: Ape.Client -): Ape.Endpoints["leaderboards"] { - async function get( - query: Ape.EndpointTypes.LeadeboardQueryWithPagination - ): Ape.EndpointData { +interface LeaderboardQuery { + language: string; + mode: MonkeyTypes.Mode; + mode2: string | number; + isDaily?: boolean; +} + +interface LeadeboardQueryWithPagination extends LeaderboardQuery { + skip?: number; + limit?: number; +} + +export default class Leaderboards { + constructor(private httpClient: Ape.HttpClient) { + this.httpClient = httpClient; + } + + async get(query: LeadeboardQueryWithPagination): Ape.EndpointData { const { language, mode, mode2, isDaily, skip = 0, limit = 50 } = query; const searchQuery = { @@ -18,12 +30,10 @@ export default function getLeaderboardsEndpoints( const endpointPath = `${BASE_PATH}/${isDaily ? "daily" : ""}`; - return await apeClient.get(endpointPath, { searchQuery }); + return await this.httpClient.get(endpointPath, { searchQuery }); } - async function getRank( - query: Ape.EndpointTypes.LeaderboardQuery - ): Ape.EndpointData { + async getRank(query: LeaderboardQuery): Ape.EndpointData { const { language, mode, mode2, isDaily } = query; const searchQuery = { @@ -34,8 +44,6 @@ export default function getLeaderboardsEndpoints( const endpointPath = `${BASE_PATH}${isDaily ? "/daily" : ""}/rank`; - return await apeClient.get(endpointPath, { searchQuery }); + return await this.httpClient.get(endpointPath, { searchQuery }); } - - return { get, getRank }; } diff --git a/frontend/src/ts/ape/endpoints/presets.ts b/frontend/src/ts/ape/endpoints/presets.ts index a5b74f0b4..cfefb5c91 100644 --- a/frontend/src/ts/ape/endpoints/presets.ts +++ b/frontend/src/ts/ape/endpoints/presets.ts @@ -1,13 +1,15 @@ const BASE_PATH = "/presets"; -export default function getPresetsEndpoints( - apeClient: Ape.Client -): Ape.Endpoints["presets"] { - async function get(): Ape.EndpointData { - return await apeClient.get(BASE_PATH); +export default class Presets { + constructor(private httpClient: Ape.HttpClient) { + this.httpClient = httpClient; } - async function add( + async get(): Ape.EndpointData { + return await this.httpClient.get(BASE_PATH); + } + + async add( presetName: string, configChanges: MonkeyTypes.ConfigChanges ): Ape.EndpointData { @@ -16,10 +18,10 @@ export default function getPresetsEndpoints( config: configChanges, }; - return await apeClient.post(BASE_PATH, { payload }); + return await this.httpClient.post(BASE_PATH, { payload }); } - async function edit( + async edit( presetId: string, presetName: string, configChanges: MonkeyTypes.ConfigChanges @@ -30,12 +32,10 @@ export default function getPresetsEndpoints( config: configChanges, }; - return await apeClient.patch(BASE_PATH, { payload }); + return await this.httpClient.patch(BASE_PATH, { payload }); } - async function _delete(presetId: string): Ape.EndpointData { - return await apeClient.delete(`${BASE_PATH}/${presetId}`); + async delete(presetId: string): Ape.EndpointData { + return await this.httpClient.delete(`${BASE_PATH}/${presetId}`); } - - return { get, add, edit, delete: _delete }; } diff --git a/frontend/src/ts/ape/endpoints/psas.ts b/frontend/src/ts/ape/endpoints/psas.ts index 21605fd74..9e8065bd5 100644 --- a/frontend/src/ts/ape/endpoints/psas.ts +++ b/frontend/src/ts/ape/endpoints/psas.ts @@ -2,16 +2,16 @@ import { CLIENT_VERSION } from "../../version"; const BASE_PATH = "/psas"; -export default function getPsasEndpoints( - apeClient: Ape.Client -): Ape.Endpoints["psas"] { - async function get(): Ape.EndpointData { - return await apeClient.get(BASE_PATH, { +export default class Psas { + constructor(private httpClient: Ape.HttpClient) { + this.httpClient = httpClient; + } + + async get(): Ape.EndpointData { + return await this.httpClient.get(BASE_PATH, { headers: { "Client-Version": CLIENT_VERSION, }, }); } - - return { get }; } diff --git a/frontend/src/ts/ape/endpoints/quotes.ts b/frontend/src/ts/ape/endpoints/quotes.ts index 29f6173e8..f27d72a63 100644 --- a/frontend/src/ts/ape/endpoints/quotes.ts +++ b/frontend/src/ts/ape/endpoints/quotes.ts @@ -1,13 +1,15 @@ const BASE_PATH = "/quotes"; -export default function getQuotesEndpoints( - apeClient: Ape.Client -): Ape.Endpoints["quotes"] { - async function get(): Ape.EndpointData { - return await apeClient.get(BASE_PATH); +export default class Quotes { + constructor(private httpClient: Ape.HttpClient) { + this.httpClient = httpClient; } - async function submit( + async get(): Ape.EndpointData { + return await this.httpClient.get(BASE_PATH); + } + + async submit( text: string, source: string, language: string, @@ -20,10 +22,10 @@ export default function getQuotesEndpoints( captcha, }; - return await apeClient.post(BASE_PATH, { payload }); + return await this.httpClient.post(BASE_PATH, { payload }); } - async function approveSubmission( + async approveSubmission( quoteSubmissionId: string, editText?: string, editSource?: string @@ -34,38 +36,35 @@ export default function getQuotesEndpoints( editSource, }; - return await apeClient.post(`${BASE_PATH}/approve`, { payload }); + return await this.httpClient.post(`${BASE_PATH}/approve`, { payload }); } - async function rejectSubmission(quoteSubmissionId: string): Ape.EndpointData { - return await apeClient.post(`${BASE_PATH}/reject`, { + async rejectSubmission(quoteSubmissionId: string): Ape.EndpointData { + return await this.httpClient.post(`${BASE_PATH}/reject`, { payload: { quoteId: quoteSubmissionId }, }); } - async function getRating(quote: MonkeyTypes.Quote): Ape.EndpointData { + async getRating(quote: MonkeyTypes.Quote): Ape.EndpointData { const searchQuery = { quoteId: quote.id, language: quote.language, }; - return await apeClient.get(`${BASE_PATH}/rating`, { searchQuery }); + return await this.httpClient.get(`${BASE_PATH}/rating`, { searchQuery }); } - async function addRating( - quote: MonkeyTypes.Quote, - rating: number - ): Ape.EndpointData { + async addRating(quote: MonkeyTypes.Quote, rating: number): Ape.EndpointData { const payload = { quoteId: quote.id, rating, language: quote.language, }; - return await apeClient.post(`${BASE_PATH}/rating`, { payload }); + return await this.httpClient.post(`${BASE_PATH}/rating`, { payload }); } - async function report( + async report( quoteId: string, quoteLanguage: string, reason: string, @@ -80,16 +79,6 @@ export default function getQuotesEndpoints( captcha, }; - return await apeClient.post(`${BASE_PATH}/report`, { payload }); + return await this.httpClient.post(`${BASE_PATH}/report`, { payload }); } - - return { - get, - submit, - approveSubmission, - rejectSubmission, - getRating, - addRating, - report, - }; } diff --git a/frontend/src/ts/ape/endpoints/results.ts b/frontend/src/ts/ape/endpoints/results.ts index 4e33939c9..a26ca99a9 100644 --- a/frontend/src/ts/ape/endpoints/results.ts +++ b/frontend/src/ts/ape/endpoints/results.ts @@ -2,34 +2,29 @@ import { CLIENT_VERSION } from "../../version"; const BASE_PATH = "/results"; -export default function getResultsEndpoints( - apeClient: Ape.Client -): Ape.Endpoints["results"] { - async function get(): Ape.EndpointData { - return await apeClient.get(BASE_PATH); +export default class Results { + constructor(private httpClient: Ape.HttpClient) { + this.httpClient = httpClient; } - async function save( - result: MonkeyTypes.Result - ): Ape.EndpointData { - return await apeClient.post(BASE_PATH, { + async get(): Ape.EndpointData { + return await this.httpClient.get(BASE_PATH); + } + + async save(result: MonkeyTypes.Result): Ape.EndpointData { + return await this.httpClient.post(BASE_PATH, { payload: { result }, headers: { "Client-Version": CLIENT_VERSION }, }); } - async function updateTags( - resultId: string, - tagIds: string[] - ): Ape.EndpointData { - return await apeClient.patch(`${BASE_PATH}/tags`, { + async updateTags(resultId: string, tagIds: string[]): Ape.EndpointData { + return await this.httpClient.patch(`${BASE_PATH}/tags`, { payload: { resultId, tagIds }, }); } - async function deleteAll(): Ape.EndpointData { - return await apeClient.delete(BASE_PATH); + async deleteAll(): Ape.EndpointData { + return await this.httpClient.delete(BASE_PATH); } - - return { get, save, updateTags, deleteAll }; } diff --git a/frontend/src/ts/ape/endpoints/users.ts b/frontend/src/ts/ape/endpoints/users.ts index ea054d60a..84472bb11 100644 --- a/frontend/src/ts/ape/endpoints/users.ts +++ b/frontend/src/ts/ape/endpoints/users.ts @@ -1,39 +1,39 @@ const BASE_PATH = "/users"; -export default function getUsersEndpoints( - apeClient: Ape.Client -): Ape.Endpoints["users"] { - async function getData(): Ape.EndpointData { - return await apeClient.get(BASE_PATH); +export default class Users { + constructor(private httpClient: Ape.HttpClient) { + this.httpClient = httpClient; } - async function create( - name: string, - email?: string, - uid?: string - ): Ape.EndpointData { + async getData(): Ape.EndpointData { + return await this.httpClient.get(BASE_PATH); + } + + async create(name: string, email?: string, uid?: string): Ape.EndpointData { const payload = { email, name, uid, }; - return await apeClient.post(`${BASE_PATH}/signup`, { payload }); + return await this.httpClient.post(`${BASE_PATH}/signup`, { payload }); } - async function getNameAvailability(name: string): Ape.EndpointData { - return await apeClient.get(`${BASE_PATH}/checkName/${name}`); + async getNameAvailability(name: string): Ape.EndpointData { + return await this.httpClient.get(`${BASE_PATH}/checkName/${name}`); } - async function _delete(): Ape.EndpointData { - return await apeClient.delete(BASE_PATH); + async delete(): Ape.EndpointData { + return await this.httpClient.delete(BASE_PATH); } - async function updateName(name: string): Ape.EndpointData { - return await apeClient.patch(`${BASE_PATH}/name`, { payload: { name } }); + async updateName(name: string): Ape.EndpointData { + return await this.httpClient.patch(`${BASE_PATH}/name`, { + payload: { name }, + }); } - async function updateLeaderboardMemory( + async updateLeaderboardMemory( mode: string, mode2: MonkeyTypes.Mode2, language: string, @@ -46,55 +46,58 @@ export default function getUsersEndpoints( rank, }; - return await apeClient.patch(`${BASE_PATH}/leaderboardMemory`, { payload }); + return await this.httpClient.patch(`${BASE_PATH}/leaderboardMemory`, { + payload, + }); } - async function updateEmail( - newEmail: string, - previousEmail: string - ): Ape.EndpointData { + async updateEmail(newEmail: string, previousEmail: string): Ape.EndpointData { const payload = { newEmail, previousEmail, }; - return await apeClient.patch(`${BASE_PATH}/email`, { payload }); + return await this.httpClient.patch(`${BASE_PATH}/email`, { payload }); } - async function deletePersonalBests(): Ape.EndpointData { - return await apeClient.delete(`${BASE_PATH}/personalBests`); + async deletePersonalBests(): Ape.EndpointData { + return await this.httpClient.delete(`${BASE_PATH}/personalBests`); } - async function getTags(): Ape.EndpointData { - return await apeClient.get(`${BASE_PATH}/tags`); + async getTags(): Ape.EndpointData { + return await this.httpClient.get(`${BASE_PATH}/tags`); } - async function createTag(tagName: string): Ape.EndpointData { - return await apeClient.post(`${BASE_PATH}/tags`, { payload: { tagName } }); + async createTag(tagName: string): Ape.EndpointData { + return await this.httpClient.post(`${BASE_PATH}/tags`, { + payload: { tagName }, + }); } - async function editTag(tagId: string, newName: string): Ape.EndpointData { + async editTag(tagId: string, newName: string): Ape.EndpointData { const payload = { tagId, newName, }; - return await apeClient.patch(`${BASE_PATH}/tags`, { payload }); + return await this.httpClient.patch(`${BASE_PATH}/tags`, { payload }); } - async function deleteTag(tagId: string): Ape.EndpointData { - return await apeClient.delete(`${BASE_PATH}/tags/${tagId}`); + async deleteTag(tagId: string): Ape.EndpointData { + return await this.httpClient.delete(`${BASE_PATH}/tags/${tagId}`); } - async function deleteTagPersonalBest(tagId: string): Ape.EndpointData { - return await apeClient.delete(`${BASE_PATH}/tags/${tagId}/personalBest`); + async deleteTagPersonalBest(tagId: string): Ape.EndpointData { + return await this.httpClient.delete( + `${BASE_PATH}/tags/${tagId}/personalBest` + ); } - async function getCustomThemes(): Ape.EndpointData { - return await apeClient.get(`${BASE_PATH}/customThemes`); + async getCustomThemes(): Ape.EndpointData { + return await this.httpClient.get(`${BASE_PATH}/customThemes`); } - async function editCustomTheme( + async editCustomTheme( themeId: string, newTheme: Partial ): Ape.EndpointData { @@ -105,74 +108,58 @@ export default function getUsersEndpoints( colors: newTheme.colors, }, }; - return await apeClient.patch(`${BASE_PATH}/customThemes`, { payload }); + return await this.httpClient.patch(`${BASE_PATH}/customThemes`, { + payload, + }); } - async function deleteCustomTheme(themeId: string): Ape.EndpointData { + async deleteCustomTheme(themeId: string): Ape.EndpointData { const payload = { themeId: themeId, }; - return await apeClient.delete(`${BASE_PATH}/customThemes`, { payload }); + return await this.httpClient.delete(`${BASE_PATH}/customThemes`, { + payload, + }); } - async function addCustomTheme( + async addCustomTheme( newTheme: Partial ): Ape.EndpointData { const payload = { name: newTheme.name, colors: newTheme.colors }; - return await apeClient.post(`${BASE_PATH}/customThemes`, { payload }); + return await this.httpClient.post(`${BASE_PATH}/customThemes`, { payload }); } - async function linkDiscord(data: { + async linkDiscord(data: { tokenType: string; accessToken: string; uid?: string; }): Ape.EndpointData { - return await apeClient.post(`${BASE_PATH}/discord/link`, { + return await this.httpClient.post(`${BASE_PATH}/discord/link`, { payload: { data }, }); } - async function unlinkDiscord(): Ape.EndpointData { - return await apeClient.post(`${BASE_PATH}/discord/unlink`); + async unlinkDiscord(): Ape.EndpointData { + return await this.httpClient.post(`${BASE_PATH}/discord/unlink`); } - async function addQuoteToFavorites( + async addQuoteToFavorites( language: string, quoteId: string ): Ape.EndpointData { const payload = { language, quoteId }; - return await apeClient.post(`${BASE_PATH}/favoriteQuotes`, { payload }); + return await this.httpClient.post(`${BASE_PATH}/favoriteQuotes`, { + payload, + }); } - async function removeQuoteFromFavorites( + async removeQuoteFromFavorites( language: string, quoteId: string ): Ape.EndpointData { const payload = { language, quoteId }; - return await apeClient.delete(`${BASE_PATH}/favoriteQuotes`, { payload }); + return await this.httpClient.delete(`${BASE_PATH}/favoriteQuotes`, { + payload, + }); } - - return { - getData, - create, - getNameAvailability, - delete: _delete, - updateName, - updateLeaderboardMemory, - updateEmail, - deletePersonalBests, - getTags, - createTag, - editTag, - deleteTag, - deleteTagPersonalBest, - linkDiscord, - unlinkDiscord, - getCustomThemes, - addCustomTheme, - editCustomTheme, - deleteCustomTheme, - addQuoteToFavorites, - removeQuoteFromFavorites, - }; } diff --git a/frontend/src/ts/ape/index.ts b/frontend/src/ts/ape/index.ts index 5420c95e0..5369b76cc 100644 --- a/frontend/src/ts/ape/index.ts +++ b/frontend/src/ts/ape/index.ts @@ -1,7 +1,5 @@ -import axios, { AxiosRequestConfig, AxiosResponse } from "axios"; import endpoints from "./endpoints"; -import { Auth } from "../firebase"; -import { getIdToken } from "firebase/auth"; +import { buildHttpClient } from "./adapters/axios-adapter"; const DEV_SERVER_HOST = "http://localhost:5005"; const PROD_SERVER_HOST = "https://api.monkeytype.com"; @@ -11,117 +9,18 @@ const BASE_URL = window.location.hostname === "localhost" ? DEV_SERVER_HOST : PROD_SERVER_HOST; const API_URL = `${BASE_URL}${API_PATH}`; -// Adapts the ape client's view of request options to the underlying HTTP client. -async function adaptRequestOptions( - options: Ape.RequestOptions -): Promise { - const currentUser = Auth.currentUser; - const idToken = currentUser && (await getIdToken(currentUser)); - - return { - params: options.searchQuery, - data: options.payload, - headers: { - ...options.headers, - Accept: "application/json", - "Content-Type": "application/json", - ...(idToken && { Authorization: `Bearer ${idToken}` }), - }, - }; -} - -type AxiosClientMethod = ( - endpoint: string, - config: AxiosRequestConfig -) => Promise; - -type AxiosClientDataMethod = ( - endpoint: string, - data: any, - config: AxiosRequestConfig -) => Promise; - -type AxiosClientMethods = AxiosClientMethod & AxiosClientDataMethod; - -// Wrap the underlying HTTP client's method with our own. -function apeifyClientMethod( - clientMethod: AxiosClientMethods, - methodType: Ape.MethodTypes -): Ape.ClientMethod { - return async ( - endpoint: string, - options: Ape.RequestOptions = {} - ): Ape.EndpointData => { - let errorMessage = ""; - - try { - const requestOptions: AxiosRequestConfig = await adaptRequestOptions( - options - ); - - let response; - if (methodType === "get" || methodType === "delete") { - response = await clientMethod(endpoint, requestOptions); - } else { - response = await clientMethod( - endpoint, - requestOptions.data, - requestOptions - ); - } - - const { message, data } = response.data as Ape.ApiResponse; - - return { - status: response.status, - message, - data, - }; - } catch (error) { - console.error(error); - - const typedError = error as Error; - errorMessage = typedError.message; - - if (axios.isAxiosError(typedError)) { - return { - status: typedError.response?.status ?? 500, - message: typedError.message, - ...typedError.response?.data, - }; - } - } - - return { - status: 500, - message: errorMessage, - }; - }; -} - -const axiosClient = axios.create({ - baseURL: API_URL, - timeout: 10000, -}); - -const apeClient: Ape.Client = { - get: apeifyClientMethod(axiosClient.get, "get"), - post: apeifyClientMethod(axiosClient.post, "post"), - put: apeifyClientMethod(axiosClient.put, "put"), - patch: apeifyClientMethod(axiosClient.patch, "patch"), - delete: apeifyClientMethod(axiosClient.delete, "delete"), -}; +const httpClient = buildHttpClient(API_URL, 10000); // API Endpoints -const Ape: Ape.Endpoints = { - users: endpoints.getUsersEndpoints(apeClient), - configs: endpoints.getConfigsEndpoints(apeClient), - results: endpoints.getResultsEndpoints(apeClient), - psas: endpoints.getPsasEndpoints(apeClient), - quotes: endpoints.getQuotesEndpoints(apeClient), - leaderboards: endpoints.getLeaderboardsEndpoints(apeClient), - presets: endpoints.getPresetsEndpoints(apeClient), - apeKeys: endpoints.getApeKeysEndpoints(apeClient), +const Ape = { + users: new endpoints.Users(httpClient), + configs: new endpoints.Configs(httpClient), + results: new endpoints.Results(httpClient), + psas: new endpoints.Psas(httpClient), + quotes: new endpoints.Quotes(httpClient), + leaderboards: new endpoints.Leaderboards(httpClient), + presets: new endpoints.Presets(httpClient), + apeKeys: new endpoints.ApeKeys(httpClient), }; export default Ape; diff --git a/frontend/src/ts/ape/types/ape.d.ts b/frontend/src/ts/ape/types/ape.d.ts index b5f4334c0..3509ecb21 100644 --- a/frontend/src/ts/ape/types/ape.d.ts +++ b/frontend/src/ts/ape/types/ape.d.ts @@ -1,163 +1,35 @@ declare namespace Ape { - type ClientMethod = ( - endpoint: string, - config?: RequestOptions - ) => Promise; - - interface ApiResponse { - message: string; - data: any | null; - } - - interface Client { - get: ClientMethod; - post: ClientMethod; - put: ClientMethod; - patch: ClientMethod; - delete: ClientMethod; - } - - type MethodTypes = keyof Client; - interface RequestOptions { headers?: Record; searchQuery?: Record; payload?: any; } - interface Response { + interface HttpClientResponse { status: number; message: string; data?: any; } - type EndpointData = Promise; - type Endpoint = () => EndpointData; + type EndpointData = Promise; - declare namespace EndpointTypes { - interface LeaderboardQuery { - language: string; - mode: MonkeyTypes.Mode; - mode2: string | number; - isDaily?: boolean; - } + type HttpClientMethod = ( + endpoint: string, + config?: RequestOptions + ) => Promise; - interface LeadeboardQueryWithPagination extends LeaderboardQuery { - skip?: number; - limit?: number; - } + interface HttpClient { + get: HttpClientMethod; + post: HttpClientMethod; + put: HttpClientMethod; + patch: HttpClientMethod; + delete: HttpClientMethod; } - interface Endpoints { - configs: { - get: Endpoint; - save: (config: MonkeyTypes.Config) => EndpointData; - }; + type HttpMethodTypes = keyof HttpClient; - leaderboards: { - get: (query: EndpointTypes.LeadeboardQueryWithPagination) => EndpointData; - getRank: (query: EndpointTypes.LeaderboardQuery) => EndpointData; - }; - - presets: { - get: Endpoint; - add: ( - presetName: string, - configChanges: MonkeyTypes.ConfigChanges - ) => EndpointData; - edit: ( - presetId: string, - presetName: string, - configChanges: MonkeyTypes.ConfigChanges - ) => EndpointData; - delete: (presetId: string) => EndpointData; - }; - - psas: { - get: Endpoint; - }; - - quotes: { - get: Endpoint; - submit: ( - text: string, - source: string, - language: string, - captcha: string - ) => EndpointData; - approveSubmission: ( - quoteSubmissionId: string, - editText?: string, - editSource?: string - ) => EndpointData; - rejectSubmission: (quoteSubmissionId: string) => EndpointData; - getRating: (quote: MonkeyTypes.Quote) => EndpointData; - addRating: (quote: MonkeyTypes.Quote, rating: number) => EndpointData; - report: ( - quoteId: string, - quoteLanguage: string, - reason: string, - comment: string, - captcha: string - ) => EndpointData; - }; - - users: { - getData: Endpoint; - create: (name: string, email?: string, uid?: string) => EndpointData; - getNameAvailability: (name: string) => EndpointData; - delete: Endpoint; - updateName: (name: string) => EndpointData; - updateLeaderboardMemory: ( - mode: string, - mode2: MonkeyTypes.Mode2, - language: string, - rank: number - ) => EndpointData; - updateEmail: (newEmail: string, previousEmail: string) => EndpointData; - deletePersonalBests: Endpoint; - getCustomThemes: () => EndpointData; - addCustomTheme: ( - newTheme: Partial - ) => EndpointData; - editCustomTheme: ( - themeId: string, - newTheme: Partial - ) => EndpointData; - deleteCustomTheme: (themeId: string) => EndpointData; - getTags: Endpoint; - createTag: (tagName: string) => EndpointData; - editTag: (tagId: string, newName: string) => EndpointData; - deleteTag: (tagId: string) => EndpointData; - deleteTagPersonalBest: (tagId: string) => EndpointData; - linkDiscord: (data: { - tokenType: string; - accessToken: string; - uid?: string; - }) => EndpointData; - unlinkDiscord: Endpoint; - addQuoteToFavorites: (language: string, quoteId: string) => EndpointData; - removeQuoteFromFavorites: ( - language: string, - quoteId: string - ) => EndpointData; - }; - - results: { - get: Endpoint; - save: (result: MonkeyTypes.Result) => EndpointData; - updateTags: (resultId: string, tagIds: string[]) => EndpointData; - deleteAll: Endpoint; - }; - - apeKeys: { - get: Endpoint; - generate: (name: string, enabled: boolean) => EndpointData; - update: ( - apeKeyId: string, - updates: { name?: string; enabled?: boolean } - ) => EndpointData; - delete: (apeKeyId: string) => EndpointData; - }; + interface ApiResponse { + message: string; + data: any | null; } } diff --git a/frontend/src/ts/db.ts b/frontend/src/ts/db.ts index f6d9a72b7..281eea65b 100644 --- a/frontend/src/ts/db.ts +++ b/frontend/src/ts/db.ts @@ -73,7 +73,7 @@ export async function initSnapshot(): Promise< configResponse, tagsResponse, presetsResponse, - ].map((response: Ape.Response) => response.data); + ].map((response: Ape.HttpClientResponse) => response.data); snap.name = userData.name; snap.personalBests = userData.personalBests;