From 95a9b63ccf5393b27c0d28a489258494e109743e Mon Sep 17 00:00:00 2001 From: zkldi Date: Wed, 11 Aug 2021 03:34:43 +0100 Subject: [PATCH 1/3] Kai-API dan retrieving (and MIN support) Fixes #246 --- server/.github/test.conf.json5 | 1 + server/package.json | 2 +- server/pnpm-lock.yaml | 8 +- server/src/lib/constants/classes.ts | 2 +- .../api/arc-iidx/class-handler.test.ts | 6 +- .../api/arc-iidx/class-handler.ts | 42 ++--- .../import-types/api/arc-iidx/parser.ts | 6 +- .../import-types/api/min-sdvx/parser.ts | 7 + .../common/api-kai/iidx/class-handler.test.ts | 178 ++++++++++++++++++ .../common/api-kai/iidx/class-handler.ts | 74 ++++++++ .../common/api-kai/iidx/parser.ts | 10 +- .../common/api-kai/sdvx/class-handler.test.ts | 149 +++++++++++++++ .../common/api-kai/sdvx/class-handler.ts | 65 +++++++ .../common/api-kai/sdvx/parser.ts | 5 +- .../import-types/common/api-kai/types.ts | 2 +- .../score-import/import-types/common/types.ts | 2 + .../score-import/import-types/converters.ts | 1 + server/src/lib/setup/config.ts | 2 + server/src/test-utils/mock-fetch.ts | 6 +- server/src/utils/misc.ts | 14 +- 20 files changed, 542 insertions(+), 40 deletions(-) create mode 100644 server/src/lib/score-import/import-types/api/min-sdvx/parser.ts create mode 100644 server/src/lib/score-import/import-types/common/api-kai/iidx/class-handler.test.ts create mode 100644 server/src/lib/score-import/import-types/common/api-kai/iidx/class-handler.ts create mode 100644 server/src/lib/score-import/import-types/common/api-kai/sdvx/class-handler.test.ts create mode 100644 server/src/lib/score-import/import-types/common/api-kai/sdvx/class-handler.ts diff --git a/server/.github/test.conf.json5 b/server/.github/test.conf.json5 index 9c07fc144..cc974850e 100644 --- a/server/.github/test.conf.json5 +++ b/server/.github/test.conf.json5 @@ -8,6 +8,7 @@ SESSION_SECRET: "unused", FLO_API_URL: "https://flo.example.com", EAG_API_URL: "https://eag.example.com", + MIN_API_URL: "https://min.example.com", ARC_API_URL: "https://arc.example.com", ARC_AUTH_TOKEN: "unused", CDN_FILE_ROOT: "./local-cdn", diff --git a/server/package.json b/server/package.json index fb2a73b33..cb0c6d742 100644 --- a/server/package.json +++ b/server/package.json @@ -69,7 +69,7 @@ "redis": "3.1.2", "rimraf": "3.0.2", "safe-json-stringify": "1.2.0", - "tachi-common": "^0.1.5", + "tachi-common": "^0.1.6", "typescript": "4.3.4", "winston": "3.3.3" }, diff --git a/server/pnpm-lock.yaml b/server/pnpm-lock.yaml index 143914ee6..022bd1196 100644 --- a/server/pnpm-lock.yaml +++ b/server/pnpm-lock.yaml @@ -51,7 +51,7 @@ specifiers: rimraf: 3.0.2 safe-json-stringify: 1.2.0 supertest: 6.1.3 - tachi-common: ^0.1.5 + tachi-common: ^0.1.6 tap: 15.0.9 ts-node: 10.0.0 tsconfig-paths: 3.10.1 @@ -84,7 +84,7 @@ dependencies: redis: 3.1.2 rimraf: 3.0.2 safe-json-stringify: 1.2.0 - tachi-common: 0.1.5_ts-node@10.0.0+typescript@4.3.4 + tachi-common: 0.1.6_ts-node@10.0.0+typescript@4.3.4 typescript: 4.3.4 winston: 3.3.3 @@ -3774,8 +3774,8 @@ packages: strip-ansi: 6.0.0 dev: true - /tachi-common/0.1.5_ts-node@10.0.0+typescript@4.3.4: - resolution: {integrity: sha512-caqTQ43TqTb861lenL27cIFxU9enZJCulSBU6kk1db2/XJNmNsLNtoo5xDwbgYs4eSwxkENGBZ1THbeBG0B5RQ==} + /tachi-common/0.1.6_ts-node@10.0.0+typescript@4.3.4: + resolution: {integrity: sha512-H2kjAIUXyvNGuPlOgvWc25KmujETee4XqRRSG/BQIall0FSiO7yWApZQMelQZITebdlKwzpd976P2NSejTv0Yg==} dependencies: monk: 7.3.4 tap: 15.0.9_ts-node@10.0.0+typescript@4.3.4 diff --git a/server/src/lib/constants/classes.ts b/server/src/lib/constants/classes.ts index 8f36f2074..2f48f3aaa 100644 --- a/server/src/lib/constants/classes.ts +++ b/server/src/lib/constants/classes.ts @@ -1,4 +1,4 @@ -export enum IIDXClasses { +export enum IIDXDans { KYU_7, KYU_6, KYU_5, diff --git a/server/src/lib/score-import/import-types/api/arc-iidx/class-handler.test.ts b/server/src/lib/score-import/import-types/api/arc-iidx/class-handler.test.ts index c32aa409e..db0aa0db9 100644 --- a/server/src/lib/score-import/import-types/api/arc-iidx/class-handler.test.ts +++ b/server/src/lib/score-import/import-types/api/arc-iidx/class-handler.test.ts @@ -2,7 +2,7 @@ import t from "tap"; import { CloseAllConnections } from "test-utils/close-connections"; import { MockJSONFetch } from "test-utils/mock-fetch"; import ResetDBState from "test-utils/resets"; -import { IIDXClasses } from "lib/constants/classes"; +import { IIDXDans } from "lib/constants/classes"; import CreateLogCtx from "lib/logger/logger"; import { CreateArcIIDXClassHandler } from "./class-handler"; @@ -34,10 +34,10 @@ t.test("#CreateArcIIDXClassHandler", (t) => { // its not async but typescript complains const res = await fn("iidx", "SP", 1, {}, logger); - t.equal(res!.dan, IIDXClasses.CHUUDEN); + t.equal(res!.dan, IIDXDans.CHUUDEN); const res2 = await fn("iidx", "DP", 1, {}, logger); - t.equal(res2!.dan, IIDXClasses.DAN_2); + t.equal(res2!.dan, IIDXDans.DAN_2); t.end(); }); diff --git a/server/src/lib/score-import/import-types/api/arc-iidx/class-handler.ts b/server/src/lib/score-import/import-types/api/arc-iidx/class-handler.ts index 1f21d67ea..b79e23763 100644 --- a/server/src/lib/score-import/import-types/api/arc-iidx/class-handler.ts +++ b/server/src/lib/score-import/import-types/api/arc-iidx/class-handler.ts @@ -1,6 +1,6 @@ import nodeFetch from "utils/fetch"; import { HasOwnProperty } from "utils/misc"; -import { IIDXClasses } from "lib/constants/classes"; +import { IIDXDans } from "lib/constants/classes"; import { ClassHandler } from "../../../framework/user-game-stats/types"; import { ServerConfig } from "lib/setup/config"; @@ -72,24 +72,24 @@ export async function CreateArcIIDXClassHandler( } const ARCClasses = { - 皆伝: IIDXClasses.KAIDEN, - 中伝: IIDXClasses.CHUUDEN, - 十段: IIDXClasses.DAN_10, - 九段: IIDXClasses.DAN_9, - 八段: IIDXClasses.DAN_8, - 七段: IIDXClasses.DAN_7, - 六段: IIDXClasses.DAN_6, - 五段: IIDXClasses.DAN_5, - 四段: IIDXClasses.DAN_4, - 三段: IIDXClasses.DAN_3, - 二段: IIDXClasses.DAN_2, // These two look very similar but they aren't - ニ段: IIDXClasses.DAN_2, // and ARC uses both, from what I can tell. - 初段: IIDXClasses.DAN_1, - 一級: IIDXClasses.KYU_1, - 二級: IIDXClasses.KYU_2, - 三級: IIDXClasses.KYU_3, - 四級: IIDXClasses.KYU_4, - 五級: IIDXClasses.KYU_5, - 六級: IIDXClasses.KYU_6, - 七級: IIDXClasses.KYU_7, + 皆伝: IIDXDans.KAIDEN, + 中伝: IIDXDans.CHUUDEN, + 十段: IIDXDans.DAN_10, + 九段: IIDXDans.DAN_9, + 八段: IIDXDans.DAN_8, + 七段: IIDXDans.DAN_7, + 六段: IIDXDans.DAN_6, + 五段: IIDXDans.DAN_5, + 四段: IIDXDans.DAN_4, + 三段: IIDXDans.DAN_3, + 二段: IIDXDans.DAN_2, // These two look very similar but they aren't + ニ段: IIDXDans.DAN_2, // and ARC uses both, from what I can tell. + 初段: IIDXDans.DAN_1, + 一級: IIDXDans.KYU_1, + 二級: IIDXDans.KYU_2, + 三級: IIDXDans.KYU_3, + 四級: IIDXDans.KYU_4, + 五級: IIDXDans.KYU_5, + 六級: IIDXDans.KYU_6, + 七級: IIDXDans.KYU_7, }; diff --git a/server/src/lib/score-import/import-types/api/arc-iidx/parser.ts b/server/src/lib/score-import/import-types/api/arc-iidx/parser.ts index 7c5218109..0d1c1d7ac 100644 --- a/server/src/lib/score-import/import-types/api/arc-iidx/parser.ts +++ b/server/src/lib/score-import/import-types/api/arc-iidx/parser.ts @@ -21,7 +21,11 @@ export async function ParseArcIIDX( fetch ), context: {}, - classHandler: await CreateArcIIDXClassHandler(arcProfileID, ServerConfig.ARC_AUTH_TOKEN), + classHandler: await CreateArcIIDXClassHandler( + arcProfileID, + ServerConfig.ARC_AUTH_TOKEN, + fetch + ), game: "iidx", }; } diff --git a/server/src/lib/score-import/import-types/api/min-sdvx/parser.ts b/server/src/lib/score-import/import-types/api/min-sdvx/parser.ts new file mode 100644 index 000000000..c11cc7478 --- /dev/null +++ b/server/src/lib/score-import/import-types/api/min-sdvx/parser.ts @@ -0,0 +1,7 @@ +import { KtLogger } from "lib/logger/logger"; +import { KaiAuthDocument } from "tachi-common"; +import { ParseKaiSDVX } from "../../common/api-kai/sdvx/parser"; + +export function ParseMinSDVX(authDoc: KaiAuthDocument, logger: KtLogger) { + return ParseKaiSDVX("MIN", authDoc, logger); +} diff --git a/server/src/lib/score-import/import-types/common/api-kai/iidx/class-handler.test.ts b/server/src/lib/score-import/import-types/common/api-kai/iidx/class-handler.test.ts new file mode 100644 index 000000000..b7f64d4f2 --- /dev/null +++ b/server/src/lib/score-import/import-types/common/api-kai/iidx/class-handler.test.ts @@ -0,0 +1,178 @@ +import { IIDXDans } from "lib/constants/classes"; +import CreateLogCtx from "lib/logger/logger"; +import t from "tap"; +import { CloseAllConnections } from "test-utils/close-connections"; +import { MockBasicFetch, MockJSONFetch } from "test-utils/mock-fetch"; +import ResetDBState from "test-utils/resets"; +import { KaiTypeToBaseURL } from "utils/misc"; +import { CreateKaiIIDXClassHandler } from "./class-handler"; + +const logger = CreateLogCtx(__filename); + +t.test("#CreateKaiIIDXClassHandler", async (t) => { + t.beforeEach(ResetDBState); + + const fn = await CreateKaiIIDXClassHandler( + "FLO", + "token", + MockJSONFetch({ + [`${KaiTypeToBaseURL("FLO")}/api/iidx/v2/player_profile`]: { + _links: {}, + iidx_id: 12345678, + dj_name: "SOMEONE", + sp: 18, + dp: 4, + access_time: "2021-08-08T18:50:40Z", + register_time: "2019-01-19T12:53:50Z", + }, + }) + ); + + t.test("Should return a function with arity 5.", (t) => { + t.equal(fn.length, 5); + + t.end(); + }); + + t.test("Should call the provided URL with the authentication token", (t) => { + const res = fn("iidx", "SP", 1, {}, logger); + + t.strictSame(res, { dan: 18 }); + + t.end(); + }); + + t.test("Should return nothing if dan is not a number", async (t) => { + const fn = await CreateKaiIIDXClassHandler( + "FLO", + "token", + MockJSONFetch({ + [`${KaiTypeToBaseURL("FLO")}/api/iidx/v2/player_profile`]: { + _links: {}, + iidx_id: 12345678, + dj_name: "SOMEONE", + sp: "NOT A NUMBER", + dp: 4, + access_time: "2021-08-08T18:50:40Z", + register_time: "2019-01-19T12:53:50Z", + }, + }) + ); + + const res = fn("iidx", "SP", 1, {}, logger); + + t.strictSame(res, {}); + + t.end(); + }); + + t.test("Should return nothing if dan is too great", async (t) => { + const fn = await CreateKaiIIDXClassHandler( + "FLO", + "token", + MockJSONFetch({ + [`${KaiTypeToBaseURL("FLO")}/api/iidx/v2/player_profile`]: { + _links: {}, + iidx_id: 12345678, + dj_name: "SOMEONE", + sp: IIDXDans.KAIDEN + 1, + dp: 4, + access_time: "2021-08-08T18:50:40Z", + register_time: "2019-01-19T12:53:50Z", + }, + }) + ); + + const res = fn("iidx", "SP", 1, {}, logger); + + t.strictSame(res, {}); + + t.end(); + }); + + t.test("Should return nothing if dan is negative", async (t) => { + const fn = await CreateKaiIIDXClassHandler( + "FLO", + "token", + MockJSONFetch({ + [`${KaiTypeToBaseURL("FLO")}/api/iidx/v2/player_profile`]: { + _links: {}, + iidx_id: 12345678, + dj_name: "SOMEONE", + sp: IIDXDans.KYU_7 - 1, + dp: 4, + access_time: "2021-08-08T18:50:40Z", + register_time: "2019-01-19T12:53:50Z", + }, + }) + ); + + const res = fn("iidx", "SP", 1, {}, logger); + + t.strictSame(res, {}); + + t.end(); + }); + + t.test("Should gracefully handle negative API responses", async (t) => { + const fn = await CreateKaiIIDXClassHandler("FLO", "token", MockBasicFetch({ status: 500 })); + + const res = fn("iidx", "SP", 1, {}, logger); + + t.strictSame(res, {}); + + t.end(); + }); + + t.test("Should ignore null dans", async (t) => { + const fn = await CreateKaiIIDXClassHandler( + "FLO", + "token", + MockJSONFetch({ + [`${KaiTypeToBaseURL("FLO")}/api/iidx/v2/player_profile`]: { + _links: {}, + iidx_id: 12345678, + dj_name: "SOMEONE", + sp: 1, + dp: null, + access_time: "2021-08-08T18:50:40Z", + register_time: "2019-01-19T12:53:50Z", + }, + }) + ); + + const res = fn("iidx", "DP", 1, {}, logger); + + t.strictSame(res, {}); + + t.end(); + }); + + t.test("Should handle invalid playtypes", async (t) => { + const fn = await CreateKaiIIDXClassHandler( + "FLO", + "token", + MockJSONFetch({ + [`${KaiTypeToBaseURL("FLO")}/api/iidx/v2/player_profile`]: { + _links: {}, + iidx_id: 12345678, + dj_name: "SOMEONE", + sp: 1, + dp: 1, + access_time: "2021-08-08T18:50:40Z", + register_time: "2019-01-19T12:53:50Z", + }, + }) + ); + + const res = fn("iidx", "14K", 1, {}, logger); + + t.strictSame(res, {}); + + t.end(); + }); + + t.end(); +}); + +t.teardown(CloseAllConnections); diff --git a/server/src/lib/score-import/import-types/common/api-kai/iidx/class-handler.ts b/server/src/lib/score-import/import-types/common/api-kai/iidx/class-handler.ts new file mode 100644 index 000000000..d7ab488b9 --- /dev/null +++ b/server/src/lib/score-import/import-types/common/api-kai/iidx/class-handler.ts @@ -0,0 +1,74 @@ +import { IIDXDans } from "lib/constants/classes"; +import { ClassHandler } from "lib/score-import/framework/user-game-stats/types"; +import nodeFetch from "utils/fetch"; +import { KaiTypeToBaseURL } from "utils/misc"; + +export async function CreateKaiIIDXClassHandler( + kaiType: "FLO" | "EAG", + token: string, + fetch = nodeFetch +): Promise { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let json: any; + let err: unknown; + const baseUrl = KaiTypeToBaseURL(kaiType); + + // SP and DP dans are located in the same place, + // fetch once, then return a function that traverses this data. + try { + const res = await fetch(`${baseUrl}/api/iidx/v2/player_profile`, { + headers: { + Authorization: `Bearer ${token}`, + }, + }); + + json = await res.json(); + } catch (e) { + err = e; + } + + return (game, playtype, userID, ratings, logger) => { + if (err) { + logger.error(`An error occured while updating classes for ${baseUrl}.`, { err }); + return {}; + } + + let iidxDan: number | null; + + if (playtype === "SP") { + iidxDan = json.sp; + } else if (playtype === "DP") { + iidxDan = json.dp; + } else { + logger.warn(`KAIIIDXClassUpdater called with invalid playtype of ${playtype}.`); + return {}; + } + + if (iidxDan === null) { + return {}; + } + + if (!Number.isInteger(iidxDan)) { + logger.warn(`${baseUrl} returned a dan of ${iidxDan}, which was not a number.`); + return {}; + } + + if (iidxDan > IIDXDans.KAIDEN) { + logger.warn( + `${baseUrl} returned a dan of ${iidxDan}, which was greater than KAIDEN (${IIDXDans.KAIDEN}.)` + ); + return {}; + } + + if (iidxDan < IIDXDans.KYU_7) { + logger.warn( + `${baseUrl} returned a dan of ${iidxDan}, which was less than KYU_7 (${IIDXDans.KYU_7}.)` + ); + return {}; + } + + return { + dan: iidxDan, + }; + }; +} diff --git a/server/src/lib/score-import/import-types/common/api-kai/iidx/parser.ts b/server/src/lib/score-import/import-types/common/api-kai/iidx/parser.ts index d042c1009..0ebec543b 100644 --- a/server/src/lib/score-import/import-types/common/api-kai/iidx/parser.ts +++ b/server/src/lib/score-import/import-types/common/api-kai/iidx/parser.ts @@ -5,14 +5,16 @@ import { KaiContext } from "../types"; import { TraverseKaiAPI } from "../traverse-api"; import { ParserFunctionReturns } from "../../types"; import { ServerConfig } from "lib/setup/config"; +import { KaiTypeToBaseURL } from "utils/misc"; +import { CreateKaiIIDXClassHandler } from "./class-handler"; -export function ParseKaiIIDX( +export async function ParseKaiIIDX( service: "FLO" | "EAG", authDoc: KaiAuthDocument, logger: KtLogger, fetch = nodeFetch -): ParserFunctionReturns { - const baseUrl = service === "FLO" ? ServerConfig.FLO_API_URL : ServerConfig.EAG_API_URL; +): Promise> { + const baseUrl = KaiTypeToBaseURL(service); return { iterable: TraverseKaiAPI( @@ -25,7 +27,7 @@ export function ParseKaiIIDX( context: { service, }, - classHandler: null, + classHandler: await CreateKaiIIDXClassHandler(service, authDoc.token, fetch), game: "iidx", }; } diff --git a/server/src/lib/score-import/import-types/common/api-kai/sdvx/class-handler.test.ts b/server/src/lib/score-import/import-types/common/api-kai/sdvx/class-handler.test.ts new file mode 100644 index 000000000..09e5ca0c3 --- /dev/null +++ b/server/src/lib/score-import/import-types/common/api-kai/sdvx/class-handler.test.ts @@ -0,0 +1,149 @@ +import { SDVXDans } from "lib/constants/classes"; +import CreateLogCtx from "lib/logger/logger"; +import t from "tap"; +import { CloseAllConnections } from "test-utils/close-connections"; +import { MockBasicFetch, MockJSONFetch } from "test-utils/mock-fetch"; +import ResetDBState from "test-utils/resets"; +import { KaiTypeToBaseURL } from "utils/misc"; +import { CreateKaiSDVXClassHandler } from "./class-handler"; + +const logger = CreateLogCtx(__filename); + +t.test("#CreateKaiSDVXClassHandler", async (t) => { + t.beforeEach(ResetDBState); + + const fn = await CreateKaiSDVXClassHandler( + "FLO", + "token", + MockJSONFetch({ + [`${KaiTypeToBaseURL("FLO")}/api/sdvx/v1/player_profile`]: { + _links: {}, + sdvx_id: 12345678, + name: "SOMEONE", + skill_level: 10, + access_time: "2019-08-26T18:22:36Z", + register_time: "2019-08-26T18:22:36Z", + }, + }) + ); + + t.test("Should return a function with arity 5.", (t) => { + t.equal(fn.length, 5); + + t.end(); + }); + + t.test("Should call the provided URL with the authentication token", (t) => { + const res = fn("sdvx", "Single", 1, {}, logger); + + t.strictSame(res, { dan: 10 }); + + t.end(); + }); + + t.test("Should return nothing if dan is not a number", async (t) => { + const fn = await CreateKaiSDVXClassHandler( + "FLO", + "token", + MockJSONFetch({ + [`${KaiTypeToBaseURL("FLO")}/api/sdvx/v1/player_profile`]: { + _links: {}, + sdvx_id: 12345678, + name: "SOMEONE", + skill_level: "NOT A NUMBER", + access_time: "2019-08-26T18:22:36Z", + register_time: "2019-08-26T18:22:36Z", + }, + }) + ); + + const res = fn("sdvx", "Single", 1, {}, logger); + + t.strictSame(res, {}); + + t.end(); + }); + + t.test("Should return nothing if dan is too great", async (t) => { + const fn = await CreateKaiSDVXClassHandler( + "FLO", + "token", + MockJSONFetch({ + [`${KaiTypeToBaseURL("FLO")}/api/sdvx/v1/player_profile`]: { + _links: {}, + sdvx_id: 12345678, + name: "SOMEONE", + skill_level: SDVXDans.INF + 1, + access_time: "2019-08-26T18:22:36Z", + register_time: "2019-08-26T18:22:36Z", + }, + }) + ); + + const res = fn("sdvx", "Single", 1, {}, logger); + + t.strictSame(res, {}); + + t.end(); + }); + + t.test("Should return nothing if dan is negative", async (t) => { + const fn = await CreateKaiSDVXClassHandler( + "FLO", + "token", + MockJSONFetch({ + [`${KaiTypeToBaseURL("FLO")}/api/sdvx/v1/player_profile`]: { + _links: {}, + sdvx_id: 12345678, + name: "SOMEONE", + skill_level: -1, + access_time: "2019-08-26T18:22:36Z", + register_time: "2019-08-26T18:22:36Z", + }, + }) + ); + + const res = fn("sdvx", "Single", 1, {}, logger); + + t.strictSame(res, {}); + + t.end(); + }); + + t.test("Should gracefully handle negative API responses", async (t) => { + const fn = await CreateKaiSDVXClassHandler("FLO", "token", MockBasicFetch({ status: 500 })); + + const res = fn("sdvx", "Single", 1, {}, logger); + + t.strictSame(res, {}); + + t.end(); + }); + + t.test("Should ignore null dans", async (t) => { + const fn = await CreateKaiSDVXClassHandler( + "FLO", + "token", + MockJSONFetch({ + [`${KaiTypeToBaseURL("FLO")}/api/sdvx/v1/player_profile`]: { + _links: {}, + sdvx_id: 12345678, + name: "SOMEONE", + skill_level: null, + access_time: "2019-08-26T18:22:36Z", + register_time: "2019-08-26T18:22:36Z", + }, + }) + ); + + const res = fn("sdvx", "Single", 1, {}, logger); + + t.strictSame(res, {}); + + t.end(); + }); + + t.end(); +}); + +t.teardown(CloseAllConnections); diff --git a/server/src/lib/score-import/import-types/common/api-kai/sdvx/class-handler.ts b/server/src/lib/score-import/import-types/common/api-kai/sdvx/class-handler.ts new file mode 100644 index 000000000..64941f8bb --- /dev/null +++ b/server/src/lib/score-import/import-types/common/api-kai/sdvx/class-handler.ts @@ -0,0 +1,65 @@ +import { SDVXDans } from "lib/constants/classes"; +import { ClassHandler } from "lib/score-import/framework/user-game-stats/types"; +import nodeFetch from "utils/fetch"; +import { KaiTypeToBaseURL } from "utils/misc"; + +export async function CreateKaiSDVXClassHandler( + kaiType: "FLO" | "EAG", + token: string, + fetch = nodeFetch +): Promise { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let json: any; + let err: unknown; + const baseUrl = KaiTypeToBaseURL(kaiType); + + // SP and DP dans are located in the same place, + // fetch once, then return a function that traverses this data. + try { + const res = await fetch(`${baseUrl}/api/sdvx/v1/player_profile`, { + headers: { + Authorization: `Bearer ${token}`, + }, + }); + + json = await res.json(); + } catch (e) { + err = e; + } + + return (game, playtype, userID, ratings, logger) => { + if (err) { + logger.error(`An error occured while updating classes for ${baseUrl}.`, { err }); + return {}; + } + + const sdvxDan: number | null = json.skill_level; + + if (sdvxDan === null) { + return {}; + } + + if (!Number.isInteger(sdvxDan)) { + logger.warn(`${baseUrl} returned a dan of ${sdvxDan}, which was not a number.`); + return {}; + } + + if (sdvxDan > SDVXDans.INF) { + logger.warn( + `${baseUrl} returned a dan of ${sdvxDan}, which was greater than INF (${SDVXDans.INF}.)` + ); + return {}; + } + + if (sdvxDan < SDVXDans.DAN_1) { + logger.warn( + `${baseUrl} returned a dan of ${sdvxDan}, which was less than DAN_1 (${SDVXDans.DAN_1}.)` + ); + return {}; + } + + return { + dan: sdvxDan, + }; + }; +} diff --git a/server/src/lib/score-import/import-types/common/api-kai/sdvx/parser.ts b/server/src/lib/score-import/import-types/common/api-kai/sdvx/parser.ts index a1d3fec6e..21954b3b4 100644 --- a/server/src/lib/score-import/import-types/common/api-kai/sdvx/parser.ts +++ b/server/src/lib/score-import/import-types/common/api-kai/sdvx/parser.ts @@ -5,14 +5,15 @@ import { KaiContext } from "../types"; import { TraverseKaiAPI } from "../traverse-api"; import { ParserFunctionReturns } from "../../types"; import { ServerConfig } from "lib/setup/config"; +import { KaiTypeToBaseURL } from "utils/misc"; export function ParseKaiSDVX( - service: "FLO" | "EAG", + service: "FLO" | "EAG" | "MIN", authDoc: KaiAuthDocument, logger: KtLogger, fetch = nodeFetch ): ParserFunctionReturns { - const baseUrl = service === "FLO" ? ServerConfig.FLO_API_URL : ServerConfig.EAG_API_URL; + const baseUrl = KaiTypeToBaseURL(service); return { iterable: TraverseKaiAPI( diff --git a/server/src/lib/score-import/import-types/common/api-kai/types.ts b/server/src/lib/score-import/import-types/common/api-kai/types.ts index 0ac4d99e1..5590a19c1 100644 --- a/server/src/lib/score-import/import-types/common/api-kai/types.ts +++ b/server/src/lib/score-import/import-types/common/api-kai/types.ts @@ -31,5 +31,5 @@ export interface KaiSDVXScore { } export interface KaiContext { - service: "FLO" | "EAG"; + service: "FLO" | "EAG" | "MIN"; } diff --git a/server/src/lib/score-import/import-types/common/types.ts b/server/src/lib/score-import/import-types/common/types.ts index cc9e7651e..f78c3c386 100644 --- a/server/src/lib/score-import/import-types/common/types.ts +++ b/server/src/lib/score-import/import-types/common/types.ts @@ -44,6 +44,7 @@ export interface ImportTypeDataMap { "api/arc-ddr": unknown; "api/flo-iidx": unknown; "api/flo-sdvx": unknown; + "api/min-sdvx": unknown; "api/eag-iidx": unknown; "api/eag-sdvx": unknown; } @@ -68,6 +69,7 @@ export interface ImportTypeContextMap { "api/arc-ddr": EmptyObject; "api/flo-iidx": KaiContext; "api/flo-sdvx": KaiContext; + "api/min-sdvx": KaiContext; "api/eag-iidx": KaiContext; "api/eag-sdvx": KaiContext; } diff --git a/server/src/lib/score-import/import-types/converters.ts b/server/src/lib/score-import/import-types/converters.ts index a3ebe469b..6f8ed5f80 100644 --- a/server/src/lib/score-import/import-types/converters.ts +++ b/server/src/lib/score-import/import-types/converters.ts @@ -30,6 +30,7 @@ export const Converters: ConverterMap = { "api/eag-sdvx": ConvertAPIKaiSDVX, "api/flo-iidx": ConvertAPIKaiIIDX, "api/flo-sdvx": ConvertAPIKaiSDVX, + "api/min-sdvx": ConvertAPIKaiSDVX, "api/arc-iidx": ConvertAPIArcIIDX, "api/arc-ddr": ConvertAPIArcDDR, "api/arc-sdvx": ConvertAPIArcSDVX, diff --git a/server/src/lib/setup/config.ts b/server/src/lib/setup/config.ts index e3cb6eebc..be1730def 100644 --- a/server/src/lib/setup/config.ts +++ b/server/src/lib/setup/config.ts @@ -45,6 +45,7 @@ export interface TachiConfig { SESSION_SECRET: string; FLO_API_URL: string; EAG_API_URL: string; + MIN_API_URL: string; ARC_API_URL: string; ARC_AUTH_TOKEN: string; CDN_FILE_ROOT: string; @@ -64,6 +65,7 @@ const err = p(config, { SESSION_SECRET: "string", FLO_API_URL: isValidURL, EAG_API_URL: isValidURL, + MIN_API_URL: isValidURL, ARC_API_URL: isValidURL, ARC_AUTH_TOKEN: "string", CDN_FILE_ROOT: "string", diff --git a/server/src/test-utils/mock-fetch.ts b/server/src/test-utils/mock-fetch.ts index 8a471a900..eb8bd658a 100644 --- a/server/src/test-utils/mock-fetch.ts +++ b/server/src/test-utils/mock-fetch.ts @@ -22,6 +22,10 @@ export function MockJSONFetch(urlDataMap: Record) { }; } - throw new Error(`Unexpected url ${url} - No Data Present?`); + throw new Error( + `Unexpected url ${url} - No Data Present? Valid urls are ${Object.keys(urlDataMap).join( + ", " + )}` + ); }) as NodeFetch; } diff --git a/server/src/utils/misc.ts b/server/src/utils/misc.ts index 3fbe19e81..095273ad7 100644 --- a/server/src/utils/misc.ts +++ b/server/src/utils/misc.ts @@ -1,5 +1,5 @@ import crypto from "crypto"; -import { ServerTypeInfo } from "lib/setup/config"; +import { ServerConfig, ServerTypeInfo } from "lib/setup/config"; import { Game, Playtypes, GetGameConfig, AnySongDocument, AnyChartDocument } from "tachi-common"; // https://github.com/sindresorhus/escape-string-regexp/blob/main/index.js @@ -76,3 +76,15 @@ export function IsString(val: unknown): val is string { export function DedupeArr(arr: T[]): T[] { return [...new Set(arr)]; } + +export function KaiTypeToBaseURL(kaiType: "FLO" | "EAG" | "MIN") { + if (kaiType === "FLO") { + return ServerConfig.FLO_API_URL; + } else if (kaiType === "EAG") { + return ServerConfig.EAG_API_URL; + } else if (kaiType === "MIN") { + return ServerConfig.MIN_API_URL; + } + + throw new Error(`Invalid Kai Type ${kaiType} provided.`); +} From c173b67345d019c4a0bc464692e4a31eb732455f Mon Sep 17 00:00:00 2001 From: zkldi Date: Wed, 11 Aug 2021 03:56:50 +0100 Subject: [PATCH 2/3] Fix broken tests --- .../import-types/common/api-kai/iidx/parser.test.ts | 4 ++-- .../import-types/common/api-kai/sdvx/class-handler.ts | 2 +- .../import-types/common/api-kai/sdvx/parser.test.ts | 4 ++-- .../import-types/common/api-kai/sdvx/parser.ts | 7 ++++--- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/server/src/lib/score-import/import-types/common/api-kai/iidx/parser.test.ts b/server/src/lib/score-import/import-types/common/api-kai/iidx/parser.test.ts index 1fbe4242b..5f0c33a86 100644 --- a/server/src/lib/score-import/import-types/common/api-kai/iidx/parser.test.ts +++ b/server/src/lib/score-import/import-types/common/api-kai/iidx/parser.test.ts @@ -33,7 +33,7 @@ t.test("#ParseKaiIIDX", (t) => { }, }); - const res = ParseKaiIIDX("FLO", fakeAuth, logger, mockFloAPI); + const res = await ParseKaiIIDX("FLO", fakeAuth, logger, mockFloAPI); t.equal(res.game, "iidx"); t.equal(res.classHandler, null); @@ -66,7 +66,7 @@ t.test("#ParseKaiIIDX", (t) => { }, }); - const res = ParseKaiIIDX("EAG", fakeAuth, logger, mockEagAPI); + const res = await ParseKaiIIDX("EAG", fakeAuth, logger, mockEagAPI); t.equal(res.game, "iidx"); t.equal(res.classHandler, null); diff --git a/server/src/lib/score-import/import-types/common/api-kai/sdvx/class-handler.ts b/server/src/lib/score-import/import-types/common/api-kai/sdvx/class-handler.ts index 64941f8bb..85eb8ebad 100644 --- a/server/src/lib/score-import/import-types/common/api-kai/sdvx/class-handler.ts +++ b/server/src/lib/score-import/import-types/common/api-kai/sdvx/class-handler.ts @@ -4,7 +4,7 @@ import nodeFetch from "utils/fetch"; import { KaiTypeToBaseURL } from "utils/misc"; export async function CreateKaiSDVXClassHandler( - kaiType: "FLO" | "EAG", + kaiType: "FLO" | "EAG" | "MIN", token: string, fetch = nodeFetch ): Promise { diff --git a/server/src/lib/score-import/import-types/common/api-kai/sdvx/parser.test.ts b/server/src/lib/score-import/import-types/common/api-kai/sdvx/parser.test.ts index 7f0039441..41e350945 100644 --- a/server/src/lib/score-import/import-types/common/api-kai/sdvx/parser.test.ts +++ b/server/src/lib/score-import/import-types/common/api-kai/sdvx/parser.test.ts @@ -33,7 +33,7 @@ t.test("#ParseKaiSDVX", (t) => { }, }); - const res = ParseKaiSDVX("FLO", fakeAuth, logger, mockFloAPI); + const res = await ParseKaiSDVX("FLO", fakeAuth, logger, mockFloAPI); t.equal(res.game, "sdvx"); t.equal(res.classHandler, null); @@ -66,7 +66,7 @@ t.test("#ParseKaiSDVX", (t) => { }, }); - const res = ParseKaiSDVX("EAG", fakeAuth, logger, mockEagAPI); + const res = await ParseKaiSDVX("EAG", fakeAuth, logger, mockEagAPI); t.equal(res.game, "sdvx"); t.equal(res.classHandler, null); diff --git a/server/src/lib/score-import/import-types/common/api-kai/sdvx/parser.ts b/server/src/lib/score-import/import-types/common/api-kai/sdvx/parser.ts index 21954b3b4..b0ca8246c 100644 --- a/server/src/lib/score-import/import-types/common/api-kai/sdvx/parser.ts +++ b/server/src/lib/score-import/import-types/common/api-kai/sdvx/parser.ts @@ -6,13 +6,14 @@ import { TraverseKaiAPI } from "../traverse-api"; import { ParserFunctionReturns } from "../../types"; import { ServerConfig } from "lib/setup/config"; import { KaiTypeToBaseURL } from "utils/misc"; +import { CreateKaiSDVXClassHandler } from "./class-handler"; -export function ParseKaiSDVX( +export async function ParseKaiSDVX( service: "FLO" | "EAG" | "MIN", authDoc: KaiAuthDocument, logger: KtLogger, fetch = nodeFetch -): ParserFunctionReturns { +): Promise> { const baseUrl = KaiTypeToBaseURL(service); return { @@ -26,7 +27,7 @@ export function ParseKaiSDVX( context: { service, }, - classHandler: null, + classHandler: await CreateKaiSDVXClassHandler(service, authDoc.token, fetch), game: "sdvx", }; } From 5f5f6d780aae3a2d340d7bc43c616a43d9d1c9b0 Mon Sep 17 00:00:00 2001 From: zkldi Date: Wed, 11 Aug 2021 03:58:52 +0100 Subject: [PATCH 3/3] fix false asserts --- .../import-types/common/api-kai/iidx/parser.test.ts | 2 -- .../import-types/common/api-kai/sdvx/parser.test.ts | 2 -- 2 files changed, 4 deletions(-) diff --git a/server/src/lib/score-import/import-types/common/api-kai/iidx/parser.test.ts b/server/src/lib/score-import/import-types/common/api-kai/iidx/parser.test.ts index 5f0c33a86..fa80cb5b8 100644 --- a/server/src/lib/score-import/import-types/common/api-kai/iidx/parser.test.ts +++ b/server/src/lib/score-import/import-types/common/api-kai/iidx/parser.test.ts @@ -36,7 +36,6 @@ t.test("#ParseKaiIIDX", (t) => { const res = await ParseKaiIIDX("FLO", fakeAuth, logger, mockFloAPI); t.equal(res.game, "iidx"); - t.equal(res.classHandler, null); t.strictSame(res.context, { service: "FLO" }); const iter = []; @@ -69,7 +68,6 @@ t.test("#ParseKaiIIDX", (t) => { const res = await ParseKaiIIDX("EAG", fakeAuth, logger, mockEagAPI); t.equal(res.game, "iidx"); - t.equal(res.classHandler, null); t.strictSame(res.context, { service: "EAG" }); const iter = []; diff --git a/server/src/lib/score-import/import-types/common/api-kai/sdvx/parser.test.ts b/server/src/lib/score-import/import-types/common/api-kai/sdvx/parser.test.ts index 41e350945..530df784d 100644 --- a/server/src/lib/score-import/import-types/common/api-kai/sdvx/parser.test.ts +++ b/server/src/lib/score-import/import-types/common/api-kai/sdvx/parser.test.ts @@ -36,7 +36,6 @@ t.test("#ParseKaiSDVX", (t) => { const res = await ParseKaiSDVX("FLO", fakeAuth, logger, mockFloAPI); t.equal(res.game, "sdvx"); - t.equal(res.classHandler, null); t.strictSame(res.context, { service: "FLO" }); const iter = []; @@ -69,7 +68,6 @@ t.test("#ParseKaiSDVX", (t) => { const res = await ParseKaiSDVX("EAG", fakeAuth, logger, mockEagAPI); t.equal(res.game, "sdvx"); - t.equal(res.classHandler, null); t.strictSame(res.context, { service: "EAG" }); const iter = [];