From 8bfe33d93cc193ccc84465f7173f249c56842324 Mon Sep 17 00:00:00 2001 From: zkldi Date: Mon, 26 Apr 2021 19:37:32 +0100 Subject: [PATCH] Sessions. --- server/src/core/session-core.ts | 8 + server/src/db/db.ts | 11 +- server/src/internal-api/import/import.test.ts | 30 ++ server/src/logger.ts | 13 +- .../core/sessions/name-generation.ts | 38 +++ .../core/sessions/performance-calc.ts | 44 +++ .../framework/core/sessions/sessions.ts | 275 ++++++++++++++++++ .../framework/importing/score-importing.ts | 11 +- .../framework/score-import-main.ts | 52 +++- .../file/csv-eamusement-iidx/parser.ts | 1 + server/src/scripts/set-indexes.ts | 65 +++++ server/src/test-utils/mock-db/adjectives.json | 5 + server/src/test-utils/mock-db/nouns.json | 3 + server/src/types.ts | 18 +- 14 files changed, 541 insertions(+), 33 deletions(-) create mode 100644 server/src/core/session-core.ts create mode 100644 server/src/score-import/framework/core/sessions/name-generation.ts create mode 100644 server/src/score-import/framework/core/sessions/performance-calc.ts create mode 100644 server/src/score-import/framework/core/sessions/sessions.ts create mode 100644 server/src/scripts/set-indexes.ts create mode 100644 server/src/test-utils/mock-db/adjectives.json create mode 100644 server/src/test-utils/mock-db/nouns.json diff --git a/server/src/core/session-core.ts b/server/src/core/session-core.ts new file mode 100644 index 000000000..11725eeba --- /dev/null +++ b/server/src/core/session-core.ts @@ -0,0 +1,8 @@ +import { SessionDocument } from "kamaitachi-common"; +import db from "../db/db"; + +export function GetScoresFromSession(session: SessionDocument) { + return db.scores.find({ + scoreID: { $in: session.scoreInfo.map((e) => e.scoreID) }, + }); +} diff --git a/server/src/db/db.ts b/server/src/db/db.ts index 5584a38b8..624c3d38d 100644 --- a/server/src/db/db.ts +++ b/server/src/db/db.ts @@ -24,6 +24,7 @@ import { } from "kamaitachi-common"; import monk, { ICollection } from "monk"; import CreateLogCtx from "../logger"; +import { TextDocument } from "../types"; const logger = CreateLogCtx("db.ts"); @@ -32,10 +33,10 @@ const base = process.env.MONGO_BASE_URL ?? "127.0.0.1"; const url = process.env.NODE_ENV === "test" ? `${base}:27017/testingdb` : `${base}:27017/ktblackdb`; let dbtime: [number, number] = [0, 0]; -// if (process.env.NODE_ENV !== "test") { -logger.info(`Connecting to database ${url}...`); -dbtime = process.hrtime(); -// } +if (process.env.NODE_ENV !== "test") { + logger.info(`Connecting to database ${url}...`); + dbtime = process.hrtime(); +} export let monkDB = monk(url); @@ -109,6 +110,8 @@ const db = { invites: monkDB.get("invites"), counters: monkDB.get("counters"), "iidx-eam-scores": monkDB.get("iidx-eam-scores"), + adjectives: monkDB.get("adjectives"), + nouns: monkDB.get("nouns"), }; export function GetGameChartCollection(game: Game) { diff --git a/server/src/internal-api/import/import.test.ts b/server/src/internal-api/import/import.test.ts index 350cd7a60..01f839801 100644 --- a/server/src/internal-api/import/import.test.ts +++ b/server/src/internal-api/import/import.test.ts @@ -52,6 +52,16 @@ t.test("POST /internal-api/import/file", async (t) => { t.equal(res.body.body.scoreIDs.length, 2, "Should have 2 successful scores."); + let scoreCount = await db.scores.find({ + scoreID: { $in: res.body.body.scoreIDs }, + }); + + t.equal( + scoreCount.length, + res.body.body.scoreIDs.length, + "All returned scoreIDs should be inserted to the DB." + ); + t.end(); }); @@ -67,6 +77,16 @@ t.test("POST /internal-api/import/file", async (t) => { t.equal(res.body.body.errors.length, 0, "Should have 0 failed scores."); + let scoreCount = await db.scores.find({ + scoreID: { $in: res.body.body.scoreIDs }, + }); + + t.equal( + scoreCount.length, + res.body.body.scoreIDs.length, + "All returned scoreIDs should be inserted to the DB." + ); + t.end(); }); @@ -82,6 +102,16 @@ t.test("POST /internal-api/import/file", async (t) => { t.equal(res.body.body.errors.length, 0, "Should have 0 failed scores."); + let scoreCount = await db.scores.find({ + scoreID: { $in: res.body.body.scoreIDs }, + }); + + t.equal( + scoreCount.length, + res.body.body.scoreIDs.length, + "All returned scoreIDs should be inserted to the DB." + ); + t.end(); }); diff --git a/server/src/logger.ts b/server/src/logger.ts index c1ef5f059..3ad55f25c 100644 --- a/server/src/logger.ts +++ b/server/src/logger.ts @@ -1,6 +1,7 @@ import winston, { format, LeveledLogMethod, Logger, transports } from "winston"; import { ImportTypes, PublicUserDocument } from "kamaitachi-common"; import { FormatUserDoc } from "./core/format-user"; +import { KtLogger } from "./types"; const level = process.env.LOG_LEVEL ?? "info"; @@ -139,21 +140,21 @@ export const rootLogger = winston.createLogger({ transports: tports, }); -function CreateLogCtx(context: string, lg = rootLogger): Logger & { severe: LeveledLogMethod } { - return lg.child({ context }) as Logger & { severe: LeveledLogMethod }; +function CreateLogCtx(context: string, lg = rootLogger): KtLogger { + return lg.child({ context }) as KtLogger; } -export function AppendLogCtx(context: string, lg: Logger) { +export function AppendLogCtx(context: string, lg: KtLogger): KtLogger { let newContext = [...lg.defaultMeta.context, context]; - return lg.child({ context: newContext }); + return lg.child({ context: newContext }) as KtLogger; } export function CreateScoreLogger( user: PublicUserDocument, importID: string, importType: ImportTypes -) { +): KtLogger { const meta = { context: ["Score Import", importType, FormatUserDoc(user)], importID, @@ -164,7 +165,7 @@ export function CreateScoreLogger( childLogger.defaultMeta = meta; - return childLogger; + return childLogger as KtLogger; } export const Transports = tports; diff --git a/server/src/score-import/framework/core/sessions/name-generation.ts b/server/src/score-import/framework/core/sessions/name-generation.ts new file mode 100644 index 000000000..f6255cf64 --- /dev/null +++ b/server/src/score-import/framework/core/sessions/name-generation.ts @@ -0,0 +1,38 @@ +import db from "../../../../db/db"; +import { KtLogger } from "../../../../types"; + +export async function GenerateRandomSessionName(logger: KtLogger) { + let adjectives = await db.adjectives.aggregate([ + { + $sample: { + size: 2, + }, + }, + ]); + + let nouns = await db.nouns.aggregate([ + { + $sample: { + size: 1, + }, + }, + ]); + + if (adjectives.length < 2) { + logger.severe( + `Could not source 2 adjectives from the adjectives DB. Filling in with defaults, but this should be investigated.` + ); + + adjectives = ["Ambient", "Scary"]; + } + + if (nouns.length < 1) { + logger.severe( + `Could not source a noun from the noun DB. Filling in with defaults, but this should be investigated.` + ); + + nouns = ["Noise"]; + } + + return `${adjectives[0].text} ${adjectives[1].text} ${nouns[0].text}`; +} diff --git a/server/src/score-import/framework/core/sessions/performance-calc.ts b/server/src/score-import/framework/core/sessions/performance-calc.ts new file mode 100644 index 000000000..c098ba4c0 --- /dev/null +++ b/server/src/score-import/framework/core/sessions/performance-calc.ts @@ -0,0 +1,44 @@ +import { ScoreDocument, SessionDocument } from "kamaitachi-common"; + +type ScoreCalculatedDataOnly = Pick; + +const RELEVANT_SCORES = 10; + +function AverageBest10(numbers: number[]) { + return ( + numbers + .sort((a, b) => b - a) + .slice(0, RELEVANT_SCORES) + .reduce((a, r) => a + r, 0) / RELEVANT_SCORES + ); +} + +export function CreateSessionCalcData( + scoreCalcData: ScoreCalculatedDataOnly[] +): SessionDocument["calculatedData"] { + if (scoreCalcData.length < RELEVANT_SCORES) { + return { + scorePerf: null, + lampPerf: null, + perf: null, + }; + } + + // @optimisable -- could be done in one loop. + let scorePerf = AverageBest10(scoreCalcData.map((e) => e.calculatedData.rating)); + + let lampPerf = AverageBest10(scoreCalcData.map((e) => e.calculatedData.lampRating)); + + let perf = + scoreCalcData + .map((e) => Math.max(e.calculatedData.rating, e.calculatedData.lampRating)) + .sort((a, b) => b - a) + .slice(0, RELEVANT_SCORES) + .reduce((a, r) => a + r, 0) / RELEVANT_SCORES; + + return { + scorePerf, + lampPerf, + perf, + }; +} diff --git a/server/src/score-import/framework/core/sessions/sessions.ts b/server/src/score-import/framework/core/sessions/sessions.ts new file mode 100644 index 000000000..a71efa297 --- /dev/null +++ b/server/src/score-import/framework/core/sessions/sessions.ts @@ -0,0 +1,275 @@ +import { + integer, + ImportTypes, + ScoreDocument, + ScoreCore, + SessionDocument, + Playtypes, + SessionScoreInfo, + SessionInfoReturn, + Game, +} from "kamaitachi-common"; +import { Logger } from "winston"; +import db from "../../../../db/db"; +import { AppendLogCtx } from "../../../../logger"; +import { GenerateRandomSessionName } from "./name-generation"; +import crypto from "crypto"; +import { CreateSessionCalcData } from "./performance-calc"; +import { GetScoresFromSession } from "../../../../core/session-core"; +import { KtLogger, ScorePlaytypeMap } from "../../../../types"; + +const TWO_HOURS = 1000 * 60 * 60 * 2; + +export async function CreateSessions( + userID: integer, + importType: ImportTypes, + game: Game, + scorePtMap: ScorePlaytypeMap, + logger: KtLogger +) { + let allSessionInfo = []; + + /* eslint-disable no-await-in-loop */ + for (const playtype in scorePtMap) { + // @ts-expect-error This is my least favourite thing about ts. + let scores = scorePtMap[playtype] as ScoreDocument[]; + + let sessionInfo = await LoadScoresIntoSessions( + userID, + importType, + scores, + game, + playtype as Playtypes[Game], + logger + ); + + allSessionInfo.push(...sessionInfo); + } + /* eslint-enable no-await-in-loop */ + + return allSessionInfo; +} + +function ProcessScoreIntoSessionScoreInfo( + score: ScoreDocument, + previousPB: ScoreDocument | undefined +): SessionScoreInfo { + if (!previousPB) { + return { + scoreID: score.scoreID, + isNewScore: true, + }; + } + + return { + scoreID: score.scoreID, + isNewScore: false, + gradeDelta: score.scoreData.gradeIndex - previousPB.scoreData.gradeIndex, + lampDelta: score.scoreData.lampIndex - previousPB.scoreData.lampIndex, + percentDelta: score.scoreData.percent - previousPB.scoreData.percent, + scoreDelta: score.scoreData.score - previousPB.scoreData.score, + }; +} + +function CreateSessionID() { + return `Q${crypto.randomBytes(20).toString("hex")}`; +} + +function UpdateExistingSession( + existingSession: SessionDocument, + newInfo: SessionScoreInfo[], + oldScores: ScoreDocument[], + newScores: ScoreDocument[] +) { + let allScores = [...oldScores, ...newScores]; + + let calculatedData = CreateSessionCalcData(allScores); + + existingSession.calculatedData = calculatedData; + existingSession.scoreInfo = [...existingSession.scoreInfo, ...newInfo]; + + if (newScores[0].timeAchieved! < existingSession.timeStarted) { + existingSession.timeStarted = newScores[0].timeAchieved!; + } + + if (newScores[newScores.length - 1].timeAchieved! > existingSession.timeEnded) { + existingSession.timeEnded = newScores[newScores.length - 1].timeAchieved!; + } + + return existingSession; +} + +async function CreateSession( + userID: integer, + importType: ImportTypes, + groupInfo: SessionScoreInfo[], + groupScores: ScoreDocument[], + game: Game, + playtype: Playtypes[Game], + logger: KtLogger +): Promise { + let name = await GenerateRandomSessionName(logger); + + let calculatedData = CreateSessionCalcData(groupScores); + + return { + userID, + importType, + name, + sessionID: CreateSessionID(), + desc: null, + game, + playtype, + highlight: false, + scoreInfo: groupInfo, + timeInserted: Date.now(), + timeStarted: groupScores[0].timeAchieved!, + timeEnded: groupScores[groupScores.length - 1].timeAchieved!, + calculatedData, + }; +} + +export async function LoadScoresIntoSessions( + userID: integer, + importType: ImportTypes, + importScores: ScoreDocument[], + game: Game, + playtype: Playtypes[Game], + baseLogger: KtLogger +): Promise { + const logger = AppendLogCtx("Session Generation", baseLogger); + + let timestampedScores = []; + + for (const score of importScores) { + if (!score.timeAchieved) { + logger.verbose(`Ignored score ${score.scoreID}, as it had no timeAchieved.`); + // ignore scores without timestamps. We can't use these for sessions. + continue; + } + + timestampedScores.push(score); + } + + // If we have nothing to work with, why bother? + if (timestampedScores.length === 0) { + logger.verbose(`Skipped calculating sessions as there were no timestamped scores`); + return []; + } + + // sort scores ascendingly. + timestampedScores.sort((a, b) => a.timeAchieved! - b.timeAchieved!); + + // The "Score Groups" for the array of scores provided. + // This contains scores split on 2hr margins, which allows for more optimised + // session db requests. + let sessionScoreGroups: ScoreDocument[][] = []; + let curGroup: ScoreDocument[] = []; + let lastTimestamp = 0; + + for (const score of timestampedScores) { + if (score.timeAchieved! < lastTimestamp + TWO_HOURS) { + curGroup.push(score); + } else { + sessionScoreGroups.push(curGroup); + curGroup = [score]; + } + } + + // I think this check is redundant? + if (curGroup.length !== 0) { + sessionScoreGroups.push(curGroup); + } + + logger.verbose(`Created ${sessionScoreGroups.length} groups from timestamped scores.`); + + let sessionInfoReturns: SessionInfoReturn[] = []; + + // All async operations inside here *need* to be done in lockstep to avoid colliding sessions. + // realistically, that shouldn't be possible, but hey. + /* eslint-disable no-await-in-loop */ + for (const groupScores of sessionScoreGroups) { + if (groupScores.length === 0) { + continue; + } + + let startOfGroup = groupScores[0].timeAchieved!; + let endOfGroup = groupScores[groupScores.length - 1].timeAchieved!; + + let pbs = await db.scores.find({ + chartID: { $in: groupScores.map((e) => e.chartID) }, + userID, + isScorePB: true, + }); + + let coercedPBs = await ScoreCore.AutoCoerce(db.scores, pbs); + + let pbMap: Map = new Map(); + for (const pb of coercedPBs) { + pbMap.set(pb.chartID, pb); + } + + let groupInfo = groupScores.map((e) => + ProcessScoreIntoSessionScoreInfo(e, pbMap.get(e.chartID)) + ); + + // Find any sessions with +/-2hrs of this group. This is rather exhaustive, and could result in some issues + // if this query returns more than one session. We should account for that by smushing sessions together. + // As of now, we dont currently do it. @TODO. + let nearbySession = await db.sessions.findOne({ + userID, + // importType, not necessary? + game, + playtype, + $or: [ + { timeStarted: { $gte: startOfGroup - TWO_HOURS, $lt: endOfGroup + TWO_HOURS } }, + { timeEnded: { $gte: startOfGroup - TWO_HOURS, $lt: endOfGroup + TWO_HOURS } }, + ], + }); + + let infoReturn: SessionInfoReturn; + + if (nearbySession) { + logger.verbose( + `Found nearby session for ${userID} (${game} ${playtype}) around ${startOfGroup} ${endOfGroup}.` + ); + + let oldScores = await GetScoresFromSession(nearbySession); + + let session = UpdateExistingSession(nearbySession, groupInfo, oldScores, groupScores); + + infoReturn = { sessionID: session.sessionID, type: "Appended" }; + + await db.sessions.update( + { + _id: session._id, + }, + { + $set: session, + } + ); + } else { + logger.verbose( + `Creating new session for ${userID} (${game} ${playtype}) around ${startOfGroup} ${endOfGroup}.` + ); + + let session = await CreateSession( + userID, + importType, + groupInfo, + groupScores, + game, + playtype, + logger + ); + + infoReturn = { sessionID: session.sessionID, type: "Created" }; + await db.sessions.insert(session); + } + + sessionInfoReturns.push(infoReturn); + } + /* eslint-enable no-await-in-loop */ + + return sessionInfoReturns; +} diff --git a/server/src/score-import/framework/importing/score-importing.ts b/server/src/score-import/framework/importing/score-importing.ts index 8e88fdbd3..e92273648 100644 --- a/server/src/score-import/framework/importing/score-importing.ts +++ b/server/src/score-import/framework/importing/score-importing.ts @@ -5,7 +5,7 @@ import { ScoreDocument, AnySongDocument, } from "kamaitachi-common"; -import { DryScore, ConverterFunction, ConverterFnReturn } from "../../../types"; +import { DryScore, ConverterFunction, ConverterFnReturn, KtLogger } from "../../../types"; import HydrateScore from "../core/hydrate-score"; import { QueueScoreInsert, ScoreIDs } from "../core/insert-score"; import { @@ -16,7 +16,6 @@ import { } from "../core/converter-errors"; import { CreateScoreID } from "../core/score-id"; import db from "../../../db/db"; -import { Logger } from "winston"; import { AppendLogCtx } from "../../../logger"; /** @@ -32,7 +31,7 @@ export async function ImportAllIterableData( iterableData: Iterable | AsyncIterable, ConverterFunction: ConverterFunction, context: C, - logger: Logger + logger: KtLogger ): Promise { logger.verbose(`Starting Data Processing...`); @@ -95,7 +94,7 @@ export async function ImportIterableDatapoint( data: D, ConverterFunction: ConverterFunction, context: C, - logger: Logger + logger: KtLogger ) { const converterReturns = await ConverterFunction(data, context, logger); @@ -111,7 +110,7 @@ export async function ImportIterableDatapoint( async function ImportFromConverterReturn( userID: integer, cfnReturn: ConverterFnReturn, // a single return, not an array! - logger: Logger + logger: KtLogger ): Promise { // null => processing didnt result in a score document, but not an error, no processing needed! if (cfnReturn === null) { @@ -205,7 +204,7 @@ async function HydrateAndInsertScore( dryScore: DryScore, chart: AnyChartDocument, song: AnySongDocument, - importLogger: Logger + importLogger: KtLogger ): Promise { const scoreID = CreateScoreID(userID, dryScore, chart.chartID); diff --git a/server/src/score-import/framework/score-import-main.ts b/server/src/score-import/framework/score-import-main.ts index 289406f40..7b84bde02 100644 --- a/server/src/score-import/framework/score-import-main.ts +++ b/server/src/score-import/framework/score-import-main.ts @@ -1,8 +1,38 @@ -import { ImportTypes, KTBlackImportDocument, PublicUserDocument } from "kamaitachi-common"; -import { ImportInputParser } from "../../types"; +import { + ImportProcessingInfo, + ImportTypes, + KTBlackImportDocument, + PublicUserDocument, +} from "kamaitachi-common"; +import { ImportInputParser, ScorePlaytypeMap } from "../../types"; import { InsertQueue } from "./core/insert-score"; import { ImportAllIterableData } from "./importing/score-importing"; import { CreateImportLoggerAndID } from "./core/import-logger"; +import { Logger } from "winston"; +import { CreateSessions } from "./core/sessions/sessions"; + +function ParseImportInfo(importInfo: ImportProcessingInfo[]) { + let scorePlaytypeMap: ScorePlaytypeMap = Object.create(null); + + let scoreIDs = []; + let errors = []; + + for (const info of importInfo) { + if (info.success) { + scoreIDs.push(info.content.score.scoreID); + + if (scorePlaytypeMap[info.content.score.playtype]) { + scorePlaytypeMap[info.content.score.playtype]!.push(info.content.score); + } else { + scorePlaytypeMap[info.content.score.playtype] = [info.content.score]; + } + } else { + errors.push({ type: info.type, message: info.message }); + } + } + + return { scoreIDs, errors, scorePlaytypeMap }; +} export default async function ScoreImportMain( user: PublicUserDocument, @@ -12,7 +42,7 @@ export default async function ScoreImportMain( const timeStarted = Date.now(); const { importID, logger } = CreateImportLoggerAndID(user, importType); - const { iterable, ConverterFunction, context, idStrings } = await InputParser(logger); + const { iterable, ConverterFunction, context, idStrings, game } = await InputParser(logger); // @todo: scope logger properly logger.verbose("Received import request."); @@ -32,22 +62,12 @@ export default async function ScoreImportMain( logger.verbose(`Emptied ${emptied} documents from score queue.`); } + let { scorePlaytypeMap, errors, scoreIDs } = ParseImportInfo(importInfo); + // Update user's rating information // @todo - // Construct sessions from successful scores - // @todo - - let scoreIDs = []; - let errors = []; - - for (const info of importInfo) { - if (info.success) { - scoreIDs.push(info.content.score.scoreID); - } else { - errors.push({ type: info.type, message: info.message }); - } - } + let sessionInfo = await CreateSessions(user.id, importType, game, scorePlaytypeMap, logger); // Create import document const ImportDocument: KTBlackImportDocument = { diff --git a/server/src/score-import/import-types/file/csv-eamusement-iidx/parser.ts b/server/src/score-import/import-types/file/csv-eamusement-iidx/parser.ts index f3b232636..1f7a9ee22 100644 --- a/server/src/score-import/import-types/file/csv-eamusement-iidx/parser.ts +++ b/server/src/score-import/import-types/file/csv-eamusement-iidx/parser.ts @@ -262,6 +262,7 @@ function ParseEamusementCSV( iterable: iterableData, context, ConverterFunction: ConverterFn, + game: "iidx", idStrings: [`iidx:${context.playtype}` as "iidx:SP" | "iidx:DP"], }; } diff --git a/server/src/scripts/set-indexes.ts b/server/src/scripts/set-indexes.ts new file mode 100644 index 000000000..89751163c --- /dev/null +++ b/server/src/scripts/set-indexes.ts @@ -0,0 +1,65 @@ +import monk from "monk"; +import { Command } from "commander"; +import CreateLogCtx from "../logger"; +import { IndexOptions } from "mongodb"; +import { ValidDatabases } from "kamaitachi-common"; + +const logger = CreateLogCtx("set-indexes.ts"); + +const program = new Command(); + +program.option("-d, --db ", "The database to index."); +program.option( + "-r, --reset", + "Whether to reset all indexes on this database before indexing or not." +); + +program.parse(process.argv); +const options = program.opts(); + +const db = monk(`${process.env.MONGO_BASE_URL ?? "127.0.0.1"}/${options.db}`); + +interface Index { + fields: Record; + options?: IndexOptions; +} + +function index(fields: Record, options?: IndexOptions) { + return { fields, options }; +} + +const UNIQUE = { unique: true }; + +const indexes: Partial> = { + scores: [index({ scoreID: 1 }, UNIQUE)], + "charts-iidx": [ + index({ chartID: 1 }, UNIQUE), + index({ songID: 1, difficulty: 1, playtype: 1 }, UNIQUE), + ], + "songs-iidx": [index({ id: 1 }, UNIQUE), index({ title: 1 })], + sessions: [ + // lol + index({ timeStarted: 1, timeEnded: 1, userID: 1, game: 1, playtype: 1 }), + ], +}; + +(async () => { + logger.info(`Starting indexing for ${options.db}...`); + + for (const collection in indexes) { + if (options.reset) { + // eslint-disable-next-line no-await-in-loop + await db.get(collection).dropIndexes(); + } + + // @ts-expect-error yeah blah blah + for (const index of indexes[collection]) { + // eslint-disable-next-line no-await-in-loop + let r = await db.get(collection).createIndex(index.fields, index.options); + + logger.info(r); + } + } + + logger.info("Done."); +})(); diff --git a/server/src/test-utils/mock-db/adjectives.json b/server/src/test-utils/mock-db/adjectives.json new file mode 100644 index 000000000..be5127913 --- /dev/null +++ b/server/src/test-utils/mock-db/adjectives.json @@ -0,0 +1,5 @@ +[{ + "text": "adjective1" +}, { + "text": "adjective2" +}] \ No newline at end of file diff --git a/server/src/test-utils/mock-db/nouns.json b/server/src/test-utils/mock-db/nouns.json new file mode 100644 index 000000000..c45385d3b --- /dev/null +++ b/server/src/test-utils/mock-db/nouns.json @@ -0,0 +1,3 @@ +[{ + "text": "noun1" +}] \ No newline at end of file diff --git a/server/src/types.ts b/server/src/types.ts index 6e6428a12..ee382d7a1 100644 --- a/server/src/types.ts +++ b/server/src/types.ts @@ -5,8 +5,11 @@ import { integer, ScoreDocument, AnySongDocument, + MongoDBDocument, + Game, + Playtypes, } from "kamaitachi-common"; -import { Logger } from "winston"; +import { Logger, LeveledLogMethod } from "winston"; import { ConverterFailure } from "./score-import/framework/core/converter-errors"; import { Converters } from "./score-import/import-types/import-types"; @@ -57,6 +60,7 @@ export interface ParserFunctionReturnsSync { iterable: Iterable; idStrings: [IDStrings] & IDStrings[]; context: C; + game: Game; ConverterFunction: ConverterFunction; } @@ -68,9 +72,17 @@ export interface ParserFunctionReturnsAsync { iterable: AsyncIterable; idStrings: [IDStrings] & IDStrings[]; context: C; + /** + * The game this import is for. + */ + game: Game; ConverterFunction: ConverterFunction; } +export type ScorePlaytypeMap = Partial>; + +export type KtLogger = Logger & { severe: LeveledLogMethod }; + /** * An intermediate score format that will be filled out by * HydrateScore. @@ -97,3 +109,7 @@ export interface OrphanedScore { export type RevaluedObject = { [K in keyof T]: RevaluedObject | U; }; + +export interface TextDocument extends MongoDBDocument { + text: string; +}