mirror of
https://github.com/zkldi/Tachi.git
synced 2026-10-03 04:18:16 +03:00
Merge pull request #276 from zkldi/zkldi/issue-275-Import-API-SYNC-method
Import "API-SYNC" method
This commit is contained in:
+1
-1
@@ -70,7 +70,7 @@
|
||||
"redis": "3.1.2",
|
||||
"rimraf": "3.0.2",
|
||||
"safe-json-stringify": "1.2.0",
|
||||
"tachi-common": "^0.1.6",
|
||||
"tachi-common": "^0.1.9",
|
||||
"typescript": "4.3.4",
|
||||
"winston": "3.3.3"
|
||||
},
|
||||
|
||||
Generated
+4
-4
@@ -51,7 +51,7 @@ specifiers:
|
||||
rimraf: 3.0.2
|
||||
safe-json-stringify: 1.2.0
|
||||
supertest: 6.1.3
|
||||
tachi-common: ^0.1.6
|
||||
tachi-common: ^0.1.9
|
||||
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.6_ts-node@10.0.0+typescript@4.3.4
|
||||
tachi-common: 0.1.9_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.6_ts-node@10.0.0+typescript@4.3.4:
|
||||
resolution: {integrity: sha512-H2kjAIUXyvNGuPlOgvWc25KmujETee4XqRRSG/BQIall0FSiO7yWApZQMelQZITebdlKwzpd976P2NSejTv0Yg==}
|
||||
/tachi-common/0.1.9_ts-node@10.0.0+typescript@4.3.4:
|
||||
resolution: {integrity: sha512-nNChWe41kwizagY9tS5PCHTTSbT7VP87lejXn8sX19x+StNax/KJDyGhwXEuYyqo0pCoPYttHGZc4Aar6SgVyQ==}
|
||||
dependencies:
|
||||
monk: 7.3.4
|
||||
tap: 15.0.9_ts-node@10.0.0+typescript@4.3.4
|
||||
|
||||
Vendored
+2
@@ -27,6 +27,7 @@ import {
|
||||
ImportLockDocument,
|
||||
UGPTSettings,
|
||||
SessionViewDocument,
|
||||
ARCSavedProfileDocument,
|
||||
} from "tachi-common";
|
||||
import monk, { TMiddleware } from "monk";
|
||||
import CreateLogCtx from "lib/logger/logger";
|
||||
@@ -146,6 +147,7 @@ const db = {
|
||||
"game-settings": monkDB.get<UGPTSettings>("game-settings"),
|
||||
"game-stats-snapshots": monkDB.get<UserGameStatsSnapshot>("game-stats-snapshots"),
|
||||
"session-view-cache": monkDB.get<SessionViewDocument>("session-view-cache"),
|
||||
"arc-saved-profiles": monkDB.get<ARCSavedProfileDocument>("arc-saved-profiles"),
|
||||
};
|
||||
|
||||
export default db;
|
||||
|
||||
@@ -5,29 +5,37 @@ import { MockJSONFetch } from "test-utils/mock-fetch";
|
||||
import ResetDBState from "test-utils/resets";
|
||||
import CreateLogCtx from "lib/logger/logger";
|
||||
import { ParseArcDDR } from "./parser";
|
||||
import db from "external/mongo/db";
|
||||
|
||||
const logger = CreateLogCtx(__filename);
|
||||
|
||||
t.test("#ParseArcDDR", (t) => {
|
||||
t.beforeEach(ResetDBState);
|
||||
|
||||
const mockArcAPI = MockJSONFetch({
|
||||
"https://arc.example.com/api/v1/ddr/16/player_bests?profile_id=profile": {
|
||||
_links: {
|
||||
_next: "https://arc.example.com/api/v1/ddr/16/player_bests?profile_id=profile&page=2",
|
||||
},
|
||||
_items: [1, 2, 3],
|
||||
},
|
||||
"https://arc.example.com/api/v1/ddr/16/player_bests?profile_id=profile&page=2": {
|
||||
_links: {
|
||||
_next: null,
|
||||
},
|
||||
_items: [4, 5, 6],
|
||||
},
|
||||
});
|
||||
|
||||
t.test("Should iterate over the API.", async (t) => {
|
||||
const mockArcAPI = MockJSONFetch({
|
||||
"https://arc.example.com/api/v1/ddr/16/player_bests?profile_id=profile": {
|
||||
_links: {
|
||||
_next: "https://arc.example.com/api/v1/ddr/16/player_bests?profile_id=profile&page=2",
|
||||
},
|
||||
_items: [1, 2, 3],
|
||||
},
|
||||
"https://arc.example.com/api/v1/ddr/16/player_bests?profile_id=profile&page=2": {
|
||||
_links: {
|
||||
_next: null,
|
||||
},
|
||||
_items: [4, 5, 6],
|
||||
},
|
||||
await db["arc-saved-profiles"].insert({
|
||||
userID: 1,
|
||||
accountID: "profile",
|
||||
forImportType: "api/arc-ddr",
|
||||
name: "foo",
|
||||
});
|
||||
|
||||
const res = ParseArcDDR("profile", logger, mockArcAPI);
|
||||
const res = await ParseArcDDR(1, logger, mockArcAPI);
|
||||
|
||||
t.equal(res.game, "ddr");
|
||||
t.strictSame(res.context, {});
|
||||
@@ -40,6 +48,25 @@ t.test("#ParseArcDDR", (t) => {
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.test("Should throw a fatal error if no saved profile exists for this user.", async (t) => {
|
||||
t.rejects(() => ParseArcDDR(1, logger, mockArcAPI), {
|
||||
message: /No authentication was stored for api\/arc-ddr/iu,
|
||||
});
|
||||
|
||||
await db["arc-saved-profiles"].insert({
|
||||
userID: 1,
|
||||
accountID: "profile",
|
||||
forImportType: "api/arc-sdvx",
|
||||
name: "SDVX PROFILE",
|
||||
});
|
||||
|
||||
t.rejects(() => ParseArcDDR(1, logger, mockArcAPI), {
|
||||
message: /No authentication was stored for api\/arc-ddr/iu,
|
||||
});
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
|
||||
@@ -4,17 +4,21 @@ import { TraverseKaiAPI } from "../../common/api-kai/traverse-api";
|
||||
import { ParserFunctionReturns } from "../../common/types";
|
||||
import { EmptyObject } from "utils/types";
|
||||
import { ServerConfig } from "lib/setup/config";
|
||||
import { integer } from "tachi-common";
|
||||
import { GetArcAuthGuaranteed } from "utils/queries/auth";
|
||||
|
||||
export function ParseArcDDR(
|
||||
arcProfileID: string,
|
||||
export async function ParseArcDDR(
|
||||
userID: integer,
|
||||
logger: KtLogger,
|
||||
fetch = nodeFetch
|
||||
): ParserFunctionReturns<unknown, EmptyObject> {
|
||||
): Promise<ParserFunctionReturns<unknown, EmptyObject>> {
|
||||
const authDoc = await GetArcAuthGuaranteed(userID, "api/arc-ddr", logger);
|
||||
|
||||
return {
|
||||
iterable: TraverseKaiAPI(
|
||||
ServerConfig.ARC_API_URL,
|
||||
// DDR Ace.
|
||||
`/api/v1/ddr/16/player_bests?profile_id=${arcProfileID}`,
|
||||
`/api/v1/ddr/16/player_bests?profile_id=${authDoc.accountID}`,
|
||||
ServerConfig.ARC_AUTH_TOKEN,
|
||||
logger,
|
||||
fetch
|
||||
|
||||
@@ -5,29 +5,37 @@ import { MockJSONFetch } from "test-utils/mock-fetch";
|
||||
import ResetDBState from "test-utils/resets";
|
||||
import CreateLogCtx from "lib/logger/logger";
|
||||
import { ParseArcIIDX } from "./parser";
|
||||
import db from "external/mongo/db";
|
||||
|
||||
const logger = CreateLogCtx(__filename);
|
||||
|
||||
t.test("#ParseArcIIDX", (t) => {
|
||||
t.beforeEach(ResetDBState);
|
||||
|
||||
const mockArcAPI = MockJSONFetch({
|
||||
"https://arc.example.com/api/v1/iidx/27/player_bests?profile_id=profile": {
|
||||
_links: {
|
||||
_next: "https://arc.example.com/api/v1/iidx/27/player_bests?profile_id=profile&page=2",
|
||||
},
|
||||
_items: [1, 2, 3],
|
||||
},
|
||||
"https://arc.example.com/api/v1/iidx/27/player_bests?profile_id=profile&page=2": {
|
||||
_links: {
|
||||
_next: null,
|
||||
},
|
||||
_items: [4, 5, 6],
|
||||
},
|
||||
});
|
||||
|
||||
t.test("Should iterate over the API.", async (t) => {
|
||||
const mockArcAPI = MockJSONFetch({
|
||||
"https://arc.example.com/api/v1/iidx/27/player_bests?profile_id=profile": {
|
||||
_links: {
|
||||
_next: "https://arc.example.com/api/v1/iidx/27/player_bests?profile_id=profile&page=2",
|
||||
},
|
||||
_items: [1, 2, 3],
|
||||
},
|
||||
"https://arc.example.com/api/v1/iidx/27/player_bests?profile_id=profile&page=2": {
|
||||
_links: {
|
||||
_next: null,
|
||||
},
|
||||
_items: [4, 5, 6],
|
||||
},
|
||||
await db["arc-saved-profiles"].insert({
|
||||
userID: 1,
|
||||
accountID: "profile",
|
||||
forImportType: "api/arc-iidx",
|
||||
name: "foo",
|
||||
});
|
||||
|
||||
const res = await ParseArcIIDX("profile", logger, mockArcAPI);
|
||||
const res = await ParseArcIIDX(1, logger, mockArcAPI);
|
||||
|
||||
t.equal(res.game, "iidx");
|
||||
t.strictSame(res.context, {});
|
||||
@@ -39,6 +47,25 @@ t.test("#ParseArcIIDX", (t) => {
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.test("Should throw a fatal error if no saved profile exists for this user.", async (t) => {
|
||||
t.rejects(() => ParseArcIIDX(1, logger, mockArcAPI), {
|
||||
message: /No authentication was stored for api\/arc-iidx/iu,
|
||||
});
|
||||
|
||||
await db["arc-saved-profiles"].insert({
|
||||
userID: 1,
|
||||
accountID: "profile",
|
||||
forImportType: "api/arc-sdvx",
|
||||
name: "SDVX PROFILE",
|
||||
});
|
||||
|
||||
t.rejects(() => ParseArcIIDX(1, logger, mockArcAPI), {
|
||||
message: /No authentication was stored for api\/arc-iidx/iu,
|
||||
});
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
|
||||
@@ -5,24 +5,28 @@ import { ParserFunctionReturns } from "../../common/types";
|
||||
import { EmptyObject } from "utils/types";
|
||||
import { ServerConfig } from "lib/setup/config";
|
||||
import { CreateArcIIDXClassHandler } from "./class-handler";
|
||||
import { GetArcAuthGuaranteed } from "utils/queries/auth";
|
||||
import { integer } from "tachi-common";
|
||||
|
||||
export async function ParseArcIIDX(
|
||||
arcProfileID: string,
|
||||
userID: integer,
|
||||
logger: KtLogger,
|
||||
fetch = nodeFetch
|
||||
): Promise<ParserFunctionReturns<unknown, EmptyObject>> {
|
||||
const authDoc = await GetArcAuthGuaranteed(userID, "api/arc-iidx", logger);
|
||||
|
||||
return {
|
||||
iterable: TraverseKaiAPI(
|
||||
ServerConfig.ARC_API_URL,
|
||||
// HEROIC VERSE
|
||||
`/api/v1/iidx/27/player_bests?profile_id=${arcProfileID}`,
|
||||
`/api/v1/iidx/27/player_bests?profile_id=${authDoc.accountID}`,
|
||||
ServerConfig.ARC_AUTH_TOKEN,
|
||||
logger,
|
||||
fetch
|
||||
),
|
||||
context: {},
|
||||
classHandler: await CreateArcIIDXClassHandler(
|
||||
arcProfileID,
|
||||
authDoc.accountID,
|
||||
ServerConfig.ARC_AUTH_TOKEN,
|
||||
fetch
|
||||
),
|
||||
|
||||
@@ -5,29 +5,36 @@ import { MockJSONFetch } from "test-utils/mock-fetch";
|
||||
import ResetDBState from "test-utils/resets";
|
||||
import CreateLogCtx from "lib/logger/logger";
|
||||
import { ParseArcSDVX } from "./parser";
|
||||
import db from "external/mongo/db";
|
||||
|
||||
const logger = CreateLogCtx(__filename);
|
||||
|
||||
t.test("#ParseArcSDVX", (t) => {
|
||||
t.beforeEach(ResetDBState);
|
||||
const mockArcAPI = MockJSONFetch({
|
||||
"https://arc.example.com/api/v1/sdvx/5/player_bests?profile_id=profile": {
|
||||
_links: {
|
||||
_next: "https://arc.example.com/api/v1/sdvx/5/player_bests?profile_id=profile&page=2",
|
||||
},
|
||||
_items: [1, 2, 3],
|
||||
},
|
||||
"https://arc.example.com/api/v1/sdvx/5/player_bests?profile_id=profile&page=2": {
|
||||
_links: {
|
||||
_next: null,
|
||||
},
|
||||
_items: [4, 5, 6],
|
||||
},
|
||||
});
|
||||
|
||||
t.test("Should iterate over the API.", async (t) => {
|
||||
const mockArcAPI = MockJSONFetch({
|
||||
"https://arc.example.com/api/v1/sdvx/5/player_bests?profile_id=profile": {
|
||||
_links: {
|
||||
_next: "https://arc.example.com/api/v1/sdvx/5/player_bests?profile_id=profile&page=2",
|
||||
},
|
||||
_items: [1, 2, 3],
|
||||
},
|
||||
"https://arc.example.com/api/v1/sdvx/5/player_bests?profile_id=profile&page=2": {
|
||||
_links: {
|
||||
_next: null,
|
||||
},
|
||||
_items: [4, 5, 6],
|
||||
},
|
||||
await db["arc-saved-profiles"].insert({
|
||||
userID: 1,
|
||||
accountID: "profile",
|
||||
forImportType: "api/arc-sdvx",
|
||||
name: "foo",
|
||||
});
|
||||
|
||||
const res = ParseArcSDVX("profile", logger, mockArcAPI);
|
||||
const res = await ParseArcSDVX(1, logger, mockArcAPI);
|
||||
|
||||
t.equal(res.game, "sdvx");
|
||||
t.strictSame(res.context, {});
|
||||
@@ -40,6 +47,25 @@ t.test("#ParseArcSDVX", (t) => {
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.test("Should throw a fatal error if no saved profile exists for this user.", async (t) => {
|
||||
t.rejects(() => ParseArcSDVX(1, logger, mockArcAPI), {
|
||||
message: /No authentication was stored for api\/arc-sdvx/iu,
|
||||
});
|
||||
|
||||
await db["arc-saved-profiles"].insert({
|
||||
userID: 1,
|
||||
accountID: "profile",
|
||||
forImportType: "api/arc-iidx",
|
||||
name: "IIDX PROFILE",
|
||||
});
|
||||
|
||||
t.rejects(() => ParseArcSDVX(1, logger, mockArcAPI), {
|
||||
message: /No authentication was stored for api\/arc-sdvx/iu,
|
||||
});
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
|
||||
@@ -4,17 +4,21 @@ import { TraverseKaiAPI } from "../../common/api-kai/traverse-api";
|
||||
import { ParserFunctionReturns } from "../../common/types";
|
||||
import { EmptyObject } from "utils/types";
|
||||
import { ServerConfig } from "lib/setup/config";
|
||||
import { GetArcAuthGuaranteed } from "utils/queries/auth";
|
||||
import { integer } from "tachi-common";
|
||||
|
||||
export function ParseArcSDVX(
|
||||
arcProfileID: string,
|
||||
export async function ParseArcSDVX(
|
||||
userID: integer,
|
||||
logger: KtLogger,
|
||||
fetch = nodeFetch
|
||||
): ParserFunctionReturns<unknown, EmptyObject> {
|
||||
): Promise<ParserFunctionReturns<unknown, EmptyObject>> {
|
||||
const authDoc = await GetArcAuthGuaranteed(userID, "api/arc-sdvx", logger);
|
||||
|
||||
return {
|
||||
iterable: TraverseKaiAPI(
|
||||
ServerConfig.ARC_API_URL,
|
||||
// VIVID WAVE.
|
||||
`/api/v1/sdvx/5/player_bests?profile_id=${arcProfileID}`,
|
||||
`/api/v1/sdvx/5/player_bests?profile_id=${authDoc.accountID}`,
|
||||
ServerConfig.ARC_AUTH_TOKEN,
|
||||
logger,
|
||||
fetch
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { KtLogger } from "lib/logger/logger";
|
||||
import { ParseKaiIIDX } from "../../common/api-kai/iidx/parser";
|
||||
import { KaiAuthDocument } from "tachi-common";
|
||||
import { integer } from "tachi-common";
|
||||
import { GetKaiAuthGuaranteed } from "utils/queries/auth";
|
||||
|
||||
export async function ParseEagIIDX(userID: integer, logger: KtLogger) {
|
||||
const authDoc = await GetKaiAuthGuaranteed(userID, "EAG", logger);
|
||||
|
||||
export function ParseEagIIDX(authDoc: KaiAuthDocument, logger: KtLogger) {
|
||||
return ParseKaiIIDX("EAG", authDoc, logger);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { KtLogger } from "lib/logger/logger";
|
||||
import { KaiAuthDocument } from "tachi-common";
|
||||
import { integer, KaiAuthDocument } from "tachi-common";
|
||||
import { GetKaiAuthGuaranteed } from "utils/queries/auth";
|
||||
import { ParseKaiSDVX } from "../../common/api-kai/sdvx/parser";
|
||||
|
||||
export function ParseEagSDVX(authDoc: KaiAuthDocument, logger: KtLogger) {
|
||||
export async function ParseEagSDVX(userID: integer, logger: KtLogger) {
|
||||
const authDoc = await GetKaiAuthGuaranteed(userID, "EAG", logger);
|
||||
|
||||
return ParseKaiSDVX("EAG", authDoc, logger);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { KtLogger } from "lib/logger/logger";
|
||||
import { ParseKaiIIDX } from "../../common/api-kai/iidx/parser";
|
||||
import { KaiAuthDocument } from "tachi-common";
|
||||
import { integer } from "tachi-common";
|
||||
import { GetKaiAuthGuaranteed } from "utils/queries/auth";
|
||||
|
||||
export async function ParseFloIIDX(userID: integer, logger: KtLogger) {
|
||||
const authDoc = await GetKaiAuthGuaranteed(userID, "FLO", logger);
|
||||
|
||||
export function ParseFloIIDX(authDoc: KaiAuthDocument, logger: KtLogger) {
|
||||
return ParseKaiIIDX("FLO", authDoc, logger);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { KtLogger } from "lib/logger/logger";
|
||||
import { KaiAuthDocument } from "tachi-common";
|
||||
import { integer, KaiAuthDocument } from "tachi-common";
|
||||
import { GetKaiAuthGuaranteed } from "utils/queries/auth";
|
||||
import { ParseKaiSDVX } from "../../common/api-kai/sdvx/parser";
|
||||
|
||||
export function ParseFloSDVX(authDoc: KaiAuthDocument, logger: KtLogger) {
|
||||
export async function ParseFloSDVX(userID: integer, logger: KtLogger) {
|
||||
const authDoc = await GetKaiAuthGuaranteed(userID, "FLO", logger);
|
||||
|
||||
return ParseKaiSDVX("FLO", authDoc, logger);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { KtLogger } from "lib/logger/logger";
|
||||
import { KaiAuthDocument } from "tachi-common";
|
||||
import { integer } from "tachi-common";
|
||||
import { GetKaiAuthGuaranteed } from "utils/queries/auth";
|
||||
import { ParseKaiSDVX } from "../../common/api-kai/sdvx/parser";
|
||||
|
||||
export function ParseMinSDVX(authDoc: KaiAuthDocument, logger: KtLogger) {
|
||||
export async function ParseMinSDVX(userID: integer, logger: KtLogger) {
|
||||
const authDoc = await GetKaiAuthGuaranteed(userID, "MIN", logger);
|
||||
|
||||
return ParseKaiSDVX("MIN", authDoc, logger);
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ function ParseBatchManual(
|
||||
} catch (err) {
|
||||
throw new ScoreImportFatalError(
|
||||
400,
|
||||
`Invalid JSON. (${err?.message ?? "No Error Message Available."})`
|
||||
`Invalid JSON. (${(err as Error)?.message ?? "No Error Message Available."})`
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,11 @@ function isValidURL(self: unknown) {
|
||||
}
|
||||
}
|
||||
|
||||
interface OAuth2Info {
|
||||
CLIENT_ID: string;
|
||||
CLIENT_SECRET: string;
|
||||
}
|
||||
|
||||
export interface TachiConfig {
|
||||
MONGO_CONNECTION_URL: string;
|
||||
MONGO_DATABASE_NAME: string;
|
||||
@@ -47,6 +52,9 @@ export interface TachiConfig {
|
||||
EAG_API_URL: string;
|
||||
MIN_API_URL: string;
|
||||
ARC_API_URL: string;
|
||||
FLO_OAUTH2_INFO?: OAuth2Info;
|
||||
EAG_OAUTH2_INFO?: OAuth2Info;
|
||||
MIN_OAUTH2_INFO?: OAuth2Info;
|
||||
ARC_AUTH_TOKEN: string;
|
||||
CDN_FILE_ROOT: string;
|
||||
TYPE: "ktchi" | "btchi" | "omni";
|
||||
@@ -57,6 +65,11 @@ export interface TachiConfig {
|
||||
SERVER_TYPE_INFO: StaticConfig.ServerConfig;
|
||||
}
|
||||
|
||||
const isValidOauth2 = p.optional({
|
||||
CLIENT_ID: "string",
|
||||
CLIENT_SECRET: "string",
|
||||
});
|
||||
|
||||
const err = p(config, {
|
||||
MONGO_CONNECTION_URL: "string",
|
||||
MONGO_DATABASE_NAME: "string",
|
||||
@@ -67,6 +80,9 @@ const err = p(config, {
|
||||
EAG_API_URL: isValidURL,
|
||||
MIN_API_URL: isValidURL,
|
||||
ARC_API_URL: isValidURL,
|
||||
FLO_OAUTH2_INFO: isValidOauth2,
|
||||
EAG_OAUTH2_INFO: isValidOauth2,
|
||||
MIN_OAUTH2_INFO: isValidOauth2,
|
||||
ARC_AUTH_TOKEN: "string",
|
||||
CDN_FILE_ROOT: "string",
|
||||
PORT: p.isPositiveInteger,
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import t from "tap";
|
||||
import mockApi from "test-utils/mock-api";
|
||||
import { GetGamePTConfig } from "tachi-common";
|
||||
import { GetGamePTConfig, UserGameStats } from "tachi-common";
|
||||
import { CloseAllConnections } from "test-utils/close-connections";
|
||||
import ResetDBState from "test-utils/resets";
|
||||
import db from "external/mongo/db";
|
||||
|
||||
t.test("GET /api/v1/games/:game/:playtype", (t) => {
|
||||
t.test("Should return information about the game:playtype.", async (t) => {
|
||||
@@ -25,4 +27,76 @@ t.test("GET /api/v1/games/:game/:playtype", (t) => {
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.test("GET /api/v1/games/:game/:playtype/leaderboard", (t) => {
|
||||
t.beforeEach(ResetDBState);
|
||||
|
||||
t.test("Should return the leaderboards for this game", async (t) => {
|
||||
const res = await mockApi.get("/api/v1/games/iidx/SP/leaderboard");
|
||||
|
||||
t.equal(res.statusCode, 200);
|
||||
|
||||
t.equal(res.body.body.gameStats.length, 1);
|
||||
t.equal(res.body.body.users.length, 1);
|
||||
|
||||
t.hasStrict(res.body.body, {
|
||||
gameStats: [
|
||||
{
|
||||
userID: 1,
|
||||
game: "iidx",
|
||||
playtype: "SP",
|
||||
},
|
||||
],
|
||||
users: [
|
||||
{
|
||||
id: 1,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.test("Should reject unknown alg", async (t) => {
|
||||
const res = await mockApi.get("/api/v1/games/iidx/SP/leaderboard?alg=naiveRating");
|
||||
|
||||
t.equal(res.statusCode, 400);
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.test("Should use provided algorithm to resort results.", async (t) => {
|
||||
await db["game-stats"].insert([
|
||||
{
|
||||
userID: 2,
|
||||
game: "iidx",
|
||||
playtype: "SP",
|
||||
ratings: {
|
||||
BPI: 100,
|
||||
ktRating: 50,
|
||||
},
|
||||
},
|
||||
{
|
||||
userID: 3,
|
||||
game: "iidx",
|
||||
playtype: "SP",
|
||||
ratings: {
|
||||
BPI: 50,
|
||||
ktRating: 100,
|
||||
},
|
||||
},
|
||||
] as UserGameStats[]);
|
||||
|
||||
const res = await mockApi.get("/api/v1/games/iidx/SP/leaderboard?alg=BPI");
|
||||
|
||||
t.strictSame(
|
||||
res.body.body.gameStats.map((e: UserGameStats) => e.userID),
|
||||
[2, 3, 1]
|
||||
);
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.teardown(CloseAllConnections);
|
||||
|
||||
@@ -37,7 +37,6 @@ router.get("/", (req, res) => {
|
||||
* Returns user-game-stats for this game in batches of 100.
|
||||
* This is sorted by the games default-sorting-statistic.
|
||||
*
|
||||
* @param start - A number dictating what value to start from.
|
||||
* @param alg - An alternative algorithm to use instead of the gpts default.
|
||||
*
|
||||
* @name GET /api/v1/games/:game/:playtype/leaderboard
|
||||
@@ -47,18 +46,6 @@ router.get("/leaderboard", async (req, res) => {
|
||||
const playtype = req[SYMBOL_TachiData]!.playtype!;
|
||||
const gptConfig = GetGamePTConfig(game, playtype);
|
||||
|
||||
let start: null | integer = null;
|
||||
if (IsString(req.query.start)) {
|
||||
start = ParseStrPositiveInt(req.query.start);
|
||||
|
||||
if (start === null) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
description: `Invalid value of ${req.query.start} for start.`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let alg = gptConfig.defaultProfileRatingAlg;
|
||||
if (IsString(req.query.alg)) {
|
||||
const temp = CheckStrProfileAlg(game, playtype, req.query.alg);
|
||||
@@ -81,10 +68,6 @@ router.get("/leaderboard", async (req, res) => {
|
||||
},
|
||||
};
|
||||
|
||||
if (start !== null) {
|
||||
options.skip = start * 100;
|
||||
}
|
||||
|
||||
const gameStats = await db["game-stats"].find(
|
||||
{
|
||||
game,
|
||||
|
||||
@@ -225,6 +225,20 @@ t.test("POST /api/v1/import/file", async (t) => {
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.test("Invalid JSON", async (t) => {
|
||||
const res = await mockApi
|
||||
.post("/api/v1/import/file")
|
||||
.set("Cookie", cookie)
|
||||
.attach("scoreData", Buffer.from("{invalid JSON"))
|
||||
.field("importType", "file/batch-manual");
|
||||
|
||||
t.equal(res.body.success, false, "Should be successful.");
|
||||
|
||||
t.equal(res.statusCode, 400);
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.test("Single import", async (t) => {
|
||||
const res = await mockApi
|
||||
.post("/api/v1/import/file")
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Router } from "express";
|
||||
import { FileUploadImportTypes } from "tachi-common";
|
||||
import { APIImportTypes, FileUploadImportTypes, integer } from "tachi-common";
|
||||
import Prudence from "prudence";
|
||||
import { GetUserWithIDGuaranteed } from "utils/user";
|
||||
import CreateLogCtx, { KtLogger } from "lib/logger/logger";
|
||||
@@ -16,6 +16,14 @@ import { ParseMerIIDX } from "lib/score-import/import-types/file/mer-iidx/parser
|
||||
import ParsePLIIIDXCSV from "lib/score-import/import-types/file/pli-iidx-csv/parser";
|
||||
import { ServerTypeInfo } from "lib/setup/config";
|
||||
import { RequirePermissions } from "server/middleware/auth";
|
||||
import { ParseEagIIDX } from "lib/score-import/import-types/api/eag-iidx/parser";
|
||||
import { ParseEagSDVX } from "lib/score-import/import-types/api/eag-sdvx/parser";
|
||||
import { ParseFloIIDX } from "lib/score-import/import-types/api/flo-iidx/parser";
|
||||
import { ParseFloSDVX } from "lib/score-import/import-types/api/flo-sdvx/parser";
|
||||
import { ParseMinSDVX } from "lib/score-import/import-types/api/min-sdvx/parser";
|
||||
import { ParseArcDDR } from "lib/score-import/import-types/api/arc-ddr/parser";
|
||||
import { ParseArcSDVX } from "lib/score-import/import-types/api/arc-sdvx/parser";
|
||||
import { ParseArcIIDX } from "lib/score-import/import-types/api/arc-iidx/parser";
|
||||
|
||||
const logger = CreateLogCtx(__filename);
|
||||
|
||||
@@ -27,6 +35,8 @@ const ParseMultipartScoredata = CreateMulterSingleUploadMiddleware(
|
||||
logger
|
||||
);
|
||||
|
||||
const fileImportTypes = ServerTypeInfo.supportedImportTypes.filter((e) => e.startsWith("file/"));
|
||||
|
||||
/**
|
||||
* Import scores from a file. Expects the post request to be multipart, and to provide a scoreData file.
|
||||
* @name POST /api/v1/import/file
|
||||
@@ -37,7 +47,7 @@ router.post(
|
||||
ParseMultipartScoredata,
|
||||
prValidate(
|
||||
{
|
||||
importType: Prudence.isIn(ServerTypeInfo.supportedImportTypes),
|
||||
importType: Prudence.isIn(fileImportTypes),
|
||||
},
|
||||
{},
|
||||
{ allowExcessKeys: true }
|
||||
@@ -74,6 +84,43 @@ router.post(
|
||||
}
|
||||
);
|
||||
|
||||
const apiImportTypes = ServerTypeInfo.supportedImportTypes.filter((e) => e.startsWith("api/"));
|
||||
|
||||
/**
|
||||
* Import scores from another API. This typically will perform a full sync.
|
||||
* @name POST /api/v1/import/from-api
|
||||
*/
|
||||
router.post(
|
||||
"/from-api",
|
||||
RequirePermissions("submit_score"),
|
||||
prValidate(
|
||||
{
|
||||
importType: Prudence.isIn(apiImportTypes),
|
||||
},
|
||||
{},
|
||||
{ allowExcessKeys: true }
|
||||
),
|
||||
async (req, res) => {
|
||||
const importType = req.body.importType as APIImportTypes;
|
||||
|
||||
const userDoc = await GetUserWithIDGuaranteed(req.session.tachi!.userID);
|
||||
|
||||
const inputParser = (logger: KtLogger) =>
|
||||
ResolveAPIImportParser(userDoc.id, importType, logger);
|
||||
|
||||
// see the argument above about typescript falsely expanding types.
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const responseData = await ExpressWrappedScoreImportMain<any, any>(
|
||||
userDoc,
|
||||
true,
|
||||
importType,
|
||||
inputParser
|
||||
);
|
||||
|
||||
return res.status(responseData.statusCode).json(responseData.body);
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Resolves the data from a file upload into an iterable,
|
||||
* The appropriate processing function to map that iterable over,
|
||||
@@ -109,4 +156,32 @@ export function ResolveFileUploadData(
|
||||
}
|
||||
}
|
||||
|
||||
export function ResolveAPIImportParser(
|
||||
userID: integer,
|
||||
importType: APIImportTypes,
|
||||
logger: KtLogger
|
||||
) {
|
||||
switch (importType) {
|
||||
case "api/eag-iidx":
|
||||
return ParseEagIIDX(userID, logger);
|
||||
case "api/eag-sdvx":
|
||||
return ParseEagSDVX(userID, logger);
|
||||
case "api/flo-iidx":
|
||||
return ParseFloIIDX(userID, logger);
|
||||
case "api/flo-sdvx":
|
||||
return ParseFloSDVX(userID, logger);
|
||||
case "api/min-sdvx":
|
||||
return ParseMinSDVX(userID, logger);
|
||||
case "api/arc-ddr":
|
||||
return ParseArcDDR(userID, logger);
|
||||
case "api/arc-iidx":
|
||||
return ParseArcIIDX(userID, logger);
|
||||
case "api/arc-sdvx":
|
||||
return ParseArcSDVX(userID, logger);
|
||||
default:
|
||||
logger.error(`Unknown importType ${importType} has no handler?`);
|
||||
throw new ScoreImportFatalError(500, `Unknown importType ${importType}.`);
|
||||
}
|
||||
}
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
[]
|
||||
@@ -0,0 +1,51 @@
|
||||
import db from "external/mongo/db";
|
||||
import { KtLogger } from "lib/logger/logger";
|
||||
import ScoreImportFatalError from "lib/score-import/framework/score-importing/score-import-error";
|
||||
import { integer } from "tachi-common";
|
||||
|
||||
export function GetKaiAuth(userID: integer, service: "FLO" | "EAG" | "MIN") {
|
||||
return db["kai-auth-tokens"].findOne({
|
||||
userID,
|
||||
service,
|
||||
});
|
||||
}
|
||||
|
||||
export async function GetKaiAuthGuaranteed(
|
||||
userID: integer,
|
||||
service: "FLO" | "EAG" | "MIN",
|
||||
logger: KtLogger
|
||||
) {
|
||||
const authDoc = await GetKaiAuth(userID, service);
|
||||
|
||||
if (!authDoc) {
|
||||
logger.error(`No authentication was stored for ${service}.`);
|
||||
throw new ScoreImportFatalError(401, `No authentication was stored for ${service}.`);
|
||||
}
|
||||
|
||||
return authDoc;
|
||||
}
|
||||
|
||||
export function GetArcAuth(
|
||||
userID: integer,
|
||||
forImportType: "api/arc-iidx" | "api/arc-ddr" | "api/arc-sdvx"
|
||||
) {
|
||||
return db["arc-saved-profiles"].findOne({
|
||||
userID,
|
||||
forImportType,
|
||||
});
|
||||
}
|
||||
|
||||
export async function GetArcAuthGuaranteed(
|
||||
userID: integer,
|
||||
forImportType: "api/arc-iidx" | "api/arc-ddr" | "api/arc-sdvx",
|
||||
logger: KtLogger
|
||||
) {
|
||||
const authDoc = await GetArcAuth(userID, forImportType);
|
||||
|
||||
if (!authDoc) {
|
||||
logger.error(`No authentication was stored for ${forImportType}.`);
|
||||
throw new ScoreImportFatalError(401, `No authentication was stored for ${forImportType}.`);
|
||||
}
|
||||
|
||||
return authDoc;
|
||||
}
|
||||
Reference in New Issue
Block a user