mirror of
https://github.com/monkeytypegame/monkeytype.git
synced 2025-10-27 17:27:32 +08:00
Merge branch 'master' of https://github.com/Miodec/monkeytype
This commit is contained in:
commit
3726ec08e3
13 changed files with 308 additions and 459 deletions
107
frontend/src/ts/ape/adapters/axios-adapter.ts
Normal file
107
frontend/src/ts/ape/adapters/axios-adapter.ts
Normal file
|
|
@ -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<AxiosResponse>;
|
||||
|
||||
type AxiosClientDataMethod = (
|
||||
endpoint: string,
|
||||
data: any,
|
||||
config: AxiosRequestConfig
|
||||
) => Promise<AxiosResponse>;
|
||||
|
||||
type AxiosClientMethods = AxiosClientMethod & AxiosClientDataMethod;
|
||||
|
||||
async function adaptRequestOptions(
|
||||
options: Ape.RequestOptions
|
||||
): Promise<AxiosRequestConfig> {
|
||||
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"),
|
||||
};
|
||||
}
|
||||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 } });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<MonkeyTypes.Mode>
|
||||
): Ape.EndpointData {
|
||||
return await apeClient.post(BASE_PATH, {
|
||||
async get(): Ape.EndpointData {
|
||||
return await this.httpClient.get(BASE_PATH);
|
||||
}
|
||||
|
||||
async save(result: MonkeyTypes.Result<MonkeyTypes.Mode>): 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 };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<M extends MonkeyTypes.Mode>(
|
||||
async updateLeaderboardMemory<M extends MonkeyTypes.Mode>(
|
||||
mode: string,
|
||||
mode2: MonkeyTypes.Mode2<M>,
|
||||
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<MonkeyTypes.CustomTheme>
|
||||
): 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<MonkeyTypes.CustomTheme>
|
||||
): 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,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<AxiosRequestConfig> {
|
||||
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<AxiosResponse>;
|
||||
|
||||
type AxiosClientDataMethod = (
|
||||
endpoint: string,
|
||||
data: any,
|
||||
config: AxiosRequestConfig
|
||||
) => Promise<AxiosResponse>;
|
||||
|
||||
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;
|
||||
|
|
|
|||
160
frontend/src/ts/ape/types/ape.d.ts
vendored
160
frontend/src/ts/ape/types/ape.d.ts
vendored
|
|
@ -1,163 +1,35 @@
|
|||
declare namespace Ape {
|
||||
type ClientMethod = (
|
||||
endpoint: string,
|
||||
config?: RequestOptions
|
||||
) => Promise<Response>;
|
||||
|
||||
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<string, string>;
|
||||
searchQuery?: Record<string, any>;
|
||||
payload?: any;
|
||||
}
|
||||
|
||||
interface Response {
|
||||
interface HttpClientResponse {
|
||||
status: number;
|
||||
message: string;
|
||||
data?: any;
|
||||
}
|
||||
|
||||
type EndpointData = Promise<Response>;
|
||||
type Endpoint = () => EndpointData;
|
||||
type EndpointData = Promise<HttpClientResponse>;
|
||||
|
||||
declare namespace EndpointTypes {
|
||||
interface LeaderboardQuery {
|
||||
language: string;
|
||||
mode: MonkeyTypes.Mode;
|
||||
mode2: string | number;
|
||||
isDaily?: boolean;
|
||||
}
|
||||
type HttpClientMethod = (
|
||||
endpoint: string,
|
||||
config?: RequestOptions
|
||||
) => Promise<HttpClientResponse>;
|
||||
|
||||
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: <M extends MonkeyTypes.Mode>(
|
||||
mode: string,
|
||||
mode2: MonkeyTypes.Mode2<M>,
|
||||
language: string,
|
||||
rank: number
|
||||
) => EndpointData;
|
||||
updateEmail: (newEmail: string, previousEmail: string) => EndpointData;
|
||||
deletePersonalBests: Endpoint;
|
||||
getCustomThemes: () => EndpointData;
|
||||
addCustomTheme: (
|
||||
newTheme: Partial<MonkeyTypes.CustomTheme>
|
||||
) => EndpointData;
|
||||
editCustomTheme: (
|
||||
themeId: string,
|
||||
newTheme: Partial<MonkeyTypes.CustomTheme>
|
||||
) => 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<MonkeyTypes.Mode>) => 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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue