Merge pull request #276 from zkldi/zkldi/issue-275-Import-API-SYNC-method

Import "API-SYNC" method
This commit is contained in:
zkldi
2021-08-11 19:47:37 +01:00
committed by GitHub
22 changed files with 412 additions and 89 deletions
+1 -1
View File
@@ -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"
},
+4 -4
View File
@@ -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
+2
View File
@@ -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."})`
);
}
+16
View File
@@ -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 @@
[]
+51
View File
@@ -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;
}