From 967c3208991fc2364f78c704c64e6798a4b45eb5 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Mon, 28 Mar 2022 17:18:21 +0100 Subject: [PATCH 001/106] Achievables have been given the better name of 'targets' --- server/src/lib/score-import/framework/goals/goals.ts | 2 +- server/src/lib/{achievables => targets}/goal-utils.ts | 0 server/src/lib/{achievables => targets}/goals.test.ts | 0 server/src/lib/{achievables => targets}/goals.ts | 0 .../router/api/v1/games/_game/_playtype/targets/goals/router.ts | 2 +- .../users/_userID/games/_game/_playtype/targets/goals/router.ts | 2 +- 6 files changed, 3 insertions(+), 3 deletions(-) rename server/src/lib/{achievables => targets}/goal-utils.ts (100%) rename server/src/lib/{achievables => targets}/goals.test.ts (100%) rename server/src/lib/{achievables => targets}/goals.ts (100%) diff --git a/server/src/lib/score-import/framework/goals/goals.ts b/server/src/lib/score-import/framework/goals/goals.ts index 4377473f5..c3df626e5 100644 --- a/server/src/lib/score-import/framework/goals/goals.ts +++ b/server/src/lib/score-import/framework/goals/goals.ts @@ -1,5 +1,5 @@ import { integer, Game, GoalDocument, UserGoalDocument } from "tachi-common"; -import { EvaluateGoalForUser } from "lib/achievables/goals"; +import { EvaluateGoalForUser } from "lib/targets/goals"; import db from "external/mongo/db"; import { KtLogger } from "lib/logger/logger"; import { EmitWebhookEvent } from "lib/webhooks/webhooks"; diff --git a/server/src/lib/achievables/goal-utils.ts b/server/src/lib/targets/goal-utils.ts similarity index 100% rename from server/src/lib/achievables/goal-utils.ts rename to server/src/lib/targets/goal-utils.ts diff --git a/server/src/lib/achievables/goals.test.ts b/server/src/lib/targets/goals.test.ts similarity index 100% rename from server/src/lib/achievables/goals.test.ts rename to server/src/lib/targets/goals.test.ts diff --git a/server/src/lib/achievables/goals.ts b/server/src/lib/targets/goals.ts similarity index 100% rename from server/src/lib/achievables/goals.ts rename to server/src/lib/targets/goals.ts diff --git a/server/src/server/router/api/v1/games/_game/_playtype/targets/goals/router.ts b/server/src/server/router/api/v1/games/_game/_playtype/targets/goals/router.ts index 1d79578f4..a3b8f5fc5 100644 --- a/server/src/server/router/api/v1/games/_game/_playtype/targets/goals/router.ts +++ b/server/src/server/router/api/v1/games/_game/_playtype/targets/goals/router.ts @@ -1,6 +1,6 @@ import { RequestHandler, Router } from "express"; import db from "external/mongo/db"; -import { EvaluatedGoalReturn, EvaluateGoalForUser } from "lib/achievables/goals"; +import { EvaluatedGoalReturn, EvaluateGoalForUser } from "lib/targets/goals"; import { SYMBOL_TachiData } from "lib/constants/tachi"; import CreateLogCtx from "lib/logger/logger"; import prValidate from "server/middleware/prudence-validate"; diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts index 68672847a..fb1b8cb75 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts @@ -4,7 +4,7 @@ import { SYMBOL_TachiData } from "lib/constants/tachi"; import prValidate from "server/middleware/prudence-validate"; import p from "prudence"; import { GoalDocument } from "tachi-common"; -import { ConstructGoal, SubscribeFailReasons, SubscribeToGoal } from "lib/achievables/goals"; +import { ConstructGoal, SubscribeFailReasons, SubscribeToGoal } from "lib/targets/goals"; import CreateLogCtx from "lib/logger/logger"; import { RequirePermissions } from "server/middleware/auth"; import { AssignToReqTachiData } from "utils/req-tachi-data"; From 968ff857cdfab7f53088bc9a605e6a0e00be3050 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Mon, 28 Mar 2022 17:20:25 +0100 Subject: [PATCH 002/106] Remove unecessary as any cast --- server/src/lib/targets/goals.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/server/src/lib/targets/goals.ts b/server/src/lib/targets/goals.ts index ff1de9193..5785d6310 100644 --- a/server/src/lib/targets/goals.ts +++ b/server/src/lib/targets/goals.ts @@ -317,6 +317,8 @@ export async function SubscribeToGoal( return SubscribeFailReasons.ALREADY_ACHIEVED; } + // @ts-expect-error TS can't resolve this. + // because it can't explode out the types. const userGoal: UserGoalDocument = { outOf: result.outOf, outOfHuman: result.outOfHuman, @@ -330,9 +332,8 @@ export async function SubscribeToGoal( game: goalDocument.game, playtype: goalDocument.playtype, goalID: goalDocument.goalID, - // Typescript gets pretty irritated at this because it can't explode out - // the types. That's whatever. - achieved: result.achieved as any, + + achieved: result.achieved, wasInstantlyAchieved: result.achieved, }; From 3be9baa39bd3dde433532615746a0c3916fc6b94 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Mon, 28 Mar 2022 17:55:07 +0100 Subject: [PATCH 003/106] Fix various linting warnings --- server/src/scripts/state-sync/dedupe-score-ids.ts | 1 + server/src/scripts/sync-database.ts | 6 +++++- server/src/utils/queries/summary.ts | 5 +---- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/server/src/scripts/state-sync/dedupe-score-ids.ts b/server/src/scripts/state-sync/dedupe-score-ids.ts index e5ef0eb74..8289b91e8 100644 --- a/server/src/scripts/state-sync/dedupe-score-ids.ts +++ b/server/src/scripts/state-sync/dedupe-score-ids.ts @@ -26,6 +26,7 @@ async function DedupeScoreIDs() { for (const dup of dups) { dup.dups.shift(); + // eslint-disable-next-line no-await-in-loop await db.scores.remove({ _id: { $in: dup.dups } }); } diff --git a/server/src/scripts/sync-database.ts b/server/src/scripts/sync-database.ts index 61453742b..3bc93d171 100644 --- a/server/src/scripts/sync-database.ts +++ b/server/src/scripts/sync-database.ts @@ -1,7 +1,7 @@ /* eslint-disable no-await-in-loop */ // This script syncs this tachi instances database up with the tachi-database-seeds. -import db, { monkDB } from "external/mongo/db"; +import { monkDB } from "external/mongo/db"; import fjsh from "fast-json-stable-hash"; import { PullDatabaseSeeds } from "lib/database-seeds/repo"; import CreateLogCtx, { KtLogger } from "lib/logger/logger"; @@ -24,7 +24,11 @@ import { InitaliseFolderChartLookup } from "utils/folder"; interface SyncInstructions { pattern: RegExp; handler: ( + // These 'any's are necessary due to what I think might be a bug in typescript? + // see: https://github.com/microsoft/TypeScript/issues/48461 + // eslint-disable-next-line @typescript-eslint/no-explicit-any c: any[], + // eslint-disable-next-line @typescript-eslint/no-explicit-any collection: ICollection, logger: KtLogger, collectionName: string diff --git a/server/src/utils/queries/summary.ts b/server/src/utils/queries/summary.ts index 8963424b3..689695ddb 100644 --- a/server/src/utils/queries/summary.ts +++ b/server/src/utils/queries/summary.ts @@ -1,9 +1,6 @@ import db from "external/mongo/db"; import { integer } from "tachi-common"; -import { - GetGradeLampDistributionForFolder, - GetGradeLampDistributionForFolders, -} from "utils/folder"; +import { GetGradeLampDistributionForFolders } from "utils/folder"; import { GetTimeXHoursAgo } from "utils/misc"; // Various utils related to the player summary endpoint. From 4614bdd56b8220e9b3fb879c77c24710a99890a8 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Mon, 28 Mar 2022 23:21:46 +0100 Subject: [PATCH 004/106] Add milestone-sets to the database. --- server/src/external/mongo/db.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/server/src/external/mongo/db.ts b/server/src/external/mongo/db.ts index 9a0011851..489e77e98 100644 --- a/server/src/external/mongo/db.ts +++ b/server/src/external/mongo/db.ts @@ -21,6 +21,7 @@ import { InviteCodeDocument, KaiAuthDocument, MilestoneDocument, + MilestoneSetDocument, OrphanChart, PBScoreDocument, PrivateUserInfoDocument, @@ -190,6 +191,7 @@ const db = { "verify-email-codes": monkDB.get<{ userID: integer; code: string; email: string }>("verify-email-codes"), "recent-folder-views": monkDB.get("recent-folder-views"), + "milestone-sets": monkDB.get("milestone-sets"), }; export type StaticDatabases = @@ -226,7 +228,8 @@ export type StaticDatabases = | "score-blacklist" | "verify-email-codes" | "class-achievements" - | "recent-folder-views"; + | "recent-folder-views" + | "milestone-sets"; export type Databases = StaticDatabases | `songs-${Game}` | `charts-${Game}`; From 21ef74aa3272dc7f5114e7aa80c971adefeecd45 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Mon, 28 Mar 2022 23:21:52 +0100 Subject: [PATCH 005/106] This isn't a typescript bug. --- server/src/scripts/sync-database.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/server/src/scripts/sync-database.ts b/server/src/scripts/sync-database.ts index 3bc93d171..219e0318b 100644 --- a/server/src/scripts/sync-database.ts +++ b/server/src/scripts/sync-database.ts @@ -24,8 +24,7 @@ import { InitaliseFolderChartLookup } from "utils/folder"; interface SyncInstructions { pattern: RegExp; handler: ( - // These 'any's are necessary due to what I think might be a bug in typescript? - // see: https://github.com/microsoft/TypeScript/issues/48461 + // These 'any's are necessary because generifying here kinda sucks. // eslint-disable-next-line @typescript-eslint/no-explicit-any c: any[], // eslint-disable-next-line @typescript-eslint/no-explicit-any From 42a558891de707c2fee7c81d6bfeb10f86306571 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Mon, 28 Mar 2022 23:22:03 +0100 Subject: [PATCH 006/106] Outline for milestones API. --- .../_playtype/targets/milestones/router.ts | 37 +++++++++++++++++++ .../games/_game/_playtype/targets/router.ts | 2 + 2 files changed, 39 insertions(+) create mode 100644 server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/milestones/router.ts diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/milestones/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/milestones/router.ts new file mode 100644 index 000000000..e5d186e03 --- /dev/null +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/milestones/router.ts @@ -0,0 +1,37 @@ +import { Router } from "express"; +import db from "external/mongo/db"; +import { SYMBOL_TachiData } from "lib/constants/tachi"; + +const router: Router = Router({ mergeParams: true }); + +/** + * Retrieves this users' set milestones + * + * @name GET /api/v1/users/:userID/games/:game/:playtype/targets/milestones + */ +router.get("/", async (req, res) => { + const user = req[SYMBOL_TachiData]!.requestedUser!; + const game = req[SYMBOL_TachiData]!.game!; + const playtype = req[SYMBOL_TachiData]!.playtype!; + + const userMilestones = await db["user-milestones"].find({ + userID: user.id, + game, + playtype, + }); + + const milestones = await db.milestones.find({ + milestoneID: { $in: userMilestones.map((e) => e.milestoneID) }, + }); + + return res.status(200).json({ + success: true, + description: `Retrieved ${userMilestones.length} milestone(s)`, + body: { + milestones, + userMilestones, + }, + }); +}); + +export default router; diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/router.ts index 63929423c..9ba386131 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/router.ts @@ -1,8 +1,10 @@ import { Router } from "express"; import goalsRouter from "./goals/router"; +import milestonesRouter from "./milestones/router"; const router: Router = Router({ mergeParams: true }); router.use("/goals", goalsRouter); +router.use("/milestones", milestonesRouter); export default router; From 40ed9d067547c6f08d73a7bbb12927b0978c44e1 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Mon, 28 Mar 2022 23:30:44 +0100 Subject: [PATCH 007/106] Jesus christ zkldi this is why we write tests. --- .../users/_userID/games/_game/_playtype/targets/goals/router.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts index fb1b8cb75..5e3d23a65 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts @@ -194,6 +194,7 @@ const GetGoalSubscription: RequestHandler = async (req, res, next) => { userID: user.id, game, playtype, + goalID: req.params.goalID, }); if (!userGoal) { From 46b9bf86fb349df0c5392661435f480e4e3b9c72 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Tue, 29 Mar 2022 02:33:15 +0100 Subject: [PATCH 008/106] rename user-milestones and user-goals to goal-subs and milestone-subs --- server/package.json | 2 +- server/pnpm-lock.yaml | 5368 ++++++----------- server/src/external/mongo/db.ts | 8 +- server/src/external/mongo/indexes.ts | 4 +- server/src/external/mongo/schemas.ts | 4 +- .../framework/goals/goals.test.ts | 20 +- .../lib/score-import/framework/goals/goals.ts | 4 +- .../framework/milestones/milestones.test.ts | 6 +- .../framework/milestones/milestones.ts | 6 +- server/src/lib/targets/goals.ts | 4 +- .../_game/_playtype/targets/goals/router.ts | 6 +- .../games/_game/_playtype/router.test.ts | 4 +- .../_userID/games/_game/_playtype/router.ts | 4 +- .../_game/_playtype/targets/goals/router.ts | 10 +- .../_playtype/targets/milestones/router.ts | 130 +- server/src/utils/queries/summary.ts | 4 +- 16 files changed, 1899 insertions(+), 3685 deletions(-) diff --git a/server/package.json b/server/package.json index 093627602..31cc75c29 100644 --- a/server/package.json +++ b/server/package.json @@ -84,7 +84,7 @@ "rimraf": "3.0.2", "safe-json-stringify": "1.2.0", "seq-logging": "1.1.1", - "tachi-common": "0.7.3", + "tachi-common": "0.7.5", "ts-node": "10.4.0", "tsconfig-paths": "3.12.0", "typescript": "4.5.5", diff --git a/server/pnpm-lock.yaml b/server/pnpm-lock.yaml index cd98cfb98..26a7b674e 100644 --- a/server/pnpm-lock.yaml +++ b/server/pnpm-lock.yaml @@ -1,30 +1,30 @@ lockfileVersion: 5.3 specifiers: - "@aws-sdk/client-s3": 3.49.0 - "@aws-sdk/signature-v4-crt": 3.49.0 - "@types/bcrypt": 5.0.0 - "@types/bcryptjs": ^2.4.2 - "@types/bson": 1.0.11 - "@types/bunyan": ^1.8.8 - "@types/connect-redis": 0.0.18 - "@types/express": 4.17.13 - "@types/express-session": 1.17.4 - "@types/mkdirp": 1.0.2 - "@types/multer": 1.4.7 - "@types/node": 16.11.7 - "@types/node-fetch": 2.5.12 - "@types/nodemailer": ^6.4.4 - "@types/range-parser": 1.2.4 - "@types/rate-limit-redis": 1.7.4 - "@types/rimraf": 3.0.2 - "@types/safe-json-stringify": 1.1.2 - "@types/supertest": 2.0.11 - "@types/tap": 15.0.5 - "@types/uuid": 8.3.4 - "@typescript-eslint/eslint-plugin": 5.10.2 - "@typescript-eslint/parser": 5.10.2 - "@valuabletouch/winston-seq": 1.3.0 + '@aws-sdk/client-s3': 3.49.0 + '@aws-sdk/signature-v4-crt': 3.49.0 + '@types/bcrypt': 5.0.0 + '@types/bcryptjs': ^2.4.2 + '@types/bson': 1.0.11 + '@types/bunyan': ^1.8.8 + '@types/connect-redis': 0.0.18 + '@types/express': 4.17.13 + '@types/express-session': 1.17.4 + '@types/mkdirp': 1.0.2 + '@types/multer': 1.4.7 + '@types/node': 16.11.7 + '@types/node-fetch': 2.5.12 + '@types/nodemailer': ^6.4.4 + '@types/range-parser': 1.2.4 + '@types/rate-limit-redis': 1.7.4 + '@types/rimraf': 3.0.2 + '@types/safe-json-stringify': 1.1.2 + '@types/supertest': 2.0.11 + '@types/tap': 15.0.5 + '@types/uuid': 8.3.4 + '@typescript-eslint/eslint-plugin': 5.10.2 + '@typescript-eslint/parser': 5.10.2 + '@valuabletouch/winston-seq': 1.3.0 bcryptjs: 2.4.3 bullmq: 1.68.4 bunyan: 1.8.15 @@ -65,7 +65,7 @@ specifiers: safe-json-stringify: 1.2.0 seq-logging: 1.1.1 supertest: 6.2.2 - tachi-common: 0.7.3 + tachi-common: 0.7.5 tap: 15.1.6 ts-node: 10.4.0 tsconfig-paths: 3.12.0 @@ -76,10 +76,10 @@ specifiers: winston-transport: 4.5.0 dependencies: - "@aws-sdk/client-s3": 3.49.0_@aws-sdk+signature-v4-crt@3.49.0 - "@aws-sdk/signature-v4-crt": 3.49.0 - "@types/bson": 1.0.11 - "@valuabletouch/winston-seq": 1.3.0 + '@aws-sdk/client-s3': 3.49.0_@aws-sdk+signature-v4-crt@3.49.0 + '@aws-sdk/signature-v4-crt': 3.49.0 + '@types/bson': 1.0.11 + '@valuabletouch/winston-seq': 1.3.0 bcryptjs: 2.4.3 bullmq: 1.68.4 bunyan: 1.8.15 @@ -112,7 +112,7 @@ dependencies: rimraf: 3.0.2 safe-json-stringify: 1.2.0 seq-logging: 1.1.1 - tachi-common: 0.7.3 + tachi-common: 0.7.5 ts-node: 10.4.0_2615db9039ce432b4abf2fc39ef336ec tsconfig-paths: 3.12.0 typescript: 4.5.5 @@ -121,26 +121,26 @@ dependencies: winston-transport: 4.5.0 devDependencies: - "@types/bcrypt": 5.0.0 - "@types/bcryptjs": 2.4.2 - "@types/bunyan": 1.8.8 - "@types/connect-redis": 0.0.18 - "@types/express": 4.17.13 - "@types/express-session": 1.17.4 - "@types/mkdirp": 1.0.2 - "@types/multer": 1.4.7 - "@types/node": 16.11.7 - "@types/node-fetch": 2.5.12 - "@types/nodemailer": 6.4.4 - "@types/range-parser": 1.2.4 - "@types/rate-limit-redis": 1.7.4 - "@types/rimraf": 3.0.2 - "@types/safe-json-stringify": 1.1.2 - "@types/supertest": 2.0.11 - "@types/tap": 15.0.5 - "@types/uuid": 8.3.4 - "@typescript-eslint/eslint-plugin": 5.10.2_2595c2126aec4d4b6e944b931dabb4c2 - "@typescript-eslint/parser": 5.10.2_eslint@8.8.0+typescript@4.5.5 + '@types/bcrypt': 5.0.0 + '@types/bcryptjs': 2.4.2 + '@types/bunyan': 1.8.8 + '@types/connect-redis': 0.0.18 + '@types/express': 4.17.13 + '@types/express-session': 1.17.4 + '@types/mkdirp': 1.0.2 + '@types/multer': 1.4.7 + '@types/node': 16.11.7 + '@types/node-fetch': 2.5.12 + '@types/nodemailer': 6.4.4 + '@types/range-parser': 1.2.4 + '@types/rate-limit-redis': 1.7.4 + '@types/rimraf': 3.0.2 + '@types/safe-json-stringify': 1.1.2 + '@types/supertest': 2.0.11 + '@types/tap': 15.0.5 + '@types/uuid': 8.3.4 + '@typescript-eslint/eslint-plugin': 5.10.2_2595c2126aec4d4b6e944b931dabb4c2 + '@typescript-eslint/parser': 5.10.2_eslint@8.8.0+typescript@4.5.5 eslint: 8.8.0 eslint-config-prettier: 8.3.0_eslint@8.8.0 eslint-plugin-import: 2.25.4_eslint@8.8.0 @@ -153,819 +153,646 @@ devDependencies: why-is-node-running: 2.2.1 packages: + /@ampproject/remapping/2.1.0: - resolution: - { - integrity: sha512-d5RysTlJ7hmw5Tw4UxgxcY3lkMe92n8sXCcuLPAyIAHK6j8DefDwtGnVVDgOnv+RnEosulDJ9NPKQL27bDId0g==, - } - engines: { node: ">=6.0.0" } + resolution: {integrity: sha512-d5RysTlJ7hmw5Tw4UxgxcY3lkMe92n8sXCcuLPAyIAHK6j8DefDwtGnVVDgOnv+RnEosulDJ9NPKQL27bDId0g==} + engines: {node: '>=6.0.0'} dependencies: - "@jridgewell/trace-mapping": 0.3.2 + '@jridgewell/trace-mapping': 0.3.2 dev: true /@aws-crypto/crc32/2.0.0: - resolution: - { - integrity: sha512-TvE1r2CUueyXOuHdEigYjIZVesInd9KN+K/TFFNfkkxRThiNxO6i4ZqqAVMoEjAamZZ1AA8WXJkjCz7YShHPQA==, - } + resolution: {integrity: sha512-TvE1r2CUueyXOuHdEigYjIZVesInd9KN+K/TFFNfkkxRThiNxO6i4ZqqAVMoEjAamZZ1AA8WXJkjCz7YShHPQA==} dependencies: - "@aws-crypto/util": 2.0.1 - "@aws-sdk/types": 3.49.0 + '@aws-crypto/util': 2.0.1 + '@aws-sdk/types': 3.49.0 tslib: 1.14.1 dev: false /@aws-crypto/ie11-detection/2.0.0: - resolution: - { - integrity: sha512-pkVXf/dq6PITJ0jzYZ69VhL8VFOFoPZLZqtU/12SGnzYuJOOGNfF41q9GxdI1yqC8R13Rq3jOLKDFpUJFT5eTA==, - } + resolution: {integrity: sha512-pkVXf/dq6PITJ0jzYZ69VhL8VFOFoPZLZqtU/12SGnzYuJOOGNfF41q9GxdI1yqC8R13Rq3jOLKDFpUJFT5eTA==} dependencies: tslib: 1.14.1 dev: false /@aws-crypto/sha256-browser/2.0.0: - resolution: - { - integrity: sha512-rYXOQ8BFOaqMEHJrLHul/25ckWH6GTJtdLSajhlqGMx0PmSueAuvboCuZCTqEKlxR8CQOwRarxYMZZSYlhRA1A==, - } + resolution: {integrity: sha512-rYXOQ8BFOaqMEHJrLHul/25ckWH6GTJtdLSajhlqGMx0PmSueAuvboCuZCTqEKlxR8CQOwRarxYMZZSYlhRA1A==} dependencies: - "@aws-crypto/ie11-detection": 2.0.0 - "@aws-crypto/sha256-js": 2.0.0 - "@aws-crypto/supports-web-crypto": 2.0.0 - "@aws-crypto/util": 2.0.1 - "@aws-sdk/types": 3.49.0 - "@aws-sdk/util-locate-window": 3.49.0 - "@aws-sdk/util-utf8-browser": 3.49.0 + '@aws-crypto/ie11-detection': 2.0.0 + '@aws-crypto/sha256-js': 2.0.0 + '@aws-crypto/supports-web-crypto': 2.0.0 + '@aws-crypto/util': 2.0.1 + '@aws-sdk/types': 3.49.0 + '@aws-sdk/util-locate-window': 3.49.0 + '@aws-sdk/util-utf8-browser': 3.49.0 tslib: 1.14.1 dev: false /@aws-crypto/sha256-js/2.0.0: - resolution: - { - integrity: sha512-VZY+mCY4Nmrs5WGfitmNqXzaE873fcIZDu54cbaDaaamsaTOP1DBImV9F4pICc3EHjQXujyE8jig+PFCaew9ig==, - } + resolution: {integrity: sha512-VZY+mCY4Nmrs5WGfitmNqXzaE873fcIZDu54cbaDaaamsaTOP1DBImV9F4pICc3EHjQXujyE8jig+PFCaew9ig==} dependencies: - "@aws-crypto/util": 2.0.1 - "@aws-sdk/types": 3.49.0 + '@aws-crypto/util': 2.0.1 + '@aws-sdk/types': 3.49.0 tslib: 1.14.1 dev: false /@aws-crypto/supports-web-crypto/2.0.0: - resolution: - { - integrity: sha512-Ge7WQ3E0OC7FHYprsZV3h0QIcpdyJLvIeg+uTuHqRYm8D6qCFJoiC+edSzSyFiHtZf+NOQDJ1q46qxjtzIY2nA==, - } + resolution: {integrity: sha512-Ge7WQ3E0OC7FHYprsZV3h0QIcpdyJLvIeg+uTuHqRYm8D6qCFJoiC+edSzSyFiHtZf+NOQDJ1q46qxjtzIY2nA==} dependencies: tslib: 1.14.1 dev: false /@aws-crypto/util/2.0.1: - resolution: - { - integrity: sha512-JJmFFwvbm08lULw4Nm5QOLg8+lAQeC8aCXK5xrtxntYzYXCGfHwUJ4Is3770Q7HmICsXthGQ+ZsDL7C2uH3yBQ==, - } + resolution: {integrity: sha512-JJmFFwvbm08lULw4Nm5QOLg8+lAQeC8aCXK5xrtxntYzYXCGfHwUJ4Is3770Q7HmICsXthGQ+ZsDL7C2uH3yBQ==} dependencies: - "@aws-sdk/types": 3.49.0 - "@aws-sdk/util-utf8-browser": 3.49.0 + '@aws-sdk/types': 3.49.0 + '@aws-sdk/util-utf8-browser': 3.49.0 tslib: 1.14.1 dev: false /@aws-sdk/abort-controller/3.49.0: - resolution: - { - integrity: sha512-1qLlgOgg3yrtm+AJEP7yoIk90o6ns3Dia8S9DRjj29jY40446etH3v/tIbHPE+em69HLXl1K6e5KD6t7EDxnlg==, - } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-1qLlgOgg3yrtm+AJEP7yoIk90o6ns3Dia8S9DRjj29jY40446etH3v/tIbHPE+em69HLXl1K6e5KD6t7EDxnlg==} + engines: {node: '>= 12.0.0'} dependencies: - "@aws-sdk/types": 3.49.0 + '@aws-sdk/types': 3.49.0 tslib: 2.3.1 dev: false /@aws-sdk/chunked-blob-reader-native/3.49.0: - resolution: - { - integrity: sha512-ppjmDWyufMB41Hmq5Gixd2+/c4kk2IPKKWT9zI9spKOYbbL/vY3FmRq4beQ6n5kWOzDPxKZ6wv04yrMS9yBy1A==, - } + resolution: {integrity: sha512-ppjmDWyufMB41Hmq5Gixd2+/c4kk2IPKKWT9zI9spKOYbbL/vY3FmRq4beQ6n5kWOzDPxKZ6wv04yrMS9yBy1A==} dependencies: - "@aws-sdk/util-base64-browser": 3.49.0 + '@aws-sdk/util-base64-browser': 3.49.0 tslib: 2.3.1 dev: false /@aws-sdk/chunked-blob-reader/3.49.0: - resolution: - { - integrity: sha512-UI1rK4aBgwsQ6dOQs5Im1cNSb3c/RH1wKjD49zcwWyxe8e96C5G2LcshhVH3onWY5NgqQgs/ffEsXziGcNXRIg==, - } + resolution: {integrity: sha512-UI1rK4aBgwsQ6dOQs5Im1cNSb3c/RH1wKjD49zcwWyxe8e96C5G2LcshhVH3onWY5NgqQgs/ffEsXziGcNXRIg==} dependencies: tslib: 2.3.1 dev: false /@aws-sdk/client-s3/3.49.0_@aws-sdk+signature-v4-crt@3.49.0: - resolution: - { - integrity: sha512-LidddhZFxYb/jlEtHnbBpqVZmjgZNKY/jaZy3jRNcVhw8L0m1IYVigqgw5E6ta9/dma5EwLOA8Z21O/rCuDapw==, - } - engines: { node: ">=12.0.0" } + resolution: {integrity: sha512-LidddhZFxYb/jlEtHnbBpqVZmjgZNKY/jaZy3jRNcVhw8L0m1IYVigqgw5E6ta9/dma5EwLOA8Z21O/rCuDapw==} + engines: {node: '>=12.0.0'} dependencies: - "@aws-crypto/sha256-browser": 2.0.0 - "@aws-crypto/sha256-js": 2.0.0 - "@aws-sdk/client-sts": 3.49.0 - "@aws-sdk/config-resolver": 3.49.0 - "@aws-sdk/credential-provider-node": 3.49.0 - "@aws-sdk/eventstream-serde-browser": 3.49.0 - "@aws-sdk/eventstream-serde-config-resolver": 3.49.0 - "@aws-sdk/eventstream-serde-node": 3.49.0 - "@aws-sdk/fetch-http-handler": 3.49.0 - "@aws-sdk/hash-blob-browser": 3.49.0 - "@aws-sdk/hash-node": 3.49.0 - "@aws-sdk/hash-stream-node": 3.49.0 - "@aws-sdk/invalid-dependency": 3.49.0 - "@aws-sdk/md5-js": 3.49.0 - "@aws-sdk/middleware-apply-body-checksum": 3.49.0 - "@aws-sdk/middleware-bucket-endpoint": 3.49.0 - "@aws-sdk/middleware-content-length": 3.49.0 - "@aws-sdk/middleware-expect-continue": 3.49.0 - "@aws-sdk/middleware-host-header": 3.49.0 - "@aws-sdk/middleware-location-constraint": 3.49.0 - "@aws-sdk/middleware-logger": 3.49.0 - "@aws-sdk/middleware-retry": 3.49.0 - "@aws-sdk/middleware-sdk-s3": 3.49.0_@aws-sdk+signature-v4-crt@3.49.0 - "@aws-sdk/middleware-serde": 3.49.0 - "@aws-sdk/middleware-signing": 3.49.0 - "@aws-sdk/middleware-ssec": 3.49.0 - "@aws-sdk/middleware-stack": 3.49.0 - "@aws-sdk/middleware-user-agent": 3.49.0 - "@aws-sdk/node-config-provider": 3.49.0 - "@aws-sdk/node-http-handler": 3.49.0 - "@aws-sdk/protocol-http": 3.49.0 - "@aws-sdk/smithy-client": 3.49.0 - "@aws-sdk/types": 3.49.0 - "@aws-sdk/url-parser": 3.49.0 - "@aws-sdk/util-base64-browser": 3.49.0 - "@aws-sdk/util-base64-node": 3.49.0 - "@aws-sdk/util-body-length-browser": 3.49.0 - "@aws-sdk/util-body-length-node": 3.49.0 - "@aws-sdk/util-defaults-mode-browser": 3.49.0 - "@aws-sdk/util-defaults-mode-node": 3.49.0 - "@aws-sdk/util-user-agent-browser": 3.49.0 - "@aws-sdk/util-user-agent-node": 3.49.0 - "@aws-sdk/util-utf8-browser": 3.49.0 - "@aws-sdk/util-utf8-node": 3.49.0 - "@aws-sdk/util-waiter": 3.49.0 - "@aws-sdk/xml-builder": 3.49.0 + '@aws-crypto/sha256-browser': 2.0.0 + '@aws-crypto/sha256-js': 2.0.0 + '@aws-sdk/client-sts': 3.49.0 + '@aws-sdk/config-resolver': 3.49.0 + '@aws-sdk/credential-provider-node': 3.49.0 + '@aws-sdk/eventstream-serde-browser': 3.49.0 + '@aws-sdk/eventstream-serde-config-resolver': 3.49.0 + '@aws-sdk/eventstream-serde-node': 3.49.0 + '@aws-sdk/fetch-http-handler': 3.49.0 + '@aws-sdk/hash-blob-browser': 3.49.0 + '@aws-sdk/hash-node': 3.49.0 + '@aws-sdk/hash-stream-node': 3.49.0 + '@aws-sdk/invalid-dependency': 3.49.0 + '@aws-sdk/md5-js': 3.49.0 + '@aws-sdk/middleware-apply-body-checksum': 3.49.0 + '@aws-sdk/middleware-bucket-endpoint': 3.49.0 + '@aws-sdk/middleware-content-length': 3.49.0 + '@aws-sdk/middleware-expect-continue': 3.49.0 + '@aws-sdk/middleware-host-header': 3.49.0 + '@aws-sdk/middleware-location-constraint': 3.49.0 + '@aws-sdk/middleware-logger': 3.49.0 + '@aws-sdk/middleware-retry': 3.49.0 + '@aws-sdk/middleware-sdk-s3': 3.49.0_@aws-sdk+signature-v4-crt@3.49.0 + '@aws-sdk/middleware-serde': 3.49.0 + '@aws-sdk/middleware-signing': 3.49.0 + '@aws-sdk/middleware-ssec': 3.49.0 + '@aws-sdk/middleware-stack': 3.49.0 + '@aws-sdk/middleware-user-agent': 3.49.0 + '@aws-sdk/node-config-provider': 3.49.0 + '@aws-sdk/node-http-handler': 3.49.0 + '@aws-sdk/protocol-http': 3.49.0 + '@aws-sdk/smithy-client': 3.49.0 + '@aws-sdk/types': 3.49.0 + '@aws-sdk/url-parser': 3.49.0 + '@aws-sdk/util-base64-browser': 3.49.0 + '@aws-sdk/util-base64-node': 3.49.0 + '@aws-sdk/util-body-length-browser': 3.49.0 + '@aws-sdk/util-body-length-node': 3.49.0 + '@aws-sdk/util-defaults-mode-browser': 3.49.0 + '@aws-sdk/util-defaults-mode-node': 3.49.0 + '@aws-sdk/util-user-agent-browser': 3.49.0 + '@aws-sdk/util-user-agent-node': 3.49.0 + '@aws-sdk/util-utf8-browser': 3.49.0 + '@aws-sdk/util-utf8-node': 3.49.0 + '@aws-sdk/util-waiter': 3.49.0 + '@aws-sdk/xml-builder': 3.49.0 entities: 2.2.0 fast-xml-parser: 3.19.0 tslib: 2.3.1 transitivePeerDependencies: - - "@aws-sdk/signature-v4-crt" + - '@aws-sdk/signature-v4-crt' dev: false /@aws-sdk/client-sso/3.49.0: - resolution: - { - integrity: sha512-rY8uZo4DeNwwKf+Sx0TX/5ysXJKf+0SQSCTWD9S4a0AjtiaLc6hKCX+sJY43VDHvNYieKtXLDYHBdhhqZKwG+g==, - } - engines: { node: ">=12.0.0" } + resolution: {integrity: sha512-rY8uZo4DeNwwKf+Sx0TX/5ysXJKf+0SQSCTWD9S4a0AjtiaLc6hKCX+sJY43VDHvNYieKtXLDYHBdhhqZKwG+g==} + engines: {node: '>=12.0.0'} dependencies: - "@aws-crypto/sha256-browser": 2.0.0 - "@aws-crypto/sha256-js": 2.0.0 - "@aws-sdk/config-resolver": 3.49.0 - "@aws-sdk/fetch-http-handler": 3.49.0 - "@aws-sdk/hash-node": 3.49.0 - "@aws-sdk/invalid-dependency": 3.49.0 - "@aws-sdk/middleware-content-length": 3.49.0 - "@aws-sdk/middleware-host-header": 3.49.0 - "@aws-sdk/middleware-logger": 3.49.0 - "@aws-sdk/middleware-retry": 3.49.0 - "@aws-sdk/middleware-serde": 3.49.0 - "@aws-sdk/middleware-stack": 3.49.0 - "@aws-sdk/middleware-user-agent": 3.49.0 - "@aws-sdk/node-config-provider": 3.49.0 - "@aws-sdk/node-http-handler": 3.49.0 - "@aws-sdk/protocol-http": 3.49.0 - "@aws-sdk/smithy-client": 3.49.0 - "@aws-sdk/types": 3.49.0 - "@aws-sdk/url-parser": 3.49.0 - "@aws-sdk/util-base64-browser": 3.49.0 - "@aws-sdk/util-base64-node": 3.49.0 - "@aws-sdk/util-body-length-browser": 3.49.0 - "@aws-sdk/util-body-length-node": 3.49.0 - "@aws-sdk/util-defaults-mode-browser": 3.49.0 - "@aws-sdk/util-defaults-mode-node": 3.49.0 - "@aws-sdk/util-user-agent-browser": 3.49.0 - "@aws-sdk/util-user-agent-node": 3.49.0 - "@aws-sdk/util-utf8-browser": 3.49.0 - "@aws-sdk/util-utf8-node": 3.49.0 + '@aws-crypto/sha256-browser': 2.0.0 + '@aws-crypto/sha256-js': 2.0.0 + '@aws-sdk/config-resolver': 3.49.0 + '@aws-sdk/fetch-http-handler': 3.49.0 + '@aws-sdk/hash-node': 3.49.0 + '@aws-sdk/invalid-dependency': 3.49.0 + '@aws-sdk/middleware-content-length': 3.49.0 + '@aws-sdk/middleware-host-header': 3.49.0 + '@aws-sdk/middleware-logger': 3.49.0 + '@aws-sdk/middleware-retry': 3.49.0 + '@aws-sdk/middleware-serde': 3.49.0 + '@aws-sdk/middleware-stack': 3.49.0 + '@aws-sdk/middleware-user-agent': 3.49.0 + '@aws-sdk/node-config-provider': 3.49.0 + '@aws-sdk/node-http-handler': 3.49.0 + '@aws-sdk/protocol-http': 3.49.0 + '@aws-sdk/smithy-client': 3.49.0 + '@aws-sdk/types': 3.49.0 + '@aws-sdk/url-parser': 3.49.0 + '@aws-sdk/util-base64-browser': 3.49.0 + '@aws-sdk/util-base64-node': 3.49.0 + '@aws-sdk/util-body-length-browser': 3.49.0 + '@aws-sdk/util-body-length-node': 3.49.0 + '@aws-sdk/util-defaults-mode-browser': 3.49.0 + '@aws-sdk/util-defaults-mode-node': 3.49.0 + '@aws-sdk/util-user-agent-browser': 3.49.0 + '@aws-sdk/util-user-agent-node': 3.49.0 + '@aws-sdk/util-utf8-browser': 3.49.0 + '@aws-sdk/util-utf8-node': 3.49.0 tslib: 2.3.1 dev: false /@aws-sdk/client-sts/3.49.0: - resolution: - { - integrity: sha512-vqHNCuQriMZV1aeXc6cza5S9A/2zgfNiTelsmbDQdlCiZQ+YL3nTp1WFeYHap4ffYlLOAD0xv0yz/+naV4oHtQ==, - } - engines: { node: ">=12.0.0" } + resolution: {integrity: sha512-vqHNCuQriMZV1aeXc6cza5S9A/2zgfNiTelsmbDQdlCiZQ+YL3nTp1WFeYHap4ffYlLOAD0xv0yz/+naV4oHtQ==} + engines: {node: '>=12.0.0'} dependencies: - "@aws-crypto/sha256-browser": 2.0.0 - "@aws-crypto/sha256-js": 2.0.0 - "@aws-sdk/config-resolver": 3.49.0 - "@aws-sdk/credential-provider-node": 3.49.0 - "@aws-sdk/fetch-http-handler": 3.49.0 - "@aws-sdk/hash-node": 3.49.0 - "@aws-sdk/invalid-dependency": 3.49.0 - "@aws-sdk/middleware-content-length": 3.49.0 - "@aws-sdk/middleware-host-header": 3.49.0 - "@aws-sdk/middleware-logger": 3.49.0 - "@aws-sdk/middleware-retry": 3.49.0 - "@aws-sdk/middleware-sdk-sts": 3.49.0 - "@aws-sdk/middleware-serde": 3.49.0 - "@aws-sdk/middleware-signing": 3.49.0 - "@aws-sdk/middleware-stack": 3.49.0 - "@aws-sdk/middleware-user-agent": 3.49.0 - "@aws-sdk/node-config-provider": 3.49.0 - "@aws-sdk/node-http-handler": 3.49.0 - "@aws-sdk/protocol-http": 3.49.0 - "@aws-sdk/smithy-client": 3.49.0 - "@aws-sdk/types": 3.49.0 - "@aws-sdk/url-parser": 3.49.0 - "@aws-sdk/util-base64-browser": 3.49.0 - "@aws-sdk/util-base64-node": 3.49.0 - "@aws-sdk/util-body-length-browser": 3.49.0 - "@aws-sdk/util-body-length-node": 3.49.0 - "@aws-sdk/util-defaults-mode-browser": 3.49.0 - "@aws-sdk/util-defaults-mode-node": 3.49.0 - "@aws-sdk/util-user-agent-browser": 3.49.0 - "@aws-sdk/util-user-agent-node": 3.49.0 - "@aws-sdk/util-utf8-browser": 3.49.0 - "@aws-sdk/util-utf8-node": 3.49.0 + '@aws-crypto/sha256-browser': 2.0.0 + '@aws-crypto/sha256-js': 2.0.0 + '@aws-sdk/config-resolver': 3.49.0 + '@aws-sdk/credential-provider-node': 3.49.0 + '@aws-sdk/fetch-http-handler': 3.49.0 + '@aws-sdk/hash-node': 3.49.0 + '@aws-sdk/invalid-dependency': 3.49.0 + '@aws-sdk/middleware-content-length': 3.49.0 + '@aws-sdk/middleware-host-header': 3.49.0 + '@aws-sdk/middleware-logger': 3.49.0 + '@aws-sdk/middleware-retry': 3.49.0 + '@aws-sdk/middleware-sdk-sts': 3.49.0 + '@aws-sdk/middleware-serde': 3.49.0 + '@aws-sdk/middleware-signing': 3.49.0 + '@aws-sdk/middleware-stack': 3.49.0 + '@aws-sdk/middleware-user-agent': 3.49.0 + '@aws-sdk/node-config-provider': 3.49.0 + '@aws-sdk/node-http-handler': 3.49.0 + '@aws-sdk/protocol-http': 3.49.0 + '@aws-sdk/smithy-client': 3.49.0 + '@aws-sdk/types': 3.49.0 + '@aws-sdk/url-parser': 3.49.0 + '@aws-sdk/util-base64-browser': 3.49.0 + '@aws-sdk/util-base64-node': 3.49.0 + '@aws-sdk/util-body-length-browser': 3.49.0 + '@aws-sdk/util-body-length-node': 3.49.0 + '@aws-sdk/util-defaults-mode-browser': 3.49.0 + '@aws-sdk/util-defaults-mode-node': 3.49.0 + '@aws-sdk/util-user-agent-browser': 3.49.0 + '@aws-sdk/util-user-agent-node': 3.49.0 + '@aws-sdk/util-utf8-browser': 3.49.0 + '@aws-sdk/util-utf8-node': 3.49.0 entities: 2.2.0 fast-xml-parser: 3.19.0 tslib: 2.3.1 dev: false /@aws-sdk/config-resolver/3.49.0: - resolution: - { - integrity: sha512-4kYV+89E9MG+/wfPY3dmwqzquQxNd951jdfjQbSg1ii68X/owqmWda4bLulV0Z4iGUz9TXkbaJCWyRyjsFNe4w==, - } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-4kYV+89E9MG+/wfPY3dmwqzquQxNd951jdfjQbSg1ii68X/owqmWda4bLulV0Z4iGUz9TXkbaJCWyRyjsFNe4w==} + engines: {node: '>= 12.0.0'} dependencies: - "@aws-sdk/signature-v4": 3.49.0 - "@aws-sdk/types": 3.49.0 - "@aws-sdk/util-config-provider": 3.49.0 + '@aws-sdk/signature-v4': 3.49.0 + '@aws-sdk/types': 3.49.0 + '@aws-sdk/util-config-provider': 3.49.0 tslib: 2.3.1 dev: false /@aws-sdk/credential-provider-env/3.49.0: - resolution: - { - integrity: sha512-mx82N0un6URmmiM11X3ObK3ka5R99lEFdhwPc0jqsCPnXRVioUtK5DXkJ8FmgixbDcs5Pdij9A8MINSeBrG0bQ==, - } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-mx82N0un6URmmiM11X3ObK3ka5R99lEFdhwPc0jqsCPnXRVioUtK5DXkJ8FmgixbDcs5Pdij9A8MINSeBrG0bQ==} + engines: {node: '>= 12.0.0'} dependencies: - "@aws-sdk/property-provider": 3.49.0 - "@aws-sdk/types": 3.49.0 + '@aws-sdk/property-provider': 3.49.0 + '@aws-sdk/types': 3.49.0 tslib: 2.3.1 dev: false /@aws-sdk/credential-provider-imds/3.49.0: - resolution: - { - integrity: sha512-d/H5VgmRiIupQdPg9QcX16kdvuiS/YytRYCQOncVstNXHvYZM9EgeIXJLXyPNaD2W8SS7CBf4KKWYJn3V+9AZw==, - } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-d/H5VgmRiIupQdPg9QcX16kdvuiS/YytRYCQOncVstNXHvYZM9EgeIXJLXyPNaD2W8SS7CBf4KKWYJn3V+9AZw==} + engines: {node: '>= 12.0.0'} dependencies: - "@aws-sdk/node-config-provider": 3.49.0 - "@aws-sdk/property-provider": 3.49.0 - "@aws-sdk/types": 3.49.0 - "@aws-sdk/url-parser": 3.49.0 + '@aws-sdk/node-config-provider': 3.49.0 + '@aws-sdk/property-provider': 3.49.0 + '@aws-sdk/types': 3.49.0 + '@aws-sdk/url-parser': 3.49.0 tslib: 2.3.1 dev: false /@aws-sdk/credential-provider-ini/3.49.0: - resolution: - { - integrity: sha512-QlWfxDwZVyX36pghgkKGBR+fBmX/mjmvc0kzxUmqGhUYAkDQ0YKR11ybpGBrxBKT4lIOE+9z6Tu4nLjp3OlJIg==, - } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-QlWfxDwZVyX36pghgkKGBR+fBmX/mjmvc0kzxUmqGhUYAkDQ0YKR11ybpGBrxBKT4lIOE+9z6Tu4nLjp3OlJIg==} + engines: {node: '>= 12.0.0'} dependencies: - "@aws-sdk/credential-provider-env": 3.49.0 - "@aws-sdk/credential-provider-imds": 3.49.0 - "@aws-sdk/credential-provider-sso": 3.49.0 - "@aws-sdk/credential-provider-web-identity": 3.49.0 - "@aws-sdk/property-provider": 3.49.0 - "@aws-sdk/shared-ini-file-loader": 3.49.0 - "@aws-sdk/types": 3.49.0 - "@aws-sdk/util-credentials": 3.49.0 + '@aws-sdk/credential-provider-env': 3.49.0 + '@aws-sdk/credential-provider-imds': 3.49.0 + '@aws-sdk/credential-provider-sso': 3.49.0 + '@aws-sdk/credential-provider-web-identity': 3.49.0 + '@aws-sdk/property-provider': 3.49.0 + '@aws-sdk/shared-ini-file-loader': 3.49.0 + '@aws-sdk/types': 3.49.0 + '@aws-sdk/util-credentials': 3.49.0 tslib: 2.3.1 dev: false /@aws-sdk/credential-provider-node/3.49.0: - resolution: - { - integrity: sha512-DonoqOZHcqfNuwMG1fGJpHJTqf1QinihRKzlOoUWzmseqBCiuagGouSN7nOQgee5BrzF0X9/nao9lnPc4on2TA==, - } - engines: { node: ">=12.0.0" } + resolution: {integrity: sha512-DonoqOZHcqfNuwMG1fGJpHJTqf1QinihRKzlOoUWzmseqBCiuagGouSN7nOQgee5BrzF0X9/nao9lnPc4on2TA==} + engines: {node: '>=12.0.0'} dependencies: - "@aws-sdk/credential-provider-env": 3.49.0 - "@aws-sdk/credential-provider-imds": 3.49.0 - "@aws-sdk/credential-provider-ini": 3.49.0 - "@aws-sdk/credential-provider-process": 3.49.0 - "@aws-sdk/credential-provider-sso": 3.49.0 - "@aws-sdk/credential-provider-web-identity": 3.49.0 - "@aws-sdk/property-provider": 3.49.0 - "@aws-sdk/shared-ini-file-loader": 3.49.0 - "@aws-sdk/types": 3.49.0 - "@aws-sdk/util-credentials": 3.49.0 + '@aws-sdk/credential-provider-env': 3.49.0 + '@aws-sdk/credential-provider-imds': 3.49.0 + '@aws-sdk/credential-provider-ini': 3.49.0 + '@aws-sdk/credential-provider-process': 3.49.0 + '@aws-sdk/credential-provider-sso': 3.49.0 + '@aws-sdk/credential-provider-web-identity': 3.49.0 + '@aws-sdk/property-provider': 3.49.0 + '@aws-sdk/shared-ini-file-loader': 3.49.0 + '@aws-sdk/types': 3.49.0 + '@aws-sdk/util-credentials': 3.49.0 tslib: 2.3.1 dev: false /@aws-sdk/credential-provider-process/3.49.0: - resolution: - { - integrity: sha512-Zn7CJHoXln8O+yBKdTBvcwCz6GDh48s7HtIsqjoOrqoL0oZWhgy8gn8S9KQ+HIqpiO8N0lMMteXuPl5fbC2zGg==, - } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-Zn7CJHoXln8O+yBKdTBvcwCz6GDh48s7HtIsqjoOrqoL0oZWhgy8gn8S9KQ+HIqpiO8N0lMMteXuPl5fbC2zGg==} + engines: {node: '>= 12.0.0'} dependencies: - "@aws-sdk/property-provider": 3.49.0 - "@aws-sdk/shared-ini-file-loader": 3.49.0 - "@aws-sdk/types": 3.49.0 - "@aws-sdk/util-credentials": 3.49.0 + '@aws-sdk/property-provider': 3.49.0 + '@aws-sdk/shared-ini-file-loader': 3.49.0 + '@aws-sdk/types': 3.49.0 + '@aws-sdk/util-credentials': 3.49.0 tslib: 2.3.1 dev: false /@aws-sdk/credential-provider-sso/3.49.0: - resolution: - { - integrity: sha512-sFTqbQOyGR88K10Y1OgauEPKDmEibxtAkLSFjkJ6Yw/Jyp+lftchoa+TxRrNvDY1zjZX+XauVI6UmD5Pi4E1NQ==, - } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-sFTqbQOyGR88K10Y1OgauEPKDmEibxtAkLSFjkJ6Yw/Jyp+lftchoa+TxRrNvDY1zjZX+XauVI6UmD5Pi4E1NQ==} + engines: {node: '>= 12.0.0'} dependencies: - "@aws-sdk/client-sso": 3.49.0 - "@aws-sdk/property-provider": 3.49.0 - "@aws-sdk/shared-ini-file-loader": 3.49.0 - "@aws-sdk/types": 3.49.0 - "@aws-sdk/util-credentials": 3.49.0 + '@aws-sdk/client-sso': 3.49.0 + '@aws-sdk/property-provider': 3.49.0 + '@aws-sdk/shared-ini-file-loader': 3.49.0 + '@aws-sdk/types': 3.49.0 + '@aws-sdk/util-credentials': 3.49.0 tslib: 2.3.1 dev: false /@aws-sdk/credential-provider-web-identity/3.49.0: - resolution: - { - integrity: sha512-mlIHUTn04unB0HEwEO12YIY5848uE9B+gYI+YwlNZ70KXGs3lj2horOgPgbxeIfulCVw0aRTKChUg2ActTosYg==, - } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-mlIHUTn04unB0HEwEO12YIY5848uE9B+gYI+YwlNZ70KXGs3lj2horOgPgbxeIfulCVw0aRTKChUg2ActTosYg==} + engines: {node: '>= 12.0.0'} dependencies: - "@aws-sdk/property-provider": 3.49.0 - "@aws-sdk/types": 3.49.0 + '@aws-sdk/property-provider': 3.49.0 + '@aws-sdk/types': 3.49.0 tslib: 2.3.1 dev: false /@aws-sdk/eventstream-marshaller/3.49.0: - resolution: - { - integrity: sha512-EDE7aim4c/G9vCWE+jaHQNWHzrHgF1WlUEok1tgLj+fbvqKduDK/1SNleMZbMLC7tFRARN+KKFB0Ctegyp8j4w==, - } + resolution: {integrity: sha512-EDE7aim4c/G9vCWE+jaHQNWHzrHgF1WlUEok1tgLj+fbvqKduDK/1SNleMZbMLC7tFRARN+KKFB0Ctegyp8j4w==} dependencies: - "@aws-crypto/crc32": 2.0.0 - "@aws-sdk/types": 3.49.0 - "@aws-sdk/util-hex-encoding": 3.49.0 + '@aws-crypto/crc32': 2.0.0 + '@aws-sdk/types': 3.49.0 + '@aws-sdk/util-hex-encoding': 3.49.0 tslib: 2.3.1 dev: false /@aws-sdk/eventstream-serde-browser/3.49.0: - resolution: - { - integrity: sha512-XHDHVGmxzEEXzHESzXYiycE3CxHLedBQljQPCIXKJkUhmwf84oUDB/cRsgWoOwOpcRQQBZGUPTPOOOHVWQOlZA==, - } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-XHDHVGmxzEEXzHESzXYiycE3CxHLedBQljQPCIXKJkUhmwf84oUDB/cRsgWoOwOpcRQQBZGUPTPOOOHVWQOlZA==} + engines: {node: '>= 12.0.0'} dependencies: - "@aws-sdk/eventstream-marshaller": 3.49.0 - "@aws-sdk/eventstream-serde-universal": 3.49.0 - "@aws-sdk/types": 3.49.0 + '@aws-sdk/eventstream-marshaller': 3.49.0 + '@aws-sdk/eventstream-serde-universal': 3.49.0 + '@aws-sdk/types': 3.49.0 tslib: 2.3.1 dev: false /@aws-sdk/eventstream-serde-config-resolver/3.49.0: - resolution: - { - integrity: sha512-7xCaQGfZD0xLpWVUsGEQ2PDAkkfnl1gc10h4jLm0xtEGzr0x3B3fLrmDiZTz+wBxRRuCqjiaBaNuPT+HocGYmA==, - } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-7xCaQGfZD0xLpWVUsGEQ2PDAkkfnl1gc10h4jLm0xtEGzr0x3B3fLrmDiZTz+wBxRRuCqjiaBaNuPT+HocGYmA==} + engines: {node: '>= 12.0.0'} dependencies: - "@aws-sdk/types": 3.49.0 + '@aws-sdk/types': 3.49.0 tslib: 2.3.1 dev: false /@aws-sdk/eventstream-serde-node/3.49.0: - resolution: - { - integrity: sha512-hRZa1QSCtSve3vSgOxtGRn7nlOJFGzaov6hDj+bFEWY6ukHWtG9lbit6ECALcud4BjxvaJet37kqToxnpDFMuQ==, - } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-hRZa1QSCtSve3vSgOxtGRn7nlOJFGzaov6hDj+bFEWY6ukHWtG9lbit6ECALcud4BjxvaJet37kqToxnpDFMuQ==} + engines: {node: '>= 12.0.0'} dependencies: - "@aws-sdk/eventstream-marshaller": 3.49.0 - "@aws-sdk/eventstream-serde-universal": 3.49.0 - "@aws-sdk/types": 3.49.0 + '@aws-sdk/eventstream-marshaller': 3.49.0 + '@aws-sdk/eventstream-serde-universal': 3.49.0 + '@aws-sdk/types': 3.49.0 tslib: 2.3.1 dev: false /@aws-sdk/eventstream-serde-universal/3.49.0: - resolution: - { - integrity: sha512-oW/k0QdJTEwRLbdWpakIKABYKEOokBaPd+VSOYmCNceYPzv74RTDqlzTTRVIGj51Jp+Bm1af3l0k1cpjPDnWfQ==, - } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-oW/k0QdJTEwRLbdWpakIKABYKEOokBaPd+VSOYmCNceYPzv74RTDqlzTTRVIGj51Jp+Bm1af3l0k1cpjPDnWfQ==} + engines: {node: '>= 12.0.0'} dependencies: - "@aws-sdk/eventstream-marshaller": 3.49.0 - "@aws-sdk/types": 3.49.0 + '@aws-sdk/eventstream-marshaller': 3.49.0 + '@aws-sdk/types': 3.49.0 tslib: 2.3.1 dev: false /@aws-sdk/fetch-http-handler/3.49.0: - resolution: - { - integrity: sha512-ougJRsvoIGP0qTgHSGVF1B5Ui95Y/SP5CpY/y6B8vMGrwJ+MmvcCUE3Qd2UJGjO1PfUneno8PHK4u5x7hc2ceA==, - } + resolution: {integrity: sha512-ougJRsvoIGP0qTgHSGVF1B5Ui95Y/SP5CpY/y6B8vMGrwJ+MmvcCUE3Qd2UJGjO1PfUneno8PHK4u5x7hc2ceA==} dependencies: - "@aws-sdk/protocol-http": 3.49.0 - "@aws-sdk/querystring-builder": 3.49.0 - "@aws-sdk/types": 3.49.0 - "@aws-sdk/util-base64-browser": 3.49.0 + '@aws-sdk/protocol-http': 3.49.0 + '@aws-sdk/querystring-builder': 3.49.0 + '@aws-sdk/types': 3.49.0 + '@aws-sdk/util-base64-browser': 3.49.0 tslib: 2.3.1 dev: false /@aws-sdk/hash-blob-browser/3.49.0: - resolution: - { - integrity: sha512-DLLP8dY0eYAXefJJzAYO1QxHW567MN4D2KDxyOKkWOVR2S6ZLgtJb24MhT9RpV3AOM/x686HOv5V4r1rPbuHhw==, - } + resolution: {integrity: sha512-DLLP8dY0eYAXefJJzAYO1QxHW567MN4D2KDxyOKkWOVR2S6ZLgtJb24MhT9RpV3AOM/x686HOv5V4r1rPbuHhw==} dependencies: - "@aws-sdk/chunked-blob-reader": 3.49.0 - "@aws-sdk/chunked-blob-reader-native": 3.49.0 - "@aws-sdk/types": 3.49.0 + '@aws-sdk/chunked-blob-reader': 3.49.0 + '@aws-sdk/chunked-blob-reader-native': 3.49.0 + '@aws-sdk/types': 3.49.0 tslib: 2.3.1 dev: false /@aws-sdk/hash-node/3.49.0: - resolution: - { - integrity: sha512-nRNCmSkcJOzWzKU6NihlKW/6H1HxaaBjUe9ETnwWIgwPC7NZ6IWtri48Cf7Itvveu3dln3ZM2WSXN20TlQpuBw==, - } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-nRNCmSkcJOzWzKU6NihlKW/6H1HxaaBjUe9ETnwWIgwPC7NZ6IWtri48Cf7Itvveu3dln3ZM2WSXN20TlQpuBw==} + engines: {node: '>= 12.0.0'} dependencies: - "@aws-sdk/types": 3.49.0 - "@aws-sdk/util-buffer-from": 3.49.0 + '@aws-sdk/types': 3.49.0 + '@aws-sdk/util-buffer-from': 3.49.0 tslib: 2.3.1 dev: false /@aws-sdk/hash-stream-node/3.49.0: - resolution: - { - integrity: sha512-ZsqGEs3yp5YH5vk1LhpIiSAzNzXHW89DvKn+BSSiDPdufPdOjLks8wKIVhAUfADcJ+S/AATTAt4SoA4mD+IHvQ==, - } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-ZsqGEs3yp5YH5vk1LhpIiSAzNzXHW89DvKn+BSSiDPdufPdOjLks8wKIVhAUfADcJ+S/AATTAt4SoA4mD+IHvQ==} + engines: {node: '>= 12.0.0'} dependencies: - "@aws-sdk/types": 3.49.0 + '@aws-sdk/types': 3.49.0 tslib: 2.3.1 dev: false /@aws-sdk/invalid-dependency/3.49.0: - resolution: - { - integrity: sha512-MXf/9l/Oiy+KGKDvInv0RT4rtS+iNftYRTlTmUn3RR74Kz8Fvw+O0RcUjzSrNn5SpjCf/UsGrF+KBTm2gFYQCA==, - } + resolution: {integrity: sha512-MXf/9l/Oiy+KGKDvInv0RT4rtS+iNftYRTlTmUn3RR74Kz8Fvw+O0RcUjzSrNn5SpjCf/UsGrF+KBTm2gFYQCA==} dependencies: - "@aws-sdk/types": 3.49.0 + '@aws-sdk/types': 3.49.0 tslib: 2.3.1 dev: false /@aws-sdk/is-array-buffer/3.49.0: - resolution: - { - integrity: sha512-tLba+xvlm1+aAnv+bGieVZo8DCENbqfS9kLf/hp+9hrUSiNAsxs9Pqi34JBpMKGn6h9qORp6f8ClRS+gK8yvWg==, - } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-tLba+xvlm1+aAnv+bGieVZo8DCENbqfS9kLf/hp+9hrUSiNAsxs9Pqi34JBpMKGn6h9qORp6f8ClRS+gK8yvWg==} + engines: {node: '>= 12.0.0'} dependencies: tslib: 2.3.1 dev: false /@aws-sdk/md5-js/3.49.0: - resolution: - { - integrity: sha512-CJJxl7FfTNseaw3RAPmihpys1cfbCyg7sLTev6PncqBhoVl+5AWOoITeZj3azE3CKevY2V7PEdmHjzKRNLmcdA==, - } + resolution: {integrity: sha512-CJJxl7FfTNseaw3RAPmihpys1cfbCyg7sLTev6PncqBhoVl+5AWOoITeZj3azE3CKevY2V7PEdmHjzKRNLmcdA==} dependencies: - "@aws-sdk/types": 3.49.0 - "@aws-sdk/util-utf8-browser": 3.49.0 - "@aws-sdk/util-utf8-node": 3.49.0 + '@aws-sdk/types': 3.49.0 + '@aws-sdk/util-utf8-browser': 3.49.0 + '@aws-sdk/util-utf8-node': 3.49.0 tslib: 2.3.1 dev: false /@aws-sdk/middleware-apply-body-checksum/3.49.0: - resolution: - { - integrity: sha512-uNUETgYrOLCZNad+vVsTblTqzuaCVCt67o4VawsziR5QuOCxX/27Ni3x1VNtpI5pUhD8FiT/9/lBpcMTvCSIgA==, - } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-uNUETgYrOLCZNad+vVsTblTqzuaCVCt67o4VawsziR5QuOCxX/27Ni3x1VNtpI5pUhD8FiT/9/lBpcMTvCSIgA==} + engines: {node: '>= 12.0.0'} dependencies: - "@aws-sdk/is-array-buffer": 3.49.0 - "@aws-sdk/protocol-http": 3.49.0 - "@aws-sdk/types": 3.49.0 + '@aws-sdk/is-array-buffer': 3.49.0 + '@aws-sdk/protocol-http': 3.49.0 + '@aws-sdk/types': 3.49.0 tslib: 2.3.1 dev: false /@aws-sdk/middleware-bucket-endpoint/3.49.0: - resolution: - { - integrity: sha512-6GK6BXNRRe+JpBUomW1Wrj7HGU3gdl9XvrQekj772kmiBLx/40ntAxZnhjFjkNKLy7IM+he8yygnEUhbV/dEmg==, - } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-6GK6BXNRRe+JpBUomW1Wrj7HGU3gdl9XvrQekj772kmiBLx/40ntAxZnhjFjkNKLy7IM+he8yygnEUhbV/dEmg==} + engines: {node: '>= 12.0.0'} dependencies: - "@aws-sdk/protocol-http": 3.49.0 - "@aws-sdk/types": 3.49.0 - "@aws-sdk/util-arn-parser": 3.49.0 - "@aws-sdk/util-config-provider": 3.49.0 + '@aws-sdk/protocol-http': 3.49.0 + '@aws-sdk/types': 3.49.0 + '@aws-sdk/util-arn-parser': 3.49.0 + '@aws-sdk/util-config-provider': 3.49.0 tslib: 2.3.1 dev: false /@aws-sdk/middleware-content-length/3.49.0: - resolution: - { - integrity: sha512-7IaghPT7X92gNoyn9LzZj4V5YpGPDCYQTWhpzZoIlw88vOR4I0zKg7jwYeElRoNfRCI4OYhF26ajKnNHzNMlbA==, - } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-7IaghPT7X92gNoyn9LzZj4V5YpGPDCYQTWhpzZoIlw88vOR4I0zKg7jwYeElRoNfRCI4OYhF26ajKnNHzNMlbA==} + engines: {node: '>= 12.0.0'} dependencies: - "@aws-sdk/protocol-http": 3.49.0 - "@aws-sdk/types": 3.49.0 + '@aws-sdk/protocol-http': 3.49.0 + '@aws-sdk/types': 3.49.0 tslib: 2.3.1 dev: false /@aws-sdk/middleware-expect-continue/3.49.0: - resolution: - { - integrity: sha512-i0FMywhvCdn3+gPuUgtM0ZRd3xBHozropbhvs3fu9Y6g1/YiOgTYuVVqHtObvktVWCV72i8fTvn8TEkyFHRDYQ==, - } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-i0FMywhvCdn3+gPuUgtM0ZRd3xBHozropbhvs3fu9Y6g1/YiOgTYuVVqHtObvktVWCV72i8fTvn8TEkyFHRDYQ==} + engines: {node: '>= 12.0.0'} dependencies: - "@aws-sdk/middleware-header-default": 3.49.0 - "@aws-sdk/protocol-http": 3.49.0 - "@aws-sdk/types": 3.49.0 + '@aws-sdk/middleware-header-default': 3.49.0 + '@aws-sdk/protocol-http': 3.49.0 + '@aws-sdk/types': 3.49.0 tslib: 2.3.1 dev: false /@aws-sdk/middleware-header-default/3.49.0: - resolution: - { - integrity: sha512-3xRNPwFMWQqgGKgxDWMgQXNdIr4EVq5ltrLqwsZopXKZli9/fidOrDN9z9j4AW9YAXyEAYB0fscKT7LcsvxFZg==, - } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-3xRNPwFMWQqgGKgxDWMgQXNdIr4EVq5ltrLqwsZopXKZli9/fidOrDN9z9j4AW9YAXyEAYB0fscKT7LcsvxFZg==} + engines: {node: '>= 12.0.0'} dependencies: - "@aws-sdk/protocol-http": 3.49.0 - "@aws-sdk/types": 3.49.0 + '@aws-sdk/protocol-http': 3.49.0 + '@aws-sdk/types': 3.49.0 tslib: 2.3.1 dev: false /@aws-sdk/middleware-host-header/3.49.0: - resolution: - { - integrity: sha512-5l1ILqHs2mGqNvJb5mRe7hHbTQOO292jghE/LItpNx2tiu7BBGzP1bslHcyEVYoRBX9Oqyfou7s9Ww2yBWtImQ==, - } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-5l1ILqHs2mGqNvJb5mRe7hHbTQOO292jghE/LItpNx2tiu7BBGzP1bslHcyEVYoRBX9Oqyfou7s9Ww2yBWtImQ==} + engines: {node: '>= 12.0.0'} dependencies: - "@aws-sdk/protocol-http": 3.49.0 - "@aws-sdk/types": 3.49.0 + '@aws-sdk/protocol-http': 3.49.0 + '@aws-sdk/types': 3.49.0 tslib: 2.3.1 dev: false /@aws-sdk/middleware-location-constraint/3.49.0: - resolution: - { - integrity: sha512-PUQr06/9XQP0JfUY6nFvK5HHcO4aQbzn/iKvB8dl471IXcKVLnJdB2OuIjUUW6fQbKX1K4xcDEifWTSMIECIlg==, - } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-PUQr06/9XQP0JfUY6nFvK5HHcO4aQbzn/iKvB8dl471IXcKVLnJdB2OuIjUUW6fQbKX1K4xcDEifWTSMIECIlg==} + engines: {node: '>= 12.0.0'} dependencies: - "@aws-sdk/types": 3.49.0 + '@aws-sdk/types': 3.49.0 tslib: 2.3.1 dev: false /@aws-sdk/middleware-logger/3.49.0: - resolution: - { - integrity: sha512-OicQ0w5sCJXgpeZ+t3XeJA2R/09YfuSe53Ma6aWZm/2/r8vG/SW0yAnwFvwJeCm3DKtBxU1qO2eWhFqvJuWRVA==, - } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-OicQ0w5sCJXgpeZ+t3XeJA2R/09YfuSe53Ma6aWZm/2/r8vG/SW0yAnwFvwJeCm3DKtBxU1qO2eWhFqvJuWRVA==} + engines: {node: '>= 12.0.0'} dependencies: - "@aws-sdk/types": 3.49.0 + '@aws-sdk/types': 3.49.0 tslib: 2.3.1 dev: false /@aws-sdk/middleware-retry/3.49.0: - resolution: - { - integrity: sha512-RSS4R4PNULHA1b1eYR50YPPfOV2jRuXgLNLVVYMEzzC23MabKEXJYPge+pI1Q9rRrbahVj7aQcOTSpT0MsiEeA==, - } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-RSS4R4PNULHA1b1eYR50YPPfOV2jRuXgLNLVVYMEzzC23MabKEXJYPge+pI1Q9rRrbahVj7aQcOTSpT0MsiEeA==} + engines: {node: '>= 12.0.0'} dependencies: - "@aws-sdk/protocol-http": 3.49.0 - "@aws-sdk/service-error-classification": 3.49.0 - "@aws-sdk/types": 3.49.0 + '@aws-sdk/protocol-http': 3.49.0 + '@aws-sdk/service-error-classification': 3.49.0 + '@aws-sdk/types': 3.49.0 tslib: 2.3.1 uuid: 8.3.2 dev: false /@aws-sdk/middleware-sdk-s3/3.49.0_@aws-sdk+signature-v4-crt@3.49.0: - resolution: - { - integrity: sha512-PeQHJsecGCnXILgOPLgcoZXCa4+2bctus2krhN08ES5oh1obeQB+EOC4v1sWV0DdfyTHEZMDSVLS9qTFHl9IUA==, - } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-PeQHJsecGCnXILgOPLgcoZXCa4+2bctus2krhN08ES5oh1obeQB+EOC4v1sWV0DdfyTHEZMDSVLS9qTFHl9IUA==} + engines: {node: '>= 12.0.0'} peerDependencies: - "@aws-sdk/signature-v4-crt": ^3.31.0 + '@aws-sdk/signature-v4-crt': ^3.31.0 dependencies: - "@aws-sdk/protocol-http": 3.49.0 - "@aws-sdk/signature-v4": 3.49.0 - "@aws-sdk/signature-v4-crt": 3.49.0 - "@aws-sdk/types": 3.49.0 - "@aws-sdk/util-arn-parser": 3.49.0 + '@aws-sdk/protocol-http': 3.49.0 + '@aws-sdk/signature-v4': 3.49.0 + '@aws-sdk/signature-v4-crt': 3.49.0 + '@aws-sdk/types': 3.49.0 + '@aws-sdk/util-arn-parser': 3.49.0 tslib: 2.3.1 dev: false /@aws-sdk/middleware-sdk-sts/3.49.0: - resolution: - { - integrity: sha512-ZnLV37HKpMdK2x+69ZNqAqKr8rmweyMc//yZQRLtUKpKyQ/XSr0/KUBZXkgIny27aaBsrkplA5g1X2GrClRRZA==, - } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-ZnLV37HKpMdK2x+69ZNqAqKr8rmweyMc//yZQRLtUKpKyQ/XSr0/KUBZXkgIny27aaBsrkplA5g1X2GrClRRZA==} + engines: {node: '>= 12.0.0'} dependencies: - "@aws-sdk/middleware-signing": 3.49.0 - "@aws-sdk/property-provider": 3.49.0 - "@aws-sdk/protocol-http": 3.49.0 - "@aws-sdk/signature-v4": 3.49.0 - "@aws-sdk/types": 3.49.0 + '@aws-sdk/middleware-signing': 3.49.0 + '@aws-sdk/property-provider': 3.49.0 + '@aws-sdk/protocol-http': 3.49.0 + '@aws-sdk/signature-v4': 3.49.0 + '@aws-sdk/types': 3.49.0 tslib: 2.3.1 dev: false /@aws-sdk/middleware-serde/3.49.0: - resolution: - { - integrity: sha512-bMAlwR19uFUZ4om+U+/vnPvkcyYw83HdtERF9wrjAqNUnqYsifA0xn4R7Sakg6CW8V0h82dsST6zVfmHd+E2VA==, - } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-bMAlwR19uFUZ4om+U+/vnPvkcyYw83HdtERF9wrjAqNUnqYsifA0xn4R7Sakg6CW8V0h82dsST6zVfmHd+E2VA==} + engines: {node: '>= 12.0.0'} dependencies: - "@aws-sdk/types": 3.49.0 + '@aws-sdk/types': 3.49.0 tslib: 2.3.1 dev: false /@aws-sdk/middleware-signing/3.49.0: - resolution: - { - integrity: sha512-9p0xZjxV9zEQdOpA4MtzmblPRUgchevg5Q70E23yAU/Wjwzwf/6J93cUxdcS8HnmOIl0zQjJaYMTcBsvgJF1eg==, - } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-9p0xZjxV9zEQdOpA4MtzmblPRUgchevg5Q70E23yAU/Wjwzwf/6J93cUxdcS8HnmOIl0zQjJaYMTcBsvgJF1eg==} + engines: {node: '>= 12.0.0'} dependencies: - "@aws-sdk/property-provider": 3.49.0 - "@aws-sdk/protocol-http": 3.49.0 - "@aws-sdk/signature-v4": 3.49.0 - "@aws-sdk/types": 3.49.0 + '@aws-sdk/property-provider': 3.49.0 + '@aws-sdk/protocol-http': 3.49.0 + '@aws-sdk/signature-v4': 3.49.0 + '@aws-sdk/types': 3.49.0 tslib: 2.3.1 dev: false /@aws-sdk/middleware-ssec/3.49.0: - resolution: - { - integrity: sha512-S7hfgOCYoqPe4YegSnwhQJ0KBVEhP6dIp7yLa1/yCJkqW2KSnLTvg6ao9vEFf/lhctZ72YZjm8v/2siXWCnDEA==, - } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-S7hfgOCYoqPe4YegSnwhQJ0KBVEhP6dIp7yLa1/yCJkqW2KSnLTvg6ao9vEFf/lhctZ72YZjm8v/2siXWCnDEA==} + engines: {node: '>= 12.0.0'} dependencies: - "@aws-sdk/types": 3.49.0 + '@aws-sdk/types': 3.49.0 tslib: 2.3.1 dev: false /@aws-sdk/middleware-stack/3.49.0: - resolution: - { - integrity: sha512-OZxs2P7EH58gkvkTI7ESlUPGam0TkE0ZxOX35KjCOcYMuWvTMshKBzHRLX64nz3DYdaHSIGHgB1v8LKELZjltw==, - } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-OZxs2P7EH58gkvkTI7ESlUPGam0TkE0ZxOX35KjCOcYMuWvTMshKBzHRLX64nz3DYdaHSIGHgB1v8LKELZjltw==} + engines: {node: '>= 12.0.0'} dependencies: tslib: 2.3.1 dev: false /@aws-sdk/middleware-user-agent/3.49.0: - resolution: - { - integrity: sha512-uAcKgafZ12L8UnyeQGgSFtwOKUfiBWDLt4P2fEHvZRz/HAIcK4pu1PXPArjwteinhUiCDNFfPw8hAPvstMoG6w==, - } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-uAcKgafZ12L8UnyeQGgSFtwOKUfiBWDLt4P2fEHvZRz/HAIcK4pu1PXPArjwteinhUiCDNFfPw8hAPvstMoG6w==} + engines: {node: '>= 12.0.0'} dependencies: - "@aws-sdk/protocol-http": 3.49.0 - "@aws-sdk/types": 3.49.0 + '@aws-sdk/protocol-http': 3.49.0 + '@aws-sdk/types': 3.49.0 tslib: 2.3.1 dev: false /@aws-sdk/node-config-provider/3.49.0: - resolution: - { - integrity: sha512-OCn5c6M/RJDEO80Q+Iy4ADSYgqd/uyEsvp+7lU4di4bMND9kYT4JO2ky2SWjIuARGq/mhMOhaN2DO9MbcqV20g==, - } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-OCn5c6M/RJDEO80Q+Iy4ADSYgqd/uyEsvp+7lU4di4bMND9kYT4JO2ky2SWjIuARGq/mhMOhaN2DO9MbcqV20g==} + engines: {node: '>= 12.0.0'} dependencies: - "@aws-sdk/property-provider": 3.49.0 - "@aws-sdk/shared-ini-file-loader": 3.49.0 - "@aws-sdk/types": 3.49.0 + '@aws-sdk/property-provider': 3.49.0 + '@aws-sdk/shared-ini-file-loader': 3.49.0 + '@aws-sdk/types': 3.49.0 tslib: 2.3.1 dev: false /@aws-sdk/node-http-handler/3.49.0: - resolution: - { - integrity: sha512-t7D9hoSigBihC9RRgYrkzgSER0fYzZe7192pJAaP6jk13ZOpccQRfXZoKge/cm42aHJsTy8DdQdGtLg7wLTp0g==, - } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-t7D9hoSigBihC9RRgYrkzgSER0fYzZe7192pJAaP6jk13ZOpccQRfXZoKge/cm42aHJsTy8DdQdGtLg7wLTp0g==} + engines: {node: '>= 12.0.0'} dependencies: - "@aws-sdk/abort-controller": 3.49.0 - "@aws-sdk/protocol-http": 3.49.0 - "@aws-sdk/querystring-builder": 3.49.0 - "@aws-sdk/types": 3.49.0 + '@aws-sdk/abort-controller': 3.49.0 + '@aws-sdk/protocol-http': 3.49.0 + '@aws-sdk/querystring-builder': 3.49.0 + '@aws-sdk/types': 3.49.0 tslib: 2.3.1 dev: false /@aws-sdk/property-provider/3.49.0: - resolution: - { - integrity: sha512-6D48YpriOUpniezVuRI8J+MG+vgwb5C1SzBdgN4+S6DqbsEZy+kdEubXdoMICEd+Z8h7lH3p5zMr6po0icGCfA==, - } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-6D48YpriOUpniezVuRI8J+MG+vgwb5C1SzBdgN4+S6DqbsEZy+kdEubXdoMICEd+Z8h7lH3p5zMr6po0icGCfA==} + engines: {node: '>= 12.0.0'} dependencies: - "@aws-sdk/types": 3.49.0 + '@aws-sdk/types': 3.49.0 tslib: 2.3.1 dev: false /@aws-sdk/protocol-http/3.49.0: - resolution: - { - integrity: sha512-lb9CO7/vm26v4UwveWb4jSapqWWP/p0b9SuHpRExq+yMuvHqwxcoGrxmvS7FsanWbepRF1895dxU/Ar6/4pviA==, - } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-lb9CO7/vm26v4UwveWb4jSapqWWP/p0b9SuHpRExq+yMuvHqwxcoGrxmvS7FsanWbepRF1895dxU/Ar6/4pviA==} + engines: {node: '>= 12.0.0'} dependencies: - "@aws-sdk/types": 3.49.0 + '@aws-sdk/types': 3.49.0 tslib: 2.3.1 dev: false /@aws-sdk/querystring-builder/3.49.0: - resolution: - { - integrity: sha512-+NlgIYihVyUetZcrF1ADY0eg8WW1/ETD5bzTwguTiKduXVHmavSakXK1jJF5vg05mefljToZXjQnOaEs9+K9LA==, - } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-+NlgIYihVyUetZcrF1ADY0eg8WW1/ETD5bzTwguTiKduXVHmavSakXK1jJF5vg05mefljToZXjQnOaEs9+K9LA==} + engines: {node: '>= 12.0.0'} dependencies: - "@aws-sdk/types": 3.49.0 - "@aws-sdk/util-uri-escape": 3.49.0 + '@aws-sdk/types': 3.49.0 + '@aws-sdk/util-uri-escape': 3.49.0 tslib: 2.3.1 dev: false /@aws-sdk/querystring-parser/3.49.0: - resolution: - { - integrity: sha512-4bSCHI5A8wi+JjsD1gAhMuGRGjDmlw6MoMWUiv4R2J8Ow/9mV8biKRo2ZytUPiSSu4G5JQ7mEkqFsm/VgkstDQ==, - } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-4bSCHI5A8wi+JjsD1gAhMuGRGjDmlw6MoMWUiv4R2J8Ow/9mV8biKRo2ZytUPiSSu4G5JQ7mEkqFsm/VgkstDQ==} + engines: {node: '>= 12.0.0'} dependencies: - "@aws-sdk/types": 3.49.0 + '@aws-sdk/types': 3.49.0 tslib: 2.3.1 dev: false /@aws-sdk/service-error-classification/3.49.0: - resolution: - { - integrity: sha512-iVmf7RcrIsM/We5ip8fe14RzEpSLF5eN8oqhCMftEdmMVnOYMd/9x0f1w69fbyBJNIIpyREqM8eAKz5OWQn5/g==, - } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-iVmf7RcrIsM/We5ip8fe14RzEpSLF5eN8oqhCMftEdmMVnOYMd/9x0f1w69fbyBJNIIpyREqM8eAKz5OWQn5/g==} + engines: {node: '>= 12.0.0'} dev: false /@aws-sdk/shared-ini-file-loader/3.49.0: - resolution: - { - integrity: sha512-TcgKU6U/3JZpenRFhGSy5R5QsBWkYoeawTK1rTK6deu3UbxVwtOkietbfwP3kIwKZ4hz6OkNeHcOJtXX/InZKw==, - } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-TcgKU6U/3JZpenRFhGSy5R5QsBWkYoeawTK1rTK6deu3UbxVwtOkietbfwP3kIwKZ4hz6OkNeHcOJtXX/InZKw==} + engines: {node: '>= 12.0.0'} dependencies: tslib: 2.3.1 dev: false /@aws-sdk/signature-v4-crt/3.49.0: - resolution: - { - integrity: sha512-jTHX6kNBV4ulj3y7klUznsmb8SsSz1IcDoorYftuVcGZsXtRsd96XSsPA9NSWHWtAj+1O7iQ28u4nRV7d04WdA==, - } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-jTHX6kNBV4ulj3y7klUznsmb8SsSz1IcDoorYftuVcGZsXtRsd96XSsPA9NSWHWtAj+1O7iQ28u4nRV7d04WdA==} + engines: {node: '>= 12.0.0'} dependencies: - "@aws-sdk/is-array-buffer": 3.49.0 - "@aws-sdk/querystring-parser": 3.49.0 - "@aws-sdk/signature-v4": 3.49.0 - "@aws-sdk/util-hex-encoding": 3.49.0 - "@aws-sdk/util-uri-escape": 3.49.0 + '@aws-sdk/is-array-buffer': 3.49.0 + '@aws-sdk/querystring-parser': 3.49.0 + '@aws-sdk/signature-v4': 3.49.0 + '@aws-sdk/util-hex-encoding': 3.49.0 + '@aws-sdk/util-uri-escape': 3.49.0 aws-crt: 1.10.6 tslib: 2.3.1 transitivePeerDependencies: @@ -976,289 +803,211 @@ packages: dev: false /@aws-sdk/signature-v4/3.49.0: - resolution: - { - integrity: sha512-mQSGclWmv/9/MsgthBuKMHN6nkkhGTLXspkhqJ9xSUhjhoaHQVwMoJc39PowJGbYFn1AtCvHAqJtFXTGsMRwPA==, - } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-mQSGclWmv/9/MsgthBuKMHN6nkkhGTLXspkhqJ9xSUhjhoaHQVwMoJc39PowJGbYFn1AtCvHAqJtFXTGsMRwPA==} + engines: {node: '>= 12.0.0'} dependencies: - "@aws-sdk/is-array-buffer": 3.49.0 - "@aws-sdk/types": 3.49.0 - "@aws-sdk/util-hex-encoding": 3.49.0 - "@aws-sdk/util-uri-escape": 3.49.0 + '@aws-sdk/is-array-buffer': 3.49.0 + '@aws-sdk/types': 3.49.0 + '@aws-sdk/util-hex-encoding': 3.49.0 + '@aws-sdk/util-uri-escape': 3.49.0 tslib: 2.3.1 dev: false /@aws-sdk/smithy-client/3.49.0: - resolution: - { - integrity: sha512-ANh9SlEvuru1DcalVoQ7K6wSCYuw4jyeTsIDsJSa13ckrmXAg+ql9vq61ELAXTSNVmuAISuuPjbVaWvOYCtdCQ==, - } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-ANh9SlEvuru1DcalVoQ7K6wSCYuw4jyeTsIDsJSa13ckrmXAg+ql9vq61ELAXTSNVmuAISuuPjbVaWvOYCtdCQ==} + engines: {node: '>= 12.0.0'} dependencies: - "@aws-sdk/middleware-stack": 3.49.0 - "@aws-sdk/types": 3.49.0 + '@aws-sdk/middleware-stack': 3.49.0 + '@aws-sdk/types': 3.49.0 tslib: 2.3.1 dev: false /@aws-sdk/types/3.49.0: - resolution: - { - integrity: sha512-8bCqEpquTlPN6xkjaJ+s+RoEFIu5r4G8oXOsQ5HYBvBdpx62HnCqzHLFNHycL2b8sE+VysQgNvmdQYR98vdMGQ==, - } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-8bCqEpquTlPN6xkjaJ+s+RoEFIu5r4G8oXOsQ5HYBvBdpx62HnCqzHLFNHycL2b8sE+VysQgNvmdQYR98vdMGQ==} + engines: {node: '>= 12.0.0'} dev: false /@aws-sdk/url-parser/3.49.0: - resolution: - { - integrity: sha512-l5td6eu+sIjzNsZYVGr7Mz6vssf2j/8/QrrTYF76J2R6OoceCsKdyJgJPP/tXBdcp4HUZDdVcMqtWRbxGC4izQ==, - } + resolution: {integrity: sha512-l5td6eu+sIjzNsZYVGr7Mz6vssf2j/8/QrrTYF76J2R6OoceCsKdyJgJPP/tXBdcp4HUZDdVcMqtWRbxGC4izQ==} dependencies: - "@aws-sdk/querystring-parser": 3.49.0 - "@aws-sdk/types": 3.49.0 + '@aws-sdk/querystring-parser': 3.49.0 + '@aws-sdk/types': 3.49.0 tslib: 2.3.1 dev: false /@aws-sdk/util-arn-parser/3.49.0: - resolution: - { - integrity: sha512-mAF4lzxqMUFMQAf+NQSxW7jC5hYXNM7mPItBFs/yI3F25MXBw88Q+wAof7iIyRnRcpJvgZ3I7P8vJriAwrSbFg==, - } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-mAF4lzxqMUFMQAf+NQSxW7jC5hYXNM7mPItBFs/yI3F25MXBw88Q+wAof7iIyRnRcpJvgZ3I7P8vJriAwrSbFg==} + engines: {node: '>= 12.0.0'} dependencies: tslib: 2.3.1 dev: false /@aws-sdk/util-base64-browser/3.49.0: - resolution: - { - integrity: sha512-HFXJbsJC6AfrnO9M8KuFDo4ihvLbCbCFCfpWy0Gs4t8kTcvGqH8fIpfVsQKAtFHMmb8fen2LduOk+NNSA7srYw==, - } + resolution: {integrity: sha512-HFXJbsJC6AfrnO9M8KuFDo4ihvLbCbCFCfpWy0Gs4t8kTcvGqH8fIpfVsQKAtFHMmb8fen2LduOk+NNSA7srYw==} dependencies: tslib: 2.3.1 dev: false /@aws-sdk/util-base64-node/3.49.0: - resolution: - { - integrity: sha512-xFAzOLZJOEZipG3KVLjB5z1g5PJSi6cmZOGWg2NC2/H5N0/Z+e5ObnIH8mpfO1d6kWchUuo3qJ6fTOvg/ynw7A==, - } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-xFAzOLZJOEZipG3KVLjB5z1g5PJSi6cmZOGWg2NC2/H5N0/Z+e5ObnIH8mpfO1d6kWchUuo3qJ6fTOvg/ynw7A==} + engines: {node: '>= 12.0.0'} dependencies: - "@aws-sdk/util-buffer-from": 3.49.0 + '@aws-sdk/util-buffer-from': 3.49.0 tslib: 2.3.1 dev: false /@aws-sdk/util-body-length-browser/3.49.0: - resolution: - { - integrity: sha512-4a9Bw33JGKefaZDORlosQRMKxJGEYEiDD5kgNvwIv+KRl5yj2unePia6aFWMqXTWqidOb9WVlqc0Lh73ei5pTg==, - } + resolution: {integrity: sha512-4a9Bw33JGKefaZDORlosQRMKxJGEYEiDD5kgNvwIv+KRl5yj2unePia6aFWMqXTWqidOb9WVlqc0Lh73ei5pTg==} dependencies: tslib: 2.3.1 dev: false /@aws-sdk/util-body-length-node/3.49.0: - resolution: - { - integrity: sha512-ME5Sc8jo9BzToUjWskQKZM/NqN9PpwRDTOSH6EISDBUiH5bhWfY8MLkZqIN2UZz/XOiV3yOeWAU+fMYNnGdAQQ==, - } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-ME5Sc8jo9BzToUjWskQKZM/NqN9PpwRDTOSH6EISDBUiH5bhWfY8MLkZqIN2UZz/XOiV3yOeWAU+fMYNnGdAQQ==} + engines: {node: '>= 12.0.0'} dependencies: tslib: 2.3.1 dev: false /@aws-sdk/util-buffer-from/3.49.0: - resolution: - { - integrity: sha512-8JbIPYn91f+16QpDk000PdIBlBZu8/SoL1nF2fpAJ+M98jXpKUws3oiCztJ2FPIKRe/3ikKuZM4HxWrDyJa40Q==, - } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-8JbIPYn91f+16QpDk000PdIBlBZu8/SoL1nF2fpAJ+M98jXpKUws3oiCztJ2FPIKRe/3ikKuZM4HxWrDyJa40Q==} + engines: {node: '>= 12.0.0'} dependencies: - "@aws-sdk/is-array-buffer": 3.49.0 + '@aws-sdk/is-array-buffer': 3.49.0 tslib: 2.3.1 dev: false /@aws-sdk/util-config-provider/3.49.0: - resolution: - { - integrity: sha512-oVGT9q9UIGdv9Cra4B51QNciWKYQXTlfh8oD2FgLp91NbGTIkQLvK7Pah4TbBoa5+0u/obBI07UwCVn7wphWBQ==, - } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-oVGT9q9UIGdv9Cra4B51QNciWKYQXTlfh8oD2FgLp91NbGTIkQLvK7Pah4TbBoa5+0u/obBI07UwCVn7wphWBQ==} + engines: {node: '>= 12.0.0'} dependencies: tslib: 2.3.1 dev: false /@aws-sdk/util-credentials/3.49.0: - resolution: - { - integrity: sha512-RzbKeuylb56m0zPuLGl5/TkN07+c4PKhZu3hikpsvN8n8n7aFHWPUus63QEGgVaUMCZD0QV6HqJfsCVVFF7UIg==, - } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-RzbKeuylb56m0zPuLGl5/TkN07+c4PKhZu3hikpsvN8n8n7aFHWPUus63QEGgVaUMCZD0QV6HqJfsCVVFF7UIg==} + engines: {node: '>= 12.0.0'} dependencies: - "@aws-sdk/shared-ini-file-loader": 3.49.0 + '@aws-sdk/shared-ini-file-loader': 3.49.0 tslib: 2.3.1 dev: false /@aws-sdk/util-defaults-mode-browser/3.49.0: - resolution: - { - integrity: sha512-MNOvFET5TNBHgvsmfcLuxVDDUV0OIjE1lxrdbXWol7bMgLc2uL3/QOXWKCOUzcFCqTOnWCvbnwkOjs3f7Dpzmw==, - } - engines: { node: ">= 10.0.0" } + resolution: {integrity: sha512-MNOvFET5TNBHgvsmfcLuxVDDUV0OIjE1lxrdbXWol7bMgLc2uL3/QOXWKCOUzcFCqTOnWCvbnwkOjs3f7Dpzmw==} + engines: {node: '>= 10.0.0'} dependencies: - "@aws-sdk/property-provider": 3.49.0 - "@aws-sdk/types": 3.49.0 + '@aws-sdk/property-provider': 3.49.0 + '@aws-sdk/types': 3.49.0 bowser: 2.11.0 tslib: 2.3.1 dev: false /@aws-sdk/util-defaults-mode-node/3.49.0: - resolution: - { - integrity: sha512-bd5/ZJlNIX25n83mvcCh9eYQiDdf6gLHNH6ZftA/EbFfkE0o/qHrFhBhiB9HBY9ZoHhtoqrJNv5W9qKFHx1EIA==, - } - engines: { node: ">= 10.0.0" } + resolution: {integrity: sha512-bd5/ZJlNIX25n83mvcCh9eYQiDdf6gLHNH6ZftA/EbFfkE0o/qHrFhBhiB9HBY9ZoHhtoqrJNv5W9qKFHx1EIA==} + engines: {node: '>= 10.0.0'} dependencies: - "@aws-sdk/config-resolver": 3.49.0 - "@aws-sdk/credential-provider-imds": 3.49.0 - "@aws-sdk/node-config-provider": 3.49.0 - "@aws-sdk/property-provider": 3.49.0 - "@aws-sdk/types": 3.49.0 + '@aws-sdk/config-resolver': 3.49.0 + '@aws-sdk/credential-provider-imds': 3.49.0 + '@aws-sdk/node-config-provider': 3.49.0 + '@aws-sdk/property-provider': 3.49.0 + '@aws-sdk/types': 3.49.0 tslib: 2.3.1 dev: false /@aws-sdk/util-hex-encoding/3.49.0: - resolution: - { - integrity: sha512-ZbPu8Dd3Qm0BMP71FWUH7KPpZA/6izfkDlxbvHxtHdW7XYZALuJ0cVRpWGIY2fCSuA9X8Jfn60KMyjuSAuzM1w==, - } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-ZbPu8Dd3Qm0BMP71FWUH7KPpZA/6izfkDlxbvHxtHdW7XYZALuJ0cVRpWGIY2fCSuA9X8Jfn60KMyjuSAuzM1w==} + engines: {node: '>= 12.0.0'} dependencies: tslib: 2.3.1 dev: false /@aws-sdk/util-locate-window/3.49.0: - resolution: - { - integrity: sha512-ryw+t+quF1raaK0nXSplMiCVnahNLNgNDijZCFFkddGTMaCy+L4VRLYyNms3bgwt3G0BmVn9f3uyDWRSkn5sSg==, - } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-ryw+t+quF1raaK0nXSplMiCVnahNLNgNDijZCFFkddGTMaCy+L4VRLYyNms3bgwt3G0BmVn9f3uyDWRSkn5sSg==} + engines: {node: '>= 12.0.0'} dependencies: tslib: 2.3.1 dev: false /@aws-sdk/util-uri-escape/3.49.0: - resolution: - { - integrity: sha512-NH7iQUYvijYZEOzZkF/QQrp8kBOA9H0Z89hR/63FDCjr1M0Cdcs1bLaFO0a0qbW9NQtoYNsMBMk7pTveDrAzTw==, - } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-NH7iQUYvijYZEOzZkF/QQrp8kBOA9H0Z89hR/63FDCjr1M0Cdcs1bLaFO0a0qbW9NQtoYNsMBMk7pTveDrAzTw==} + engines: {node: '>= 12.0.0'} dependencies: tslib: 2.3.1 dev: false /@aws-sdk/util-user-agent-browser/3.49.0: - resolution: - { - integrity: sha512-RR4E6WlDSu9SivPjx/Jddo87PeVg6dhRL0XGdDBpew7i8bfwqCvxQydkbWIetxucLrt9zII9QnLDQUPBue1xUw==, - } + resolution: {integrity: sha512-RR4E6WlDSu9SivPjx/Jddo87PeVg6dhRL0XGdDBpew7i8bfwqCvxQydkbWIetxucLrt9zII9QnLDQUPBue1xUw==} dependencies: - "@aws-sdk/types": 3.49.0 + '@aws-sdk/types': 3.49.0 bowser: 2.11.0 tslib: 2.3.1 dev: false /@aws-sdk/util-user-agent-node/3.49.0: - resolution: - { - integrity: sha512-ixUkF6kcDfsWO0kivyOKAnBITJm7InGa04ALbgAfuuE7RU1cVkXVMFIn5vux7QkziK7+JwozM9SNPIwNukElDw==, - } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-ixUkF6kcDfsWO0kivyOKAnBITJm7InGa04ALbgAfuuE7RU1cVkXVMFIn5vux7QkziK7+JwozM9SNPIwNukElDw==} + engines: {node: '>= 12.0.0'} dependencies: - "@aws-sdk/node-config-provider": 3.49.0 - "@aws-sdk/types": 3.49.0 + '@aws-sdk/node-config-provider': 3.49.0 + '@aws-sdk/types': 3.49.0 tslib: 2.3.1 dev: false /@aws-sdk/util-utf8-browser/3.49.0: - resolution: - { - integrity: sha512-u9ZgAiTWX9yZFQ/ptlnVpYJ/rXF7aE2Wagar1IjhZrnxXbpVJvcX1EeRayxI1P5AAp2y2fiEKHZzX9ugTwOcEg==, - } + resolution: {integrity: sha512-u9ZgAiTWX9yZFQ/ptlnVpYJ/rXF7aE2Wagar1IjhZrnxXbpVJvcX1EeRayxI1P5AAp2y2fiEKHZzX9ugTwOcEg==} dependencies: tslib: 2.3.1 dev: false /@aws-sdk/util-utf8-node/3.49.0: - resolution: - { - integrity: sha512-QTF5b5OT2y6xsQl8sDiiXqg2n/VtgqFA+tP3WMooOSFd/ZFBbT6HoiSHXHMeTjpB/L9ZT+eUaCoBz8Jq09lBDg==, - } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-QTF5b5OT2y6xsQl8sDiiXqg2n/VtgqFA+tP3WMooOSFd/ZFBbT6HoiSHXHMeTjpB/L9ZT+eUaCoBz8Jq09lBDg==} + engines: {node: '>= 12.0.0'} dependencies: - "@aws-sdk/util-buffer-from": 3.49.0 + '@aws-sdk/util-buffer-from': 3.49.0 tslib: 2.3.1 dev: false /@aws-sdk/util-waiter/3.49.0: - resolution: - { - integrity: sha512-eKrKOLcIhjA/MyjIfwgLL6ZUfGYf6VxBXryuRf8CmJKAo71ZO6ipAsy1X3uYrfwQQBMKV4VZu48BJx5PUBM7GA==, - } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-eKrKOLcIhjA/MyjIfwgLL6ZUfGYf6VxBXryuRf8CmJKAo71ZO6ipAsy1X3uYrfwQQBMKV4VZu48BJx5PUBM7GA==} + engines: {node: '>= 12.0.0'} dependencies: - "@aws-sdk/abort-controller": 3.49.0 - "@aws-sdk/types": 3.49.0 + '@aws-sdk/abort-controller': 3.49.0 + '@aws-sdk/types': 3.49.0 tslib: 2.3.1 dev: false /@aws-sdk/xml-builder/3.49.0: - resolution: - { - integrity: sha512-eU5sIZRWq8pNmw++RfpFI8396UtDW8JxYLcKfJLGQC/qDhwCo+sNPKydXFmDrXTIt/khs3K0qx/vZ5V76irc2Q==, - } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-eU5sIZRWq8pNmw++RfpFI8396UtDW8JxYLcKfJLGQC/qDhwCo+sNPKydXFmDrXTIt/khs3K0qx/vZ5V76irc2Q==} + engines: {node: '>= 12.0.0'} dependencies: tslib: 2.3.1 dev: false /@babel/code-frame/7.16.7: - resolution: - { - integrity: sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==, - } - engines: { node: ">=6.9.0" } + resolution: {integrity: sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==} + engines: {node: '>=6.9.0'} dependencies: - "@babel/highlight": 7.16.10 + '@babel/highlight': 7.16.10 dev: true /@babel/compat-data/7.17.0: - resolution: - { - integrity: sha512-392byTlpGWXMv4FbyWw3sAZ/FrW/DrwqLGXpy0mbyNe9Taqv1mg9yON5/o0cnr8XYCkFTZbC1eV+c+LAROgrng==, - } - engines: { node: ">=6.9.0" } + resolution: {integrity: sha512-392byTlpGWXMv4FbyWw3sAZ/FrW/DrwqLGXpy0mbyNe9Taqv1mg9yON5/o0cnr8XYCkFTZbC1eV+c+LAROgrng==} + engines: {node: '>=6.9.0'} dev: true /@babel/core/7.17.0: - resolution: - { - integrity: sha512-x/5Ea+RO5MvF9ize5DeVICJoVrNv0Mi2RnIABrZEKYvPEpldXwauPkgvYA17cKa6WpU3LoYvYbuEMFtSNFsarA==, - } - engines: { node: ">=6.9.0" } + resolution: {integrity: sha512-x/5Ea+RO5MvF9ize5DeVICJoVrNv0Mi2RnIABrZEKYvPEpldXwauPkgvYA17cKa6WpU3LoYvYbuEMFtSNFsarA==} + engines: {node: '>=6.9.0'} dependencies: - "@ampproject/remapping": 2.1.0 - "@babel/code-frame": 7.16.7 - "@babel/generator": 7.17.0 - "@babel/helper-compilation-targets": 7.16.7_@babel+core@7.17.0 - "@babel/helper-module-transforms": 7.16.7 - "@babel/helpers": 7.17.0 - "@babel/parser": 7.17.0 - "@babel/template": 7.16.7 - "@babel/traverse": 7.17.0 - "@babel/types": 7.17.0 + '@ampproject/remapping': 2.1.0 + '@babel/code-frame': 7.16.7 + '@babel/generator': 7.17.0 + '@babel/helper-compilation-targets': 7.16.7_@babel+core@7.17.0 + '@babel/helper-module-transforms': 7.16.7 + '@babel/helpers': 7.17.0 + '@babel/parser': 7.17.0 + '@babel/template': 7.16.7 + '@babel/traverse': 7.17.0 + '@babel/types': 7.17.0 convert-source-map: 1.8.0 debug: 4.3.3 gensync: 1.0.0-beta.2 @@ -1269,202 +1018,151 @@ packages: dev: true /@babel/generator/7.17.0: - resolution: - { - integrity: sha512-I3Omiv6FGOC29dtlZhkfXO6pgkmukJSlT26QjVvS1DGZe/NzSVCPG41X0tS21oZkJYlovfj9qDWgKP+Cn4bXxw==, - } - engines: { node: ">=6.9.0" } + resolution: {integrity: sha512-I3Omiv6FGOC29dtlZhkfXO6pgkmukJSlT26QjVvS1DGZe/NzSVCPG41X0tS21oZkJYlovfj9qDWgKP+Cn4bXxw==} + engines: {node: '>=6.9.0'} dependencies: - "@babel/types": 7.17.0 + '@babel/types': 7.17.0 jsesc: 2.5.2 source-map: 0.5.7 dev: true /@babel/helper-compilation-targets/7.16.7_@babel+core@7.17.0: - resolution: - { - integrity: sha512-mGojBwIWcwGD6rfqgRXVlVYmPAv7eOpIemUG3dGnDdCY4Pae70ROij3XmfrH6Fa1h1aiDylpglbZyktfzyo/hA==, - } - engines: { node: ">=6.9.0" } + resolution: {integrity: sha512-mGojBwIWcwGD6rfqgRXVlVYmPAv7eOpIemUG3dGnDdCY4Pae70ROij3XmfrH6Fa1h1aiDylpglbZyktfzyo/hA==} + engines: {node: '>=6.9.0'} peerDependencies: - "@babel/core": ^7.0.0 + '@babel/core': ^7.0.0 dependencies: - "@babel/compat-data": 7.17.0 - "@babel/core": 7.17.0 - "@babel/helper-validator-option": 7.16.7 + '@babel/compat-data': 7.17.0 + '@babel/core': 7.17.0 + '@babel/helper-validator-option': 7.16.7 browserslist: 4.19.1 semver: 6.3.0 dev: true /@babel/helper-environment-visitor/7.16.7: - resolution: - { - integrity: sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==, - } - engines: { node: ">=6.9.0" } + resolution: {integrity: sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==} + engines: {node: '>=6.9.0'} dependencies: - "@babel/types": 7.17.0 + '@babel/types': 7.17.0 dev: true /@babel/helper-function-name/7.16.7: - resolution: - { - integrity: sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==, - } - engines: { node: ">=6.9.0" } + resolution: {integrity: sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==} + engines: {node: '>=6.9.0'} dependencies: - "@babel/helper-get-function-arity": 7.16.7 - "@babel/template": 7.16.7 - "@babel/types": 7.17.0 + '@babel/helper-get-function-arity': 7.16.7 + '@babel/template': 7.16.7 + '@babel/types': 7.17.0 dev: true /@babel/helper-get-function-arity/7.16.7: - resolution: - { - integrity: sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==, - } - engines: { node: ">=6.9.0" } + resolution: {integrity: sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==} + engines: {node: '>=6.9.0'} dependencies: - "@babel/types": 7.17.0 + '@babel/types': 7.17.0 dev: true /@babel/helper-hoist-variables/7.16.7: - resolution: - { - integrity: sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==, - } - engines: { node: ">=6.9.0" } + resolution: {integrity: sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==} + engines: {node: '>=6.9.0'} dependencies: - "@babel/types": 7.17.0 + '@babel/types': 7.17.0 dev: true /@babel/helper-module-imports/7.16.7: - resolution: - { - integrity: sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==, - } - engines: { node: ">=6.9.0" } + resolution: {integrity: sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==} + engines: {node: '>=6.9.0'} dependencies: - "@babel/types": 7.17.0 + '@babel/types': 7.17.0 dev: true /@babel/helper-module-transforms/7.16.7: - resolution: - { - integrity: sha512-gaqtLDxJEFCeQbYp9aLAefjhkKdjKcdh6DB7jniIGU3Pz52WAmP268zK0VgPz9hUNkMSYeH976K2/Y6yPadpng==, - } - engines: { node: ">=6.9.0" } + resolution: {integrity: sha512-gaqtLDxJEFCeQbYp9aLAefjhkKdjKcdh6DB7jniIGU3Pz52WAmP268zK0VgPz9hUNkMSYeH976K2/Y6yPadpng==} + engines: {node: '>=6.9.0'} dependencies: - "@babel/helper-environment-visitor": 7.16.7 - "@babel/helper-module-imports": 7.16.7 - "@babel/helper-simple-access": 7.16.7 - "@babel/helper-split-export-declaration": 7.16.7 - "@babel/helper-validator-identifier": 7.16.7 - "@babel/template": 7.16.7 - "@babel/traverse": 7.17.0 - "@babel/types": 7.17.0 + '@babel/helper-environment-visitor': 7.16.7 + '@babel/helper-module-imports': 7.16.7 + '@babel/helper-simple-access': 7.16.7 + '@babel/helper-split-export-declaration': 7.16.7 + '@babel/helper-validator-identifier': 7.16.7 + '@babel/template': 7.16.7 + '@babel/traverse': 7.17.0 + '@babel/types': 7.17.0 transitivePeerDependencies: - supports-color dev: true /@babel/helper-simple-access/7.16.7: - resolution: - { - integrity: sha512-ZIzHVyoeLMvXMN/vok/a4LWRy8G2v205mNP0XOuf9XRLyX5/u9CnVulUtDgUTama3lT+bf/UqucuZjqiGuTS1g==, - } - engines: { node: ">=6.9.0" } + resolution: {integrity: sha512-ZIzHVyoeLMvXMN/vok/a4LWRy8G2v205mNP0XOuf9XRLyX5/u9CnVulUtDgUTama3lT+bf/UqucuZjqiGuTS1g==} + engines: {node: '>=6.9.0'} dependencies: - "@babel/types": 7.17.0 + '@babel/types': 7.17.0 dev: true /@babel/helper-split-export-declaration/7.16.7: - resolution: - { - integrity: sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==, - } - engines: { node: ">=6.9.0" } + resolution: {integrity: sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==} + engines: {node: '>=6.9.0'} dependencies: - "@babel/types": 7.17.0 + '@babel/types': 7.17.0 dev: true /@babel/helper-validator-identifier/7.16.7: - resolution: - { - integrity: sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==, - } - engines: { node: ">=6.9.0" } + resolution: {integrity: sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==} + engines: {node: '>=6.9.0'} dev: true /@babel/helper-validator-option/7.16.7: - resolution: - { - integrity: sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==, - } - engines: { node: ">=6.9.0" } + resolution: {integrity: sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==} + engines: {node: '>=6.9.0'} dev: true /@babel/helpers/7.17.0: - resolution: - { - integrity: sha512-Xe/9NFxjPwELUvW2dsukcMZIp6XwPSbI4ojFBJuX5ramHuVE22SVcZIwqzdWo5uCgeTXW8qV97lMvSOjq+1+nQ==, - } - engines: { node: ">=6.9.0" } + resolution: {integrity: sha512-Xe/9NFxjPwELUvW2dsukcMZIp6XwPSbI4ojFBJuX5ramHuVE22SVcZIwqzdWo5uCgeTXW8qV97lMvSOjq+1+nQ==} + engines: {node: '>=6.9.0'} dependencies: - "@babel/template": 7.16.7 - "@babel/traverse": 7.17.0 - "@babel/types": 7.17.0 + '@babel/template': 7.16.7 + '@babel/traverse': 7.17.0 + '@babel/types': 7.17.0 transitivePeerDependencies: - supports-color dev: true /@babel/highlight/7.16.10: - resolution: - { - integrity: sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==, - } - engines: { node: ">=6.9.0" } + resolution: {integrity: sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==} + engines: {node: '>=6.9.0'} dependencies: - "@babel/helper-validator-identifier": 7.16.7 + '@babel/helper-validator-identifier': 7.16.7 chalk: 2.4.2 js-tokens: 4.0.0 dev: true /@babel/parser/7.17.0: - resolution: - { - integrity: sha512-VKXSCQx5D8S04ej+Dqsr1CzYvvWgf20jIw2D+YhQCrIlr2UZGaDds23Y0xg75/skOxpLCRpUZvk/1EAVkGoDOw==, - } - engines: { node: ">=6.0.0" } + resolution: {integrity: sha512-VKXSCQx5D8S04ej+Dqsr1CzYvvWgf20jIw2D+YhQCrIlr2UZGaDds23Y0xg75/skOxpLCRpUZvk/1EAVkGoDOw==} + engines: {node: '>=6.0.0'} hasBin: true dev: true /@babel/template/7.16.7: - resolution: - { - integrity: sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==, - } - engines: { node: ">=6.9.0" } + resolution: {integrity: sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==} + engines: {node: '>=6.9.0'} dependencies: - "@babel/code-frame": 7.16.7 - "@babel/parser": 7.17.0 - "@babel/types": 7.17.0 + '@babel/code-frame': 7.16.7 + '@babel/parser': 7.17.0 + '@babel/types': 7.17.0 dev: true /@babel/traverse/7.17.0: - resolution: - { - integrity: sha512-fpFIXvqD6kC7c7PUNnZ0Z8cQXlarCLtCUpt2S1Dx7PjoRtCFffvOkHHSom+m5HIxMZn5bIBVb71lhabcmjEsqg==, - } - engines: { node: ">=6.9.0" } + resolution: {integrity: sha512-fpFIXvqD6kC7c7PUNnZ0Z8cQXlarCLtCUpt2S1Dx7PjoRtCFffvOkHHSom+m5HIxMZn5bIBVb71lhabcmjEsqg==} + engines: {node: '>=6.9.0'} dependencies: - "@babel/code-frame": 7.16.7 - "@babel/generator": 7.17.0 - "@babel/helper-environment-visitor": 7.16.7 - "@babel/helper-function-name": 7.16.7 - "@babel/helper-hoist-variables": 7.16.7 - "@babel/helper-split-export-declaration": 7.16.7 - "@babel/parser": 7.17.0 - "@babel/types": 7.17.0 + '@babel/code-frame': 7.16.7 + '@babel/generator': 7.17.0 + '@babel/helper-environment-visitor': 7.16.7 + '@babel/helper-function-name': 7.16.7 + '@babel/helper-hoist-variables': 7.16.7 + '@babel/helper-split-export-declaration': 7.16.7 + '@babel/parser': 7.17.0 + '@babel/types': 7.17.0 debug: 4.3.3 globals: 11.12.0 transitivePeerDependencies: @@ -1472,39 +1170,27 @@ packages: dev: true /@babel/types/7.17.0: - resolution: - { - integrity: sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==, - } - engines: { node: ">=6.9.0" } + resolution: {integrity: sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==} + engines: {node: '>=6.9.0'} dependencies: - "@babel/helper-validator-identifier": 7.16.7 + '@babel/helper-validator-identifier': 7.16.7 to-fast-properties: 2.0.0 dev: true /@cspotcode/source-map-consumer/0.8.0: - resolution: - { - integrity: sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==, - } - engines: { node: ">= 12" } + resolution: {integrity: sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==} + engines: {node: '>= 12'} dev: false /@cspotcode/source-map-support/0.7.0: - resolution: - { - integrity: sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==, - } - engines: { node: ">=12" } + resolution: {integrity: sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==} + engines: {node: '>=12'} dependencies: - "@cspotcode/source-map-consumer": 0.8.0 + '@cspotcode/source-map-consumer': 0.8.0 dev: false /@dabh/diagnostics/2.0.2: - resolution: - { - integrity: sha512-+A1YivoVDNNVCdfozHSR8v/jyuuLTMXwjWuxPFlFlUapXoGc+Gj9mDlTDDfrwl7rXCl2tNZ0kE8sIBO6YOn96Q==, - } + resolution: {integrity: sha512-+A1YivoVDNNVCdfozHSR8v/jyuuLTMXwjWuxPFlFlUapXoGc+Gj9mDlTDDfrwl7rXCl2tNZ0kE8sIBO6YOn96Q==} dependencies: colorspace: 1.1.4 enabled: 2.0.0 @@ -1512,11 +1198,8 @@ packages: dev: false /@eslint/eslintrc/1.0.5: - resolution: - { - integrity: sha512-BLxsnmK3KyPunz5wmCCpqy0YelEoxxGmH73Is+Z74oOTMtExcjkr3dDR6quwrjh1YspA8DH9gnX1o069KiS9AQ==, - } - engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + resolution: {integrity: sha512-BLxsnmK3KyPunz5wmCCpqy0YelEoxxGmH73Is+Z74oOTMtExcjkr3dDR6quwrjh1YspA8DH9gnX1o069KiS9AQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: ajv: 6.12.6 debug: 4.3.3 @@ -1532,12 +1215,9 @@ packages: dev: true /@httptoolkit/websocket-stream/6.0.0: - resolution: - { - integrity: sha512-EC8m9JbhpGX2okfvLakqrmy4Le0VyNKR7b3IdvFZR/BfFO4ruh/XceBvXhCFHkykchnFxuOSlRwFiqNSXlwcGA==, - } + resolution: {integrity: sha512-EC8m9JbhpGX2okfvLakqrmy4Le0VyNKR7b3IdvFZR/BfFO4ruh/XceBvXhCFHkykchnFxuOSlRwFiqNSXlwcGA==} dependencies: - "@types/ws": 8.2.2 + '@types/ws': 8.2.2 duplexify: 3.7.1 inherits: 2.0.4 isomorphic-ws: 4.0.1_ws@7.5.6 @@ -1551,13 +1231,10 @@ packages: dev: false /@humanwhocodes/config-array/0.9.3: - resolution: - { - integrity: sha512-3xSMlXHh03hCcCmFc0rbKp3Ivt2PFEJnQUJDDMTJQ2wkECZWdq4GePs2ctc5H8zV+cHPaq8k2vU8mrQjA6iHdQ==, - } - engines: { node: ">=10.10.0" } + resolution: {integrity: sha512-3xSMlXHh03hCcCmFc0rbKp3Ivt2PFEJnQUJDDMTJQ2wkECZWdq4GePs2ctc5H8zV+cHPaq8k2vU8mrQjA6iHdQ==} + engines: {node: '>=10.10.0'} dependencies: - "@humanwhocodes/object-schema": 1.2.1 + '@humanwhocodes/object-schema': 1.2.1 debug: 4.3.3 minimatch: 3.0.5 transitivePeerDependencies: @@ -1565,18 +1242,12 @@ packages: dev: true /@humanwhocodes/object-schema/1.2.1: - resolution: - { - integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==, - } + resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} dev: true /@istanbuljs/load-nyc-config/1.1.0: - resolution: - { - integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} + engines: {node: '>=8'} dependencies: camelcase: 5.3.1 find-up: 4.1.0 @@ -1586,455 +1257,305 @@ packages: dev: true /@istanbuljs/schema/0.1.3: - resolution: - { - integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} + engines: {node: '>=8'} dev: true /@jridgewell/resolve-uri/3.0.4: - resolution: - { - integrity: sha512-cz8HFjOFfUBtvN+NXYSFMHYRdxZMaEl0XypVrhzxBgadKIXhIkRd8aMeHhmF56Sl7SuS8OnUpQ73/k9LE4VnLg==, - } - engines: { node: ">=6.0.0" } + resolution: {integrity: sha512-cz8HFjOFfUBtvN+NXYSFMHYRdxZMaEl0XypVrhzxBgadKIXhIkRd8aMeHhmF56Sl7SuS8OnUpQ73/k9LE4VnLg==} + engines: {node: '>=6.0.0'} dev: true /@jridgewell/sourcemap-codec/1.4.10: - resolution: - { - integrity: sha512-Ht8wIW5v165atIX1p+JvKR5ONzUyF4Ac8DZIQ5kZs9zrb6M8SJNXpx1zn04rn65VjBMygRoMXcyYwNK0fT7bEg==, - } + resolution: {integrity: sha512-Ht8wIW5v165atIX1p+JvKR5ONzUyF4Ac8DZIQ5kZs9zrb6M8SJNXpx1zn04rn65VjBMygRoMXcyYwNK0fT7bEg==} dev: true /@jridgewell/trace-mapping/0.3.2: - resolution: - { - integrity: sha512-9KzzH4kMjA2XmBRHfqG2/Vtl7s92l6uNDd0wW7frDE+EUvQFGqNXhWp0UGJjSkt3v2AYjzOZn1QO9XaTNJIt1Q==, - } + resolution: {integrity: sha512-9KzzH4kMjA2XmBRHfqG2/Vtl7s92l6uNDd0wW7frDE+EUvQFGqNXhWp0UGJjSkt3v2AYjzOZn1QO9XaTNJIt1Q==} dependencies: - "@jridgewell/resolve-uri": 3.0.4 - "@jridgewell/sourcemap-codec": 1.4.10 + '@jridgewell/resolve-uri': 3.0.4 + '@jridgewell/sourcemap-codec': 1.4.10 dev: true /@nodelib/fs.scandir/2.1.5: - resolution: - { - integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==, - } - engines: { node: ">= 8" } + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} dependencies: - "@nodelib/fs.stat": 2.0.5 + '@nodelib/fs.stat': 2.0.5 run-parallel: 1.2.0 dev: true /@nodelib/fs.stat/2.0.5: - resolution: - { - integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==, - } - engines: { node: ">= 8" } + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} dev: true /@nodelib/fs.walk/1.2.8: - resolution: - { - integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==, - } - engines: { node: ">= 8" } + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} dependencies: - "@nodelib/fs.scandir": 2.1.5 + '@nodelib/fs.scandir': 2.1.5 fastq: 1.13.0 dev: true /@tsconfig/node10/1.0.8: - resolution: - { - integrity: sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==, - } + resolution: {integrity: sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==} dev: false /@tsconfig/node12/1.0.9: - resolution: - { - integrity: sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==, - } + resolution: {integrity: sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==} dev: false /@tsconfig/node14/1.0.1: - resolution: - { - integrity: sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==, - } + resolution: {integrity: sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==} dev: false /@tsconfig/node16/1.0.2: - resolution: - { - integrity: sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==, - } + resolution: {integrity: sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==} dev: false /@types/bcrypt/5.0.0: - resolution: - { - integrity: sha512-agtcFKaruL8TmcvqbndlqHPSJgsolhf/qPWchFlgnW1gECTN/nKbFcoFnvKAQRFfKbh+BO6A3SWdJu9t+xF3Lw==, - } + resolution: {integrity: sha512-agtcFKaruL8TmcvqbndlqHPSJgsolhf/qPWchFlgnW1gECTN/nKbFcoFnvKAQRFfKbh+BO6A3SWdJu9t+xF3Lw==} dependencies: - "@types/node": 17.0.15 + '@types/node': 17.0.15 dev: true /@types/bcryptjs/2.4.2: - resolution: - { - integrity: sha512-LiMQ6EOPob/4yUL66SZzu6Yh77cbzJFYll+ZfaPiPPFswtIlA/Fs1MzdKYA7JApHU49zQTbJGX3PDmCpIdDBRQ==, - } + resolution: {integrity: sha512-LiMQ6EOPob/4yUL66SZzu6Yh77cbzJFYll+ZfaPiPPFswtIlA/Fs1MzdKYA7JApHU49zQTbJGX3PDmCpIdDBRQ==} dev: true /@types/body-parser/1.19.2: - resolution: - { - integrity: sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==, - } + resolution: {integrity: sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==} dependencies: - "@types/connect": 3.4.35 - "@types/node": 17.0.15 + '@types/connect': 3.4.35 + '@types/node': 17.0.15 dev: true /@types/bson/1.0.11: - resolution: - { - integrity: sha512-j+UcCWI+FsbI5/FQP/Kj2CXyplWAz39ktHFkXk84h7dNblKRSoNJs95PZFRd96NQGqsPEPgeclqnznWZr14ZDA==, - } + resolution: {integrity: sha512-j+UcCWI+FsbI5/FQP/Kj2CXyplWAz39ktHFkXk84h7dNblKRSoNJs95PZFRd96NQGqsPEPgeclqnznWZr14ZDA==} dependencies: - "@types/node": 17.0.15 + '@types/node': 17.0.15 dev: false /@types/bson/4.2.0: - resolution: - { - integrity: sha512-ELCPqAdroMdcuxqwMgUpifQyRoTpyYCNr1V9xKyF40VsBobsj+BbWNRvwGchMgBPGqkw655ypkjj2MEF5ywVwg==, - } + resolution: {integrity: sha512-ELCPqAdroMdcuxqwMgUpifQyRoTpyYCNr1V9xKyF40VsBobsj+BbWNRvwGchMgBPGqkw655ypkjj2MEF5ywVwg==} deprecated: This is a stub types definition. bson provides its own type definitions, so you do not need this installed. dependencies: bson: 4.6.1 dev: false /@types/bunyan/1.8.8: - resolution: - { - integrity: sha512-Cblq+Yydg3u+sGiz2mjHjC5MPmdjY+No4qvHrF+BUhblsmSfMvsHLbOG62tPbonsqBj6sbWv1LHcsoe5Jw+/Ow==, - } + resolution: {integrity: sha512-Cblq+Yydg3u+sGiz2mjHjC5MPmdjY+No4qvHrF+BUhblsmSfMvsHLbOG62tPbonsqBj6sbWv1LHcsoe5Jw+/Ow==} dependencies: - "@types/node": 17.0.15 + '@types/node': 17.0.15 dev: true /@types/connect-redis/0.0.18: - resolution: - { - integrity: sha512-iGygGbXgPIr94DEAuoluWhzre3c2/ew5NPlbW9IWvwCTXMM1YCmc7M9wpXMkYqt6kB9aO1sjZnmDzyugUu+2vQ==, - } + resolution: {integrity: sha512-iGygGbXgPIr94DEAuoluWhzre3c2/ew5NPlbW9IWvwCTXMM1YCmc7M9wpXMkYqt6kB9aO1sjZnmDzyugUu+2vQ==} dependencies: - "@types/express": 4.17.13 - "@types/express-session": 1.17.4 - "@types/ioredis": 4.28.8 - "@types/redis": 2.8.32 + '@types/express': 4.17.13 + '@types/express-session': 1.17.4 + '@types/ioredis': 4.28.8 + '@types/redis': 2.8.32 dev: true /@types/connect/3.4.35: - resolution: - { - integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==, - } + resolution: {integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==} dependencies: - "@types/node": 17.0.15 + '@types/node': 17.0.15 dev: true /@types/cookiejar/2.1.2: - resolution: - { - integrity: sha512-t73xJJrvdTjXrn4jLS9VSGRbz0nUY3cl2DMGDU48lKl+HR9dbbjW2A9r3g40VA++mQpy6uuHg33gy7du2BKpog==, - } + resolution: {integrity: sha512-t73xJJrvdTjXrn4jLS9VSGRbz0nUY3cl2DMGDU48lKl+HR9dbbjW2A9r3g40VA++mQpy6uuHg33gy7du2BKpog==} dev: true /@types/express-rate-limit/3.3.5: - resolution: - { - integrity: sha512-v6M28DcbdATYvzTN5lFJhN2EP6BgY8MbTskcKJ/m5bmGuzFKHfbEVXF3po8U8ivOyyeT5o6HV0janPlFBBN5yg==, - } + resolution: {integrity: sha512-v6M28DcbdATYvzTN5lFJhN2EP6BgY8MbTskcKJ/m5bmGuzFKHfbEVXF3po8U8ivOyyeT5o6HV0janPlFBBN5yg==} dependencies: - "@types/express": 4.17.13 + '@types/express': 4.17.13 dev: true /@types/express-serve-static-core/4.17.28: - resolution: - { - integrity: sha512-P1BJAEAW3E2DJUlkgq4tOL3RyMunoWXqbSCygWo5ZIWTjUgN1YnaXWW4VWl/oc8vs/XoYibEGBKP0uZyF4AHig==, - } + resolution: {integrity: sha512-P1BJAEAW3E2DJUlkgq4tOL3RyMunoWXqbSCygWo5ZIWTjUgN1YnaXWW4VWl/oc8vs/XoYibEGBKP0uZyF4AHig==} dependencies: - "@types/node": 17.0.15 - "@types/qs": 6.9.7 - "@types/range-parser": 1.2.4 + '@types/node': 17.0.15 + '@types/qs': 6.9.7 + '@types/range-parser': 1.2.4 dev: true /@types/express-session/1.17.4: - resolution: - { - integrity: sha512-7cNlSI8+oOBUHTfPXMwDxF/Lchx5aJ3ho7+p9jJZYVg9dVDJFh3qdMXmJtRsysnvS+C6x46k9DRYmrmCkE+MVg==, - } + resolution: {integrity: sha512-7cNlSI8+oOBUHTfPXMwDxF/Lchx5aJ3ho7+p9jJZYVg9dVDJFh3qdMXmJtRsysnvS+C6x46k9DRYmrmCkE+MVg==} dependencies: - "@types/express": 4.17.13 + '@types/express': 4.17.13 dev: true /@types/express/4.17.13: - resolution: - { - integrity: sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==, - } + resolution: {integrity: sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==} dependencies: - "@types/body-parser": 1.19.2 - "@types/express-serve-static-core": 4.17.28 - "@types/qs": 6.9.7 - "@types/serve-static": 1.13.10 + '@types/body-parser': 1.19.2 + '@types/express-serve-static-core': 4.17.28 + '@types/qs': 6.9.7 + '@types/serve-static': 1.13.10 dev: true /@types/glob/7.2.0: - resolution: - { - integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==, - } + resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==} dependencies: - "@types/minimatch": 3.0.5 - "@types/node": 17.0.15 + '@types/minimatch': 3.0.5 + '@types/node': 17.0.15 dev: true /@types/ioredis/4.28.8: - resolution: - { - integrity: sha512-mULOyO2smtvkE1zmzRRA4P0+1UjEqusi014kXOL1q3CY0RgqkR5/wKvv+vAJbPw2Q66wPyylKeevUy+m/FaRMg==, - } + resolution: {integrity: sha512-mULOyO2smtvkE1zmzRRA4P0+1UjEqusi014kXOL1q3CY0RgqkR5/wKvv+vAJbPw2Q66wPyylKeevUy+m/FaRMg==} dependencies: - "@types/node": 17.0.15 + '@types/node': 17.0.15 dev: true /@types/json-schema/7.0.9: - resolution: - { - integrity: sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==, - } + resolution: {integrity: sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==} dev: true /@types/json5/0.0.29: - resolution: { integrity: sha1-7ihweulOEdK4J7y+UnC86n8+ce4= } + resolution: {integrity: sha1-7ihweulOEdK4J7y+UnC86n8+ce4=} /@types/mime/1.3.2: - resolution: - { - integrity: sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==, - } + resolution: {integrity: sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==} dev: true /@types/minimatch/3.0.5: - resolution: - { - integrity: sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==, - } + resolution: {integrity: sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==} dev: true /@types/mkdirp/1.0.2: - resolution: - { - integrity: sha512-o0K1tSO0Dx5X6xlU5F1D6625FawhC3dU3iqr25lluNv/+/QIVH8RLNEiVokgIZo+mz+87w/3Mkg/VvQS+J51fQ==, - } + resolution: {integrity: sha512-o0K1tSO0Dx5X6xlU5F1D6625FawhC3dU3iqr25lluNv/+/QIVH8RLNEiVokgIZo+mz+87w/3Mkg/VvQS+J51fQ==} dependencies: - "@types/node": 17.0.15 + '@types/node': 17.0.15 dev: true /@types/mongodb/3.6.18: - resolution: - { - integrity: sha512-JSVFt9p0rTfZ4EgzXmVHUB3ue00xe3CRbQho8nXfImzEDDM4O7I3po1bwbWl/EIbLENxUreZxqLOc8lvcnLVPA==, - } + resolution: {integrity: sha512-JSVFt9p0rTfZ4EgzXmVHUB3ue00xe3CRbQho8nXfImzEDDM4O7I3po1bwbWl/EIbLENxUreZxqLOc8lvcnLVPA==} dependencies: - "@types/bson": 4.2.0 - "@types/node": 17.0.15 + '@types/bson': 4.2.0 + '@types/node': 17.0.15 dev: false /@types/mongodb/3.6.20: - resolution: - { - integrity: sha512-WcdpPJCakFzcWWD9juKoZbRtQxKIMYF/JIAM4JrNHrMcnJL6/a2NWjXxW7fo9hxboxxkg+icff8d7+WIEvKgYQ==, - } + resolution: {integrity: sha512-WcdpPJCakFzcWWD9juKoZbRtQxKIMYF/JIAM4JrNHrMcnJL6/a2NWjXxW7fo9hxboxxkg+icff8d7+WIEvKgYQ==} dependencies: - "@types/bson": 4.2.0 - "@types/node": 17.0.15 + '@types/bson': 4.2.0 + '@types/node': 17.0.15 dev: false /@types/multer/1.4.7: - resolution: - { - integrity: sha512-/SNsDidUFCvqqcWDwxv2feww/yqhNeTRL5CVoL3jU4Goc4kKEL10T7Eye65ZqPNi4HRx8sAEX59pV1aEH7drNA==, - } + resolution: {integrity: sha512-/SNsDidUFCvqqcWDwxv2feww/yqhNeTRL5CVoL3jU4Goc4kKEL10T7Eye65ZqPNi4HRx8sAEX59pV1aEH7drNA==} dependencies: - "@types/express": 4.17.13 + '@types/express': 4.17.13 dev: true /@types/node-fetch/2.5.12: - resolution: - { - integrity: sha512-MKgC4dlq4kKNa/mYrwpKfzQMB5X3ee5U6fSprkKpToBqBmX4nFZL9cW5jl6sWn+xpRJ7ypWh2yyqqr8UUCstSw==, - } + resolution: {integrity: sha512-MKgC4dlq4kKNa/mYrwpKfzQMB5X3ee5U6fSprkKpToBqBmX4nFZL9cW5jl6sWn+xpRJ7ypWh2yyqqr8UUCstSw==} dependencies: - "@types/node": 17.0.15 + '@types/node': 17.0.15 form-data: 3.0.1 dev: true /@types/node/16.11.7: - resolution: - { - integrity: sha512-QB5D2sqfSjCmTuWcBWyJ+/44bcjO7VbjSbOE0ucoVbAsSNQc4Lt6QkgkVXkTDwkL4z/beecZNDvVX15D4P8Jbw==, - } + resolution: {integrity: sha512-QB5D2sqfSjCmTuWcBWyJ+/44bcjO7VbjSbOE0ucoVbAsSNQc4Lt6QkgkVXkTDwkL4z/beecZNDvVX15D4P8Jbw==} dev: true /@types/node/17.0.15: - resolution: - { - integrity: sha512-zWt4SDDv1S9WRBNxLFxFRHxdD9tvH8f5/kg5/IaLFdnSNXsDY4eL3Q3XXN+VxUnWIhyVFDwcsmAprvwXoM/ClA==, - } + resolution: {integrity: sha512-zWt4SDDv1S9WRBNxLFxFRHxdD9tvH8f5/kg5/IaLFdnSNXsDY4eL3Q3XXN+VxUnWIhyVFDwcsmAprvwXoM/ClA==} /@types/nodemailer/6.4.4: - resolution: - { - integrity: sha512-Ksw4t7iliXeYGvIQcSIgWQ5BLuC/mljIEbjf615svhZL10PE9t+ei8O9gDaD3FPCasUJn9KTLwz2JFJyiiyuqw==, - } + resolution: {integrity: sha512-Ksw4t7iliXeYGvIQcSIgWQ5BLuC/mljIEbjf615svhZL10PE9t+ei8O9gDaD3FPCasUJn9KTLwz2JFJyiiyuqw==} dependencies: - "@types/node": 17.0.15 + '@types/node': 17.0.15 dev: true /@types/qs/6.9.7: - resolution: - { - integrity: sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==, - } + resolution: {integrity: sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==} dev: true /@types/range-parser/1.2.4: - resolution: - { - integrity: sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==, - } + resolution: {integrity: sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==} dev: true /@types/rate-limit-redis/1.7.4: - resolution: - { - integrity: sha512-TDsh9CJHHhx+sGHlqF6MI+PgEv91o/wQ67e2IR02ebYPzWQ8Rk0pcVY+WBy2pjxa+cAkPwjcOFDd21VToKaOzQ==, - } + resolution: {integrity: sha512-TDsh9CJHHhx+sGHlqF6MI+PgEv91o/wQ67e2IR02ebYPzWQ8Rk0pcVY+WBy2pjxa+cAkPwjcOFDd21VToKaOzQ==} dependencies: - "@types/express-rate-limit": 3.3.5 - "@types/ioredis": 4.28.8 - "@types/redis": 2.8.32 + '@types/express-rate-limit': 3.3.5 + '@types/ioredis': 4.28.8 + '@types/redis': 2.8.32 dev: true /@types/redis/2.8.32: - resolution: - { - integrity: sha512-7jkMKxcGq9p242exlbsVzuJb57KqHRhNl4dHoQu2Y5v9bCAbtIXXH0R3HleSQW4CTOqpHIYUW3t6tpUj4BVQ+w==, - } + resolution: {integrity: sha512-7jkMKxcGq9p242exlbsVzuJb57KqHRhNl4dHoQu2Y5v9bCAbtIXXH0R3HleSQW4CTOqpHIYUW3t6tpUj4BVQ+w==} dependencies: - "@types/node": 17.0.15 + '@types/node': 17.0.15 dev: true /@types/rimraf/3.0.2: - resolution: - { - integrity: sha512-F3OznnSLAUxFrCEu/L5PY8+ny8DtcFRjx7fZZ9bycvXRi3KPTRS9HOitGZwvPg0juRhXFWIeKX58cnX5YqLohQ==, - } + resolution: {integrity: sha512-F3OznnSLAUxFrCEu/L5PY8+ny8DtcFRjx7fZZ9bycvXRi3KPTRS9HOitGZwvPg0juRhXFWIeKX58cnX5YqLohQ==} dependencies: - "@types/glob": 7.2.0 - "@types/node": 17.0.15 + '@types/glob': 7.2.0 + '@types/node': 17.0.15 dev: true /@types/safe-json-stringify/1.1.2: - resolution: - { - integrity: sha512-Hj/LZMBXFH3Qj9sNmNu6syBpkZqBaM00HgP7naf9CnZhB3kdxmrenWUD2cY6vvTWjuBM3NPOLknPKDUOQuTWXA==, - } + resolution: {integrity: sha512-Hj/LZMBXFH3Qj9sNmNu6syBpkZqBaM00HgP7naf9CnZhB3kdxmrenWUD2cY6vvTWjuBM3NPOLknPKDUOQuTWXA==} dev: true /@types/serve-static/1.13.10: - resolution: - { - integrity: sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==, - } + resolution: {integrity: sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==} dependencies: - "@types/mime": 1.3.2 - "@types/node": 17.0.15 + '@types/mime': 1.3.2 + '@types/node': 17.0.15 dev: true /@types/superagent/4.1.15: - resolution: - { - integrity: sha512-mu/N4uvfDN2zVQQ5AYJI/g4qxn2bHB6521t1UuH09ShNWjebTqN0ZFuYK9uYjcgmI0dTQEs+Owi1EO6U0OkOZQ==, - } + resolution: {integrity: sha512-mu/N4uvfDN2zVQQ5AYJI/g4qxn2bHB6521t1UuH09ShNWjebTqN0ZFuYK9uYjcgmI0dTQEs+Owi1EO6U0OkOZQ==} dependencies: - "@types/cookiejar": 2.1.2 - "@types/node": 17.0.15 + '@types/cookiejar': 2.1.2 + '@types/node': 17.0.15 dev: true /@types/supertest/2.0.11: - resolution: - { - integrity: sha512-uci4Esokrw9qGb9bvhhSVEjd6rkny/dk5PK/Qz4yxKiyppEI+dOPlNrZBahE3i+PoKFYyDxChVXZ/ysS/nrm1Q==, - } + resolution: {integrity: sha512-uci4Esokrw9qGb9bvhhSVEjd6rkny/dk5PK/Qz4yxKiyppEI+dOPlNrZBahE3i+PoKFYyDxChVXZ/ysS/nrm1Q==} dependencies: - "@types/superagent": 4.1.15 + '@types/superagent': 4.1.15 dev: true /@types/tap/15.0.5: - resolution: - { - integrity: sha512-MaP+EgZNFAGRvVjHWv8ldrLvYBn4PnmAlzY7IL3/RPAPkOXdggTSTgLFONbnTpdQTe8+ixYGAySKAm9ESlZm1A==, - } + resolution: {integrity: sha512-MaP+EgZNFAGRvVjHWv8ldrLvYBn4PnmAlzY7IL3/RPAPkOXdggTSTgLFONbnTpdQTe8+ixYGAySKAm9ESlZm1A==} dependencies: - "@types/node": 17.0.15 + '@types/node': 17.0.15 dev: true /@types/uuid/8.3.4: - resolution: - { - integrity: sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==, - } + resolution: {integrity: sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==} dev: true /@types/ws/8.2.2: - resolution: - { - integrity: sha512-NOn5eIcgWLOo6qW8AcuLZ7G8PycXu0xTxxkS6Q18VWFxgPUSOwV0pBj2a/4viNZVu25i7RIB7GttdkAIUUXOOg==, - } + resolution: {integrity: sha512-NOn5eIcgWLOo6qW8AcuLZ7G8PycXu0xTxxkS6Q18VWFxgPUSOwV0pBj2a/4viNZVu25i7RIB7GttdkAIUUXOOg==} dependencies: - "@types/node": 17.0.15 + '@types/node': 17.0.15 dev: false /@typescript-eslint/eslint-plugin/5.10.2_2595c2126aec4d4b6e944b931dabb4c2: - resolution: - { - integrity: sha512-4W/9lLuE+v27O/oe7hXJKjNtBLnZE8tQAFpapdxwSVHqtmIoPB1gph3+ahNwVuNL37BX7YQHyGF9Xv6XCnIX2Q==, - } - engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + resolution: {integrity: sha512-4W/9lLuE+v27O/oe7hXJKjNtBLnZE8tQAFpapdxwSVHqtmIoPB1gph3+ahNwVuNL37BX7YQHyGF9Xv6XCnIX2Q==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: - "@typescript-eslint/parser": ^5.0.0 + '@typescript-eslint/parser': ^5.0.0 eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - typescript: "*" + typescript: '*' peerDependenciesMeta: typescript: optional: true dependencies: - "@typescript-eslint/parser": 5.10.2_eslint@8.8.0+typescript@4.5.5 - "@typescript-eslint/scope-manager": 5.10.2 - "@typescript-eslint/type-utils": 5.10.2_eslint@8.8.0+typescript@4.5.5 - "@typescript-eslint/utils": 5.10.2_eslint@8.8.0+typescript@4.5.5 + '@typescript-eslint/parser': 5.10.2_eslint@8.8.0+typescript@4.5.5 + '@typescript-eslint/scope-manager': 5.10.2 + '@typescript-eslint/type-utils': 5.10.2_eslint@8.8.0+typescript@4.5.5 + '@typescript-eslint/utils': 5.10.2_eslint@8.8.0+typescript@4.5.5 debug: 4.3.3 eslint: 8.8.0 functional-red-black-tree: 1.0.1 @@ -2048,21 +1569,18 @@ packages: dev: true /@typescript-eslint/parser/5.10.2_eslint@8.8.0+typescript@4.5.5: - resolution: - { - integrity: sha512-JaNYGkaQVhP6HNF+lkdOr2cAs2wdSZBoalE22uYWq8IEv/OVH0RksSGydk+sW8cLoSeYmC+OHvRyv2i4AQ7Czg==, - } - engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + resolution: {integrity: sha512-JaNYGkaQVhP6HNF+lkdOr2cAs2wdSZBoalE22uYWq8IEv/OVH0RksSGydk+sW8cLoSeYmC+OHvRyv2i4AQ7Czg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - typescript: "*" + typescript: '*' peerDependenciesMeta: typescript: optional: true dependencies: - "@typescript-eslint/scope-manager": 5.10.2 - "@typescript-eslint/types": 5.10.2 - "@typescript-eslint/typescript-estree": 5.10.2_typescript@4.5.5 + '@typescript-eslint/scope-manager': 5.10.2 + '@typescript-eslint/types': 5.10.2 + '@typescript-eslint/typescript-estree': 5.10.2_typescript@4.5.5 debug: 4.3.3 eslint: 8.8.0 typescript: 4.5.5 @@ -2071,30 +1589,24 @@ packages: dev: true /@typescript-eslint/scope-manager/5.10.2: - resolution: - { - integrity: sha512-39Tm6f4RoZoVUWBYr3ekS75TYgpr5Y+X0xLZxXqcZNDWZdJdYbKd3q2IR4V9y5NxxiPu/jxJ8XP7EgHiEQtFnw==, - } - engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + resolution: {integrity: sha512-39Tm6f4RoZoVUWBYr3ekS75TYgpr5Y+X0xLZxXqcZNDWZdJdYbKd3q2IR4V9y5NxxiPu/jxJ8XP7EgHiEQtFnw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: - "@typescript-eslint/types": 5.10.2 - "@typescript-eslint/visitor-keys": 5.10.2 + '@typescript-eslint/types': 5.10.2 + '@typescript-eslint/visitor-keys': 5.10.2 dev: true /@typescript-eslint/type-utils/5.10.2_eslint@8.8.0+typescript@4.5.5: - resolution: - { - integrity: sha512-uRKSvw/Ccs5FYEoXW04Z5VfzF2iiZcx8Fu7DGIB7RHozuP0VbKNzP1KfZkHBTM75pCpsWxIthEH1B33dmGBKHw==, - } - engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + resolution: {integrity: sha512-uRKSvw/Ccs5FYEoXW04Z5VfzF2iiZcx8Fu7DGIB7RHozuP0VbKNzP1KfZkHBTM75pCpsWxIthEH1B33dmGBKHw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: - eslint: "*" - typescript: "*" + eslint: '*' + typescript: '*' peerDependenciesMeta: typescript: optional: true dependencies: - "@typescript-eslint/utils": 5.10.2_eslint@8.8.0+typescript@4.5.5 + '@typescript-eslint/utils': 5.10.2_eslint@8.8.0+typescript@4.5.5 debug: 4.3.3 eslint: 8.8.0 tsutils: 3.21.0_typescript@4.5.5 @@ -2104,27 +1616,21 @@ packages: dev: true /@typescript-eslint/types/5.10.2: - resolution: - { - integrity: sha512-Qfp0qk/5j2Rz3p3/WhWgu4S1JtMcPgFLnmAKAW061uXxKSa7VWKZsDXVaMXh2N60CX9h6YLaBoy9PJAfCOjk3w==, - } - engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + resolution: {integrity: sha512-Qfp0qk/5j2Rz3p3/WhWgu4S1JtMcPgFLnmAKAW061uXxKSa7VWKZsDXVaMXh2N60CX9h6YLaBoy9PJAfCOjk3w==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true /@typescript-eslint/typescript-estree/5.10.2_typescript@4.5.5: - resolution: - { - integrity: sha512-WHHw6a9vvZls6JkTgGljwCsMkv8wu8XU8WaYKeYhxhWXH/atZeiMW6uDFPLZOvzNOGmuSMvHtZKd6AuC8PrwKQ==, - } - engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + resolution: {integrity: sha512-WHHw6a9vvZls6JkTgGljwCsMkv8wu8XU8WaYKeYhxhWXH/atZeiMW6uDFPLZOvzNOGmuSMvHtZKd6AuC8PrwKQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: - typescript: "*" + typescript: '*' peerDependenciesMeta: typescript: optional: true dependencies: - "@typescript-eslint/types": 5.10.2 - "@typescript-eslint/visitor-keys": 5.10.2 + '@typescript-eslint/types': 5.10.2 + '@typescript-eslint/visitor-keys': 5.10.2 debug: 4.3.3 globby: 11.1.0 is-glob: 4.0.3 @@ -2136,18 +1642,15 @@ packages: dev: true /@typescript-eslint/utils/5.10.2_eslint@8.8.0+typescript@4.5.5: - resolution: - { - integrity: sha512-vuJaBeig1NnBRkf7q9tgMLREiYD7zsMrsN1DA3wcoMDvr3BTFiIpKjGiYZoKPllfEwN7spUjv7ZqD+JhbVjEPg==, - } - engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + resolution: {integrity: sha512-vuJaBeig1NnBRkf7q9tgMLREiYD7zsMrsN1DA3wcoMDvr3BTFiIpKjGiYZoKPllfEwN7spUjv7ZqD+JhbVjEPg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: - "@types/json-schema": 7.0.9 - "@typescript-eslint/scope-manager": 5.10.2 - "@typescript-eslint/types": 5.10.2 - "@typescript-eslint/typescript-estree": 5.10.2_typescript@4.5.5 + '@types/json-schema': 7.0.9 + '@typescript-eslint/scope-manager': 5.10.2 + '@typescript-eslint/types': 5.10.2 + '@typescript-eslint/typescript-estree': 5.10.2_typescript@4.5.5 eslint: 8.8.0 eslint-scope: 5.1.1 eslint-utils: 3.0.0_eslint@8.8.0 @@ -2157,41 +1660,29 @@ packages: dev: true /@typescript-eslint/visitor-keys/5.10.2: - resolution: - { - integrity: sha512-zHIhYGGGrFJvvyfwHk5M08C5B5K4bewkm+rrvNTKk1/S15YHR+SA/QUF8ZWscXSfEaB8Nn2puZj+iHcoxVOD/Q==, - } - engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + resolution: {integrity: sha512-zHIhYGGGrFJvvyfwHk5M08C5B5K4bewkm+rrvNTKk1/S15YHR+SA/QUF8ZWscXSfEaB8Nn2puZj+iHcoxVOD/Q==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: - "@typescript-eslint/types": 5.10.2 + '@typescript-eslint/types': 5.10.2 eslint-visitor-keys: 3.2.0 dev: true /@valuabletouch/winston-seq/1.3.0: - resolution: - { - integrity: sha512-j6oHAXTTdhwUap0VJGHIwiKhbw9IffOWYMrMI1Y2uXStNCNSnwIHYHHfjDw46hrynbVN838DRR+voGtYEvzF1w==, - } + resolution: {integrity: sha512-j6oHAXTTdhwUap0VJGHIwiKhbw9IffOWYMrMI1Y2uXStNCNSnwIHYHHfjDw46hrynbVN838DRR+voGtYEvzF1w==} dependencies: seq-logging: 1.1.1 winston-transport: 4.5.0 dev: false /accepts/1.3.8: - resolution: - { - integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==, - } - engines: { node: ">= 0.6" } + resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} + engines: {node: '>= 0.6'} dependencies: mime-types: 2.1.34 negotiator: 0.6.3 /acorn-jsx/5.3.2_acorn@8.7.0: - resolution: - { - integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==, - } + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: @@ -2199,37 +1690,25 @@ packages: dev: true /acorn-walk/8.2.0: - resolution: - { - integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==, - } - engines: { node: ">=0.4.0" } + resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==} + engines: {node: '>=0.4.0'} dev: false /acorn/8.7.0: - resolution: - { - integrity: sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==, - } - engines: { node: ">=0.4.0" } + resolution: {integrity: sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==} + engines: {node: '>=0.4.0'} hasBin: true /aggregate-error/3.1.0: - resolution: - { - integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} + engines: {node: '>=8'} dependencies: clean-stack: 2.2.0 indent-string: 4.0.0 dev: true /ajv/6.12.6: - resolution: - { - integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==, - } + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} dependencies: fast-deep-equal: 3.1.3 fast-json-stable-stringify: 2.1.0 @@ -2238,110 +1717,83 @@ packages: dev: true /ansi-regex/2.1.1: - resolution: { integrity: sha1-w7M6te42DYbg5ijwRorn7yfWVN8= } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha1-w7M6te42DYbg5ijwRorn7yfWVN8=} + engines: {node: '>=0.10.0'} /ansi-regex/5.0.1: - resolution: - { - integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} dev: true /ansi-styles/3.2.1: - resolution: - { - integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==, - } - engines: { node: ">=4" } + resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} + engines: {node: '>=4'} dependencies: color-convert: 1.9.3 dev: true /ansi-styles/4.3.0: - resolution: - { - integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} dependencies: color-convert: 2.0.1 dev: true /ansi/0.3.1: - resolution: { integrity: sha1-DELU+xcWDVqa8eSEus4cZpIsGyE= } + resolution: {integrity: sha1-DELU+xcWDVqa8eSEus4cZpIsGyE=} dev: false /anymatch/3.1.2: - resolution: - { - integrity: sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==, - } - engines: { node: ">= 8" } + resolution: {integrity: sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==} + engines: {node: '>= 8'} dependencies: normalize-path: 3.0.0 picomatch: 2.3.1 dev: true /append-field/1.0.0: - resolution: { integrity: sha1-HjRA6RXwsSA9I3SOeO3XubW0PlY= } + resolution: {integrity: sha1-HjRA6RXwsSA9I3SOeO3XubW0PlY=} dev: false /append-transform/2.0.0: - resolution: - { - integrity: sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==} + engines: {node: '>=8'} dependencies: default-require-extensions: 3.0.0 dev: true /archy/1.0.0: - resolution: { integrity: sha1-+cjBN1fMHde8N5rHeyxipcKGjEA= } + resolution: {integrity: sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=} dev: true /are-we-there-yet/1.0.6: - resolution: { integrity: sha1-otKMkxAqpsyWJFomy5VN4G7FPww= } + resolution: {integrity: sha1-otKMkxAqpsyWJFomy5VN4G7FPww=} dependencies: delegates: 1.0.0 readable-stream: 2.3.7 dev: false /arg/4.1.3: - resolution: - { - integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==, - } + resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} dev: false /argparse/1.0.10: - resolution: - { - integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==, - } + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} dependencies: sprintf-js: 1.0.3 dev: true /argparse/2.0.1: - resolution: - { - integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==, - } + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} dev: true /array-flatten/1.1.1: - resolution: { integrity: sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= } + resolution: {integrity: sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=} dev: false /array-includes/3.1.4: - resolution: - { - integrity: sha512-ZTNSQkmWumEbiHO2GF4GmWxYVTiQyJy2XOTa15sdQSrvKn7l+180egQMqlrMOUMCyLMD7pmyQe4mMDUT6Behrw==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-ZTNSQkmWumEbiHO2GF4GmWxYVTiQyJy2XOTa15sdQSrvKn7l+180egQMqlrMOUMCyLMD7pmyQe4mMDUT6Behrw==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.2 define-properties: 1.1.3 @@ -2351,19 +1803,13 @@ packages: dev: true /array-union/2.1.0: - resolution: - { - integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} dev: true /array.prototype.flat/1.2.5: - resolution: - { - integrity: sha512-KaYU+S+ndVqyUnignHftkwc58o3uVU1jzczILJ1tN2YaIZpFIKBiP/x/j97E5MVPsaCloPbqWLB/8qCTVvT2qg==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-KaYU+S+ndVqyUnignHftkwc58o3uVU1jzczILJ1tN2YaIZpFIKBiP/x/j97E5MVPsaCloPbqWLB/8qCTVvT2qg==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.2 define-properties: 1.1.3 @@ -2371,50 +1817,38 @@ packages: dev: true /asap/2.0.6: - resolution: { integrity: sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= } + resolution: {integrity: sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=} dev: true /asn1/0.2.6: - resolution: - { - integrity: sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==, - } + resolution: {integrity: sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==} dependencies: safer-buffer: 2.1.2 dev: true /assert-plus/1.0.0: - resolution: { integrity: sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= } - engines: { node: ">=0.8" } + resolution: {integrity: sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=} + engines: {node: '>=0.8'} dev: true /async-hook-domain/2.0.4: - resolution: - { - integrity: sha512-14LjCmlK1PK8eDtTezR6WX8TMaYNIzBIsd2D1sGoGjgx0BuNMMoSdk7i/drlbtamy0AWv9yv2tkB+ASdmeqFIw==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-14LjCmlK1PK8eDtTezR6WX8TMaYNIzBIsd2D1sGoGjgx0BuNMMoSdk7i/drlbtamy0AWv9yv2tkB+ASdmeqFIw==} + engines: {node: '>=10'} dev: true /async/3.2.3: - resolution: - { - integrity: sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==, - } + resolution: {integrity: sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==} dev: false /asynckit/0.4.0: - resolution: { integrity: sha1-x57Zf380y48robyXkLzDZkdLS3k= } + resolution: {integrity: sha1-x57Zf380y48robyXkLzDZkdLS3k=} dev: true /aws-crt/1.10.6: - resolution: - { - integrity: sha512-LCOFwFdUk3NJNUkm0YuiqU2jPtnC5OZYeLqIZyqVzDoSpK2wL7QAaA553v4YPA5xs3QU7jCmaDl6y3LjJTm4+w==, - } + resolution: {integrity: sha512-LCOFwFdUk3NJNUkm0YuiqU2jPtnC5OZYeLqIZyqVzDoSpK2wL7QAaA553v4YPA5xs3QU7jCmaDl6y3LjJTm4+w==} requiresBuild: true dependencies: - "@httptoolkit/websocket-stream": 6.0.0 + '@httptoolkit/websocket-stream': 6.0.0 axios: 0.24.0 cmake-js: 6.3.0 crypto-js: 4.1.1 @@ -2430,21 +1864,15 @@ packages: dev: false /aws-sign2/0.7.0: - resolution: { integrity: sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= } + resolution: {integrity: sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=} dev: true /aws4/1.11.0: - resolution: - { - integrity: sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==, - } + resolution: {integrity: sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==} dev: true /axios/0.21.4_debug@4.3.3: - resolution: - { - integrity: sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==, - } + resolution: {integrity: sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==} dependencies: follow-redirects: 1.14.7_debug@4.3.3 transitivePeerDependencies: @@ -2452,10 +1880,7 @@ packages: dev: false /axios/0.24.0: - resolution: - { - integrity: sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==, - } + resolution: {integrity: sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==} dependencies: follow-redirects: 1.14.7_debug@4.3.3 transitivePeerDependencies: @@ -2463,74 +1888,53 @@ packages: dev: false /balanced-match/1.0.2: - resolution: - { - integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==, - } + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} /base64-js/1.5.1: - resolution: - { - integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==, - } + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} dev: false /bcrypt-pbkdf/1.0.2: - resolution: { integrity: sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= } + resolution: {integrity: sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=} dependencies: tweetnacl: 0.14.5 dev: true /bcryptjs/2.4.3: - resolution: { integrity: sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms= } + resolution: {integrity: sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms=} dev: false /big-integer/1.6.51: - resolution: - { - integrity: sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==, - } - engines: { node: ">=0.6" } + resolution: {integrity: sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==} + engines: {node: '>=0.6'} dev: false /binary-extensions/2.2.0: - resolution: - { - integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} + engines: {node: '>=8'} dev: true /binary/0.3.0: - resolution: { integrity: sha1-n2BVO8XOjDOG87VTz/R0Yq3sqnk= } + resolution: {integrity: sha1-n2BVO8XOjDOG87VTz/R0Yq3sqnk=} dependencies: buffers: 0.1.1 chainsaw: 0.1.0 dev: false /bind-obj-methods/3.0.0: - resolution: - { - integrity: sha512-nLEaaz3/sEzNSyPWRsN9HNsqwk1AUyECtGj+XwGdIi3xABnEqecvXtIJ0wehQXuuER5uZ/5fTs2usONgYjG+iw==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-nLEaaz3/sEzNSyPWRsN9HNsqwk1AUyECtGj+XwGdIi3xABnEqecvXtIJ0wehQXuuER5uZ/5fTs2usONgYjG+iw==} + engines: {node: '>=10'} dev: true /bl/2.2.1: - resolution: - { - integrity: sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==, - } + resolution: {integrity: sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==} dependencies: readable-stream: 2.3.7 safe-buffer: 5.2.1 dev: false /bl/4.1.0: - resolution: - { - integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==, - } + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} dependencies: buffer: 5.7.1 inherits: 2.0.4 @@ -2538,15 +1942,12 @@ packages: dev: false /bluebird/3.4.7: - resolution: { integrity: sha1-9y12C+Cbf3bQjtj66Ysomo0F+rM= } + resolution: {integrity: sha1-9y12C+Cbf3bQjtj66Ysomo0F+rM=} dev: false /body-parser/1.19.1: - resolution: - { - integrity: sha512-8ljfQi5eBk8EJfECMrgqNGWPEY5jWP+1IzkzkGdFFEwFQZZyaZ21UqdaHktgiMlH0xLHqIFtE/u2OYE5dOtViA==, - } - engines: { node: ">= 0.8" } + resolution: {integrity: sha512-8ljfQi5eBk8EJfECMrgqNGWPEY5jWP+1IzkzkGdFFEwFQZZyaZ21UqdaHktgiMlH0xLHqIFtE/u2OYE5dOtViA==} + engines: {node: '>= 0.8'} dependencies: bytes: 3.1.1 content-type: 1.0.4 @@ -2561,41 +1962,29 @@ packages: dev: false /boolbase/1.0.0: - resolution: { integrity: sha1-aN/1++YMUes3cl6p4+0xDcwed24= } + resolution: {integrity: sha1-aN/1++YMUes3cl6p4+0xDcwed24=} dev: false /bowser/2.11.0: - resolution: - { - integrity: sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==, - } + resolution: {integrity: sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==} dev: false /brace-expansion/1.1.11: - resolution: - { - integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==, - } + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 /braces/3.0.2: - resolution: - { - integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} + engines: {node: '>=8'} dependencies: fill-range: 7.0.1 dev: true /browserslist/4.19.1: - resolution: - { - integrity: sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A==, - } - engines: { node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7 } + resolution: {integrity: sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true dependencies: caniuse-lite: 1.0.30001309 @@ -2606,61 +1995,43 @@ packages: dev: true /bson/1.1.6: - resolution: - { - integrity: sha512-EvVNVeGo4tHxwi8L6bPj3y3itEvStdwvvlojVxxbyYfoaxJ6keLgrTuKdyfEAszFK+H3olzBuafE0yoh0D1gdg==, - } - engines: { node: ">=0.6.19" } + resolution: {integrity: sha512-EvVNVeGo4tHxwi8L6bPj3y3itEvStdwvvlojVxxbyYfoaxJ6keLgrTuKdyfEAszFK+H3olzBuafE0yoh0D1gdg==} + engines: {node: '>=0.6.19'} dev: false /bson/4.6.1: - resolution: - { - integrity: sha512-I1LQ7Hz5zgwR4QquilLNZwbhPw0Apx7i7X9kGMBTsqPdml/03Q9NBtD9nt/19ahjlphktQImrnderxqpzeVDjw==, - } - engines: { node: ">=6.9.0" } + resolution: {integrity: sha512-I1LQ7Hz5zgwR4QquilLNZwbhPw0Apx7i7X9kGMBTsqPdml/03Q9NBtD9nt/19ahjlphktQImrnderxqpzeVDjw==} + engines: {node: '>=6.9.0'} dependencies: buffer: 5.7.1 dev: false /buffer-from/1.1.2: - resolution: - { - integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==, - } + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} /buffer-indexof-polyfill/1.0.2: - resolution: - { - integrity: sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==, - } - engines: { node: ">=0.10" } + resolution: {integrity: sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==} + engines: {node: '>=0.10'} dev: false /buffer-shims/1.0.0: - resolution: { integrity: sha1-mXjOMXOIxkmth5MCjDR37wRKi1E= } + resolution: {integrity: sha1-mXjOMXOIxkmth5MCjDR37wRKi1E=} dev: false /buffer/5.7.1: - resolution: - { - integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==, - } + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} dependencies: base64-js: 1.5.1 ieee754: 1.2.1 dev: false /buffers/0.1.1: - resolution: { integrity: sha1-skV5w77U1tOWru5tmorn9Ugqt7s= } - engines: { node: ">=0.2.0" } + resolution: {integrity: sha1-skV5w77U1tOWru5tmorn9Ugqt7s=} + engines: {node: '>=0.2.0'} dev: false /bullmq/1.68.4: - resolution: - { - integrity: sha512-I0BYxhRocWHcNpzdXX5jR8DwKG1PkGJ8KUt9SJhWCCWd4sYvsMl6KV6J35ZfigGydXuAja19tbEefTUxdVv/sw==, - } + resolution: {integrity: sha512-I0BYxhRocWHcNpzdXX5jR8DwKG1PkGJ8KUt9SJhWCCWd4sYvsMl6KV6J35ZfigGydXuAja19tbEefTUxdVv/sw==} dependencies: cron-parser: 2.18.0 get-port: 5.1.1 @@ -2676,11 +2047,8 @@ packages: dev: false /bunyan/1.8.15: - resolution: - { - integrity: sha512-0tECWShh6wUysgucJcBAoYegf3JJoZWibxdqhTm7OHPeT42qdjkZ29QCMcKwbgU1kiH+auSIasNRXMLWXafXig==, - } - engines: { "0": node >=0.10.0 } + resolution: {integrity: sha512-0tECWShh6wUysgucJcBAoYegf3JJoZWibxdqhTm7OHPeT42qdjkZ29QCMcKwbgU1kiH+auSIasNRXMLWXafXig==} + engines: {'0': node >=0.10.0} hasBin: true optionalDependencies: dtrace-provider: 0.8.8 @@ -2690,27 +2058,21 @@ packages: dev: false /busboy/0.2.14: - resolution: { integrity: sha1-bCpiLvz0fFe7vh4qnDetNseSVFM= } - engines: { node: ">=0.8.0" } + resolution: {integrity: sha1-bCpiLvz0fFe7vh4qnDetNseSVFM=} + engines: {node: '>=0.8.0'} dependencies: dicer: 0.2.5 readable-stream: 1.1.14 dev: false /bytes/3.1.1: - resolution: - { - integrity: sha512-dWe4nWO/ruEOY7HkUJ5gFt1DCFV9zPRoJr8pV0/ASQermOZjtq8jMjOprC0Kd10GLN+l7xaUPvxzJFWtxGu8Fg==, - } - engines: { node: ">= 0.8" } + resolution: {integrity: sha512-dWe4nWO/ruEOY7HkUJ5gFt1DCFV9zPRoJr8pV0/ASQermOZjtq8jMjOprC0Kd10GLN+l7xaUPvxzJFWtxGu8Fg==} + engines: {node: '>= 0.8'} dev: false /caching-transform/4.0.0: - resolution: - { - integrity: sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==} + engines: {node: '>=8'} dependencies: hasha: 5.2.2 make-dir: 3.1.0 @@ -2719,58 +2081,43 @@ packages: dev: true /call-bind/1.0.2: - resolution: - { - integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==, - } + resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==} dependencies: function-bind: 1.1.1 get-intrinsic: 1.1.1 /callsites/3.1.0: - resolution: - { - integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==, - } - engines: { node: ">=6" } + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} dev: true /camelcase/2.1.1: - resolution: { integrity: sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8= } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=} + engines: {node: '>=0.10.0'} dev: false /camelcase/5.3.1: - resolution: - { - integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==, - } - engines: { node: ">=6" } + resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} + engines: {node: '>=6'} dev: true /caniuse-lite/1.0.30001309: - resolution: - { - integrity: sha512-Pl8vfigmBXXq+/yUz1jUwULeq9xhMJznzdc/xwl4WclDAuebcTHVefpz8lE/bMI+UN7TOkSSe7B7RnZd6+dzjA==, - } + resolution: {integrity: sha512-Pl8vfigmBXXq+/yUz1jUwULeq9xhMJznzdc/xwl4WclDAuebcTHVefpz8lE/bMI+UN7TOkSSe7B7RnZd6+dzjA==} dev: true /caseless/0.12.0: - resolution: { integrity: sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= } + resolution: {integrity: sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=} dev: true /chainsaw/0.1.0: - resolution: { integrity: sha1-XqtQsor+WAdNDVgpE4iCi15fvJg= } + resolution: {integrity: sha1-XqtQsor+WAdNDVgpE4iCi15fvJg=} dependencies: traverse: 0.3.9 dev: false /chalk/2.4.2: - resolution: - { - integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==, - } - engines: { node: ">=4" } + resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} + engines: {node: '>=4'} dependencies: ansi-styles: 3.2.1 escape-string-regexp: 1.0.5 @@ -2778,22 +2125,16 @@ packages: dev: true /chalk/4.1.2: - resolution: - { - integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} dependencies: ansi-styles: 4.3.0 supports-color: 7.2.0 dev: true /chokidar/3.5.3: - resolution: - { - integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==, - } - engines: { node: ">= 8.10.0" } + resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} + engines: {node: '>= 8.10.0'} dependencies: anymatch: 3.1.2 braces: 3.0.2 @@ -2807,30 +2148,21 @@ packages: dev: true /chownr/1.1.4: - resolution: - { - integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==, - } + resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} dev: false /chownr/2.0.0: - resolution: - { - integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} + engines: {node: '>=10'} dev: false /clean-stack/2.2.0: - resolution: - { - integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==, - } - engines: { node: ">=6" } + resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} + engines: {node: '>=6'} dev: true /cliui/3.2.0: - resolution: { integrity: sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0= } + resolution: {integrity: sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=} dependencies: string-width: 1.0.2 strip-ansi: 3.0.1 @@ -2838,10 +2170,7 @@ packages: dev: false /cliui/6.0.0: - resolution: - { - integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==, - } + resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} dependencies: string-width: 4.2.3 strip-ansi: 6.0.1 @@ -2849,10 +2178,7 @@ packages: dev: true /cliui/7.0.4: - resolution: - { - integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==, - } + resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} dependencies: string-width: 4.2.3 strip-ansi: 6.0.1 @@ -2860,29 +2186,23 @@ packages: dev: true /clone/1.0.4: - resolution: { integrity: sha1-2jCcwmPfFZlMaIypAheco8fNfH4= } - engines: { node: ">=0.8" } + resolution: {integrity: sha1-2jCcwmPfFZlMaIypAheco8fNfH4=} + engines: {node: '>=0.8'} dev: false /clone/2.1.2: - resolution: { integrity: sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18= } - engines: { node: ">=0.8" } + resolution: {integrity: sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=} + engines: {node: '>=0.8'} dev: false /cluster-key-slot/1.1.0: - resolution: - { - integrity: sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw==, - } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw==} + engines: {node: '>=0.10.0'} dev: false /cmake-js/6.3.0: - resolution: - { - integrity: sha512-1uqTOmFt6BIqKlrX+39/aewU/JVhyZWDqwAL+6psToUwxj3yWPJiwxiZFmV0XdcoWmqGs7peZTxTbJtAcH8hxw==, - } - engines: { node: ">= 10.0.0" } + resolution: {integrity: sha512-1uqTOmFt6BIqKlrX+39/aewU/JVhyZWDqwAL+6psToUwxj3yWPJiwxiZFmV0XdcoWmqGs7peZTxTbJtAcH8hxw==} + engines: {node: '>= 10.0.0'} hasBin: true dependencies: axios: 0.21.4_debug@4.3.3 @@ -2905,131 +2225,92 @@ packages: dev: false /code-point-at/1.1.0: - resolution: { integrity: sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=} + engines: {node: '>=0.10.0'} dev: false /color-convert/1.9.3: - resolution: - { - integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==, - } + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} dependencies: color-name: 1.1.3 /color-convert/2.0.1: - resolution: - { - integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==, - } - engines: { node: ">=7.0.0" } + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} dependencies: color-name: 1.1.4 dev: true /color-name/1.1.3: - resolution: { integrity: sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= } + resolution: {integrity: sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=} /color-name/1.1.4: - resolution: - { - integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==, - } + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} /color-string/1.9.0: - resolution: - { - integrity: sha512-9Mrz2AQLefkH1UvASKj6v6hj/7eWgjnT/cVsR8CumieLoT+g900exWeNogqtweI8dxloXN9BDQTYro1oWu/5CQ==, - } + resolution: {integrity: sha512-9Mrz2AQLefkH1UvASKj6v6hj/7eWgjnT/cVsR8CumieLoT+g900exWeNogqtweI8dxloXN9BDQTYro1oWu/5CQ==} dependencies: color-name: 1.1.4 simple-swizzle: 0.2.2 dev: false /color-support/1.1.3: - resolution: - { - integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==, - } + resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} hasBin: true dev: true /color/3.2.1: - resolution: - { - integrity: sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==, - } + resolution: {integrity: sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==} dependencies: color-convert: 1.9.3 color-string: 1.9.0 dev: false /colors/1.4.0: - resolution: - { - integrity: sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==, - } - engines: { node: ">=0.1.90" } + resolution: {integrity: sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==} + engines: {node: '>=0.1.90'} dev: false /colorspace/1.1.4: - resolution: - { - integrity: sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==, - } + resolution: {integrity: sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==} dependencies: color: 3.2.1 text-hex: 1.0.0 dev: false /combined-stream/1.0.8: - resolution: - { - integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==, - } - engines: { node: ">= 0.8" } + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} dependencies: delayed-stream: 1.0.0 dev: true /commander/9.0.0: - resolution: - { - integrity: sha512-JJfP2saEKbQqvW+FI93OYUB4ByV5cizMpFMiiJI8xDbBvQvSkIk0VvQdn1CZ8mqAO8Loq2h0gYTYtDFUZUeERw==, - } - engines: { node: ^12.20.0 || >=14 } + resolution: {integrity: sha512-JJfP2saEKbQqvW+FI93OYUB4ByV5cizMpFMiiJI8xDbBvQvSkIk0VvQdn1CZ8mqAO8Loq2h0gYTYtDFUZUeERw==} + engines: {node: ^12.20.0 || >=14} dev: false /commist/1.1.0: - resolution: - { - integrity: sha512-rraC8NXWOEjhADbZe9QBNzLAN5Q3fsTPQtBV+fEVj6xKIgDgNiEVE6ZNfHpZOqfQ21YUzfVNUXLOEZquYvQPPg==, - } + resolution: {integrity: sha512-rraC8NXWOEjhADbZe9QBNzLAN5Q3fsTPQtBV+fEVj6xKIgDgNiEVE6ZNfHpZOqfQ21YUzfVNUXLOEZquYvQPPg==} dependencies: leven: 2.1.0 minimist: 1.2.5 dev: false /commondir/1.0.1: - resolution: { integrity: sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= } + resolution: {integrity: sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=} dev: true /component-emitter/1.3.0: - resolution: - { - integrity: sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==, - } + resolution: {integrity: sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==} dev: true /concat-map/0.0.1: - resolution: { integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= } + resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=} /concat-stream/1.6.2: - resolution: - { - integrity: sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==, - } - engines: { "0": node >= 0.8 } + resolution: {integrity: sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==} + engines: {'0': node >= 0.8} dependencies: buffer-from: 1.1.2 inherits: 2.0.4 @@ -3038,11 +2319,8 @@ packages: dev: false /concat-stream/2.0.0: - resolution: - { - integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==, - } - engines: { "0": node >= 6.0 } + resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==} + engines: {'0': node >= 6.0} dependencies: buffer-from: 1.1.2 inherits: 2.0.4 @@ -3051,75 +2329,51 @@ packages: dev: false /connect-redis/6.1.1: - resolution: - { - integrity: sha512-4on27NmUz1hVCVsrUFUOe06YkgCx88ZZ29Wi7D5mXg22alN/Xx+Y+iPrJ1Cc7coUKAe5SP3W2LF/zo6WXSJFFg==, - } - engines: { node: ">=12" } + resolution: {integrity: sha512-4on27NmUz1hVCVsrUFUOe06YkgCx88ZZ29Wi7D5mXg22alN/Xx+Y+iPrJ1Cc7coUKAe5SP3W2LF/zo6WXSJFFg==} + engines: {node: '>=12'} dev: false /content-disposition/0.5.4: - resolution: - { - integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==, - } - engines: { node: ">= 0.6" } + resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} + engines: {node: '>= 0.6'} dependencies: safe-buffer: 5.2.1 /content-type/1.0.4: - resolution: - { - integrity: sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==, - } - engines: { node: ">= 0.6" } + resolution: {integrity: sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==} + engines: {node: '>= 0.6'} dev: false /convert-source-map/1.8.0: - resolution: - { - integrity: sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==, - } + resolution: {integrity: sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==} dependencies: safe-buffer: 5.1.2 dev: true /cookie-signature/1.0.6: - resolution: { integrity: sha1-4wOogrNCzD7oylE6eZmXNNqzriw= } + resolution: {integrity: sha1-4wOogrNCzD7oylE6eZmXNNqzriw=} dev: false /cookie/0.4.1: - resolution: - { - integrity: sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==, - } - engines: { node: ">= 0.6" } + resolution: {integrity: sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==} + engines: {node: '>= 0.6'} dev: false /cookiejar/2.1.3: - resolution: - { - integrity: sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ==, - } + resolution: {integrity: sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ==} dev: true /core-util-is/1.0.2: - resolution: { integrity: sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= } + resolution: {integrity: sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=} dev: true /core-util-is/1.0.3: - resolution: - { - integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==, - } + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} dev: false /coveralls/3.1.1: - resolution: - { - integrity: sha512-+dxnG2NHncSD1NrqbSM3dn/lE57O6Qf/koe9+I7c+wzkqRmEvcp0kgJdxKInzYzkICKkFMZsX3Vct3++tsF9ww==, - } - engines: { node: ">=6" } + resolution: {integrity: sha512-+dxnG2NHncSD1NrqbSM3dn/lE57O6Qf/koe9+I7c+wzkqRmEvcp0kgJdxKInzYzkICKkFMZsX3Vct3++tsF9ww==} + engines: {node: '>=6'} hasBin: true dependencies: js-yaml: 3.14.1 @@ -3130,29 +2384,20 @@ packages: dev: true /create-require/1.1.1: - resolution: - { - integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==, - } + resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} dev: false /cron-parser/2.18.0: - resolution: - { - integrity: sha512-s4odpheTyydAbTBQepsqd2rNWGa2iV3cyo8g7zbI2QQYGLVsfbhmwukayS1XHppe02Oy1fg7mg6xoaraVJeEcg==, - } - engines: { node: ">=0.8" } + resolution: {integrity: sha512-s4odpheTyydAbTBQepsqd2rNWGa2iV3cyo8g7zbI2QQYGLVsfbhmwukayS1XHppe02Oy1fg7mg6xoaraVJeEcg==} + engines: {node: '>=0.8'} dependencies: is-nan: 1.3.2 moment-timezone: 0.5.34 dev: false /cross-spawn/7.0.3: - resolution: - { - integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==, - } - engines: { node: ">= 8" } + resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + engines: {node: '>= 8'} dependencies: path-key: 3.1.1 shebang-command: 2.0.0 @@ -3160,17 +2405,11 @@ packages: dev: true /crypto-js/4.1.1: - resolution: - { - integrity: sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==, - } + resolution: {integrity: sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==} dev: false /css-select/4.2.1: - resolution: - { - integrity: sha512-/aUslKhzkTNCQUB2qTX84lVmfia9NyjP3WpDGtj/WxhwBzWBYUV3DgUpurHTme8UTPcPlAD1DJ+b0nN/t50zDQ==, - } + resolution: {integrity: sha512-/aUslKhzkTNCQUB2qTX84lVmfia9NyjP3WpDGtj/WxhwBzWBYUV3DgUpurHTme8UTPcPlAD1DJ+b0nN/t50zDQ==} dependencies: boolbase: 1.0.0 css-what: 5.1.0 @@ -3180,52 +2419,37 @@ packages: dev: false /css-what/5.1.0: - resolution: - { - integrity: sha512-arSMRWIIFY0hV8pIxZMEfmMI47Wj3R/aWpZDDxWYCPEiOMv6tfOrnpDtgxBYPEQD4V0Y/958+1TdC3iWTFcUPw==, - } - engines: { node: ">= 6" } + resolution: {integrity: sha512-arSMRWIIFY0hV8pIxZMEfmMI47Wj3R/aWpZDDxWYCPEiOMv6tfOrnpDtgxBYPEQD4V0Y/958+1TdC3iWTFcUPw==} + engines: {node: '>= 6'} dev: false /csv-parse/5.0.4: - resolution: - { - integrity: sha512-5AIdl8l6n3iYQYxan5djB5eKDa+vBnhfWZtRpJTcrETWfVLYN0WSj3L9RwvgYt+psoO77juUr8TG8qpfGZifVQ==, - } + resolution: {integrity: sha512-5AIdl8l6n3iYQYxan5djB5eKDa+vBnhfWZtRpJTcrETWfVLYN0WSj3L9RwvgYt+psoO77juUr8TG8qpfGZifVQ==} dev: false /dashdash/1.14.1: - resolution: { integrity: sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA= } - engines: { node: ">=0.10" } + resolution: {integrity: sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=} + engines: {node: '>=0.10'} dependencies: assert-plus: 1.0.0 dev: true /debug/2.6.9: - resolution: - { - integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==, - } + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} dependencies: ms: 2.0.0 /debug/3.2.7: - resolution: - { - integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==, - } + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} dependencies: ms: 2.1.3 dev: true /debug/4.3.3: - resolution: - { - integrity: sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==, - } - engines: { node: ">=6.0" } + resolution: {integrity: sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==} + engines: {node: '>=6.0'} peerDependencies: - supports-color: "*" + supports-color: '*' peerDependenciesMeta: supports-color: optional: true @@ -3233,147 +2457,111 @@ packages: ms: 2.1.2 /decamelize/1.2.0: - resolution: { integrity: sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=} + engines: {node: '>=0.10.0'} /deep-extend/0.6.0: - resolution: - { - integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==, - } - engines: { node: ">=4.0.0" } + resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} + engines: {node: '>=4.0.0'} dev: false /deep-is/0.1.4: - resolution: - { - integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==, - } + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} dev: true /deepmerge/4.2.2: - resolution: - { - integrity: sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==, - } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==} + engines: {node: '>=0.10.0'} dev: false /default-require-extensions/3.0.0: - resolution: - { - integrity: sha512-ek6DpXq/SCpvjhpFsLFRVtIxJCRw6fUR42lYMVZuUMK7n8eMz4Uh5clckdBjEpLhn/gEBZo7hDJnJcwdKLKQjg==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-ek6DpXq/SCpvjhpFsLFRVtIxJCRw6fUR42lYMVZuUMK7n8eMz4Uh5clckdBjEpLhn/gEBZo7hDJnJcwdKLKQjg==} + engines: {node: '>=8'} dependencies: strip-bom: 4.0.0 dev: true /defaults/1.0.3: - resolution: { integrity: sha1-xlYFHpgX2f8I7YgUd/P+QBnz730= } + resolution: {integrity: sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=} dependencies: clone: 1.0.4 dev: false /define-properties/1.1.3: - resolution: - { - integrity: sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==} + engines: {node: '>= 0.4'} dependencies: object-keys: 1.1.1 /delayed-stream/1.0.0: - resolution: { integrity: sha1-3zrhmayt+31ECqrgsp4icrJOxhk= } - engines: { node: ">=0.4.0" } + resolution: {integrity: sha1-3zrhmayt+31ECqrgsp4icrJOxhk=} + engines: {node: '>=0.4.0'} dev: true /delegates/1.0.0: - resolution: { integrity: sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= } + resolution: {integrity: sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=} dev: false /denque/1.5.1: - resolution: - { - integrity: sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==, - } - engines: { node: ">=0.10" } + resolution: {integrity: sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==} + engines: {node: '>=0.10'} dev: false /depd/1.1.2: - resolution: { integrity: sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= } - engines: { node: ">= 0.6" } + resolution: {integrity: sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=} + engines: {node: '>= 0.6'} /depd/2.0.0: - resolution: - { - integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==, - } - engines: { node: ">= 0.8" } + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} dev: false /destroy/1.0.4: - resolution: { integrity: sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= } + resolution: {integrity: sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=} dev: false /dezalgo/1.0.3: - resolution: { integrity: sha1-f3Qt4Gb8dIvI24IFad3c5Jvw1FY= } + resolution: {integrity: sha1-f3Qt4Gb8dIvI24IFad3c5Jvw1FY=} dependencies: asap: 2.0.6 wrappy: 1.0.2 dev: true /dicer/0.2.5: - resolution: { integrity: sha1-WZbAhrszIYyBLAkL3cCc0S+stw8= } - engines: { node: ">=0.8.0" } + resolution: {integrity: sha1-WZbAhrszIYyBLAkL3cCc0S+stw8=} + engines: {node: '>=0.8.0'} dependencies: readable-stream: 1.1.14 streamsearch: 0.1.2 dev: false /diff/4.0.2: - resolution: - { - integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==, - } - engines: { node: ">=0.3.1" } + resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} + engines: {node: '>=0.3.1'} /dir-glob/3.0.1: - resolution: - { - integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} dependencies: path-type: 4.0.0 dev: true /doctrine/2.1.0: - resolution: - { - integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==, - } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} + engines: {node: '>=0.10.0'} dependencies: esutils: 2.0.3 dev: true /doctrine/3.0.0: - resolution: - { - integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==, - } - engines: { node: ">=6.0.0" } + resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} + engines: {node: '>=6.0.0'} dependencies: esutils: 2.0.3 dev: true /dom-serializer/1.3.2: - resolution: - { - integrity: sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig==, - } + resolution: {integrity: sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig==} dependencies: domelementtype: 2.2.0 domhandler: 4.3.1 @@ -3381,27 +2569,18 @@ packages: dev: false /domelementtype/2.2.0: - resolution: - { - integrity: sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==, - } + resolution: {integrity: sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==} dev: false /domhandler/4.3.1: - resolution: - { - integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==, - } - engines: { node: ">= 4" } + resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==} + engines: {node: '>= 4'} dependencies: domelementtype: 2.2.0 dev: false /domutils/2.8.0: - resolution: - { - integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==, - } + resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==} dependencies: dom-serializer: 1.3.2 domelementtype: 2.2.0 @@ -3409,19 +2588,13 @@ packages: dev: false /dotenv/16.0.0: - resolution: - { - integrity: sha512-qD9WU0MPM4SWLPJy/r2Be+2WgQj8plChsyrCNQzW/0WjvcJQiKQJ9mH3ZgB3fxbUUxgc/11ZJ0Fi5KiimWGz2Q==, - } - engines: { node: ">=12" } + resolution: {integrity: sha512-qD9WU0MPM4SWLPJy/r2Be+2WgQj8plChsyrCNQzW/0WjvcJQiKQJ9mH3ZgB3fxbUUxgc/11ZJ0Fi5KiimWGz2Q==} + engines: {node: '>=12'} dev: false /dtrace-provider/0.8.8: - resolution: - { - integrity: sha512-b7Z7cNtHPhH9EJhNNbbeqTcXB8LGFFZhq1PGgEvpeHlzd36bhbdTWoE/Ba/YguqpBSlAPKnARWhVlhunCMwfxg==, - } - engines: { node: ">=0.10" } + resolution: {integrity: sha512-b7Z7cNtHPhH9EJhNNbbeqTcXB8LGFFZhq1PGgEvpeHlzd36bhbdTWoE/Ba/YguqpBSlAPKnARWhVlhunCMwfxg==} + engines: {node: '>=0.10'} requiresBuild: true dependencies: nan: 2.15.0 @@ -3429,16 +2602,13 @@ packages: optional: true /duplexer2/0.1.4: - resolution: { integrity: sha1-ixLauHjA1p4+eJEFFmKjL8a93ME= } + resolution: {integrity: sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=} dependencies: readable-stream: 2.3.7 dev: false /duplexify/3.7.1: - resolution: - { - integrity: sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==, - } + resolution: {integrity: sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==} dependencies: end-of-stream: 1.4.4 inherits: 2.0.4 @@ -3447,10 +2617,7 @@ packages: dev: false /duplexify/4.1.2: - resolution: - { - integrity: sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw==, - } + resolution: {integrity: sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw==} dependencies: end-of-stream: 1.4.4 inherits: 2.0.4 @@ -3459,64 +2626,46 @@ packages: dev: false /ecc-jsbn/0.1.2: - resolution: { integrity: sha1-OoOpBOVDUyh4dMVkt1SThoSamMk= } + resolution: {integrity: sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=} dependencies: jsbn: 0.1.1 safer-buffer: 2.1.2 dev: true /ee-first/1.1.1: - resolution: { integrity: sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= } + resolution: {integrity: sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=} dev: false /electron-to-chromium/1.4.65: - resolution: - { - integrity: sha512-0/d8Skk8sW3FxXP0Dd6MnBlrwx7Qo9cqQec3BlIAlvKnrmS3pHsIbaroEi+nd0kZkGpQ6apMEre7xndzjlEnLw==, - } + resolution: {integrity: sha512-0/d8Skk8sW3FxXP0Dd6MnBlrwx7Qo9cqQec3BlIAlvKnrmS3pHsIbaroEi+nd0kZkGpQ6apMEre7xndzjlEnLw==} dev: true /emoji-regex/8.0.0: - resolution: - { - integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==, - } + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} dev: true /enabled/2.0.0: - resolution: - { - integrity: sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==, - } + resolution: {integrity: sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==} dev: false /encodeurl/1.0.2: - resolution: { integrity: sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= } - engines: { node: ">= 0.8" } + resolution: {integrity: sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=} + engines: {node: '>= 0.8'} dev: false /end-of-stream/1.4.4: - resolution: - { - integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==, - } + resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} dependencies: once: 1.4.0 dev: false /entities/2.2.0: - resolution: - { - integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==, - } + resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==} dev: false /es-abstract/1.19.1: - resolution: - { - integrity: sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.2 es-to-primitive: 1.2.1 @@ -3541,11 +2690,8 @@ packages: dev: true /es-to-primitive/1.2.1: - resolution: - { - integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} + engines: {node: '>= 0.4'} dependencies: is-callable: 1.2.4 is-date-object: 1.0.5 @@ -3553,84 +2699,60 @@ packages: dev: true /es6-error/4.1.1: - resolution: - { - integrity: sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==, - } + resolution: {integrity: sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==} dev: true /escalade/3.1.1: - resolution: - { - integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==, - } - engines: { node: ">=6" } + resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} + engines: {node: '>=6'} dev: true /escape-html/1.0.3: - resolution: { integrity: sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= } + resolution: {integrity: sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=} dev: false /escape-string-regexp/1.0.5: - resolution: { integrity: sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= } - engines: { node: ">=0.8.0" } + resolution: {integrity: sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=} + engines: {node: '>=0.8.0'} dev: true /escape-string-regexp/2.0.0: - resolution: - { - integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} + engines: {node: '>=8'} dev: true /escape-string-regexp/4.0.0: - resolution: - { - integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} dev: true /eslint-config-prettier/8.3.0_eslint@8.8.0: - resolution: - { - integrity: sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==, - } + resolution: {integrity: sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==} hasBin: true peerDependencies: - eslint: ">=7.0.0" + eslint: '>=7.0.0' dependencies: eslint: 8.8.0 dev: true /eslint-import-resolver-node/0.3.6: - resolution: - { - integrity: sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==, - } + resolution: {integrity: sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==} dependencies: debug: 3.2.7 resolve: 1.22.0 dev: true /eslint-module-utils/2.7.3: - resolution: - { - integrity: sha512-088JEC7O3lDZM9xGe0RerkOMd0EjFl+Yvd1jPWIkMT5u3H9+HC34mWWPnqPrN13gieT9pBOO+Qt07Nb/6TresQ==, - } - engines: { node: ">=4" } + resolution: {integrity: sha512-088JEC7O3lDZM9xGe0RerkOMd0EjFl+Yvd1jPWIkMT5u3H9+HC34mWWPnqPrN13gieT9pBOO+Qt07Nb/6TresQ==} + engines: {node: '>=4'} dependencies: debug: 3.2.7 find-up: 2.1.0 dev: true /eslint-plugin-import/2.25.4_eslint@8.8.0: - resolution: - { - integrity: sha512-/KJBASVFxpu0xg1kIBn9AUa8hQVnszpwgE7Ld0lKAlx7Ie87yzEzCgSkekt+le/YVhiaosO4Y14GDAOc41nfxA==, - } - engines: { node: ">=4" } + resolution: {integrity: sha512-/KJBASVFxpu0xg1kIBn9AUa8hQVnszpwgE7Ld0lKAlx7Ie87yzEzCgSkekt+le/YVhiaosO4Y14GDAOc41nfxA==} + engines: {node: '>=4'} peerDependencies: eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 dependencies: @@ -3651,15 +2773,12 @@ packages: dev: true /eslint-plugin-prettier/4.0.0_43197c8d12d1d439034cfcf65e1c48c2: - resolution: - { - integrity: sha512-98MqmCJ7vJodoQK359bqQWaxOE0CS8paAz/GgjaZLyex4TTk3g9HugoO89EqWCrFiOqn9EVvcoo7gZzONCWVwQ==, - } - engines: { node: ">=6.0.0" } + resolution: {integrity: sha512-98MqmCJ7vJodoQK359bqQWaxOE0CS8paAz/GgjaZLyex4TTk3g9HugoO89EqWCrFiOqn9EVvcoo7gZzONCWVwQ==} + engines: {node: '>=6.0.0'} peerDependencies: - eslint: ">=7.28.0" - eslint-config-prettier: "*" - prettier: ">=2.0.0" + eslint: '>=7.28.0' + eslint-config-prettier: '*' + prettier: '>=2.0.0' peerDependenciesMeta: eslint-config-prettier: optional: true @@ -3671,66 +2790,48 @@ packages: dev: true /eslint-scope/5.1.1: - resolution: - { - integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==, - } - engines: { node: ">=8.0.0" } + resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} + engines: {node: '>=8.0.0'} dependencies: esrecurse: 4.3.0 estraverse: 4.3.0 dev: true /eslint-scope/7.1.0: - resolution: - { - integrity: sha512-aWwkhnS0qAXqNOgKOK0dJ2nvzEbhEvpy8OlJ9kZ0FeZnA6zpjv1/Vei+puGFFX7zkPCkHHXb7IDX3A+7yPrRWg==, - } - engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + resolution: {integrity: sha512-aWwkhnS0qAXqNOgKOK0dJ2nvzEbhEvpy8OlJ9kZ0FeZnA6zpjv1/Vei+puGFFX7zkPCkHHXb7IDX3A+7yPrRWg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: esrecurse: 4.3.0 estraverse: 5.3.0 dev: true /eslint-utils/3.0.0_eslint@8.8.0: - resolution: - { - integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==, - } - engines: { node: ^10.0.0 || ^12.0.0 || >= 14.0.0 } + resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==} + engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0} peerDependencies: - eslint: ">=5" + eslint: '>=5' dependencies: eslint: 8.8.0 eslint-visitor-keys: 2.1.0 dev: true /eslint-visitor-keys/2.1.0: - resolution: - { - integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==} + engines: {node: '>=10'} dev: true /eslint-visitor-keys/3.2.0: - resolution: - { - integrity: sha512-IOzT0X126zn7ALX0dwFiUQEdsfzrm4+ISsQS8nukaJXwEyYKRSnEIIDULYg1mCtGp7UUXgfGl7BIolXREQK+XQ==, - } - engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + resolution: {integrity: sha512-IOzT0X126zn7ALX0dwFiUQEdsfzrm4+ISsQS8nukaJXwEyYKRSnEIIDULYg1mCtGp7UUXgfGl7BIolXREQK+XQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true /eslint/8.8.0: - resolution: - { - integrity: sha512-H3KXAzQGBH1plhYS3okDix2ZthuYJlQQEGE5k0IKuEqUSiyu4AmxxlJ2MtTYeJ3xB4jDhcYCwGOg2TXYdnDXlQ==, - } - engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + resolution: {integrity: sha512-H3KXAzQGBH1plhYS3okDix2ZthuYJlQQEGE5k0IKuEqUSiyu4AmxxlJ2MtTYeJ3xB4jDhcYCwGOg2TXYdnDXlQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} hasBin: true dependencies: - "@eslint/eslintrc": 1.0.5 - "@humanwhocodes/config-array": 0.9.3 + '@eslint/eslintrc': 1.0.5 + '@humanwhocodes/config-array': 0.9.3 ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.3 @@ -3769,11 +2870,8 @@ packages: dev: true /espree/9.3.0: - resolution: - { - integrity: sha512-d/5nCsb0JcqsSEeQzFZ8DH1RmxPcglRWh24EFTlUEmCKoehXGdpsx0RkHDubqUI8LSAIKMQp4r9SzQ3n+sm4HQ==, - } - engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + resolution: {integrity: sha512-d/5nCsb0JcqsSEeQzFZ8DH1RmxPcglRWh24EFTlUEmCKoehXGdpsx0RkHDubqUI8LSAIKMQp4r9SzQ3n+sm4HQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: acorn: 8.7.0 acorn-jsx: 5.3.2_acorn@8.7.0 @@ -3781,72 +2879,51 @@ packages: dev: true /esprima/4.0.1: - resolution: - { - integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==, - } - engines: { node: ">=4" } + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} hasBin: true dev: true /esquery/1.4.0: - resolution: - { - integrity: sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==, - } - engines: { node: ">=0.10" } + resolution: {integrity: sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==} + engines: {node: '>=0.10'} dependencies: estraverse: 5.3.0 dev: true /esrecurse/4.3.0: - resolution: - { - integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==, - } - engines: { node: ">=4.0" } + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} dependencies: estraverse: 5.3.0 dev: true /estraverse/4.3.0: - resolution: - { - integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==, - } - engines: { node: ">=4.0" } + resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} + engines: {node: '>=4.0'} dev: true /estraverse/5.3.0: - resolution: - { - integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==, - } - engines: { node: ">=4.0" } + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} dev: true /esutils/2.0.3: - resolution: - { - integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==, - } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} dev: true /etag/1.8.1: - resolution: { integrity: sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= } - engines: { node: ">= 0.6" } + resolution: {integrity: sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=} + engines: {node: '>= 0.6'} dev: false /events-to-array/1.1.2: - resolution: { integrity: sha1-LUH1Y+H+QA7Uli/hpNXGp1Od9/Y= } + resolution: {integrity: sha1-LUH1Y+H+QA7Uli/hpNXGp1Od9/Y=} dev: true /express-async-errors/3.1.1_express@4.17.2: - resolution: - { - integrity: sha512-h6aK1da4tpqWSbyCa3FxB/V6Ehd4EEB15zyQq9qe75OZBp0krinNKuH4rAY+S/U/2I36vdLAUFSjQJ+TFmODng==, - } + resolution: {integrity: sha512-h6aK1da4tpqWSbyCa3FxB/V6Ehd4EEB15zyQq9qe75OZBp0krinNKuH4rAY+S/U/2I36vdLAUFSjQJ+TFmODng==} peerDependencies: express: ^4.16.2 dependencies: @@ -3854,28 +2931,19 @@ packages: dev: false /express-rate-limit/5.5.1: - resolution: - { - integrity: sha512-MTjE2eIbHv5DyfuFz4zLYWxpqVhEhkTiwFGuB74Q9CSou2WHO52nlE5y3Zlg6SIsiYUIPj6ifFxnkPz6O3sIUg==, - } + resolution: {integrity: sha512-MTjE2eIbHv5DyfuFz4zLYWxpqVhEhkTiwFGuB74Q9CSou2WHO52nlE5y3Zlg6SIsiYUIPj6ifFxnkPz6O3sIUg==} dev: false /express-request-mock/3.1.0: - resolution: - { - integrity: sha512-Ep1uuqCRUc2nTjwAeaVuviDGPCOU2xIhyVHQoN6+xDc9ANJJY42auiAO0guJTvnyyHY/8Q3/hZWEjHf1eo6hSQ==, - } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-Ep1uuqCRUc2nTjwAeaVuviDGPCOU2xIhyVHQoN6+xDc9ANJJY42auiAO0guJTvnyyHY/8Q3/hZWEjHf1eo6hSQ==} + engines: {node: '>= 12.0.0'} dependencies: node-mocks-http: 1.11.0 dev: true /express-session/1.17.2: - resolution: - { - integrity: sha512-mPcYcLA0lvh7D4Oqr5aNJFMtBMKPLl++OKKxkHzZ0U0oDq1rpKBnkR5f5vCHR26VeArlTOEF9td4x5IjICksRQ==, - } - engines: { node: ">= 0.8.0" } + resolution: {integrity: sha512-mPcYcLA0lvh7D4Oqr5aNJFMtBMKPLl++OKKxkHzZ0U0oDq1rpKBnkR5f5vCHR26VeArlTOEF9td4x5IjICksRQ==} + engines: {node: '>= 0.8.0'} dependencies: cookie: 0.4.1 cookie-signature: 1.0.6 @@ -3888,11 +2956,8 @@ packages: dev: false /express/4.17.2: - resolution: - { - integrity: sha512-oxlxJxcQlYwqPWKVJJtvQiwHgosH/LrLSPA+H4UxpyvSS6jC5aH+5MoHFM+KABgTOt0APue4w66Ha8jCUo9QGg==, - } - engines: { node: ">= 0.10.0" } + resolution: {integrity: sha512-oxlxJxcQlYwqPWKVJJtvQiwHgosH/LrLSPA+H4UxpyvSS6jC5aH+5MoHFM+KABgTOt0APue4w66Ha8jCUo9QGg==} + engines: {node: '>= 0.10.0'} dependencies: accepts: 1.3.8 array-flatten: 1.1.1 @@ -3927,146 +2992,98 @@ packages: dev: false /extend/3.0.2: - resolution: - { - integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==, - } + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} dev: true /extsprintf/1.3.0: - resolution: { integrity: sha1-lpGEQOMEGnpBT4xS48V06zw+HgU= } - engines: { "0": node >=0.6.0 } + resolution: {integrity: sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=} + engines: {'0': node >=0.6.0} dev: true /fast-deep-equal/3.1.3: - resolution: - { - integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==, - } + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} dev: true /fast-diff/1.2.0: - resolution: - { - integrity: sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==, - } + resolution: {integrity: sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==} dev: true /fast-glob/3.2.11: - resolution: - { - integrity: sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==, - } - engines: { node: ">=8.6.0" } + resolution: {integrity: sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==} + engines: {node: '>=8.6.0'} dependencies: - "@nodelib/fs.stat": 2.0.5 - "@nodelib/fs.walk": 1.2.8 + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 glob-parent: 5.1.2 merge2: 1.4.1 micromatch: 4.0.4 dev: true /fast-json-stable-hash/1.0.3: - resolution: - { - integrity: sha512-iLK3Tq2J/KbiUn/5OHyhxikHqzwFXSM2e803Dp2JtBdQjsCVuZwWvL+WqWXPPRUEuDX4c0kqID1SU84RCfxs3A==, - } + resolution: {integrity: sha512-iLK3Tq2J/KbiUn/5OHyhxikHqzwFXSM2e803Dp2JtBdQjsCVuZwWvL+WqWXPPRUEuDX4c0kqID1SU84RCfxs3A==} dev: false /fast-json-stable-stringify/2.1.0: - resolution: - { - integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==, - } + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} dev: true /fast-levenshtein/2.0.6: - resolution: { integrity: sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= } + resolution: {integrity: sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=} dev: true /fast-safe-stringify/2.1.1: - resolution: - { - integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==, - } + resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} dev: true /fast-xml-parser/3.19.0: - resolution: - { - integrity: sha512-4pXwmBplsCPv8FOY1WRakF970TjNGnGnfbOnLqjlYvMiF1SR3yOHyxMR/YCXpPTOspNF5gwudqktIP4VsWkvBg==, - } + resolution: {integrity: sha512-4pXwmBplsCPv8FOY1WRakF970TjNGnGnfbOnLqjlYvMiF1SR3yOHyxMR/YCXpPTOspNF5gwudqktIP4VsWkvBg==} hasBin: true dev: false /fast-xml-parser/4.0.2: - resolution: - { - integrity: sha512-3GOSbMTZxxrPPQ+aURM7Wia10bi71HBbiG/3mOEEkRSAkRtg4m7UhMSnB2rzOhBeRHyJUWsllOfyNnjTT1b85w==, - } + resolution: {integrity: sha512-3GOSbMTZxxrPPQ+aURM7Wia10bi71HBbiG/3mOEEkRSAkRtg4m7UhMSnB2rzOhBeRHyJUWsllOfyNnjTT1b85w==} hasBin: true dependencies: strnum: 1.0.5 dev: false /fastestsmallesttextencoderdecoder/1.0.22: - resolution: - { - integrity: sha512-Pb8d48e+oIuY4MaM64Cd7OW1gt4nxCHs7/ddPPZ/Ic3sg8yVGM7O9wDvZ7us6ScaUupzM+pfBolwtYhN1IxBIw==, - } + resolution: {integrity: sha512-Pb8d48e+oIuY4MaM64Cd7OW1gt4nxCHs7/ddPPZ/Ic3sg8yVGM7O9wDvZ7us6ScaUupzM+pfBolwtYhN1IxBIw==} dev: false /fastq/1.13.0: - resolution: - { - integrity: sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==, - } + resolution: {integrity: sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==} dependencies: reusify: 1.0.4 dev: true /fecha/4.2.1: - resolution: - { - integrity: sha512-MMMQ0ludy/nBs1/o0zVOiKTpG7qMbonKUzjJgQFEuvq6INZ1OraKPRAWkBq5vlKLOUMpmNYG1JoN3oDPUQ9m3Q==, - } + resolution: {integrity: sha512-MMMQ0ludy/nBs1/o0zVOiKTpG7qMbonKUzjJgQFEuvq6INZ1OraKPRAWkBq5vlKLOUMpmNYG1JoN3oDPUQ9m3Q==} dev: false /file-entry-cache/6.0.1: - resolution: - { - integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==, - } - engines: { node: ^10.12.0 || >=12.0.0 } + resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} + engines: {node: ^10.12.0 || >=12.0.0} dependencies: flat-cache: 3.0.4 dev: true /file-stream-rotator/0.6.1: - resolution: - { - integrity: sha512-u+dBid4PvZw17PmDeRcNOtCP9CCK/9lRN2w+r1xIS7yOL9JFrIBKTvrYsxT4P0pGtThYTn++QS5ChHaUov3+zQ==, - } + resolution: {integrity: sha512-u+dBid4PvZw17PmDeRcNOtCP9CCK/9lRN2w+r1xIS7yOL9JFrIBKTvrYsxT4P0pGtThYTn++QS5ChHaUov3+zQ==} dependencies: moment: 2.29.1 dev: false /fill-range/7.0.1: - resolution: - { - integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} + engines: {node: '>=8'} dependencies: to-regex-range: 5.0.1 dev: true /finalhandler/1.1.2: - resolution: - { - integrity: sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==, - } - engines: { node: ">= 0.8" } + resolution: {integrity: sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==} + engines: {node: '>= 0.8'} dependencies: debug: 2.6.9 encodeurl: 1.0.2 @@ -4078,11 +3095,8 @@ packages: dev: false /find-cache-dir/3.3.2: - resolution: - { - integrity: sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==} + engines: {node: '>=8'} dependencies: commondir: 1.0.1 make-dir: 3.1.0 @@ -4090,60 +3104,45 @@ packages: dev: true /find-up/2.1.0: - resolution: { integrity: sha1-RdG35QbHF93UgndaK3eSCjwMV6c= } - engines: { node: ">=4" } + resolution: {integrity: sha1-RdG35QbHF93UgndaK3eSCjwMV6c=} + engines: {node: '>=4'} dependencies: locate-path: 2.0.0 dev: true /find-up/4.1.0: - resolution: - { - integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} dependencies: locate-path: 5.0.0 path-exists: 4.0.0 dev: true /findit/2.0.0: - resolution: { integrity: sha1-ZQnwEmr0wXhVHPqZOU4DLhOk1W4= } + resolution: {integrity: sha1-ZQnwEmr0wXhVHPqZOU4DLhOk1W4=} dev: true /flat-cache/3.0.4: - resolution: - { - integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==, - } - engines: { node: ^10.12.0 || >=12.0.0 } + resolution: {integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==} + engines: {node: ^10.12.0 || >=12.0.0} dependencies: flatted: 3.2.5 rimraf: 3.0.2 dev: true /flatted/3.2.5: - resolution: - { - integrity: sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==, - } + resolution: {integrity: sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==} dev: true /fn.name/1.1.0: - resolution: - { - integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==, - } + resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==} dev: false /follow-redirects/1.14.7_debug@4.3.3: - resolution: - { - integrity: sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ==, - } - engines: { node: ">=4.0" } + resolution: {integrity: sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ==} + engines: {node: '>=4.0'} peerDependencies: - debug: "*" + debug: '*' peerDependenciesMeta: debug: optional: true @@ -4152,26 +3151,20 @@ packages: dev: false /foreground-child/2.0.0: - resolution: - { - integrity: sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==, - } - engines: { node: ">=8.0.0" } + resolution: {integrity: sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==} + engines: {node: '>=8.0.0'} dependencies: cross-spawn: 7.0.3 signal-exit: 3.0.7 dev: true /forever-agent/0.6.1: - resolution: { integrity: sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= } + resolution: {integrity: sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=} dev: true /form-data/2.3.3: - resolution: - { - integrity: sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==, - } - engines: { node: ">= 0.12" } + resolution: {integrity: sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==} + engines: {node: '>= 0.12'} dependencies: asynckit: 0.4.0 combined-stream: 1.0.8 @@ -4179,11 +3172,8 @@ packages: dev: true /form-data/3.0.1: - resolution: - { - integrity: sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==, - } - engines: { node: ">= 6" } + resolution: {integrity: sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==} + engines: {node: '>= 6'} dependencies: asynckit: 0.4.0 combined-stream: 1.0.8 @@ -4191,11 +3181,8 @@ packages: dev: true /form-data/4.0.0: - resolution: - { - integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==, - } - engines: { node: ">= 6" } + resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} + engines: {node: '>= 6'} dependencies: asynckit: 0.4.0 combined-stream: 1.0.8 @@ -4203,10 +3190,7 @@ packages: dev: true /formidable/2.0.1: - resolution: - { - integrity: sha512-rjTMNbp2BpfQShhFbR3Ruk3qk2y9jKpvMW78nJgx8QKtxjDVrwbZG+wvDOmVbifHyOUOQJXxqEy6r0faRrPzTQ==, - } + resolution: {integrity: sha512-rjTMNbp2BpfQShhFbR3Ruk3qk2y9jKpvMW78nJgx8QKtxjDVrwbZG+wvDOmVbifHyOUOQJXxqEy6r0faRrPzTQ==} dependencies: dezalgo: 1.0.3 hexoid: 1.0.0 @@ -4215,33 +3199,24 @@ packages: dev: true /forwarded/0.2.0: - resolution: - { - integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==, - } - engines: { node: ">= 0.6" } + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} dev: false /fresh/0.5.2: - resolution: { integrity: sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= } - engines: { node: ">= 0.6" } + resolution: {integrity: sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=} + engines: {node: '>= 0.6'} /fromentries/1.3.2: - resolution: - { - integrity: sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==, - } + resolution: {integrity: sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==} dev: true /fs-exists-cached/1.0.0: - resolution: { integrity: sha1-zyVVTKBQ3EmuZla0HeQiWJidy84= } + resolution: {integrity: sha1-zyVVTKBQ3EmuZla0HeQiWJidy84=} dev: true /fs-extra/5.0.0: - resolution: - { - integrity: sha512-66Pm4RYbjzdyeuqudYqhFiNBbCIuI9kgRqLPSHIlXHidW8NIQtVdkM1yeZ4lXwuhbTETv3EUGMNHAAw6hiundQ==, - } + resolution: {integrity: sha512-66Pm4RYbjzdyeuqudYqhFiNBbCIuI9kgRqLPSHIlXHidW8NIQtVdkM1yeZ4lXwuhbTETv3EUGMNHAAw6hiundQ==} dependencies: graceful-fs: 4.2.9 jsonfile: 4.0.0 @@ -4249,44 +3224,32 @@ packages: dev: false /fs-minipass/1.2.7: - resolution: - { - integrity: sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==, - } + resolution: {integrity: sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==} dependencies: minipass: 2.9.0 dev: false /fs-minipass/2.1.0: - resolution: - { - integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==, - } - engines: { node: ">= 8" } + resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} + engines: {node: '>= 8'} dependencies: minipass: 3.1.6 dev: false /fs.realpath/1.0.0: - resolution: { integrity: sha1-FQStJSMVjKpA20onh8sBQRmU6k8= } + resolution: {integrity: sha1-FQStJSMVjKpA20onh8sBQRmU6k8=} /fsevents/2.3.2: - resolution: - { - integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==, - } - engines: { node: ^8.16.0 || ^10.6.0 || >=11.0.0 } + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] requiresBuild: true dev: true optional: true /fstream/1.0.12: - resolution: - { - integrity: sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==, - } - engines: { node: ">=0.6" } + resolution: {integrity: sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==} + engines: {node: '>=0.6'} dependencies: graceful-fs: 4.2.9 inherits: 2.0.4 @@ -4295,24 +3258,18 @@ packages: dev: false /function-bind/1.1.1: - resolution: - { - integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==, - } + resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} /function-loop/2.0.1: - resolution: - { - integrity: sha512-ktIR+O6i/4h+j/ZhZJNdzeI4i9lEPeEK6UPR2EVyTVBqOwcU3Za9xYKLH64ZR9HmcROyRrOkizNyjjtWJzDDkQ==, - } + resolution: {integrity: sha512-ktIR+O6i/4h+j/ZhZJNdzeI4i9lEPeEK6UPR2EVyTVBqOwcU3Za9xYKLH64ZR9HmcROyRrOkizNyjjtWJzDDkQ==} dev: true /functional-red-black-tree/1.0.1: - resolution: { integrity: sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= } + resolution: {integrity: sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=} dev: true /gauge/1.2.7: - resolution: { integrity: sha1-6c7FSD09TuDvRLYKfZnkk14TbZM= } + resolution: {integrity: sha1-6c7FSD09TuDvRLYKfZnkk14TbZM=} dependencies: ansi: 0.3.1 has-unicode: 2.0.1 @@ -4322,86 +3279,62 @@ packages: dev: false /gensync/1.0.0-beta.2: - resolution: - { - integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==, - } - engines: { node: ">=6.9.0" } + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} dev: true /get-caller-file/2.0.5: - resolution: - { - integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==, - } - engines: { node: 6.* || 8.* || >= 10.* } + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} dev: true /get-intrinsic/1.1.1: - resolution: - { - integrity: sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==, - } + resolution: {integrity: sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==} dependencies: function-bind: 1.1.1 has: 1.0.3 has-symbols: 1.0.2 /get-package-type/0.1.0: - resolution: - { - integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==, - } - engines: { node: ">=8.0.0" } + resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} + engines: {node: '>=8.0.0'} dev: true /get-port/5.1.1: - resolution: - { - integrity: sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==} + engines: {node: '>=8'} dev: false /get-symbol-description/1.0.0: - resolution: - { - integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.2 get-intrinsic: 1.1.1 dev: true /getpass/0.1.7: - resolution: { integrity: sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo= } + resolution: {integrity: sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=} dependencies: assert-plus: 1.0.0 dev: true /glob-parent/5.1.2: - resolution: - { - integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==, - } - engines: { node: ">= 6" } + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} dependencies: is-glob: 4.0.3 dev: true /glob-parent/6.0.2: - resolution: - { - integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==, - } - engines: { node: ">=10.13.0" } + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} dependencies: is-glob: 4.0.3 dev: true /glob/6.0.4: - resolution: { integrity: sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI= } + resolution: {integrity: sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=} dependencies: inflight: 1.0.6 inherits: 2.0.4 @@ -4411,10 +3344,7 @@ packages: dev: false /glob/7.2.0: - resolution: - { - integrity: sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==, - } + resolution: {integrity: sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==} dependencies: fs.realpath: 1.0.0 inflight: 1.0.6 @@ -4424,29 +3354,20 @@ packages: path-is-absolute: 1.0.1 /globals/11.12.0: - resolution: - { - integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==, - } - engines: { node: ">=4" } + resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} + engines: {node: '>=4'} dev: true /globals/13.12.1: - resolution: - { - integrity: sha512-317dFlgY2pdJZ9rspXDks7073GpDmXdfbM3vYYp0HAMKGDh1FfWPleI2ljVNLQX5M5lXcAslTcPTrOrMEFOjyw==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-317dFlgY2pdJZ9rspXDks7073GpDmXdfbM3vYYp0HAMKGDh1FfWPleI2ljVNLQX5M5lXcAslTcPTrOrMEFOjyw==} + engines: {node: '>=8'} dependencies: type-fest: 0.20.2 dev: true /globby/11.1.0: - resolution: - { - integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} dependencies: array-union: 2.1.0 dir-glob: 3.0.1 @@ -4457,22 +3378,16 @@ packages: dev: true /graceful-fs/4.2.9: - resolution: - { - integrity: sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==, - } + resolution: {integrity: sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==} /har-schema/2.0.0: - resolution: { integrity: sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= } - engines: { node: ">=4" } + resolution: {integrity: sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=} + engines: {node: '>=4'} dev: true /har-validator/5.1.5: - resolution: - { - integrity: sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==, - } - engines: { node: ">=6" } + resolution: {integrity: sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==} + engines: {node: '>=6'} deprecated: this library is no longer supported dependencies: ajv: 6.12.6 @@ -4480,120 +3395,81 @@ packages: dev: true /has-bigints/1.0.1: - resolution: - { - integrity: sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==, - } + resolution: {integrity: sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==} dev: true /has-flag/3.0.0: - resolution: { integrity: sha1-tdRU3CGZriJWmfNGfloH87lVuv0= } - engines: { node: ">=4" } + resolution: {integrity: sha1-tdRU3CGZriJWmfNGfloH87lVuv0=} + engines: {node: '>=4'} dev: true /has-flag/4.0.0: - resolution: - { - integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} dev: true /has-symbols/1.0.2: - resolution: - { - integrity: sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==} + engines: {node: '>= 0.4'} /has-tostringtag/1.0.0: - resolution: - { - integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==} + engines: {node: '>= 0.4'} dependencies: has-symbols: 1.0.2 dev: true /has-unicode/2.0.1: - resolution: { integrity: sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= } + resolution: {integrity: sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=} dev: false /has/1.0.3: - resolution: - { - integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==, - } - engines: { node: ">= 0.4.0" } + resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} + engines: {node: '>= 0.4.0'} dependencies: function-bind: 1.1.1 /hasha/5.2.2: - resolution: - { - integrity: sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==} + engines: {node: '>=8'} dependencies: is-stream: 2.0.1 type-fest: 0.8.1 dev: true /he/1.2.0: - resolution: - { - integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==, - } + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} hasBin: true dev: false /helmet/5.0.2: - resolution: - { - integrity: sha512-QWlwUZZ8BtlvwYVTSDTBChGf8EOcQ2LkGMnQJxSzD1mUu8CCjXJZq/BXP8eWw4kikRnzlhtYo3lCk0ucmYA3Vg==, - } - engines: { node: ">=12.0.0" } + resolution: {integrity: sha512-QWlwUZZ8BtlvwYVTSDTBChGf8EOcQ2LkGMnQJxSzD1mUu8CCjXJZq/BXP8eWw4kikRnzlhtYo3lCk0ucmYA3Vg==} + engines: {node: '>=12.0.0'} dev: false /help-me/3.0.0: - resolution: - { - integrity: sha512-hx73jClhyk910sidBB7ERlnhMlFsJJIBqSVMFDwPN8o2v9nmp5KgLq1Xz1Bf1fCMMZ6mPrX159iG0VLy/fPMtQ==, - } + resolution: {integrity: sha512-hx73jClhyk910sidBB7ERlnhMlFsJJIBqSVMFDwPN8o2v9nmp5KgLq1Xz1Bf1fCMMZ6mPrX159iG0VLy/fPMtQ==} dependencies: glob: 7.2.0 readable-stream: 3.6.0 dev: false /hexoid/1.0.0: - resolution: - { - integrity: sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==} + engines: {node: '>=8'} dev: true /html-entities/2.3.2: - resolution: - { - integrity: sha512-c3Ab/url5ksaT0WyleslpBEthOzWhrjQbg75y7XUsfSzi3Dgzt0l8w5e7DylRn15MTlMMD58dTfzddNS2kcAjQ==, - } + resolution: {integrity: sha512-c3Ab/url5ksaT0WyleslpBEthOzWhrjQbg75y7XUsfSzi3Dgzt0l8w5e7DylRn15MTlMMD58dTfzddNS2kcAjQ==} dev: false /html-escaper/2.0.2: - resolution: - { - integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==, - } + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} dev: true /http-errors/1.8.1: - resolution: - { - integrity: sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==, - } - engines: { node: ">= 0.6" } + resolution: {integrity: sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==} + engines: {node: '>= 0.6'} dependencies: depd: 1.1.2 inherits: 2.0.4 @@ -4603,8 +3479,8 @@ packages: dev: false /http-signature/1.2.0: - resolution: { integrity: sha1-muzZJRFHcvPZW2WmCruPfBj7rOE= } - engines: { node: ">=0.8", npm: ">=1.3.7" } + resolution: {integrity: sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=} + engines: {node: '>=0.8', npm: '>=1.3.7'} dependencies: assert-plus: 1.0.0 jsprim: 1.4.2 @@ -4612,87 +3488,60 @@ packages: dev: true /iconv-lite/0.4.24: - resolution: - { - integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==, - } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} dependencies: safer-buffer: 2.1.2 dev: false /ieee754/1.2.1: - resolution: - { - integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==, - } + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} dev: false /ignore/4.0.6: - resolution: - { - integrity: sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==, - } - engines: { node: ">= 4" } + resolution: {integrity: sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==} + engines: {node: '>= 4'} dev: true /ignore/5.2.0: - resolution: - { - integrity: sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==, - } - engines: { node: ">= 4" } + resolution: {integrity: sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==} + engines: {node: '>= 4'} dev: true /import-fresh/3.3.0: - resolution: - { - integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==, - } - engines: { node: ">=6" } + resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} + engines: {node: '>=6'} dependencies: parent-module: 1.0.1 resolve-from: 4.0.0 dev: true /imurmurhash/0.1.4: - resolution: { integrity: sha1-khi5srkoojixPcT7a21XbyMUU+o= } - engines: { node: ">=0.8.19" } + resolution: {integrity: sha1-khi5srkoojixPcT7a21XbyMUU+o=} + engines: {node: '>=0.8.19'} dev: true /indent-string/4.0.0: - resolution: - { - integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} dev: true /inflight/1.0.6: - resolution: { integrity: sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= } + resolution: {integrity: sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=} dependencies: once: 1.4.0 wrappy: 1.0.2 /inherits/2.0.4: - resolution: - { - integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==, - } + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} /ini/1.3.8: - resolution: - { - integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==, - } + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} dev: false /internal-slot/1.0.3: - resolution: - { - integrity: sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==} + engines: {node: '>= 0.4'} dependencies: get-intrinsic: 1.1.1 has: 1.0.3 @@ -4700,16 +3549,13 @@ packages: dev: true /invert-kv/1.0.0: - resolution: { integrity: sha1-EEqOSqym09jNFXqO+L+rLXo//bY= } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha1-EEqOSqym09jNFXqO+L+rLXo//bY=} + engines: {node: '>=0.10.0'} dev: false /ioredis/4.28.5: - resolution: - { - integrity: sha512-3GYo0GJtLqgNXj4YhrisLaNNvWSNwSS2wS4OELGfGxH8I69+XfNdnmV1AyN+ZqMh0i7eX+SWjrwFKDBDgfBC1A==, - } - engines: { node: ">=6" } + resolution: {integrity: sha512-3GYo0GJtLqgNXj4YhrisLaNNvWSNwSS2wS4OELGfGxH8I69+XfNdnmV1AyN+ZqMh0i7eX+SWjrwFKDBDgfBC1A==} + engines: {node: '>=6'} dependencies: cluster-key-slot: 1.1.0 debug: 4.3.3 @@ -4727,267 +3573,192 @@ packages: dev: false /ipaddr.js/1.9.1: - resolution: - { - integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==, - } - engines: { node: ">= 0.10" } + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} dev: false /is-arrayish/0.3.2: - resolution: - { - integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==, - } + resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} dev: false /is-bigint/1.0.4: - resolution: - { - integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==, - } + resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} dependencies: has-bigints: 1.0.1 dev: true /is-binary-path/2.1.0: - resolution: - { - integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} dependencies: binary-extensions: 2.2.0 dev: true /is-boolean-object/1.1.2: - resolution: - { - integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.2 has-tostringtag: 1.0.0 dev: true /is-callable/1.2.4: - resolution: - { - integrity: sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==} + engines: {node: '>= 0.4'} dev: true /is-core-module/2.8.1: - resolution: - { - integrity: sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==, - } + resolution: {integrity: sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==} dependencies: has: 1.0.3 dev: true /is-date-object/1.0.5: - resolution: - { - integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} + engines: {node: '>= 0.4'} dependencies: has-tostringtag: 1.0.0 dev: true /is-extglob/2.1.1: - resolution: { integrity: sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=} + engines: {node: '>=0.10.0'} dev: true /is-fullwidth-code-point/1.0.0: - resolution: { integrity: sha1-754xOG8DGn8NZDr4L95QxFfvAMs= } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha1-754xOG8DGn8NZDr4L95QxFfvAMs=} + engines: {node: '>=0.10.0'} dependencies: number-is-nan: 1.0.1 dev: false /is-fullwidth-code-point/3.0.0: - resolution: - { - integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} dev: true /is-glob/4.0.3: - resolution: - { - integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==, - } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} dependencies: is-extglob: 2.1.1 dev: true /is-iojs/1.1.0: - resolution: { integrity: sha1-TBEDO11dlNbqs3dd7cm+fQCDJfE= } + resolution: {integrity: sha1-TBEDO11dlNbqs3dd7cm+fQCDJfE=} dev: false /is-nan/1.3.2: - resolution: - { - integrity: sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.2 define-properties: 1.1.3 dev: false /is-negative-zero/2.0.2: - resolution: - { - integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==} + engines: {node: '>= 0.4'} dev: true /is-number-object/1.0.6: - resolution: - { - integrity: sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==} + engines: {node: '>= 0.4'} dependencies: has-tostringtag: 1.0.0 dev: true /is-number/7.0.0: - resolution: - { - integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==, - } - engines: { node: ">=0.12.0" } + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} dev: true /is-regex/1.1.4: - resolution: - { - integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.2 has-tostringtag: 1.0.0 dev: true /is-shared-array-buffer/1.0.1: - resolution: - { - integrity: sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA==, - } + resolution: {integrity: sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA==} dev: true /is-stream/2.0.1: - resolution: - { - integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} /is-string/1.0.7: - resolution: - { - integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} + engines: {node: '>= 0.4'} dependencies: has-tostringtag: 1.0.0 dev: true /is-symbol/1.0.4: - resolution: - { - integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} + engines: {node: '>= 0.4'} dependencies: has-symbols: 1.0.2 dev: true /is-typedarray/1.0.0: - resolution: { integrity: sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= } + resolution: {integrity: sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=} dev: true /is-weakref/1.0.2: - resolution: - { - integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==, - } + resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} dependencies: call-bind: 1.0.2 dev: true /is-windows/1.0.2: - resolution: - { - integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==, - } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} + engines: {node: '>=0.10.0'} dev: true /isarray/0.0.1: - resolution: { integrity: sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= } + resolution: {integrity: sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=} dev: false /isarray/1.0.0: - resolution: { integrity: sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= } + resolution: {integrity: sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=} dev: false /isexe/2.0.0: - resolution: { integrity: sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= } + resolution: {integrity: sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=} /isomorphic-ws/4.0.1_ws@7.5.6: - resolution: - { - integrity: sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==, - } + resolution: {integrity: sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==} peerDependencies: - ws: "*" + ws: '*' dependencies: ws: 7.5.6 dev: false /isstream/0.1.2: - resolution: { integrity: sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= } + resolution: {integrity: sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=} dev: true /istanbul-lib-coverage/3.2.0: - resolution: - { - integrity: sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==} + engines: {node: '>=8'} dev: true /istanbul-lib-hook/3.0.0: - resolution: - { - integrity: sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==} + engines: {node: '>=8'} dependencies: append-transform: 2.0.0 dev: true /istanbul-lib-instrument/4.0.3: - resolution: - { - integrity: sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==} + engines: {node: '>=8'} dependencies: - "@babel/core": 7.17.0 - "@istanbuljs/schema": 0.1.3 + '@babel/core': 7.17.0 + '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.0 semver: 6.3.0 transitivePeerDependencies: @@ -4995,11 +3766,8 @@ packages: dev: true /istanbul-lib-processinfo/2.0.2: - resolution: - { - integrity: sha512-kOwpa7z9hme+IBPZMzQ5vdQj8srYgAtaRqeI48NGmAQ+/5yKiHLV0QbYqQpxsdEF0+w14SoB8YbnHKcXE2KnYw==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-kOwpa7z9hme+IBPZMzQ5vdQj8srYgAtaRqeI48NGmAQ+/5yKiHLV0QbYqQpxsdEF0+w14SoB8YbnHKcXE2KnYw==} + engines: {node: '>=8'} dependencies: archy: 1.0.0 cross-spawn: 7.0.3 @@ -5011,11 +3779,8 @@ packages: dev: true /istanbul-lib-report/3.0.0: - resolution: - { - integrity: sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==} + engines: {node: '>=8'} dependencies: istanbul-lib-coverage: 3.2.0 make-dir: 3.1.0 @@ -5023,11 +3788,8 @@ packages: dev: true /istanbul-lib-source-maps/4.0.1: - resolution: - { - integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} + engines: {node: '>=10'} dependencies: debug: 4.3.3 istanbul-lib-coverage: 3.2.0 @@ -5037,45 +3799,30 @@ packages: dev: true /istanbul-reports/3.1.4: - resolution: - { - integrity: sha512-r1/DshN4KSE7xWEknZLLLLDn5CJybV3nw01VTkp6D5jzLuELlcbudfj/eSQFvrKsJuTVCGnePO7ho82Nw9zzfw==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-r1/DshN4KSE7xWEknZLLLLDn5CJybV3nw01VTkp6D5jzLuELlcbudfj/eSQFvrKsJuTVCGnePO7ho82Nw9zzfw==} + engines: {node: '>=8'} dependencies: html-escaper: 2.0.2 istanbul-lib-report: 3.0.0 dev: true /jackspeak/1.4.1: - resolution: - { - integrity: sha512-npN8f+M4+IQ8xD3CcWi3U62VQwKlT3Tj4GxbdT/fYTmeogD9eBF9OFdpoFG/VPNoshRjPUijdkp/p2XrzUHaVg==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-npN8f+M4+IQ8xD3CcWi3U62VQwKlT3Tj4GxbdT/fYTmeogD9eBF9OFdpoFG/VPNoshRjPUijdkp/p2XrzUHaVg==} + engines: {node: '>=8'} dependencies: cliui: 7.0.4 dev: true /js-sdsl/2.1.4: - resolution: - { - integrity: sha512-/Ew+CJWHNddr7sjwgxaVeIORIH4AMVC9dy0hPf540ZGMVgS9d3ajwuVdyhDt6/QUvT8ATjR3yuYBKsS79F+H4A==, - } + resolution: {integrity: sha512-/Ew+CJWHNddr7sjwgxaVeIORIH4AMVC9dy0hPf540ZGMVgS9d3ajwuVdyhDt6/QUvT8ATjR3yuYBKsS79F+H4A==} dev: false /js-tokens/4.0.0: - resolution: - { - integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==, - } + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} dev: true /js-yaml/3.14.1: - resolution: - { - integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==, - } + resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} hasBin: true dependencies: argparse: 1.0.10 @@ -5083,81 +3830,60 @@ packages: dev: true /js-yaml/4.1.0: - resolution: - { - integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==, - } + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true dependencies: argparse: 2.0.1 dev: true /jsbn/0.1.1: - resolution: { integrity: sha1-peZUwuWi3rXyAdls77yoDA7y9RM= } + resolution: {integrity: sha1-peZUwuWi3rXyAdls77yoDA7y9RM=} dev: true /jsesc/2.5.2: - resolution: - { - integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==, - } - engines: { node: ">=4" } + resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} + engines: {node: '>=4'} hasBin: true dev: true /json-schema-traverse/0.4.1: - resolution: - { - integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==, - } + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} dev: true /json-schema/0.4.0: - resolution: - { - integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==, - } + resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} dev: true /json-stable-stringify-without-jsonify/1.0.1: - resolution: { integrity: sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= } + resolution: {integrity: sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=} dev: true /json-stringify-safe/5.0.1: - resolution: { integrity: sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= } + resolution: {integrity: sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=} dev: true /json5/1.0.1: - resolution: - { - integrity: sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==, - } + resolution: {integrity: sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==} hasBin: true dependencies: minimist: 1.2.5 /json5/2.2.0: - resolution: - { - integrity: sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==, - } - engines: { node: ">=6" } + resolution: {integrity: sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==} + engines: {node: '>=6'} hasBin: true dependencies: minimist: 1.2.5 /jsonfile/4.0.0: - resolution: { integrity: sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss= } + resolution: {integrity: sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=} optionalDependencies: graceful-fs: 4.2.9 dev: false /jsprim/1.4.2: - resolution: - { - integrity: sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==, - } - engines: { node: ">=0.6.0" } + resolution: {integrity: sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==} + engines: {node: '>=0.6.0'} dependencies: assert-plus: 1.0.0 extsprintf: 1.3.0 @@ -5166,46 +3892,37 @@ packages: dev: true /kuler/2.0.0: - resolution: - { - integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==, - } + resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==} dev: false /lcid/1.0.0: - resolution: { integrity: sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU= } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=} + engines: {node: '>=0.10.0'} dependencies: invert-kv: 1.0.0 dev: false /lcov-parse/1.0.0: - resolution: { integrity: sha1-6w1GtUER68VhrLTECO+TY73I9+A= } + resolution: {integrity: sha1-6w1GtUER68VhrLTECO+TY73I9+A=} hasBin: true dev: true /leven/2.1.0: - resolution: { integrity: sha1-wuep93IJTe6dNCAq6KzORoeHVYA= } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha1-wuep93IJTe6dNCAq6KzORoeHVYA=} + engines: {node: '>=0.10.0'} dev: false /levn/0.4.1: - resolution: - { - integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==, - } - engines: { node: ">= 0.8.0" } + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} dependencies: prelude-ls: 1.2.1 type-check: 0.4.0 dev: true /libtap/1.1.4: - resolution: - { - integrity: sha512-jM+QyAeRdVs1bJrNpjlu+l8gRdDkAehqls31AwSnqXghVLUP6nbYeU2Xfs2svYS7ZdksvnHvrxCKRBFEz/BCjA==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-jM+QyAeRdVs1bJrNpjlu+l8gRdDkAehqls31AwSnqXghVLUP6nbYeU2Xfs2svYS7ZdksvnHvrxCKRBFEz/BCjA==} + engines: {node: '>=10'} dependencies: async-hook-domain: 2.0.4 bind-obj-methods: 3.0.0 @@ -5224,82 +3941,67 @@ packages: dev: true /listenercount/1.0.1: - resolution: { integrity: sha1-hMinKrWcRyUyFIDJdeZQg0LnCTc= } + resolution: {integrity: sha1-hMinKrWcRyUyFIDJdeZQg0LnCTc=} dev: false /locate-path/2.0.0: - resolution: { integrity: sha1-K1aLJl7slExtnA3pw9u7ygNUzY4= } - engines: { node: ">=4" } + resolution: {integrity: sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=} + engines: {node: '>=4'} dependencies: p-locate: 2.0.0 path-exists: 3.0.0 dev: true /locate-path/5.0.0: - resolution: - { - integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} dependencies: p-locate: 4.1.0 dev: true /lodash.defaults/4.2.0: - resolution: { integrity: sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw= } + resolution: {integrity: sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=} dev: false /lodash.flatten/4.4.0: - resolution: { integrity: sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8= } + resolution: {integrity: sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=} dev: false /lodash.flattendeep/4.4.0: - resolution: { integrity: sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI= } + resolution: {integrity: sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=} dev: true /lodash.isarguments/3.1.0: - resolution: { integrity: sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo= } + resolution: {integrity: sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=} dev: false /lodash.merge/4.6.2: - resolution: - { - integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==, - } + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} dev: true /lodash.pad/4.5.1: - resolution: { integrity: sha1-QzCUmoM6fI2iLMIPaibE1Z3runA= } + resolution: {integrity: sha1-QzCUmoM6fI2iLMIPaibE1Z3runA=} dev: false /lodash.padend/4.6.1: - resolution: { integrity: sha1-U8y6BH0G4VjTEfRdpiX05J5vFm4= } + resolution: {integrity: sha1-U8y6BH0G4VjTEfRdpiX05J5vFm4=} dev: false /lodash.padstart/4.6.1: - resolution: { integrity: sha1-0uPuv/DZ05rVD1y9G1KnvOa7YRs= } + resolution: {integrity: sha1-0uPuv/DZ05rVD1y9G1KnvOa7YRs=} dev: false /lodash/4.17.21: - resolution: - { - integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==, - } + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} dev: false /log-driver/1.2.7: - resolution: - { - integrity: sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==, - } - engines: { node: ">=0.8.6" } + resolution: {integrity: sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==} + engines: {node: '>=0.8.6'} dev: true /logform/2.3.2: - resolution: - { - integrity: sha512-V6JiPThZzTsbVRspNO6TmHkR99oqYTs8fivMBYQkjZj6rxW92KxtDCPE6IkAk1DNBnYKNkjm4jYBm6JDUcyhOA==, - } + resolution: {integrity: sha512-V6JiPThZzTsbVRspNO6TmHkR99oqYTs8fivMBYQkjZj6rxW92KxtDCPE6IkAk1DNBnYKNkjm4jYBm6JDUcyhOA==} dependencies: colors: 1.4.0 fecha: 4.2.1 @@ -5309,208 +4011,145 @@ packages: dev: false /lru-cache/6.0.0: - resolution: - { - integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} dependencies: yallist: 4.0.0 /make-dir/3.1.0: - resolution: - { - integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} + engines: {node: '>=8'} dependencies: semver: 6.3.0 dev: true /make-error/1.3.6: - resolution: - { - integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==, - } + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} dev: false /media-typer/0.3.0: - resolution: { integrity: sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= } - engines: { node: ">= 0.6" } + resolution: {integrity: sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=} + engines: {node: '>= 0.6'} /memory-pager/1.5.0: - resolution: - { - integrity: sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==, - } + resolution: {integrity: sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==} dev: false optional: true /memory-stream/0.0.3: - resolution: { integrity: sha1-6+jdHDuLw4wOeUHp3dWuvmtN6D8= } + resolution: {integrity: sha1-6+jdHDuLw4wOeUHp3dWuvmtN6D8=} dependencies: readable-stream: 1.0.34 dev: false /merge-descriptors/1.0.1: - resolution: { integrity: sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= } + resolution: {integrity: sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=} /merge2/1.4.1: - resolution: - { - integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==, - } - engines: { node: ">= 8" } + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} dev: true /methods/1.1.2: - resolution: { integrity: sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= } - engines: { node: ">= 0.6" } + resolution: {integrity: sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=} + engines: {node: '>= 0.6'} /micromatch/4.0.4: - resolution: - { - integrity: sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==, - } - engines: { node: ">=8.6" } + resolution: {integrity: sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==} + engines: {node: '>=8.6'} dependencies: braces: 3.0.2 picomatch: 2.3.1 dev: true /mime-db/1.51.0: - resolution: - { - integrity: sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==, - } - engines: { node: ">= 0.6" } + resolution: {integrity: sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==} + engines: {node: '>= 0.6'} /mime-types/2.1.34: - resolution: - { - integrity: sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==, - } - engines: { node: ">= 0.6" } + resolution: {integrity: sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==} + engines: {node: '>= 0.6'} dependencies: mime-db: 1.51.0 /mime/1.6.0: - resolution: - { - integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==, - } - engines: { node: ">=4" } + resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} + engines: {node: '>=4'} hasBin: true /mime/2.6.0: - resolution: - { - integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==, - } - engines: { node: ">=4.0.0" } + resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==} + engines: {node: '>=4.0.0'} hasBin: true dev: true /minimatch/3.0.5: - resolution: - { - integrity: sha512-tUpxzX0VAzJHjLu0xUfFv1gwVp9ba3IOuRAVH2EGuRW8a5emA2FlACLqiT/lDVtS1W+TGNwqz3sWaNyLgDJWuw==, - } + resolution: {integrity: sha512-tUpxzX0VAzJHjLu0xUfFv1gwVp9ba3IOuRAVH2EGuRW8a5emA2FlACLqiT/lDVtS1W+TGNwqz3sWaNyLgDJWuw==} dependencies: brace-expansion: 1.1.11 /minimist/1.2.5: - resolution: - { - integrity: sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==, - } + resolution: {integrity: sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==} /minipass/2.9.0: - resolution: - { - integrity: sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==, - } + resolution: {integrity: sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==} dependencies: safe-buffer: 5.2.1 yallist: 3.1.1 dev: false /minipass/3.1.6: - resolution: - { - integrity: sha512-rty5kpw9/z8SX9dmxblFA6edItUmwJgMeYDZRrwlIVN27i8gysGbznJwUggw2V/FVqFSDdWy040ZPS811DYAqQ==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-rty5kpw9/z8SX9dmxblFA6edItUmwJgMeYDZRrwlIVN27i8gysGbznJwUggw2V/FVqFSDdWy040ZPS811DYAqQ==} + engines: {node: '>=8'} dependencies: yallist: 4.0.0 /minizlib/1.3.3: - resolution: - { - integrity: sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==, - } + resolution: {integrity: sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==} dependencies: minipass: 2.9.0 dev: false /minizlib/2.1.2: - resolution: - { - integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==, - } - engines: { node: ">= 8" } + resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} + engines: {node: '>= 8'} dependencies: minipass: 3.1.6 yallist: 4.0.0 dev: false /mkdirp/0.5.5: - resolution: - { - integrity: sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==, - } + resolution: {integrity: sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==} hasBin: true dependencies: minimist: 1.2.5 dev: false /mkdirp/1.0.4: - resolution: - { - integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} + engines: {node: '>=10'} hasBin: true /moment-timezone/0.5.34: - resolution: - { - integrity: sha512-3zAEHh2hKUs3EXLESx/wsgw6IQdusOT8Bxm3D9UrHPQR7zlMmzwybC8zHEM1tQ4LJwP7fcxrWr8tuBg05fFCbg==, - } + resolution: {integrity: sha512-3zAEHh2hKUs3EXLESx/wsgw6IQdusOT8Bxm3D9UrHPQR7zlMmzwybC8zHEM1tQ4LJwP7fcxrWr8tuBg05fFCbg==} dependencies: moment: 2.29.1 dev: false /moment/2.29.1: - resolution: - { - integrity: sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==, - } + resolution: {integrity: sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==} dev: false /mongodb/3.6.3: - resolution: - { - integrity: sha512-rOZuR0QkodZiM+UbQE5kDsJykBqWi0CL4Ec2i1nrGrUI3KO11r6Fbxskqmq3JK2NH7aW4dcccBuUujAP0ERl5w==, - } - engines: { node: ">=4" } + resolution: {integrity: sha512-rOZuR0QkodZiM+UbQE5kDsJykBqWi0CL4Ec2i1nrGrUI3KO11r6Fbxskqmq3JK2NH7aW4dcccBuUujAP0ERl5w==} + engines: {node: '>=4'} peerDependencies: - aws4: "*" - bson-ext: "*" - kerberos: "*" - mongodb-client-encryption: "*" - mongodb-extjson: "*" - snappy: "*" + aws4: '*' + bson-ext: '*' + kerberos: '*' + mongodb-client-encryption: '*' + mongodb-extjson: '*' + snappy: '*' peerDependenciesMeta: aws4: optional: true @@ -5535,39 +4174,33 @@ packages: dev: false /monk-middleware-cast-ids/0.2.1: - resolution: { integrity: sha1-QMQOWmyzPM7cKJIglDJ17ohhxSk= } + resolution: {integrity: sha1-QMQOWmyzPM7cKJIglDJ17ohhxSk=} dev: false /monk-middleware-fields/0.2.0: - resolution: { integrity: sha1-/2N6819ZSIecyyvhWpE2CRG+psE= } + resolution: {integrity: sha1-/2N6819ZSIecyyvhWpE2CRG+psE=} dev: false /monk-middleware-handle-callback/0.2.2: - resolution: - { - integrity: sha512-5hBynb7asZ2uw9XVze7C3XH0zXT51yFDvYydk/5HnWWzh2NLglDSiKDcX0yLKPHzFgiq+5Z4Laq5fFVnFsmm8w==, - } + resolution: {integrity: sha512-5hBynb7asZ2uw9XVze7C3XH0zXT51yFDvYydk/5HnWWzh2NLglDSiKDcX0yLKPHzFgiq+5Z4Laq5fFVnFsmm8w==} dev: false /monk-middleware-options/0.2.1: - resolution: { integrity: sha1-WNrhxRjUZjbr3/UG+t/Hc7tEKIY= } + resolution: {integrity: sha1-WNrhxRjUZjbr3/UG+t/Hc7tEKIY=} dev: false /monk-middleware-query/0.2.0: - resolution: { integrity: sha1-qSbGd9SlYgxiFRsKVtDAwVFnWHQ= } + resolution: {integrity: sha1-qSbGd9SlYgxiFRsKVtDAwVFnWHQ=} dev: false /monk-middleware-wait-for-connection/0.2.0: - resolution: { integrity: sha1-MSlY0w5Yi1fQl1TdfJe0hDMWg1o= } + resolution: {integrity: sha1-MSlY0w5Yi1fQl1TdfJe0hDMWg1o=} dev: false /monk/7.3.4: - resolution: - { - integrity: sha512-PkPNiElwroVyKQj01usyziOvwiKYBUVSq7YU1FB4KFr0J3v0GeXW0TebYsLR4u33WB8JGqPiAcuzDspfdujqQg==, - } + resolution: {integrity: sha512-PkPNiElwroVyKQj01usyziOvwiKYBUVSq7YU1FB4KFr0J3v0GeXW0TebYsLR4u33WB8JGqPiAcuzDspfdujqQg==} dependencies: - "@types/mongodb": 3.6.20 + '@types/mongodb': 3.6.20 debug: 4.3.3 mongodb: 3.6.3 monk-middleware-cast-ids: 0.2.1 @@ -5588,10 +4221,7 @@ packages: dev: false /mqtt-packet/6.10.0: - resolution: - { - integrity: sha512-ja8+mFKIHdB1Tpl6vac+sktqy3gA8t9Mduom1BA75cI+R9AHnZOiaBQwpGiWnaVJLDGRdNhQmFaAqd7tkKSMGA==, - } + resolution: {integrity: sha512-ja8+mFKIHdB1Tpl6vac+sktqy3gA8t9Mduom1BA75cI+R9AHnZOiaBQwpGiWnaVJLDGRdNhQmFaAqd7tkKSMGA==} dependencies: bl: 4.1.0 debug: 4.3.3 @@ -5601,11 +4231,8 @@ packages: dev: false /mqtt/4.3.4: - resolution: - { - integrity: sha512-yAVDfVHz3Cjn6K68z54mf7fTni/AWsPhiEsRwZSvet2wO47R6NFUn2psWxYIph2JxWtL3ZKa/da8pjJKSaXPdQ==, - } - engines: { node: ">=10.0.0" } + resolution: {integrity: sha512-yAVDfVHz3Cjn6K68z54mf7fTni/AWsPhiEsRwZSvet2wO47R6NFUn2psWxYIph2JxWtL3ZKa/da8pjJKSaXPdQ==} + engines: {node: '>=10.0.0'} hasBin: true dependencies: commist: 1.1.0 @@ -5632,25 +4259,16 @@ packages: dev: false /ms/2.0.0: - resolution: { integrity: sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= } + resolution: {integrity: sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=} /ms/2.1.2: - resolution: - { - integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==, - } + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} /ms/2.1.3: - resolution: - { - integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==, - } + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} /msgpackr-extract/1.0.16: - resolution: - { - integrity: sha512-fxdRfQUxPrL/TizyfYfMn09dK58e+d65bRD/fcaVH4052vj30QOzzqxcQIS7B0NsqlypEQ/6Du3QmP2DhWFfCA==, - } + resolution: {integrity: sha512-fxdRfQUxPrL/TizyfYfMn09dK58e+d65bRD/fcaVH4052vj30QOzzqxcQIS7B0NsqlypEQ/6Du3QmP2DhWFfCA==} requiresBuild: true dependencies: nan: 2.15.0 @@ -5659,20 +4277,14 @@ packages: optional: true /msgpackr/1.5.4: - resolution: - { - integrity: sha512-Z7w5Jg+2Q9z9gJxeM68d7tSuWZZGnFIRhZnyqcZCa/1dKkhOCNvR1TUV3zzJ3+vj78vlwKRzUgVDlW4jiSOeDA==, - } + resolution: {integrity: sha512-Z7w5Jg+2Q9z9gJxeM68d7tSuWZZGnFIRhZnyqcZCa/1dKkhOCNvR1TUV3zzJ3+vj78vlwKRzUgVDlW4jiSOeDA==} optionalDependencies: msgpackr-extract: 1.0.16 dev: false /multer/1.4.4: - resolution: - { - integrity: sha512-2wY2+xD4udX612aMqMcB8Ws2Voq6NIUPEtD1be6m411T4uDH/VtL9i//xvcyFlTVfRdaBsk7hV5tgrGQqhuBiw==, - } - engines: { node: ">= 0.10.0" } + resolution: {integrity: sha512-2wY2+xD4udX612aMqMcB8Ws2Voq6NIUPEtD1be6m411T4uDH/VtL9i//xvcyFlTVfRdaBsk7hV5tgrGQqhuBiw==} + engines: {node: '>= 0.10.0'} dependencies: append-field: 1.0.0 busboy: 0.2.14 @@ -5685,8 +4297,8 @@ packages: dev: false /mv/2.1.1: - resolution: { integrity: sha1-rmzg1vbV4KT32JN5jQPB6pVZtqI= } - engines: { node: ">=0.8.0" } + resolution: {integrity: sha1-rmzg1vbV4KT32JN5jQPB6pVZtqI=} + engines: {node: '>=0.8.0'} requiresBuild: true dependencies: mkdirp: 0.5.5 @@ -5696,46 +4308,34 @@ packages: optional: true /nan/2.15.0: - resolution: - { - integrity: sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==, - } + resolution: {integrity: sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==} dev: false optional: true /natural-compare/1.4.0: - resolution: { integrity: sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= } + resolution: {integrity: sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=} dev: true /ncp/2.0.0: - resolution: { integrity: sha1-GVoh1sRuNh0vsSgbo4uR6d9727M= } + resolution: {integrity: sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=} hasBin: true dev: false optional: true /negotiator/0.6.3: - resolution: - { - integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==, - } - engines: { node: ">= 0.6" } + resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} + engines: {node: '>= 0.6'} /node-cache/5.1.2: - resolution: - { - integrity: sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg==, - } - engines: { node: ">= 8.0.0" } + resolution: {integrity: sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg==} + engines: {node: '>= 8.0.0'} dependencies: clone: 2.1.2 dev: false /node-fetch/2.6.7: - resolution: - { - integrity: sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==, - } - engines: { node: 4.x || >=6.0.0 } + resolution: {integrity: sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==} + engines: {node: 4.x || >=6.0.0} peerDependencies: encoding: ^0.1.0 peerDependenciesMeta: @@ -5746,30 +4346,21 @@ packages: dev: false /node-gyp-build/4.3.0: - resolution: - { - integrity: sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q==, - } + resolution: {integrity: sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q==} hasBin: true dev: false optional: true /node-html-parser/5.3.3: - resolution: - { - integrity: sha512-ncg1033CaX9UexbyA7e1N0aAoAYRDiV8jkTvzEnfd1GDvzFdrsXLzR4p4ik8mwLgnaKP/jyUFWDy9q3jvRT2Jw==, - } + resolution: {integrity: sha512-ncg1033CaX9UexbyA7e1N0aAoAYRDiV8jkTvzEnfd1GDvzFdrsXLzR4p4ik8mwLgnaKP/jyUFWDy9q3jvRT2Jw==} dependencies: css-select: 4.2.1 he: 1.2.0 dev: false /node-mocks-http/1.11.0: - resolution: - { - integrity: sha512-jS/WzSOcKbOeGrcgKbenZeNhxUNnP36Yw11+hL4TTxQXErGfqYZ+MaYNNvhaTiGIJlzNSqgQkk9j8dSu1YWSuw==, - } - engines: { node: ">=0.6" } + resolution: {integrity: sha512-jS/WzSOcKbOeGrcgKbenZeNhxUNnP36Yw11+hL4TTxQXErGfqYZ+MaYNNvhaTiGIJlzNSqgQkk9j8dSu1YWSuw==} + engines: {node: '>=0.6'} dependencies: accepts: 1.3.8 content-disposition: 0.5.4 @@ -5784,40 +4375,28 @@ packages: dev: true /node-preload/0.2.1: - resolution: - { - integrity: sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==} + engines: {node: '>=8'} dependencies: process-on-spawn: 1.0.0 dev: true /node-releases/2.0.1: - resolution: - { - integrity: sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==, - } + resolution: {integrity: sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==} dev: true /nodemailer/6.7.2: - resolution: - { - integrity: sha512-Dz7zVwlef4k5R71fdmxwR8Q39fiboGbu3xgswkzGwczUfjp873rVxt1O46+Fh0j1ORnAC6L9+heI8uUpO6DT7Q==, - } - engines: { node: ">=6.0.0" } + resolution: {integrity: sha512-Dz7zVwlef4k5R71fdmxwR8Q39fiboGbu3xgswkzGwczUfjp873rVxt1O46+Fh0j1ORnAC6L9+heI8uUpO6DT7Q==} + engines: {node: '>=6.0.0'} dev: false /normalize-path/3.0.0: - resolution: - { - integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==, - } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} dev: true /npmlog/1.2.1: - resolution: { integrity: sha1-KOe+YZYJtT960d0wChDWTXFiaLY= } + resolution: {integrity: sha1-KOe+YZYJtT960d0wChDWTXFiaLY=} dependencies: ansi: 0.3.1 are-we-there-yet: 1.0.6 @@ -5825,19 +4404,13 @@ packages: dev: false /nth-check/2.0.1: - resolution: - { - integrity: sha512-it1vE95zF6dTT9lBsYbxvqh0Soy4SPowchj0UBGj/V6cTPnXXtQOPUbhZ6CmGzAD/rW22LQK6E96pcdJXk4A4w==, - } + resolution: {integrity: sha512-it1vE95zF6dTT9lBsYbxvqh0Soy4SPowchj0UBGj/V6cTPnXXtQOPUbhZ6CmGzAD/rW22LQK6E96pcdJXk4A4w==} dependencies: boolbase: 1.0.0 dev: false /number-allocator/1.0.9: - resolution: - { - integrity: sha512-sIIF0dZKMs3roPUD7rLreH8H3x47QKV9dHZ+PeSnH24gL0CxKxz/823woGZC0hLBSb2Ar/rOOeHiNbnPBum/Mw==, - } + resolution: {integrity: sha512-sIIF0dZKMs3roPUD7rLreH8H3x47QKV9dHZ+PeSnH24gL0CxKxz/823woGZC0hLBSb2Ar/rOOeHiNbnPBum/Mw==} dependencies: debug: 4.3.3 js-sdsl: 2.1.4 @@ -5846,20 +4419,17 @@ packages: dev: false /number-is-nan/1.0.1: - resolution: { integrity: sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=} + engines: {node: '>=0.10.0'} dev: false /nyc/15.1.0: - resolution: - { - integrity: sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==, - } - engines: { node: ">=8.9" } + resolution: {integrity: sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==} + engines: {node: '>=8.9'} hasBin: true dependencies: - "@istanbuljs/load-nyc-config": 1.1.0 - "@istanbuljs/schema": 0.1.3 + '@istanbuljs/load-nyc-config': 1.1.0 + '@istanbuljs/schema': 0.1.3 caching-transform: 4.0.0 convert-source-map: 1.8.0 decamelize: 1.2.0 @@ -5890,45 +4460,30 @@ packages: dev: true /oauth-sign/0.9.0: - resolution: - { - integrity: sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==, - } + resolution: {integrity: sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==} dev: true /object-assign/4.1.1: - resolution: { integrity: sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=} + engines: {node: '>=0.10.0'} dev: false /object-hash/2.2.0: - resolution: - { - integrity: sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==, - } - engines: { node: ">= 6" } + resolution: {integrity: sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==} + engines: {node: '>= 6'} dev: false /object-inspect/1.12.0: - resolution: - { - integrity: sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==, - } + resolution: {integrity: sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==} dev: true /object-keys/1.1.1: - resolution: - { - integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} /object.assign/4.1.2: - resolution: - { - integrity: sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.2 define-properties: 1.1.3 @@ -5937,11 +4492,8 @@ packages: dev: true /object.values/1.1.5: - resolution: - { - integrity: sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.2 define-properties: 1.1.3 @@ -5949,48 +4501,36 @@ packages: dev: true /on-finished/2.3.0: - resolution: { integrity: sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= } - engines: { node: ">= 0.8" } + resolution: {integrity: sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=} + engines: {node: '>= 0.8'} dependencies: ee-first: 1.1.1 dev: false /on-headers/1.0.2: - resolution: - { - integrity: sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==, - } - engines: { node: ">= 0.8" } + resolution: {integrity: sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==} + engines: {node: '>= 0.8'} dev: false /once/1.4.0: - resolution: { integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E= } + resolution: {integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E=} dependencies: wrappy: 1.0.2 /one-time/1.0.0: - resolution: - { - integrity: sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==, - } + resolution: {integrity: sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==} dependencies: fn.name: 1.1.0 dev: false /opener/1.5.2: - resolution: - { - integrity: sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==, - } + resolution: {integrity: sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==} hasBin: true dev: true /optionator/0.9.1: - resolution: - { - integrity: sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==, - } - engines: { node: ">= 0.8.0" } + resolution: {integrity: sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==} + engines: {node: '>= 0.8.0'} dependencies: deep-is: 0.1.4 fast-levenshtein: 2.0.6 @@ -6001,99 +4541,75 @@ packages: dev: true /os-locale/1.4.0: - resolution: { integrity: sha1-IPnxeuKe00XoveWDsT0gCYA8FNk= } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=} + engines: {node: '>=0.10.0'} dependencies: lcid: 1.0.0 dev: false /own-or-env/1.0.2: - resolution: - { - integrity: sha512-NQ7v0fliWtK7Lkb+WdFqe6ky9XAzYmlkXthQrBbzlYbmFKoAYbDDcwmOm6q8kOuwSRXW8bdL5ORksploUJmWgw==, - } + resolution: {integrity: sha512-NQ7v0fliWtK7Lkb+WdFqe6ky9XAzYmlkXthQrBbzlYbmFKoAYbDDcwmOm6q8kOuwSRXW8bdL5ORksploUJmWgw==} dependencies: own-or: 1.0.0 dev: true /own-or/1.0.0: - resolution: { integrity: sha1-Tod/vtqaLsgAD7wLyuOWRe6L+Nw= } + resolution: {integrity: sha1-Tod/vtqaLsgAD7wLyuOWRe6L+Nw=} dev: true /p-limit/1.3.0: - resolution: - { - integrity: sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==, - } - engines: { node: ">=4" } + resolution: {integrity: sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==} + engines: {node: '>=4'} dependencies: p-try: 1.0.0 dev: true /p-limit/2.3.0: - resolution: - { - integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==, - } - engines: { node: ">=6" } + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} dependencies: p-try: 2.2.0 dev: true /p-locate/2.0.0: - resolution: { integrity: sha1-IKAQOyIqcMj9OcwuWAaA893l7EM= } - engines: { node: ">=4" } + resolution: {integrity: sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=} + engines: {node: '>=4'} dependencies: p-limit: 1.3.0 dev: true /p-locate/4.1.0: - resolution: - { - integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} dependencies: p-limit: 2.3.0 dev: true /p-map/2.1.0: - resolution: - { - integrity: sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==, - } - engines: { node: ">=6" } + resolution: {integrity: sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==} + engines: {node: '>=6'} dev: false /p-map/3.0.0: - resolution: - { - integrity: sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==} + engines: {node: '>=8'} dependencies: aggregate-error: 3.1.0 dev: true /p-try/1.0.0: - resolution: { integrity: sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M= } - engines: { node: ">=4" } + resolution: {integrity: sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=} + engines: {node: '>=4'} dev: true /p-try/2.2.0: - resolution: - { - integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==, - } - engines: { node: ">=6" } + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} dev: true /package-hash/4.0.0: - resolution: - { - integrity: sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==} + engines: {node: '>=8'} dependencies: graceful-fs: 4.2.9 hasha: 5.2.2 @@ -6102,255 +4618,174 @@ packages: dev: true /parent-module/1.0.1: - resolution: - { - integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==, - } - engines: { node: ">=6" } + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} dependencies: callsites: 3.1.0 dev: true /parseurl/1.3.3: - resolution: - { - integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==, - } - engines: { node: ">= 0.8" } + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} /path-exists/3.0.0: - resolution: { integrity: sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= } - engines: { node: ">=4" } + resolution: {integrity: sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=} + engines: {node: '>=4'} dev: true /path-exists/4.0.0: - resolution: - { - integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} dev: true /path-is-absolute/1.0.1: - resolution: { integrity: sha1-F0uSaHNVNP+8es5r9TpanhtcX18= } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha1-F0uSaHNVNP+8es5r9TpanhtcX18=} + engines: {node: '>=0.10.0'} /path-key/3.1.1: - resolution: - { - integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} dev: true /path-parse/1.0.7: - resolution: - { - integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==, - } + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} dev: true /path-to-regexp/0.1.7: - resolution: { integrity: sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= } + resolution: {integrity: sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=} dev: false /path-type/4.0.0: - resolution: - { - integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} dev: true /performance-now/2.1.0: - resolution: { integrity: sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= } + resolution: {integrity: sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=} dev: true /picocolors/1.0.0: - resolution: - { - integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==, - } + resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} dev: true /picomatch/2.3.1: - resolution: - { - integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==, - } - engines: { node: ">=8.6" } + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} dev: true /pkg-dir/4.2.0: - resolution: - { - integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} + engines: {node: '>=8'} dependencies: find-up: 4.1.0 dev: true /prelude-ls/1.2.1: - resolution: - { - integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==, - } - engines: { node: ">= 0.8.0" } + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} dev: true /prettier-linter-helpers/1.0.0: - resolution: - { - integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==, - } - engines: { node: ">=6.0.0" } + resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} + engines: {node: '>=6.0.0'} dependencies: fast-diff: 1.2.0 dev: true /prettier/2.5.1: - resolution: - { - integrity: sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==, - } - engines: { node: ">=10.13.0" } + resolution: {integrity: sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==} + engines: {node: '>=10.13.0'} hasBin: true dev: true /process-nextick-args/1.0.7: - resolution: { integrity: sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M= } + resolution: {integrity: sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=} dev: false /process-nextick-args/2.0.1: - resolution: - { - integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==, - } + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} dev: false /process-on-spawn/1.0.0: - resolution: - { - integrity: sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==} + engines: {node: '>=8'} dependencies: fromentries: 1.3.2 dev: true /proxy-addr/2.0.7: - resolution: - { - integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==, - } - engines: { node: ">= 0.10" } + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} dependencies: forwarded: 0.2.0 ipaddr.js: 1.9.1 dev: false /prudence/0.9.7: - resolution: - { - integrity: sha512-7214GHTBThBI/PGlKWXUSJ++ca0ld54VpF4k98U1EDAFXai5mnvWbb4speVOnWkilqfN25x8biWUAj4gHNu5Og==, - } + resolution: {integrity: sha512-7214GHTBThBI/PGlKWXUSJ++ca0ld54VpF4k98U1EDAFXai5mnvWbb4speVOnWkilqfN25x8biWUAj4gHNu5Og==} dev: false /psl/1.8.0: - resolution: - { - integrity: sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==, - } + resolution: {integrity: sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==} dev: true /pump/3.0.0: - resolution: - { - integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==, - } + resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==} dependencies: end-of-stream: 1.4.4 once: 1.4.0 dev: false /punycode/2.1.1: - resolution: - { - integrity: sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==, - } - engines: { node: ">=6" } + resolution: {integrity: sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==} + engines: {node: '>=6'} dev: true /qs/6.10.3: - resolution: - { - integrity: sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==, - } - engines: { node: ">=0.6" } + resolution: {integrity: sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==} + engines: {node: '>=0.6'} dependencies: side-channel: 1.0.4 dev: true /qs/6.5.3: - resolution: - { - integrity: sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==, - } - engines: { node: ">=0.6" } + resolution: {integrity: sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==} + engines: {node: '>=0.6'} dev: true /qs/6.9.3: - resolution: - { - integrity: sha512-EbZYNarm6138UKKq46tdx08Yo/q9ZhFoAXAI1meAFd2GtbRDhbZY2WQSICskT0c5q99aFzLG1D4nvTk9tqfXIw==, - } - engines: { node: ">=0.6" } + resolution: {integrity: sha512-EbZYNarm6138UKKq46tdx08Yo/q9ZhFoAXAI1meAFd2GtbRDhbZY2WQSICskT0c5q99aFzLG1D4nvTk9tqfXIw==} + engines: {node: '>=0.6'} dev: true /qs/6.9.6: - resolution: - { - integrity: sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ==, - } - engines: { node: ">=0.6" } + resolution: {integrity: sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ==} + engines: {node: '>=0.6'} dev: false /queue-microtask/1.2.3: - resolution: - { - integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==, - } + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} dev: true /random-bytes/1.0.0: - resolution: { integrity: sha1-T2ih3Arli9P7lYSMMDJNt11kNgs= } - engines: { node: ">= 0.8" } + resolution: {integrity: sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=} + engines: {node: '>= 0.8'} dev: false /range-parser/1.2.1: - resolution: - { - integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==, - } - engines: { node: ">= 0.6" } + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} /rate-limit-redis/2.1.0: - resolution: - { - integrity: sha512-6SAsTCzY0v6UCIKLOLLYqR2XzFmgdtF7jWXlSPq2FrNIZk8tZ7xwBvyGW7GFMCe5I4S9lYNdrSJ9E84rz3/CpA==, - } + resolution: {integrity: sha512-6SAsTCzY0v6UCIKLOLLYqR2XzFmgdtF7jWXlSPq2FrNIZk8tZ7xwBvyGW7GFMCe5I4S9lYNdrSJ9E84rz3/CpA==} dependencies: defaults: 1.0.3 redis: 3.1.2 dev: false /raw-body/2.4.2: - resolution: - { - integrity: sha512-RPMAFUJP19WIet/99ngh6Iv8fzAbqum4Li7AD6DtGaW2RpMB/11xDoalPiJMTbu6I3hkbMVkATvZrqb9EEqeeQ==, - } - engines: { node: ">= 0.8" } + resolution: {integrity: sha512-RPMAFUJP19WIet/99ngh6Iv8fzAbqum4Li7AD6DtGaW2RpMB/11xDoalPiJMTbu6I3hkbMVkATvZrqb9EEqeeQ==} + engines: {node: '>= 0.8'} dependencies: bytes: 3.1.1 http-errors: 1.8.1 @@ -6359,10 +4794,7 @@ packages: dev: false /rc/1.2.8: - resolution: - { - integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==, - } + resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} hasBin: true dependencies: deep-extend: 0.6.0 @@ -6372,7 +4804,7 @@ packages: dev: false /readable-stream/1.0.34: - resolution: { integrity: sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw= } + resolution: {integrity: sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=} dependencies: core-util-is: 1.0.3 inherits: 2.0.4 @@ -6381,7 +4813,7 @@ packages: dev: false /readable-stream/1.1.14: - resolution: { integrity: sha1-fPTFTvZI44EwhMY23SB54WbAgdk= } + resolution: {integrity: sha1-fPTFTvZI44EwhMY23SB54WbAgdk=} dependencies: core-util-is: 1.0.3 inherits: 2.0.4 @@ -6390,7 +4822,7 @@ packages: dev: false /readable-stream/2.1.5: - resolution: { integrity: sha1-ZvqLcg4UOLNkaB8q0aY8YYRIydA= } + resolution: {integrity: sha1-ZvqLcg4UOLNkaB8q0aY8YYRIydA=} dependencies: buffer-shims: 1.0.0 core-util-is: 1.0.3 @@ -6402,10 +4834,7 @@ packages: dev: false /readable-stream/2.3.7: - resolution: - { - integrity: sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==, - } + resolution: {integrity: sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==} dependencies: core-util-is: 1.0.3 inherits: 2.0.4 @@ -6417,51 +4846,39 @@ packages: dev: false /readable-stream/3.6.0: - resolution: - { - integrity: sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==, - } - engines: { node: ">= 6" } + resolution: {integrity: sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==} + engines: {node: '>= 6'} dependencies: inherits: 2.0.4 string_decoder: 1.3.0 util-deprecate: 1.0.2 /readdirp/3.6.0: - resolution: - { - integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==, - } - engines: { node: ">=8.10.0" } + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} dependencies: picomatch: 2.3.1 dev: true /redis-commands/1.7.0: - resolution: - { - integrity: sha512-nJWqw3bTFy21hX/CPKHth6sfhZbdiHP6bTawSgQBlKOVRG7EZkfHbbHwQJnrE4vsQf0CMNE+3gJ4Fmm16vdVlQ==, - } + resolution: {integrity: sha512-nJWqw3bTFy21hX/CPKHth6sfhZbdiHP6bTawSgQBlKOVRG7EZkfHbbHwQJnrE4vsQf0CMNE+3gJ4Fmm16vdVlQ==} dev: false /redis-errors/1.2.0: - resolution: { integrity: sha1-62LSrbFeTq9GEMBK/hUpOEJQq60= } - engines: { node: ">=4" } + resolution: {integrity: sha1-62LSrbFeTq9GEMBK/hUpOEJQq60=} + engines: {node: '>=4'} dev: false /redis-parser/3.0.0: - resolution: { integrity: sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ= } - engines: { node: ">=4" } + resolution: {integrity: sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ=} + engines: {node: '>=4'} dependencies: redis-errors: 1.2.0 dev: false /redis/3.1.2: - resolution: - { - integrity: sha512-grn5KoZLr/qrRQVwoSkmzdbw6pwF+/rwODtrOr6vuBRiR/f3rjSTGupbF90Zpqm2oenix8Do6RV7pYEkGwlKkw==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-grn5KoZLr/qrRQVwoSkmzdbw6pwF+/rwODtrOr6vuBRiR/f3rjSTGupbF90Zpqm2oenix8Do6RV7pYEkGwlKkw==} + engines: {node: '>=10'} dependencies: denque: 1.5.1 redis-commands: 1.7.0 @@ -6470,30 +4887,24 @@ packages: dev: false /regexpp/3.2.0: - resolution: - { - integrity: sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==} + engines: {node: '>=8'} dev: true /reinterval/1.1.0: - resolution: { integrity: sha1-M2Hs+jymwYKDOA3Qu5VG85D17Oc= } + resolution: {integrity: sha1-M2Hs+jymwYKDOA3Qu5VG85D17Oc=} dev: false /release-zalgo/1.0.0: - resolution: { integrity: sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA= } - engines: { node: ">=4" } + resolution: {integrity: sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA=} + engines: {node: '>=4'} dependencies: es6-error: 4.1.1 dev: true /request/2.88.2: - resolution: - { - integrity: sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==, - } - engines: { node: ">= 6" } + resolution: {integrity: sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==} + engines: {node: '>= 6'} deprecated: request has been deprecated, see https://github.com/request/request/issues/3142 dependencies: aws-sign2: 0.7.0 @@ -6519,53 +4930,38 @@ packages: dev: true /require-directory/2.1.1: - resolution: { integrity: sha1-jGStX9MNqxyXbiNE/+f3kqam30I= } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha1-jGStX9MNqxyXbiNE/+f3kqam30I=} + engines: {node: '>=0.10.0'} dev: true /require-main-filename/2.0.0: - resolution: - { - integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==, - } + resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} dev: true /require_optional/1.0.1: - resolution: - { - integrity: sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==, - } + resolution: {integrity: sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==} dependencies: resolve-from: 2.0.0 semver: 5.7.1 dev: false /resolve-from/2.0.0: - resolution: { integrity: sha1-lICrIOlP+h2egKgEx+oUdhGWa1c= } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=} + engines: {node: '>=0.10.0'} dev: false /resolve-from/4.0.0: - resolution: - { - integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==, - } - engines: { node: ">=4" } + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} dev: true /resolve-from/5.0.0: - resolution: - { - integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} dev: true /resolve/1.22.0: - resolution: - { - integrity: sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==, - } + resolution: {integrity: sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==} hasBin: true dependencies: is-core-module: 2.8.1 @@ -6574,98 +4970,62 @@ packages: dev: true /reusify/1.0.4: - resolution: - { - integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==, - } - engines: { iojs: ">=1.0.0", node: ">=0.10.0" } + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} dev: true /rfdc/1.3.0: - resolution: - { - integrity: sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==, - } + resolution: {integrity: sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==} dev: false /rg-stats/0.2.0: - resolution: - { - integrity: sha512-d+IFYlryQMZjMYVJgL0vIZCdUpkYhUC9vzfMHOs/HNEeM5jjlVLDyuiru4UJlc67XoqGgby2Mp6IkcFoRXz4Gw==, - } + resolution: {integrity: sha512-d+IFYlryQMZjMYVJgL0vIZCdUpkYhUC9vzfMHOs/HNEeM5jjlVLDyuiru4UJlc67XoqGgby2Mp6IkcFoRXz4Gw==} dev: false /rimraf/2.4.5: - resolution: { integrity: sha1-7nEM5dk6j9uFb7Xqj/Di11k0sto= } + resolution: {integrity: sha1-7nEM5dk6j9uFb7Xqj/Di11k0sto=} hasBin: true dependencies: glob: 6.0.4 dev: false /rimraf/3.0.2: - resolution: - { - integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==, - } + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} hasBin: true dependencies: glob: 7.2.0 /run-parallel/1.2.0: - resolution: - { - integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==, - } + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} dependencies: queue-microtask: 1.2.3 dev: true /safe-buffer/5.1.2: - resolution: - { - integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==, - } + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} /safe-buffer/5.2.1: - resolution: - { - integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==, - } + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} /safe-json-stringify/1.2.0: - resolution: - { - integrity: sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg==, - } + resolution: {integrity: sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg==} dev: false /safe-stable-stringify/1.1.1: - resolution: - { - integrity: sha512-ERq4hUjKDbJfE4+XtZLFPCDi8Vb1JqaxAPTxWFLBx8XcAlf9Bda/ZJdVezs/NAfsMQScyIlUMx+Yeu7P7rx5jw==, - } + resolution: {integrity: sha512-ERq4hUjKDbJfE4+XtZLFPCDi8Vb1JqaxAPTxWFLBx8XcAlf9Bda/ZJdVezs/NAfsMQScyIlUMx+Yeu7P7rx5jw==} dev: false /safe-stable-stringify/2.3.1: - resolution: - { - integrity: sha512-kYBSfT+troD9cDA85VDnHZ1rpHC50O0g1e6WlGHVCz/g+JS+9WKLj+XwFYyR8UbrZN8ll9HUpDAAddY58MGisg==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-kYBSfT+troD9cDA85VDnHZ1rpHC50O0g1e6WlGHVCz/g+JS+9WKLj+XwFYyR8UbrZN8ll9HUpDAAddY58MGisg==} + engines: {node: '>=10'} dev: false /safer-buffer/2.1.2: - resolution: - { - integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==, - } + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} /saslprep/1.0.3: - resolution: - { - integrity: sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==, - } - engines: { node: ">=6" } + resolution: {integrity: sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==} + engines: {node: '>=6'} requiresBuild: true dependencies: sparse-bitfield: 3.0.3 @@ -6673,37 +5033,25 @@ packages: optional: true /semver/5.7.1: - resolution: - { - integrity: sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==, - } + resolution: {integrity: sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==} hasBin: true dev: false /semver/6.3.0: - resolution: - { - integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==, - } + resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==} hasBin: true /semver/7.3.5: - resolution: - { - integrity: sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==} + engines: {node: '>=10'} hasBin: true dependencies: lru-cache: 6.0.0 dev: true /send/0.17.2: - resolution: - { - integrity: sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==, - } - engines: { node: ">= 0.8.0" } + resolution: {integrity: sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==} + engines: {node: '>= 0.8.0'} dependencies: debug: 2.6.9 depd: 1.1.2 @@ -6721,18 +5069,12 @@ packages: dev: false /seq-logging/1.1.1: - resolution: - { - integrity: sha512-9miWILWu22dKNCkZi2UePAnZeQEzaYsQRKbAi5eSBUbuOyyeYPyYO1bEvIvZAZRjltEwY1S0yz94hc0/f+niDg==, - } + resolution: {integrity: sha512-9miWILWu22dKNCkZi2UePAnZeQEzaYsQRKbAi5eSBUbuOyyeYPyYO1bEvIvZAZRjltEwY1S0yz94hc0/f+niDg==} dev: false /serve-static/1.14.2: - resolution: - { - integrity: sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==, - } - engines: { node: ">= 0.8.0" } + resolution: {integrity: sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==} + engines: {node: '>= 0.8.0'} dependencies: encodeurl: 1.0.2 escape-html: 1.0.3 @@ -6741,43 +5083,31 @@ packages: dev: false /set-blocking/2.0.0: - resolution: { integrity: sha1-BF+XgtARrppoA93TgrJDkrPYkPc= } + resolution: {integrity: sha1-BF+XgtARrppoA93TgrJDkrPYkPc=} dev: true /setimmediate/1.0.5: - resolution: { integrity: sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU= } + resolution: {integrity: sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=} dev: false /setprototypeof/1.2.0: - resolution: - { - integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==, - } + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} dev: false /shebang-command/2.0.0: - resolution: - { - integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} dependencies: shebang-regex: 3.0.0 dev: true /shebang-regex/3.0.0: - resolution: - { - integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} dev: true /side-channel/1.0.4: - resolution: - { - integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==, - } + resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} dependencies: call-bind: 1.0.2 get-intrinsic: 1.1.1 @@ -6785,69 +5115,51 @@ packages: dev: true /siginfo/2.0.0: - resolution: - { - integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==, - } + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} dev: true /signal-exit/3.0.7: - resolution: - { - integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==, - } + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} dev: true /simple-swizzle/0.2.2: - resolution: { integrity: sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo= } + resolution: {integrity: sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=} dependencies: is-arrayish: 0.3.2 dev: false /slash/3.0.0: - resolution: - { - integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} dev: true /source-map-support/0.5.21: - resolution: - { - integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==, - } + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} dependencies: buffer-from: 1.1.2 source-map: 0.6.1 dev: true /source-map/0.5.7: - resolution: { integrity: sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=} + engines: {node: '>=0.10.0'} dev: true /source-map/0.6.1: - resolution: - { - integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==, - } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} dev: true /sparse-bitfield/3.0.3: - resolution: { integrity: sha1-/0rm5oZWBWuks+eSqzM004JzyhE= } + resolution: {integrity: sha1-/0rm5oZWBWuks+eSqzM004JzyhE=} dependencies: memory-pager: 1.5.0 dev: false optional: true /spawn-wrap/2.0.0: - resolution: - { - integrity: sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==} + engines: {node: '>=8'} dependencies: foreground-child: 2.0.0 is-windows: 1.0.2 @@ -6858,28 +5170,22 @@ packages: dev: true /split2/3.2.2: - resolution: - { - integrity: sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==, - } + resolution: {integrity: sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==} dependencies: readable-stream: 3.6.0 dev: false /splitargs/0.0.7: - resolution: { integrity: sha1-/p965lc3GzOxDLgNoUPPgknPazs= } + resolution: {integrity: sha1-/p965lc3GzOxDLgNoUPPgknPazs=} dev: false /sprintf-js/1.0.3: - resolution: { integrity: sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= } + resolution: {integrity: sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=} dev: true /sshpk/1.17.0: - resolution: - { - integrity: sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==, - } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==} + engines: {node: '>=0.10.0'} hasBin: true dependencies: asn1: 0.2.6 @@ -6894,50 +5200,41 @@ packages: dev: true /stack-trace/0.0.10: - resolution: { integrity: sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA= } + resolution: {integrity: sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=} dev: false /stack-utils/2.0.5: - resolution: - { - integrity: sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==} + engines: {node: '>=10'} dependencies: escape-string-regexp: 2.0.0 dev: true /stackback/0.0.2: - resolution: { integrity: sha1-Gsig2Ug4SNFpXkGLbQMaPDzmjjs= } + resolution: {integrity: sha1-Gsig2Ug4SNFpXkGLbQMaPDzmjjs=} dev: true /standard-as-callback/2.1.0: - resolution: - { - integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==, - } + resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==} dev: false /statuses/1.5.0: - resolution: { integrity: sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= } - engines: { node: ">= 0.6" } + resolution: {integrity: sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=} + engines: {node: '>= 0.6'} dev: false /stream-shift/1.0.1: - resolution: - { - integrity: sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==, - } + resolution: {integrity: sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==} dev: false /streamsearch/0.1.2: - resolution: { integrity: sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo= } - engines: { node: ">=0.8.0" } + resolution: {integrity: sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=} + engines: {node: '>=0.8.0'} dev: false /string-width/1.0.2: - resolution: { integrity: sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=} + engines: {node: '>=0.10.0'} dependencies: code-point-at: 1.1.0 is-fullwidth-code-point: 1.0.0 @@ -6945,11 +5242,8 @@ packages: dev: false /string-width/4.2.3: - resolution: - { - integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} dependencies: emoji-regex: 8.0.0 is-fullwidth-code-point: 3.0.0 @@ -6957,100 +5251,73 @@ packages: dev: true /string.prototype.trimend/1.0.4: - resolution: - { - integrity: sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==, - } + resolution: {integrity: sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==} dependencies: call-bind: 1.0.2 define-properties: 1.1.3 dev: true /string.prototype.trimstart/1.0.4: - resolution: - { - integrity: sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==, - } + resolution: {integrity: sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==} dependencies: call-bind: 1.0.2 define-properties: 1.1.3 dev: true /string_decoder/0.10.31: - resolution: { integrity: sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ= } + resolution: {integrity: sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=} dev: false /string_decoder/1.1.1: - resolution: - { - integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==, - } + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} dependencies: safe-buffer: 5.1.2 dev: false /string_decoder/1.3.0: - resolution: - { - integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==, - } + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} dependencies: safe-buffer: 5.2.1 /strip-ansi/3.0.1: - resolution: { integrity: sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=} + engines: {node: '>=0.10.0'} dependencies: ansi-regex: 2.1.1 /strip-ansi/6.0.1: - resolution: - { - integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} dependencies: ansi-regex: 5.0.1 dev: true /strip-bom/3.0.0: - resolution: { integrity: sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= } - engines: { node: ">=4" } + resolution: {integrity: sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=} + engines: {node: '>=4'} /strip-bom/4.0.0: - resolution: - { - integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} + engines: {node: '>=8'} dev: true /strip-json-comments/2.0.1: - resolution: { integrity: sha1-PFMZQukIwml8DsNEhYwobHygpgo= } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha1-PFMZQukIwml8DsNEhYwobHygpgo=} + engines: {node: '>=0.10.0'} dev: false /strip-json-comments/3.1.1: - resolution: - { - integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} dev: true /strnum/1.0.5: - resolution: - { - integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==, - } + resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==} dev: false /superagent/7.1.1: - resolution: - { - integrity: sha512-CQ2weSS6M+doIwwYFoMatklhRbx6sVNdB99OEJ5czcP3cng76Ljqus694knFWgOj3RkrtxZqIgpe6vhe0J7QWQ==, - } - engines: { node: ">=6.4.0 <13 || >=14" } + resolution: {integrity: sha512-CQ2weSS6M+doIwwYFoMatklhRbx6sVNdB99OEJ5czcP3cng76Ljqus694knFWgOj3RkrtxZqIgpe6vhe0J7QWQ==} + engines: {node: '>=6.4.0 <13 || >=14'} dependencies: component-emitter: 1.3.0 cookiejar: 2.1.3 @@ -7068,11 +5335,8 @@ packages: dev: true /supertest/6.2.2: - resolution: - { - integrity: sha512-wCw9WhAtKJsBvh07RaS+/By91NNE0Wh0DN19/hWPlBOU8tAfOtbZoVSV4xXeoKoxgPx0rx2y+y+8660XtE7jzg==, - } - engines: { node: ">=6.0.0" } + resolution: {integrity: sha512-wCw9WhAtKJsBvh07RaS+/By91NNE0Wh0DN19/hWPlBOU8tAfOtbZoVSV4xXeoKoxgPx0rx2y+y+8660XtE7jzg==} + engines: {node: '>=6.0.0'} dependencies: methods: 1.1.2 superagent: 7.1.1 @@ -7081,48 +5345,33 @@ packages: dev: true /supports-color/5.5.0: - resolution: - { - integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==, - } - engines: { node: ">=4" } + resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + engines: {node: '>=4'} dependencies: has-flag: 3.0.0 dev: true /supports-color/7.2.0: - resolution: - { - integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} dependencies: has-flag: 4.0.0 dev: true /supports-preserve-symlinks-flag/1.0.0: - resolution: - { - integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} dev: true - /tachi-common/0.7.3: - resolution: - { - integrity: sha512-Lizfa/dnZDbN8oeDr9xGDWib9eUiikf7AGdRumZfUFCa/mKg2ohpejSDpHr2bZs/ItA3tEUa8vaUanX3Sm7VJg==, - } + /tachi-common/0.7.5: + resolution: {integrity: sha512-F++XGcYlIQqXbwl82qJWYwzvv41ykmXed6j0aMauW2TyOJbuSM04Sw8j0TONXlU9X1QsIWLahG8I2p0wLrT0Qw==} dependencies: - "@types/mongodb": 3.6.18 + '@types/mongodb': 3.6.18 dev: false /tap-mocha-reporter/5.0.1: - resolution: - { - integrity: sha512-1knFWOwd4khx/7uSEnUeaP9IPW3w+sqTgJMhrwah6t46nZ8P25atOKAjSvVDsT67lOPu0nfdOqUwoyKn+3E5pA==, - } - engines: { node: ">= 8" } + resolution: {integrity: sha512-1knFWOwd4khx/7uSEnUeaP9IPW3w+sqTgJMhrwah6t46nZ8P25atOKAjSvVDsT67lOPu0nfdOqUwoyKn+3E5pA==} + engines: {node: '>= 8'} hasBin: true dependencies: color-support: 1.1.3 @@ -7138,11 +5387,8 @@ packages: dev: true /tap-parser/10.1.0: - resolution: - { - integrity: sha512-FujQeciDaOiOvaIVGS1Rpb0v4R6XkOjvWCWowlz5oKuhPkEJ8U6pxgqt38xuzYhPt8dWEnfHn2jqpZdJEkW7pA==, - } - engines: { node: ">= 8" } + resolution: {integrity: sha512-FujQeciDaOiOvaIVGS1Rpb0v4R6XkOjvWCWowlz5oKuhPkEJ8U6pxgqt38xuzYhPt8dWEnfHn2jqpZdJEkW7pA==} + engines: {node: '>= 8'} hasBin: true dependencies: events-to-array: 1.1.2 @@ -7151,25 +5397,19 @@ packages: dev: true /tap-yaml/1.0.0: - resolution: - { - integrity: sha512-Rxbx4EnrWkYk0/ztcm5u3/VznbyFJpyXO12dDBHKWiDVxy7O2Qw6MRrwO5H6Ww0U5YhRY/4C/VzWmFPhBQc4qQ==, - } + resolution: {integrity: sha512-Rxbx4EnrWkYk0/ztcm5u3/VznbyFJpyXO12dDBHKWiDVxy7O2Qw6MRrwO5H6Ww0U5YhRY/4C/VzWmFPhBQc4qQ==} dependencies: yaml: 1.10.2 dev: true /tap/15.1.6_ts-node@10.4.0+typescript@4.5.5: - resolution: - { - integrity: sha512-TN7xH6Q2tUPTd6qwmkhuFJcx1vUR8e4dDUpBKc61G0krOzne7Ia6aKIFb8O/0kVazachSSuVME1V8nQ2xwWL8w==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-TN7xH6Q2tUPTd6qwmkhuFJcx1vUR8e4dDUpBKc61G0krOzne7Ia6aKIFb8O/0kVazachSSuVME1V8nQ2xwWL8w==} + engines: {node: '>=10'} hasBin: true peerDependencies: - flow-remove-types: ">=2.112.0" - ts-node: ">=8.5.2" - typescript: ">=3.7.2" + flow-remove-types: '>=2.112.0' + ts-node: '>=8.5.2' + typescript: '>=3.7.2' peerDependenciesMeta: flow-remove-types: optional: true @@ -7208,16 +5448,13 @@ packages: bundledDependencies: - ink - treport - - "@types/react" - - "@isaacs/import-jsx" + - '@types/react' + - '@isaacs/import-jsx' - react /tar/4.4.19: - resolution: - { - integrity: sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA==, - } - engines: { node: ">=4.5" } + resolution: {integrity: sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA==} + engines: {node: '>=4.5'} dependencies: chownr: 1.1.4 fs-minipass: 1.2.7 @@ -7229,11 +5466,8 @@ packages: dev: false /tar/6.1.11: - resolution: - { - integrity: sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==, - } - engines: { node: ">= 10" } + resolution: {integrity: sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==} + engines: {node: '>= 10'} dependencies: chownr: 2.0.0 fs-minipass: 2.1.0 @@ -7244,114 +5478,90 @@ packages: dev: false /tcompare/5.0.7: - resolution: - { - integrity: sha512-d9iddt6YYGgyxJw5bjsN7UJUO1kGOtjSlNy/4PoGYAjQS5pAT/hzIoLf1bZCw+uUxRmZJh7Yy1aA7xKVRT9B4w==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-d9iddt6YYGgyxJw5bjsN7UJUO1kGOtjSlNy/4PoGYAjQS5pAT/hzIoLf1bZCw+uUxRmZJh7Yy1aA7xKVRT9B4w==} + engines: {node: '>=10'} dependencies: diff: 4.0.2 dev: true /test-exclude/6.0.0: - resolution: - { - integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} + engines: {node: '>=8'} dependencies: - "@istanbuljs/schema": 0.1.3 + '@istanbuljs/schema': 0.1.3 glob: 7.2.0 minimatch: 3.0.5 dev: true /text-hex/1.0.0: - resolution: - { - integrity: sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==, - } + resolution: {integrity: sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==} dev: false /text-table/0.2.0: - resolution: { integrity: sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= } + resolution: {integrity: sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=} dev: true /to-fast-properties/2.0.0: - resolution: { integrity: sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= } - engines: { node: ">=4" } + resolution: {integrity: sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=} + engines: {node: '>=4'} dev: true /to-regex-range/5.0.1: - resolution: - { - integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==, - } - engines: { node: ">=8.0" } + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} dependencies: is-number: 7.0.0 dev: true /toidentifier/1.0.1: - resolution: - { - integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==, - } - engines: { node: ">=0.6" } + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} dev: false /tough-cookie/2.5.0: - resolution: - { - integrity: sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==, - } - engines: { node: ">=0.8" } + resolution: {integrity: sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==} + engines: {node: '>=0.8'} dependencies: psl: 1.8.0 punycode: 2.1.1 dev: true /tr46/0.0.3: - resolution: { integrity: sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= } + resolution: {integrity: sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=} dev: false /traverse/0.3.9: - resolution: { integrity: sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk= } + resolution: {integrity: sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk=} dev: false /triple-beam/1.3.0: - resolution: - { - integrity: sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==, - } + resolution: {integrity: sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==} dev: false /trivial-deferred/1.0.1: - resolution: { integrity: sha1-N21NKdlR1jaKb3oK6FwvTV4GWPM= } + resolution: {integrity: sha1-N21NKdlR1jaKb3oK6FwvTV4GWPM=} dev: true /ts-node/10.4.0_2615db9039ce432b4abf2fc39ef336ec: - resolution: - { - integrity: sha512-g0FlPvvCXSIO1JDF6S232P5jPYqBkRL9qly81ZgAOSU7rwI0stphCgd2kLiCrU9DjQCrJMWEqcNSjQL02s6d8A==, - } + resolution: {integrity: sha512-g0FlPvvCXSIO1JDF6S232P5jPYqBkRL9qly81ZgAOSU7rwI0stphCgd2kLiCrU9DjQCrJMWEqcNSjQL02s6d8A==} hasBin: true peerDependencies: - "@swc/core": ">=1.2.50" - "@swc/wasm": ">=1.2.50" - "@types/node": "*" - typescript: ">=2.7" + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' peerDependenciesMeta: - "@swc/core": + '@swc/core': optional: true - "@swc/wasm": + '@swc/wasm': optional: true dependencies: - "@cspotcode/source-map-support": 0.7.0 - "@tsconfig/node10": 1.0.8 - "@tsconfig/node12": 1.0.9 - "@tsconfig/node14": 1.0.1 - "@tsconfig/node16": 1.0.2 - "@types/node": 16.11.7 + '@cspotcode/source-map-support': 0.7.0 + '@tsconfig/node10': 1.0.8 + '@tsconfig/node12': 1.0.9 + '@tsconfig/node14': 1.0.1 + '@tsconfig/node16': 1.0.2 + '@types/node': 16.11.7 acorn: 8.7.0 acorn-walk: 8.2.0 arg: 4.1.3 @@ -7363,125 +5573,89 @@ packages: dev: false /tsconfig-paths/3.12.0: - resolution: - { - integrity: sha512-e5adrnOYT6zqVnWqZu7i/BQ3BnhzvGbjEjejFXO20lKIKpwTaupkCPgEfv4GZK1IBciJUEhYs3J3p75FdaTFVg==, - } + resolution: {integrity: sha512-e5adrnOYT6zqVnWqZu7i/BQ3BnhzvGbjEjejFXO20lKIKpwTaupkCPgEfv4GZK1IBciJUEhYs3J3p75FdaTFVg==} dependencies: - "@types/json5": 0.0.29 + '@types/json5': 0.0.29 json5: 1.0.1 minimist: 1.2.5 strip-bom: 3.0.0 /tslib/1.14.1: - resolution: - { - integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==, - } + resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} /tslib/2.3.1: - resolution: - { - integrity: sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==, - } + resolution: {integrity: sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==} dev: false /tsutils/3.21.0_typescript@4.5.5: - resolution: - { - integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==, - } - engines: { node: ">= 6" } + resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} + engines: {node: '>= 6'} peerDependencies: - typescript: ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' dependencies: tslib: 1.14.1 typescript: 4.5.5 dev: true /tunnel-agent/0.6.0: - resolution: { integrity: sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= } + resolution: {integrity: sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=} dependencies: safe-buffer: 5.2.1 dev: true /tweetnacl/0.14.5: - resolution: { integrity: sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= } + resolution: {integrity: sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=} dev: true /type-check/0.4.0: - resolution: - { - integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==, - } - engines: { node: ">= 0.8.0" } + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} dependencies: prelude-ls: 1.2.1 dev: true /type-fest/0.20.2: - resolution: - { - integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} + engines: {node: '>=10'} dev: true /type-fest/0.8.1: - resolution: - { - integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} + engines: {node: '>=8'} dev: true /type-is/1.6.18: - resolution: - { - integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==, - } - engines: { node: ">= 0.6" } + resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} + engines: {node: '>= 0.6'} dependencies: media-typer: 0.3.0 mime-types: 2.1.34 /typedarray-to-buffer/3.1.5: - resolution: - { - integrity: sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==, - } + resolution: {integrity: sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==} dependencies: is-typedarray: 1.0.0 dev: true /typedarray/0.0.6: - resolution: { integrity: sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= } + resolution: {integrity: sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=} dev: false /typescript/4.5.5: - resolution: - { - integrity: sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==, - } - engines: { node: ">=4.2.0" } + resolution: {integrity: sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==} + engines: {node: '>=4.2.0'} hasBin: true dev: false /uid-safe/2.1.5: - resolution: - { - integrity: sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==, - } - engines: { node: ">= 0.8" } + resolution: {integrity: sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==} + engines: {node: '>= 0.8'} dependencies: random-bytes: 1.0.0 dev: false /unbox-primitive/1.0.1: - resolution: - { - integrity: sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==, - } + resolution: {integrity: sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==} dependencies: function-bind: 1.1.1 has-bigints: 1.0.1 @@ -7490,33 +5664,24 @@ packages: dev: true /unicode-length/2.0.2: - resolution: - { - integrity: sha512-Ph/j1VbS3/r77nhoY2WU0GWGjVYOHL3xpKp0y/Eq2e5r0mT/6b649vm7KFO6RdAdrZkYLdxphYVgvODxPB+Ebg==, - } + resolution: {integrity: sha512-Ph/j1VbS3/r77nhoY2WU0GWGjVYOHL3xpKp0y/Eq2e5r0mT/6b649vm7KFO6RdAdrZkYLdxphYVgvODxPB+Ebg==} dependencies: punycode: 2.1.1 strip-ansi: 3.0.1 dev: true /universalify/0.1.2: - resolution: - { - integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==, - } - engines: { node: ">= 4.0.0" } + resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} + engines: {node: '>= 4.0.0'} dev: false /unpipe/1.0.0: - resolution: { integrity: sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= } - engines: { node: ">= 0.8" } + resolution: {integrity: sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=} + engines: {node: '>= 0.8'} dev: false /unzipper/0.8.14: - resolution: - { - integrity: sha512-8rFtE7EP5ssOwGpN2dt1Q4njl0N1hUXJ7sSPz0leU2hRdq6+pra57z4YPBlVqm40vcgv6ooKZEAx48fMTv9x4w==, - } + resolution: {integrity: sha512-8rFtE7EP5ssOwGpN2dt1Q4njl0N1hUXJ7sSPz0leU2hRdq6+pra57z4YPBlVqm40vcgv6ooKZEAx48fMTv9x4w==} dependencies: big-integer: 1.6.51 binary: 0.3.0 @@ -7530,58 +5695,46 @@ packages: dev: false /uri-js/4.4.1: - resolution: - { - integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==, - } + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} dependencies: punycode: 2.1.1 dev: true /url-join/0.0.1: - resolution: { integrity: sha1-HbSK1CLTQCRpqH99l73r/k+x48g= } + resolution: {integrity: sha1-HbSK1CLTQCRpqH99l73r/k+x48g=} dev: false /util-deprecate/1.0.2: - resolution: { integrity: sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= } + resolution: {integrity: sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=} /utils-merge/1.0.1: - resolution: { integrity: sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= } - engines: { node: ">= 0.4.0" } + resolution: {integrity: sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=} + engines: {node: '>= 0.4.0'} dev: false /uuid/3.4.0: - resolution: - { - integrity: sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==, - } + resolution: {integrity: sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==} deprecated: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details. hasBin: true dev: true /uuid/8.3.2: - resolution: - { - integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==, - } + resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} hasBin: true dev: false /v8-compile-cache/2.3.0: - resolution: - { - integrity: sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==, - } + resolution: {integrity: sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==} dev: true /vary/1.1.2: - resolution: { integrity: sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= } - engines: { node: ">= 0.8" } + resolution: {integrity: sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=} + engines: {node: '>= 0.8'} dev: false /verror/1.10.0: - resolution: { integrity: sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA= } - engines: { "0": node >=0.6.0 } + resolution: {integrity: sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=} + engines: {'0': node >=0.6.0} dependencies: assert-plus: 1.0.0 core-util-is: 1.0.2 @@ -7589,21 +5742,18 @@ packages: dev: true /webidl-conversions/3.0.1: - resolution: { integrity: sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE= } + resolution: {integrity: sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=} dev: false /whatwg-url/5.0.0: - resolution: { integrity: sha1-lmRU6HZUYuN2RNNib2dCzotwll0= } + resolution: {integrity: sha1-lmRU6HZUYuN2RNNib2dCzotwll0=} dependencies: tr46: 0.0.3 webidl-conversions: 3.0.1 dev: false /which-boxed-primitive/1.0.2: - resolution: - { - integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==, - } + resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} dependencies: is-bigint: 1.0.4 is-boolean-object: 1.1.2 @@ -7613,36 +5763,27 @@ packages: dev: true /which-module/2.0.0: - resolution: { integrity: sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= } + resolution: {integrity: sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=} dev: true /which/1.3.1: - resolution: - { - integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==, - } + resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} hasBin: true dependencies: isexe: 2.0.0 dev: false /which/2.0.2: - resolution: - { - integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==, - } - engines: { node: ">= 8" } + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} hasBin: true dependencies: isexe: 2.0.0 dev: true /why-is-node-running/2.2.1: - resolution: - { - integrity: sha512-8lT29hvpJh5tdVQfyUKB1FWuRuqms2ucPD+mt64ofv48j0CgMFhkQZxlWcEefNfr/8pJNqbH/gTB8z7jkyngUA==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-8lT29hvpJh5tdVQfyUKB1FWuRuqms2ucPD+mt64ofv48j0CgMFhkQZxlWcEefNfr/8pJNqbH/gTB8z7jkyngUA==} + engines: {node: '>=8'} hasBin: true dependencies: siginfo: 2.0.0 @@ -7650,17 +5791,14 @@ packages: dev: true /window-size/0.1.4: - resolution: { integrity: sha1-+OGqHuWlPsW/FR/6CXQqatdpeHY= } - engines: { node: ">= 0.10.0" } + resolution: {integrity: sha1-+OGqHuWlPsW/FR/6CXQqatdpeHY=} + engines: {node: '>= 0.10.0'} hasBin: true dev: false /winston-daily-rotate-file/4.6.0_winston@3.5.1: - resolution: - { - integrity: sha512-mvpFb1LYmTvh/vz0dIS/aDCwEm0cvDa8D/tE4xWwdUYolD250wf+n0y1PZ2xr7fbvTLF/PQYqXtFIFrmog03Ow==, - } - engines: { node: ">=12" } + resolution: {integrity: sha512-mvpFb1LYmTvh/vz0dIS/aDCwEm0cvDa8D/tE4xWwdUYolD250wf+n0y1PZ2xr7fbvTLF/PQYqXtFIFrmog03Ow==} + engines: {node: '>=12'} peerDependencies: winston: ^3 dependencies: @@ -7672,11 +5810,8 @@ packages: dev: false /winston-transport/4.5.0: - resolution: - { - integrity: sha512-YpZzcUzBedhlTAfJg6vJDlyEai/IFMIVcaEZZyl3UXIl4gmqRpU7AE89AHLkbzLUsv0NVmw7ts+iztqKxxPW1Q==, - } - engines: { node: ">= 6.4.0" } + resolution: {integrity: sha512-YpZzcUzBedhlTAfJg6vJDlyEai/IFMIVcaEZZyl3UXIl4gmqRpU7AE89AHLkbzLUsv0NVmw7ts+iztqKxxPW1Q==} + engines: {node: '>= 6.4.0'} dependencies: logform: 2.3.2 readable-stream: 3.6.0 @@ -7684,13 +5819,10 @@ packages: dev: false /winston/3.5.1: - resolution: - { - integrity: sha512-tbRtVy+vsSSCLcZq/8nXZaOie/S2tPXPFt4be/Q3vI/WtYwm7rrwidxVw2GRa38FIXcJ1kUM6MOZ9Jmnk3F3UA==, - } - engines: { node: ">= 6.4.0" } + resolution: {integrity: sha512-tbRtVy+vsSSCLcZq/8nXZaOie/S2tPXPFt4be/Q3vI/WtYwm7rrwidxVw2GRa38FIXcJ1kUM6MOZ9Jmnk3F3UA==} + engines: {node: '>= 6.4.0'} dependencies: - "@dabh/diagnostics": 2.0.2 + '@dabh/diagnostics': 2.0.2 async: 3.2.3 is-stream: 2.0.1 logform: 2.3.2 @@ -7703,27 +5835,21 @@ packages: dev: false /word-wrap/1.2.3: - resolution: - { - integrity: sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==, - } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==} + engines: {node: '>=0.10.0'} dev: true /wrap-ansi/2.1.0: - resolution: { integrity: sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU= } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=} + engines: {node: '>=0.10.0'} dependencies: string-width: 1.0.2 strip-ansi: 3.0.1 dev: false /wrap-ansi/6.2.0: - resolution: - { - integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} dependencies: ansi-styles: 4.3.0 string-width: 4.2.3 @@ -7731,11 +5857,8 @@ packages: dev: true /wrap-ansi/7.0.0: - resolution: - { - integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} dependencies: ansi-styles: 4.3.0 string-width: 4.2.3 @@ -7743,13 +5866,10 @@ packages: dev: true /wrappy/1.0.2: - resolution: { integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= } + resolution: {integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=} /write-file-atomic/3.0.3: - resolution: - { - integrity: sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==, - } + resolution: {integrity: sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==} dependencies: imurmurhash: 0.1.4 is-typedarray: 1.0.0 @@ -7758,11 +5878,8 @@ packages: dev: true /ws/7.5.6: - resolution: - { - integrity: sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA==, - } - engines: { node: ">=8.3.0" } + resolution: {integrity: sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA==} + engines: {node: '>=8.3.0'} peerDependencies: bufferutil: ^4.0.1 utf-8-validate: ^5.0.2 @@ -7774,69 +5891,45 @@ packages: dev: false /xtend/4.0.2: - resolution: - { - integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==, - } - engines: { node: ">=0.4" } + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} dev: false /y18n/3.2.2: - resolution: - { - integrity: sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ==, - } + resolution: {integrity: sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ==} dev: false /y18n/4.0.3: - resolution: - { - integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==, - } + resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} dev: true /yallist/3.1.1: - resolution: - { - integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==, - } + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} dev: false /yallist/4.0.0: - resolution: - { - integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==, - } + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} /yaml/1.10.2: - resolution: - { - integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==, - } - engines: { node: ">= 6" } + resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} + engines: {node: '>= 6'} dev: true /yapool/1.0.0: - resolution: { integrity: sha1-9pPymjFbUNmp2iZGp6ZkXJaYW2o= } + resolution: {integrity: sha1-9pPymjFbUNmp2iZGp6ZkXJaYW2o=} dev: true /yargs-parser/18.1.3: - resolution: - { - integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==, - } - engines: { node: ">=6" } + resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} + engines: {node: '>=6'} dependencies: camelcase: 5.3.1 decamelize: 1.2.0 dev: true /yargs/15.4.1: - resolution: - { - integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==} + engines: {node: '>=8'} dependencies: cliui: 6.0.0 decamelize: 1.2.0 @@ -7852,7 +5945,7 @@ packages: dev: true /yargs/3.32.0: - resolution: { integrity: sha1-AwiOnr+edWtpdRYR0qXvWRSCyZU= } + resolution: {integrity: sha1-AwiOnr+edWtpdRYR0qXvWRSCyZU=} dependencies: camelcase: 2.1.1 cliui: 3.2.0 @@ -7864,9 +5957,6 @@ packages: dev: false /yn/3.1.1: - resolution: - { - integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==, - } - engines: { node: ">=6" } + resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} + engines: {node: '>=6'} dev: false diff --git a/server/src/external/mongo/db.ts b/server/src/external/mongo/db.ts index 489e77e98..d27660026 100644 --- a/server/src/external/mongo/db.ts +++ b/server/src/external/mongo/db.ts @@ -152,9 +152,9 @@ const db = { folders: monkDB.get("folders"), "folder-chart-lookup": monkDB.get("folder-chart-lookup"), goals: monkDB.get("goals"), - "user-goals": monkDB.get("user-goals"), + "goal-subs": monkDB.get("goal-subs"), milestones: monkDB.get("milestones"), - "user-milestones": monkDB.get("user-milestones"), + "milestone-subs": monkDB.get("milestone-subs"), users: monkDB.get("users"), imports: monkDB.get("imports"), "import-timings": monkDB.get("import-timings"), @@ -204,8 +204,8 @@ export type StaticDatabases = | "imports" | "import-timings" | "goals" - | "user-goals" - | "user-milestones" + | "goal-subs" + | "milestone-subs" | "milestones" | "game-stats" | "game-settings" diff --git a/server/src/external/mongo/indexes.ts b/server/src/external/mongo/indexes.ts index 558ec96e8..fd93cf072 100644 --- a/server/src/external/mongo/indexes.ts +++ b/server/src/external/mongo/indexes.ts @@ -42,9 +42,9 @@ const staticIndexes: Partial> = { "game-settings": [index({ userID: 1, game: 1, playtype: 1 }, UNIQUE)], "folder-chart-lookup": [index({ chartID: 1, folderID: 1 }, UNIQUE)], goals: [index({ goalID: 1 }, UNIQUE)], - "user-goals": [index({ goalID: 1, userID: 1 }, UNIQUE), index({ goalID: 1 })], + "goal-subs": [index({ goalID: 1, userID: 1 }, UNIQUE), index({ goalID: 1 })], milestones: [index({ milestoneID: 1 }, UNIQUE), index({ group: 1, game: 1, playtype: 1 })], - "user-milestones": [ + "milestone-subs": [ index({ milestoneID: 1, userID: 1 }, UNIQUE), index({ userID: 1, game: 1, playtype: 1 }), ], diff --git a/server/src/external/mongo/schemas.ts b/server/src/external/mongo/schemas.ts index 41bb623c2..9bd076f53 100644 --- a/server/src/external/mongo/schemas.ts +++ b/server/src/external/mongo/schemas.ts @@ -688,7 +688,7 @@ export const DatabaseSchemas: Record< } ), }), - "user-goals": prSchemaify({ + "goal-subs": prSchemaify({ goalID: "string", userID: p.isPositiveNonZeroInteger, game: p.isIn(games), @@ -702,7 +702,7 @@ export const DatabaseSchemas: Record< outOf: "number", outOfHuman: "string", }), - "user-milestones": prSchemaify({ + "milestone-subs": prSchemaify({ milestoneID: "string", userID: p.isPositiveNonZeroInteger, game: p.isIn(games), diff --git a/server/src/lib/score-import/framework/goals/goals.test.ts b/server/src/lib/score-import/framework/goals/goals.test.ts index ad96bcdbd..f64d08a15 100644 --- a/server/src/lib/score-import/framework/goals/goals.test.ts +++ b/server/src/lib/score-import/framework/goals/goals.test.ts @@ -116,7 +116,7 @@ t.test("#GetRelevantGoals", (t) => { await db.goals.insert(goals); - await db["user-goals"].insert( + await db["goal-subs"].insert( goals.map((e) => ({ achieved: false, wasInstantlyAchieved: false, @@ -205,7 +205,7 @@ t.test("#UpdateGoalsForUser", (t) => { await db.goals.insert(baseGoalDocument); delete baseGoalDocument._id; - await db["user-goals"].insert(baseUserGoalDocument); + await db["goal-subs"].insert(baseUserGoalDocument); // we dont delete _id here because updategoalsforuser // depends on usergoal _id @@ -236,7 +236,7 @@ t.test("#UpdateGoalsForUser", (t) => { }, ]); - const r = await db["user-goals"].findOne({ goalID: "FAKE_GOAL_ID", userID: 1 }); + const r = await db["goal-subs"].findOne({ goalID: "FAKE_GOAL_ID", userID: 1 }); t.hasStrict( r, @@ -264,7 +264,7 @@ t.test("#UpdateGoalsForUser", (t) => { outOfHuman: "2", }) as unknown as UserGoalDocument; - await db["user-goals"].insert(userGoal); + await db["goal-subs"].insert(userGoal); // we dont delete _id here because updategoalsforuser // depends on usergoal _id @@ -296,7 +296,7 @@ t.test("#UpdateGoalsForUser", (t) => { }, ]); - const r = await db["user-goals"].findOne({ goalID: "FAKE_GOAL_ID", userID: 1 }); + const r = await db["goal-subs"].findOne({ goalID: "FAKE_GOAL_ID", userID: 1 }); t.hasStrict( r, @@ -360,7 +360,7 @@ t.test("#ProcessGoal", (t) => { }); t.test("Should process the users goal if a score has changed.", async (t) => { - await db["user-goals"].insert(HC511UserGoal); + await db["goal-subs"].insert(HC511UserGoal); await db["personal-bests"].insert(TestingIIDXSPScorePB); // score is EX HARD CLEAR by default. const res = await ProcessGoal(HC511Goal, HC511UserGoal, 1, logger); @@ -393,7 +393,7 @@ t.test("#ProcessGoal", (t) => { }); t.test("Should return undefined if there's no score.", async (t) => { - await db["user-goals"].insert(HC511UserGoal); + await db["goal-subs"].insert(HC511UserGoal); const res = await ProcessGoal(HC511Goal, HC511UserGoal, 1, logger); @@ -403,7 +403,7 @@ t.test("#ProcessGoal", (t) => { }); t.test("Should return undefined if the progress has not changed.", async (t) => { - await db["user-goals"].insert(HC511UserGoal); + await db["goal-subs"].insert(HC511UserGoal); await db["personal-bests"].insert(TestingIIDXSPScorePB); const firstUpdate = await ProcessGoal(HC511Goal, HC511UserGoal, 1, logger); @@ -411,9 +411,9 @@ t.test("#ProcessGoal", (t) => { // ignore this one t.not(firstUpdate, undefined, "Should NOT return undefined."); - await db["user-goals"].bulkWrite([firstUpdate!.bwrite]); + await db["goal-subs"].bulkWrite([firstUpdate!.bwrite]); - const userGoal = await db["user-goals"].findOne({ userID: 1, goalID: HC511Goal.goalID }); + const userGoal = await db["goal-subs"].findOne({ userID: 1, goalID: HC511Goal.goalID }); const secondUpdate = await ProcessGoal(HC511Goal, userGoal!, 1, logger); diff --git a/server/src/lib/score-import/framework/goals/goals.ts b/server/src/lib/score-import/framework/goals/goals.ts index c3df626e5..081d07434 100644 --- a/server/src/lib/score-import/framework/goals/goals.ts +++ b/server/src/lib/score-import/framework/goals/goals.ts @@ -76,7 +76,7 @@ export async function UpdateGoalsForUser( }); } - await db["user-goals"].bulkWrite(bulkWrite, { ordered: false }); + await db["goal-subs"].bulkWrite(bulkWrite, { ordered: false }); return importInfo; } @@ -173,7 +173,7 @@ export async function GetRelevantGoals( chartIDs: Set, logger: KtLogger ): Promise<{ goals: GoalDocument[]; userGoalsMap: Map }> { - const userGoals = await db["user-goals"].find({ game, userID }, { projectID: true }); + const userGoals = await db["goal-subs"].find({ game, userID }, { projectID: true }); logger.verbose(`Found user has ${userGoals.length} goals.`); diff --git a/server/src/lib/score-import/framework/milestones/milestones.test.ts b/server/src/lib/score-import/framework/milestones/milestones.test.ts index 83acbec62..eceb2e3b0 100644 --- a/server/src/lib/score-import/framework/milestones/milestones.test.ts +++ b/server/src/lib/score-import/framework/milestones/milestones.test.ts @@ -21,7 +21,7 @@ t.test("#UpdateUsersMilestones", (t) => { t.beforeEach(ResetDBState); t.beforeEach(() => db.milestones.insert(TestingIIDXSPMilestone)); t.beforeEach(() => - db["user-milestones"].insert({ + db["milestone-subs"].insert({ achieved: false, wasInstantlyAchieved: false, game: "iidx", @@ -153,7 +153,7 @@ t.test("#UpdateUsersMilestones", (t) => { milestoneID: "some_other_milestone_with_mutual_goals", }) ); - await db["user-milestones"].insert({ + await db["milestone-subs"].insert({ achieved: false, wasInstantlyAchieved: false, game: "iidx", @@ -262,7 +262,7 @@ t.test("#UpdateUsersMilestones", (t) => { ), ]); - await db["user-milestones"].insert([ + await db["milestone-subs"].insert([ { achieved: false, wasInstantlyAchieved: false, diff --git a/server/src/lib/score-import/framework/milestones/milestones.ts b/server/src/lib/score-import/framework/milestones/milestones.ts index 824bdbdef..fc67fd204 100644 --- a/server/src/lib/score-import/framework/milestones/milestones.ts +++ b/server/src/lib/score-import/framework/milestones/milestones.ts @@ -144,7 +144,7 @@ export async function UpdateUsersMilestones( } if (bwrite.length !== 0) { - await db["user-milestones"].bulkWrite(bwrite, { ordered: false }); + await db["milestone-subs"].bulkWrite(bwrite, { ordered: false }); } return importMilestoneInfo; @@ -157,13 +157,13 @@ async function GetRelevantMilestones( userID: integer, logger: KtLogger ) { - const userMilestones = await db["user-milestones"].find({ + const userMilestones = await db["milestone-subs"].find({ game, playtype: { $in: playtypes }, userID, }); - logger.debug(`Found ${userMilestones.length} user-milestones.`); + logger.debug(`Found ${userMilestones.length} milestone-subs.`); const milestones = await db.milestones.find({ milestoneID: { $in: userMilestones.map((e) => e.milestoneID) }, diff --git a/server/src/lib/targets/goals.ts b/server/src/lib/targets/goals.ts index 5785d6310..3bfe71f68 100644 --- a/server/src/lib/targets/goals.ts +++ b/server/src/lib/targets/goals.ts @@ -298,7 +298,7 @@ export async function SubscribeToGoal( logger.info(`Inserting new goal ${goalDocument.title}.`); } - const userAlreadySubscribed = await db["user-goals"].findOne({ + const userAlreadySubscribed = await db["goal-subs"].findOne({ userID, goalID: goalDocument.goalID, }); @@ -337,7 +337,7 @@ export async function SubscribeToGoal( wasInstantlyAchieved: result.achieved, }; - await db["user-goals"].insert(userGoal); + await db["goal-subs"].insert(userGoal); return userGoal; } diff --git a/server/src/server/router/api/v1/games/_game/_playtype/targets/goals/router.ts b/server/src/server/router/api/v1/games/_game/_playtype/targets/goals/router.ts index a3b8f5fc5..20d38f770 100644 --- a/server/src/server/router/api/v1/games/_game/_playtype/targets/goals/router.ts +++ b/server/src/server/router/api/v1/games/_game/_playtype/targets/goals/router.ts @@ -21,7 +21,7 @@ router.get("/recently-achieved", async (req, res) => { const game = req[SYMBOL_TachiData]!.game!; const playtype = req[SYMBOL_TachiData]!.playtype!; - const recentlyAchievedUserGoals = await db["user-goals"].find( + const recentlyAchievedUserGoals = await db["goal-subs"].find( { game, playtype, @@ -86,7 +86,7 @@ const ResolveGoalID: RequestHandler = async (req, res, next) => { router.get("/:goalID", ResolveGoalID, async (req, res) => { const goal = req[SYMBOL_TachiData]!.goalDoc!; - const userGoals = await db["user-goals"].find({ + const userGoals = await db["goal-subs"].find({ goalID: goal.goalID, }); @@ -150,7 +150,7 @@ router.get( const goal = req[SYMBOL_TachiData]!.goalDoc!; const goalID = goal.goalID; - const userGoal = await db["user-goals"].findOne({ + const userGoal = await db["goal-subs"].findOne({ userID: user.id, goalID, }); diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.test.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.test.ts index 3cb3f5f5d..e9b07494a 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.test.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.test.ts @@ -186,7 +186,7 @@ t.test("GET /api/v1/users/:userID/games/:game/:playtype/goals", (t) => { t.test( "Should return all of a users goals, and only unachieved goals if the argument is set.", async (t) => { - await db["user-goals"].insert([ + await db["goal-subs"].insert([ { goalID: "foo", userID: 1, @@ -256,7 +256,7 @@ t.test("GET /api/v1/users/:userID/games/:game/:playtype/milestones", (t) => { t.test( "Should return all of a users milestones, and only unachieved milestones if the argument is set.", async (t) => { - await db["user-milestones"].insert([ + await db["milestone-subs"].insert([ { milestoneID: "foo", userID: 1, diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.ts index 596e96a10..324702622 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.ts @@ -156,7 +156,7 @@ router.get("/goals", async (req, res) => { query.achieved = false; } - const userGoals = await db["user-goals"].find(query); + const userGoals = await db["goal-subs"].find(query); const goals = await db.goals.find({ goalID: { $in: userGoals.map((e) => e.goalID) }, @@ -193,7 +193,7 @@ router.get("/milestones", async (req, res) => { query.achieved = false; } - const userMilestones = await db["user-milestones"].find(query); + const userMilestones = await db["milestone-subs"].find(query); const milestones = await db.milestones.find({ milestoneID: { $in: userMilestones.map((e) => e.milestoneID) }, diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts index 5e3d23a65..ee36b1ed4 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts @@ -25,7 +25,7 @@ router.get("/", async (req, res) => { const game = req[SYMBOL_TachiData]!.game!; const playtype = req[SYMBOL_TachiData]!.playtype!; - const userGoals = await db["user-goals"].find({ + const userGoals = await db["goal-subs"].find({ userID: user.id, game, playtype, @@ -129,7 +129,7 @@ router.post( const game = req[SYMBOL_TachiData]!.game!; const playtype = req[SYMBOL_TachiData]!.playtype!; - const existingGoalsCount = await db["user-goals"].count({ + const existingGoalsCount = await db["goal-subs"].count({ userID: user.id, game, playtype, @@ -190,7 +190,7 @@ const GetGoalSubscription: RequestHandler = async (req, res, next) => { const game = req[SYMBOL_TachiData]!.game!; const playtype = req[SYMBOL_TachiData]!.playtype!; - const userGoal = await db["user-goals"].findOne({ + const userGoal = await db["goal-subs"].findOne({ userID: user.id, game, playtype, @@ -255,7 +255,7 @@ router.delete( const game = req[SYMBOL_TachiData]!.game!; const playtype = req[SYMBOL_TachiData]!.playtype!; - const userGoal = await db["user-goals"].findOne({ + const userGoal = await db["goal-subs"].findOne({ goalID, userID: user.id, game, @@ -276,7 +276,7 @@ router.delete( }); } - await db["user-goals"].remove({ + await db["goal-subs"].remove({ userID: user.id, goalID, game, diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/milestones/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/milestones/router.ts index e5d186e03..3eab1b06e 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/milestones/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/milestones/router.ts @@ -1,11 +1,21 @@ -import { Router } from "express"; +import { RequestHandler, Router } from "express"; import db from "external/mongo/db"; import { SYMBOL_TachiData } from "lib/constants/tachi"; +import { + CalculateMilestoneOutOf, + EvaluateMilestoneProgress, + GetGoalsInMilestone, +} from "lib/targets/milestones"; +import { RequirePermissions } from "server/middleware/auth"; +import { UserMilestoneDocument } from "tachi-common"; +import { GetMilestoneForIDGuaranteed } from "utils/db"; +import { AssignToReqTachiData } from "utils/req-tachi-data"; +import { FormatUserDoc } from "utils/user"; const router: Router = Router({ mergeParams: true }); /** - * Retrieves this users' set milestones + * Retrieves this users' set milestones. * * @name GET /api/v1/users/:userID/games/:game/:playtype/targets/milestones */ @@ -14,7 +24,7 @@ router.get("/", async (req, res) => { const game = req[SYMBOL_TachiData]!.game!; const playtype = req[SYMBOL_TachiData]!.playtype!; - const userMilestones = await db["user-milestones"].find({ + const userMilestones = await db["milestone-subs"].find({ userID: user.id, game, playtype, @@ -34,4 +44,118 @@ router.get("/", async (req, res) => { }); }); +const GetMilestoneSubscription: RequestHandler = async (req, res, next) => { + const user = req[SYMBOL_TachiData]!.requestedUser!; + const game = req[SYMBOL_TachiData]!.game!; + const playtype = req[SYMBOL_TachiData]!.playtype!; + + const userMilestone = await db["milestone-subs"].findOne({ + userID: user.id, + game, + playtype, + milestoneID: req.params.milestoneID, + }); + + if (!userMilestone) { + return res.status(404).json({ + success: false, + description: `${user.username} is not subscribed to this milestone.`, + }); + } + + AssignToReqTachiData(req, { userMilestoneDoc: userMilestone }); + + return next(); +}; + +/** + * Returns this user's progress on this milestone. + * This also evaluates individual progress on all of the milestones goals. + * + * @name GET /api/v1/users/:userID/games/:game/:playtype/targets/milestones/:milestoneID + */ +router.get("/:milestoneID", GetMilestoneSubscription, async (req, res) => { + const user = req[SYMBOL_TachiData]!.requestedUser!; + const userMilestone = req[SYMBOL_TachiData]!.userMilestoneDoc!; + + const milestone = await GetMilestoneForIDGuaranteed(userMilestone.milestoneID); + + const { progress, results, goals } = await EvaluateMilestoneProgress(user.id, milestone); + + return res.status(200).json({ + success: true, + description: `Returned information about ${FormatUserDoc(user)}'s progress on ${ + milestone.name + }.`, + body: { + userMilestone, + results, + goals, + }, + }); +}); + +/** + * Subscribe to a milestone. + * + * @name PUT /api/v1/users/:userID/games/:game/:playtype/targets/milestones/:milestoneID + */ +router.put("/:milestoneID", RequirePermissions(""), async (req, res) => { + const user = req[SYMBOL_TachiData]!.requestedUser!; + const game = req[SYMBOL_TachiData]!.game!; + const playtype = req[SYMBOL_TachiData]!.playtype!; + + const existingMilestonesCount = await db["milestone-subs"].count({ + userID: user.id, + game, + playtype, + }); + + if (existingMilestonesCount > 100) { + return res.status(400).json({ + success: false, + description: `You already have 100 milestones. You cannot have anymore for this game.`, + }); + } + + const milestone = await db.milestones.findOne({ + milestoneID: req.params.milestoneID, + game, + playtype, + }); + + if (!milestone) { + return res.status(404).json({ + success: false, + description: `A milestone with the ID ${req.params.milestoneID} does not exist.`, + }); + } + + const alreadySubscibed = await db["milestone-subs"].findOne({ + userID: user.id, + milestoneID: milestone.milestoneID, + }); + + if (alreadySubscibed) { + return res.status(409).json({ + success: false, + description: `You are already subscribed to this goal.`, + }); + } + + const { progress, outOf, achieved } = await EvaluateMilestoneProgress(user.id, milestone); + + if (achieved) { + return res.status(400).json({ + success: false, + description: `You cannot assign a milestone that would be immediately achieved.`, + }); + } + + const userMilestone: UserMilestoneDocument = { + achieved, + game, + }; +}); + export default router; diff --git a/server/src/utils/queries/summary.ts b/server/src/utils/queries/summary.ts index 689695ddb..57ee60754 100644 --- a/server/src/utils/queries/summary.ts +++ b/server/src/utils/queries/summary.ts @@ -52,12 +52,12 @@ export async function GetRecentlyViewedFoldersAnyGPT(userID: integer) { export async function GetRecentlyAchievedGoals(userID: integer) { const time = GetTimeXHoursAgo(REASONABLE_HOURS_AGO); - const achievedGoals = await db["user-goals"].find({ + const achievedGoals = await db["goal-subs"].find({ timeAchieved: { $gte: time }, userID, }); - const improvedGoals = await db["user-goals"].find({ + const improvedGoals = await db["goal-subs"].find({ lastInteraction: { $gte: time }, achieved: false, userID, From 533d043ef45df1d291cd62d08670a95e2cda47ba Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Tue, 29 Mar 2022 02:34:38 +0100 Subject: [PATCH 009/106] Update permissions to their new names --- server/src/server/middleware/auth.ts | 3 +-- .../_userID/games/_game/_playtype/targets/goals/router.ts | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/server/src/server/middleware/auth.ts b/server/src/server/middleware/auth.ts index c2dff5af4..9685b55f3 100644 --- a/server/src/server/middleware/auth.ts +++ b/server/src/server/middleware/auth.ts @@ -22,8 +22,7 @@ export const AllPermissions: Record = { customise_session: true, customise_score: true, delete_score: true, - set_goals: true, - unset_goals: true, + manage_targets: true, }; export const SetRequestPermissions: RequestHandler = CreateSetRequestPermissions("description"); diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts index ee36b1ed4..b474e4508 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts @@ -68,7 +68,7 @@ type GoalCreationBody = Pick; router.post( "/add-goal", RequireAuthedAsUser, - RequirePermissions("set_goals"), + RequirePermissions("manage_targets"), prValidate({ criteria: { key: p.isIn( @@ -247,7 +247,7 @@ router.delete( "/:goalID", RequireAuthedAsUser, GetGoalSubscription, - RequirePermissions("unset_goals"), + RequirePermissions("manage_targets"), prValidate({ goalID: "string" }), async (req, res) => { const goalID = req.params.goalID; From 5860f4e49989defea5ff55a12eab50efc6654765 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Tue, 29 Mar 2022 15:14:39 +0100 Subject: [PATCH 010/106] Rename userGoal to goalSub, same for milestones --- .../framework/goals/goals.test.ts | 12 ++-- .../lib/score-import/framework/goals/goals.ts | 60 +++++++++---------- .../framework/milestones/milestones.ts | 34 +++++------ server/src/lib/targets/goals.ts | 6 +- .../_game/_playtype/targets/goals/router.ts | 22 +++---- .../games/_game/_playtype/router.test.ts | 8 +-- .../_userID/games/_game/_playtype/router.ts | 16 ++--- .../_game/_playtype/targets/goals/router.ts | 38 ++++++------ .../_playtype/targets/milestones/router.ts | 22 +++---- server/src/utils/types.ts | 6 +- 10 files changed, 113 insertions(+), 111 deletions(-) diff --git a/server/src/lib/score-import/framework/goals/goals.test.ts b/server/src/lib/score-import/framework/goals/goals.test.ts index f64d08a15..10b9f0ec1 100644 --- a/server/src/lib/score-import/framework/goals/goals.test.ts +++ b/server/src/lib/score-import/framework/goals/goals.test.ts @@ -153,7 +153,7 @@ t.test("#GetRelevantGoals", (t) => { const res = await GetRelevantGoals("iidx", 1, chartIDs, logger); t.equal(res.goals.length, 5, "Should correctly resolve to 5 goals."); - t.equal(res.userGoalsMap.size, 5, "Should also return 5 userGoals."); + t.equal(res.goalSubsMap.size, 5, "Should also return 5 goalSubs."); t.end(); } @@ -259,12 +259,12 @@ t.test("#UpdateGoalsForUser", (t) => { const goal = deepmerge(baseGoalDocument, { criteria: { value: 2 } }); await db.goals.insert(goal); - const userGoal = deepmerge(baseUserGoalDocument, { + const goalSub = deepmerge(baseUserGoalDocument, { outOf: 2, outOfHuman: "2", }) as unknown as UserGoalDocument; - await db["goal-subs"].insert(userGoal); + await db["goal-subs"].insert(goalSub); // we dont delete _id here because updategoalsforuser // depends on usergoal _id @@ -272,7 +272,7 @@ t.test("#UpdateGoalsForUser", (t) => { deepmerge(TestingIIDXSPScorePB, { scoreData: { score: 1 } }) ); - const ugMap = new Map([["FAKE_GOAL_ID", userGoal]]); + const ugMap = new Map([["FAKE_GOAL_ID", goalSub]]); const res = await UpdateGoalsForUser([goal], ugMap, 1, logger); @@ -413,9 +413,9 @@ t.test("#ProcessGoal", (t) => { await db["goal-subs"].bulkWrite([firstUpdate!.bwrite]); - const userGoal = await db["goal-subs"].findOne({ userID: 1, goalID: HC511Goal.goalID }); + const goalSub = await db["goal-subs"].findOne({ userID: 1, goalID: HC511Goal.goalID }); - const secondUpdate = await ProcessGoal(HC511Goal, userGoal!, 1, logger); + const secondUpdate = await ProcessGoal(HC511Goal, goalSub!, 1, logger); t.equal(secondUpdate, undefined, "Should return undefined."); diff --git a/server/src/lib/score-import/framework/goals/goals.ts b/server/src/lib/score-import/framework/goals/goals.ts index 081d07434..d51d93bc4 100644 --- a/server/src/lib/score-import/framework/goals/goals.ts +++ b/server/src/lib/score-import/framework/goals/goals.ts @@ -13,7 +13,7 @@ export async function GetAndUpdateUsersGoals( chartIDs: Set, logger: KtLogger ) { - const { goals, userGoalsMap } = await GetRelevantGoals(game, userID, chartIDs, logger); + const { goals, goalSubsMap } = await GetRelevantGoals(game, userID, chartIDs, logger); if (!goals.length) { // if we hit the below code with an empty array mongodb will flip out on the bulkwrite op @@ -22,27 +22,27 @@ export async function GetAndUpdateUsersGoals( logger.verbose(`Found ${goals.length} relevant goals.`); - return UpdateGoalsForUser(goals, userGoalsMap, userID, logger); + return UpdateGoalsForUser(goals, goalSubsMap, userID, logger); } export async function UpdateGoalsForUser( goals: GoalDocument[], - userGoalsMap: Map, + goalSubsMap: Map, userID: integer, logger: KtLogger ) { const returns = await Promise.all( goals.map((goal: GoalDocument) => { - const userGoal = userGoalsMap.get(goal.goalID); + const goalSub = goalSubsMap.get(goal.goalID); - if (!userGoal) { + if (!goalSub) { logger.error( - `UserGoal:GoalID mismatch ${goal.goalID} - this user has no userGoal for this, yet it is set.` + `UserGoal:GoalID mismatch ${goal.goalID} - this user has no goalSub for this, yet it is set.` ); return; } - return ProcessGoal(goal, userGoal, userID, logger); + return ProcessGoal(goal, goalSub, userID, logger); }) ); @@ -89,7 +89,7 @@ export async function UpdateGoalsForUser( */ export async function ProcessGoal( goal: GoalDocument, - userGoal: UserGoalDocument, + goalSub: UserGoalDocument, userID: integer, logger: KtLogger ) { @@ -101,7 +101,7 @@ export async function ProcessGoal( } // nothing has changed - if (userGoal.progress === res.progress && userGoal.outOf === res.outOf) { + if (goalSub.progress === res.progress && goalSub.outOf === res.outOf) { return; } @@ -114,16 +114,16 @@ export async function ProcessGoal( }; const oldData = { - progress: userGoal.progress, - progressHuman: userGoal.progressHuman, - outOf: userGoal.outOf, - outOfHuman: userGoal.outOfHuman, - achieved: userGoal.achieved, + progress: goalSub.progress, + progressHuman: goalSub.progressHuman, + outOf: goalSub.outOf, + outOfHuman: goalSub.outOfHuman, + achieved: goalSub.achieved, }; let webhookEvent = null; // if this is a newly-achieved goal - if (res.achieved && !userGoal.achieved) { + if (res.achieved && !goalSub.achieved) { webhookEvent = { goalID: goal.goalID, old: oldData, @@ -134,7 +134,7 @@ export async function ProcessGoal( const bulkWrite = { updateOne: { - filter: { _id: userGoal._id! }, + filter: { _id: goalSub._id! }, update: { $set: { ...newData, @@ -165,23 +165,23 @@ export async function ProcessGoal( * * This optimisation allows users to have *lots* of goals, but only ever * evaluate the ones we need to. - * @returns An array of Goals, and an array of userGoals. + * @returns An array of Goals, and an array of goalSubs. */ export async function GetRelevantGoals( game: Game, userID: integer, chartIDs: Set, logger: KtLogger -): Promise<{ goals: GoalDocument[]; userGoalsMap: Map }> { - const userGoals = await db["goal-subs"].find({ game, userID }, { projectID: true }); +): Promise<{ goals: GoalDocument[]; goalSubsMap: Map }> { + const goalSubs = await db["goal-subs"].find({ game, userID }, { projectID: true }); - logger.verbose(`Found user has ${userGoals.length} goals.`); + logger.verbose(`Found user has ${goalSubs.length} goals.`); - if (!userGoals.length) { - return { goals: [], userGoalsMap: new Map() }; + if (!goalSubs.length) { + return { goals: [], goalSubsMap: new Map() }; } - const goalIDs = userGoals.map((e) => e.goalID); + const goalIDs = goalSubs.map((e) => e.goalID); const chartIDsArr: string[] = []; for (const c of chartIDs) { @@ -204,20 +204,20 @@ export async function GetRelevantGoals( const goalSet = new Set(goals.map((e) => e.goalID)); - const userGoalsMap: Map = new Map(); + const goalSubsMap: Map = new Map(); - for (const userGoal of userGoals) { - if (!goalSet.has(userGoal.goalID)) { + for (const goalSub of goalSubs) { + if (!goalSet.has(goalSub.goalID)) { continue; } - // since these are guaranteed to be unique, lets make a hot map of goalID -> userGoalDocument, so we can - // pull them in for post-processing and filter out the userGoalDocuments that aren't relevant. - userGoalsMap.set(userGoal.goalID, userGoal); + // since these are guaranteed to be unique, lets make a hot map of goalID -> goalSubDocument, so we can + // pull them in for post-processing and filter out the goalSubDocuments that aren't relevant. + goalSubsMap.set(goalSub.goalID, goalSub); } return { goals, - userGoalsMap, + goalSubsMap, }; } diff --git a/server/src/lib/score-import/framework/milestones/milestones.ts b/server/src/lib/score-import/framework/milestones/milestones.ts index fc67fd204..357276661 100644 --- a/server/src/lib/score-import/framework/milestones/milestones.ts +++ b/server/src/lib/score-import/framework/milestones/milestones.ts @@ -56,15 +56,15 @@ export async function UpdateUsersMilestones( userID: integer, logger: KtLogger ) { - const userGoalInfoMap: Map = new Map(); + const goalSubInfoMap: Map = new Map(); const goalIDs = []; for (const e of importGoalInfo) { - userGoalInfoMap.set(e.goalID, e.new); + goalSubInfoMap.set(e.goalID, e.new); goalIDs.push(e.goalID); } - const { milestones, userMilestones } = await GetRelevantMilestones( + const { milestones, milestoneSubs } = await GetRelevantMilestones( goalIDs, game, playtypes, @@ -74,9 +74,9 @@ export async function UpdateUsersMilestones( // create a map here to avoid linear searching when // co-iterating - const userMilestoneMap = new Map(); - for (const um of userMilestones) { - userMilestoneMap.set(um.milestoneID, um); + const milestoneSubMap = new Map(); + for (const um of milestoneSubs) { + milestoneSubMap.set(um.milestoneID, um); } const importGoalMap = new Map(); @@ -92,9 +92,9 @@ export async function UpdateUsersMilestones( for (const milestone of milestones) { const { achieved, progress } = ProcessMilestoneFromGII(milestone, importGoalMap); - const userMilestone = userMilestoneMap.get(milestone.milestoneID); + const milestoneSub = milestoneSubMap.get(milestone.milestoneID); - if (!userMilestone) { + if (!milestoneSub) { logger.severe( `Invalid state achieved in milestone processing - processed milestone that user did not have? ${milestone.milestoneID}` ); @@ -115,10 +115,10 @@ export async function UpdateUsersMilestones( }); const milestoneInfo = { - milestoneID: userMilestone.milestoneID, + milestoneID: milestoneSub.milestoneID, old: { - progress: userMilestone.progress, - achieved: userMilestone.achieved, + progress: milestoneSub.progress, + achieved: milestoneSub.achieved, }, new: { progress, @@ -126,11 +126,11 @@ export async function UpdateUsersMilestones( }, }; - if (progress !== userMilestone.progress) { + if (progress !== milestoneSub.progress) { importMilestoneInfo.push(milestoneInfo); } - if (achieved && !userMilestone.achieved) { + if (achieved && !milestoneSub.achieved) { EmitWebhookEvent({ type: "milestone-achieved/v1", content: { @@ -157,20 +157,20 @@ async function GetRelevantMilestones( userID: integer, logger: KtLogger ) { - const userMilestones = await db["milestone-subs"].find({ + const milestoneSubs = await db["milestone-subs"].find({ game, playtype: { $in: playtypes }, userID, }); - logger.debug(`Found ${userMilestones.length} milestone-subs.`); + logger.debug(`Found ${milestoneSubs.length} milestone-subs.`); const milestones = await db.milestones.find({ - milestoneID: { $in: userMilestones.map((e) => e.milestoneID) }, + milestoneID: { $in: milestoneSubs.map((e) => e.milestoneID) }, "milestoneData.goals.goalID": { $in: goalIDs }, }); logger.debug(`Found ${milestones.length} relevant milestones.`); - return { userMilestones, milestones }; + return { milestoneSubs, milestones }; } diff --git a/server/src/lib/targets/goals.ts b/server/src/lib/targets/goals.ts index 3bfe71f68..b48399cd6 100644 --- a/server/src/lib/targets/goals.ts +++ b/server/src/lib/targets/goals.ts @@ -319,7 +319,7 @@ export async function SubscribeToGoal( // @ts-expect-error TS can't resolve this. // because it can't explode out the types. - const userGoal: UserGoalDocument = { + const goalSub: UserGoalDocument = { outOf: result.outOf, outOfHuman: result.outOfHuman, progress: result.progress, @@ -337,7 +337,7 @@ export async function SubscribeToGoal( wasInstantlyAchieved: result.achieved, }; - await db["goal-subs"].insert(userGoal); + await db["goal-subs"].insert(goalSub); - return userGoal; + return goalSub; } diff --git a/server/src/server/router/api/v1/games/_game/_playtype/targets/goals/router.ts b/server/src/server/router/api/v1/games/_game/_playtype/targets/goals/router.ts index 20d38f770..6c3cdf7e1 100644 --- a/server/src/server/router/api/v1/games/_game/_playtype/targets/goals/router.ts +++ b/server/src/server/router/api/v1/games/_game/_playtype/targets/goals/router.ts @@ -48,7 +48,7 @@ router.get("/recently-achieved", async (req, res) => { success: true, description: `Retrieved ${recentlyAchievedUserGoals.length} recently achieved goals.`, body: { - userGoals: recentlyAchievedUserGoals, + goalSubs: recentlyAchievedUserGoals, goals, users, }, @@ -86,18 +86,18 @@ const ResolveGoalID: RequestHandler = async (req, res, next) => { router.get("/:goalID", ResolveGoalID, async (req, res) => { const goal = req[SYMBOL_TachiData]!.goalDoc!; - const userGoals = await db["goal-subs"].find({ + const goalSubs = await db["goal-subs"].find({ goalID: goal.goalID, }); - const users = await GetUsersWithIDs(userGoals.map((e) => e.userID)); + const users = await GetUsersWithIDs(goalSubs.map((e) => e.userID)); return res.status(200).json({ success: true, description: `Retrieved information about ${goal.title}.`, body: { goal, - userGoals, + goalSubs, users, }, }); @@ -150,20 +150,20 @@ router.get( const goal = req[SYMBOL_TachiData]!.goalDoc!; const goalID = goal.goalID; - const userGoal = await db["goal-subs"].findOne({ + const goalSub = await db["goal-subs"].findOne({ userID: user.id, goalID, }); let goalResults: EvaluatedGoalReturn; // shortcut evaluation by using the user goal - if (userGoal) { + if (goalSub) { goalResults = { - achieved: userGoal.achieved, - outOf: userGoal.outOf, - outOfHuman: userGoal.outOfHuman, - progress: userGoal.progress, - progressHuman: userGoal.progressHuman, + achieved: goalSub.achieved, + outOf: goalSub.outOf, + outOfHuman: goalSub.outOfHuman, + progress: goalSub.progress, + progressHuman: goalSub.progressHuman, }; } else { const results = await EvaluateGoalForUser(goal, user.id, logger); diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.test.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.test.ts index e9b07494a..ca59ca6f8 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.test.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.test.ts @@ -211,7 +211,7 @@ t.test("GET /api/v1/users/:userID/games/:game/:playtype/goals", (t) => { success: true, description: "Successfully returned 2 goal(s).", body: { - userGoals: [ + goalSubs: [ { goalID: "foo", achieved: false, @@ -233,7 +233,7 @@ t.test("GET /api/v1/users/:userID/games/:game/:playtype/goals", (t) => { success: true, description: "Successfully returned 1 goal(s).", body: { - userGoals: [ + goalSubs: [ { goalID: "foo", achieved: false, @@ -284,7 +284,7 @@ t.test("GET /api/v1/users/:userID/games/:game/:playtype/milestones", (t) => { success: true, description: "Successfully returned 2 milestone(s).", body: { - userMilestones: [ + milestoneSubs: [ { milestoneID: "foo", achieved: false, @@ -306,7 +306,7 @@ t.test("GET /api/v1/users/:userID/games/:game/:playtype/milestones", (t) => { success: true, description: "Successfully returned 1 milestone(s).", body: { - userMilestones: [ + milestoneSubs: [ { milestoneID: "foo", achieved: false, diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.ts index 324702622..f8bbdcfbe 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.ts @@ -156,17 +156,17 @@ router.get("/goals", async (req, res) => { query.achieved = false; } - const userGoals = await db["goal-subs"].find(query); + const goalSubs = await db["goal-subs"].find(query); const goals = await db.goals.find({ - goalID: { $in: userGoals.map((e) => e.goalID) }, + goalID: { $in: goalSubs.map((e) => e.goalID) }, }); return res.status(200).json({ success: true, - description: `Successfully returned ${userGoals.length} goal(s).`, + description: `Successfully returned ${goalSubs.length} goal(s).`, body: { - userGoals, + goalSubs, goals, }, }); @@ -193,17 +193,17 @@ router.get("/milestones", async (req, res) => { query.achieved = false; } - const userMilestones = await db["milestone-subs"].find(query); + const milestoneSubs = await db["milestone-subs"].find(query); const milestones = await db.milestones.find({ - milestoneID: { $in: userMilestones.map((e) => e.milestoneID) }, + milestoneID: { $in: milestoneSubs.map((e) => e.milestoneID) }, }); return res.status(200).json({ success: true, - description: `Successfully returned ${userMilestones.length} milestone(s).`, + description: `Successfully returned ${milestoneSubs.length} milestone(s).`, body: { - userMilestones, + milestoneSubs, milestones, }, }); diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts index b474e4508..8e606c492 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts @@ -25,22 +25,22 @@ router.get("/", async (req, res) => { const game = req[SYMBOL_TachiData]!.game!; const playtype = req[SYMBOL_TachiData]!.playtype!; - const userGoals = await db["goal-subs"].find({ + const goalSubs = await db["goal-subs"].find({ userID: user.id, game, playtype, }); const goals = await db.goals.find({ - goalID: { $in: userGoals.map((e) => e.goalID) }, + goalID: { $in: goalSubs.map((e) => e.goalID) }, }); return res.status(200).json({ success: true, - description: `Retrieved ${userGoals.length} goal(s).`, + description: `Retrieved ${goalSubs.length} goal(s).`, body: { goals, - userGoals, + goalSubs, }, }); }); @@ -158,16 +158,16 @@ router.post( }); } - const userGoal = await SubscribeToGoal(user.id, goal, { origin: "manual" }); + const goalSub = await SubscribeToGoal(user.id, goal, { origin: "manual" }); - if (userGoal === SubscribeFailReasons.ALREADY_SUBSCRIBED) { + if (goalSub === SubscribeFailReasons.ALREADY_SUBSCRIBED) { return res.status(409).json({ success: false, description: `You are already subscribed to this goal.`, }); } - if (userGoal === SubscribeFailReasons.ALREADY_ACHIEVED) { + if (goalSub === SubscribeFailReasons.ALREADY_ACHIEVED) { return res.status(400).json({ success: false, description: `You can't directly assign goals that you would immediately achieve.`, @@ -179,7 +179,7 @@ router.post( description: `Subscribed to ${goal.title}.`, body: { goal, - userGoal, + goalSub, }, }); } @@ -190,21 +190,21 @@ const GetGoalSubscription: RequestHandler = async (req, res, next) => { const game = req[SYMBOL_TachiData]!.game!; const playtype = req[SYMBOL_TachiData]!.playtype!; - const userGoal = await db["goal-subs"].findOne({ + const goalSub = await db["goal-subs"].findOne({ userID: user.id, game, playtype, goalID: req.params.goalID, }); - if (!userGoal) { + if (!goalSub) { return res.status(404).json({ success: false, description: `${user.username} is not subscribed to this goal.`, }); } - AssignToReqTachiData(req, { userGoalDoc: userGoal }); + AssignToReqTachiData(req, { goalSubDoc: goalSub }); return next(); }; @@ -216,22 +216,22 @@ const GetGoalSubscription: RequestHandler = async (req, res, next) => { */ router.get("/:goalID", GetGoalSubscription, async (req, res) => { const user = req[SYMBOL_TachiData]!.requestedUser!; - const userGoal = req[SYMBOL_TachiData]!.userGoalDoc!; + const goalSub = req[SYMBOL_TachiData]!.goalSubDoc!; let milestone = null; - if (userGoal.from.origin === "milestone") { - milestone = await GetMilestoneForIDGuaranteed(userGoal.from.milestoneID); + if (goalSub.from.origin === "milestone") { + milestone = await GetMilestoneForIDGuaranteed(goalSub.from.milestoneID); } - const goal = await GetGoalForIDGuaranteed(userGoal.goalID); + const goal = await GetGoalForIDGuaranteed(goalSub.goalID); return res.status(200).json({ success: true, description: `Returned information about goal ${goal.title}.`, body: { goal, - userGoal, + goalSub, milestone, user, }, @@ -255,21 +255,21 @@ router.delete( const game = req[SYMBOL_TachiData]!.game!; const playtype = req[SYMBOL_TachiData]!.playtype!; - const userGoal = await db["goal-subs"].findOne({ + const goalSub = await db["goal-subs"].findOne({ goalID, userID: user.id, game, playtype, }); - if (!userGoal) { + if (!goalSub) { return res.status(400).json({ success: false, description: `You aren't subscribed to this goal.`, }); } - if (userGoal.from.origin === "milestone") { + if (goalSub.from.origin === "milestone") { return res.status(400).json({ success: false, description: `This goal is from a milestone. You can't remove it directly, only by removing the parent milestone.`, diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/milestones/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/milestones/router.ts index 3eab1b06e..09e3bad3a 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/milestones/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/milestones/router.ts @@ -24,22 +24,22 @@ router.get("/", async (req, res) => { const game = req[SYMBOL_TachiData]!.game!; const playtype = req[SYMBOL_TachiData]!.playtype!; - const userMilestones = await db["milestone-subs"].find({ + const milestoneSubs = await db["milestone-subs"].find({ userID: user.id, game, playtype, }); const milestones = await db.milestones.find({ - milestoneID: { $in: userMilestones.map((e) => e.milestoneID) }, + milestoneID: { $in: milestoneSubs.map((e) => e.milestoneID) }, }); return res.status(200).json({ success: true, - description: `Retrieved ${userMilestones.length} milestone(s)`, + description: `Retrieved ${milestoneSubs.length} milestone(s)`, body: { milestones, - userMilestones, + milestoneSubs, }, }); }); @@ -49,21 +49,21 @@ const GetMilestoneSubscription: RequestHandler = async (req, res, next) => { const game = req[SYMBOL_TachiData]!.game!; const playtype = req[SYMBOL_TachiData]!.playtype!; - const userMilestone = await db["milestone-subs"].findOne({ + const milestoneSub = await db["milestone-subs"].findOne({ userID: user.id, game, playtype, milestoneID: req.params.milestoneID, }); - if (!userMilestone) { + if (!milestoneSub) { return res.status(404).json({ success: false, description: `${user.username} is not subscribed to this milestone.`, }); } - AssignToReqTachiData(req, { userMilestoneDoc: userMilestone }); + AssignToReqTachiData(req, { milestoneSubDoc: milestoneSub }); return next(); }; @@ -76,9 +76,9 @@ const GetMilestoneSubscription: RequestHandler = async (req, res, next) => { */ router.get("/:milestoneID", GetMilestoneSubscription, async (req, res) => { const user = req[SYMBOL_TachiData]!.requestedUser!; - const userMilestone = req[SYMBOL_TachiData]!.userMilestoneDoc!; + const milestoneSub = req[SYMBOL_TachiData]!.milestoneSubDoc!; - const milestone = await GetMilestoneForIDGuaranteed(userMilestone.milestoneID); + const milestone = await GetMilestoneForIDGuaranteed(milestoneSub.milestoneID); const { progress, results, goals } = await EvaluateMilestoneProgress(user.id, milestone); @@ -88,7 +88,7 @@ router.get("/:milestoneID", GetMilestoneSubscription, async (req, res) => { milestone.name }.`, body: { - userMilestone, + milestoneSub, results, goals, }, @@ -152,7 +152,7 @@ router.put("/:milestoneID", RequirePermissions(""), async (req, res) => { }); } - const userMilestone: UserMilestoneDocument = { + const milestoneSub: UserMilestoneDocument = { achieved, game, }; diff --git a/server/src/utils/types.ts b/server/src/utils/types.ts index 402c81089..8639e70a0 100644 --- a/server/src/utils/types.ts +++ b/server/src/utils/types.ts @@ -11,8 +11,9 @@ import { SongDocument, UserSettings, TachiAPIClientDocument, - UserGoalDocument, GoalDocument, + MilestoneSubscriptionDocument, + GoalSubscriptionDocument, } from "tachi-common"; declare module "express-session" { @@ -65,7 +66,8 @@ export interface TachiRequestData { tableDoc?: TableDocument; folderDoc?: FolderDocument; goalDoc?: GoalDocument; - userGoalDoc?: UserGoalDocument; + goalSubDoc?: GoalSubscriptionDocument; + milestoneSubDoc?: MilestoneSubscriptionDocument; apiClientDoc: Omit; } From 35ec22e3c0a67c1bcd972bab1e360bf50ee0f80c Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Tue, 29 Mar 2022 15:17:45 +0100 Subject: [PATCH 011/106] Rename the types, too --- server/src/external/mongo/db.ts | 8 ++++---- .../score-import/framework/goals/goals.test.ts | 18 +++++++++--------- .../lib/score-import/framework/goals/goals.ts | 10 +++++----- .../framework/milestones/milestones.ts | 4 ++-- server/src/lib/targets/goals.ts | 4 ++-- .../games/_game/_playtype/router.test.ts | 8 ++++---- .../_userID/games/_game/_playtype/router.ts | 8 ++++---- .../_playtype/targets/milestones/router.ts | 4 ++-- server/src/test-utils/test-data.ts | 4 ++-- 9 files changed, 34 insertions(+), 34 deletions(-) diff --git a/server/src/external/mongo/db.ts b/server/src/external/mongo/db.ts index d27660026..527db645f 100644 --- a/server/src/external/mongo/db.ts +++ b/server/src/external/mongo/db.ts @@ -36,8 +36,8 @@ import { UGPTSettings, UserGameStats, UserGameStatsSnapshot, - UserGoalDocument, - UserMilestoneDocument, + GoalSubscriptionDocument, + MilestoneSubscriptionDocument, UserSettings, } from "tachi-common"; import { GetMillisecondsSince } from "utils/misc"; @@ -152,9 +152,9 @@ const db = { folders: monkDB.get("folders"), "folder-chart-lookup": monkDB.get("folder-chart-lookup"), goals: monkDB.get("goals"), - "goal-subs": monkDB.get("goal-subs"), + "goal-subs": monkDB.get("goal-subs"), milestones: monkDB.get("milestones"), - "milestone-subs": monkDB.get("milestone-subs"), + "milestone-subs": monkDB.get("milestone-subs"), users: monkDB.get("users"), imports: monkDB.get("imports"), "import-timings": monkDB.get("import-timings"), diff --git a/server/src/lib/score-import/framework/goals/goals.test.ts b/server/src/lib/score-import/framework/goals/goals.test.ts index 10b9f0ec1..3c421a980 100644 --- a/server/src/lib/score-import/framework/goals/goals.test.ts +++ b/server/src/lib/score-import/framework/goals/goals.test.ts @@ -2,7 +2,7 @@ import t from "tap"; import db from "external/mongo/db"; import ResetDBState from "test-utils/resets"; import { GetRelevantFolderGoals, GetRelevantGoals, UpdateGoalsForUser, ProcessGoal } from "./goals"; -import { GoalDocument, UserGoalDocument } from "tachi-common"; +import { GoalDocument, GoalSubscriptionDocument } from "tachi-common"; import { CreateFolderChartLookup } from "utils/folder"; import { GetKTDataJSON, @@ -182,7 +182,7 @@ t.test("#UpdateGoalsForUser", (t) => { }, }; - const baseUserGoalDocument: UserGoalDocument = { + const baseGoalSubscriptionDocument: GoalSubscriptionDocument = { achieved: false, wasInstantlyAchieved: false, game: "iidx", @@ -205,14 +205,14 @@ t.test("#UpdateGoalsForUser", (t) => { await db.goals.insert(baseGoalDocument); delete baseGoalDocument._id; - await db["goal-subs"].insert(baseUserGoalDocument); + await db["goal-subs"].insert(baseGoalSubscriptionDocument); // we dont delete _id here because updategoalsforuser // depends on usergoal _id await db["personal-bests"].insert(TestingIIDXSPScorePB); delete TestingIIDXSPScorePB._id; - const ugMap = new Map([["FAKE_GOAL_ID", baseUserGoalDocument]]); + const ugMap = new Map([["FAKE_GOAL_ID", baseGoalSubscriptionDocument]]); const res = await UpdateGoalsForUser([baseGoalDocument], ugMap, 1, logger); @@ -250,7 +250,7 @@ t.test("#UpdateGoalsForUser", (t) => { "Should update goals in the database." ); - delete baseUserGoalDocument._id; + delete baseGoalSubscriptionDocument._id; t.end(); }); @@ -259,10 +259,10 @@ t.test("#UpdateGoalsForUser", (t) => { const goal = deepmerge(baseGoalDocument, { criteria: { value: 2 } }); await db.goals.insert(goal); - const goalSub = deepmerge(baseUserGoalDocument, { + const goalSub = deepmerge(baseGoalSubscriptionDocument, { outOf: 2, outOfHuman: "2", - }) as unknown as UserGoalDocument; + }) as unknown as GoalSubscriptionDocument; await db["goal-subs"].insert(goalSub); // we dont delete _id here because updategoalsforuser @@ -310,7 +310,7 @@ t.test("#UpdateGoalsForUser", (t) => { "Should update goals in the database." ); - delete baseUserGoalDocument._id; + delete baseGoalSubscriptionDocument._id; t.end(); }); @@ -334,7 +334,7 @@ t.test("#UpdateGoalsForUser", (t) => { t.test("Should handle (skip) invalid goals.", async (t) => { const res = await UpdateGoalsForUser( [deepmerge(baseGoalDocument, { charts: { type: "INVALID" } })], - new Map([["FAKE_GOAL_ID", baseUserGoalDocument]]), + new Map([["FAKE_GOAL_ID", baseGoalSubscriptionDocument]]), 1, logger ); diff --git a/server/src/lib/score-import/framework/goals/goals.ts b/server/src/lib/score-import/framework/goals/goals.ts index d51d93bc4..b47e0105c 100644 --- a/server/src/lib/score-import/framework/goals/goals.ts +++ b/server/src/lib/score-import/framework/goals/goals.ts @@ -1,4 +1,4 @@ -import { integer, Game, GoalDocument, UserGoalDocument } from "tachi-common"; +import { integer, Game, GoalDocument, GoalSubscriptionDocument } from "tachi-common"; import { EvaluateGoalForUser } from "lib/targets/goals"; import db from "external/mongo/db"; import { KtLogger } from "lib/logger/logger"; @@ -27,7 +27,7 @@ export async function GetAndUpdateUsersGoals( export async function UpdateGoalsForUser( goals: GoalDocument[], - goalSubsMap: Map, + goalSubsMap: Map, userID: integer, logger: KtLogger ) { @@ -89,7 +89,7 @@ export async function UpdateGoalsForUser( */ export async function ProcessGoal( goal: GoalDocument, - goalSub: UserGoalDocument, + goalSub: GoalSubscriptionDocument, userID: integer, logger: KtLogger ) { @@ -172,7 +172,7 @@ export async function GetRelevantGoals( userID: integer, chartIDs: Set, logger: KtLogger -): Promise<{ goals: GoalDocument[]; goalSubsMap: Map }> { +): Promise<{ goals: GoalDocument[]; goalSubsMap: Map }> { const goalSubs = await db["goal-subs"].find({ game, userID }, { projectID: true }); logger.verbose(`Found user has ${goalSubs.length} goals.`); @@ -204,7 +204,7 @@ export async function GetRelevantGoals( const goalSet = new Set(goals.map((e) => e.goalID)); - const goalSubsMap: Map = new Map(); + const goalSubsMap: Map = new Map(); for (const goalSub of goalSubs) { if (!goalSet.has(goalSub.goalID)) { diff --git a/server/src/lib/score-import/framework/milestones/milestones.ts b/server/src/lib/score-import/framework/milestones/milestones.ts index 357276661..9f6518922 100644 --- a/server/src/lib/score-import/framework/milestones/milestones.ts +++ b/server/src/lib/score-import/framework/milestones/milestones.ts @@ -9,7 +9,7 @@ import { MilestoneDocument, MilestoneImportInfo, Playtypes, - UserMilestoneDocument, + MilestoneSubscriptionDocument, } from "tachi-common"; import { CalculateMilestoneOutOf, GetGoalIDsFromMilestone } from "utils/milestone"; @@ -85,7 +85,7 @@ export async function UpdateUsersMilestones( importGoalMap.set(ig.goalID, ig.new); } - const bwrite: BulkWriteUpdateOneOperation[] = []; + const bwrite: BulkWriteUpdateOneOperation[] = []; const importMilestoneInfo: MilestoneImportInfo[] = []; diff --git a/server/src/lib/targets/goals.ts b/server/src/lib/targets/goals.ts index b48399cd6..0f4ba6673 100644 --- a/server/src/lib/targets/goals.ts +++ b/server/src/lib/targets/goals.ts @@ -10,7 +10,7 @@ import { integer, PBScoreDocument, Playtypes, - UserGoalDocument, + GoalSubscriptionDocument, } from "tachi-common"; import { GetFolderChartIDs } from "utils/folder"; import { CreateGoalTitle, ValidateGoalChartsAndCriteria } from "./goal-utils"; @@ -319,7 +319,7 @@ export async function SubscribeToGoal( // @ts-expect-error TS can't resolve this. // because it can't explode out the types. - const goalSub: UserGoalDocument = { + const goalSub: GoalSubscriptionDocument = { outOf: result.outOf, outOfHuman: result.outOfHuman, progress: result.progress, diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.test.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.test.ts index ca59ca6f8..aff060fba 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.test.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.test.ts @@ -4,8 +4,8 @@ import { GoalDocument, MilestoneDocument, ScoreDocument, - UserGoalDocument, - UserMilestoneDocument, + GoalSubscriptionDocument, + MilestoneSubscriptionDocument, } from "tachi-common"; import t from "tap"; import mockApi from "test-utils/mock-api"; @@ -201,7 +201,7 @@ t.test("GET /api/v1/users/:userID/games/:game/:playtype/goals", (t) => { playtype: "SP", achieved: true, }, - ] as UserGoalDocument[]); + ] as GoalSubscriptionDocument[]); await db.goals.insert([{ goalID: "foo" }, { goalID: "bar" }] as GoalDocument[]); @@ -271,7 +271,7 @@ t.test("GET /api/v1/users/:userID/games/:game/:playtype/milestones", (t) => { playtype: "SP", achieved: true, }, - ] as UserMilestoneDocument[]); + ] as MilestoneSubscriptionDocument[]); await db.milestones.insert([ { milestoneID: "foo" }, diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.ts index f8bbdcfbe..f0cc5e709 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.ts @@ -7,8 +7,8 @@ import { integer, PBScoreDocument, UserGameStatsSnapshot, - UserGoalDocument, - UserMilestoneDocument, + GoalSubscriptionDocument, + MilestoneSubscriptionDocument, } from "tachi-common"; import { IsString } from "utils/misc"; import { CheckStrProfileAlg } from "utils/string-checks"; @@ -146,7 +146,7 @@ router.get("/goals", async (req, res) => { const game = req[SYMBOL_TachiData]!.game!; const playtype = req[SYMBOL_TachiData]!.playtype!; - const query: FilterQuery = { + const query: FilterQuery = { userID: user.id, game, playtype, @@ -183,7 +183,7 @@ router.get("/milestones", async (req, res) => { const game = req[SYMBOL_TachiData]!.game!; const playtype = req[SYMBOL_TachiData]!.playtype!; - const query: FilterQuery = { + const query: FilterQuery = { userID: user.id, game, playtype, diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/milestones/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/milestones/router.ts index 09e3bad3a..cd1787f0c 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/milestones/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/milestones/router.ts @@ -7,7 +7,7 @@ import { GetGoalsInMilestone, } from "lib/targets/milestones"; import { RequirePermissions } from "server/middleware/auth"; -import { UserMilestoneDocument } from "tachi-common"; +import { MilestoneSubscriptionDocument } from "tachi-common"; import { GetMilestoneForIDGuaranteed } from "utils/db"; import { AssignToReqTachiData } from "utils/req-tachi-data"; import { FormatUserDoc } from "utils/user"; @@ -152,7 +152,7 @@ router.put("/:milestoneID", RequirePermissions(""), async (req, res) => { }); } - const milestoneSub: UserMilestoneDocument = { + const milestoneSub: MilestoneSubscriptionDocument = { achieved, game, }; diff --git a/server/src/test-utils/test-data.ts b/server/src/test-utils/test-data.ts index ac54ad1ec..f9a22bca5 100644 --- a/server/src/test-utils/test-data.ts +++ b/server/src/test-utils/test-data.ts @@ -21,7 +21,7 @@ import { PBScoreDocument, ScoreDocument, SongDocument, - UserGoalDocument, + GoalSubscriptionDocument, } from "tachi-common"; import { ApplyNTimes, RFA } from "utils/misc"; @@ -422,7 +422,7 @@ export const HC511Goal: GoalDocument = { }, }; -export const HC511UserGoal: UserGoalDocument = { +export const HC511UserGoal: GoalSubscriptionDocument = { achieved: false, wasInstantlyAchieved: false, timeAchieved: null, From 431b539ce1d835322bc17174e808ef02f7c7c54d Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Tue, 29 Mar 2022 15:28:12 +0100 Subject: [PATCH 012/106] More renaming and moving (I lost my original work...) --- .../lib/score-import/framework/milestones/milestones.ts | 2 +- .../{utils/milestone.ts => lib/targets/milestones.ts} | 0 .../games/_game/_playtype/targets/milestones/router.ts | 9 ++++++++- .../mock-db/{user-goals.json => goal-subs.json} | 0 .../{user-milestones.json => milestone-subs.json} | 0 5 files changed, 9 insertions(+), 2 deletions(-) rename server/src/{utils/milestone.ts => lib/targets/milestones.ts} (100%) rename server/src/test-utils/mock-db/{user-goals.json => goal-subs.json} (100%) rename server/src/test-utils/mock-db/{user-milestones.json => milestone-subs.json} (100%) diff --git a/server/src/lib/score-import/framework/milestones/milestones.ts b/server/src/lib/score-import/framework/milestones/milestones.ts index 9f6518922..cec2018c2 100644 --- a/server/src/lib/score-import/framework/milestones/milestones.ts +++ b/server/src/lib/score-import/framework/milestones/milestones.ts @@ -11,7 +11,7 @@ import { Playtypes, MilestoneSubscriptionDocument, } from "tachi-common"; -import { CalculateMilestoneOutOf, GetGoalIDsFromMilestone } from "utils/milestone"; +import { CalculateMilestoneOutOf, GetGoalIDsFromMilestone } from "lib/targets/milestones"; /** * Processes and updates a user's milestones from their Goal Import Info (i.e. what is returned diff --git a/server/src/utils/milestone.ts b/server/src/lib/targets/milestones.ts similarity index 100% rename from server/src/utils/milestone.ts rename to server/src/lib/targets/milestones.ts diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/milestones/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/milestones/router.ts index cd1787f0c..ae62f7193 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/milestones/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/milestones/router.ts @@ -143,7 +143,7 @@ router.put("/:milestoneID", RequirePermissions(""), async (req, res) => { }); } - const { progress, outOf, achieved } = await EvaluateMilestoneProgress(user.id, milestone); + const { progress, achieved } = await EvaluateMilestoneProgress(user.id, milestone); if (achieved) { return res.status(400).json({ @@ -155,6 +155,13 @@ router.put("/:milestoneID", RequirePermissions(""), async (req, res) => { const milestoneSub: MilestoneSubscriptionDocument = { achieved, game, + milestoneID: milestone.milestoneID, + playtype, + progress, + timeAchieved: null, + timeSet: Date.now(), + userID: user.id, + wasInstantlyAchieved: false, }; }); diff --git a/server/src/test-utils/mock-db/user-goals.json b/server/src/test-utils/mock-db/goal-subs.json similarity index 100% rename from server/src/test-utils/mock-db/user-goals.json rename to server/src/test-utils/mock-db/goal-subs.json diff --git a/server/src/test-utils/mock-db/user-milestones.json b/server/src/test-utils/mock-db/milestone-subs.json similarity index 100% rename from server/src/test-utils/mock-db/user-milestones.json rename to server/src/test-utils/mock-db/milestone-subs.json From 8df27e07b2016e55fd4d687e415f5085dda71e62 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Tue, 29 Mar 2022 15:57:05 +0100 Subject: [PATCH 013/106] Save my work this time... --- server/src/lib/targets/milestones.ts | 132 ++++++++++++++++++++++++++- 1 file changed, 130 insertions(+), 2 deletions(-) diff --git a/server/src/lib/targets/milestones.ts b/server/src/lib/targets/milestones.ts index f97d4374b..9794b4eae 100644 --- a/server/src/lib/targets/milestones.ts +++ b/server/src/lib/targets/milestones.ts @@ -1,4 +1,9 @@ -import { integer, MilestoneDocument } from "tachi-common"; +import db from "external/mongo/db"; +import CreateLogCtx from "lib/logger/logger"; +import { GoalSubscriptionDocument, integer, MilestoneDocument } from "tachi-common"; +import { EvaluatedGoalReturn, EvaluateGoalForUser } from "./goals"; + +const logger = CreateLogCtx(__filename); /** * Retrieves the goalID documents in a single array from the @@ -10,11 +15,42 @@ export function GetGoalIDsFromMilestone(milestone: MilestoneDocument) { return milestone.milestoneData.map((e) => e.goals.map((e) => e.goalID)).flat(1); } +/** + * Return all the goals inside this milestone. + */ +export async function GetGoalsInMilestone(milestone: MilestoneDocument) { + const goalIDs = GetGoalIDsFromMilestone(milestone); + + const goals = await db.goals.find({ + goalID: { $in: goalIDs }, + }); + + if (goals.length !== goalIDs.length) { + logger.error( + `Milestone ${milestone.name} has ${goalIDs.length} goals registered, but we could only find ${goals.length} in the database?`, + { goals: goals.length, milestone, goalIDs: goalIDs.length } + ); + throw new Error(`Milestone is corrupt. Not the right amount of goals in db?`); + } + + if (goalIDs.length < 2) { + logger.error( + `Milestone ${milestone.name} resolves to less than 2 goals. Isn't a valid milestone?`, + { milestone } + ); + throw new Error(`Milestone is corrupt. Doesn't have enough goals.`); + } + + return goals; +} + /** * Work out how many goals need to be achieved for this * milestone to be considered completed. */ -export function CalculateMilestoneOutOf(milestone: MilestoneDocument, goalIDs: string[]): integer { +export function CalculateMilestoneOutOf(milestone: MilestoneDocument) { + const goalIDs = GetGoalIDsFromMilestone(milestone); + if (milestone.criteria.type === "all") { return goalIDs.length; } else if (milestone.criteria.type === "abs") { @@ -39,3 +75,95 @@ export function CalculateMilestoneOutOf(milestone: MilestoneDocument, goalIDs: s `Invalid milestone.criteria.type of ${milestone.criteria.type} -- milestoneID ${milestone.milestoneID}` ); } + +type EvaluatedGoalResult = EvaluatedGoalReturn & { goalID: string }; + +/** + * Evaluate a user's progress on a milestone, regardless of whether they have it + * assigned or not. + * + * @returns All of the goals in the milestone. The users progress on each individual goal, + * their overall progress, what the milestone was outOf, and whether they achieved it or + * not. + */ +export async function EvaluateMilestoneProgress(userID: integer, milestone: MilestoneDocument) { + const goals = await GetGoalsInMilestone(milestone); + + const isSubscribedToMilestone = await db["milestone-subs"].findOne({ + milestoneID: milestone.milestoneID, + }); + + // If the user is subscribed the milestone, we don't need to calculate + // their progress on each goal. + const goalSubMap = new Map(); + if (isSubscribedToMilestone) { + const goalSubs = await db["goal-subs"].find({ + goalID: { $in: goals.map((e) => e.goalID) }, + userID, + }); + + for (const sub of goalSubs) { + goalSubMap.set(sub.goalID, sub); + } + } + + const results: EvaluatedGoalResult[] = await Promise.all( + goals.map(async (goal) => { + if (isSubscribedToMilestone) { + const goalSub = goalSubMap.get(goal.goalID); + + if (!goalSub) { + logger.error( + `User ${userID} has a corrupt subscription to milestone '${milestone.name}', They do not have all the goals in this milestone assigned.` + ); + + throw new Error( + `User has corrupt subscription to milestone. Cannot calculate.` + ); + } + + return { + achieved: goalSub.achieved, + progress: goalSub.progress, + outOf: goalSub.outOf, + progressHuman: goalSub.progressHuman, + outOfHuman: goalSub.outOfHuman, + goalID: goal.goalID, + }; + } + + const result = await EvaluateGoalForUser(goal, userID, logger); + + if (!result) { + logger.error( + `Failed to calculate ${userID} result for goal '${goal.title}'. Is the goal valid?`, + { goal, milestone } + ); + + throw new Error(`Goal inside milestone is corrupt.`); + } + + return { + achieved: result.achieved, + progress: result.progress, + outOf: result.outOf, + progressHuman: result.progressHuman, + outOfHuman: result.outOfHuman, + goalID: goal.goalID, + }; + }) + ); + + const progress = results.filter((e) => e.achieved).length; + const outOf = CalculateMilestoneOutOf(milestone); + + const achieved = progress >= outOf; + + return { + goals, + results, + achieved, + progress, + outOf, + }; +} From ff7d9ab82fc5c75672fa3222039e0115be3ab063 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Tue, 29 Mar 2022 16:14:20 +0100 Subject: [PATCH 014/106] Untested Milestone Functionality --- server/src/lib/targets/goals.ts | 1 - server/src/lib/targets/milestones.ts | 78 +++++++++++++++++-- .../_playtype/targets/milestones/router.ts | 6 +- 3 files changed, 78 insertions(+), 7 deletions(-) diff --git a/server/src/lib/targets/goals.ts b/server/src/lib/targets/goals.ts index 0f4ba6673..d5c10098a 100644 --- a/server/src/lib/targets/goals.ts +++ b/server/src/lib/targets/goals.ts @@ -332,7 +332,6 @@ export async function SubscribeToGoal( game: goalDocument.game, playtype: goalDocument.playtype, goalID: goalDocument.goalID, - achieved: result.achieved, wasInstantlyAchieved: result.achieved, }; diff --git a/server/src/lib/targets/milestones.ts b/server/src/lib/targets/milestones.ts index 9794b4eae..1eb4b52da 100644 --- a/server/src/lib/targets/milestones.ts +++ b/server/src/lib/targets/milestones.ts @@ -1,7 +1,12 @@ import db from "external/mongo/db"; import CreateLogCtx from "lib/logger/logger"; -import { GoalSubscriptionDocument, integer, MilestoneDocument } from "tachi-common"; -import { EvaluatedGoalReturn, EvaluateGoalForUser } from "./goals"; +import { + GoalSubscriptionDocument, + integer, + MilestoneDocument, + MilestoneSubscriptionDocument, +} from "tachi-common"; +import { EvaluatedGoalReturn, EvaluateGoalForUser, SubscribeToGoal } from "./goals"; const logger = CreateLogCtx(__filename); @@ -107,7 +112,7 @@ export async function EvaluateMilestoneProgress(userID: integer, milestone: Mile } } - const results: EvaluatedGoalResult[] = await Promise.all( + const goalResults: EvaluatedGoalResult[] = await Promise.all( goals.map(async (goal) => { if (isSubscribedToMilestone) { const goalSub = goalSubMap.get(goal.goalID); @@ -154,16 +159,79 @@ export async function EvaluateMilestoneProgress(userID: integer, milestone: Mile }) ); - const progress = results.filter((e) => e.achieved).length; + const progress = goalResults.filter((e) => e.achieved).length; const outOf = CalculateMilestoneOutOf(milestone); const achieved = progress >= outOf; return { goals, - results, + goalResults, achieved, progress, outOf, }; } + +/** + * Subscribes the given user to a provided milestone. If the user is already subscribed, + * null is returned. + * + * @param cancelIfAchieved - Don't subscribe to the milestone if subscribing would cause + * the user to immediately achieve it. + */ +export async function SubscribeToMilestone( + userID: integer, + milestone: MilestoneDocument, + cancelIfAchieved = true +) { + const isSubscribedToMilestone = await db["milestone-subs"].findOne({ + userID, + milestoneID: milestone.milestoneID, + }); + + if (isSubscribedToMilestone) { + return null; + } + + const result = await EvaluateMilestoneProgress(userID, milestone); + + if (result.achieved && cancelIfAchieved) { + return null; + } + + // @ts-expect-error TS can't resolve this. + // because it can't explode out the types. + const milestoneSub: MilestoneSubscriptionDocument = { + progress: result.progress, + userID, + milestoneID: milestone.milestoneID, + wasInstantlyAchieved: result.achieved, + timeSet: Date.now(), + game: milestone.game, + playtype: milestone.playtype, + achieved: result.achieved, + timeAchieved: result.achieved ? Date.now() : null, + }; + + // @optimisable, EvaluateMilestoneProgress calculates the users progress + // on each goal. We could probably shorten this by directly inserting the records + // from result.goalResults ourselves. + // evaluating goals is fairly cheap though. + await Promise.all( + result.goals.map((goal) => + SubscribeToGoal( + userID, + goal, + { milestoneID: milestone.milestoneID, origin: "milestone" }, + false + ) + ) + ); + + await db["milestone-subs"].insert(milestoneSub); + + logger.info(`User ${userID} subscribed to '${milestone.name}'.`); + + return { milestoneSub, goals: result.goals, goalResults: result.goalResults }; +} diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/milestones/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/milestones/router.ts index ae62f7193..b06b56d0f 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/milestones/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/milestones/router.ts @@ -80,7 +80,11 @@ router.get("/:milestoneID", GetMilestoneSubscription, async (req, res) => { const milestone = await GetMilestoneForIDGuaranteed(milestoneSub.milestoneID); - const { progress, results, goals } = await EvaluateMilestoneProgress(user.id, milestone); + const { + progress, + goalResults: results, + goals, + } = await EvaluateMilestoneProgress(user.id, milestone); return res.status(200).json({ success: true, From c8673e27b6db69a482f1d116f6236304ffebb38d Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Tue, 29 Mar 2022 16:16:20 +0100 Subject: [PATCH 015/106] Rename goal.title to goal.name --- server/package.json | 2 +- server/pnpm-lock.yaml | 8 +++--- server/src/lib/targets/milestones.ts | 2 +- .../_game/_playtype/targets/goals/router.ts | 6 ++--- .../_game/_playtype/targets/goals/router.ts | 4 +-- .../_playtype/targets/milestones/router.ts | 25 +++++++------------ 6 files changed, 20 insertions(+), 27 deletions(-) diff --git a/server/package.json b/server/package.json index 31cc75c29..29b19fb05 100644 --- a/server/package.json +++ b/server/package.json @@ -84,7 +84,7 @@ "rimraf": "3.0.2", "safe-json-stringify": "1.2.0", "seq-logging": "1.1.1", - "tachi-common": "0.7.5", + "tachi-common": "0.7.6", "ts-node": "10.4.0", "tsconfig-paths": "3.12.0", "typescript": "4.5.5", diff --git a/server/pnpm-lock.yaml b/server/pnpm-lock.yaml index 26a7b674e..a2871c6c0 100644 --- a/server/pnpm-lock.yaml +++ b/server/pnpm-lock.yaml @@ -65,7 +65,7 @@ specifiers: safe-json-stringify: 1.2.0 seq-logging: 1.1.1 supertest: 6.2.2 - tachi-common: 0.7.5 + tachi-common: 0.7.6 tap: 15.1.6 ts-node: 10.4.0 tsconfig-paths: 3.12.0 @@ -112,7 +112,7 @@ dependencies: rimraf: 3.0.2 safe-json-stringify: 1.2.0 seq-logging: 1.1.1 - tachi-common: 0.7.5 + tachi-common: 0.7.6 ts-node: 10.4.0_2615db9039ce432b4abf2fc39ef336ec tsconfig-paths: 3.12.0 typescript: 4.5.5 @@ -5363,8 +5363,8 @@ packages: engines: {node: '>= 0.4'} dev: true - /tachi-common/0.7.5: - resolution: {integrity: sha512-F++XGcYlIQqXbwl82qJWYwzvv41ykmXed6j0aMauW2TyOJbuSM04Sw8j0TONXlU9X1QsIWLahG8I2p0wLrT0Qw==} + /tachi-common/0.7.6: + resolution: {integrity: sha512-stdhYYWvHMGmaqnIszmymAbIhwZcMXXXP4hhosayZzCaWlzZENqXA21wAwrFn+BiazWpTLdSn46KP+94Jg06OA==} dependencies: '@types/mongodb': 3.6.18 dev: false diff --git a/server/src/lib/targets/milestones.ts b/server/src/lib/targets/milestones.ts index 1eb4b52da..6103705d6 100644 --- a/server/src/lib/targets/milestones.ts +++ b/server/src/lib/targets/milestones.ts @@ -141,7 +141,7 @@ export async function EvaluateMilestoneProgress(userID: integer, milestone: Mile if (!result) { logger.error( - `Failed to calculate ${userID} result for goal '${goal.title}'. Is the goal valid?`, + `Failed to calculate ${userID} result for goal '${goal.name}'. Is the goal valid?`, { goal, milestone } ); diff --git a/server/src/server/router/api/v1/games/_game/_playtype/targets/goals/router.ts b/server/src/server/router/api/v1/games/_game/_playtype/targets/goals/router.ts index 6c3cdf7e1..06e396d55 100644 --- a/server/src/server/router/api/v1/games/_game/_playtype/targets/goals/router.ts +++ b/server/src/server/router/api/v1/games/_game/_playtype/targets/goals/router.ts @@ -94,7 +94,7 @@ router.get("/:goalID", ResolveGoalID, async (req, res) => { return res.status(200).json({ success: true, - description: `Retrieved information about ${goal.title}.`, + description: `Retrieved information about ${goal.name}.`, body: { goal, goalSubs, @@ -170,7 +170,7 @@ router.get( if (!results) { throw new Error( - `Failed to evaluate goal ${goal.title} (${goal.goalID}) for user ${user.id}. More information above.` + `Failed to evaluate goal ${goal.name} (${goal.goalID}) for user ${user.id}. More information above.` ); } @@ -179,7 +179,7 @@ router.get( return res.status(200).json({ success: true, - description: `Evaluated ${goal.title} for ${user.username}.`, + description: `Evaluated ${goal.name} for ${user.username}.`, body: goalResults, }); } diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts index 8e606c492..4f0ebf8fa 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts @@ -176,7 +176,7 @@ router.post( return res.status(200).json({ success: true, - description: `Subscribed to ${goal.title}.`, + description: `Subscribed to ${goal.name}.`, body: { goal, goalSub, @@ -228,7 +228,7 @@ router.get("/:goalID", GetGoalSubscription, async (req, res) => { return res.status(200).json({ success: true, - description: `Returned information about goal ${goal.title}.`, + description: `Returned information about goal ${goal.name}.`, body: { goal, goalSub, diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/milestones/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/milestones/router.ts index b06b56d0f..3c0dd0e54 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/milestones/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/milestones/router.ts @@ -2,15 +2,14 @@ import { RequestHandler, Router } from "express"; import db from "external/mongo/db"; import { SYMBOL_TachiData } from "lib/constants/tachi"; import { - CalculateMilestoneOutOf, EvaluateMilestoneProgress, - GetGoalsInMilestone, + SubscribeToMilestone, } from "lib/targets/milestones"; import { RequirePermissions } from "server/middleware/auth"; import { MilestoneSubscriptionDocument } from "tachi-common"; import { GetMilestoneForIDGuaranteed } from "utils/db"; import { AssignToReqTachiData } from "utils/req-tachi-data"; -import { FormatUserDoc } from "utils/user"; +import { FormatUserDoc, GetUserWithID } from "utils/user"; const router: Router = Router({ mergeParams: true }); @@ -104,7 +103,7 @@ router.get("/:milestoneID", GetMilestoneSubscription, async (req, res) => { * * @name PUT /api/v1/users/:userID/games/:game/:playtype/targets/milestones/:milestoneID */ -router.put("/:milestoneID", RequirePermissions(""), async (req, res) => { +router.put("/:milestoneID", RequirePermissions("manage_targets"), async (req, res) => { const user = req[SYMBOL_TachiData]!.requestedUser!; const game = req[SYMBOL_TachiData]!.game!; const playtype = req[SYMBOL_TachiData]!.playtype!; @@ -147,7 +146,7 @@ router.put("/:milestoneID", RequirePermissions(""), async (req, res) => { }); } - const { progress, achieved } = await EvaluateMilestoneProgress(user.id, milestone); + const x = await SubscribeToMilestone(user.id, milestone); if (achieved) { return res.status(400).json({ @@ -156,17 +155,11 @@ router.put("/:milestoneID", RequirePermissions(""), async (req, res) => { }); } - const milestoneSub: MilestoneSubscriptionDocument = { - achieved, - game, - milestoneID: milestone.milestoneID, - playtype, - progress, - timeAchieved: null, - timeSet: Date.now(), - userID: user.id, - wasInstantlyAchieved: false, - }; + return res.status(200).json({ + success: true, + description: `Subscribed to milestone '${milestone.name}'.`, + body: + }); }); export default router; From 19a1220ca1412b12ed577a38b4221ce005709690 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Tue, 29 Mar 2022 16:19:07 +0100 Subject: [PATCH 016/106] Assorted tsc fixes --- server/src/lib/score-import/framework/goals/goals.test.ts | 8 ++++---- .../lib/score-import/framework/milestones/milestones.ts | 2 +- server/src/lib/targets/goals.ts | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/server/src/lib/score-import/framework/goals/goals.test.ts b/server/src/lib/score-import/framework/goals/goals.test.ts index 3c421a980..935ec6d99 100644 --- a/server/src/lib/score-import/framework/goals/goals.test.ts +++ b/server/src/lib/score-import/framework/goals/goals.test.ts @@ -30,7 +30,7 @@ t.test("#GetRelevantFolderGoals", (t) => { goalID: "fake_goal_id", playtype: "SP", timeAdded: 0, - title: "get > 1 ex score on any level 10.", + name: "get > 1 ex score on any level 10.", criteria: { mode: "single", value: 1, @@ -47,7 +47,7 @@ t.test("#GetRelevantFolderGoals", (t) => { goalID: "fake_bad_goal_id", playtype: "SP", timeAdded: 0, - title: "get > 1 ex score on some other folder.", + name: "get > 1 ex score on some other folder.", criteria: { mode: "single", value: 1, @@ -106,7 +106,7 @@ t.test("#GetRelevantGoals", (t) => { goalID: crypto.randomBytes(20).toString("hex"), playtype: "SP", timeAdded: 0, - title: "get > 1 ex score on some other folder.", + name: "get > 1 ex score on some other folder.", criteria: { mode: "single", value: 1, @@ -174,7 +174,7 @@ t.test("#UpdateGoalsForUser", (t) => { goalID: "FAKE_GOAL_ID", playtype: "SP", timeAdded: 0, - title: "get > 1 ex score on some other folder.", + name: "get > 1 ex score on some other folder.", criteria: { mode: "single", value: 1, diff --git a/server/src/lib/score-import/framework/milestones/milestones.ts b/server/src/lib/score-import/framework/milestones/milestones.ts index cec2018c2..db36a74cb 100644 --- a/server/src/lib/score-import/framework/milestones/milestones.ts +++ b/server/src/lib/score-import/framework/milestones/milestones.ts @@ -39,7 +39,7 @@ export function ProcessMilestoneFromGII( progress++; } - const outOf = CalculateMilestoneOutOf(milestone, goalIDs); + const outOf = CalculateMilestoneOutOf(milestone); // milestone achieved! if (progress >= outOf) { diff --git a/server/src/lib/targets/goals.ts b/server/src/lib/targets/goals.ts index d5c10098a..3ffa66834 100644 --- a/server/src/lib/targets/goals.ts +++ b/server/src/lib/targets/goals.ts @@ -13,7 +13,7 @@ import { GoalSubscriptionDocument, } from "tachi-common"; import { GetFolderChartIDs } from "utils/folder"; -import { CreateGoalTitle, ValidateGoalChartsAndCriteria } from "./goal-utils"; +import { CreateGoalTitle as CreateGoalName, ValidateGoalChartsAndCriteria } from "./goal-utils"; const logger = CreateLogCtx(__filename); @@ -266,7 +266,7 @@ export async function ConstructGoal( criteria, charts, goalID: CreateGoalID(charts, criteria, game, playtype), - title: await CreateGoalTitle(charts, criteria, game, playtype), + name: await CreateGoalName(charts, criteria, game, playtype), } as GoalDocument; } @@ -295,7 +295,7 @@ export async function SubscribeToGoal( if (!goalExists) { await db.goals.insert(goalDocument); - logger.info(`Inserting new goal ${goalDocument.title}.`); + logger.info(`Inserting new goal '${goalDocument.name}'.`); } const userAlreadySubscribed = await db["goal-subs"].findOne({ From 5dc667d2a18124a94d00c29a264cf4e9931194d4 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Tue, 29 Mar 2022 16:47:47 +0100 Subject: [PATCH 017/106] Tests pass again --- server/src/external/mongo/schemas.ts | 6 ++++++ server/src/test-utils/test-data.ts | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/server/src/external/mongo/schemas.ts b/server/src/external/mongo/schemas.ts index 9bd076f53..a0c21fac2 100644 --- a/server/src/external/mongo/schemas.ts +++ b/server/src/external/mongo/schemas.ts @@ -719,4 +719,10 @@ export const DatabaseSchemas: Record< folderID: "string", lastViewed: "number", }), + "milestone-sets": prSchemaify({ + setID: "string", + game: p.isIn(games), + playtype: isValidPlaytype, + milestones: ["string"], + }), }; diff --git a/server/src/test-utils/test-data.ts b/server/src/test-utils/test-data.ts index f9a22bca5..020ed3413 100644 --- a/server/src/test-utils/test-data.ts +++ b/server/src/test-utils/test-data.ts @@ -414,7 +414,7 @@ export const HC511Goal: GoalDocument = { goalID: "mock_goalID", playtype: "SP", timeAdded: 0, - title: "HC 5.1.1. SPA", + name: "HC 5.1.1. SPA", criteria: { mode: "single", value: 5, From fb5c9503c3ee18b578e92570b8c6f1b3b7fe5ad4 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Wed, 30 Mar 2022 03:57:47 +0100 Subject: [PATCH 018/106] More general progress --- server/src/lib/constants/err-codes.ts | 4 + server/src/lib/targets/goals.ts | 6 +- server/src/lib/targets/milestones.ts | 48 ++++- server/src/lib/targets/types.ts | 0 .../_game/_playtype/targets/goals/router.ts | 4 +- .../_playtype/targets/milestones/router.ts | 179 ++++++++++++------ server/src/utils/types.ts | 2 + 7 files changed, 170 insertions(+), 73 deletions(-) create mode 100644 server/src/lib/constants/err-codes.ts create mode 100644 server/src/lib/targets/types.ts diff --git a/server/src/lib/constants/err-codes.ts b/server/src/lib/constants/err-codes.ts new file mode 100644 index 000000000..1b3ddbe32 --- /dev/null +++ b/server/src/lib/constants/err-codes.ts @@ -0,0 +1,4 @@ +export enum SubscribeFailReasons { + ALREADY_SUBSCRIBED, + ALREADY_ACHIEVED, +} diff --git a/server/src/lib/targets/goals.ts b/server/src/lib/targets/goals.ts index 3ffa66834..b2c820585 100644 --- a/server/src/lib/targets/goals.ts +++ b/server/src/lib/targets/goals.ts @@ -1,5 +1,6 @@ import db from "external/mongo/db"; import fjsh from "fast-json-stable-hash"; +import { SubscribeFailReasons } from "lib/constants/err-codes"; import CreateLogCtx, { KtLogger } from "lib/logger/logger"; import { FilterQuery } from "mongodb"; import { @@ -270,11 +271,6 @@ export async function ConstructGoal( } as GoalDocument; } -export enum SubscribeFailReasons { - ALREADY_SUBSCRIBED, - ALREADY_ACHIEVED, -} - /** * Subscribes a user to the provided goal document. Handles deduping goals naturally * and general good stuff. diff --git a/server/src/lib/targets/milestones.ts b/server/src/lib/targets/milestones.ts index 6103705d6..210d57359 100644 --- a/server/src/lib/targets/milestones.ts +++ b/server/src/lib/targets/milestones.ts @@ -1,4 +1,5 @@ import db from "external/mongo/db"; +import { SubscribeFailReasons } from "lib/constants/err-codes"; import CreateLogCtx from "lib/logger/logger"; import { GoalSubscriptionDocument, @@ -191,13 +192,13 @@ export async function SubscribeToMilestone( }); if (isSubscribedToMilestone) { - return null; + return SubscribeFailReasons.ALREADY_SUBSCRIBED; } const result = await EvaluateMilestoneProgress(userID, milestone); if (result.achieved && cancelIfAchieved) { - return null; + return SubscribeFailReasons.ALREADY_ACHIEVED; } // @ts-expect-error TS can't resolve this. @@ -219,14 +220,30 @@ export async function SubscribeToMilestone( // from result.goalResults ourselves. // evaluating goals is fairly cheap though. await Promise.all( - result.goals.map((goal) => - SubscribeToGoal( + result.goals.map(async (goal) => { + const res = await SubscribeToGoal( userID, goal, { milestoneID: milestone.milestoneID, origin: "milestone" }, false - ) - ) + ); + + // If the user is already subscribed to this goal, maybe manually + if (res === SubscribeFailReasons.ALREADY_SUBSCRIBED) { + await db["goal-subs"].update( + { + userID, + milestoneID: milestone.milestoneID, + }, + { + $set: { + origin: "milestone", + milestoneID: milestone.milestoneID, + }, + } + ); + } + }) ); await db["milestone-subs"].insert(milestoneSub); @@ -235,3 +252,22 @@ export async function SubscribeToMilestone( return { milestoneSub, goals: result.goals, goalResults: result.goalResults }; } + +export async function UnsubscribeFromMilestone(userID: integer, milestone: MilestoneDocument) { + const goalIDs = GetGoalIDsFromMilestone(milestone); + + // Remove all attached goals subs to this milestone. + // This is a bit of a pain, since it means users can remove goals they legitimately + // have assigned on their own by subscribing to a milestone that also has that goal + // then unsubscribing. + // ah well. + await db["goal-subs"].remove({ + goalID: { $in: goalIDs }, + userID, + }); + + await db["milestone-subs"].remove({ + userID, + milestoneID: milestone.milestoneID, + }); +} diff --git a/server/src/lib/targets/types.ts b/server/src/lib/targets/types.ts new file mode 100644 index 000000000..e69de29bb diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts index 4f0ebf8fa..44fab8802 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts @@ -228,7 +228,7 @@ router.get("/:goalID", GetGoalSubscription, async (req, res) => { return res.status(200).json({ success: true, - description: `Returned information about goal ${goal.name}.`, + description: `Returned information about goal '${goal.name}'.`, body: { goal, goalSub, @@ -239,7 +239,7 @@ router.get("/:goalID", GetGoalSubscription, async (req, res) => { }); /** - * Removes a goal from your profile. + * Unsubscribe from a goal. * * @name DELETE /api/v1/users/:userID/games/:game/:playtype/targets/goals/:goalID */ diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/milestones/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/milestones/router.ts index 3c0dd0e54..2aae070e2 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/milestones/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/milestones/router.ts @@ -1,15 +1,19 @@ import { RequestHandler, Router } from "express"; import db from "external/mongo/db"; +import { SubscribeFailReasons } from "lib/constants/err-codes"; import { SYMBOL_TachiData } from "lib/constants/tachi"; +import CreateLogCtx from "lib/logger/logger"; import { EvaluateMilestoneProgress, SubscribeToMilestone, + UnsubscribeFromMilestone, } from "lib/targets/milestones"; import { RequirePermissions } from "server/middleware/auth"; -import { MilestoneSubscriptionDocument } from "tachi-common"; import { GetMilestoneForIDGuaranteed } from "utils/db"; import { AssignToReqTachiData } from "utils/req-tachi-data"; -import { FormatUserDoc, GetUserWithID } from "utils/user"; +import { FormatUserDoc } from "utils/user"; + +const logger = CreateLogCtx(__filename); const router: Router = Router({ mergeParams: true }); @@ -67,23 +71,44 @@ const GetMilestoneSubscription: RequestHandler = async (req, res, next) => { return next(); }; +const GetMilestone: RequestHandler = async (req, res, next) => { + const user = req[SYMBOL_TachiData]!.requestedUser!; + const game = req[SYMBOL_TachiData]!.game!; + const playtype = req[SYMBOL_TachiData]!.playtype!; + + const milestone = await db.milestones.findOne({ + game, + playtype, + milestoneID: req.params.milestoneID, + }); + + if (!milestone) { + return res.status(404).json({ + success: false, + description: `Can't find a milestone with id '${req.params.milestoneID}'.`, + }); + } + + AssignToReqTachiData(req, { milestoneDoc: milestone }); + + return next(); +}; + /** * Returns this user's progress on this milestone. * This also evaluates individual progress on all of the milestones goals. * * @name GET /api/v1/users/:userID/games/:game/:playtype/targets/milestones/:milestoneID */ -router.get("/:milestoneID", GetMilestoneSubscription, async (req, res) => { +router.get("/:milestoneID", GetMilestone, GetMilestoneSubscription, async (req, res) => { const user = req[SYMBOL_TachiData]!.requestedUser!; const milestoneSub = req[SYMBOL_TachiData]!.milestoneSubDoc!; + const milestone = req[SYMBOL_TachiData]!.milestoneDoc!; - const milestone = await GetMilestoneForIDGuaranteed(milestoneSub.milestoneID); - - const { - progress, - goalResults: results, - goals, - } = await EvaluateMilestoneProgress(user.id, milestone); + // Evaluate each goal for the user. This operation is much faster if the user is + // subscribed to the milestone (they are), as we can just read their goalSub + // for each goal. + const { goalResults: results, goals } = await EvaluateMilestoneProgress(user.id, milestone); return res.status(200).json({ success: true, @@ -103,63 +128,97 @@ router.get("/:milestoneID", GetMilestoneSubscription, async (req, res) => { * * @name PUT /api/v1/users/:userID/games/:game/:playtype/targets/milestones/:milestoneID */ -router.put("/:milestoneID", RequirePermissions("manage_targets"), async (req, res) => { - const user = req[SYMBOL_TachiData]!.requestedUser!; - const game = req[SYMBOL_TachiData]!.game!; - const playtype = req[SYMBOL_TachiData]!.playtype!; +router.put( + "/:milestoneID", + GetMilestone, + RequirePermissions("manage_targets"), + async (req, res) => { + const user = req[SYMBOL_TachiData]!.requestedUser!; + const game = req[SYMBOL_TachiData]!.game!; + const playtype = req[SYMBOL_TachiData]!.playtype!; - const existingMilestonesCount = await db["milestone-subs"].count({ - userID: user.id, - game, - playtype, - }); + const existingMilestonesCount = await db["milestone-subs"].count({ + userID: user.id, + game, + playtype, + }); - if (existingMilestonesCount > 100) { - return res.status(400).json({ - success: false, - description: `You already have 100 milestones. You cannot have anymore for this game.`, + if (existingMilestonesCount > 100) { + return res.status(400).json({ + success: false, + description: `You already have 100 milestones. You cannot have anymore for this game.`, + }); + } + + const milestone = req[SYMBOL_TachiData]!.milestoneDoc!; + + const alreadySubscibed = await db["milestone-subs"].findOne({ + userID: user.id, + milestoneID: milestone.milestoneID, + }); + + if (alreadySubscibed) { + return res.status(409).json({ + success: false, + description: `You are already subscribed to this goal.`, + }); + } + + const subResult = await SubscribeToMilestone(user.id, milestone); + + if (subResult === SubscribeFailReasons.ALREADY_ACHIEVED) { + return res.status(400).json({ + success: false, + description: `You cannot assign a milestone that would be immediately achieved.`, + }); + } + + if (subResult === SubscribeFailReasons.ALREADY_SUBSCRIBED) { + return res.status(409).json({ + success: false, + description: `You're already subscribed to this milestone.`, + }); + } + + return res.status(200).json({ + success: true, + description: `Subscribed to milestone '${milestone.name}'.`, + body: subResult, }); } +); - const milestone = await db.milestones.findOne({ - milestoneID: req.params.milestoneID, - game, - playtype, - }); +/** + * Unsubscribe from a milestone. + * + * @name DELETE /api/v1/users/:userID/games/:game/:playtype/targets/milestones/:milestoneID + */ +router.delete( + "/:milestoneID", + GetMilestone, + RequirePermissions("manage_targets"), + async (req, res) => { + const user = req[SYMBOL_TachiData]!.requestedUser!; + const milestone = req[SYMBOL_TachiData]!.milestoneDoc!; - if (!milestone) { - return res.status(404).json({ - success: false, - description: `A milestone with the ID ${req.params.milestoneID} does not exist.`, + logger.info( + `User ${FormatUserDoc(user)} is unsubscribing from milestone '${milestone.name}'.`, + { + milestone, + user, + } + ); + + await UnsubscribeFromMilestone(user.id, milestone); + + return res.status(200).json({ + success: true, + description: `Unsubscribed from milestone.`, + body: { + milestone, + }, }); } - - const alreadySubscibed = await db["milestone-subs"].findOne({ - userID: user.id, - milestoneID: milestone.milestoneID, - }); - - if (alreadySubscibed) { - return res.status(409).json({ - success: false, - description: `You are already subscribed to this goal.`, - }); - } - - const x = await SubscribeToMilestone(user.id, milestone); - - if (achieved) { - return res.status(400).json({ - success: false, - description: `You cannot assign a milestone that would be immediately achieved.`, - }); - } - - return res.status(200).json({ - success: true, - description: `Subscribed to milestone '${milestone.name}'.`, - body: - }); -}); +); export default router; diff --git a/server/src/utils/types.ts b/server/src/utils/types.ts index 8639e70a0..2d7c3fd15 100644 --- a/server/src/utils/types.ts +++ b/server/src/utils/types.ts @@ -14,6 +14,7 @@ import { GoalDocument, MilestoneSubscriptionDocument, GoalSubscriptionDocument, + MilestoneDocument, } from "tachi-common"; declare module "express-session" { @@ -66,6 +67,7 @@ export interface TachiRequestData { tableDoc?: TableDocument; folderDoc?: FolderDocument; goalDoc?: GoalDocument; + milestoneDoc?: MilestoneDocument; goalSubDoc?: GoalSubscriptionDocument; milestoneSubDoc?: MilestoneSubscriptionDocument; From 73c7011e89d66cb164da82baf9ef51eae157e2cd Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Wed, 30 Mar 2022 04:18:43 +0100 Subject: [PATCH 019/106] Refactor to my slightly unhinged push/pull parentMilestones system --- server/package.json | 2 +- server/pnpm-lock.yaml | 8 ++-- .../framework/goals/goals.test.ts | 8 +--- server/src/lib/targets/goals.ts | 7 ++-- server/src/lib/targets/milestones.ts | 40 ++++++++++++------- .../_game/_playtype/targets/goals/router.ts | 21 +++++----- server/src/test-utils/test-data.ts | 4 +- 7 files changed, 49 insertions(+), 41 deletions(-) diff --git a/server/package.json b/server/package.json index 29b19fb05..191f87408 100644 --- a/server/package.json +++ b/server/package.json @@ -84,7 +84,7 @@ "rimraf": "3.0.2", "safe-json-stringify": "1.2.0", "seq-logging": "1.1.1", - "tachi-common": "0.7.6", + "tachi-common": "0.7.7", "ts-node": "10.4.0", "tsconfig-paths": "3.12.0", "typescript": "4.5.5", diff --git a/server/pnpm-lock.yaml b/server/pnpm-lock.yaml index a2871c6c0..ff0e79f75 100644 --- a/server/pnpm-lock.yaml +++ b/server/pnpm-lock.yaml @@ -65,7 +65,7 @@ specifiers: safe-json-stringify: 1.2.0 seq-logging: 1.1.1 supertest: 6.2.2 - tachi-common: 0.7.6 + tachi-common: 0.7.7 tap: 15.1.6 ts-node: 10.4.0 tsconfig-paths: 3.12.0 @@ -112,7 +112,7 @@ dependencies: rimraf: 3.0.2 safe-json-stringify: 1.2.0 seq-logging: 1.1.1 - tachi-common: 0.7.6 + tachi-common: 0.7.7 ts-node: 10.4.0_2615db9039ce432b4abf2fc39ef336ec tsconfig-paths: 3.12.0 typescript: 4.5.5 @@ -5363,8 +5363,8 @@ packages: engines: {node: '>= 0.4'} dev: true - /tachi-common/0.7.6: - resolution: {integrity: sha512-stdhYYWvHMGmaqnIszmymAbIhwZcMXXXP4hhosayZzCaWlzZENqXA21wAwrFn+BiazWpTLdSn46KP+94Jg06OA==} + /tachi-common/0.7.7: + resolution: {integrity: sha512-WsV4bCKdhackC24aTt7vdJTz/IEdRHiSm3+NeDWlU3AOmdMQV8YTcMCjeNscTr4yoccjpRNNG/avs+h/fFLDeg==} dependencies: '@types/mongodb': 3.6.18 dev: false diff --git a/server/src/lib/score-import/framework/goals/goals.test.ts b/server/src/lib/score-import/framework/goals/goals.test.ts index 935ec6d99..a19c60483 100644 --- a/server/src/lib/score-import/framework/goals/goals.test.ts +++ b/server/src/lib/score-import/framework/goals/goals.test.ts @@ -131,9 +131,7 @@ t.test("#GetRelevantGoals", (t) => { progressHuman: "NO DATA", timeSet: Date.now(), userID: 1, - from: { - origin: "manual", - }, + parentMilestones: [], })) ); }); @@ -196,9 +194,7 @@ t.test("#UpdateGoalsForUser", (t) => { timeAchieved: null, timeSet: 0, userID: 1, - from: { - origin: "manual", - }, + parentMilestones: [], }; t.test("Should correctly update goals when user achieves goal.", async (t) => { diff --git a/server/src/lib/targets/goals.ts b/server/src/lib/targets/goals.ts index b2c820585..fb8dd594f 100644 --- a/server/src/lib/targets/goals.ts +++ b/server/src/lib/targets/goals.ts @@ -7,7 +7,6 @@ import { Game, GetGamePTConfig, GoalDocument, - GoalOrigin, integer, PBScoreDocument, Playtypes, @@ -284,7 +283,7 @@ export async function ConstructGoal( export async function SubscribeToGoal( userID: integer, goalDocument: GoalDocument, - origin: GoalOrigin, + parentMilestone?: string, cancelIfAchieved = true ) { const goalExists = await db.goals.findOne({ goalID: goalDocument.goalID }); @@ -324,7 +323,9 @@ export async function SubscribeToGoal( lastInteraction: Date.now(), timeAchieved: result.achieved ? Date.now() : null, timeSet: Date.now(), - from: origin, + // if this goal subscription came from a milestone, add the milestone to + // the list of parents. + parentMilestones: parentMilestone ? [parentMilestone] : [], game: goalDocument.game, playtype: goalDocument.playtype, goalID: goalDocument.goalID, diff --git a/server/src/lib/targets/milestones.ts b/server/src/lib/targets/milestones.ts index 210d57359..b99fae9e9 100644 --- a/server/src/lib/targets/milestones.ts +++ b/server/src/lib/targets/milestones.ts @@ -221,14 +221,11 @@ export async function SubscribeToMilestone( // evaluating goals is fairly cheap though. await Promise.all( result.goals.map(async (goal) => { - const res = await SubscribeToGoal( - userID, - goal, - { milestoneID: milestone.milestoneID, origin: "milestone" }, - false - ); + const res = await SubscribeToGoal(userID, goal, milestone.milestoneID, false); - // If the user is already subscribed to this goal, maybe manually + // If the user is already subscribed to this goal -- i.e. manually or as part + // of another milestone + // add this milestoneID to the list of parents instead. if (res === SubscribeFailReasons.ALREADY_SUBSCRIBED) { await db["goal-subs"].update( { @@ -236,9 +233,8 @@ export async function SubscribeToMilestone( milestoneID: milestone.milestoneID, }, { - $set: { - origin: "milestone", - milestoneID: milestone.milestoneID, + $push: { + parentMilestones: milestone.milestoneID, }, } ); @@ -256,16 +252,30 @@ export async function SubscribeToMilestone( export async function UnsubscribeFromMilestone(userID: integer, milestone: MilestoneDocument) { const goalIDs = GetGoalIDsFromMilestone(milestone); - // Remove all attached goals subs to this milestone. - // This is a bit of a pain, since it means users can remove goals they legitimately - // have assigned on their own by subscribing to a milestone that also has that goal - // then unsubscribing. - // ah well. + // Pull this milestone ID from all of the goalSubscriptions that have it. + // since it's no longer going to be their parent. + await db["goal-subs"].update( + { + goalID: { $in: goalIDs }, + userID, + parentMilestones: milestone.milestoneID, + }, + { + $pull: { + parentMilestones: milestone.milestoneID, + }, + } + ); + + // then, remove all of the ones that now have no parent blocking their demise. + // that's pretty morbid, jesus christ. await db["goal-subs"].remove({ goalID: { $in: goalIDs }, userID, + parentMilestones: { $size: 0 }, }); + // remove the user's milestone sub, aswell. await db["milestone-subs"].remove({ userID, milestoneID: milestone.milestoneID, diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts index 44fab8802..387c4348e 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts @@ -3,13 +3,14 @@ import db from "external/mongo/db"; import { SYMBOL_TachiData } from "lib/constants/tachi"; import prValidate from "server/middleware/prudence-validate"; import p from "prudence"; -import { GoalDocument } from "tachi-common"; -import { ConstructGoal, SubscribeFailReasons, SubscribeToGoal } from "lib/targets/goals"; +import { GoalDocument, MilestoneDocument } from "tachi-common"; +import { ConstructGoal, SubscribeToGoal } from "lib/targets/goals"; import CreateLogCtx from "lib/logger/logger"; import { RequirePermissions } from "server/middleware/auth"; import { AssignToReqTachiData } from "utils/req-tachi-data"; import { GetGoalForIDGuaranteed, GetMilestoneForIDGuaranteed } from "utils/db"; import { RequireAuthedAsUser } from "../../../../../middleware"; +import { SubscribeFailReasons } from "lib/constants/err-codes"; const router: Router = Router({ mergeParams: true }); @@ -158,7 +159,7 @@ router.post( }); } - const goalSub = await SubscribeToGoal(user.id, goal, { origin: "manual" }); + const goalSub = await SubscribeToGoal(user.id, goal); if (goalSub === SubscribeFailReasons.ALREADY_SUBSCRIBED) { return res.status(409).json({ @@ -218,10 +219,12 @@ router.get("/:goalID", GetGoalSubscription, async (req, res) => { const user = req[SYMBOL_TachiData]!.requestedUser!; const goalSub = req[SYMBOL_TachiData]!.goalSubDoc!; - let milestone = null; + let milestones: MilestoneDocument[] = []; - if (goalSub.from.origin === "milestone") { - milestone = await GetMilestoneForIDGuaranteed(goalSub.from.milestoneID); + if (goalSub.parentMilestones.length !== 0) { + milestones = await Promise.all( + goalSub.parentMilestones.map((e) => GetMilestoneForIDGuaranteed(e)) + ); } const goal = await GetGoalForIDGuaranteed(goalSub.goalID); @@ -232,7 +235,7 @@ router.get("/:goalID", GetGoalSubscription, async (req, res) => { body: { goal, goalSub, - milestone, + milestones, user, }, }); @@ -269,10 +272,10 @@ router.delete( }); } - if (goalSub.from.origin === "milestone") { + if (goalSub.parentMilestones.length) { return res.status(400).json({ success: false, - description: `This goal is from a milestone. You can't remove it directly, only by removing the parent milestone.`, + description: `This goal is part of a milestone you are subscribed to. It can only be removed by unsubscribing from the relevant milestones.`, }); } diff --git a/server/src/test-utils/test-data.ts b/server/src/test-utils/test-data.ts index 020ed3413..b40ff3703 100644 --- a/server/src/test-utils/test-data.ts +++ b/server/src/test-utils/test-data.ts @@ -436,9 +436,7 @@ export const HC511UserGoal: GoalSubscriptionDocument = { progressHuman: "NO DATA", timeSet: 0, userID: 1, - from: { - origin: "manual", - }, + parentMilestones: [], }; export const TestingIIDXFolderSP10: FolderDocument = { From 25323ccde3e608fcfa45e08cf9d84bd5c4bf866b Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Wed, 30 Mar 2022 23:36:27 +0100 Subject: [PATCH 020/106] Handle case where user goal becomes unachieved but goal is marked as instantlyAchieved --- .../lib/score-import/framework/goals/goals.ts | 31 ++++++++++++++----- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/server/src/lib/score-import/framework/goals/goals.ts b/server/src/lib/score-import/framework/goals/goals.ts index b47e0105c..234721477 100644 --- a/server/src/lib/score-import/framework/goals/goals.ts +++ b/server/src/lib/score-import/framework/goals/goals.ts @@ -132,18 +132,33 @@ export async function ProcessGoal( }; } + const setData = { + ...newData, + timeAchieved: newData.achieved ? Date.now() : null, + // we're guaranteed that this works, because things + // that haven't changed return nothing instead of + // getting to this point. + lastInteraction: Date.now(), + } as Partial; + + // If this goal was achieved, and is now *not* achieved, we need to unset + // some things. + if (goalSub.achieved && !res.achieved) { + logger.info(`User ${userID} lost their achieved status on ${goal.name}.`, { + goal, + res, + goalSub, + }); + + // This goal can't be marked as instantly achieved, since it was lost. + setData.wasInstantlyAchieved = false; + } + const bulkWrite = { updateOne: { filter: { _id: goalSub._id! }, update: { - $set: { - ...newData, - timeAchieved: newData.achieved ? Date.now() : null, - // we're guaranteed that this works, because things - // that haven't changed return nothing instead of - // getting to this point. - lastInteraction: Date.now(), - } as Partial, + $set: setData, }, }, }; From 413100044dc974508022fbfccdf149d2457e0f46 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Wed, 30 Mar 2022 23:57:22 +0100 Subject: [PATCH 021/106] Add regression test in instantlyAchieved goals --- .../framework/goals/goals.test.ts | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/server/src/lib/score-import/framework/goals/goals.test.ts b/server/src/lib/score-import/framework/goals/goals.test.ts index a19c60483..0cbb3956a 100644 --- a/server/src/lib/score-import/framework/goals/goals.test.ts +++ b/server/src/lib/score-import/framework/goals/goals.test.ts @@ -388,6 +388,57 @@ t.test("#ProcessGoal", (t) => { t.end(); }); + t.test("Should unset wasInstantlyAchieved if the goal became unachieved.", async (t) => { + const achievedGoalSub: GoalSubscriptionDocument = { + achieved: true, + game: "iidx", + goalID: "mock_goalID", + lastInteraction: null, + outOf: 5, + outOfHuman: "HARD CLEAR", + parentMilestones: [], + playtype: "SP", + progress: 6, + progressHuman: "EX HARD CLEAR", + timeAchieved: 1000, + timeSet: 1000, + wasInstantlyAchieved: true, + userID: 1, + }; + + await db["goal-subs"].insert(achievedGoalSub); + + const res = await ProcessGoal(HC511Goal, achievedGoalSub, 1, logger); + + t.not(res, undefined, "Should NOT return undefined."); + + t.hasStrict( + res?.import, + { + goalID: "mock_goalID", + old: { + progress: 6, + outOf: 5, + achieved: true, + }, + new: { + progress: null, + outOf: 5, + achieved: false, + }, + }, + "Should unachieve the goal." + ); + + t.equal( + res?.bwrite.updateOne.update.$set.wasInstantlyAchieved, + false, + "Goal is to be set as not instantly achieved." + ); + + t.end(); + }); + t.test("Should return undefined if there's no score.", async (t) => { await db["goal-subs"].insert(HC511UserGoal); From c15b61ecf5f575a34367b0f24d3d9ac2dd3fa4ed Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Wed, 30 Mar 2022 23:58:00 +0100 Subject: [PATCH 022/106] Remove useless/redundant !. invocation in tests --- server/src/lib/score-import/framework/goals/goals.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/lib/score-import/framework/goals/goals.test.ts b/server/src/lib/score-import/framework/goals/goals.test.ts index 0cbb3956a..fd62fa29e 100644 --- a/server/src/lib/score-import/framework/goals/goals.test.ts +++ b/server/src/lib/score-import/framework/goals/goals.test.ts @@ -364,7 +364,7 @@ t.test("#ProcessGoal", (t) => { t.not(res, undefined, "Should NOT return undefined."); t.strictSame( - res!.import, + res?.import, { goalID: "mock_goalID", old: { From 72c8f2d2c9a3c3086ca3e26fd9af7f3c4810e7a0 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Thu, 31 Mar 2022 00:00:07 +0100 Subject: [PATCH 023/106] Pipe webhook error messages to info. --- server/src/lib/webhooks/webhooks.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/server/src/lib/webhooks/webhooks.ts b/server/src/lib/webhooks/webhooks.ts index f55fb4aaa..3f4e08f30 100644 --- a/server/src/lib/webhooks/webhooks.ts +++ b/server/src/lib/webhooks/webhooks.ts @@ -33,6 +33,9 @@ export async function EmitWebhookEvent(content: WebhookEvents) { "Content-Type": "application/json", Authorization: `Bearer ${client.clientSecret}`, }, + }).catch((err) => { + // We don't care about errors. It's probably on their end. + logger.info(err.message); }); } } From e4323fa1bcf7c4e0e7f7db09ef09967f5fcad223 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Thu, 31 Mar 2022 00:00:55 +0100 Subject: [PATCH 024/106] Clean types in webhooks.ts --- server/src/lib/webhooks/webhooks.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/lib/webhooks/webhooks.ts b/server/src/lib/webhooks/webhooks.ts index 3f4e08f30..58d6129f2 100644 --- a/server/src/lib/webhooks/webhooks.ts +++ b/server/src/lib/webhooks/webhooks.ts @@ -12,7 +12,7 @@ export async function GetWebhookUrlInfo() { { projection: { webhookUri: 1, clientSecret: 1 } } ); - return urls; + return urls as { webhookUri: string; clientSecret: string }[]; } /** @@ -26,7 +26,7 @@ export async function EmitWebhookEvent(content: WebhookEvents) { // We don't actually care about the response of these. Just fire them and forget. for (const client of webhookUrls) { // we know this to be non-null because of GetWebhookUrlInfo. - fetch(client.webhookUri!, { + fetch(client.webhookUri, { method: "POST", body: JSON.stringify(content), headers: { From 32f1cb37eb1814ded1596da71050b14b91047062 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Thu, 31 Mar 2022 00:01:17 +0100 Subject: [PATCH 025/106] Remove unused goals/milestones endpoints --- .../games/_game/_playtype/router.test.ts | 143 ------------------ .../_userID/games/_game/_playtype/router.ts | 88 +---------- 2 files changed, 3 insertions(+), 228 deletions(-) diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.test.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.test.ts index aff060fba..eb5323286 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.test.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.test.ts @@ -180,149 +180,6 @@ t.test("GET /api/v1/users/:userID/games/:game/:playtype/history", (t) => { t.end(); }); -t.test("GET /api/v1/users/:userID/games/:game/:playtype/goals", (t) => { - t.beforeEach(ResetDBState); - - t.test( - "Should return all of a users goals, and only unachieved goals if the argument is set.", - async (t) => { - await db["goal-subs"].insert([ - { - goalID: "foo", - userID: 1, - game: "iidx", - playtype: "SP", - achieved: false, - }, - { - goalID: "bar", - userID: 1, - game: "iidx", - playtype: "SP", - achieved: true, - }, - ] as GoalSubscriptionDocument[]); - - await db.goals.insert([{ goalID: "foo" }, { goalID: "bar" }] as GoalDocument[]); - - const res = await mockApi.get("/api/v1/users/1/games/iidx/SP/goals"); - - t.hasStrict(res.body, { - success: true, - description: "Successfully returned 2 goal(s).", - body: { - goalSubs: [ - { - goalID: "foo", - achieved: false, - }, - { - goalID: "bar", - achieved: true, - }, - ], - goals: [{ goalID: "bar" }, { goalID: "foo" }], - }, - }); - - const resUnachieved = await mockApi.get( - "/api/v1/users/1/games/iidx/SP/goals?unachieved=true" - ); - - t.hasStrict(resUnachieved.body, { - success: true, - description: "Successfully returned 1 goal(s).", - body: { - goalSubs: [ - { - goalID: "foo", - achieved: false, - }, - ], - goals: [{ goalID: "foo" }], - }, - }); - - t.end(); - } - ); - - t.end(); -}); - -t.test("GET /api/v1/users/:userID/games/:game/:playtype/milestones", (t) => { - t.beforeEach(ResetDBState); - - t.test( - "Should return all of a users milestones, and only unachieved milestones if the argument is set.", - async (t) => { - await db["milestone-subs"].insert([ - { - milestoneID: "foo", - userID: 1, - game: "iidx", - playtype: "SP", - achieved: false, - }, - { - milestoneID: "bar", - userID: 1, - game: "iidx", - playtype: "SP", - achieved: true, - }, - ] as MilestoneSubscriptionDocument[]); - - await db.milestones.insert([ - { milestoneID: "foo" }, - { milestoneID: "bar" }, - ] as MilestoneDocument[]); - - const res = await mockApi.get("/api/v1/users/1/games/iidx/SP/milestones"); - - t.hasStrict(res.body, { - success: true, - description: "Successfully returned 2 milestone(s).", - body: { - milestoneSubs: [ - { - milestoneID: "foo", - achieved: false, - }, - { - milestoneID: "bar", - achieved: true, - }, - ], - milestones: [{ milestoneID: "bar" }, { milestoneID: "foo" }], - }, - }); - - const resUnachieved = await mockApi.get( - "/api/v1/users/1/games/iidx/SP/milestones?unachieved=true" - ); - - t.hasStrict(resUnachieved.body, { - success: true, - description: "Successfully returned 1 milestone(s).", - body: { - milestoneSubs: [ - { - milestoneID: "foo", - achieved: false, - }, - ], - milestones: [{ milestoneID: "foo" }], - }, - }); - - t.end(); - } - ); - - t.end(); -}); - t.test("GET /api/v1/users/:userID/games/:game/:playtype/most-played", (t) => { t.beforeEach(ResetDBState); diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.ts index f0cc5e709..63dad1226 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.ts @@ -1,15 +1,7 @@ import { Router } from "express"; import db from "external/mongo/db"; import { SYMBOL_TachiData } from "lib/constants/tachi"; -import { FilterQuery } from "mongodb"; -import { - GetGamePTConfig, - integer, - PBScoreDocument, - UserGameStatsSnapshot, - GoalSubscriptionDocument, - MilestoneSubscriptionDocument, -} from "tachi-common"; +import { GetGamePTConfig, integer, PBScoreDocument, UserGameStatsSnapshot } from "tachi-common"; import { IsString } from "utils/misc"; import { CheckStrProfileAlg } from "utils/string-checks"; import { @@ -26,7 +18,7 @@ import sessionsRouter from "./sessions/router"; import settingsRouter from "./settings/router"; import showcaseRouter from "./showcase/router"; import tablesRouter from "./tables/router"; -import goalsRouter from "./targets/goals/router"; +import targetsRouter from "./targets/router"; const router: Router = Router({ mergeParams: true }); @@ -135,80 +127,6 @@ router.get("/history", async (req, res) => { }); }); -/** - * Returns a user's set goals for this game. - * @param unachieved - If set, achieved goals will be hidden. - * - * @name GET /api/v1/users/:userID/games/:game/:playtype/goals - */ -router.get("/goals", async (req, res) => { - const user = req[SYMBOL_TachiData]!.requestedUser!; - const game = req[SYMBOL_TachiData]!.game!; - const playtype = req[SYMBOL_TachiData]!.playtype!; - - const query: FilterQuery = { - userID: user.id, - game, - playtype, - }; - - if (req.query.unachieved) { - query.achieved = false; - } - - const goalSubs = await db["goal-subs"].find(query); - - const goals = await db.goals.find({ - goalID: { $in: goalSubs.map((e) => e.goalID) }, - }); - - return res.status(200).json({ - success: true, - description: `Successfully returned ${goalSubs.length} goal(s).`, - body: { - goalSubs, - goals, - }, - }); -}); - -/** - * Returns a user's set milestones for this game. - * @param unachieved - If set, achieved milestones will be hidden. - * - * @name GET /api/v1/users/:userID/games/:game/:playtype/milestones - */ -router.get("/milestones", async (req, res) => { - const user = req[SYMBOL_TachiData]!.requestedUser!; - const game = req[SYMBOL_TachiData]!.game!; - const playtype = req[SYMBOL_TachiData]!.playtype!; - - const query: FilterQuery = { - userID: user.id, - game, - playtype, - }; - - if (req.query.unachieved) { - query.achieved = false; - } - - const milestoneSubs = await db["milestone-subs"].find(query); - - const milestones = await db.milestones.find({ - milestoneID: { $in: milestoneSubs.map((e) => e.milestoneID) }, - }); - - return res.status(200).json({ - success: true, - description: `Successfully returned ${milestoneSubs.length} milestone(s).`, - body: { - milestoneSubs, - milestones, - }, - }); -}); - /** * Returns the users most played charts by playcount. * @@ -378,6 +296,6 @@ router.use("/tables", tablesRouter); router.use("/showcase", showcaseRouter); router.use("/settings", settingsRouter); router.use("/folders", foldersRouter); -router.use("/goals", goalsRouter); +router.use("/targets", targetsRouter); export default router; From dff4027c9410957cbec208a1e787b40712e6a047 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Thu, 31 Mar 2022 00:01:32 +0100 Subject: [PATCH 026/106] Remove unused imports --- .../v1/users/_userID/games/_game/_playtype/router.test.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.test.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.test.ts index eb5323286..e9ec94104 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.test.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.test.ts @@ -1,12 +1,6 @@ import deepmerge from "deepmerge"; import db from "external/mongo/db"; -import { - GoalDocument, - MilestoneDocument, - ScoreDocument, - GoalSubscriptionDocument, - MilestoneSubscriptionDocument, -} from "tachi-common"; +import { ScoreDocument } from "tachi-common"; import t from "tap"; import mockApi from "test-utils/mock-api"; import ResetDBState from "test-utils/resets"; From ee86fb1af7f7780732502f9c3decda79c2ef8e17 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Thu, 31 Mar 2022 00:16:32 +0100 Subject: [PATCH 027/106] Make max goals and milestones configurable --- server/src/lib/setup/config.ts | 6 ++++++ server/src/lib/targets/goals.ts | 2 +- .../games/_game/_playtype/targets/goals/router.ts | 5 +++-- .../games/_game/_playtype/targets/milestones/router.ts | 10 ++++++---- 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/server/src/lib/setup/config.ts b/server/src/lib/setup/config.ts index afbee29fe..d2de97f81 100644 --- a/server/src/lib/setup/config.ts +++ b/server/src/lib/setup/config.ts @@ -79,6 +79,8 @@ export interface TachiServerConfig { }; USC_QUEUE_SIZE: integer; BEATORAJA_QUEUE_SIZE: integer; + MAX_GOAL_SUBSCRIPTIONS: integer; + MAX_MILESTONE_SUBSCRIPTIONS: integer; OUR_URL: string; INVITE_CODE_CONFIG?: { BATCH_SIZE: integer; @@ -152,6 +154,8 @@ const err = p(config, { }), USC_QUEUE_SIZE: p.optional(p.gteInt(2)), BEATORAJA_QUEUE_SIZE: p.optional(p.gteInt(2)), + MAX_GOAL_SUBSCRIPTIONS: p.optional(p.isPositiveInteger), + MAX_MILESTONE_SUBSCRIPTIONS: p.optional(p.isPositiveInteger), OUR_URL: "string", INVITE_CODE_CONFIG: p.optional({ BATCH_SIZE: p.isPositiveInteger, @@ -209,6 +213,8 @@ tachiServerConfig.RATE_LIMIT ??= 500; tachiServerConfig.OAUTH_CLIENT_CAP ??= 15; tachiServerConfig.USC_QUEUE_SIZE ??= 3; tachiServerConfig.BEATORAJA_QUEUE_SIZE ??= 3; +tachiServerConfig.MAX_GOAL_SUBSCRIPTIONS ??= 1_000; +tachiServerConfig.MAX_MILESTONE_SUBSCRIPTIONS ??= 100; // Assign sane defaults to the logger config. tachiServerConfig.LOGGER_CONFIG = Object.assign( diff --git a/server/src/lib/targets/goals.ts b/server/src/lib/targets/goals.ts index fb8dd594f..a72b04778 100644 --- a/server/src/lib/targets/goals.ts +++ b/server/src/lib/targets/goals.ts @@ -320,7 +320,7 @@ export async function SubscribeToGoal( progress: result.progress, progressHuman: result.progressHuman, userID, - lastInteraction: Date.now(), + lastInteraction: null, timeAchieved: result.achieved ? Date.now() : null, timeSet: Date.now(), // if this goal subscription came from a milestone, add the milestone to diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts index 387c4348e..f5339826a 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts @@ -11,6 +11,7 @@ import { AssignToReqTachiData } from "utils/req-tachi-data"; import { GetGoalForIDGuaranteed, GetMilestoneForIDGuaranteed } from "utils/db"; import { RequireAuthedAsUser } from "../../../../../middleware"; import { SubscribeFailReasons } from "lib/constants/err-codes"; +import { ServerConfig } from "lib/setup/config"; const router: Router = Router({ mergeParams: true }); @@ -136,10 +137,10 @@ router.post( playtype, }); - if (existingGoalsCount > 1_000) { + if (existingGoalsCount > ServerConfig.MAX_GOAL_SUBSCRIPTIONS) { return res.status(400).json({ success: false, - description: `You already have 1000 goals. You cannot have anymore.`, + description: `You already have ${ServerConfig.MAX_GOAL_SUBSCRIPTIONS} goals. You cannot have anymore.`, }); } diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/milestones/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/milestones/router.ts index 2aae070e2..6ec77b3b3 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/milestones/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/milestones/router.ts @@ -3,15 +3,16 @@ import db from "external/mongo/db"; import { SubscribeFailReasons } from "lib/constants/err-codes"; import { SYMBOL_TachiData } from "lib/constants/tachi"; import CreateLogCtx from "lib/logger/logger"; +import { ServerConfig } from "lib/setup/config"; import { EvaluateMilestoneProgress, SubscribeToMilestone, UnsubscribeFromMilestone, } from "lib/targets/milestones"; import { RequirePermissions } from "server/middleware/auth"; -import { GetMilestoneForIDGuaranteed } from "utils/db"; import { AssignToReqTachiData } from "utils/req-tachi-data"; import { FormatUserDoc } from "utils/user"; +import { RequireAuthedAsUser } from "../../../../../middleware"; const logger = CreateLogCtx(__filename); @@ -72,7 +73,6 @@ const GetMilestoneSubscription: RequestHandler = async (req, res, next) => { }; const GetMilestone: RequestHandler = async (req, res, next) => { - const user = req[SYMBOL_TachiData]!.requestedUser!; const game = req[SYMBOL_TachiData]!.game!; const playtype = req[SYMBOL_TachiData]!.playtype!; @@ -130,6 +130,7 @@ router.get("/:milestoneID", GetMilestone, GetMilestoneSubscription, async (req, */ router.put( "/:milestoneID", + RequireAuthedAsUser, GetMilestone, RequirePermissions("manage_targets"), async (req, res) => { @@ -143,10 +144,10 @@ router.put( playtype, }); - if (existingMilestonesCount > 100) { + if (existingMilestonesCount > ServerConfig.MAX_MILESTONE_SUBSCRIPTIONS) { return res.status(400).json({ success: false, - description: `You already have 100 milestones. You cannot have anymore for this game.`, + description: `You already have ${ServerConfig.MAX_MILESTONE_SUBSCRIPTIONS} milestones. You cannot have anymore for this game.`, }); } @@ -195,6 +196,7 @@ router.put( */ router.delete( "/:milestoneID", + RequireAuthedAsUser, GetMilestone, RequirePermissions("manage_targets"), async (req, res) => { From 4cfdecfded9c77ff25bc7541c312e80cf4739679 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Thu, 31 Mar 2022 01:18:19 +0100 Subject: [PATCH 028/106] GPT Milestone Endpoints --- server/package.json | 2 +- server/pnpm-lock.yaml | 8 +- server/src/lib/targets/milestones.ts | 11 +- .../_game/_playtype/targets/goals/router.ts | 2 +- .../_playtype/targets/milestones/router.ts | 117 ++++++++++++++++++ .../games/_game/_playtype/targets/router.ts | 2 + .../_game/_playtype/targets/goals/router.ts | 2 +- 7 files changed, 128 insertions(+), 16 deletions(-) create mode 100644 server/src/server/router/api/v1/games/_game/_playtype/targets/milestones/router.ts diff --git a/server/package.json b/server/package.json index 191f87408..e89012acd 100644 --- a/server/package.json +++ b/server/package.json @@ -84,7 +84,7 @@ "rimraf": "3.0.2", "safe-json-stringify": "1.2.0", "seq-logging": "1.1.1", - "tachi-common": "0.7.7", + "tachi-common": "0.7.8", "ts-node": "10.4.0", "tsconfig-paths": "3.12.0", "typescript": "4.5.5", diff --git a/server/pnpm-lock.yaml b/server/pnpm-lock.yaml index ff0e79f75..1534ed770 100644 --- a/server/pnpm-lock.yaml +++ b/server/pnpm-lock.yaml @@ -65,7 +65,7 @@ specifiers: safe-json-stringify: 1.2.0 seq-logging: 1.1.1 supertest: 6.2.2 - tachi-common: 0.7.7 + tachi-common: 0.7.8 tap: 15.1.6 ts-node: 10.4.0 tsconfig-paths: 3.12.0 @@ -112,7 +112,7 @@ dependencies: rimraf: 3.0.2 safe-json-stringify: 1.2.0 seq-logging: 1.1.1 - tachi-common: 0.7.7 + tachi-common: 0.7.8 ts-node: 10.4.0_2615db9039ce432b4abf2fc39ef336ec tsconfig-paths: 3.12.0 typescript: 4.5.5 @@ -5363,8 +5363,8 @@ packages: engines: {node: '>= 0.4'} dev: true - /tachi-common/0.7.7: - resolution: {integrity: sha512-WsV4bCKdhackC24aTt7vdJTz/IEdRHiSm3+NeDWlU3AOmdMQV8YTcMCjeNscTr4yoccjpRNNG/avs+h/fFLDeg==} + /tachi-common/0.7.8: + resolution: {integrity: sha512-rDT+scIl1WQacdPrEIvM7fenRvkdjEQwMJMBDz1Pf/VQoYvyBeRL0rxjHrd26L8qkg1yPhwVKhs9DZUMnt4QzA==} dependencies: '@types/mongodb': 3.6.18 dev: false diff --git a/server/src/lib/targets/milestones.ts b/server/src/lib/targets/milestones.ts index b99fae9e9..f538f77b2 100644 --- a/server/src/lib/targets/milestones.ts +++ b/server/src/lib/targets/milestones.ts @@ -59,7 +59,7 @@ export function CalculateMilestoneOutOf(milestone: MilestoneDocument) { if (milestone.criteria.type === "all") { return goalIDs.length; - } else if (milestone.criteria.type === "abs") { + } else if (milestone.criteria.type === "total") { if (milestone.criteria.value === null) { throw new Error( `Invalid milestone ${milestone.milestoneID} - abs and null are not compatible.` @@ -67,17 +67,10 @@ export function CalculateMilestoneOutOf(milestone: MilestoneDocument) { } return milestone.criteria.value!; - } else if (milestone.criteria.type === "proportion") { - if (milestone.criteria.value === null) { - throw new Error( - `Invalid milestone ${milestone.milestoneID} - proportion and null are not compatible.` - ); - } - - return Math.floor(milestone.criteria.value * goalIDs.length); } throw new Error( + // @ts-expect-error Yeah obviously this shouldn't happen. Mayaswell throw though. `Invalid milestone.criteria.type of ${milestone.criteria.type} -- milestoneID ${milestone.milestoneID}` ); } diff --git a/server/src/server/router/api/v1/games/_game/_playtype/targets/goals/router.ts b/server/src/server/router/api/v1/games/_game/_playtype/targets/goals/router.ts index 06e396d55..f30571f43 100644 --- a/server/src/server/router/api/v1/games/_game/_playtype/targets/goals/router.ts +++ b/server/src/server/router/api/v1/games/_game/_playtype/targets/goals/router.ts @@ -108,7 +108,7 @@ router.get("/:goalID", ResolveGoalID, async (req, res) => { * * @param userID - The userID to evaluate this goal against. Must be a player of this GPT. * - * @name GET /api/v1/games/:game/:playtype/targets/goals/:goalID/evaluate + * @name GET /api/v1/games/:game/:playtype/targets/goals/:goalID/evaluate-for */ router.get( "/:goalID/evaluate-for", diff --git a/server/src/server/router/api/v1/games/_game/_playtype/targets/milestones/router.ts b/server/src/server/router/api/v1/games/_game/_playtype/targets/milestones/router.ts new file mode 100644 index 000000000..c037cde72 --- /dev/null +++ b/server/src/server/router/api/v1/games/_game/_playtype/targets/milestones/router.ts @@ -0,0 +1,117 @@ +import { RequestHandler, Router } from "express"; +import db from "external/mongo/db"; +import { SYMBOL_TachiAPIAuth, SYMBOL_TachiData } from "lib/constants/tachi"; +import { EvaluateMilestoneProgress, GetGoalsInMilestone } from "lib/targets/milestones"; +import prValidate from "server/middleware/prudence-validate"; +import { FormatGame } from "tachi-common"; +import { AssignToReqTachiData } from "utils/req-tachi-data"; +import { GetUsersWithIDs, ResolveUser } from "utils/user"; + +const router: Router = Router({ mergeParams: true }); + +const ResolveMilestoneID: RequestHandler = async (req, res, next) => { + const game = req[SYMBOL_TachiData]!.game!; + const playtype = req[SYMBOL_TachiData]!.playtype!; + const milestoneID = req.params.milestoneID; + + const milestone = await db.milestones.findOne({ + milestoneID, + game, + playtype, + }); + + if (!milestone) { + return res.status(404).json({ + success: false, + description: `A milestone with ID ${milestoneID} doesn't exist.`, + }); + } + + AssignToReqTachiData(req, { milestoneDoc: milestone }); + + return next(); +}; + +/** + * Retrieve information about this milestone and who is subscribed to it. + * + * @name GET /api/v1/games/:game/:playtype/targets/milestones/:milestoneID + */ +router.get("/:milestoneID", ResolveMilestoneID, async (req, res) => { + const milestone = req[SYMBOL_TachiData]!.milestoneDoc!; + + const milestoneSubs = await db["milestone-subs"].find({ + milestoneID: milestone.milestoneID, + }); + + const users = await GetUsersWithIDs(milestoneSubs.map((e) => e.userID)); + + const goals = await GetGoalsInMilestone(milestone); + + return res.status(200).json({ + success: true, + description: `Retrieved information about ${milestone.name}.`, + body: { + milestone, + milestoneSubs, + users, + goals, + }, + }); +}); + +/** + * Evaluates a milestone upon a user, even if they aren't subscribed to it. + * + * @param userID - The userID to evaluate this goal against. Must be a player of this GPT. + * + * @name GET /api/v1/games/:game/:playtype/targets/milestones/:milestone-id/evaluate-for + */ +router.get( + "/:milestoneID/evaluate-for", + ResolveMilestoneID, + prValidate({ userID: "string" }), + async (req, res) => { + const game = req[SYMBOL_TachiData]!.game!; + const playtype = req[SYMBOL_TachiData]!.playtype!; + + const userID = req.query.userID as string; + + const user = await ResolveUser(userID); + + if (!user) { + return res.status(404).json({ + success: false, + description: `The user ${userID} does not exist.`, + }); + } + + const hasPlayed = await db["game-stats"].findOne({ + game, + playtype, + userID: user.id, + }); + + if (!hasPlayed) { + return res.status(400).json({ + success: false, + description: `The user ${user.username} hasn't played ${FormatGame( + game, + playtype + )}.`, + }); + } + + const milestone = req[SYMBOL_TachiData]!.milestoneDoc!; + + const milestoneProgress = await EvaluateMilestoneProgress(user.id, milestone); + + return res.status(200).json({ + success: true, + description: `Evaluated ${milestone.name} for ${user.username}.`, + body: milestoneProgress, + }); + } +); + +export default router; diff --git a/server/src/server/router/api/v1/games/_game/_playtype/targets/router.ts b/server/src/server/router/api/v1/games/_game/_playtype/targets/router.ts index 63929423c..9ba386131 100644 --- a/server/src/server/router/api/v1/games/_game/_playtype/targets/router.ts +++ b/server/src/server/router/api/v1/games/_game/_playtype/targets/router.ts @@ -1,8 +1,10 @@ import { Router } from "express"; import goalsRouter from "./goals/router"; +import milestonesRouter from "./milestones/router"; const router: Router = Router({ mergeParams: true }); router.use("/goals", goalsRouter); +router.use("/milestones", milestonesRouter); export default router; diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts index f5339826a..dc69e0eed 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts @@ -18,7 +18,7 @@ const router: Router = Router({ mergeParams: true }); const logger = CreateLogCtx(__filename); /** - * Retrieves this users' set goals. + * Retrieves this users' set goals for this GPT. * * @name GET /api/v1/users/:userID/games/:game/:playtype/targets/goals */ From 74a0b2e04fd819aaea0ddc0e2a3dd6927b6ede7e Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Fri, 1 Apr 2022 13:47:57 +0100 Subject: [PATCH 029/106] Refactor a lot of endpoints to use GetUGPT --- server/package.json | 2 +- server/pnpm-lock.yaml | 8 +- .../_game/_playtype/targets/goals/router.ts | 4 +- .../_playtype/folders/_folderID/router.ts | 15 +- .../games/_game/_playtype/folders/router.ts | 9 +- .../games/_game/_playtype/pbs/router.ts | 22 +-- .../_userID/games/_game/_playtype/router.ts | 20 ++- .../games/_game/_playtype/scores/router.ts | 13 +- .../games/_game/_playtype/sessions/router.ts | 25 ++-- .../games/_game/_playtype/settings/router.ts | 9 +- .../games/_game/_playtype/showcase/router.ts | 13 +- .../games/_game/_playtype/tables/router.ts | 4 +- .../_game/_playtype/targets/goals/router.ts | 21 +-- .../_playtype/targets/milestones/router.ts | 19 +-- .../games/_game/_playtype/targets/router.ts | 129 +++++++++++++++++- server/src/utils/req-tachi-data.ts | 38 ++++++ 16 files changed, 237 insertions(+), 114 deletions(-) diff --git a/server/package.json b/server/package.json index e89012acd..fbde78867 100644 --- a/server/package.json +++ b/server/package.json @@ -84,7 +84,7 @@ "rimraf": "3.0.2", "safe-json-stringify": "1.2.0", "seq-logging": "1.1.1", - "tachi-common": "0.7.8", + "tachi-common": "0.7.9", "ts-node": "10.4.0", "tsconfig-paths": "3.12.0", "typescript": "4.5.5", diff --git a/server/pnpm-lock.yaml b/server/pnpm-lock.yaml index 1534ed770..61cdaa85f 100644 --- a/server/pnpm-lock.yaml +++ b/server/pnpm-lock.yaml @@ -65,7 +65,7 @@ specifiers: safe-json-stringify: 1.2.0 seq-logging: 1.1.1 supertest: 6.2.2 - tachi-common: 0.7.8 + tachi-common: 0.7.9 tap: 15.1.6 ts-node: 10.4.0 tsconfig-paths: 3.12.0 @@ -112,7 +112,7 @@ dependencies: rimraf: 3.0.2 safe-json-stringify: 1.2.0 seq-logging: 1.1.1 - tachi-common: 0.7.8 + tachi-common: 0.7.9 ts-node: 10.4.0_2615db9039ce432b4abf2fc39ef336ec tsconfig-paths: 3.12.0 typescript: 4.5.5 @@ -5363,8 +5363,8 @@ packages: engines: {node: '>= 0.4'} dev: true - /tachi-common/0.7.8: - resolution: {integrity: sha512-rDT+scIl1WQacdPrEIvM7fenRvkdjEQwMJMBDz1Pf/VQoYvyBeRL0rxjHrd26L8qkg1yPhwVKhs9DZUMnt4QzA==} + /tachi-common/0.7.9: + resolution: {integrity: sha512-q6JMyACbHb7rDSGPv+cZZHZP1XxyQIvuhnQU5jRKYQfn4LkMm6ticHUN7gjSNAVHnwR05VQnulrkc+SOcnn3nQ==} dependencies: '@types/mongodb': 3.6.18 dev: false diff --git a/server/src/server/router/api/v1/games/_game/_playtype/targets/goals/router.ts b/server/src/server/router/api/v1/games/_game/_playtype/targets/goals/router.ts index f30571f43..0d24f0a59 100644 --- a/server/src/server/router/api/v1/games/_game/_playtype/targets/goals/router.ts +++ b/server/src/server/router/api/v1/games/_game/_playtype/targets/goals/router.ts @@ -26,9 +26,7 @@ router.get("/recently-achieved", async (req, res) => { game, playtype, achieved: true, - // exclude stuff where timeAchieved == timeSet, as they're not really - // goals but rather consequences of milestone assignment. - $expr: { $ne: ["$timeAchieved", "$timeSet"] }, + wasInstantlyAchieved: false, }, { sort: { diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/folders/_folderID/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/folders/_folderID/router.ts index a22964c41..7631870e5 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/folders/_folderID/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/folders/_folderID/router.ts @@ -4,6 +4,7 @@ import { SYMBOL_TachiData } from "lib/constants/tachi"; import { FilterQuery } from "mongodb"; import { GetGamePTConfig, ScoreDocument } from "tachi-common"; import { GetFolderCharts, GetGradeLampDistributionForFolder, GetPBsOnFolder } from "utils/folder"; +import { GetUGPT } from "utils/req-tachi-data"; import { ParseStrPositiveInt } from "utils/string-checks"; import { GetFolderFromParam } from "../../../../../../../games/_game/_playtype/folders/middleware"; import { RequireSelfRequestFromUser } from "../../../../../middleware"; @@ -18,7 +19,8 @@ router.use(GetFolderFromParam); * @name GET /api/v1/users/:userID/games/:game/:playtype/folders/:folderID */ router.get("/", async (req, res) => { - const user = req[SYMBOL_TachiData]!.requestedUser!; + const { user } = GetUGPT(req); + const folder = req[SYMBOL_TachiData]!.folderDoc!; const { songs, charts, pbs } = await GetPBsOnFolder(user.id, folder); @@ -41,8 +43,9 @@ router.get("/", async (req, res) => { * @name GET /api/v1/users/:userID/games/:game/:playtype/folders/:folderID/stats */ router.get("/stats", async (req, res) => { + const { user } = GetUGPT(req); + const folder = req[SYMBOL_TachiData]!.folderDoc!; - const user = req[SYMBOL_TachiData]!.requestedUser!; const stats = await GetGradeLampDistributionForFolder(user.id, folder); @@ -64,7 +67,8 @@ router.get("/stats", async (req, res) => { * @name POST /api/v1/users/:userID/games/:game/:playtype/folders/:folderID/viewed */ router.post("/viewed", RequireSelfRequestFromUser, async (req, res) => { - const user = req[SYMBOL_TachiData]!.requestedUser!; + const { user } = GetUGPT(req); + const folder = req[SYMBOL_TachiData]!.folderDoc!; await db["recent-folder-views"].update( @@ -101,10 +105,9 @@ router.post("/viewed", RequireSelfRequestFromUser, async (req, res) => { * @name GET /api/v1/users/:userID/games/:game/:playtype/folders/:folderID/timeline */ router.get("/timeline", async (req, res) => { - const user = req[SYMBOL_TachiData]!.requestedUser!; + const { user, game, playtype } = GetUGPT(req); + const folder = req[SYMBOL_TachiData]!.folderDoc!; - const game = req[SYMBOL_TachiData]!.game!; - const playtype = req[SYMBOL_TachiData]!.playtype!; const gptConfig = GetGamePTConfig(game, playtype); const intIndex = ParseStrPositiveInt(req.query.criteriaValue); diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/folders/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/folders/router.ts index 343a5cf25..e02f0195f 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/folders/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/folders/router.ts @@ -4,6 +4,7 @@ import { SYMBOL_TachiData } from "lib/constants/tachi"; import { SearchCollection } from "lib/search/search"; import { GetGradeLampDistributionForFolders, GetRecentlyViewedFolders } from "utils/folder"; import { IsString } from "utils/misc"; +import { GetUGPT } from "utils/req-tachi-data"; import folderIDRouter from "./_folderID/router"; const router: Router = Router({ mergeParams: true }); @@ -21,9 +22,7 @@ const router: Router = Router({ mergeParams: true }); * @name GET /api/v1/users/:userID/games/:game/:playtype/folders */ router.get("/", async (req, res) => { - const game = req[SYMBOL_TachiData]!.game!; - const playtype = req[SYMBOL_TachiData]!.playtype!; - const user = req[SYMBOL_TachiData]!.requestedUser!; + const { game, playtype, user } = GetUGPT(req); if (!IsString(req.query.search)) { return res.status(400).json({ @@ -60,9 +59,7 @@ router.get("/", async (req, res) => { * @name GET /api/v1/users/:userID/games/:game/:playtype/folders/recent */ router.get("/recent", async (req, res) => { - const game = req[SYMBOL_TachiData]!.game!; - const playtype = req[SYMBOL_TachiData]!.playtype!; - const user = req[SYMBOL_TachiData]!.requestedUser!; + const { game, playtype, user } = GetUGPT(req); const { views, folders } = await GetRecentlyViewedFolders(user.id, game, playtype); diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/pbs/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/pbs/router.ts index 3e3889062..037b5c64e 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/pbs/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/pbs/router.ts @@ -6,6 +6,7 @@ import { GetGamePTConfig } from "tachi-common"; import { GetRelevantSongsAndCharts } from "utils/db"; import { IsValidScoreAlg } from "utils/misc"; import { GetAdjacentAbove, GetAdjacentBelow } from "utils/queries/pbs"; +import { GetUGPT } from "utils/req-tachi-data"; import { FilterChartsAndSongs, GetScoreIDsFromComposed } from "utils/scores"; import { GetUsersWithIDs } from "utils/user"; @@ -19,9 +20,7 @@ const router: Router = Router({ mergeParams: true }); * @name GET /api/v1/users/:userID/games/:game/:playtype/pbs */ router.get("/", async (req, res) => { - const user = req[SYMBOL_TachiData]!.requestedUser!; - const game = req[SYMBOL_TachiData]!.game!; - const playtype = req[SYMBOL_TachiData]!.playtype!; + const { user, game, playtype } = GetUGPT(req); if (typeof req.query.search !== "string") { return res.status(400).json({ @@ -71,9 +70,7 @@ router.get("/", async (req, res) => { * @name GET /api/v1/users/:userID/games/:game/:playtype/pbs/all */ router.get("/all", async (req, res) => { - const user = req[SYMBOL_TachiData]!.requestedUser!; - const game = req[SYMBOL_TachiData]!.game!; - const playtype = req[SYMBOL_TachiData]!.playtype!; + const { user, game, playtype } = GetUGPT(req); const pbs = await db["personal-bests"].find({ userID: user.id, @@ -99,9 +96,8 @@ router.get("/all", async (req, res) => { * @name GET /api/v1/users/:userID/games/:game/:playtype/pbs/best */ router.get("/best", async (req, res) => { - const user = req[SYMBOL_TachiData]!.requestedUser!; - const game = req[SYMBOL_TachiData]!.game!; - const playtype = req[SYMBOL_TachiData]!.playtype!; + const { user, game, playtype } = GetUGPT(req); + const gptConfig = GetGamePTConfig(game, playtype); if (req.query.alg && !IsValidScoreAlg(gptConfig, req.query.alg)) { @@ -152,9 +148,7 @@ router.get("/best", async (req, res) => { * @name GET /api/v1/users/:userID/games/:game/:playtype/pbs/:chartID */ router.get("/:chartID", async (req, res) => { - const user = req[SYMBOL_TachiData]!.requestedUser!; - const game = req[SYMBOL_TachiData]!.game!; - const playtype = req[SYMBOL_TachiData]!.playtype!; + const { user, game, playtype } = GetUGPT(req); const chart = await db.charts[game].findOne({ chartID: req.params.chartID, @@ -215,9 +209,7 @@ router.get("/:chartID", async (req, res) => { * @name GET /api/v1/users/:userID/games/:game/:playtype/pbs/:chartID/leaderboard-adjacent */ router.get("/:chartID/leaderboard-adjacent", async (req, res) => { - const user = req[SYMBOL_TachiData]!.requestedUser!; - const game = req[SYMBOL_TachiData]!.game!; - const playtype = req[SYMBOL_TachiData]!.playtype!; + const { user, game, playtype } = GetUGPT(req); const chart = await db.charts[game].findOne({ chartID: req.params.chartID, diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.ts index 63dad1226..ee0a19d12 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.ts @@ -3,6 +3,7 @@ import db from "external/mongo/db"; import { SYMBOL_TachiData } from "lib/constants/tachi"; import { GetGamePTConfig, integer, PBScoreDocument, UserGameStatsSnapshot } from "tachi-common"; import { IsString } from "utils/misc"; +import { GetUGPT } from "utils/req-tachi-data"; import { CheckStrProfileAlg } from "utils/string-checks"; import { GetAllRankings, @@ -29,10 +30,9 @@ router.use(CheckUserPlayedGamePlaytype); * @name GET /api/v1/users/:userID/games/:game/:playtype */ router.get("/", async (req, res) => { - const user = req[SYMBOL_TachiData]!.requestedUser!; + const { game, playtype, user } = GetUGPT(req); + const stats = req[SYMBOL_TachiData]!.requestedUserGameStats!; - const game = req[SYMBOL_TachiData]!.game!; - const playtype = req[SYMBOL_TachiData]!.playtype!; const [totalScores, firstScore, mostRecentScore, rankingData] = await Promise.all([ db.scores.count({ @@ -87,10 +87,9 @@ router.get("/", async (req, res) => { * @name GET /api/v1/users/:userID/games/:game/:playtype/history */ router.get("/history", async (req, res) => { - const user = req[SYMBOL_TachiData]!.requestedUser!; + const { game, playtype, user } = GetUGPT(req); + const stats = req[SYMBOL_TachiData]!.requestedUserGameStats!; - const game = req[SYMBOL_TachiData]!.game!; - const playtype = req[SYMBOL_TachiData]!.playtype!; const snapshots = (await db["game-stats-snapshots"].find( { @@ -133,9 +132,7 @@ router.get("/history", async (req, res) => { * @name GET /api/v1/users/:userID/games/:game/:playtype/most-played */ router.get("/most-played", async (req, res) => { - const user = req[SYMBOL_TachiData]!.requestedUser!; - const game = req[SYMBOL_TachiData]!.game!; - const playtype = req[SYMBOL_TachiData]!.playtype!; + const { game, playtype, user } = GetUGPT(req); const mostPlayed: { playcount: integer; songID: integer; _id: string }[] = await db.scores.aggregate([ @@ -208,10 +205,9 @@ router.get("/most-played", async (req, res) => { * @name GET /api/v1/users/:userID/games/:game/:playtype/leaderboard-adjacent */ router.get("/leaderboard-adjacent", async (req, res) => { - const game = req[SYMBOL_TachiData]!.game!; - const playtype = req[SYMBOL_TachiData]!.playtype!; + const { game, playtype, user } = GetUGPT(req); + const gptConfig = GetGamePTConfig(game, playtype); - const user = req[SYMBOL_TachiData]!.requestedUser!; let alg = gptConfig.defaultProfileRatingAlg; if (IsString(req.query.alg)) { diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/scores/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/scores/router.ts index 9c1eaa46c..5cfc28dd7 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/scores/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/scores/router.ts @@ -3,6 +3,7 @@ import db from "external/mongo/db"; import { SYMBOL_TachiData } from "lib/constants/tachi"; import { SearchGameSongsAndCharts } from "lib/search/search"; import { GetRelevantSongsAndCharts } from "utils/db"; +import { GetUGPT } from "utils/req-tachi-data"; import { FilterChartsAndSongs } from "utils/scores"; const router: Router = Router({ mergeParams: true }); @@ -13,9 +14,7 @@ const router: Router = Router({ mergeParams: true }); * @name GET /api/v1/users/:userID/games/:game/:playtype/scores */ router.get("/", async (req, res) => { - const user = req[SYMBOL_TachiData]!.requestedUser!; - const game = req[SYMBOL_TachiData]!.game!; - const playtype = req[SYMBOL_TachiData]!.playtype!; + const { user, game, playtype } = GetUGPT(req); if (typeof req.query.search !== "string") { return res.status(400).json({ @@ -62,9 +61,7 @@ router.get("/", async (req, res) => { * @name GET /api/v1/users/:userID/games/:game/:playtype/scores/recent */ router.get("/recent", async (req, res) => { - const user = req[SYMBOL_TachiData]!.requestedUser!; - const game = req[SYMBOL_TachiData]!.game!; - const playtype = req[SYMBOL_TachiData]!.playtype!; + const { user, game, playtype } = GetUGPT(req); const recentScores = await db.scores.find( { @@ -99,9 +96,7 @@ router.get("/recent", async (req, res) => { * @name GET /api/v1/users/:userID/games/:game/:playtype/scores/:chartID */ router.get("/:chartID", async (req, res) => { - const user = req[SYMBOL_TachiData]!.requestedUser!; - const game = req[SYMBOL_TachiData]!.game!; - const playtype = req[SYMBOL_TachiData]!.playtype!; + const { user, game, playtype } = GetUGPT(req); const chart = await db.charts[game].findOne({ chartID: req.params.chartID, diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/sessions/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/sessions/router.ts index e2f540cea..77a68fc9b 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/sessions/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/sessions/router.ts @@ -13,9 +13,8 @@ const router: Router = Router({ mergeParams: true }); * @name GET /api/v1/users/:userID/games/:game/:playtype/sessions */ router.get("/", async (req, res) => { - const user = req[SYMBOL_TachiData]!.requestedUser!; - const game = req[SYMBOL_TachiData]!.game!; - const playtype = req[SYMBOL_TachiData]!.playtype!; + const { user, game, playtype } = GetUGPT(req); + if (typeof req.query.search !== "string") { return res.status(400).json({ @@ -41,9 +40,8 @@ router.get("/", async (req, res) => { * @name GET /api/v1/users/:userID/games/:game/:playtype/sessions/best */ router.get("/best", async (req, res) => { - const user = req[SYMBOL_TachiData]!.requestedUser!; - const game = req[SYMBOL_TachiData]!.game!; - const playtype = req[SYMBOL_TachiData]!.playtype!; + const { user, game, playtype } = GetUGPT(req); + const gptConfig = GetGamePTConfig(game, playtype); let alg = gptConfig.defaultSessionRatingAlg; @@ -89,9 +87,8 @@ router.get("/best", async (req, res) => { * @name GET /api/v1/users/:userID/games/:game/:playtype/sessions/highlighted */ router.get("/highlighted", async (req, res) => { - const user = req[SYMBOL_TachiData]!.requestedUser!; - const game = req[SYMBOL_TachiData]!.game!; - const playtype = req[SYMBOL_TachiData]!.playtype!; + const { user, game, playtype } = GetUGPT(req); + const sessions = await db.sessions.find( { userID: user.id, game, playtype, highlight: true }, @@ -111,9 +108,8 @@ router.get("/highlighted", async (req, res) => { * @name GET /api/v1/users/:userID/games/:game/:playtype/sessions/recent */ router.get("/recent", async (req, res) => { - const user = req[SYMBOL_TachiData]!.requestedUser!; - const game = req[SYMBOL_TachiData]!.game!; - const playtype = req[SYMBOL_TachiData]!.playtype!; + const { user, game, playtype } = GetUGPT(req); + const sessions = await db.sessions.find( { userID: user.id, game, playtype }, @@ -133,9 +129,8 @@ router.get("/recent", async (req, res) => { * @name GET /api/v1/users/:userID/games/:game/:playtype/sessions/last */ router.get("/last", async (req, res) => { - const user = req[SYMBOL_TachiData]!.requestedUser!; - const game = req[SYMBOL_TachiData]!.game!; - const playtype = req[SYMBOL_TachiData]!.playtype!; + const { user, game, playtype } = GetUGPT(req); + const session = await db.sessions.findOne( { userID: user.id, game, playtype }, diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/settings/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/settings/router.ts index f6c1fde0d..1800e532c 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/settings/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/settings/router.ts @@ -6,6 +6,7 @@ import p from "prudence"; import { RequirePermissions } from "server/middleware/auth"; import { GetGamePTConfig } from "tachi-common"; import { FormatPrError, optNull } from "utils/prudence"; +import { GetUGPT } from "utils/req-tachi-data"; import { FormatUserDoc } from "utils/user"; import { RequireAuthedAsUser } from "../../../../middleware"; @@ -25,9 +26,7 @@ router.patch( RequireAuthedAsUser, RequirePermissions("customise_profile"), async (req, res) => { - const user = req[SYMBOL_TachiData]!.requestedUser!; - const game = req[SYMBOL_TachiData]!.game!; - const playtype = req[SYMBOL_TachiData]!.playtype!; + const { user, game, playtype } = GetUGPT(req); const gptConfig = GetGamePTConfig(game, playtype); @@ -170,9 +169,7 @@ router.patch( * @name GET /api/v1/users/:userID/games/:game/:playtype/settings */ router.get("/", async (req, res) => { - const user = req[SYMBOL_TachiData]!.requestedUser!; - const game = req[SYMBOL_TachiData]!.game!; - const playtype = req[SYMBOL_TachiData]!.playtype!; + const { user, game, playtype } = GetUGPT(req); const settings = await db["game-settings"].findOne({ userID: user.id, diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/showcase/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/showcase/router.ts index f2189279e..d26c6d748 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/showcase/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/showcase/router.ts @@ -9,6 +9,7 @@ import { RequirePermissions } from "server/middleware/auth"; import { RequireAuthedAsUser } from "server/router/api/v1/users/_userID/middleware"; import { GetGamePTConfig, ShowcaseStatDetails } from "tachi-common"; import { FormatPrError } from "utils/prudence"; +import { GetUGPT } from "utils/req-tachi-data"; import { ResolveUser } from "utils/user"; const router: Router = Router({ mergeParams: true }); @@ -20,9 +21,7 @@ const router: Router = Router({ mergeParams: true }); * @name GET /api/v1/users/:userID/games/:game/:playtype/showcase */ router.get("/", async (req, res) => { - const user = req[SYMBOL_TachiData]!.requestedUser!; - const game = req[SYMBOL_TachiData]!.game!; - const playtype = req[SYMBOL_TachiData]!.playtype!; + const { user, game, playtype } = GetUGPT(req); let projectUser; @@ -60,9 +59,7 @@ router.get("/", async (req, res) => { * @name GET /api/v1/users/:userID/games/:game/:playtype/showcase/custom */ router.get("/custom", async (req, res) => { - const user = req[SYMBOL_TachiData]!.requestedUser!; - const game = req[SYMBOL_TachiData]!.game!; - const playtype = req[SYMBOL_TachiData]!.playtype!; + const { user, game, playtype } = GetUGPT(req); let stat: ShowcaseStatDetails; @@ -164,9 +161,7 @@ router.get("/custom", async (req, res) => { * @name PUT /api/v1/users/:userID/games/:game/:playtype/showcase */ router.put("/", RequireAuthedAsUser, RequirePermissions("customise_profile"), async (req, res) => { - const user = req[SYMBOL_TachiData]!.requestedUser!; - const game = req[SYMBOL_TachiData]!.game!; - const playtype = req[SYMBOL_TachiData]!.playtype!; + const { user, game, playtype } = GetUGPT(req); const gptConfig = GetGamePTConfig(game, playtype); diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/tables/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/tables/router.ts index b811916ab..c4f10feb3 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/tables/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/tables/router.ts @@ -1,6 +1,7 @@ import { Router } from "express"; import { SYMBOL_TachiData } from "lib/constants/tachi"; import { GetFoldersFromTable, GetGradeLampDistributionForFolders } from "utils/folder"; +import { GetUGPT } from "utils/req-tachi-data"; // @todo maybe refactor where middleware is stored to avoid paths this ugly. import { GetTableFromParam } from "../../../../../../games/_game/_playtype/tables/middleware"; @@ -12,8 +13,9 @@ const router: Router = Router({ mergeParams: true }); * @name GET /api/v1/users/:userID/games/:game/:playtype/tables/:tableID */ router.get("/:tableID", GetTableFromParam, async (req, res) => { + const { user } = GetUGPT(req); + const table = req[SYMBOL_TachiData]!.tableDoc!; - const user = req[SYMBOL_TachiData]!.requestedUser!; const folders = await GetFoldersFromTable(table); diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts index dc69e0eed..698f58e65 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts @@ -7,7 +7,7 @@ import { GoalDocument, MilestoneDocument } from "tachi-common"; import { ConstructGoal, SubscribeToGoal } from "lib/targets/goals"; import CreateLogCtx from "lib/logger/logger"; import { RequirePermissions } from "server/middleware/auth"; -import { AssignToReqTachiData } from "utils/req-tachi-data"; +import { AssignToReqTachiData, GetUGPT } from "utils/req-tachi-data"; import { GetGoalForIDGuaranteed, GetMilestoneForIDGuaranteed } from "utils/db"; import { RequireAuthedAsUser } from "../../../../../middleware"; import { SubscribeFailReasons } from "lib/constants/err-codes"; @@ -23,9 +23,7 @@ const logger = CreateLogCtx(__filename); * @name GET /api/v1/users/:userID/games/:game/:playtype/targets/goals */ router.get("/", async (req, res) => { - const user = req[SYMBOL_TachiData]!.requestedUser!; - const game = req[SYMBOL_TachiData]!.game!; - const playtype = req[SYMBOL_TachiData]!.playtype!; + const { user, game, playtype } = GetUGPT(req); const goalSubs = await db["goal-subs"].find({ userID: user.id, @@ -127,9 +125,7 @@ router.post( }, }), async (req, res) => { - const user = req[SYMBOL_TachiData]!.requestedUser!; - const game = req[SYMBOL_TachiData]!.game!; - const playtype = req[SYMBOL_TachiData]!.playtype!; + const { user, game, playtype } = GetUGPT(req); const existingGoalsCount = await db["goal-subs"].count({ userID: user.id, @@ -188,9 +184,7 @@ router.post( ); const GetGoalSubscription: RequestHandler = async (req, res, next) => { - const user = req[SYMBOL_TachiData]!.requestedUser!; - const game = req[SYMBOL_TachiData]!.game!; - const playtype = req[SYMBOL_TachiData]!.playtype!; + const { user, game, playtype } = GetUGPT(req); const goalSub = await db["goal-subs"].findOne({ userID: user.id, @@ -217,7 +211,8 @@ const GetGoalSubscription: RequestHandler = async (req, res, next) => { * @name GET /api/v1/users/:userID/games/:game/:playtype/targets/goals/:goalID */ router.get("/:goalID", GetGoalSubscription, async (req, res) => { - const user = req[SYMBOL_TachiData]!.requestedUser!; + const { user } = GetUGPT(req); + const goalSub = req[SYMBOL_TachiData]!.goalSubDoc!; let milestones: MilestoneDocument[] = []; @@ -255,9 +250,7 @@ router.delete( prValidate({ goalID: "string" }), async (req, res) => { const goalID = req.params.goalID; - const user = req[SYMBOL_TachiData]!.requestedUser!; - const game = req[SYMBOL_TachiData]!.game!; - const playtype = req[SYMBOL_TachiData]!.playtype!; + const { user, game, playtype } = GetUGPT(req); const goalSub = await db["goal-subs"].findOne({ goalID, diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/milestones/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/milestones/router.ts index 6ec77b3b3..376209e02 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/milestones/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/milestones/router.ts @@ -10,7 +10,7 @@ import { UnsubscribeFromMilestone, } from "lib/targets/milestones"; import { RequirePermissions } from "server/middleware/auth"; -import { AssignToReqTachiData } from "utils/req-tachi-data"; +import { AssignToReqTachiData, GetUGPT } from "utils/req-tachi-data"; import { FormatUserDoc } from "utils/user"; import { RequireAuthedAsUser } from "../../../../../middleware"; @@ -24,9 +24,7 @@ const router: Router = Router({ mergeParams: true }); * @name GET /api/v1/users/:userID/games/:game/:playtype/targets/milestones */ router.get("/", async (req, res) => { - const user = req[SYMBOL_TachiData]!.requestedUser!; - const game = req[SYMBOL_TachiData]!.game!; - const playtype = req[SYMBOL_TachiData]!.playtype!; + const { user, game, playtype } = GetUGPT(req); const milestoneSubs = await db["milestone-subs"].find({ userID: user.id, @@ -49,9 +47,7 @@ router.get("/", async (req, res) => { }); const GetMilestoneSubscription: RequestHandler = async (req, res, next) => { - const user = req[SYMBOL_TachiData]!.requestedUser!; - const game = req[SYMBOL_TachiData]!.game!; - const playtype = req[SYMBOL_TachiData]!.playtype!; + const { user, game, playtype } = GetUGPT(req); const milestoneSub = await db["milestone-subs"].findOne({ userID: user.id, @@ -101,7 +97,8 @@ const GetMilestone: RequestHandler = async (req, res, next) => { * @name GET /api/v1/users/:userID/games/:game/:playtype/targets/milestones/:milestoneID */ router.get("/:milestoneID", GetMilestone, GetMilestoneSubscription, async (req, res) => { - const user = req[SYMBOL_TachiData]!.requestedUser!; + const { user } = GetUGPT(req); + const milestoneSub = req[SYMBOL_TachiData]!.milestoneSubDoc!; const milestone = req[SYMBOL_TachiData]!.milestoneDoc!; @@ -134,9 +131,7 @@ router.put( GetMilestone, RequirePermissions("manage_targets"), async (req, res) => { - const user = req[SYMBOL_TachiData]!.requestedUser!; - const game = req[SYMBOL_TachiData]!.game!; - const playtype = req[SYMBOL_TachiData]!.playtype!; + const { user, game, playtype } = GetUGPT(req); const existingMilestonesCount = await db["milestone-subs"].count({ userID: user.id, @@ -200,7 +195,7 @@ router.delete( GetMilestone, RequirePermissions("manage_targets"), async (req, res) => { - const user = req[SYMBOL_TachiData]!.requestedUser!; + const { user } = GetUGPT(req); const milestone = req[SYMBOL_TachiData]!.milestoneDoc!; logger.info( diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/router.ts index 9ba386131..842e2aa9b 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/router.ts @@ -1,10 +1,137 @@ import { Router } from "express"; +import db from "external/mongo/db"; +import { SYMBOL_TachiData } from "lib/constants/tachi"; +import { GetUGPT } from "utils/req-tachi-data"; import goalsRouter from "./goals/router"; import milestonesRouter from "./milestones/router"; const router: Router = Router({ mergeParams: true }); +/** + * Return a user's recently achieved goals and milestones. + * + * @name GET /api/v1/users/:userID/games/:game/:playtype/targets/recently-achieved + */ +router.get("/recently-achieved", async (req, res) => { + const { game, playtype, user } = GetUGPT(req); + + const userID = user.id; + + const recentlyAchievedGoalSubs = await db["goal-subs"].find( + { + game, + playtype, + achieved: true, + wasInstantlyAchieved: false, + userID, + }, + { + sort: { + timeAchieved: -1, + }, + limit: 50, + } + ); + + const recentlyAchievedMilestoneSubs = await db["milestone-subs"].find( + { + game, + playtype, + achieved: true, + wasInstantlyAchieved: false, + userID, + }, + { + sort: { + timeAchieved: -1, + }, + limit: 10, + } + ); + + const goals = await db.goals.find({ + goalID: { $in: recentlyAchievedGoalSubs.map((e) => e.goalID) }, + }); + + const milestones = await db.milestones.find({ + milestoneID: { $in: recentlyAchievedMilestoneSubs.map((e) => e.milestoneID) }, + }); + + return res.status(200).json({ + success: true, + description: `Returned ${user.username}'s recently achieved targets.`, + body: { + goals, + milestones, + goalSubs: recentlyAchievedGoalSubs, + milestoneSubs: recentlyAchievedMilestoneSubs, + user, + }, + }); +}); + +/** + * Returns a user's recently interacted with (raised, etc.) goals and milestones. + * + * @name GET /api/v1/users/:userID/games/:game/:playtype/targets/recently-interacted + */ +router.get("/recently-interacted", async (req, res) => { + const { game, playtype, user } = GetUGPT(req); + + const userID = user.id; + + const recentlyAchievedGoalSubs = await db["goal-subs"].find( + { + game, + playtype, + wasInstantlyAchieved: false, + userID, + lastInteraction: { $ne: null }, + }, + { + sort: { + lastInteraction: -1, + }, + limit: 50, + } + ); + + const recentlyAchievedMilestoneSubs = await db["milestone-subs"].find( + { + game, + playtype, + wasInstantlyAchieved: false, + userID, + }, + { + sort: { + lastInteraction: -1, + }, + limit: 10, + } + ); + + const goals = await db.goals.find({ + goalID: { $in: recentlyAchievedGoalSubs.map((e) => e.goalID) }, + }); + + const milestones = await db.milestones.find({ + milestoneID: { $in: recentlyAchievedMilestoneSubs.map((e) => e.milestoneID) }, + }); + + return res.status(200).json({ + success: true, + description: `Returned ${user.username}'s recently achieved targets.`, + body: { + goals, + milestones, + goalSubs: recentlyAchievedGoalSubs, + milestoneSubs: recentlyAchievedMilestoneSubs, + user, + }, + }); +}); + router.use("/goals", goalsRouter); -router.use("/milestones", milestonesRouter); export default router; diff --git a/server/src/utils/req-tachi-data.ts b/server/src/utils/req-tachi-data.ts index 8cf867399..c287486a6 100644 --- a/server/src/utils/req-tachi-data.ts +++ b/server/src/utils/req-tachi-data.ts @@ -2,6 +2,7 @@ import { SYMBOL_TachiData } from "lib/constants/tachi"; import { Request } from "express"; import { TachiRequestData } from "./types"; import deepmerge from "deepmerge"; +import CreateLogCtx from "lib/logger/logger"; export function AssignToReqTachiData(req: Request, data: Partial) { if (!req[SYMBOL_TachiData]) { @@ -10,3 +11,40 @@ export function AssignToReqTachiData(req: Request, data: Partial Date: Fri, 1 Apr 2022 13:57:31 +0100 Subject: [PATCH 030/106] Organise Imports --- .../import-types/common/api-kai/iidx/converter.ts | 2 +- .../common/batch-manual/converter.test.ts | 2 +- .../import-types/common/batch-manual/converter.ts | 4 ++-- .../common/batch-manual/parser.test.ts | 2 +- .../import-types/common/batch-manual/parser.ts | 2 +- server/src/server/middleware/auth.ts | 4 ++-- server/src/server/middleware/multer-upload.ts | 6 +++--- .../server/middleware/prudence-validate.test.ts | 4 ++-- server/src/server/middleware/prudence-validate.ts | 12 ++++++------ server/src/server/middleware/request-logger.ts | 2 +- .../src/server/middleware/sanitise-qstring.test.ts | 4 ++-- server/src/server/router/api/v1/auth/auth.ts | 8 ++++---- .../games/_game/_playtype/sessions/router.ts | 14 +++++--------- .../games/_game/_playtype/settings/router.ts | 1 - .../games/_game/_playtype/targets/router.ts | 2 +- server/src/server/router/ir/router.ts | 2 +- server/src/test-utils/mock-api.ts | 5 ++--- server/src/test-utils/server.ts | 2 +- 18 files changed, 36 insertions(+), 42 deletions(-) diff --git a/server/src/lib/score-import/import-types/common/api-kai/iidx/converter.ts b/server/src/lib/score-import/import-types/common/api-kai/iidx/converter.ts index d3734f9e2..f5a5dbc43 100644 --- a/server/src/lib/score-import/import-types/common/api-kai/iidx/converter.ts +++ b/server/src/lib/score-import/import-types/common/api-kai/iidx/converter.ts @@ -1,5 +1,5 @@ import p from "prudence"; -import { Lamps, integer } from "tachi-common"; +import { integer, Lamps } from "tachi-common"; import { FormatPrError } from "utils/prudence"; import { FindIIDXChartOnInGameIDVersion } from "utils/queries/charts"; import { FindSongOnID } from "utils/queries/songs"; diff --git a/server/src/lib/score-import/import-types/common/batch-manual/converter.test.ts b/server/src/lib/score-import/import-types/common/batch-manual/converter.test.ts index fb85d3e98..5ab5b0d59 100644 --- a/server/src/lib/score-import/import-types/common/batch-manual/converter.test.ts +++ b/server/src/lib/score-import/import-types/common/batch-manual/converter.test.ts @@ -1,6 +1,6 @@ import deepmerge from "deepmerge"; import CreateLogCtx from "lib/logger/logger"; -import { Game, BatchManualScore } from "tachi-common"; +import { BatchManualScore, Game } from "tachi-common"; import t from "tap"; import ResetDBState from "test-utils/resets"; import { GetKTDataJSON, Testing511Song, Testing511SPA } from "test-utils/test-data"; diff --git a/server/src/lib/score-import/import-types/common/batch-manual/converter.ts b/server/src/lib/score-import/import-types/common/batch-manual/converter.ts index 8256c82f1..fa5be9fea 100644 --- a/server/src/lib/score-import/import-types/common/batch-manual/converter.ts +++ b/server/src/lib/score-import/import-types/common/batch-manual/converter.ts @@ -3,10 +3,10 @@ import { KtLogger } from "lib/logger/logger"; import { BatchManualScore, ChartDocument, - ImportTypes, - SongDocument, Grades, IDStrings, + ImportTypes, + SongDocument, } from "tachi-common"; import { FindBMSChartOnHash, diff --git a/server/src/lib/score-import/import-types/common/batch-manual/parser.test.ts b/server/src/lib/score-import/import-types/common/batch-manual/parser.test.ts index 584bc34ad..f954066f0 100644 --- a/server/src/lib/score-import/import-types/common/batch-manual/parser.test.ts +++ b/server/src/lib/score-import/import-types/common/batch-manual/parser.test.ts @@ -1,9 +1,9 @@ import deepmerge from "deepmerge"; +import { IIDXDans } from "lib/constants/classes"; import CreateLogCtx from "lib/logger/logger"; import { BatchManual } from "tachi-common"; import t from "tap"; import { EscapeStringRegexp } from "utils/misc"; -import { IIDXDans } from "lib/constants/classes"; import ScoreImportFatalError from "../../../framework/score-importing/score-import-error"; import { ParseBatchManualFromObject as ParserFn } from "./parser"; diff --git a/server/src/lib/score-import/import-types/common/batch-manual/parser.ts b/server/src/lib/score-import/import-types/common/batch-manual/parser.ts index 9efbcdbef..0850b442c 100644 --- a/server/src/lib/score-import/import-types/common/batch-manual/parser.ts +++ b/server/src/lib/score-import/import-types/common/batch-manual/parser.ts @@ -1,4 +1,5 @@ import deepmerge from "deepmerge"; +import { IIDXDans, SDVXDans, WACCA_STAGEUPS } from "lib/constants/classes"; import { KtLogger } from "lib/logger/logger"; import { TachiConfig } from "lib/setup/config"; import p, { PrudenceSchema, ValidSchemaValue } from "prudence"; @@ -12,7 +13,6 @@ import { Playtypes, } from "tachi-common"; import { FormatPrError } from "utils/prudence"; -import { IIDXDans, SDVXDans, WACCA_STAGEUPS } from "lib/constants/classes"; import ScoreImportFatalError from "../../../framework/score-importing/score-import-error"; import { ParserFunctionReturns } from "../types"; import { BatchManualContext } from "./types"; diff --git a/server/src/server/middleware/auth.ts b/server/src/server/middleware/auth.ts index 9685b55f3..84a606983 100644 --- a/server/src/server/middleware/auth.ts +++ b/server/src/server/middleware/auth.ts @@ -1,10 +1,10 @@ import { RequestHandler } from "express"; import db from "external/mongo/db"; import { SYMBOL_TachiAPIAuth } from "lib/constants/tachi"; -import { SplitAuthorizationHeader } from "utils/misc"; -import { APITokenDocument, APIPermissions, UserAuthLevels } from "tachi-common"; import CreateLogCtx from "lib/logger/logger"; import { TachiConfig } from "lib/setup/config"; +import { APIPermissions, APITokenDocument, UserAuthLevels } from "tachi-common"; +import { SplitAuthorizationHeader } from "utils/misc"; const logger = CreateLogCtx(__filename); diff --git a/server/src/server/middleware/multer-upload.ts b/server/src/server/middleware/multer-upload.ts index 4b454dfab..d852394e5 100644 --- a/server/src/server/middleware/multer-upload.ts +++ b/server/src/server/middleware/multer-upload.ts @@ -1,10 +1,10 @@ // @todo #118 import { RequestHandler } from "express"; -import multer, { MulterError } from "multer"; -import CreateLogCtx from "lib/logger/logger"; -import { integer } from "tachi-common"; import { SIXTEEN_MEGABTYES } from "lib/constants/filesize"; +import CreateLogCtx from "lib/logger/logger"; +import multer, { MulterError } from "multer"; +import { integer } from "tachi-common"; const defaultLogger = CreateLogCtx(__filename); diff --git a/server/src/server/middleware/prudence-validate.test.ts b/server/src/server/middleware/prudence-validate.test.ts index 38c272df9..f7bec380b 100644 --- a/server/src/server/middleware/prudence-validate.test.ts +++ b/server/src/server/middleware/prudence-validate.test.ts @@ -1,7 +1,7 @@ -import t from "tap"; import expMiddlewareMock from "express-request-mock"; -import prValidate from "./prudence-validate"; import Prudence from "prudence"; +import t from "tap"; +import prValidate from "./prudence-validate"; t.test("#PrudenceMiddleware", (t) => { const mw = prValidate({ foo: Prudence.regex(/^baz$/u) }, { foo: "example error message" }); diff --git a/server/src/server/middleware/prudence-validate.ts b/server/src/server/middleware/prudence-validate.ts index e45f50f31..c65020a6d 100644 --- a/server/src/server/middleware/prudence-validate.ts +++ b/server/src/server/middleware/prudence-validate.ts @@ -1,11 +1,11 @@ -import Prudence, { - MiddlewareErrorHandler, - PrudenceSchema, - ErrorMessages, - PrudenceOptions, -} from "prudence"; import { RequestHandler } from "express-serve-static-core"; import CreateLogCtx from "lib/logger/logger"; +import Prudence, { + ErrorMessages, + MiddlewareErrorHandler, + PrudenceOptions, + PrudenceSchema, +} from "prudence"; const logger = CreateLogCtx(__filename); diff --git a/server/src/server/middleware/request-logger.ts b/server/src/server/middleware/request-logger.ts index 657bf4ea5..6bbb2c58a 100644 --- a/server/src/server/middleware/request-logger.ts +++ b/server/src/server/middleware/request-logger.ts @@ -1,6 +1,6 @@ -import CreateLogCtx from "lib/logger/logger"; import { RequestHandler, Response } from "express-serve-static-core"; import { SYMBOL_TachiAPIAuth } from "lib/constants/tachi"; +import CreateLogCtx from "lib/logger/logger"; import { TachiConfig } from "lib/setup/config"; const logger = CreateLogCtx(__filename); diff --git a/server/src/server/middleware/sanitise-qstring.test.ts b/server/src/server/middleware/sanitise-qstring.test.ts index bd8d4d3ae..efab76a62 100644 --- a/server/src/server/middleware/sanitise-qstring.test.ts +++ b/server/src/server/middleware/sanitise-qstring.test.ts @@ -1,6 +1,6 @@ -import SanitiseQString from "./sanitise-qstring"; -import t from "tap"; import expMiddlewareMock from "express-request-mock"; +import t from "tap"; +import SanitiseQString from "./sanitise-qstring"; t.test("#SanitiseQString", (t) => { t.test("Should allow GET requests with valid data.", async (t) => { diff --git a/server/src/server/router/api/v1/auth/auth.ts b/server/src/server/router/api/v1/auth/auth.ts index 9b89fff0e..57492b4ae 100644 --- a/server/src/server/router/api/v1/auth/auth.ts +++ b/server/src/server/router/api/v1/auth/auth.ts @@ -1,4 +1,7 @@ import bcrypt from "bcryptjs"; +import db from "external/mongo/db"; +import CreateLogCtx from "lib/logger/logger"; +import { Environment, ServerConfig } from "lib/setup/config"; import { integer, PrivateUserInfoDocument, @@ -6,13 +9,10 @@ import { UserAuthLevels, UserSettings, } from "tachi-common"; -import db from "external/mongo/db"; -import CreateLogCtx from "lib/logger/logger"; -import { FormatUserDoc } from "utils/user"; import nodeFetch from "utils/fetch"; import { Random20Hex } from "utils/misc"; -import { Environment, ServerConfig } from "lib/setup/config"; import { CreateURLWithParams } from "utils/url"; +import { FormatUserDoc } from "utils/user"; const logger = CreateLogCtx(__filename); diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/sessions/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/sessions/router.ts index 77a68fc9b..eeddf47b2 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/sessions/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/sessions/router.ts @@ -13,8 +13,7 @@ const router: Router = Router({ mergeParams: true }); * @name GET /api/v1/users/:userID/games/:game/:playtype/sessions */ router.get("/", async (req, res) => { - const { user, game, playtype } = GetUGPT(req); - + const { user, game, playtype } = GetUGPT(req); if (typeof req.query.search !== "string") { return res.status(400).json({ @@ -40,7 +39,7 @@ router.get("/", async (req, res) => { * @name GET /api/v1/users/:userID/games/:game/:playtype/sessions/best */ router.get("/best", async (req, res) => { - const { user, game, playtype } = GetUGPT(req); + const { user, game, playtype } = GetUGPT(req); const gptConfig = GetGamePTConfig(game, playtype); @@ -87,8 +86,7 @@ router.get("/best", async (req, res) => { * @name GET /api/v1/users/:userID/games/:game/:playtype/sessions/highlighted */ router.get("/highlighted", async (req, res) => { - const { user, game, playtype } = GetUGPT(req); - + const { user, game, playtype } = GetUGPT(req); const sessions = await db.sessions.find( { userID: user.id, game, playtype, highlight: true }, @@ -108,8 +106,7 @@ router.get("/highlighted", async (req, res) => { * @name GET /api/v1/users/:userID/games/:game/:playtype/sessions/recent */ router.get("/recent", async (req, res) => { - const { user, game, playtype } = GetUGPT(req); - + const { user, game, playtype } = GetUGPT(req); const sessions = await db.sessions.find( { userID: user.id, game, playtype }, @@ -129,8 +126,7 @@ router.get("/recent", async (req, res) => { * @name GET /api/v1/users/:userID/games/:game/:playtype/sessions/last */ router.get("/last", async (req, res) => { - const { user, game, playtype } = GetUGPT(req); - + const { user, game, playtype } = GetUGPT(req); const session = await db.sessions.findOne( { userID: user.id, game, playtype }, diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/settings/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/settings/router.ts index 1800e532c..aec8c30f7 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/settings/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/settings/router.ts @@ -1,6 +1,5 @@ import { Router } from "express"; import db from "external/mongo/db"; -import { SYMBOL_TachiData } from "lib/constants/tachi"; import CreateLogCtx from "lib/logger/logger"; import p from "prudence"; import { RequirePermissions } from "server/middleware/auth"; diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/router.ts index 842e2aa9b..120f71b34 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/router.ts @@ -1,6 +1,5 @@ import { Router } from "express"; import db from "external/mongo/db"; -import { SYMBOL_TachiData } from "lib/constants/tachi"; import { GetUGPT } from "utils/req-tachi-data"; import goalsRouter from "./goals/router"; import milestonesRouter from "./milestones/router"; @@ -133,5 +132,6 @@ router.get("/recently-interacted", async (req, res) => { }); router.use("/goals", goalsRouter); +router.use("/milestones", milestonesRouter); export default router; diff --git a/server/src/server/router/ir/router.ts b/server/src/server/router/ir/router.ts index 5dd7ce82c..3a58cd1e1 100644 --- a/server/src/server/router/ir/router.ts +++ b/server/src/server/router/ir/router.ts @@ -11,8 +11,8 @@ import beatorajaIR from "./beatoraja/router"; import directManualIR from "./direct-manual/router"; import fervidexIR from "./fervidex/router"; import ksHookIR from "./kshook/router"; -import uscIR from "./usc/router"; import lr2hookIR from "./lr2hook/router"; +import uscIR from "./usc/router"; const router: Router = Router({ mergeParams: true }); diff --git a/server/src/test-utils/mock-api.ts b/server/src/test-utils/mock-api.ts index a30f23fab..c24e4f62f 100644 --- a/server/src/test-utils/mock-api.ts +++ b/server/src/test-utils/mock-api.ts @@ -1,10 +1,9 @@ -import supertest from "supertest"; import CreateLogCtx from "lib/logger/logger"; +import supertest from "supertest"; +import server from "../server/server"; const logger = CreateLogCtx(__filename); -import server from "../server/server"; - logger.verbose("Creating Mock Server Connection..."); const connection = server.listen(); diff --git a/server/src/test-utils/server.ts b/server/src/test-utils/server.ts index f8806cf6f..2ec386f46 100644 --- a/server/src/test-utils/server.ts +++ b/server/src/test-utils/server.ts @@ -1,5 +1,5 @@ -import server from "../server/server"; import supertest from "supertest"; +import server from "../server/server"; const mockServer = supertest(server); From fba276672572e8c1b45067151e7eacfc3c108a66 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Fri, 1 Apr 2022 14:04:06 +0100 Subject: [PATCH 031/106] Fix compilation errors --- .../score-import/framework/milestones/milestones.test.ts | 6 ++++++ .../users/_userID/games/_game/_playtype/sessions/router.ts | 1 + 2 files changed, 7 insertions(+) diff --git a/server/src/lib/score-import/framework/milestones/milestones.test.ts b/server/src/lib/score-import/framework/milestones/milestones.test.ts index eceb2e3b0..0e49b7d8b 100644 --- a/server/src/lib/score-import/framework/milestones/milestones.test.ts +++ b/server/src/lib/score-import/framework/milestones/milestones.test.ts @@ -29,6 +29,7 @@ t.test("#UpdateUsersMilestones", (t) => { playtype: "SP", progress: 0, timeAchieved: null, + lastInteraction: null, timeSet: 0, userID: 1, }) @@ -161,6 +162,7 @@ t.test("#UpdateUsersMilestones", (t) => { playtype: "SP", progress: 0, timeAchieved: null, + lastInteraction: null, timeSet: 0, userID: 1, }); @@ -271,6 +273,7 @@ t.test("#UpdateUsersMilestones", (t) => { playtype: "SP", progress: 0, timeAchieved: null, + lastInteraction: null, timeSet: 0, userID: 1, }, @@ -283,6 +286,7 @@ t.test("#UpdateUsersMilestones", (t) => { playtype: "DP", progress: 0, timeAchieved: null, + lastInteraction: null, timeSet: 0, userID: 1, }, @@ -295,6 +299,7 @@ t.test("#UpdateUsersMilestones", (t) => { playtype: "Single", progress: 0, timeAchieved: null, + lastInteraction: null, timeSet: 0, userID: 1, }, @@ -306,6 +311,7 @@ t.test("#UpdateUsersMilestones", (t) => { playtype: "SP", progress: 0, timeAchieved: null, + lastInteraction: null, timeSet: 0, userID: 1, }, diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/sessions/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/sessions/router.ts index eeddf47b2..84ae9a80a 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/sessions/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/sessions/router.ts @@ -3,6 +3,7 @@ import db from "external/mongo/db"; import { SYMBOL_TachiData } from "lib/constants/tachi"; import { SearchSessions } from "lib/search/search"; import { GetGamePTConfig } from "tachi-common"; +import { GetUGPT } from "utils/req-tachi-data"; import { CheckStrSessionAlg } from "utils/string-checks"; const router: Router = Router({ mergeParams: true }); From b970a3d601f0d56ec05a4e96976a08e42a4aab72 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Fri, 1 Apr 2022 14:04:43 +0100 Subject: [PATCH 032/106] Add a lastInteraction prop to mile-subs --- server/src/lib/targets/milestones.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/server/src/lib/targets/milestones.ts b/server/src/lib/targets/milestones.ts index f538f77b2..09e3d7679 100644 --- a/server/src/lib/targets/milestones.ts +++ b/server/src/lib/targets/milestones.ts @@ -206,6 +206,7 @@ export async function SubscribeToMilestone( playtype: milestone.playtype, achieved: result.achieved, timeAchieved: result.achieved ? Date.now() : null, + lastInteraction: null, }; // @optimisable, EvaluateMilestoneProgress calculates the users progress From c0bb0cf8be6c9fad302c78e5acefe2b9a0013cca Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Fri, 1 Apr 2022 14:08:00 +0100 Subject: [PATCH 033/106] Add lastInteraction support to mile-subs --- .../lib/score-import/framework/milestones/milestones.ts | 8 ++++++-- .../users/_userID/games/_game/_playtype/targets/router.ts | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/server/src/lib/score-import/framework/milestones/milestones.ts b/server/src/lib/score-import/framework/milestones/milestones.ts index db36a74cb..da49ee84a 100644 --- a/server/src/lib/score-import/framework/milestones/milestones.ts +++ b/server/src/lib/score-import/framework/milestones/milestones.ts @@ -102,7 +102,7 @@ export async function UpdateUsersMilestones( throw new Error("Invalid state achieved in milestone processing."); } - bwrite.push({ + const bwriteOp: BulkWriteUpdateOneOperation = { updateOne: { filter: { milestoneID: milestone.milestoneID }, update: { @@ -112,7 +112,7 @@ export async function UpdateUsersMilestones( }, }, }, - }); + }; const milestoneInfo = { milestoneID: milestoneSub.milestoneID, @@ -128,8 +128,12 @@ export async function UpdateUsersMilestones( if (progress !== milestoneSub.progress) { importMilestoneInfo.push(milestoneInfo); + // @ts-expect-error This property isn't read only, because I said so. + bwriteOp.updateOne.update.$set!.lastInteraction = Date.now(); } + bwrite.push(bwriteOp); + if (achieved && !milestoneSub.achieved) { EmitWebhookEvent({ type: "milestone-achieved/v1", diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/router.ts index 120f71b34..320785fb6 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/router.ts @@ -101,6 +101,7 @@ router.get("/recently-interacted", async (req, res) => { playtype, wasInstantlyAchieved: false, userID, + lastInteraction: { $ne: null }, }, { sort: { From 67613bafaa5325070957ed141cde261270dbbc42 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Fri, 1 Apr 2022 14:17:38 +0100 Subject: [PATCH 034/106] Use GetGPT instead of raw symbol_ interactions --- .../_playtype/charts/_chartID/middleware.ts | 6 ++---- .../_playtype/charts/_chartID/router.test.ts | 7 +++---- .../_game/_playtype/charts/router.test.ts | 6 +++--- .../v1/games/_game/_playtype/charts/router.ts | 6 +++--- .../_game/_playtype/folders/middleware.ts | 6 ++---- .../_game/_playtype/folders/router.test.ts | 7 +++---- .../games/_game/_playtype/folders/router.ts | 4 ++-- .../api/v1/games/_game/_playtype/router.ts | 19 +++++++----------- .../v1/games/_game/_playtype/scores/router.ts | 9 ++++----- .../_game/_playtype/songs/_songID/router.ts | 4 ++-- .../v1/games/_game/_playtype/tables/router.ts | 4 ++-- .../_game/_playtype/targets/goals/router.ts | 13 +++++------- .../_playtype/targets/milestones/router.ts | 10 ++++------ .../games/_game/_playtype/folders/router.ts | 1 - .../games/_game/_playtype/pbs/router.ts | 1 - .../games/_game/_playtype/scores/router.ts | 1 - .../games/_game/_playtype/sessions/router.ts | 1 - .../games/_game/_playtype/showcase/router.ts | 1 - .../_game/_playtype/targets/goals/router.ts | 20 +++++++++---------- .../_playtype/targets/milestones/router.ts | 5 ++--- .../api/v1/users/_userID/pfp/router.test.ts | 3 +-- .../router/api/v1/users/_userID/pfp/router.ts | 6 +++--- .../api/v1/users/_userID/router.test.ts | 2 +- 23 files changed, 59 insertions(+), 83 deletions(-) diff --git a/server/src/server/router/api/v1/games/_game/_playtype/charts/_chartID/middleware.ts b/server/src/server/router/api/v1/games/_game/_playtype/charts/_chartID/middleware.ts index 4995a9d8e..b823d6c54 100644 --- a/server/src/server/router/api/v1/games/_game/_playtype/charts/_chartID/middleware.ts +++ b/server/src/server/router/api/v1/games/_game/_playtype/charts/_chartID/middleware.ts @@ -1,11 +1,9 @@ import { RequestHandler } from "express"; import db from "external/mongo/db"; -import { SYMBOL_TachiData } from "lib/constants/tachi"; -import { AssignToReqTachiData } from "utils/req-tachi-data"; +import { AssignToReqTachiData, GetGPT } from "utils/req-tachi-data"; export const ValidateAndGetChart: RequestHandler = async (req, res, next) => { - const game = req[SYMBOL_TachiData]!.game!; - const playtype = req[SYMBOL_TachiData]!.playtype!; + const { game, playtype } = GetGPT(req); const chart = await db.charts[game].findOne({ chartID: req.params.chartID, diff --git a/server/src/server/router/api/v1/games/_game/_playtype/charts/_chartID/router.test.ts b/server/src/server/router/api/v1/games/_game/_playtype/charts/_chartID/router.test.ts index f9624d332..01a278769 100644 --- a/server/src/server/router/api/v1/games/_game/_playtype/charts/_chartID/router.test.ts +++ b/server/src/server/router/api/v1/games/_game/_playtype/charts/_chartID/router.test.ts @@ -1,11 +1,10 @@ -import t from "tap"; +import deepmerge from "deepmerge"; import db from "external/mongo/db"; - +import { PBScoreDocument, PublicUserDocument } from "tachi-common"; +import t from "tap"; import mockApi from "test-utils/mock-api"; import ResetDBState from "test-utils/resets"; import { Testing511SPA, TestingIIDXSPScorePB } from "test-utils/test-data"; -import deepmerge from "deepmerge"; -import { PBScoreDocument, PublicUserDocument } from "tachi-common"; t.test("GET /api/v1/games/:game/:playtype/charts/:chartID", (t) => { t.beforeEach(ResetDBState); diff --git a/server/src/server/router/api/v1/games/_game/_playtype/charts/router.test.ts b/server/src/server/router/api/v1/games/_game/_playtype/charts/router.test.ts index f5a89edf7..138515154 100644 --- a/server/src/server/router/api/v1/games/_game/_playtype/charts/router.test.ts +++ b/server/src/server/router/api/v1/games/_game/_playtype/charts/router.test.ts @@ -1,9 +1,9 @@ -import t from "tap"; import db from "external/mongo/db"; +import { PBScoreDocument } from "tachi-common"; +import t from "tap"; +import mockApi from "test-utils/mock-api"; import ResetDBState from "test-utils/resets"; import { LoadTachiIIDXData, Testing511SPA } from "test-utils/test-data"; -import { PBScoreDocument } from "tachi-common"; -import mockApi from "test-utils/mock-api"; t.test("GET /api/v1/games/:game/:playtype/charts", (t) => { t.beforeEach(ResetDBState); diff --git a/server/src/server/router/api/v1/games/_game/_playtype/charts/router.ts b/server/src/server/router/api/v1/games/_game/_playtype/charts/router.ts index 3f5abc322..c77db0a58 100644 --- a/server/src/server/router/api/v1/games/_game/_playtype/charts/router.ts +++ b/server/src/server/router/api/v1/games/_game/_playtype/charts/router.ts @@ -1,10 +1,11 @@ import { Router } from "express"; import db from "external/mongo/db"; -import { SYMBOL_TachiAPIAuth, SYMBOL_TachiData } from "lib/constants/tachi"; +import { SYMBOL_TachiAPIAuth } from "lib/constants/tachi"; import { SearchGameSongs } from "lib/search/search"; import { ChartDocument, UGPTSettings } from "tachi-common"; import { IsString } from "utils/misc"; import { FindChartsOnPopularity } from "utils/queries/charts"; +import { GetGPT } from "utils/req-tachi-data"; import chartIDRouter from "./_chartID/router"; const router: Router = Router({ mergeParams: true }); @@ -20,8 +21,7 @@ const router: Router = Router({ mergeParams: true }); * @name GET /api/v1/games/:game/:playtype/charts */ router.get("/", async (req, res) => { - const game = req[SYMBOL_TachiData]!.game!; - const playtype = req[SYMBOL_TachiData]!.playtype!; + const { game, playtype } = GetGPT(req); let songIDs = undefined; diff --git a/server/src/server/router/api/v1/games/_game/_playtype/folders/middleware.ts b/server/src/server/router/api/v1/games/_game/_playtype/folders/middleware.ts index 59bc41bcf..6cba90bfe 100644 --- a/server/src/server/router/api/v1/games/_game/_playtype/folders/middleware.ts +++ b/server/src/server/router/api/v1/games/_game/_playtype/folders/middleware.ts @@ -1,11 +1,9 @@ import { RequestHandler } from "express"; import db from "external/mongo/db"; -import { SYMBOL_TachiData } from "lib/constants/tachi"; -import { AssignToReqTachiData } from "utils/req-tachi-data"; +import { AssignToReqTachiData, GetGPT } from "utils/req-tachi-data"; export const GetFolderFromParam: RequestHandler = async (req, res, next) => { - const game = req[SYMBOL_TachiData]!.game!; - const playtype = req[SYMBOL_TachiData]!.playtype!; + const { game, playtype } = GetGPT(req); const folder = await db.folders.findOne({ folderID: req.params.folderID, game, playtype }); diff --git a/server/src/server/router/api/v1/games/_game/_playtype/folders/router.test.ts b/server/src/server/router/api/v1/games/_game/_playtype/folders/router.test.ts index 2bec59c98..03163a7dc 100644 --- a/server/src/server/router/api/v1/games/_game/_playtype/folders/router.test.ts +++ b/server/src/server/router/api/v1/games/_game/_playtype/folders/router.test.ts @@ -1,10 +1,9 @@ -import t from "tap"; +import deepmerge from "deepmerge"; import db from "external/mongo/db"; +import { FolderDocument } from "tachi-common"; +import t from "tap"; import mockApi from "test-utils/mock-api"; import ResetDBState from "test-utils/resets"; -import deepmerge from "deepmerge"; -import { FolderDocument } from "tachi-common"; - import { Testing511SPA } from "test-utils/test-data"; import { CreateFolderChartLookup } from "utils/folder"; diff --git a/server/src/server/router/api/v1/games/_game/_playtype/folders/router.ts b/server/src/server/router/api/v1/games/_game/_playtype/folders/router.ts index d81b18bd4..25f92c99c 100644 --- a/server/src/server/router/api/v1/games/_game/_playtype/folders/router.ts +++ b/server/src/server/router/api/v1/games/_game/_playtype/folders/router.ts @@ -4,6 +4,7 @@ import { SYMBOL_TachiData } from "lib/constants/tachi"; import { SearchCollection } from "lib/search/search"; import { GetFolderCharts } from "utils/folder"; import { IsString } from "utils/misc"; +import { GetGPT } from "utils/req-tachi-data"; import { GetFolderFromParam } from "./middleware"; const router: Router = Router({ mergeParams: true }); @@ -17,8 +18,7 @@ const router: Router = Router({ mergeParams: true }); * @name GET /api/v1/games/:game/:playtype/folders */ router.get("/", async (req, res) => { - const game = req[SYMBOL_TachiData]!.game!; - const playtype = req[SYMBOL_TachiData]!.playtype!; + const { game, playtype } = GetGPT(req); if (!IsString(req.query.search)) { return res.status(400).json({ diff --git a/server/src/server/router/api/v1/games/_game/_playtype/router.ts b/server/src/server/router/api/v1/games/_game/_playtype/router.ts index 8ddc5da4e..ca667e2c8 100644 --- a/server/src/server/router/api/v1/games/_game/_playtype/router.ts +++ b/server/src/server/router/api/v1/games/_game/_playtype/router.ts @@ -1,6 +1,5 @@ import { Router } from "express"; import db from "external/mongo/db"; -import { SYMBOL_TachiData } from "lib/constants/tachi"; import { ONE_HOUR } from "lib/constants/time"; import { FindOptions } from "monk"; import NodeCache from "node-cache"; @@ -10,6 +9,7 @@ import { FormatGame, Game, GetGamePTConfig, integer, Playtypes, UserGameStats } import { GetRelevantSongsAndCharts } from "utils/db"; import { IsString } from "utils/misc"; import { GetClassDistribution } from "utils/queries/stats"; +import { GetGPT } from "utils/req-tachi-data"; import { CheckStrProfileAlg, CheckStrScoreAlg, @@ -18,11 +18,11 @@ import { import { GetUsersWithIDs } from "utils/user"; import chartsRouter from "./charts/router"; import foldersRouter from "./folders/router"; -import goalsRouter from "./targets/goals/router"; import { ValidatePlaytypeFromParam } from "./middleware"; import scoresRouter from "./scores/router"; import songIDRouter from "./songs/_songID/router"; import tablesRouter from "./tables/router"; +import goalsRouter from "./targets/goals/router"; const router: Router = Router({ mergeParams: true }); @@ -63,8 +63,7 @@ async function GetGameStats( * @name GET /api/v1/games/:game/:playtype */ router.get("/", async (req, res) => { - const game = req[SYMBOL_TachiData]!.game!; - const playtype = req[SYMBOL_TachiData]!.playtype!; + const { game, playtype } = GetGPT(req); const { scoreCount, playerCount, chartCount } = await GetGameStats(game, playtype); @@ -90,8 +89,7 @@ router.get("/", async (req, res) => { * @name GET /api/v1/games/:game/:playtype/leaderboard */ router.get("/leaderboard", async (req, res) => { - const game = req[SYMBOL_TachiData]!.game!; - const playtype = req[SYMBOL_TachiData]!.playtype!; + const { game, playtype } = GetGPT(req); const gptConfig = GetGamePTConfig(game, playtype); const limit = ParseStrPositiveNonZeroInt(req.query.limit) ?? 100; @@ -155,8 +153,7 @@ router.get("/leaderboard", async (req, res) => { * @name GET /api/v1/games/:game/:playtype/score-leaderboard */ router.get("/score-leaderboard", async (req, res) => { - const game = req[SYMBOL_TachiData]!.game!; - const playtype = req[SYMBOL_TachiData]!.playtype!; + const { game, playtype } = GetGPT(req); const gptConfig = GetGamePTConfig(game, playtype); const limit = ParseStrPositiveNonZeroInt(req.query.limit) ?? 50; @@ -226,8 +223,7 @@ router.get( class: "string", }), async (req, res) => { - const game = req[SYMBOL_TachiData]!.game!; - const playtype = req[SYMBOL_TachiData]!.playtype!; + const { game, playtype } = GetGPT(req); const gptConfig = GetGamePTConfig(game, playtype); const stat = req.query.class as string; @@ -268,8 +264,7 @@ router.get( limit: p.optional((self) => p.isBoundedInteger(1, 50)(Number(self))), }), async (req, res) => { - const game = req[SYMBOL_TachiData]!.game!; - const playtype = req[SYMBOL_TachiData]!.playtype!; + const { game, playtype } = GetGPT(req); const limit = req.query.limit ? Number(req.query.limit) : 10; diff --git a/server/src/server/router/api/v1/games/_game/_playtype/scores/router.ts b/server/src/server/router/api/v1/games/_game/_playtype/scores/router.ts index 78b309cfb..96fd783d3 100644 --- a/server/src/server/router/api/v1/games/_game/_playtype/scores/router.ts +++ b/server/src/server/router/api/v1/games/_game/_playtype/scores/router.ts @@ -1,10 +1,10 @@ import { Router } from "express"; import db from "external/mongo/db"; -import { SYMBOL_TachiData } from "lib/constants/tachi"; -import prValidate from "server/middleware/prudence-validate"; import p from "prudence"; -import { GetUsersWithIDs } from "utils/user"; +import prValidate from "server/middleware/prudence-validate"; import { GetRelevantSongsAndCharts } from "utils/db"; +import { GetGPT } from "utils/req-tachi-data"; +import { GetUsersWithIDs } from "utils/user"; const router: Router = Router({ mergeParams: true }); @@ -21,8 +21,7 @@ router.get( limit: p.optional((self) => p.isBoundedInteger(1, 500)(Number(self))), }), async (req, res) => { - const game = req[SYMBOL_TachiData]!.game!; - const playtype = req[SYMBOL_TachiData]!.playtype!; + const { game, playtype } = GetGPT(req); const limit = req.query.limit ? Number(req.query.limit) : 10; diff --git a/server/src/server/router/api/v1/games/_game/_playtype/songs/_songID/router.ts b/server/src/server/router/api/v1/games/_game/_playtype/songs/_songID/router.ts index dce568c4b..d0a2d3144 100644 --- a/server/src/server/router/api/v1/games/_game/_playtype/songs/_songID/router.ts +++ b/server/src/server/router/api/v1/games/_game/_playtype/songs/_songID/router.ts @@ -1,6 +1,7 @@ import { Router } from "express"; import db from "external/mongo/db"; import { SYMBOL_TachiData } from "lib/constants/tachi"; +import { GetGPT } from "utils/req-tachi-data"; import { ValidateAndGetSong } from "./middleware"; const router: Router = Router({ mergeParams: true }); @@ -14,8 +15,7 @@ router.use(ValidateAndGetSong); */ router.get("/", async (req, res) => { const song = req[SYMBOL_TachiData]!.songDoc!; - const game = req[SYMBOL_TachiData]!.game!; - const playtype = req[SYMBOL_TachiData]!.playtype!; + const { game, playtype } = GetGPT(req); const charts = await db.charts[game].find({ songID: song.id, diff --git a/server/src/server/router/api/v1/games/_game/_playtype/tables/router.ts b/server/src/server/router/api/v1/games/_game/_playtype/tables/router.ts index 37a45c69b..3e420b134 100644 --- a/server/src/server/router/api/v1/games/_game/_playtype/tables/router.ts +++ b/server/src/server/router/api/v1/games/_game/_playtype/tables/router.ts @@ -4,6 +4,7 @@ import { SYMBOL_TachiData } from "lib/constants/tachi"; import { FilterQuery } from "mongodb"; import { TableDocument } from "tachi-common"; import { GetFoldersFromTable } from "utils/folder"; +import { GetGPT } from "utils/req-tachi-data"; import { GetTableFromParam } from "./middleware"; const router: Router = Router({ mergeParams: true }); @@ -16,8 +17,7 @@ const router: Router = Router({ mergeParams: true }); * @name GET /api/v1/games/:game/:playtype/tables */ router.get("/", async (req, res) => { - const game = req[SYMBOL_TachiData]!.game!; - const playtype = req[SYMBOL_TachiData]!.playtype!; + const { game, playtype } = GetGPT(req); const query: FilterQuery = { game, playtype }; diff --git a/server/src/server/router/api/v1/games/_game/_playtype/targets/goals/router.ts b/server/src/server/router/api/v1/games/_game/_playtype/targets/goals/router.ts index 0d24f0a59..9e1990872 100644 --- a/server/src/server/router/api/v1/games/_game/_playtype/targets/goals/router.ts +++ b/server/src/server/router/api/v1/games/_game/_playtype/targets/goals/router.ts @@ -1,11 +1,11 @@ import { RequestHandler, Router } from "express"; import db from "external/mongo/db"; -import { EvaluatedGoalReturn, EvaluateGoalForUser } from "lib/targets/goals"; import { SYMBOL_TachiData } from "lib/constants/tachi"; import CreateLogCtx from "lib/logger/logger"; +import { EvaluatedGoalReturn, EvaluateGoalForUser } from "lib/targets/goals"; import prValidate from "server/middleware/prudence-validate"; import { FormatGame } from "tachi-common"; -import { AssignToReqTachiData } from "utils/req-tachi-data"; +import { AssignToReqTachiData, GetGPT } from "utils/req-tachi-data"; import { GetUsersWithIDs, ResolveUser } from "utils/user"; const logger = CreateLogCtx(__filename); @@ -18,8 +18,7 @@ const router: Router = Router({ mergeParams: true }); * @name GET /api/v1/games/:game/:playtype/targets/goals/recently-achieved */ router.get("/recently-achieved", async (req, res) => { - const game = req[SYMBOL_TachiData]!.game!; - const playtype = req[SYMBOL_TachiData]!.playtype!; + const { game, playtype } = GetGPT(req); const recentlyAchievedUserGoals = await db["goal-subs"].find( { @@ -54,8 +53,7 @@ router.get("/recently-achieved", async (req, res) => { }); const ResolveGoalID: RequestHandler = async (req, res, next) => { - const game = req[SYMBOL_TachiData]!.game!; - const playtype = req[SYMBOL_TachiData]!.playtype!; + const { game, playtype } = GetGPT(req); const goalID = req.params.goalID; const goal = await db.goals.findOne({ @@ -115,8 +113,7 @@ router.get( userID: "string", }), async (req, res) => { - const game = req[SYMBOL_TachiData]!.game!; - const playtype = req[SYMBOL_TachiData]!.playtype!; + const { game, playtype } = GetGPT(req); const userID = req.query.userID as string; diff --git a/server/src/server/router/api/v1/games/_game/_playtype/targets/milestones/router.ts b/server/src/server/router/api/v1/games/_game/_playtype/targets/milestones/router.ts index c037cde72..bcc395ec2 100644 --- a/server/src/server/router/api/v1/games/_game/_playtype/targets/milestones/router.ts +++ b/server/src/server/router/api/v1/games/_game/_playtype/targets/milestones/router.ts @@ -1,17 +1,16 @@ import { RequestHandler, Router } from "express"; import db from "external/mongo/db"; -import { SYMBOL_TachiAPIAuth, SYMBOL_TachiData } from "lib/constants/tachi"; +import { SYMBOL_TachiData } from "lib/constants/tachi"; import { EvaluateMilestoneProgress, GetGoalsInMilestone } from "lib/targets/milestones"; import prValidate from "server/middleware/prudence-validate"; import { FormatGame } from "tachi-common"; -import { AssignToReqTachiData } from "utils/req-tachi-data"; +import { AssignToReqTachiData, GetGPT } from "utils/req-tachi-data"; import { GetUsersWithIDs, ResolveUser } from "utils/user"; const router: Router = Router({ mergeParams: true }); const ResolveMilestoneID: RequestHandler = async (req, res, next) => { - const game = req[SYMBOL_TachiData]!.game!; - const playtype = req[SYMBOL_TachiData]!.playtype!; + const { game, playtype } = GetGPT(req); const milestoneID = req.params.milestoneID; const milestone = await db.milestones.findOne({ @@ -72,8 +71,7 @@ router.get( ResolveMilestoneID, prValidate({ userID: "string" }), async (req, res) => { - const game = req[SYMBOL_TachiData]!.game!; - const playtype = req[SYMBOL_TachiData]!.playtype!; + const { game, playtype } = GetGPT(req); const userID = req.query.userID as string; diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/folders/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/folders/router.ts index e02f0195f..934037454 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/folders/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/folders/router.ts @@ -1,6 +1,5 @@ import { Router } from "express"; import db from "external/mongo/db"; -import { SYMBOL_TachiData } from "lib/constants/tachi"; import { SearchCollection } from "lib/search/search"; import { GetGradeLampDistributionForFolders, GetRecentlyViewedFolders } from "utils/folder"; import { IsString } from "utils/misc"; diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/pbs/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/pbs/router.ts index 037b5c64e..bde226a4b 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/pbs/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/pbs/router.ts @@ -1,6 +1,5 @@ import { Router } from "express"; import db from "external/mongo/db"; -import { SYMBOL_TachiData } from "lib/constants/tachi"; import { SearchGameSongsAndCharts } from "lib/search/search"; import { GetGamePTConfig } from "tachi-common"; import { GetRelevantSongsAndCharts } from "utils/db"; diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/scores/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/scores/router.ts index 5cfc28dd7..fca5befb8 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/scores/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/scores/router.ts @@ -1,6 +1,5 @@ import { Router } from "express"; import db from "external/mongo/db"; -import { SYMBOL_TachiData } from "lib/constants/tachi"; import { SearchGameSongsAndCharts } from "lib/search/search"; import { GetRelevantSongsAndCharts } from "utils/db"; import { GetUGPT } from "utils/req-tachi-data"; diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/sessions/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/sessions/router.ts index 84ae9a80a..509d81af1 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/sessions/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/sessions/router.ts @@ -1,6 +1,5 @@ import { Router } from "express"; import db from "external/mongo/db"; -import { SYMBOL_TachiData } from "lib/constants/tachi"; import { SearchSessions } from "lib/search/search"; import { GetGamePTConfig } from "tachi-common"; import { GetUGPT } from "utils/req-tachi-data"; diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/showcase/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/showcase/router.ts index d26c6d748..466fb6dcb 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/showcase/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/showcase/router.ts @@ -1,6 +1,5 @@ import { Router } from "express"; import db from "external/mongo/db"; -import { SYMBOL_TachiData } from "lib/constants/tachi"; import { EvaluateShowcaseStat } from "lib/showcase/evaluator"; import { GetRelatedStatDocuments } from "lib/showcase/get-related"; import { EvaluateUsersStatsShowcase } from "lib/showcase/get-stats"; diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts index 698f58e65..231f49985 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts @@ -1,17 +1,17 @@ import { RequestHandler, Router } from "express"; import db from "external/mongo/db"; -import { SYMBOL_TachiData } from "lib/constants/tachi"; -import prValidate from "server/middleware/prudence-validate"; -import p from "prudence"; -import { GoalDocument, MilestoneDocument } from "tachi-common"; -import { ConstructGoal, SubscribeToGoal } from "lib/targets/goals"; -import CreateLogCtx from "lib/logger/logger"; -import { RequirePermissions } from "server/middleware/auth"; -import { AssignToReqTachiData, GetUGPT } from "utils/req-tachi-data"; -import { GetGoalForIDGuaranteed, GetMilestoneForIDGuaranteed } from "utils/db"; -import { RequireAuthedAsUser } from "../../../../../middleware"; import { SubscribeFailReasons } from "lib/constants/err-codes"; +import { SYMBOL_TachiData } from "lib/constants/tachi"; +import CreateLogCtx from "lib/logger/logger"; import { ServerConfig } from "lib/setup/config"; +import { ConstructGoal, SubscribeToGoal } from "lib/targets/goals"; +import p from "prudence"; +import { RequirePermissions } from "server/middleware/auth"; +import prValidate from "server/middleware/prudence-validate"; +import { GoalDocument, MilestoneDocument } from "tachi-common"; +import { GetGoalForIDGuaranteed, GetMilestoneForIDGuaranteed } from "utils/db"; +import { AssignToReqTachiData, GetUGPT } from "utils/req-tachi-data"; +import { RequireAuthedAsUser } from "../../../../../middleware"; const router: Router = Router({ mergeParams: true }); diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/milestones/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/milestones/router.ts index 376209e02..e8e1fb120 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/milestones/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/milestones/router.ts @@ -10,7 +10,7 @@ import { UnsubscribeFromMilestone, } from "lib/targets/milestones"; import { RequirePermissions } from "server/middleware/auth"; -import { AssignToReqTachiData, GetUGPT } from "utils/req-tachi-data"; +import { AssignToReqTachiData, GetGPT, GetUGPT } from "utils/req-tachi-data"; import { FormatUserDoc } from "utils/user"; import { RequireAuthedAsUser } from "../../../../../middleware"; @@ -69,8 +69,7 @@ const GetMilestoneSubscription: RequestHandler = async (req, res, next) => { }; const GetMilestone: RequestHandler = async (req, res, next) => { - const game = req[SYMBOL_TachiData]!.game!; - const playtype = req[SYMBOL_TachiData]!.playtype!; + const { game, playtype } = GetGPT(req); const milestone = await db.milestones.findOne({ game, diff --git a/server/src/server/router/api/v1/users/_userID/pfp/router.test.ts b/server/src/server/router/api/v1/users/_userID/pfp/router.test.ts index 6180c0b3d..6bb0ed61c 100644 --- a/server/src/server/router/api/v1/users/_userID/pfp/router.test.ts +++ b/server/src/server/router/api/v1/users/_userID/pfp/router.test.ts @@ -1,8 +1,7 @@ -import t from "tap"; import db from "external/mongo/db"; import { CDNStoreOrOverwrite } from "lib/cdn/cdn"; import { GetProfilePictureURL } from "lib/cdn/url-format"; - +import t from "tap"; import mockApi from "test-utils/mock-api"; import ResetDBState from "test-utils/resets"; import { GetKTDataBuffer } from "test-utils/test-data"; diff --git a/server/src/server/router/api/v1/users/_userID/pfp/router.ts b/server/src/server/router/api/v1/users/_userID/pfp/router.ts index 1d78d6283..22b7ae445 100644 --- a/server/src/server/router/api/v1/users/_userID/pfp/router.ts +++ b/server/src/server/router/api/v1/users/_userID/pfp/router.ts @@ -1,15 +1,15 @@ import { Router } from "express"; import db from "external/mongo/db"; -import { CDNStoreOrOverwrite, CDNRedirect, CDNDelete } from "lib/cdn/cdn"; +import { CDNDelete, CDNRedirect, CDNStoreOrOverwrite } from "lib/cdn/cdn"; import { GetProfilePictureURL } from "lib/cdn/url-format"; import { ONE_MEGABYTE } from "lib/constants/filesize"; import { SYMBOL_TachiData } from "lib/constants/tachi"; import CreateLogCtx from "lib/logger/logger"; -import { FormatUserDoc } from "utils/user"; import { RequirePermissions } from "server/middleware/auth"; import { CreateMulterSingleUploadMiddleware } from "server/middleware/multer-upload"; -import { RequireAuthedAsUser } from "../middleware"; import { HashSHA256 } from "utils/crypto"; +import { FormatUserDoc } from "utils/user"; +import { RequireAuthedAsUser } from "../middleware"; const logger = CreateLogCtx(__filename); diff --git a/server/src/server/router/api/v1/users/_userID/router.test.ts b/server/src/server/router/api/v1/users/_userID/router.test.ts index 0714156a1..124f5d041 100644 --- a/server/src/server/router/api/v1/users/_userID/router.test.ts +++ b/server/src/server/router/api/v1/users/_userID/router.test.ts @@ -1,6 +1,6 @@ import db from "external/mongo/db"; import { ONE_DAY, ONE_YEAR } from "lib/constants/time"; -import { UserGameStats, ImportTypes, ImportDocument, Game } from "tachi-common"; +import { Game, ImportDocument, ImportTypes, UserGameStats } from "tachi-common"; import t from "tap"; import { CreateFakeAuthCookie } from "test-utils/fake-auth"; import mockApi from "test-utils/mock-api"; From 8f543fc8d6e9521ab90bfc5b222b2bd20dd28dfe Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Fri, 1 Apr 2022 14:37:25 +0100 Subject: [PATCH 035/106] Rename GetRecentlyAchievedGoals to GetGoalSummary --- server/src/server/router/api/v1/users/_userID/router.ts | 4 ++-- server/src/utils/queries/summary.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/server/src/server/router/api/v1/users/_userID/router.ts b/server/src/server/router/api/v1/users/_userID/router.ts index b68e3be01..26a96f393 100644 --- a/server/src/server/router/api/v1/users/_userID/router.ts +++ b/server/src/server/router/api/v1/users/_userID/router.ts @@ -9,7 +9,7 @@ import { ImportTypes, integer, UserGameStats } from "tachi-common"; import { DeleteUndefinedProps, StripUrl } from "utils/misc"; import { optNullFluffStrField } from "utils/prudence"; import { - GetRecentlyAchievedGoals, + GetGoalSummary, GetRecentlyViewedFoldersAnyGPT, GetRecentPlaycount, GetRecentSessions, @@ -230,7 +230,7 @@ router.get("/recent-summary", async (req, res) => { GetRecentPlaycount(user.id), GetRecentSessions(user.id), GetRecentlyViewedFoldersAnyGPT(user.id), - GetRecentlyAchievedGoals(user.id), + GetGoalSummary(user.id), ]); return res.status(200).json({ diff --git a/server/src/utils/queries/summary.ts b/server/src/utils/queries/summary.ts index 57ee60754..4b9dcb62b 100644 --- a/server/src/utils/queries/summary.ts +++ b/server/src/utils/queries/summary.ts @@ -49,7 +49,7 @@ export async function GetRecentlyViewedFoldersAnyGPT(userID: integer) { return { folders, stats }; } -export async function GetRecentlyAchievedGoals(userID: integer) { +export async function GetGoalSummary(userID: integer) { const time = GetTimeXHoursAgo(REASONABLE_HOURS_AGO); const achievedGoals = await db["goal-subs"].find({ From 8dbb89d274e4983472c39fdec22bbbfbd8e28162 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Fri, 1 Apr 2022 14:47:50 +0100 Subject: [PATCH 036/106] Add GPT recently-achieved and recently-interacted endpoints --- .../games/_game/_playtype/targets/router.ts | 61 ++++++++ .../games/_game/_playtype/targets/router.ts | 101 +++---------- server/src/utils/db.ts | 141 +++++++++++++++++- 3 files changed, 219 insertions(+), 84 deletions(-) diff --git a/server/src/server/router/api/v1/games/_game/_playtype/targets/router.ts b/server/src/server/router/api/v1/games/_game/_playtype/targets/router.ts index 9ba386131..f1b8a0471 100644 --- a/server/src/server/router/api/v1/games/_game/_playtype/targets/router.ts +++ b/server/src/server/router/api/v1/games/_game/_playtype/targets/router.ts @@ -1,9 +1,70 @@ import { Router } from "express"; +import { FormatGame } from "tachi-common"; +import { + GetRecentlyAchievedGoals, + GetRecentlyAchievedMilestones, + GetRecentlyInteractedGoals, + GetRecentlyInteractedMilestones, +} from "utils/db"; +import { GetGPT } from "utils/req-tachi-data"; import goalsRouter from "./goals/router"; import milestonesRouter from "./milestones/router"; const router: Router = Router({ mergeParams: true }); +/** + * Retrieve all of this game's recently achieved goals and milestones. + * + * @name GET /api/v1/games/:game/:playtype/targets/recently-achieved + */ +router.get("/recently-achieved", async (req, res) => { + const { game, playtype } = GetGPT(req); + + const [{ goals, goalSubs }, { milestones, milestoneSubs }] = await Promise.all([ + GetRecentlyAchievedGoals({ game, playtype }), + GetRecentlyAchievedMilestones({ game, playtype }), + ]); + + return res.status(200).json({ + success: true, + description: `Retrieved some recently achieved goals for ${FormatGame(game, playtype)}`, + body: { + goals, + goalSubs, + milestones, + milestoneSubs, + }, + }); +}); + +/** + * Retrieve all of this game's recently interacted-with goals and milestones. + * + * @name GET /api/v1/games/:game/:playtype/targets/recently-interacted + */ +router.get("/recently-interacted", async (req, res) => { + const { game, playtype } = GetGPT(req); + + const [{ goals, goalSubs }, { milestones, milestoneSubs }] = await Promise.all([ + GetRecentlyInteractedGoals({ game, playtype }), + GetRecentlyInteractedMilestones({ game, playtype }), + ]); + + return res.status(200).json({ + success: true, + description: `Retrieved some recently interacted-with goals for ${FormatGame( + game, + playtype + )}`, + body: { + goals, + goalSubs, + milestones, + milestoneSubs, + }, + }); +}); + router.use("/goals", goalsRouter); router.use("/milestones", milestonesRouter); diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/router.ts index 320785fb6..df1ef4c1f 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/router.ts @@ -1,5 +1,10 @@ import { Router } from "express"; -import db from "external/mongo/db"; +import { + GetRecentlyAchievedGoals, + GetRecentlyAchievedMilestones, + GetRecentlyInteractedGoals, + GetRecentlyInteractedMilestones, +} from "utils/db"; import { GetUGPT } from "utils/req-tachi-data"; import goalsRouter from "./goals/router"; import milestonesRouter from "./milestones/router"; @@ -16,45 +21,10 @@ router.get("/recently-achieved", async (req, res) => { const userID = user.id; - const recentlyAchievedGoalSubs = await db["goal-subs"].find( - { - game, - playtype, - achieved: true, - wasInstantlyAchieved: false, - userID, - }, - { - sort: { - timeAchieved: -1, - }, - limit: 50, - } - ); - - const recentlyAchievedMilestoneSubs = await db["milestone-subs"].find( - { - game, - playtype, - achieved: true, - wasInstantlyAchieved: false, - userID, - }, - { - sort: { - timeAchieved: -1, - }, - limit: 10, - } - ); - - const goals = await db.goals.find({ - goalID: { $in: recentlyAchievedGoalSubs.map((e) => e.goalID) }, - }); - - const milestones = await db.milestones.find({ - milestoneID: { $in: recentlyAchievedMilestoneSubs.map((e) => e.milestoneID) }, - }); + const [{ goals, goalSubs }, { milestones, milestoneSubs }] = await Promise.all([ + GetRecentlyAchievedGoals({ userID, game, playtype }), + GetRecentlyAchievedMilestones({ userID, game, playtype }), + ]); return res.status(200).json({ success: true, @@ -62,8 +32,8 @@ router.get("/recently-achieved", async (req, res) => { body: { goals, milestones, - goalSubs: recentlyAchievedGoalSubs, - milestoneSubs: recentlyAchievedMilestoneSubs, + goalSubs, + milestoneSubs, user, }, }); @@ -79,45 +49,10 @@ router.get("/recently-interacted", async (req, res) => { const userID = user.id; - const recentlyAchievedGoalSubs = await db["goal-subs"].find( - { - game, - playtype, - wasInstantlyAchieved: false, - userID, - lastInteraction: { $ne: null }, - }, - { - sort: { - lastInteraction: -1, - }, - limit: 50, - } - ); - - const recentlyAchievedMilestoneSubs = await db["milestone-subs"].find( - { - game, - playtype, - wasInstantlyAchieved: false, - userID, - lastInteraction: { $ne: null }, - }, - { - sort: { - lastInteraction: -1, - }, - limit: 10, - } - ); - - const goals = await db.goals.find({ - goalID: { $in: recentlyAchievedGoalSubs.map((e) => e.goalID) }, - }); - - const milestones = await db.milestones.find({ - milestoneID: { $in: recentlyAchievedMilestoneSubs.map((e) => e.milestoneID) }, - }); + const [{ goals, goalSubs }, { milestones, milestoneSubs }] = await Promise.all([ + GetRecentlyInteractedGoals({ userID, game, playtype }), + GetRecentlyInteractedMilestones({ userID, game, playtype }), + ]); return res.status(200).json({ success: true, @@ -125,8 +60,8 @@ router.get("/recently-interacted", async (req, res) => { body: { goals, milestones, - goalSubs: recentlyAchievedGoalSubs, - milestoneSubs: recentlyAchievedMilestoneSubs, + goalSubs, + milestoneSubs, user, }, }); diff --git a/server/src/utils/db.ts b/server/src/utils/db.ts index 38aefab15..8f915205e 100644 --- a/server/src/utils/db.ts +++ b/server/src/utils/db.ts @@ -1,6 +1,15 @@ import db from "external/mongo/db"; import CreateLogCtx from "lib/logger/logger"; -import { FormatChart, Game, integer, PBScoreDocument, ScoreDocument } from "tachi-common"; +import { FilterQuery } from "mongodb"; +import { + FormatChart, + Game, + GoalSubscriptionDocument, + integer, + MilestoneSubscriptionDocument, + PBScoreDocument, + ScoreDocument, +} from "tachi-common"; const logger = CreateLogCtx(__filename); export async function GetNextCounterValue(counterName: string): Promise { @@ -153,3 +162,133 @@ export async function HumaniseChartID(game: Game, chartID: string) { return FormatChart(game, song, chart); } + +/** + * Get recently achieved goals for this query. + * + * @param baseQuery - A base query, used to limit results on GPTs or UGPTs. + * @param limit - How many recently achieved goals to search for. + * @returns - The goals and their subs. + */ +export async function GetRecentlyAchievedGoals( + baseQuery: FilterQuery, + limit = 50 +) { + const query = Object.assign( + { + wasInstantlyAchieved: false, + achieved: true, + }, + baseQuery + ); + + const goalSubs = await db["goal-subs"].find(query, { + sort: { + timeAchieved: -1, + }, + limit, + }); + + const goals = await db.goals.find({ + goalID: { $in: goalSubs.map((e) => e.goalID) }, + }); + + return { goals, goalSubs }; +} + +/** + * Get recently interacted-with goals for this query. + * + * @param baseQuery - A base query, used to limit results on GPTs or UGPTs. + * @param limit - How many recently achieved goals to search for. + * @returns - The goals and their subs. + */ +export async function GetRecentlyInteractedGoals( + baseQuery: FilterQuery, + limit = 50 +) { + const query = Object.assign( + { + lastInteraction: { $ne: null }, + }, + baseQuery + ); + + const goalSubs = await db["goal-subs"].find(query, { + sort: { + lastInteraction: -1, + }, + limit, + }); + + const goals = await db.goals.find({ + goalID: { $in: goalSubs.map((e) => e.goalID) }, + }); + + return { goals, goalSubs }; +} + +/** + * Get recently achieved goals for this query. + * + * @param baseQuery - A base query, used to limit results on GPTs or UGPTs. + * @param limit - How many recently achieved goals to search for. + * @returns - The goals and their subs. + */ +export async function GetRecentlyAchievedMilestones( + baseQuery: FilterQuery, + limit = 50 +) { + const query = Object.assign( + { + wasInstantlyAchieved: false, + achieved: true, + }, + baseQuery + ); + + const milestoneSubs = await db["milestone-subs"].find(query, { + sort: { + timeAchieved: -1, + }, + limit, + }); + + const milestones = await db.goals.find({ + goalID: { $in: milestoneSubs.map((e) => e.milestoneID) }, + }); + + return { milestones, milestoneSubs }; +} + +/** + * Get recently interacted-with milestones for this query. + * + * @param baseQuery - A base query, used to limit results on GPTs or UGPTs. + * @param limit - How many recently achieved milestones to search for. + * @returns - The milestones and their subs. + */ +export async function GetRecentlyInteractedMilestones( + baseQuery: FilterQuery, + limit = 50 +) { + const query = Object.assign( + { + lastInteraction: { $ne: null }, + }, + baseQuery + ); + + const milestoneSubs = await db["milestone-subs"].find(query, { + sort: { + lastInteraction: -1, + }, + limit, + }); + + const milestones = await db.goals.find({ + milestoneID: { $in: milestoneSubs.map((e) => e.milestoneID) }, + }); + + return { milestones, milestoneSubs }; +} From 6a7520a92d3cdf23466d5f969912db1dcd3508ab Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Fri, 1 Apr 2022 16:58:34 +0100 Subject: [PATCH 037/106] Exclude wasInstantlyAchieved from recent-interactions --- .../api/v1/games/_game/_playtype/router.ts | 4 +- .../_game/_playtype/targets/router.test.ts | 85 +++++++++++++++++++ server/src/utils/db.ts | 2 + 3 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 server/src/server/router/api/v1/games/_game/_playtype/targets/router.test.ts diff --git a/server/src/server/router/api/v1/games/_game/_playtype/router.ts b/server/src/server/router/api/v1/games/_game/_playtype/router.ts index ca667e2c8..6ea578348 100644 --- a/server/src/server/router/api/v1/games/_game/_playtype/router.ts +++ b/server/src/server/router/api/v1/games/_game/_playtype/router.ts @@ -22,7 +22,7 @@ import { ValidatePlaytypeFromParam } from "./middleware"; import scoresRouter from "./scores/router"; import songIDRouter from "./songs/_songID/router"; import tablesRouter from "./tables/router"; -import goalsRouter from "./targets/goals/router"; +import targetsRouter from "./targets/router"; const router: Router = Router({ mergeParams: true }); @@ -294,6 +294,6 @@ router.use("/songs/:songID", songIDRouter); router.use("/folders", foldersRouter); router.use("/tables", tablesRouter); router.use("/scores", scoresRouter); -router.use("/goals", goalsRouter); +router.use("/targets", targetsRouter); export default router; diff --git a/server/src/server/router/api/v1/games/_game/_playtype/targets/router.test.ts b/server/src/server/router/api/v1/games/_game/_playtype/targets/router.test.ts new file mode 100644 index 000000000..d41171515 --- /dev/null +++ b/server/src/server/router/api/v1/games/_game/_playtype/targets/router.test.ts @@ -0,0 +1,85 @@ +import dm from "deepmerge"; +import db from "external/mongo/db"; +import { GoalSubscriptionDocument } from "tachi-common"; +import t from "tap"; +import mockApi from "test-utils/mock-api"; +import ResetDBState from "test-utils/resets"; +import { HC511UserGoal } from "test-utils/test-data"; + +t.test("GET /api/v1/games/:game/:playtype/targets/recently-achieved", (t) => { + t.beforeEach(ResetDBState); + + // mutate + function m(partial: Partial): GoalSubscriptionDocument { + return dm(HC511UserGoal, partial); + } + + t.test("Should return some recently achieved goals.", async (t) => { + await db["goal-subs"].insert([ + // not achieved + HC511UserGoal, + m({ goalID: "achieved", achieved: true, timeAchieved: 1000 }), + m({ goalID: "achieved_more_recently", achieved: true, timeAchieved: 2000 }), + m({ + goalID: "achieved_instantly", + achieved: true, + timeAchieved: 1000, + wasInstantlyAchieved: true, + }), + ]); + + const res = await mockApi.get("/api/v1/games/iidx/SP/targets/recently-achieved"); + + t.equal(res.statusCode, 200); + + // Shouldn't have the unachieved goal, shouldn't have the instantly achieved goal. + // should also have them in the right order. + t.strictSame( + res.body.body.goalSubs.map((e: GoalSubscriptionDocument) => e.goalID), + ["achieved_more_recently", "achieved"] + ); + + t.end(); + }); + + t.end(); +}); + +t.test("GET /api/v1/games/:game/:playtype/targets/recently-interacted", (t) => { + t.beforeEach(ResetDBState); + + // mutate + function m(partial: Partial): GoalSubscriptionDocument { + return dm(HC511UserGoal, partial); + } + + t.test("Should return some recently interacted goals.", async (t) => { + await db["goal-subs"].insert([ + // not achieved + HC511UserGoal, + m({ goalID: "interacted", achieved: false, lastInteraction: 1000 }), + m({ goalID: "interacted_more_recently", achieved: true, lastInteraction: 2000 }), + m({ + goalID: "achieved_instantly", + achieved: true, + lastInteraction: 1000, + wasInstantlyAchieved: true, + }), + ]); + + const res = await mockApi.get("/api/v1/games/iidx/SP/targets/recently-interacted"); + + t.equal(res.statusCode, 200); + + // Shouldn't have the uninteracted goal, shouldn't have the instantly achieved goal. + // should also have them in the right order. + t.strictSame( + res.body.body.goalSubs.map((e: GoalSubscriptionDocument) => e.goalID), + ["interacted_more_recently", "interacted"] + ); + + t.end(); + }); + + t.end(); +}); diff --git a/server/src/utils/db.ts b/server/src/utils/db.ts index 8f915205e..1daaf10cd 100644 --- a/server/src/utils/db.ts +++ b/server/src/utils/db.ts @@ -209,6 +209,7 @@ export async function GetRecentlyInteractedGoals( ) { const query = Object.assign( { + wasInstantlyAchieved: false, lastInteraction: { $ne: null }, }, baseQuery @@ -275,6 +276,7 @@ export async function GetRecentlyInteractedMilestones( const query = Object.assign( { lastInteraction: { $ne: null }, + wasInstantlyAchieved: false, }, baseQuery ); From b6332174f34fc961b80600353f3443a9631d9759 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Fri, 8 Apr 2022 17:15:18 +0100 Subject: [PATCH 038/106] Milestone/Goal Search Endpoints --- server/src/external/mongo/indexes.ts | 18 +++++++--- .../games/_game/_playtype/folders/router.ts | 2 +- .../_game/_playtype/targets/goals/router.ts | 30 ++++++++++++++++- .../_playtype/targets/milestones/router.ts | 33 +++++++++++++++++++ 4 files changed, 77 insertions(+), 6 deletions(-) diff --git a/server/src/external/mongo/indexes.ts b/server/src/external/mongo/indexes.ts index fd93cf072..0ecbea13b 100644 --- a/server/src/external/mongo/indexes.ts +++ b/server/src/external/mongo/indexes.ts @@ -41,13 +41,22 @@ const staticIndexes: Partial> = { "game-stats": [index({ userID: 1, game: 1, playtype: 1 }, UNIQUE)], "game-settings": [index({ userID: 1, game: 1, playtype: 1 }, UNIQUE)], "folder-chart-lookup": [index({ chartID: 1, folderID: 1 }, UNIQUE)], - goals: [index({ goalID: 1 }, UNIQUE)], + goals: [index({ goalID: 1 }, UNIQUE), index({ game: 1, playtype: 1 }), index({ name: "text" })], "goal-subs": [index({ goalID: 1, userID: 1 }, UNIQUE), index({ goalID: 1 })], - milestones: [index({ milestoneID: 1 }, UNIQUE), index({ group: 1, game: 1, playtype: 1 })], + milestones: [ + index({ milestoneID: 1 }, UNIQUE), + index({ game: 1, playtype: 1 }), + index({ name: "text" }), + ], "milestone-subs": [ index({ milestoneID: 1, userID: 1 }, UNIQUE), index({ userID: 1, game: 1, playtype: 1 }), ], + "milestone-sets": [ + index({ setID: 1 }, UNIQUE), + index({ game: 1, playtype: 1 }), + index({ milestones: 1 }), + ], imports: [index({ importID: 1 }, UNIQUE)], "import-timings": [ index({ importID: 1 }, UNIQUE), @@ -66,7 +75,6 @@ const staticIndexes: Partial> = { index({ title: "text", searchTerms: "text" }), ], "kai-auth-tokens": [index({ userID: 1, service: 1 }, UNIQUE)], - "bms-course-lookup": [index({ md5sums: 1 }, UNIQUE)], "api-tokens": [index({ token: 1 }, UNIQUE), index({ userID: 1 })], tables: [index({ tableID: 1, game: 1, playtype: 1 }, UNIQUE)], @@ -190,7 +198,9 @@ export function SetIndexesIfNoneSet() { // This means that there are likely to be no indexes // configured in the database. if (Object.keys(r).length === 1) { - logger.info(`First-time Mongo startup detected. Running SetIndexes.`); + logger.info( + `No indexes on users, First-time Tachi-Server startup assumed. Running SetIndexes.` + ); SetIndexesWithDB(monkDB, true); } }) diff --git a/server/src/server/router/api/v1/games/_game/_playtype/folders/router.ts b/server/src/server/router/api/v1/games/_game/_playtype/folders/router.ts index 25f92c99c..a7e11e8ed 100644 --- a/server/src/server/router/api/v1/games/_game/_playtype/folders/router.ts +++ b/server/src/server/router/api/v1/games/_game/_playtype/folders/router.ts @@ -10,7 +10,7 @@ import { GetFolderFromParam } from "./middleware"; const router: Router = Router({ mergeParams: true }); /** - * Search the folders for this game. + * Search the folders for this GPT. * * @param search - The query to search for. * @param inactive - Also show inactive folders, such as those on old versions. diff --git a/server/src/server/router/api/v1/games/_game/_playtype/targets/goals/router.ts b/server/src/server/router/api/v1/games/_game/_playtype/targets/goals/router.ts index 9e1990872..df41ccbe4 100644 --- a/server/src/server/router/api/v1/games/_game/_playtype/targets/goals/router.ts +++ b/server/src/server/router/api/v1/games/_game/_playtype/targets/goals/router.ts @@ -2,9 +2,11 @@ import { RequestHandler, Router } from "express"; import db from "external/mongo/db"; import { SYMBOL_TachiData } from "lib/constants/tachi"; import CreateLogCtx from "lib/logger/logger"; +import { SearchCollection } from "lib/search/search"; import { EvaluatedGoalReturn, EvaluateGoalForUser } from "lib/targets/goals"; import prValidate from "server/middleware/prudence-validate"; import { FormatGame } from "tachi-common"; +import { IsString } from "utils/misc"; import { AssignToReqTachiData, GetGPT } from "utils/req-tachi-data"; import { GetUsersWithIDs, ResolveUser } from "utils/user"; @@ -13,7 +15,33 @@ const logger = CreateLogCtx(__filename); const router: Router = Router({ mergeParams: true }); /** - * Retrieve goals that have been recently achieved for this game. + * Search goals for this GPT. + * + * @param search - The query to search for. + * + * @name GET /api/v1/games/:game/:playtype/targets/goals + */ +router.get("/", async (req, res) => { + const { game, playtype } = GetGPT(req); + + if (!IsString(req.query.search)) { + return res.status(400).json({ + success: false, + description: `Invalid value for search.`, + }); + } + + const goals = await SearchCollection(db.goals, req.query.search, { game, playtype }, 50); + + return res.status(200).json({ + success: true, + description: `Returned ${goals.length} goals.`, + body: goals, + }); +}); + +/** + * Retrieve goals that have been recently achieved for this GPT. * * @name GET /api/v1/games/:game/:playtype/targets/goals/recently-achieved */ diff --git a/server/src/server/router/api/v1/games/_game/_playtype/targets/milestones/router.ts b/server/src/server/router/api/v1/games/_game/_playtype/targets/milestones/router.ts index bcc395ec2..14f717bf2 100644 --- a/server/src/server/router/api/v1/games/_game/_playtype/targets/milestones/router.ts +++ b/server/src/server/router/api/v1/games/_game/_playtype/targets/milestones/router.ts @@ -1,9 +1,11 @@ import { RequestHandler, Router } from "express"; import db from "external/mongo/db"; import { SYMBOL_TachiData } from "lib/constants/tachi"; +import { SearchCollection } from "lib/search/search"; import { EvaluateMilestoneProgress, GetGoalsInMilestone } from "lib/targets/milestones"; import prValidate from "server/middleware/prudence-validate"; import { FormatGame } from "tachi-common"; +import { IsString } from "utils/misc"; import { AssignToReqTachiData, GetGPT } from "utils/req-tachi-data"; import { GetUsersWithIDs, ResolveUser } from "utils/user"; @@ -31,6 +33,37 @@ const ResolveMilestoneID: RequestHandler = async (req, res, next) => { return next(); }; +/** + * Search milestones for this GPT. + * + * @param search - The query to search for. + * + * @name GET /api/v1/games/:game/:playtype/targets/milestones + */ +router.get("/", async (req, res) => { + const { game, playtype } = GetGPT(req); + + if (!IsString(req.query.search)) { + return res.status(400).json({ + success: false, + description: `Invalid value for search.`, + }); + } + + const milestones = await SearchCollection( + db.milestones, + req.query.search, + { game, playtype }, + 50 + ); + + return res.status(200).json({ + success: true, + description: `Returned ${milestones.length} milestones.`, + body: milestones, + }); +}); + /** * Retrieve information about this milestone and who is subscribed to it. * From 515db2e96ee4e92e10ccfeca5c480aee415bd909 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Fri, 8 Apr 2022 18:32:12 +0100 Subject: [PATCH 039/106] Allow instantly-achieving milestones on subscription time. --- .../_playtype/targets/milestones/router.ts | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/milestones/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/milestones/router.ts index e8e1fb120..ca2de9425 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/milestones/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/milestones/router.ts @@ -159,14 +159,17 @@ router.put( }); } - const subResult = await SubscribeToMilestone(user.id, milestone); + const subResult = await SubscribeToMilestone(user.id, milestone, false); - if (subResult === SubscribeFailReasons.ALREADY_ACHIEVED) { - return res.status(400).json({ - success: false, - description: `You cannot assign a milestone that would be immediately achieved.`, - }); - } + // Users should be able to subscribe to milestones EVEN IF they would instantly + // achieve them. + + // if (subResult === SubscribeFailReasons.ALREADY_ACHIEVED) { + // return res.status(400).json({ + // success: false, + // description: `You cannot assign a milestone that would be immediately achieved.`, + // }); + // } if (subResult === SubscribeFailReasons.ALREADY_SUBSCRIBED) { return res.status(409).json({ From 809b3b462ecd32afe25b171b8d2d3fc654e67151 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Fri, 8 Apr 2022 18:32:24 +0100 Subject: [PATCH 040/106] Ignore achieved targets on recently-interacted-with --- server/src/utils/db.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/src/utils/db.ts b/server/src/utils/db.ts index 1daaf10cd..98570d03e 100644 --- a/server/src/utils/db.ts +++ b/server/src/utils/db.ts @@ -210,6 +210,7 @@ export async function GetRecentlyInteractedGoals( const query = Object.assign( { wasInstantlyAchieved: false, + achieved: false, lastInteraction: { $ne: null }, }, baseQuery @@ -276,6 +277,7 @@ export async function GetRecentlyInteractedMilestones( const query = Object.assign( { lastInteraction: { $ne: null }, + achieved: false, wasInstantlyAchieved: false, }, baseQuery From 53b21c48fd0585cd8736f34b21e18bd76e201d0c Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Fri, 8 Apr 2022 18:33:23 +0100 Subject: [PATCH 041/106] Fix typos in GPT/Target descriptions --- .../router/api/v1/games/_game/_playtype/targets/router.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/server/router/api/v1/games/_game/_playtype/targets/router.ts b/server/src/server/router/api/v1/games/_game/_playtype/targets/router.ts index f1b8a0471..5bfecb604 100644 --- a/server/src/server/router/api/v1/games/_game/_playtype/targets/router.ts +++ b/server/src/server/router/api/v1/games/_game/_playtype/targets/router.ts @@ -27,7 +27,7 @@ router.get("/recently-achieved", async (req, res) => { return res.status(200).json({ success: true, - description: `Retrieved some recently achieved goals for ${FormatGame(game, playtype)}`, + description: `Retrieved some recently achieved targets for ${FormatGame(game, playtype)}`, body: { goals, goalSubs, @@ -52,7 +52,7 @@ router.get("/recently-interacted", async (req, res) => { return res.status(200).json({ success: true, - description: `Retrieved some recently interacted-with goals for ${FormatGame( + description: `Retrieved some recently interacted-with targets for ${FormatGame( game, playtype )}`, From 871492cb5a6ac9bb582c370d4af221c9e67c4c1d Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Sat, 9 Apr 2022 00:33:49 +0100 Subject: [PATCH 042/106] Up default GPT/highlighted limit to 100 --- .../server/router/api/v1/games/_game/_playtype/scores/router.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/server/router/api/v1/games/_game/_playtype/scores/router.ts b/server/src/server/router/api/v1/games/_game/_playtype/scores/router.ts index 96fd783d3..54f3a9925 100644 --- a/server/src/server/router/api/v1/games/_game/_playtype/scores/router.ts +++ b/server/src/server/router/api/v1/games/_game/_playtype/scores/router.ts @@ -23,7 +23,7 @@ router.get( async (req, res) => { const { game, playtype } = GetGPT(req); - const limit = req.query.limit ? Number(req.query.limit) : 10; + const limit = req.query.limit ? Number(req.query.limit) : 100; const scores = await db.scores.find( { From e67e88d0b8d9eeeb259272a630b08c48144dd275 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Sat, 9 Apr 2022 00:34:04 +0100 Subject: [PATCH 043/106] Add a popularity endpoint for GPT/goals --- .../_game/_playtype/targets/goals/router.ts | 22 +++------ server/src/utils/db.ts | 47 +++++++++++++++++-- 2 files changed, 49 insertions(+), 20 deletions(-) diff --git a/server/src/server/router/api/v1/games/_game/_playtype/targets/goals/router.ts b/server/src/server/router/api/v1/games/_game/_playtype/targets/goals/router.ts index df41ccbe4..013acdef4 100644 --- a/server/src/server/router/api/v1/games/_game/_playtype/targets/goals/router.ts +++ b/server/src/server/router/api/v1/games/_game/_playtype/targets/goals/router.ts @@ -2,11 +2,10 @@ import { RequestHandler, Router } from "express"; import db from "external/mongo/db"; import { SYMBOL_TachiData } from "lib/constants/tachi"; import CreateLogCtx from "lib/logger/logger"; -import { SearchCollection } from "lib/search/search"; import { EvaluatedGoalReturn, EvaluateGoalForUser } from "lib/targets/goals"; import prValidate from "server/middleware/prudence-validate"; import { FormatGame } from "tachi-common"; -import { IsString } from "utils/misc"; +import { GetMostSubscribedGoals } from "utils/db"; import { AssignToReqTachiData, GetGPT } from "utils/req-tachi-data"; import { GetUsersWithIDs, ResolveUser } from "utils/user"; @@ -15,23 +14,14 @@ const logger = CreateLogCtx(__filename); const router: Router = Router({ mergeParams: true }); /** - * Search goals for this GPT. + * Get the most popular goals for this GPT. * - * @param search - The query to search for. - * - * @name GET /api/v1/games/:game/:playtype/targets/goals + * @name GET /api/v1/games/:game/:playtype/targets/goals/popular */ -router.get("/", async (req, res) => { +router.get("/popular", async (req, res) => { const { game, playtype } = GetGPT(req); - if (!IsString(req.query.search)) { - return res.status(400).json({ - success: false, - description: `Invalid value for search.`, - }); - } - - const goals = await SearchCollection(db.goals, req.query.search, { game, playtype }, 50); + const goals = await GetMostSubscribedGoals({ game, playtype }); return res.status(200).json({ success: true, @@ -59,7 +49,7 @@ router.get("/recently-achieved", async (req, res) => { sort: { timeAchieved: -1, }, - limit: 10, + limit: 100, } ); diff --git a/server/src/utils/db.ts b/server/src/utils/db.ts index 98570d03e..99905ec58 100644 --- a/server/src/utils/db.ts +++ b/server/src/utils/db.ts @@ -4,6 +4,7 @@ import { FilterQuery } from "mongodb"; import { FormatChart, Game, + GoalDocument, GoalSubscriptionDocument, integer, MilestoneSubscriptionDocument, @@ -172,7 +173,7 @@ export async function HumaniseChartID(game: Game, chartID: string) { */ export async function GetRecentlyAchievedGoals( baseQuery: FilterQuery, - limit = 50 + limit = 100 ) { const query = Object.assign( { @@ -205,7 +206,7 @@ export async function GetRecentlyAchievedGoals( */ export async function GetRecentlyInteractedGoals( baseQuery: FilterQuery, - limit = 50 + limit = 100 ) { const query = Object.assign( { @@ -239,7 +240,7 @@ export async function GetRecentlyInteractedGoals( */ export async function GetRecentlyAchievedMilestones( baseQuery: FilterQuery, - limit = 50 + limit = 100 ) { const query = Object.assign( { @@ -272,7 +273,7 @@ export async function GetRecentlyAchievedMilestones( */ export async function GetRecentlyInteractedMilestones( baseQuery: FilterQuery, - limit = 50 + limit = 100 ) { const query = Object.assign( { @@ -296,3 +297,41 @@ export async function GetRecentlyInteractedMilestones( return { milestones, milestoneSubs }; } + +export async function GetMostSubscribedGoals( + query: FilterQuery, + limit = 100 +): Promise<(GoalDocument & { __subscriptions: integer })[]> { + const mostSubscribedGoals = (await db["goal-subs"].aggregate([ + { + $match: query, + }, + { + $group: { + _id: "$goalID", + subscriptions: { $sum: 1 }, + }, + }, + { + $sort: { + subscriptions: -1, + }, + }, + { + $limit: limit, + }, + { + $lookup: { + from: "goals", + localField: "_id", + foreignField: "goalID", + as: "goal", + }, + }, + ])) as { goal: GoalDocument; subscriptions: integer }[]; + + return mostSubscribedGoals.map((e) => ({ + __subscriptions: e.subscriptions, + ...e.goal, + })); +} From d93d04619b01a0e529efef0e605a6d859e471399 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Sat, 9 Apr 2022 00:39:08 +0100 Subject: [PATCH 044/106] Remove redundant recently-achieved endpoint from GPT/goals --- .../_game/_playtype/targets/goals/router.ts | 40 ------------------- 1 file changed, 40 deletions(-) diff --git a/server/src/server/router/api/v1/games/_game/_playtype/targets/goals/router.ts b/server/src/server/router/api/v1/games/_game/_playtype/targets/goals/router.ts index 013acdef4..0633149b9 100644 --- a/server/src/server/router/api/v1/games/_game/_playtype/targets/goals/router.ts +++ b/server/src/server/router/api/v1/games/_game/_playtype/targets/goals/router.ts @@ -30,46 +30,6 @@ router.get("/popular", async (req, res) => { }); }); -/** - * Retrieve goals that have been recently achieved for this GPT. - * - * @name GET /api/v1/games/:game/:playtype/targets/goals/recently-achieved - */ -router.get("/recently-achieved", async (req, res) => { - const { game, playtype } = GetGPT(req); - - const recentlyAchievedUserGoals = await db["goal-subs"].find( - { - game, - playtype, - achieved: true, - wasInstantlyAchieved: false, - }, - { - sort: { - timeAchieved: -1, - }, - limit: 100, - } - ); - - const goals = await db.goals.find({ - goalID: { $in: recentlyAchievedUserGoals.map((e) => e.goalID) }, - }); - - const users = await GetUsersWithIDs(recentlyAchievedUserGoals.map((e) => e.userID)); - - return res.status(200).json({ - success: true, - description: `Retrieved ${recentlyAchievedUserGoals.length} recently achieved goals.`, - body: { - goalSubs: recentlyAchievedUserGoals, - goals, - users, - }, - }); -}); - const ResolveGoalID: RequestHandler = async (req, res, next) => { const { game, playtype } = GetGPT(req); const goalID = req.params.goalID; From f08986a5817dab6fb34aea5f6f438bd3a94ea238 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Mon, 11 Apr 2022 21:36:35 +0100 Subject: [PATCH 045/106] Add GPT/targets/milestones/popular --- .../v1/games/_game/_playtype/scores/router.ts | 4 +- .../_playtype/targets/milestones/router.ts | 13 +++++++ server/src/utils/db.ts | 39 +++++++++++++++++++ 3 files changed, 54 insertions(+), 2 deletions(-) diff --git a/server/src/server/router/api/v1/games/_game/_playtype/scores/router.ts b/server/src/server/router/api/v1/games/_game/_playtype/scores/router.ts index 54f3a9925..7852f62fe 100644 --- a/server/src/server/router/api/v1/games/_game/_playtype/scores/router.ts +++ b/server/src/server/router/api/v1/games/_game/_playtype/scores/router.ts @@ -11,14 +11,14 @@ const router: Router = Router({ mergeParams: true }); /** * Return the most recent highlighted scores for this game. * - * @param limit - Return up to this amount. Caps at 100, defaults to 10. + * @param limit - Return up to this amount. Caps at 100, defaults to 100. * * @name GET /api/v1/games/:game/:playtype/scores/highlighted */ router.get( "/highlighted", prValidate({ - limit: p.optional((self) => p.isBoundedInteger(1, 500)(Number(self))), + limit: p.optional((self) => p.isBoundedInteger(1, 100)(Number(self))), }), async (req, res) => { const { game, playtype } = GetGPT(req); diff --git a/server/src/server/router/api/v1/games/_game/_playtype/targets/milestones/router.ts b/server/src/server/router/api/v1/games/_game/_playtype/targets/milestones/router.ts index 14f717bf2..4ba20ac3a 100644 --- a/server/src/server/router/api/v1/games/_game/_playtype/targets/milestones/router.ts +++ b/server/src/server/router/api/v1/games/_game/_playtype/targets/milestones/router.ts @@ -5,6 +5,7 @@ import { SearchCollection } from "lib/search/search"; import { EvaluateMilestoneProgress, GetGoalsInMilestone } from "lib/targets/milestones"; import prValidate from "server/middleware/prudence-validate"; import { FormatGame } from "tachi-common"; +import { GetMostSubscribedGoals, GetMostSubscribedMilestones } from "utils/db"; import { IsString } from "utils/misc"; import { AssignToReqTachiData, GetGPT } from "utils/req-tachi-data"; import { GetUsersWithIDs, ResolveUser } from "utils/user"; @@ -64,6 +65,18 @@ router.get("/", async (req, res) => { }); }); +router.get("/popular", async (req, res) => { + const { game, playtype } = GetGPT(req); + + const milestones = await GetMostSubscribedMilestones({ game, playtype }); + + return res.status(200).json({ + success: true, + description: `Returned ${milestones.length} popular milestones.`, + body: milestones, + }); +}); + /** * Retrieve information about this milestone and who is subscribed to it. * diff --git a/server/src/utils/db.ts b/server/src/utils/db.ts index 99905ec58..22f3672e9 100644 --- a/server/src/utils/db.ts +++ b/server/src/utils/db.ts @@ -7,6 +7,7 @@ import { GoalDocument, GoalSubscriptionDocument, integer, + MilestoneDocument, MilestoneSubscriptionDocument, PBScoreDocument, ScoreDocument, @@ -335,3 +336,41 @@ export async function GetMostSubscribedGoals( ...e.goal, })); } + +export async function GetMostSubscribedMilestones( + query: FilterQuery, + limit = 100 +): Promise<(MilestoneDocument & { __subscriptions: integer })[]> { + const mostSubscribedMilesones = (await db["milestone-subs"].aggregate([ + { + $match: query, + }, + { + $group: { + _id: "$milestoneID", + subscriptions: { $sum: 1 }, + }, + }, + { + $sort: { + subscriptions: -1, + }, + }, + { + $limit: limit, + }, + { + $lookup: { + from: "milestones", + localField: "_id", + foreignField: "milestoneID", + as: "milestone", + }, + }, + ])) as { milestone: MilestoneDocument; subscriptions: integer }[]; + + return mostSubscribedMilesones.map((e) => ({ + __subscriptions: e.subscriptions, + ...e.milestone, + })); +} From fb53afb43e3858ccef50decefdfc5194ae8a5693 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Mon, 11 Apr 2022 22:01:26 +0100 Subject: [PATCH 046/106] Remove some unused dependencies --- .../v1/games/_game/_playtype/targets/milestones/router.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/server/src/server/router/api/v1/games/_game/_playtype/targets/milestones/router.ts b/server/src/server/router/api/v1/games/_game/_playtype/targets/milestones/router.ts index 4ba20ac3a..e36abc2d1 100644 --- a/server/src/server/router/api/v1/games/_game/_playtype/targets/milestones/router.ts +++ b/server/src/server/router/api/v1/games/_game/_playtype/targets/milestones/router.ts @@ -5,7 +5,7 @@ import { SearchCollection } from "lib/search/search"; import { EvaluateMilestoneProgress, GetGoalsInMilestone } from "lib/targets/milestones"; import prValidate from "server/middleware/prudence-validate"; import { FormatGame } from "tachi-common"; -import { GetMostSubscribedGoals, GetMostSubscribedMilestones } from "utils/db"; +import { GetMostSubscribedMilestones } from "utils/db"; import { IsString } from "utils/misc"; import { AssignToReqTachiData, GetGPT } from "utils/req-tachi-data"; import { GetUsersWithIDs, ResolveUser } from "utils/user"; @@ -65,6 +65,11 @@ router.get("/", async (req, res) => { }); }); +/** + * Find the most subscribed-to milestones for this GPT. + * + * @name GET /api/v1/games/:game/:playtype/targets/milestones/popular + */ router.get("/popular", async (req, res) => { const { game, playtype } = GetGPT(req); From e8a688fd8f02923f0ea975ef941c90cb6c74f2ab Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Mon, 11 Apr 2022 22:31:41 +0100 Subject: [PATCH 047/106] Fix typo in milestoneID/evaluate-for --- .../api/v1/games/_game/_playtype/targets/milestones/router.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/server/router/api/v1/games/_game/_playtype/targets/milestones/router.ts b/server/src/server/router/api/v1/games/_game/_playtype/targets/milestones/router.ts index e36abc2d1..e11beab39 100644 --- a/server/src/server/router/api/v1/games/_game/_playtype/targets/milestones/router.ts +++ b/server/src/server/router/api/v1/games/_game/_playtype/targets/milestones/router.ts @@ -115,7 +115,7 @@ router.get("/:milestoneID", ResolveMilestoneID, async (req, res) => { * * @param userID - The userID to evaluate this goal against. Must be a player of this GPT. * - * @name GET /api/v1/games/:game/:playtype/targets/milestones/:milestone-id/evaluate-for + * @name GET /api/v1/games/:game/:playtype/targets/milestones/:milestoneID/evaluate-for */ router.get( "/:milestoneID/evaluate-for", From bb6f054777c303bae665fcc9e7a7e7ca466fc994 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Mon, 11 Apr 2022 22:43:02 +0100 Subject: [PATCH 048/106] Add parentMilestones to GPT/goals/goalID --- .../api/v1/games/_game/_playtype/targets/goals/router.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/server/src/server/router/api/v1/games/_game/_playtype/targets/goals/router.ts b/server/src/server/router/api/v1/games/_game/_playtype/targets/goals/router.ts index 0633149b9..ca6cf1753 100644 --- a/server/src/server/router/api/v1/games/_game/_playtype/targets/goals/router.ts +++ b/server/src/server/router/api/v1/games/_game/_playtype/targets/goals/router.ts @@ -66,6 +66,10 @@ router.get("/:goalID", ResolveGoalID, async (req, res) => { const users = await GetUsersWithIDs(goalSubs.map((e) => e.userID)); + const parentMilestones = await db.milestones.find({ + "milestoneData.goalID": goal.goalID, + }); + return res.status(200).json({ success: true, description: `Retrieved information about ${goal.name}.`, @@ -73,6 +77,7 @@ router.get("/:goalID", ResolveGoalID, async (req, res) => { goal, goalSubs, users, + parentMilestones, }, }); }); From 40ebb5971c0c8d2977a55ed2e031d3f906267521 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Tue, 12 Apr 2022 02:09:26 +0100 Subject: [PATCH 049/106] Add milestone-sets endpoints --- server/package.json | 2 +- server/pnpm-lock.yaml | 8 +- .../targets/milestone-sets/router.ts | 84 +++++++++++++++++++ .../games/_game/_playtype/targets/router.ts | 6 +- 4 files changed, 93 insertions(+), 7 deletions(-) create mode 100644 server/src/server/router/api/v1/games/_game/_playtype/targets/milestone-sets/router.ts diff --git a/server/package.json b/server/package.json index fbde78867..63acc6978 100644 --- a/server/package.json +++ b/server/package.json @@ -84,7 +84,7 @@ "rimraf": "3.0.2", "safe-json-stringify": "1.2.0", "seq-logging": "1.1.1", - "tachi-common": "0.7.9", + "tachi-common": "0.7.13", "ts-node": "10.4.0", "tsconfig-paths": "3.12.0", "typescript": "4.5.5", diff --git a/server/pnpm-lock.yaml b/server/pnpm-lock.yaml index 61cdaa85f..46faf9cec 100644 --- a/server/pnpm-lock.yaml +++ b/server/pnpm-lock.yaml @@ -65,7 +65,7 @@ specifiers: safe-json-stringify: 1.2.0 seq-logging: 1.1.1 supertest: 6.2.2 - tachi-common: 0.7.9 + tachi-common: 0.7.13 tap: 15.1.6 ts-node: 10.4.0 tsconfig-paths: 3.12.0 @@ -112,7 +112,7 @@ dependencies: rimraf: 3.0.2 safe-json-stringify: 1.2.0 seq-logging: 1.1.1 - tachi-common: 0.7.9 + tachi-common: 0.7.13 ts-node: 10.4.0_2615db9039ce432b4abf2fc39ef336ec tsconfig-paths: 3.12.0 typescript: 4.5.5 @@ -5363,8 +5363,8 @@ packages: engines: {node: '>= 0.4'} dev: true - /tachi-common/0.7.9: - resolution: {integrity: sha512-q6JMyACbHb7rDSGPv+cZZHZP1XxyQIvuhnQU5jRKYQfn4LkMm6ticHUN7gjSNAVHnwR05VQnulrkc+SOcnn3nQ==} + /tachi-common/0.7.13: + resolution: {integrity: sha512-r0pB7FO6QZ7j16jR0YgMzojbGxo03uS+mDGrD59dpL0tbN2V7fR6vLx+mmWELFBqrDIjcOJu2AOTkz7fPYL3fA==} dependencies: '@types/mongodb': 3.6.18 dev: false diff --git a/server/src/server/router/api/v1/games/_game/_playtype/targets/milestone-sets/router.ts b/server/src/server/router/api/v1/games/_game/_playtype/targets/milestone-sets/router.ts new file mode 100644 index 000000000..cbf741e41 --- /dev/null +++ b/server/src/server/router/api/v1/games/_game/_playtype/targets/milestone-sets/router.ts @@ -0,0 +1,84 @@ +import { RequestHandler, Router } from "express"; +import db from "external/mongo/db"; +import { SYMBOL_TachiData } from "lib/constants/tachi"; +import { SearchCollection } from "lib/search/search"; +import { GetChildMilestones } from "utils/db"; +import { IsString } from "utils/misc"; +import { AssignToReqTachiData, GetGPT } from "utils/req-tachi-data"; + +const router: Router = Router({ mergeParams: true }); + +const ResolveMilestoneSetID: RequestHandler = async (req, res, next) => { + const { game, playtype } = GetGPT(req); + const setID = req.params.setID; + + const set = await db["milestone-sets"].findOne({ + setID, + game, + playtype, + }); + + if (!set) { + return res.status(404).json({ + success: false, + description: `A milestone set with ID ${setID} doesn't exist.`, + }); + } + + AssignToReqTachiData(req, { milestoneSetDoc: set }); + + return next(); +}; + +/** + * Search milestone sets. + * + * @param search - The milestone set to search for. + * + * @name GET /api/v1/games/:game/:playtype/targets/milestone-sets + */ +router.get("/", async (req, res) => { + const { game, playtype } = GetGPT(req); + + if (!IsString(req.query.search)) { + return res.status(400).json({ + success: false, + description: `Invalid value for search.`, + }); + } + + const milestoneSets = await SearchCollection( + db["milestone-sets"], + req.query.search, + { game, playtype }, + 50 + ); + + return res.status(200).json({ + success: true, + description: `Returned ${milestoneSets.length} milestone sets.`, + body: milestoneSets, + }); +}); + +/** + * Retrieve a specific milestone set. + * + * @name GET /api/v1/games/:game/:playtype/targets/milestone-sets/:setID + */ +router.get("/:setID", ResolveMilestoneSetID, async (req, res) => { + const milestoneSet = req[SYMBOL_TachiData]!.milestoneSetDoc!; + + const milestones = await GetChildMilestones(milestoneSet); + + return res.status(200).json({ + success: true, + description: `Retrieved milestone set '${milestoneSet.name}'.`, + body: { + milestones, + milestoneSet, + }, + }); +}); + +export default router; diff --git a/server/src/server/router/api/v1/games/_game/_playtype/targets/router.ts b/server/src/server/router/api/v1/games/_game/_playtype/targets/router.ts index 5bfecb604..0763f162d 100644 --- a/server/src/server/router/api/v1/games/_game/_playtype/targets/router.ts +++ b/server/src/server/router/api/v1/games/_game/_playtype/targets/router.ts @@ -9,6 +9,7 @@ import { import { GetGPT } from "utils/req-tachi-data"; import goalsRouter from "./goals/router"; import milestonesRouter from "./milestones/router"; +import milestoneSetsRouter from "./milestone-sets/router"; const router: Router = Router({ mergeParams: true }); @@ -40,9 +41,9 @@ router.get("/recently-achieved", async (req, res) => { /** * Retrieve all of this game's recently interacted-with goals and milestones. * - * @name GET /api/v1/games/:game/:playtype/targets/recently-interacted + * @name GET /api/v1/games/:game/:playtype/targets/recently-raised */ -router.get("/recently-interacted", async (req, res) => { +router.get("/recently-raised", async (req, res) => { const { game, playtype } = GetGPT(req); const [{ goals, goalSubs }, { milestones, milestoneSubs }] = await Promise.all([ @@ -67,5 +68,6 @@ router.get("/recently-interacted", async (req, res) => { router.use("/goals", goalsRouter); router.use("/milestones", milestonesRouter); +router.use("/milestone-sets", milestoneSetsRouter); export default router; From b31a931f89d5c89b553daba25089ee6e9ebfe108 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Tue, 12 Apr 2022 02:09:46 +0100 Subject: [PATCH 050/106] Rename UGPT targets recently-interacted to recently-raised --- .../v1/users/_userID/games/_game/_playtype/targets/router.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/router.ts index df1ef4c1f..2189b31ba 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/router.ts @@ -41,10 +41,11 @@ router.get("/recently-achieved", async (req, res) => { /** * Returns a user's recently interacted with (raised, etc.) goals and milestones. + * Note that this does not include recently achieved. * - * @name GET /api/v1/users/:userID/games/:game/:playtype/targets/recently-interacted + * @name GET /api/v1/users/:userID/games/:game/:playtype/targets/recently-raised */ -router.get("/recently-interacted", async (req, res) => { +router.get("/recently-raised", async (req, res) => { const { game, playtype, user } = GetUGPT(req); const userID = user.id; From baf81a873016a4262a4df52c65882d21b58ec637 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Tue, 12 Apr 2022 02:10:02 +0100 Subject: [PATCH 051/106] Add milestone sets to the database and general utils --- server/src/utils/db.ts | 17 +++++++++++++++++ server/src/utils/types.ts | 2 ++ 2 files changed, 19 insertions(+) diff --git a/server/src/utils/db.ts b/server/src/utils/db.ts index 22f3672e9..f71a5d5a9 100644 --- a/server/src/utils/db.ts +++ b/server/src/utils/db.ts @@ -8,6 +8,7 @@ import { GoalSubscriptionDocument, integer, MilestoneDocument, + MilestoneSetDocument, MilestoneSubscriptionDocument, PBScoreDocument, ScoreDocument, @@ -374,3 +375,19 @@ export async function GetMostSubscribedMilestones( ...e.milestone, })); } + +export async function GetChildMilestones(milestoneSet: MilestoneSetDocument) { + const milestones = await db.milestones.find({ + milestoneID: { $in: milestoneSet.milestones }, + }); + + if (milestones.length !== milestoneSet.milestones.length) { + logger.error( + `Expected to find ${milestoneSet.milestones.length} milestones in the database, but only found ${milestones.length}.`, + { milestoneSet } + ); + throw new Error(`Failed to retrieve milestone sets' children.`); + } + + return milestones; +} diff --git a/server/src/utils/types.ts b/server/src/utils/types.ts index 2d7c3fd15..ab8a5869c 100644 --- a/server/src/utils/types.ts +++ b/server/src/utils/types.ts @@ -15,6 +15,7 @@ import { MilestoneSubscriptionDocument, GoalSubscriptionDocument, MilestoneDocument, + MilestoneSetDocument, } from "tachi-common"; declare module "express-session" { @@ -70,6 +71,7 @@ export interface TachiRequestData { milestoneDoc?: MilestoneDocument; goalSubDoc?: GoalSubscriptionDocument; milestoneSubDoc?: MilestoneSubscriptionDocument; + milestoneSetDoc?: MilestoneSetDocument; apiClientDoc: Omit; } From ba0235d5a0cb452852967461c26e20ca268604ea Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Tue, 12 Apr 2022 02:10:18 +0100 Subject: [PATCH 052/106] Return parent milestone sets on GPT milestoneID --- .../v1/games/_game/_playtype/targets/milestones/router.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/server/src/server/router/api/v1/games/_game/_playtype/targets/milestones/router.ts b/server/src/server/router/api/v1/games/_game/_playtype/targets/milestones/router.ts index e11beab39..3f4fce551 100644 --- a/server/src/server/router/api/v1/games/_game/_playtype/targets/milestones/router.ts +++ b/server/src/server/router/api/v1/games/_game/_playtype/targets/milestones/router.ts @@ -98,6 +98,10 @@ router.get("/:milestoneID", ResolveMilestoneID, async (req, res) => { const goals = await GetGoalsInMilestone(milestone); + const parentMilestoneSets = await db["milestone-sets"].find({ + milestones: milestone.milestoneID, + }); + return res.status(200).json({ success: true, description: `Retrieved information about ${milestone.name}.`, @@ -106,6 +110,7 @@ router.get("/:milestoneID", ResolveMilestoneID, async (req, res) => { milestoneSubs, users, goals, + parentMilestoneSets, }, }); }); From a649811ebc859aa17486cef30cd1893935fed884 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Tue, 12 Apr 2022 02:10:28 +0100 Subject: [PATCH 053/106] Test recently-raised instead of recently-interacted --- .../v1/games/_game/_playtype/targets/router.test.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/server/src/server/router/api/v1/games/_game/_playtype/targets/router.test.ts b/server/src/server/router/api/v1/games/_game/_playtype/targets/router.test.ts index d41171515..9585382a8 100644 --- a/server/src/server/router/api/v1/games/_game/_playtype/targets/router.test.ts +++ b/server/src/server/router/api/v1/games/_game/_playtype/targets/router.test.ts @@ -45,7 +45,7 @@ t.test("GET /api/v1/games/:game/:playtype/targets/recently-achieved", (t) => { t.end(); }); -t.test("GET /api/v1/games/:game/:playtype/targets/recently-interacted", (t) => { +t.test("GET /api/v1/games/:game/:playtype/targets/recently-raised", (t) => { t.beforeEach(ResetDBState); // mutate @@ -58,7 +58,14 @@ t.test("GET /api/v1/games/:game/:playtype/targets/recently-interacted", (t) => { // not achieved HC511UserGoal, m({ goalID: "interacted", achieved: false, lastInteraction: 1000 }), - m({ goalID: "interacted_more_recently", achieved: true, lastInteraction: 2000 }), + // happened more recently + m({ goalID: "interacted_more_recently", achieved: false, lastInteraction: 2000 }), + // shouldnt be included -- just recently-raised. + m({ + goalID: "achieved", + achieved: true, + lastInteraction: 1000, + }), m({ goalID: "achieved_instantly", achieved: true, @@ -67,7 +74,7 @@ t.test("GET /api/v1/games/:game/:playtype/targets/recently-interacted", (t) => { }), ]); - const res = await mockApi.get("/api/v1/games/iidx/SP/targets/recently-interacted"); + const res = await mockApi.get("/api/v1/games/iidx/SP/targets/recently-raised"); t.equal(res.statusCode, 200); From 2ff2d99df7e92c160859b85a093014486c471cab Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Tue, 12 Apr 2022 02:15:31 +0100 Subject: [PATCH 054/106] Fix typo. --- .../_userID/games/_game/_playtype/targets/milestones/router.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/milestones/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/milestones/router.ts index ca2de9425..78c3690df 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/milestones/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/milestones/router.ts @@ -38,7 +38,7 @@ router.get("/", async (req, res) => { return res.status(200).json({ success: true, - description: `Retrieved ${milestoneSubs.length} milestone(s)`, + description: `Retrieved ${milestoneSubs.length} milestone(s).`, body: { milestones, milestoneSubs, From 4b1fa56ed9e7b9bd1984ba9846514e2eba73a50b Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Tue, 12 Apr 2022 02:29:46 +0100 Subject: [PATCH 055/106] Fix some faulty apostrophe work --- server/src/lib/score-import/framework/pb/create-pb-doc.ts | 2 +- .../src/lib/score-import/framework/user-game-stats/classes.ts | 4 ++-- .../score-import/import-types/common/api-kai/traverse-api.ts | 2 +- .../_userID/games/_game/_playtype/targets/goals/router.ts | 2 +- .../games/_game/_playtype/targets/milestones/router.ts | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/server/src/lib/score-import/framework/pb/create-pb-doc.ts b/server/src/lib/score-import/framework/pb/create-pb-doc.ts index 444c687ed..46c323e6c 100644 --- a/server/src/lib/score-import/framework/pb/create-pb-doc.ts +++ b/server/src/lib/score-import/framework/pb/create-pb-doc.ts @@ -62,7 +62,7 @@ export async function CreatePBDoc(userID: integer, chartID: string, logger: KtLo } /** - * Updates users' rankings on a given chart. + * Updates user's rankings on a given chart. */ export async function UpdateChartRanking(chartID: string) { const scores = await db["personal-bests"].find( diff --git a/server/src/lib/score-import/framework/user-game-stats/classes.ts b/server/src/lib/score-import/framework/user-game-stats/classes.ts index 10e424ed3..6a538687f 100644 --- a/server/src/lib/score-import/framework/user-game-stats/classes.ts +++ b/server/src/lib/score-import/framework/user-game-stats/classes.ts @@ -62,13 +62,13 @@ const STATIC_CLASS_HANDLERS: ClassHandlerMap = { }; /** - * Calculates a Users' Game Stats Classes. This function is rather complex, because the reality is rather complex. + * Calculates a User's Game Stats Classes. This function is rather complex, because the reality is rather complex. * * A class is simply a hard bounded division dependent on a user. Such as a Dan or a skill level dependent on a statistic. * Not all services expose this information in the same way, so this function takes an async resolve function, * which is allowed to return its own classes. These will be merged with the classes that *we* can calculate. * - * As an example, we are always able to calculate things like Gitadora's colours. We know the users' skill statistic, + * As an example, we are always able to calculate things like Gitadora's colours. We know the user's skill statistic, * and a colour is just between X-Y skill. However, we cannot always calculate something like IIDX's dans. Infact, * there's no calculation involved. We need to instead request this information from a service. For things like ARC * they expose this on a dedicated endpoint. diff --git a/server/src/lib/score-import/import-types/common/api-kai/traverse-api.ts b/server/src/lib/score-import/import-types/common/api-kai/traverse-api.ts index bb33785a1..2a6bd8469 100644 --- a/server/src/lib/score-import/import-types/common/api-kai/traverse-api.ts +++ b/server/src/lib/score-import/import-types/common/api-kai/traverse-api.ts @@ -32,7 +32,7 @@ const MAX_ITERATIONS = 500; * Traverses a Kai-like personal_bests api. * @param baseUrl The base URL to fetch requests from. - like https://google.com * @param subUrl The endpoint for the scores - /api/v1/personal_bests - * @param authDoc The users' authentication document. + * @param authDoc The user's authentication document. * @param fetch This is so `fetch` can be mocked with something * that wont make http requests during tests. */ diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts index 231f49985..2c9e34268 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts @@ -18,7 +18,7 @@ const router: Router = Router({ mergeParams: true }); const logger = CreateLogCtx(__filename); /** - * Retrieves this users' set goals for this GPT. + * Retrieves this user's set goals for this GPT. * * @name GET /api/v1/users/:userID/games/:game/:playtype/targets/goals */ diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/milestones/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/milestones/router.ts index 78c3690df..abcdfef10 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/milestones/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/milestones/router.ts @@ -19,7 +19,7 @@ const logger = CreateLogCtx(__filename); const router: Router = Router({ mergeParams: true }); /** - * Retrieves this users' set milestones. + * Retrieves this user's set milestones. * * @name GET /api/v1/users/:userID/games/:game/:playtype/targets/milestones */ From 09705ec199839cccc0d1fa7305b8dc406a8e38a4 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Tue, 12 Apr 2022 03:30:05 +0100 Subject: [PATCH 056/106] Finish up everything. --- server/src/lib/targets/milestones.ts | 18 +++++++++++++++++- .../_playtype/targets/milestones/router.ts | 4 +++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/server/src/lib/targets/milestones.ts b/server/src/lib/targets/milestones.ts index 09e3d7679..629baa426 100644 --- a/server/src/lib/targets/milestones.ts +++ b/server/src/lib/targets/milestones.ts @@ -2,6 +2,7 @@ import db from "external/mongo/db"; import { SubscribeFailReasons } from "lib/constants/err-codes"; import CreateLogCtx from "lib/logger/logger"; import { + GoalDocument, GoalSubscriptionDocument, integer, MilestoneDocument, @@ -167,6 +168,12 @@ export async function EvaluateMilestoneProgress(userID: integer, milestone: Mile }; } +interface MilestoneSubscriptionReturns { + milestoneSub: MilestoneSubscriptionDocument; + goals: GoalDocument[]; + goalResults: EvaluatedGoalResult[]; +} + /** * Subscribes the given user to a provided milestone. If the user is already subscribed, * null is returned. @@ -174,11 +181,20 @@ export async function EvaluateMilestoneProgress(userID: integer, milestone: Mile * @param cancelIfAchieved - Don't subscribe to the milestone if subscribing would cause * the user to immediately achieve it. */ +export async function SubscribeToMilestone( + userID: integer, + milestone: MilestoneDocument, + cancelIfAchieved: false +): Promise; export async function SubscribeToMilestone( userID: integer, milestone: MilestoneDocument, cancelIfAchieved = true -) { +): Promise< + | MilestoneSubscriptionReturns + | SubscribeFailReasons.ALREADY_SUBSCRIBED + | SubscribeFailReasons.ALREADY_ACHIEVED +> { const isSubscribedToMilestone = await db["milestone-subs"].findOne({ userID, milestoneID: milestone.milestoneID, diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/milestones/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/milestones/router.ts index abcdfef10..b94147d9c 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/milestones/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/milestones/router.ts @@ -113,6 +113,7 @@ router.get("/:milestoneID", GetMilestone, GetMilestoneSubscription, async (req, }.`, body: { milestoneSub, + milestone, results, goals, }, @@ -164,6 +165,7 @@ router.put( // Users should be able to subscribe to milestones EVEN IF they would instantly // achieve them. + // This is impossible, as the third argument to the above function is false! // if (subResult === SubscribeFailReasons.ALREADY_ACHIEVED) { // return res.status(400).json({ // success: false, @@ -181,7 +183,7 @@ router.put( return res.status(200).json({ success: true, description: `Subscribed to milestone '${milestone.name}'.`, - body: subResult, + body: { ...subResult, milestone }, }); } ); From f0e0bef853a2dcb0a82510403a129aef134c7922 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Wed, 13 Apr 2022 11:40:16 +0100 Subject: [PATCH 057/106] Enable node tap snapshots --- server/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/server/package.json b/server/package.json index 63acc6978..65ee8fa63 100644 --- a/server/package.json +++ b/server/package.json @@ -6,6 +6,7 @@ "private": true, "scripts": { "test": "tap", + "snap": "tap", "watchtest": "tap --watch", "build": "tsc --project tsconfig.build.json", "lint": "eslint ./src --ext .ts --fix", From 6f2ee361076e30408252bbd54d1ef6dbc1de0fc4 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Wed, 13 Apr 2022 12:44:28 +0100 Subject: [PATCH 058/106] Add tap-snapshot utility scripts. --- server/merge-snap.js | 45 ++++++++++++++++++++++++++++++++++++++ server/snap-single-file.sh | 27 +++++++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100755 server/merge-snap.js create mode 100755 server/snap-single-file.sh diff --git a/server/merge-snap.js b/server/merge-snap.js new file mode 100755 index 000000000..79713e631 --- /dev/null +++ b/server/merge-snap.js @@ -0,0 +1,45 @@ +const fs = require("fs"); + +// This script takes a path to a .test.ts file, finds its equivalent snapshot file +// and merges it with single-process-tap's snapshot file. + +const toMerge = process.argv[2]; + +if (!toMerge) { + throw new Error("no argv passed to merge-snap. Can't merge with nothing."); +} + +const BASE_FILE = "./tap-snapshots/src/test-utils/single-process-tap.ts.test.cjs"; +const NEW_FILE = `${toMerge.replace("tachi-server/src", "tachi-server/tap-snapshots/src")}.test.cjs`; + +const baseSet = require(BASE_FILE); + +const newSet = require(NEW_FILE); + +for (const [snapshotName, snapshotValue] of Object.entries(newSet)) { + baseSet[snapshotName] = snapshotValue; +} + + +function escapeBacktickString(str) { + return str.replace(/\`\$/, " ") +} + +const cases = []; + +for (const [snapshotName, snapshotValue] of Object.entries(baseSet)) { + cases.push(`exports[\`${escapeBacktickString(snapshotName)}\`] = \`${escapeBacktickString(snapshotValue)}\`\n`) +} + +let outFile = ` +/* IMPORTANT + * This snapshot file is auto-generated, but designed for humans. + * It should be checked into source control and tracked carefully. + * Re-generate by setting TAP_SNAPSHOT=1 and running tests. + * Make sure to inspect the output below. Do not ignore changes! + */ +'use strict' +${cases.join("\n")}` + +fs.writeFileSync(BASE_FILE, outFile); +fs.rmSync(NEW_FILE); \ No newline at end of file diff --git a/server/snap-single-file.sh b/server/snap-single-file.sh new file mode 100755 index 000000000..c87be32ce --- /dev/null +++ b/server/snap-single-file.sh @@ -0,0 +1,27 @@ +#! /bin/bash + +# This script is intended to be used as follows +# ./snap-single-file.sh path/to/test.TAP_SNAPSHOT +# This will result in that individual file being snapped. + +# Of course, we need to do this like this because we are abusing +# single process tap to get our tests done faster. + +# Put us in the directory this script is in. +cd "$(dirname "$0")" || exit 1 + +# So, TAP wont exit when ran on a single file because we never close it. +# This can be fixed another time, but until then I'm going to do this insane hack. +onsigint() +{ + node ./merge-snap.js "$1" +} + +# When we recieve a sigint, trap it and call our sigint handler. +# of course, this is bash, so trap onsigint doesn't do what you want. +# instead, we do 'onsigint "$@"', which means call onsigint with arguments like $1. +trap 'onsigint "$@"' SIGINT + +# Snapshot the argument. +TAP_SNAPSHOT=1 tap "$1" + From 10833b86a06841b7d667cc9c646cbbb6d98d5e7d Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Wed, 13 Apr 2022 12:45:42 +0100 Subject: [PATCH 059/106] fix: consistent indentation in root-shell scripts --- server/make-selfsigned-https.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/server/make-selfsigned-https.sh b/server/make-selfsigned-https.sh index e7633657c..437a8566b 100755 --- a/server/make-selfsigned-https.sh +++ b/server/make-selfsigned-https.sh @@ -3,6 +3,7 @@ # This command is for dev servers only! You should use this to # create a self-signed HTTPS certificate for local dev. # That is it. This is not secure. + mkdir -p cert yes "" | openssl req -x509 -newkey rsa:4096 -keyout ./cert/key.pem -out ./cert/cert.pem -sha256 -days 365 --nodes &> /dev/null From f45dfb3b33900b5e8da7d9db5cea8a217d271b4c Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Wed, 13 Apr 2022 12:50:04 +0100 Subject: [PATCH 060/106] Move PrivateUserDocument to server-types --- server/src/external/mongo/db.ts | 2 +- server/src/server/router/api/v1/auth/auth.ts | 9 ++------- server/src/utils/types.ts | 9 +++++++++ 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/server/src/external/mongo/db.ts b/server/src/external/mongo/db.ts index 527db645f..92fba9e9c 100644 --- a/server/src/external/mongo/db.ts +++ b/server/src/external/mongo/db.ts @@ -24,7 +24,6 @@ import { MilestoneSetDocument, OrphanChart, PBScoreDocument, - PrivateUserInfoDocument, PublicUserDocument, RecentlyViewedFolderDocument, ScoreDocument, @@ -41,6 +40,7 @@ import { UserSettings, } from "tachi-common"; import { GetMillisecondsSince } from "utils/misc"; +import { PrivateUserInfoDocument } from "utils/types"; const logger = CreateLogCtx(__filename); let dbName = ServerConfig.MONGO_DATABASE_NAME; diff --git a/server/src/server/router/api/v1/auth/auth.ts b/server/src/server/router/api/v1/auth/auth.ts index 57492b4ae..ea721356b 100644 --- a/server/src/server/router/api/v1/auth/auth.ts +++ b/server/src/server/router/api/v1/auth/auth.ts @@ -2,15 +2,10 @@ import bcrypt from "bcryptjs"; import db from "external/mongo/db"; import CreateLogCtx from "lib/logger/logger"; import { Environment, ServerConfig } from "lib/setup/config"; -import { - integer, - PrivateUserInfoDocument, - PublicUserDocument, - UserAuthLevels, - UserSettings, -} from "tachi-common"; +import { integer, PublicUserDocument, UserAuthLevels, UserSettings } from "tachi-common"; import nodeFetch from "utils/fetch"; import { Random20Hex } from "utils/misc"; +import { PrivateUserInfoDocument } from "utils/types"; import { CreateURLWithParams } from "utils/url"; import { FormatUserDoc } from "utils/user"; diff --git a/server/src/utils/types.ts b/server/src/utils/types.ts index ab8a5869c..b265f5d09 100644 --- a/server/src/utils/types.ts +++ b/server/src/utils/types.ts @@ -16,6 +16,7 @@ import { GoalSubscriptionDocument, MilestoneDocument, MilestoneSetDocument, + integer, } from "tachi-common"; declare module "express-session" { @@ -75,3 +76,11 @@ export interface TachiRequestData { apiClientDoc: Omit; } + +// This is only used on tachi-server, and isn't exposed -- so shouldn't be a part +// of common. +export interface PrivateUserInfoDocument { + userID: integer; + password: string; + email: string; +} From ba5828973b7b20f9c9b2fd4a46409b633f35b571 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Wed, 13 Apr 2022 12:50:17 +0100 Subject: [PATCH 061/106] Add tablefolders to usc test chart --- server/src/test-utils/test-data.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/server/src/test-utils/test-data.ts b/server/src/test-utils/test-data.ts index b40ff3703..f6b0a5f3d 100644 --- a/server/src/test-utils/test-data.ts +++ b/server/src/test-utils/test-data.ts @@ -540,6 +540,7 @@ export const uscChart: ChartDocument<"usc:Controller" | "usc:Keyboard"> = { hashSHA1: "USC_CHART_HASH", isOfficial: false, effector: "foo", + tableFolders: [], }, tierlistInfo: {}, isPrimary: true, From 2bbae1019a90f9ef02e72f46bf415f086ce130b5 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Wed, 13 Apr 2022 12:50:24 +0100 Subject: [PATCH 062/106] Draft snapshot tests --- .../_playtype/targets/goals/router.test.ts | 356 ++++++++++++++++++ .../_game/_playtype/targets/goals/router.ts | 25 +- .../test-utils/single-process-tap.ts.test.cjs | 135 +++++++ 3 files changed, 509 insertions(+), 7 deletions(-) create mode 100644 server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts create mode 100644 server/tap-snapshots/src/test-utils/single-process-tap.ts.test.cjs diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts new file mode 100644 index 000000000..fd135371c --- /dev/null +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts @@ -0,0 +1,356 @@ +import dm from "deepmerge"; +import db from "external/mongo/db"; +import t from "tap"; +import { CreateFakeAuthCookie } from "test-utils/fake-auth"; +import mockApi from "test-utils/mock-api"; +import ResetDBState from "test-utils/resets"; +import { + HC511Goal, + HC511UserGoal, + Testing511SPA, + TestingIIDXSPMilestone, +} from "test-utils/test-data"; + +// @ts-expect-error Not sure why the types break here, but they do. +const dupedGoal = dm({}, HC511Goal); +// @ts-expect-error Not sure why the types break here, but they do. +const dupedGoalSub = dm({}, HC511UserGoal); + +t.test("GET /api/v1/users/:userID/games/:game/:playtype/targets/goals", (t) => { + t.beforeEach(ResetDBState); + + t.test("Should retrieve this user's goal subscriptions.", async (t) => { + await db.goals.insert(dupedGoal); + await db["goal-subs"].insert(dupedGoalSub); + + const res = await mockApi.get("/api/v1/users/1/games/iidx/SP/targets/goals"); + + t.equal(res.statusCode, 200); + + t.strictSame(res.body.body, { + goals: [HC511Goal], + goalSubs: [HC511UserGoal], + }); + + t.end(); + }); + + t.end(); +}); + +t.test("POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal", async (t) => { + t.beforeEach(ResetDBState); + + const cookie = await CreateFakeAuthCookie(mockApi); + + const baseInput = { + criteria: { + key: "scoreData.percent", + value: 1, + mode: "single", + }, + charts: { + type: "single", + data: Testing511SPA.chartID, + }, + }; + + function mkInput(caseName: string, merge: any) { + return { + caseName, + content: dm(baseInput, merge), + }; + } + + t.test("Should set valid goals.", async (t) => { + const validInput = [ + mkInput("Base Case", baseInput), + mkInput("Score:Single Case", { + criteria: { + key: "scoreData.score", + value: 1, + mode: "", + }, + }), + ]; + + t.end(); + }); + + t.test("Should reject invalid goals.", async (t) => { + const invalidInput = [ + mkInput("negative percent", { + criteria: { + value: -1, + }, + }), + mkInput("percent of 0 is a non-goal", { + criteria: { + value: 0, + }, + }), + mkInput("percent greater than 100", { + criteria: { + value: 100.1, + }, + }), + mkInput("abs without countNum", { + criteria: { + mode: "abs", + }, + }), + mkInput("proportion without countNum", { + criteria: { + mode: "proportion", + }, + }), + mkInput("single with countNum", { + criteria: { + mode: "single", + countNum: 123, + }, + }), + mkInput("nonsense mode", { + criteria: { + mode: "nonsense", + }, + }), + mkInput("abs with countNum but charts.type == single", { + criteria: { + mode: "abs", + countNum: 1, + }, + charts: { + type: "single", + }, + }), + mkInput("nonsense charts.type", { + charts: { + type: "nonsense", + }, + }), + mkInput("charts.data when type == any", { + charts: { + type: "any", + data: "foo", + }, + }), + mkInput("charts.data array when type == single", { + charts: { + type: "single", + data: [Testing511SPA.chartID, Testing511SPA.chartID], + }, + }), + mkInput("charts.data array of identical chartIDs", { + charts: { + type: "multi", + data: [Testing511SPA.chartID, Testing511SPA.chartID], + }, + }), + mkInput("charts.data array of single chartID", { + charts: { + type: "multi", + data: [Testing511SPA.chartID], + }, + }), + mkInput("charts.data array of chartIDs that don't exist", { + charts: { + type: "multi", + data: [Testing511SPA.chartID, "not_exist"], + }, + }), + mkInput("charts.data folder refers to folder that doesn't exist", { + charts: { + type: "folder", + data: "fake-folder", + }, + }), + mkInput("nonsense charts.data", { + charts: { + data: "nonsense", + }, + }), + ]; + + for (const input of invalidInput) { + // eslint-disable-next-line no-await-in-loop + const res = await mockApi + .post("/api/v1/users/1/games/iidx/SP/targets/goals/add-goal") + .set("Cookie", cookie) + .send(input.content); + + t.equal(res.statusCode, 400); + + t.matchSnapshot(res.body.description, `Invalid Goal: ${input.caseName}`); + } + + t.end(); + }); + + t.end(); +}); + +t.test("GET /api/v1/users/:userID/games/:game/:playtype/targets/goals/:goalID", (t) => { + t.beforeEach(ResetDBState); + + t.test("Should retrieve this user's goal subscription.", async (t) => { + await db.goals.insert(dupedGoal); + await db["goal-subs"].insert(dupedGoalSub); + + const res = await mockApi.get( + `/api/v1/users/1/games/iidx/SP/targets/goals/${HC511Goal.goalID}` + ); + + t.equal(res.statusCode, 200); + + t.hasStrict(res.body.body, { + goal: HC511Goal, + goalSub: HC511UserGoal, + milestones: [], + user: { + id: 1, + }, + }); + + t.end(); + }); + + t.test("Should return parent milestones if goal has any.", async (t) => { + await db.goals.insert(dupedGoal); + await db["goal-subs"].insert( + // @ts-expect-error Not sure why the types break here, but they do. + dm(HC511UserGoal, { + parentMilestones: [TestingIIDXSPMilestone.milestoneID], + }) + ); + await db.milestones.insert(dm(TestingIIDXSPMilestone, {})); + + const res = await mockApi.get( + `/api/v1/users/1/games/iidx/SP/targets/goals/${HC511Goal.goalID}` + ); + + t.equal(res.statusCode, 200); + + t.hasStrict(res.body.body, { + goal: HC511Goal, + goalSub: HC511UserGoal, + milestones: [TestingIIDXSPMilestone], + user: { + id: 1, + }, + }); + + t.end(); + }); + + t.test("Should panic if goal refers to a parent milestone that doesn't exist.", async (t) => { + await db.goals.insert(dupedGoal); + await db["goal-subs"].insert( + // @ts-expect-error Not sure why the types break here, but they do. + dm(HC511UserGoal, { + parentMilestones: [TestingIIDXSPMilestone.milestoneID], + }) + ); + + const res = await mockApi.get( + `/api/v1/users/1/games/iidx/SP/targets/goals/${HC511Goal.goalID}` + ); + + t.equal(res.statusCode, 500); + + t.end(); + }); + + t.test("Should return 404 if the user is not subscribed to this goal ID.", async (t) => { + const res = await mockApi.get(`/api/v1/users/1/games/iidx/SP/targets/goals/INVALID_GOAL`); + + t.equal(res.statusCode, 404); + + t.end(); + }); + + t.end(); +}); + +t.test("DELETE /api/v1/users/:userID/games/:game/:playtype/targets/goals/:goalID", async (t) => { + t.beforeEach(ResetDBState); + + const cookie = await CreateFakeAuthCookie(mockApi); + + t.test("Should delete a goal subscription if subscribed.", async (t) => { + await db.goals.insert(dupedGoal); + await db["goal-subs"].insert(dupedGoalSub); + + const res = await mockApi + .delete(`/api/v1/users/1/games/iidx/SP/targets/goals/${dupedGoalSub.goalID}`) + .set("Cookie", cookie); + + t.equal(res.statusCode, 200); + + const dbRes = await db["goal-subs"].findOne({ + userID: 1, + goalID: dupedGoal.goalID, + }); + + t.equal(dbRes, null, "Should delete the goal sub from the database."); + + t.end(); + }); + + t.test("Should reject a goal deletion if goal has parent milestones.", async (t) => { + await db.goals.insert(dupedGoal); + await db["goal-subs"].insert( + // @ts-expect-error deepmerge type error + dm(dupedGoalSub, { + parentMilestones: [TestingIIDXSPMilestone.milestoneID], + }) + ); + + const res = await mockApi + .delete(`/api/v1/users/1/games/iidx/SP/targets/goals/${dupedGoalSub.goalID}`) + .set("Cookie", cookie); + + t.equal(res.statusCode, 400); + t.equal( + res.body.description, + "This goal is part of a milestone you are subscribed to. It can only be removed by unsubscribing from the relevant milestones." + ); + + const dbRes = await db["goal-subs"].findOne({ + userID: 1, + goalID: dupedGoal.goalID, + }); + + t.not(dbRes, null, "Should NOT delete the goal sub from the database."); + + t.end(); + }); + + t.test("Should reject a goal deletion if not subscribed.", async (t) => { + await db.goals.insert(dupedGoal); + await db["goal-subs"].insert(dupedGoalSub); + + const res = await mockApi + .delete(`/api/v1/users/1/games/iidx/SP/targets/goals/INVALID_GOAL`) + .set("Cookie", cookie); + + t.equal(res.statusCode, 404); + + t.end(); + }); + + t.test("Should reject a goal deletion if not authed.", async (t) => { + await db.goals.insert(dupedGoal); + await db["goal-subs"].insert(dupedGoalSub); + + const res = await mockApi.delete( + `/api/v1/users/1/games/iidx/SP/targets/goals/${dupedGoalSub.goalID}` + ); + + t.equal(res.statusCode, 401); + + t.end(); + }); + + t.end(); +}); diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts index 2c9e34268..438a9dfb5 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts @@ -9,7 +9,7 @@ import p from "prudence"; import { RequirePermissions } from "server/middleware/auth"; import prValidate from "server/middleware/prudence-validate"; import { GoalDocument, MilestoneDocument } from "tachi-common"; -import { GetGoalForIDGuaranteed, GetMilestoneForIDGuaranteed } from "utils/db"; +import { GetGoalForIDGuaranteed } from "utils/db"; import { AssignToReqTachiData, GetUGPT } from "utils/req-tachi-data"; import { RequireAuthedAsUser } from "../../../../../middleware"; @@ -88,7 +88,7 @@ router.post( ); } - // proper validation later. + // proper valid/ation later. return p.gte(0)(self); }, }, @@ -120,6 +120,8 @@ router.post( ); } + // technically impossible, so let's ignore it. + /* istanbul ignore next */ return "Unknown charts.type."; }, }, @@ -218,9 +220,19 @@ router.get("/:goalID", GetGoalSubscription, async (req, res) => { let milestones: MilestoneDocument[] = []; if (goalSub.parentMilestones.length !== 0) { - milestones = await Promise.all( - goalSub.parentMilestones.map((e) => GetMilestoneForIDGuaranteed(e)) - ); + milestones = await db.milestones.find({ + milestoneID: { $in: goalSub.parentMilestones }, + }); + + if (milestones.length !== goalSub.parentMilestones.length) { + logger.error( + `Goal '${goalSub.goalID} (uid:${goalSub.userID})' refers to parent milestones that don't exist?` + ); + return res.status(500).json({ + success: false, + description: `Something went wrong when fetching parent milestones. This has been reported.`, + }); + } } const goal = await GetGoalForIDGuaranteed(goalSub.goalID); @@ -247,7 +259,6 @@ router.delete( RequireAuthedAsUser, GetGoalSubscription, RequirePermissions("manage_targets"), - prValidate({ goalID: "string" }), async (req, res) => { const goalID = req.params.goalID; const { user, game, playtype } = GetUGPT(req); @@ -260,7 +271,7 @@ router.delete( }); if (!goalSub) { - return res.status(400).json({ + return res.status(404).json({ success: false, description: `You aren't subscribed to this goal.`, }); diff --git a/server/tap-snapshots/src/test-utils/single-process-tap.ts.test.cjs b/server/tap-snapshots/src/test-utils/single-process-tap.ts.test.cjs new file mode 100644 index 000000000..e64f5ce19 --- /dev/null +++ b/server/tap-snapshots/src/test-utils/single-process-tap.ts.test.cjs @@ -0,0 +1,135 @@ + +/* IMPORTANT + * This snapshot file is auto-generated, but designed for humans. + * It should be checked into source control and tracked carefully. + * Re-generate by setting TAP_SNAPSHOT=1 and running tests. + * Make sure to inspect the output below. Do not ignore changes! + */ +'use strict' +exports[`src/test-utils/single-process-tap.ts TAP POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal Should reject invalid goals. > Invalid Goal: abs with countNum but charts.type == single 1`] = ` +Invalid countNum for goal with criteria.mode of 'abs'. Expected a whole number less than the total amount of charts available and greater than 1. +` + +exports[`src/test-utils/single-process-tap.ts TAP POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal Should reject invalid goals. > Invalid Goal: abs without countNum 1`] = ` +[criteria.countNum] Expected number to be greater than or equal to 0. (Received nothing) +` + +exports[`src/test-utils/single-process-tap.ts TAP POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal Should reject invalid goals. > Invalid Goal: charts.data array of chartIDs that don't exist 1`] = ` +Expected charts.data to match 2 charts. Instead, it only matched 1. Are all of these chartIDs valid? +` + +exports[`src/test-utils/single-process-tap.ts TAP POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal Should reject invalid goals. > Invalid Goal: charts.data array of identical chartIDs 1`] = ` +Expected charts.data to match 2 charts. Instead, it only matched 1. Are all of these chartIDs valid? +` + +exports[`src/test-utils/single-process-tap.ts TAP POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal Should reject invalid goals. > Invalid Goal: charts.data array of single chartID 1`] = ` +[charts.data] Expected an array of 2 to 5 strings in charts.data due to charts.type being 'multi'. (Received c2311194e3897ddb5745b1760d2c0141f933e683) +` + +exports[`src/test-utils/single-process-tap.ts TAP POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal Should reject invalid goals. > Invalid Goal: charts.data array when type == single 1`] = ` +[charts.data] Expected a string in charts.data due to charts.type being 'single'. (Received c2311194e3897ddb5745b1760d2c0141f933e683,c2311194e3897ddb5745b1760d2c0141f933e683) +` + +exports[`src/test-utils/single-process-tap.ts TAP POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal Should reject invalid goals. > Invalid Goal: charts.data folder refers to folder that doesn't exist 1`] = ` +A folder with id fake-folder does not exist for iidx:SP. +` + +exports[`src/test-utils/single-process-tap.ts TAP POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal Should reject invalid goals. > Invalid Goal: charts.data when type == any 1`] = ` +[charts.data] Invalid charts.data for type 'any'. Must not have any data! (Received foo) +` + +exports[`src/test-utils/single-process-tap.ts TAP POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal Should reject invalid goals. > Invalid Goal: negative percent 1`] = ` +[criteria.value] Expected number to be greater than or equal to 0. (Received -1) +` + +exports[`src/test-utils/single-process-tap.ts TAP POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal Should reject invalid goals. > Invalid Goal: nonsense charts.data 1`] = ` +A chart with id nonsense does not exist for iidx:SP. +` + +exports[`src/test-utils/single-process-tap.ts TAP POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal Should reject invalid goals. > Invalid Goal: nonsense charts.type 1`] = ` +[charts.type] Expected any of single, multi, folder, any. (Received nonsense) +` + +exports[`src/test-utils/single-process-tap.ts TAP POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal Should reject invalid goals. > Invalid Goal: nonsense mode 1`] = ` +[criteria.mode] Expected any of single, abs, proportion. (Received nonsense) +` + +exports[`src/test-utils/single-process-tap.ts TAP POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal Should reject invalid goals. > Invalid Goal: percent greater than 100 1`] = ` +Invalid value of 100.1 for percent goal. Percents must be between 0 and 100. +` + +exports[`src/test-utils/single-process-tap.ts TAP POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal Should reject invalid goals. > Invalid Goal: percent of 0 is a non-goal 1`] = ` +Invalid value of 0 for percent goal. Percents must be between 0 and 100. +` + +exports[`src/test-utils/single-process-tap.ts TAP POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal Should reject invalid goals. > Invalid Goal: proportion without countNum 1`] = ` +[criteria.countNum] Expected number to be greater than or equal to 0. (Received nothing) +` + +exports[`src/test-utils/single-process-tap.ts TAP POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal Should reject invalid goals. > Invalid Goal: single with countNum 1`] = ` +[criteria.countNum] Invalid countNum for mode 'single'. Must not have one! (Received 123) +` + +exports[`src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts TAP POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal Should reject invalid goals. > Invalid Goal: abs with countNum but charts.type == single 1`] = ` +Invalid countNum for goal with criteria.mode of 'abs'. Expected a whole number less than the total amount of charts available and greater than 1. +` + +exports[`src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts TAP POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal Should reject invalid goals. > Invalid Goal: abs without countNum 1`] = ` +[criteria.countNum] Expected number to be greater than or equal to 0. (Received nothing) +` + +exports[`src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts TAP POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal Should reject invalid goals. > Invalid Goal: charts.data array of chartIDs that don't exist 1`] = ` +Expected charts.data to match 2 charts. Instead, it only matched 1. Are all of these chartIDs valid? +` + +exports[`src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts TAP POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal Should reject invalid goals. > Invalid Goal: charts.data array of identical chartIDs 1`] = ` +Expected charts.data to match 2 charts. Instead, it only matched 1. Are all of these chartIDs valid? +` + +exports[`src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts TAP POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal Should reject invalid goals. > Invalid Goal: charts.data array of single chartID 1`] = ` +[charts.data] Expected an array of 2 to 5 strings in charts.data due to charts.type being 'multi'. (Received c2311194e3897ddb5745b1760d2c0141f933e683) +` + +exports[`src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts TAP POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal Should reject invalid goals. > Invalid Goal: charts.data array when type == single 1`] = ` +[charts.data] Expected a string in charts.data due to charts.type being 'single'. (Received c2311194e3897ddb5745b1760d2c0141f933e683,c2311194e3897ddb5745b1760d2c0141f933e683) +` + +exports[`src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts TAP POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal Should reject invalid goals. > Invalid Goal: charts.data folder refers to folder that doesn't exist 1`] = ` +A folder with id fake-folder does not exist for iidx:SP. +` + +exports[`src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts TAP POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal Should reject invalid goals. > Invalid Goal: charts.data when type == any 1`] = ` +[charts.data] Invalid charts.data for type 'any'. Must not have any data! (Received foo) +` + +exports[`src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts TAP POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal Should reject invalid goals. > Invalid Goal: negative percent 1`] = ` +[criteria.value] Expected number to be greater than or equal to 0. (Received -1) +` + +exports[`src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts TAP POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal Should reject invalid goals. > Invalid Goal: nonsense charts.data 1`] = ` +A chart with id nonsense does not exist for iidx:SP. +` + +exports[`src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts TAP POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal Should reject invalid goals. > Invalid Goal: nonsense charts.type 1`] = ` +[charts.type] Expected any of single, multi, folder, any. (Received nonsense) +` + +exports[`src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts TAP POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal Should reject invalid goals. > Invalid Goal: nonsense mode 1`] = ` +[criteria.mode] Expected any of single, abs, proportion. (Received nonsense) +` + +exports[`src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts TAP POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal Should reject invalid goals. > Invalid Goal: percent greater than 100 1`] = ` +Invalid value of 100.1 for percent goal. Percents must be between 0 and 100. +` + +exports[`src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts TAP POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal Should reject invalid goals. > Invalid Goal: percent of 0 is a non-goal 1`] = ` +Invalid value of 0 for percent goal. Percents must be between 0 and 100. +` + +exports[`src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts TAP POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal Should reject invalid goals. > Invalid Goal: proportion without countNum 1`] = ` +[criteria.countNum] Expected number to be greater than or equal to 0. (Received nothing) +` + +exports[`src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts TAP POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal Should reject invalid goals. > Invalid Goal: single with countNum 1`] = ` +[criteria.countNum] Invalid countNum for mode 'single'. Must not have one! (Received 123) +` From 2b9b009d8ed8eebac9d50ae3467a661da7c2a38a Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Wed, 13 Apr 2022 14:12:57 +0100 Subject: [PATCH 063/106] Undo all snapshot changes, since snapshots won't work. This reverts commit f0e0bef853a2dcb0a82510403a129aef134c7922. --- server/make-selfsigned-https.sh | 1 - server/merge-snap.js | 45 --- server/package.json | 1 - server/snap-single-file.sh | 27 -- server/src/external/mongo/db.ts | 2 +- server/src/server/router/api/v1/auth/auth.ts | 9 +- .../_playtype/targets/goals/router.test.ts | 356 ------------------ .../_game/_playtype/targets/goals/router.ts | 25 +- server/src/test-utils/test-data.ts | 1 - server/src/utils/types.ts | 9 - .../test-utils/single-process-tap.ts.test.cjs | 135 ------- 11 files changed, 15 insertions(+), 596 deletions(-) delete mode 100755 server/merge-snap.js delete mode 100755 server/snap-single-file.sh delete mode 100644 server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts delete mode 100644 server/tap-snapshots/src/test-utils/single-process-tap.ts.test.cjs diff --git a/server/make-selfsigned-https.sh b/server/make-selfsigned-https.sh index 437a8566b..e7633657c 100755 --- a/server/make-selfsigned-https.sh +++ b/server/make-selfsigned-https.sh @@ -3,7 +3,6 @@ # This command is for dev servers only! You should use this to # create a self-signed HTTPS certificate for local dev. # That is it. This is not secure. - mkdir -p cert yes "" | openssl req -x509 -newkey rsa:4096 -keyout ./cert/key.pem -out ./cert/cert.pem -sha256 -days 365 --nodes &> /dev/null diff --git a/server/merge-snap.js b/server/merge-snap.js deleted file mode 100755 index 79713e631..000000000 --- a/server/merge-snap.js +++ /dev/null @@ -1,45 +0,0 @@ -const fs = require("fs"); - -// This script takes a path to a .test.ts file, finds its equivalent snapshot file -// and merges it with single-process-tap's snapshot file. - -const toMerge = process.argv[2]; - -if (!toMerge) { - throw new Error("no argv passed to merge-snap. Can't merge with nothing."); -} - -const BASE_FILE = "./tap-snapshots/src/test-utils/single-process-tap.ts.test.cjs"; -const NEW_FILE = `${toMerge.replace("tachi-server/src", "tachi-server/tap-snapshots/src")}.test.cjs`; - -const baseSet = require(BASE_FILE); - -const newSet = require(NEW_FILE); - -for (const [snapshotName, snapshotValue] of Object.entries(newSet)) { - baseSet[snapshotName] = snapshotValue; -} - - -function escapeBacktickString(str) { - return str.replace(/\`\$/, " ") -} - -const cases = []; - -for (const [snapshotName, snapshotValue] of Object.entries(baseSet)) { - cases.push(`exports[\`${escapeBacktickString(snapshotName)}\`] = \`${escapeBacktickString(snapshotValue)}\`\n`) -} - -let outFile = ` -/* IMPORTANT - * This snapshot file is auto-generated, but designed for humans. - * It should be checked into source control and tracked carefully. - * Re-generate by setting TAP_SNAPSHOT=1 and running tests. - * Make sure to inspect the output below. Do not ignore changes! - */ -'use strict' -${cases.join("\n")}` - -fs.writeFileSync(BASE_FILE, outFile); -fs.rmSync(NEW_FILE); \ No newline at end of file diff --git a/server/package.json b/server/package.json index 65ee8fa63..63acc6978 100644 --- a/server/package.json +++ b/server/package.json @@ -6,7 +6,6 @@ "private": true, "scripts": { "test": "tap", - "snap": "tap", "watchtest": "tap --watch", "build": "tsc --project tsconfig.build.json", "lint": "eslint ./src --ext .ts --fix", diff --git a/server/snap-single-file.sh b/server/snap-single-file.sh deleted file mode 100755 index c87be32ce..000000000 --- a/server/snap-single-file.sh +++ /dev/null @@ -1,27 +0,0 @@ -#! /bin/bash - -# This script is intended to be used as follows -# ./snap-single-file.sh path/to/test.TAP_SNAPSHOT -# This will result in that individual file being snapped. - -# Of course, we need to do this like this because we are abusing -# single process tap to get our tests done faster. - -# Put us in the directory this script is in. -cd "$(dirname "$0")" || exit 1 - -# So, TAP wont exit when ran on a single file because we never close it. -# This can be fixed another time, but until then I'm going to do this insane hack. -onsigint() -{ - node ./merge-snap.js "$1" -} - -# When we recieve a sigint, trap it and call our sigint handler. -# of course, this is bash, so trap onsigint doesn't do what you want. -# instead, we do 'onsigint "$@"', which means call onsigint with arguments like $1. -trap 'onsigint "$@"' SIGINT - -# Snapshot the argument. -TAP_SNAPSHOT=1 tap "$1" - diff --git a/server/src/external/mongo/db.ts b/server/src/external/mongo/db.ts index 92fba9e9c..527db645f 100644 --- a/server/src/external/mongo/db.ts +++ b/server/src/external/mongo/db.ts @@ -24,6 +24,7 @@ import { MilestoneSetDocument, OrphanChart, PBScoreDocument, + PrivateUserInfoDocument, PublicUserDocument, RecentlyViewedFolderDocument, ScoreDocument, @@ -40,7 +41,6 @@ import { UserSettings, } from "tachi-common"; import { GetMillisecondsSince } from "utils/misc"; -import { PrivateUserInfoDocument } from "utils/types"; const logger = CreateLogCtx(__filename); let dbName = ServerConfig.MONGO_DATABASE_NAME; diff --git a/server/src/server/router/api/v1/auth/auth.ts b/server/src/server/router/api/v1/auth/auth.ts index ea721356b..57492b4ae 100644 --- a/server/src/server/router/api/v1/auth/auth.ts +++ b/server/src/server/router/api/v1/auth/auth.ts @@ -2,10 +2,15 @@ import bcrypt from "bcryptjs"; import db from "external/mongo/db"; import CreateLogCtx from "lib/logger/logger"; import { Environment, ServerConfig } from "lib/setup/config"; -import { integer, PublicUserDocument, UserAuthLevels, UserSettings } from "tachi-common"; +import { + integer, + PrivateUserInfoDocument, + PublicUserDocument, + UserAuthLevels, + UserSettings, +} from "tachi-common"; import nodeFetch from "utils/fetch"; import { Random20Hex } from "utils/misc"; -import { PrivateUserInfoDocument } from "utils/types"; import { CreateURLWithParams } from "utils/url"; import { FormatUserDoc } from "utils/user"; diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts deleted file mode 100644 index fd135371c..000000000 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts +++ /dev/null @@ -1,356 +0,0 @@ -import dm from "deepmerge"; -import db from "external/mongo/db"; -import t from "tap"; -import { CreateFakeAuthCookie } from "test-utils/fake-auth"; -import mockApi from "test-utils/mock-api"; -import ResetDBState from "test-utils/resets"; -import { - HC511Goal, - HC511UserGoal, - Testing511SPA, - TestingIIDXSPMilestone, -} from "test-utils/test-data"; - -// @ts-expect-error Not sure why the types break here, but they do. -const dupedGoal = dm({}, HC511Goal); -// @ts-expect-error Not sure why the types break here, but they do. -const dupedGoalSub = dm({}, HC511UserGoal); - -t.test("GET /api/v1/users/:userID/games/:game/:playtype/targets/goals", (t) => { - t.beforeEach(ResetDBState); - - t.test("Should retrieve this user's goal subscriptions.", async (t) => { - await db.goals.insert(dupedGoal); - await db["goal-subs"].insert(dupedGoalSub); - - const res = await mockApi.get("/api/v1/users/1/games/iidx/SP/targets/goals"); - - t.equal(res.statusCode, 200); - - t.strictSame(res.body.body, { - goals: [HC511Goal], - goalSubs: [HC511UserGoal], - }); - - t.end(); - }); - - t.end(); -}); - -t.test("POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal", async (t) => { - t.beforeEach(ResetDBState); - - const cookie = await CreateFakeAuthCookie(mockApi); - - const baseInput = { - criteria: { - key: "scoreData.percent", - value: 1, - mode: "single", - }, - charts: { - type: "single", - data: Testing511SPA.chartID, - }, - }; - - function mkInput(caseName: string, merge: any) { - return { - caseName, - content: dm(baseInput, merge), - }; - } - - t.test("Should set valid goals.", async (t) => { - const validInput = [ - mkInput("Base Case", baseInput), - mkInput("Score:Single Case", { - criteria: { - key: "scoreData.score", - value: 1, - mode: "", - }, - }), - ]; - - t.end(); - }); - - t.test("Should reject invalid goals.", async (t) => { - const invalidInput = [ - mkInput("negative percent", { - criteria: { - value: -1, - }, - }), - mkInput("percent of 0 is a non-goal", { - criteria: { - value: 0, - }, - }), - mkInput("percent greater than 100", { - criteria: { - value: 100.1, - }, - }), - mkInput("abs without countNum", { - criteria: { - mode: "abs", - }, - }), - mkInput("proportion without countNum", { - criteria: { - mode: "proportion", - }, - }), - mkInput("single with countNum", { - criteria: { - mode: "single", - countNum: 123, - }, - }), - mkInput("nonsense mode", { - criteria: { - mode: "nonsense", - }, - }), - mkInput("abs with countNum but charts.type == single", { - criteria: { - mode: "abs", - countNum: 1, - }, - charts: { - type: "single", - }, - }), - mkInput("nonsense charts.type", { - charts: { - type: "nonsense", - }, - }), - mkInput("charts.data when type == any", { - charts: { - type: "any", - data: "foo", - }, - }), - mkInput("charts.data array when type == single", { - charts: { - type: "single", - data: [Testing511SPA.chartID, Testing511SPA.chartID], - }, - }), - mkInput("charts.data array of identical chartIDs", { - charts: { - type: "multi", - data: [Testing511SPA.chartID, Testing511SPA.chartID], - }, - }), - mkInput("charts.data array of single chartID", { - charts: { - type: "multi", - data: [Testing511SPA.chartID], - }, - }), - mkInput("charts.data array of chartIDs that don't exist", { - charts: { - type: "multi", - data: [Testing511SPA.chartID, "not_exist"], - }, - }), - mkInput("charts.data folder refers to folder that doesn't exist", { - charts: { - type: "folder", - data: "fake-folder", - }, - }), - mkInput("nonsense charts.data", { - charts: { - data: "nonsense", - }, - }), - ]; - - for (const input of invalidInput) { - // eslint-disable-next-line no-await-in-loop - const res = await mockApi - .post("/api/v1/users/1/games/iidx/SP/targets/goals/add-goal") - .set("Cookie", cookie) - .send(input.content); - - t.equal(res.statusCode, 400); - - t.matchSnapshot(res.body.description, `Invalid Goal: ${input.caseName}`); - } - - t.end(); - }); - - t.end(); -}); - -t.test("GET /api/v1/users/:userID/games/:game/:playtype/targets/goals/:goalID", (t) => { - t.beforeEach(ResetDBState); - - t.test("Should retrieve this user's goal subscription.", async (t) => { - await db.goals.insert(dupedGoal); - await db["goal-subs"].insert(dupedGoalSub); - - const res = await mockApi.get( - `/api/v1/users/1/games/iidx/SP/targets/goals/${HC511Goal.goalID}` - ); - - t.equal(res.statusCode, 200); - - t.hasStrict(res.body.body, { - goal: HC511Goal, - goalSub: HC511UserGoal, - milestones: [], - user: { - id: 1, - }, - }); - - t.end(); - }); - - t.test("Should return parent milestones if goal has any.", async (t) => { - await db.goals.insert(dupedGoal); - await db["goal-subs"].insert( - // @ts-expect-error Not sure why the types break here, but they do. - dm(HC511UserGoal, { - parentMilestones: [TestingIIDXSPMilestone.milestoneID], - }) - ); - await db.milestones.insert(dm(TestingIIDXSPMilestone, {})); - - const res = await mockApi.get( - `/api/v1/users/1/games/iidx/SP/targets/goals/${HC511Goal.goalID}` - ); - - t.equal(res.statusCode, 200); - - t.hasStrict(res.body.body, { - goal: HC511Goal, - goalSub: HC511UserGoal, - milestones: [TestingIIDXSPMilestone], - user: { - id: 1, - }, - }); - - t.end(); - }); - - t.test("Should panic if goal refers to a parent milestone that doesn't exist.", async (t) => { - await db.goals.insert(dupedGoal); - await db["goal-subs"].insert( - // @ts-expect-error Not sure why the types break here, but they do. - dm(HC511UserGoal, { - parentMilestones: [TestingIIDXSPMilestone.milestoneID], - }) - ); - - const res = await mockApi.get( - `/api/v1/users/1/games/iidx/SP/targets/goals/${HC511Goal.goalID}` - ); - - t.equal(res.statusCode, 500); - - t.end(); - }); - - t.test("Should return 404 if the user is not subscribed to this goal ID.", async (t) => { - const res = await mockApi.get(`/api/v1/users/1/games/iidx/SP/targets/goals/INVALID_GOAL`); - - t.equal(res.statusCode, 404); - - t.end(); - }); - - t.end(); -}); - -t.test("DELETE /api/v1/users/:userID/games/:game/:playtype/targets/goals/:goalID", async (t) => { - t.beforeEach(ResetDBState); - - const cookie = await CreateFakeAuthCookie(mockApi); - - t.test("Should delete a goal subscription if subscribed.", async (t) => { - await db.goals.insert(dupedGoal); - await db["goal-subs"].insert(dupedGoalSub); - - const res = await mockApi - .delete(`/api/v1/users/1/games/iidx/SP/targets/goals/${dupedGoalSub.goalID}`) - .set("Cookie", cookie); - - t.equal(res.statusCode, 200); - - const dbRes = await db["goal-subs"].findOne({ - userID: 1, - goalID: dupedGoal.goalID, - }); - - t.equal(dbRes, null, "Should delete the goal sub from the database."); - - t.end(); - }); - - t.test("Should reject a goal deletion if goal has parent milestones.", async (t) => { - await db.goals.insert(dupedGoal); - await db["goal-subs"].insert( - // @ts-expect-error deepmerge type error - dm(dupedGoalSub, { - parentMilestones: [TestingIIDXSPMilestone.milestoneID], - }) - ); - - const res = await mockApi - .delete(`/api/v1/users/1/games/iidx/SP/targets/goals/${dupedGoalSub.goalID}`) - .set("Cookie", cookie); - - t.equal(res.statusCode, 400); - t.equal( - res.body.description, - "This goal is part of a milestone you are subscribed to. It can only be removed by unsubscribing from the relevant milestones." - ); - - const dbRes = await db["goal-subs"].findOne({ - userID: 1, - goalID: dupedGoal.goalID, - }); - - t.not(dbRes, null, "Should NOT delete the goal sub from the database."); - - t.end(); - }); - - t.test("Should reject a goal deletion if not subscribed.", async (t) => { - await db.goals.insert(dupedGoal); - await db["goal-subs"].insert(dupedGoalSub); - - const res = await mockApi - .delete(`/api/v1/users/1/games/iidx/SP/targets/goals/INVALID_GOAL`) - .set("Cookie", cookie); - - t.equal(res.statusCode, 404); - - t.end(); - }); - - t.test("Should reject a goal deletion if not authed.", async (t) => { - await db.goals.insert(dupedGoal); - await db["goal-subs"].insert(dupedGoalSub); - - const res = await mockApi.delete( - `/api/v1/users/1/games/iidx/SP/targets/goals/${dupedGoalSub.goalID}` - ); - - t.equal(res.statusCode, 401); - - t.end(); - }); - - t.end(); -}); diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts index 438a9dfb5..2c9e34268 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts @@ -9,7 +9,7 @@ import p from "prudence"; import { RequirePermissions } from "server/middleware/auth"; import prValidate from "server/middleware/prudence-validate"; import { GoalDocument, MilestoneDocument } from "tachi-common"; -import { GetGoalForIDGuaranteed } from "utils/db"; +import { GetGoalForIDGuaranteed, GetMilestoneForIDGuaranteed } from "utils/db"; import { AssignToReqTachiData, GetUGPT } from "utils/req-tachi-data"; import { RequireAuthedAsUser } from "../../../../../middleware"; @@ -88,7 +88,7 @@ router.post( ); } - // proper valid/ation later. + // proper validation later. return p.gte(0)(self); }, }, @@ -120,8 +120,6 @@ router.post( ); } - // technically impossible, so let's ignore it. - /* istanbul ignore next */ return "Unknown charts.type."; }, }, @@ -220,19 +218,9 @@ router.get("/:goalID", GetGoalSubscription, async (req, res) => { let milestones: MilestoneDocument[] = []; if (goalSub.parentMilestones.length !== 0) { - milestones = await db.milestones.find({ - milestoneID: { $in: goalSub.parentMilestones }, - }); - - if (milestones.length !== goalSub.parentMilestones.length) { - logger.error( - `Goal '${goalSub.goalID} (uid:${goalSub.userID})' refers to parent milestones that don't exist?` - ); - return res.status(500).json({ - success: false, - description: `Something went wrong when fetching parent milestones. This has been reported.`, - }); - } + milestones = await Promise.all( + goalSub.parentMilestones.map((e) => GetMilestoneForIDGuaranteed(e)) + ); } const goal = await GetGoalForIDGuaranteed(goalSub.goalID); @@ -259,6 +247,7 @@ router.delete( RequireAuthedAsUser, GetGoalSubscription, RequirePermissions("manage_targets"), + prValidate({ goalID: "string" }), async (req, res) => { const goalID = req.params.goalID; const { user, game, playtype } = GetUGPT(req); @@ -271,7 +260,7 @@ router.delete( }); if (!goalSub) { - return res.status(404).json({ + return res.status(400).json({ success: false, description: `You aren't subscribed to this goal.`, }); diff --git a/server/src/test-utils/test-data.ts b/server/src/test-utils/test-data.ts index f6b0a5f3d..b40ff3703 100644 --- a/server/src/test-utils/test-data.ts +++ b/server/src/test-utils/test-data.ts @@ -540,7 +540,6 @@ export const uscChart: ChartDocument<"usc:Controller" | "usc:Keyboard"> = { hashSHA1: "USC_CHART_HASH", isOfficial: false, effector: "foo", - tableFolders: [], }, tierlistInfo: {}, isPrimary: true, diff --git a/server/src/utils/types.ts b/server/src/utils/types.ts index b265f5d09..ab8a5869c 100644 --- a/server/src/utils/types.ts +++ b/server/src/utils/types.ts @@ -16,7 +16,6 @@ import { GoalSubscriptionDocument, MilestoneDocument, MilestoneSetDocument, - integer, } from "tachi-common"; declare module "express-session" { @@ -76,11 +75,3 @@ export interface TachiRequestData { apiClientDoc: Omit; } - -// This is only used on tachi-server, and isn't exposed -- so shouldn't be a part -// of common. -export interface PrivateUserInfoDocument { - userID: integer; - password: string; - email: string; -} diff --git a/server/tap-snapshots/src/test-utils/single-process-tap.ts.test.cjs b/server/tap-snapshots/src/test-utils/single-process-tap.ts.test.cjs deleted file mode 100644 index e64f5ce19..000000000 --- a/server/tap-snapshots/src/test-utils/single-process-tap.ts.test.cjs +++ /dev/null @@ -1,135 +0,0 @@ - -/* IMPORTANT - * This snapshot file is auto-generated, but designed for humans. - * It should be checked into source control and tracked carefully. - * Re-generate by setting TAP_SNAPSHOT=1 and running tests. - * Make sure to inspect the output below. Do not ignore changes! - */ -'use strict' -exports[`src/test-utils/single-process-tap.ts TAP POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal Should reject invalid goals. > Invalid Goal: abs with countNum but charts.type == single 1`] = ` -Invalid countNum for goal with criteria.mode of 'abs'. Expected a whole number less than the total amount of charts available and greater than 1. -` - -exports[`src/test-utils/single-process-tap.ts TAP POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal Should reject invalid goals. > Invalid Goal: abs without countNum 1`] = ` -[criteria.countNum] Expected number to be greater than or equal to 0. (Received nothing) -` - -exports[`src/test-utils/single-process-tap.ts TAP POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal Should reject invalid goals. > Invalid Goal: charts.data array of chartIDs that don't exist 1`] = ` -Expected charts.data to match 2 charts. Instead, it only matched 1. Are all of these chartIDs valid? -` - -exports[`src/test-utils/single-process-tap.ts TAP POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal Should reject invalid goals. > Invalid Goal: charts.data array of identical chartIDs 1`] = ` -Expected charts.data to match 2 charts. Instead, it only matched 1. Are all of these chartIDs valid? -` - -exports[`src/test-utils/single-process-tap.ts TAP POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal Should reject invalid goals. > Invalid Goal: charts.data array of single chartID 1`] = ` -[charts.data] Expected an array of 2 to 5 strings in charts.data due to charts.type being 'multi'. (Received c2311194e3897ddb5745b1760d2c0141f933e683) -` - -exports[`src/test-utils/single-process-tap.ts TAP POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal Should reject invalid goals. > Invalid Goal: charts.data array when type == single 1`] = ` -[charts.data] Expected a string in charts.data due to charts.type being 'single'. (Received c2311194e3897ddb5745b1760d2c0141f933e683,c2311194e3897ddb5745b1760d2c0141f933e683) -` - -exports[`src/test-utils/single-process-tap.ts TAP POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal Should reject invalid goals. > Invalid Goal: charts.data folder refers to folder that doesn't exist 1`] = ` -A folder with id fake-folder does not exist for iidx:SP. -` - -exports[`src/test-utils/single-process-tap.ts TAP POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal Should reject invalid goals. > Invalid Goal: charts.data when type == any 1`] = ` -[charts.data] Invalid charts.data for type 'any'. Must not have any data! (Received foo) -` - -exports[`src/test-utils/single-process-tap.ts TAP POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal Should reject invalid goals. > Invalid Goal: negative percent 1`] = ` -[criteria.value] Expected number to be greater than or equal to 0. (Received -1) -` - -exports[`src/test-utils/single-process-tap.ts TAP POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal Should reject invalid goals. > Invalid Goal: nonsense charts.data 1`] = ` -A chart with id nonsense does not exist for iidx:SP. -` - -exports[`src/test-utils/single-process-tap.ts TAP POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal Should reject invalid goals. > Invalid Goal: nonsense charts.type 1`] = ` -[charts.type] Expected any of single, multi, folder, any. (Received nonsense) -` - -exports[`src/test-utils/single-process-tap.ts TAP POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal Should reject invalid goals. > Invalid Goal: nonsense mode 1`] = ` -[criteria.mode] Expected any of single, abs, proportion. (Received nonsense) -` - -exports[`src/test-utils/single-process-tap.ts TAP POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal Should reject invalid goals. > Invalid Goal: percent greater than 100 1`] = ` -Invalid value of 100.1 for percent goal. Percents must be between 0 and 100. -` - -exports[`src/test-utils/single-process-tap.ts TAP POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal Should reject invalid goals. > Invalid Goal: percent of 0 is a non-goal 1`] = ` -Invalid value of 0 for percent goal. Percents must be between 0 and 100. -` - -exports[`src/test-utils/single-process-tap.ts TAP POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal Should reject invalid goals. > Invalid Goal: proportion without countNum 1`] = ` -[criteria.countNum] Expected number to be greater than or equal to 0. (Received nothing) -` - -exports[`src/test-utils/single-process-tap.ts TAP POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal Should reject invalid goals. > Invalid Goal: single with countNum 1`] = ` -[criteria.countNum] Invalid countNum for mode 'single'. Must not have one! (Received 123) -` - -exports[`src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts TAP POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal Should reject invalid goals. > Invalid Goal: abs with countNum but charts.type == single 1`] = ` -Invalid countNum for goal with criteria.mode of 'abs'. Expected a whole number less than the total amount of charts available and greater than 1. -` - -exports[`src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts TAP POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal Should reject invalid goals. > Invalid Goal: abs without countNum 1`] = ` -[criteria.countNum] Expected number to be greater than or equal to 0. (Received nothing) -` - -exports[`src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts TAP POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal Should reject invalid goals. > Invalid Goal: charts.data array of chartIDs that don't exist 1`] = ` -Expected charts.data to match 2 charts. Instead, it only matched 1. Are all of these chartIDs valid? -` - -exports[`src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts TAP POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal Should reject invalid goals. > Invalid Goal: charts.data array of identical chartIDs 1`] = ` -Expected charts.data to match 2 charts. Instead, it only matched 1. Are all of these chartIDs valid? -` - -exports[`src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts TAP POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal Should reject invalid goals. > Invalid Goal: charts.data array of single chartID 1`] = ` -[charts.data] Expected an array of 2 to 5 strings in charts.data due to charts.type being 'multi'. (Received c2311194e3897ddb5745b1760d2c0141f933e683) -` - -exports[`src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts TAP POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal Should reject invalid goals. > Invalid Goal: charts.data array when type == single 1`] = ` -[charts.data] Expected a string in charts.data due to charts.type being 'single'. (Received c2311194e3897ddb5745b1760d2c0141f933e683,c2311194e3897ddb5745b1760d2c0141f933e683) -` - -exports[`src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts TAP POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal Should reject invalid goals. > Invalid Goal: charts.data folder refers to folder that doesn't exist 1`] = ` -A folder with id fake-folder does not exist for iidx:SP. -` - -exports[`src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts TAP POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal Should reject invalid goals. > Invalid Goal: charts.data when type == any 1`] = ` -[charts.data] Invalid charts.data for type 'any'. Must not have any data! (Received foo) -` - -exports[`src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts TAP POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal Should reject invalid goals. > Invalid Goal: negative percent 1`] = ` -[criteria.value] Expected number to be greater than or equal to 0. (Received -1) -` - -exports[`src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts TAP POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal Should reject invalid goals. > Invalid Goal: nonsense charts.data 1`] = ` -A chart with id nonsense does not exist for iidx:SP. -` - -exports[`src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts TAP POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal Should reject invalid goals. > Invalid Goal: nonsense charts.type 1`] = ` -[charts.type] Expected any of single, multi, folder, any. (Received nonsense) -` - -exports[`src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts TAP POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal Should reject invalid goals. > Invalid Goal: nonsense mode 1`] = ` -[criteria.mode] Expected any of single, abs, proportion. (Received nonsense) -` - -exports[`src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts TAP POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal Should reject invalid goals. > Invalid Goal: percent greater than 100 1`] = ` -Invalid value of 100.1 for percent goal. Percents must be between 0 and 100. -` - -exports[`src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts TAP POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal Should reject invalid goals. > Invalid Goal: percent of 0 is a non-goal 1`] = ` -Invalid value of 0 for percent goal. Percents must be between 0 and 100. -` - -exports[`src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts TAP POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal Should reject invalid goals. > Invalid Goal: proportion without countNum 1`] = ` -[criteria.countNum] Expected number to be greater than or equal to 0. (Received nothing) -` - -exports[`src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts TAP POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal Should reject invalid goals. > Invalid Goal: single with countNum 1`] = ` -[criteria.countNum] Invalid countNum for mode 'single'. Must not have one! (Received 123) -` From 7dec7329207fa9426e396458abdfe02b6fd542a7 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Wed, 13 Apr 2022 14:14:14 +0100 Subject: [PATCH 064/106] Add new add-goal tests --- .../_playtype/targets/goals/router.test.ts | 378 ++++++++++++++++++ 1 file changed, 378 insertions(+) create mode 100644 server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts new file mode 100644 index 000000000..f6c362f7c --- /dev/null +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts @@ -0,0 +1,378 @@ +import dm from "deepmerge"; +import db from "external/mongo/db"; +import { ChartDocument } from "tachi-common"; +import t from "tap"; +import { CreateFakeAuthCookie } from "test-utils/fake-auth"; +import mockApi from "test-utils/mock-api"; +import ResetDBState from "test-utils/resets"; +import { + HC511Goal, + HC511UserGoal, + Testing511SPA, + TestingIIDXSPMilestone, +} from "test-utils/test-data"; + +// @ts-expect-error Not sure why the types break here, but they do. +const dupedGoal = dm({}, HC511Goal); +// @ts-expect-error Not sure why the types break here, but they do. +const dupedGoalSub = dm({}, HC511UserGoal); + +// having two charts available is useful here for testing multi-chart goals. +const anotherFakeChart = dm(Testing511SPA, { + chartID: "another_chart", + difficulty: "HYPER", + data: { + arcChartID: null, + }, +}) as ChartDocument; + +t.test("GET /api/v1/users/:userID/games/:game/:playtype/targets/goals", (t) => { + t.beforeEach(ResetDBState); + + t.test("Should retrieve this user's goal subscriptions.", async (t) => { + await db.goals.insert(dupedGoal); + await db["goal-subs"].insert(dupedGoalSub); + + const res = await mockApi.get("/api/v1/users/1/games/iidx/SP/targets/goals"); + + t.equal(res.statusCode, 200); + + t.strictSame(res.body.body, { + goals: [HC511Goal], + goalSubs: [HC511UserGoal], + }); + + t.end(); + }); + + t.end(); +}); + +t.test("POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal", async (t) => { + t.beforeEach(ResetDBState); + + const cookie = await CreateFakeAuthCookie(mockApi); + + const baseInput = { + criteria: { + key: "scoreData.percent", + value: 1, + mode: "single", + }, + charts: { + type: "single", + data: Testing511SPA.chartID, + }, + }; + + function mkInput(caseName: string, merge: any) { + return { + caseName, + content: dm(baseInput, merge), + }; + } + + t.test("Should set valid goals.", async (t) => { + const validInput = [ + mkInput("Base Case", baseInput), + mkInput("Score:Single Case", { + criteria: { + key: "scoreData.score", + value: 1, + mode: "", + }, + }), + ]; + + t.end(); + }); + + t.test("Should reject invalid goals.", async (t) => { + await db.charts.iidx.insert(anotherFakeChart); + + const invalidInput = [ + mkInput("negative percent", { + criteria: { + value: -1, + }, + }), + mkInput("percent of 0 is a non-goal", { + criteria: { + value: 0, + }, + }), + mkInput("percent greater than 100", { + criteria: { + value: 100.1, + }, + }), + mkInput("abs without countNum", { + criteria: { + mode: "abs", + }, + }), + mkInput("proportion without countNum", { + criteria: { + mode: "proportion", + }, + }), + mkInput("single with countNum", { + criteria: { + mode: "single", + countNum: 123, + }, + }), + mkInput("nonsense mode", { + criteria: { + mode: "nonsense", + }, + }), + mkInput("abs with countNum but charts.type == single", { + criteria: { + mode: "abs", + countNum: 1, + }, + charts: { + type: "single", + }, + }), + mkInput("nonsense charts.type", { + charts: { + type: "nonsense", + }, + }), + mkInput("charts.data when type == any", { + charts: { + type: "any", + data: "foo", + }, + }), + mkInput("charts.data array when type == single", { + charts: { + type: "single", + data: [Testing511SPA.chartID, Testing511SPA.chartID], + }, + }), + mkInput("charts.data array of identical chartIDs", { + charts: { + type: "multi", + data: [Testing511SPA.chartID, Testing511SPA.chartID], + }, + }), + mkInput("charts.data array of single chartID", { + charts: { + type: "multi", + data: [Testing511SPA.chartID], + }, + }), + mkInput("charts.data array of chartIDs that don't exist", { + charts: { + type: "multi", + data: [Testing511SPA.chartID, "not_exist"], + }, + }), + mkInput("charts.data folder refers to folder that doesn't exist", { + charts: { + type: "folder", + data: "fake-folder", + }, + }), + mkInput("nonsense charts.data", { + charts: { + data: "nonsense", + }, + }), + mkInput("multi-score for iidx is illegal", { + criteria: { + key: "scoreData.score", + value: 1, + }, + charts: { + type: "multi", + data: [Testing511SPA.chartID, "another_chart"], + }, + }), + ]; + + for (const input of invalidInput) { + // eslint-disable-next-line no-await-in-loop + const res = await mockApi + .post("/api/v1/users/1/games/iidx/SP/targets/goals/add-goal") + .set("Cookie", cookie) + .send(input.content); + + t.equal(res.statusCode, 400); + + t.matchSnapshot(res.body.description, `Invalid Goal: ${input.caseName}`); + } + + t.end(); + }); + + t.end(); +}); + +t.test("GET /api/v1/users/:userID/games/:game/:playtype/targets/goals/:goalID", (t) => { + t.beforeEach(ResetDBState); + + t.test("Should retrieve this user's goal subscription.", async (t) => { + await db.goals.insert(dupedGoal); + await db["goal-subs"].insert(dupedGoalSub); + + const res = await mockApi.get( + `/api/v1/users/1/games/iidx/SP/targets/goals/${HC511Goal.goalID}` + ); + + t.equal(res.statusCode, 200); + + t.hasStrict(res.body.body, { + goal: HC511Goal, + goalSub: HC511UserGoal, + milestones: [], + user: { + id: 1, + }, + }); + + t.end(); + }); + + t.test("Should return parent milestones if goal has any.", async (t) => { + await db.goals.insert(dupedGoal); + await db["goal-subs"].insert( + // @ts-expect-error Not sure why the types break here, but they do. + dm(HC511UserGoal, { + parentMilestones: [TestingIIDXSPMilestone.milestoneID], + }) + ); + await db.milestones.insert(dm(TestingIIDXSPMilestone, {})); + + const res = await mockApi.get( + `/api/v1/users/1/games/iidx/SP/targets/goals/${HC511Goal.goalID}` + ); + + t.equal(res.statusCode, 200); + + t.hasStrict(res.body.body, { + goal: HC511Goal, + goalSub: HC511UserGoal, + milestones: [TestingIIDXSPMilestone], + user: { + id: 1, + }, + }); + + t.end(); + }); + + t.test("Should panic if goal refers to a parent milestone that doesn't exist.", async (t) => { + await db.goals.insert(dupedGoal); + await db["goal-subs"].insert( + // @ts-expect-error Not sure why the types break here, but they do. + dm(HC511UserGoal, { + parentMilestones: [TestingIIDXSPMilestone.milestoneID], + }) + ); + + const res = await mockApi.get( + `/api/v1/users/1/games/iidx/SP/targets/goals/${HC511Goal.goalID}` + ); + + t.equal(res.statusCode, 500); + + t.end(); + }); + + t.test("Should return 404 if the user is not subscribed to this goal ID.", async (t) => { + const res = await mockApi.get(`/api/v1/users/1/games/iidx/SP/targets/goals/INVALID_GOAL`); + + t.equal(res.statusCode, 404); + + t.end(); + }); + + t.end(); +}); + +t.test("DELETE /api/v1/users/:userID/games/:game/:playtype/targets/goals/:goalID", async (t) => { + t.beforeEach(ResetDBState); + + const cookie = await CreateFakeAuthCookie(mockApi); + + t.test("Should delete a goal subscription if subscribed.", async (t) => { + await db.goals.insert(dupedGoal); + await db["goal-subs"].insert(dupedGoalSub); + + const res = await mockApi + .delete(`/api/v1/users/1/games/iidx/SP/targets/goals/${dupedGoalSub.goalID}`) + .set("Cookie", cookie); + + t.equal(res.statusCode, 200); + + const dbRes = await db["goal-subs"].findOne({ + userID: 1, + goalID: dupedGoal.goalID, + }); + + t.equal(dbRes, null, "Should delete the goal sub from the database."); + + t.end(); + }); + + t.test("Should reject a goal deletion if goal has parent milestones.", async (t) => { + await db.goals.insert(dupedGoal); + await db["goal-subs"].insert( + // @ts-expect-error deepmerge type error + dm(dupedGoalSub, { + parentMilestones: [TestingIIDXSPMilestone.milestoneID], + }) + ); + + const res = await mockApi + .delete(`/api/v1/users/1/games/iidx/SP/targets/goals/${dupedGoalSub.goalID}`) + .set("Cookie", cookie); + + t.equal(res.statusCode, 400); + t.equal( + res.body.description, + "This goal is part of a milestone you are subscribed to. It can only be removed by unsubscribing from the relevant milestones." + ); + + const dbRes = await db["goal-subs"].findOne({ + userID: 1, + goalID: dupedGoal.goalID, + }); + + t.not(dbRes, null, "Should NOT delete the goal sub from the database."); + + t.end(); + }); + + t.test("Should reject a goal deletion if not subscribed.", async (t) => { + await db.goals.insert(dupedGoal); + await db["goal-subs"].insert(dupedGoalSub); + + const res = await mockApi + .delete(`/api/v1/users/1/games/iidx/SP/targets/goals/INVALID_GOAL`) + .set("Cookie", cookie); + + t.equal(res.statusCode, 404); + + t.end(); + }); + + t.test("Should reject a goal deletion if not authed.", async (t) => { + await db.goals.insert(dupedGoal); + await db["goal-subs"].insert(dupedGoalSub); + + const res = await mockApi.delete( + `/api/v1/users/1/games/iidx/SP/targets/goals/${dupedGoalSub.goalID}` + ); + + t.equal(res.statusCode, 401); + + t.end(); + }); + + t.end(); +}); From 3891255f469d644a16cd4e085c35842c3d59327b Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Wed, 13 Apr 2022 15:28:55 +0100 Subject: [PATCH 065/106] Un-revert PrivateUserInfo changes Un-Revert privateuserinfo changes --- server/src/external/mongo/db.ts | 2 +- server/src/server/router/api/v1/auth/auth.ts | 9 ++------- server/src/utils/types.ts | 9 +++++++++ 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/server/src/external/mongo/db.ts b/server/src/external/mongo/db.ts index 527db645f..92fba9e9c 100644 --- a/server/src/external/mongo/db.ts +++ b/server/src/external/mongo/db.ts @@ -24,7 +24,6 @@ import { MilestoneSetDocument, OrphanChart, PBScoreDocument, - PrivateUserInfoDocument, PublicUserDocument, RecentlyViewedFolderDocument, ScoreDocument, @@ -41,6 +40,7 @@ import { UserSettings, } from "tachi-common"; import { GetMillisecondsSince } from "utils/misc"; +import { PrivateUserInfoDocument } from "utils/types"; const logger = CreateLogCtx(__filename); let dbName = ServerConfig.MONGO_DATABASE_NAME; diff --git a/server/src/server/router/api/v1/auth/auth.ts b/server/src/server/router/api/v1/auth/auth.ts index 57492b4ae..ea721356b 100644 --- a/server/src/server/router/api/v1/auth/auth.ts +++ b/server/src/server/router/api/v1/auth/auth.ts @@ -2,15 +2,10 @@ import bcrypt from "bcryptjs"; import db from "external/mongo/db"; import CreateLogCtx from "lib/logger/logger"; import { Environment, ServerConfig } from "lib/setup/config"; -import { - integer, - PrivateUserInfoDocument, - PublicUserDocument, - UserAuthLevels, - UserSettings, -} from "tachi-common"; +import { integer, PublicUserDocument, UserAuthLevels, UserSettings } from "tachi-common"; import nodeFetch from "utils/fetch"; import { Random20Hex } from "utils/misc"; +import { PrivateUserInfoDocument } from "utils/types"; import { CreateURLWithParams } from "utils/url"; import { FormatUserDoc } from "utils/user"; diff --git a/server/src/utils/types.ts b/server/src/utils/types.ts index ab8a5869c..b265f5d09 100644 --- a/server/src/utils/types.ts +++ b/server/src/utils/types.ts @@ -16,6 +16,7 @@ import { GoalSubscriptionDocument, MilestoneDocument, MilestoneSetDocument, + integer, } from "tachi-common"; declare module "express-session" { @@ -75,3 +76,11 @@ export interface TachiRequestData { apiClientDoc: Omit; } + +// This is only used on tachi-server, and isn't exposed -- so shouldn't be a part +// of common. +export interface PrivateUserInfoDocument { + userID: integer; + password: string; + email: string; +} From 6cfda1bb1ef99100619975fb1d3547c7d3cce82e Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Wed, 13 Apr 2022 15:38:45 +0100 Subject: [PATCH 066/106] Un-Revert table-folders patch --- server/src/test-utils/test-data.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/server/src/test-utils/test-data.ts b/server/src/test-utils/test-data.ts index b40ff3703..f6b0a5f3d 100644 --- a/server/src/test-utils/test-data.ts +++ b/server/src/test-utils/test-data.ts @@ -540,6 +540,7 @@ export const uscChart: ChartDocument<"usc:Controller" | "usc:Keyboard"> = { hashSHA1: "USC_CHART_HASH", isOfficial: false, effector: "foo", + tableFolders: [], }, tierlistInfo: {}, isPrimary: true, From a696cc61fd8f53776c6cee04db558b5ff7c50af8 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Wed, 13 Apr 2022 16:25:46 +0100 Subject: [PATCH 067/106] Undo another accidental revert in goal router. --- .../users/_userID/games/_game/_playtype/targets/goals/router.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts index 2c9e34268..b65b7b365 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts @@ -247,7 +247,6 @@ router.delete( RequireAuthedAsUser, GetGoalSubscription, RequirePermissions("manage_targets"), - prValidate({ goalID: "string" }), async (req, res) => { const goalID = req.params.goalID; const { user, game, playtype } = GetUGPT(req); From de6fe2ebe3dca3d1040b6254ab004947ff6a93ea Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Wed, 13 Apr 2022 16:31:55 +0100 Subject: [PATCH 068/106] Add snapshot testing. --- server/src/external/redis/redis.ts | 10 +++++- .../_playtype/targets/goals/router.test.ts | 3 +- .../{close-connections.ts => cleanup.ts} | 7 +++- server/src/test-utils/mock-api.ts | 2 +- .../src/test-utils/single-process-snapshot.ts | 35 +++++++++++++++++++ server/src/test-utils/single-process-tap.ts | 4 +-- .../test-utils/snapshots/snapshot-data.json | 19 ++++++++++ 7 files changed, 74 insertions(+), 6 deletions(-) rename server/src/test-utils/{close-connections.ts => cleanup.ts} (73%) create mode 100644 server/src/test-utils/single-process-snapshot.ts create mode 100644 server/src/test-utils/snapshots/snapshot-data.json diff --git a/server/src/external/redis/redis.ts b/server/src/external/redis/redis.ts index 03566aed8..05555690f 100644 --- a/server/src/external/redis/redis.ts +++ b/server/src/external/redis/redis.ts @@ -35,5 +35,13 @@ RedisClient.on("connect", () => { }); export function CloseRedisConnection() { - return RedisClient.quit(); + return new Promise((resolve, reject) => + RedisClient.quit((err, reply) => { + if (err) { + reject(err); + } else { + resolve(reply); + } + }) + ); } diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts index f6c362f7c..3c8c1e561 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts @@ -5,6 +5,7 @@ import t from "tap"; import { CreateFakeAuthCookie } from "test-utils/fake-auth"; import mockApi from "test-utils/mock-api"; import ResetDBState from "test-utils/resets"; +import { TestSnapshot } from "test-utils/single-process-snapshot"; import { HC511Goal, HC511UserGoal, @@ -203,7 +204,7 @@ t.test("POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal", asyn t.equal(res.statusCode, 400); - t.matchSnapshot(res.body.description, `Invalid Goal: ${input.caseName}`); + TestSnapshot(t, res.body.description, `Invalid Goal: ${input.caseName}`); } t.end(); diff --git a/server/src/test-utils/close-connections.ts b/server/src/test-utils/cleanup.ts similarity index 73% rename from server/src/test-utils/close-connections.ts rename to server/src/test-utils/cleanup.ts index b6d8c1e52..d3fba002f 100644 --- a/server/src/test-utils/close-connections.ts +++ b/server/src/test-utils/cleanup.ts @@ -3,8 +3,13 @@ import { CloseRedisPubSub } from "external/redis/redis-IPC"; import { CloseRedisConnection } from "external/redis/redis"; import { CloseServerConnection } from "./mock-api"; import { CloseScoreImportQueue } from "lib/score-import/worker/queue"; +import { WriteSnapshotData } from "./single-process-snapshot"; + +export async function CleanUpAfterTests() { + if (process.env.TAP_SNAPSHOT) { + WriteSnapshotData(); + } -export async function CloseAllConnections() { await CloseMongoConnection(); await CloseServerConnection(); await CloseRedisConnection(); diff --git a/server/src/test-utils/mock-api.ts b/server/src/test-utils/mock-api.ts index c24e4f62f..b5a9593f3 100644 --- a/server/src/test-utils/mock-api.ts +++ b/server/src/test-utils/mock-api.ts @@ -11,7 +11,7 @@ logger.verbose("Connecting to Supertest..."); const mockApi = supertest(connection); export function CloseServerConnection() { - connection.close(); + return connection.close(); } export default mockApi; diff --git a/server/src/test-utils/single-process-snapshot.ts b/server/src/test-utils/single-process-snapshot.ts new file mode 100644 index 000000000..d257a5f35 --- /dev/null +++ b/server/src/test-utils/single-process-snapshot.ts @@ -0,0 +1,35 @@ +import fs from "fs"; +import path from "path"; + +const SNAP_PATH = path.join(__dirname, "./snapshots/snapshot-data.json"); + +type Snapshots = Record; + +function ReadSnapshotData() { + let snapshots: Snapshots = {}; + + if (fs.existsSync(SNAP_PATH)) { + snapshots = JSON.parse(fs.readFileSync(SNAP_PATH, "utf-8")); + } + + return snapshots; +} + +export function WriteSnapshotData() { + fs.writeFileSync(SNAP_PATH, JSON.stringify(snapshotData, null, "\t")); +} + +const snapshotData = ReadSnapshotData(); + +export function TestSnapshot(t: Tap.Test, value: string, testName: string) { + if (process.env.TAP_SNAPSHOT) { + snapshotData[testName] = value; + WriteSnapshotData(); + } else { + if (!snapshotData[testName]) { + return t.fail(`No snapshot exists for ${testName}. Have you ran pnpm snap?`); + } + + t.equal(value, snapshotData[testName], `Snapshot: ${testName}`); + } +} diff --git a/server/src/test-utils/single-process-tap.ts b/server/src/test-utils/single-process-tap.ts index e8f04fcfb..349e134bb 100644 --- a/server/src/test-utils/single-process-tap.ts +++ b/server/src/test-utils/single-process-tap.ts @@ -2,7 +2,7 @@ import "external/mongo/db"; import glob from "glob"; import path from "path"; import t from "tap"; -import { CloseAllConnections } from "./close-connections"; +import { CleanUpAfterTests } from "./cleanup"; const files = glob.sync(path.join(__dirname, "../../", "**/*.test.ts")); @@ -12,4 +12,4 @@ for (const file of files) { require(file); } -t.teardown(CloseAllConnections); +t.teardown(CleanUpAfterTests); diff --git a/server/src/test-utils/snapshots/snapshot-data.json b/server/src/test-utils/snapshots/snapshot-data.json new file mode 100644 index 000000000..ab2122bf6 --- /dev/null +++ b/server/src/test-utils/snapshots/snapshot-data.json @@ -0,0 +1,19 @@ +{ + "Invalid Goal: negative percent": "[criteria.value] Expected number to be greater than or equal to 0. (Received -1)", + "Invalid Goal: percent of 0 is a non-goal": "Invalid value of 0 for percent goal. Percents must be between 0 and 100.", + "Invalid Goal: percent greater than 100": "Invalid value of 100.1 for percent goal. Percents must be between 0 and 100.", + "Invalid Goal: abs without countNum": "[criteria.countNum] Expected number to be greater than or equal to 0. (Received nothing)", + "Invalid Goal: proportion without countNum": "[criteria.countNum] Expected number to be greater than or equal to 0. (Received nothing)", + "Invalid Goal: single with countNum": "[criteria.countNum] Invalid countNum for mode 'single'. Must not have one! (Received 123)", + "Invalid Goal: nonsense mode": "[criteria.mode] Expected any of single, abs, proportion. (Received nonsense)", + "Invalid Goal: abs with countNum but charts.type == single": "Invalid countNum for goal with criteria.mode of 'abs'. Expected a whole number less than the total amount of charts available and greater than 1.", + "Invalid Goal: nonsense charts.type": "[charts.type] Expected any of single, multi, folder, any. (Received nonsense)", + "Invalid Goal: charts.data when type == any": "[charts.data] Invalid charts.data for type 'any'. Must not have any data! (Received foo)", + "Invalid Goal: charts.data array when type == single": "[charts.data] Expected a string in charts.data due to charts.type being 'single'. (Received c2311194e3897ddb5745b1760d2c0141f933e683,c2311194e3897ddb5745b1760d2c0141f933e683)", + "Invalid Goal: charts.data array of identical chartIDs": "Expected charts.data to match 2 charts. Instead, it only matched 1. Are all of these chartIDs valid?", + "Invalid Goal: charts.data array of single chartID": "[charts.data] Expected an array of 2 to 5 strings in charts.data due to charts.type being 'multi'. (Received c2311194e3897ddb5745b1760d2c0141f933e683)", + "Invalid Goal: charts.data array of chartIDs that don't exist": "Expected charts.data to match 2 charts. Instead, it only matched 1. Are all of these chartIDs valid?", + "Invalid Goal: charts.data folder refers to folder that doesn't exist": "A folder with id fake-folder does not exist for iidx:SP.", + "Invalid Goal: nonsense charts.data": "A chart with id nonsense does not exist for iidx:SP.", + "Invalid Goal: multi-score for iidx is illegal": "Invalid key for iidx with multiple charts. Creating score goals on multiple charts where score is relative to notecount is a terrible idea, and has been disabled." +} \ No newline at end of file From 567ab7ce5d8697482e7dcb007c6003e164f031f8 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Wed, 13 Apr 2022 20:50:05 +0100 Subject: [PATCH 069/106] Full Add-Goal Tests --- server/src/lib/targets/goal-utils.ts | 3 +- .../_playtype/targets/goals/router.test.ts | 310 ++++++++++++++++-- .../_game/_playtype/targets/goals/router.ts | 3 + .../test-utils/mock-db/personal-bests.json | 39 ++- .../test-utils/snapshots/snapshot-data.json | 16 +- 5 files changed, 345 insertions(+), 26 deletions(-) diff --git a/server/src/lib/targets/goal-utils.ts b/server/src/lib/targets/goal-utils.ts index fa4823e7f..f19a1e5e4 100644 --- a/server/src/lib/targets/goal-utils.ts +++ b/server/src/lib/targets/goal-utils.ts @@ -159,7 +159,8 @@ export async function ValidateGoalChartsAndCriteria( } chartCount = multiCharts.length; - } else if (charts.type === "any") { + } else { + // (charts.type === "any") chartCount = await db.charts[game].count({ playtype }); } diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts index 3c8c1e561..e4c28471e 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts @@ -1,6 +1,7 @@ +/* eslint-disable no-await-in-loop */ import dm from "deepmerge"; import db from "external/mongo/db"; -import { ChartDocument } from "tachi-common"; +import { ChartDocument, IIDX_GRADES, IIDX_LAMPS } from "tachi-common"; import t from "tap"; import { CreateFakeAuthCookie } from "test-utils/fake-auth"; import mockApi from "test-utils/mock-api"; @@ -10,6 +11,7 @@ import { HC511Goal, HC511UserGoal, Testing511SPA, + TestingIIDXFolderSP10, TestingIIDXSPMilestone, } from "test-utils/test-data"; @@ -57,7 +59,7 @@ t.test("POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal", asyn const baseInput = { criteria: { key: "scoreData.percent", - value: 1, + value: 80, mode: "single", }, charts: { @@ -67,30 +69,181 @@ t.test("POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal", asyn }; function mkInput(caseName: string, merge: any) { - return { + const input = { caseName, - content: dm(baseInput, merge), + content: dm(baseInput, merge) as any, }; + + // if we've set this prop to undefined, we want to delete it, actually. + if (input.content.charts.data === undefined) { + delete input.content.charts.data; + } + + return input; } + t.beforeEach(() => db.charts.iidx.insert(anotherFakeChart)); + // @ts-expect-error weird type issues + t.beforeEach(() => db.folders.insert(dm(TestingIIDXFolderSP10, {}))); + + const multiCharts = { + type: "multi", + data: [Testing511SPA.chartID, "another_chart"], + }; + + const anyCharts = { + type: "any", + data: undefined, + }; + + const folderCharts = { + type: "folder", + data: TestingIIDXFolderSP10.folderID, + }; + t.test("Should set valid goals.", async (t) => { const validInput = [ - mkInput("Base Case", baseInput), + mkInput("Percent:Single Case", {}), mkInput("Score:Single Case", { criteria: { key: "scoreData.score", - value: 1, - mode: "", + value: 1000, }, }), + mkInput("LampIndex:Single Case", { + criteria: { + key: "scoreData.lampIndex", + value: IIDX_LAMPS.EX_HARD_CLEAR, + }, + }), + mkInput("GradeIndex:Single Case", { + criteria: { + key: "scoreData.gradeIndex", + value: IIDX_GRADES.AAA, + }, + }), + mkInput("Percent:Multi Case", { + charts: multiCharts, + }), + // NOT LEGAL FOR IIDX, due to being nonsense! NEEDS SPECIFIC TESTING. + // mkInput("Score:Multi Case", { + // criteria: { + // key: "scoreData.score", + // value: 1, + // }, + // charts: multiCharts, + // }), + mkInput("LampIndex:Multi Case", { + criteria: { + key: "scoreData.lampIndex", + value: IIDX_LAMPS.HARD_CLEAR, + }, + charts: multiCharts, + }), + mkInput("GradeIndex:Multi Case", { + criteria: { + key: "scoreData.gradeIndex", + value: IIDX_GRADES.AAA, + }, + charts: multiCharts, + }), + mkInput("Percent:Any Case", { + charts: anyCharts, + }), + // also illegal in iidx, bms and pms. + // mkInput("Score:Any Case", { + // criteria: { + // key: "scoreData.score", + // value: 1000, + // }, + // charts: anyCharts, + // }), + mkInput("LampIndex:Any Case", { + criteria: { + key: "scoreData.lampIndex", + value: IIDX_LAMPS.HARD_CLEAR, + }, + charts: anyCharts, + }), + mkInput("GradeIndex:Any Case", { + criteria: { + key: "scoreData.gradeIndex", + value: IIDX_GRADES.AAA, + }, + charts: anyCharts, + }), + mkInput("Percent:Folder Case", { + charts: folderCharts, + }), + // also illegal in iidx, bms and pms. + // mkInput("Score:Folder Case", { + // criteria: { + // key: "scoreData.score", + // value: 1000, + // }, + // charts: folderCharts, + // }), + mkInput("LampIndex:Folder Case", { + criteria: { + key: "scoreData.lampIndex", + value: IIDX_LAMPS.HARD_CLEAR, + }, + charts: folderCharts, + }), + mkInput("GradeIndex:Folder Case", { + criteria: { + key: "scoreData.gradeIndex", + value: IIDX_GRADES.AAA, + }, + charts: folderCharts, + }), ]; + for (const input of validInput) { + await t.test(`Valid Goal: ${input.caseName}`, async (t) => { + const res = await mockApi + .post("/api/v1/users/1/games/iidx/SP/targets/goals/add-goal") + .set("Cookie", cookie) + .send(input.content); + + t.equal( + // this looks stupid, but results in better errmsgs. + res.statusCode === 200 ? 200 : res.body.description, + 200, + "Should return 200" + ); + + // don't bother doing circular db checks if this is good. + if (res.statusCode !== 200) { + return; + } + + const existsInDb = await db.goals.findOne({ + charts: input.content.charts, + criteria: input.content.criteria, + }); + + t.not(existsInDb, null, "A new goal should be added to the database."); + + const subscribed = await db["goal-subs"].findOne({ + userID: 1, + goalID: existsInDb?.goalID ?? "edge_case", + }); + + t.not( + subscribed, + null, + `Requesting user 1 should be subscribed to their new goal.` + ); + + t.end(); + }); + } + t.end(); }); t.test("Should reject invalid goals.", async (t) => { - await db.charts.iidx.insert(anotherFakeChart); - const invalidInput = [ mkInput("negative percent", { criteria: { @@ -107,15 +260,47 @@ t.test("POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal", asyn value: 100.1, }, }), + mkInput("too big grade", { + criteria: { + key: "scoreData.gradeIndex", + value: IIDX_GRADES.MAX + 1, + }, + }), + mkInput("invalid grade", { + criteria: { + key: "scoreData.gradeIndex", + value: 0.5, + }, + }), + mkInput("too big lamp", { + criteria: { + key: "scoreData.lampIndex", + value: IIDX_LAMPS.FULL_COMBO + 1, + }, + }), + mkInput("invalid lamp", { + criteria: { + key: "scoreData.lampIndex", + value: 0.5, + }, + }), + mkInput("too big score", { + criteria: { + key: "scoreData.gradeIndex", + value: 9_000, + }, + }), mkInput("abs without countNum", { criteria: { mode: "abs", }, + charts: multiCharts, }), mkInput("proportion without countNum", { criteria: { mode: "proportion", }, + charts: multiCharts, }), mkInput("single with countNum", { criteria: { @@ -123,6 +308,27 @@ t.test("POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal", asyn countNum: 123, }, }), + mkInput("proportion with negative countNum", { + criteria: { + mode: "proportion", + countNum: -0.5, + }, + charts: multiCharts, + }), + mkInput("proportion with greater than 1 countNum", { + criteria: { + mode: "proportion", + countNum: 1.1, + }, + charts: multiCharts, + }), + mkInput("proportion which results in countNum of 0", { + criteria: { + mode: "proportion", + countNum: 0.05, + }, + charts: multiCharts, + }), mkInput("nonsense mode", { criteria: { mode: "nonsense", @@ -151,7 +357,7 @@ t.test("POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal", asyn mkInput("charts.data array when type == single", { charts: { type: "single", - data: [Testing511SPA.chartID, Testing511SPA.chartID], + data: multiCharts.data, }, }), mkInput("charts.data array of identical chartIDs", { @@ -166,7 +372,7 @@ t.test("POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal", asyn data: [Testing511SPA.chartID], }, }), - mkInput("charts.data array of chartIDs that don't exist", { + mkInput("charts.data array of chartIDs where some don't exist", { charts: { type: "multi", data: [Testing511SPA.chartID, "not_exist"], @@ -186,30 +392,90 @@ t.test("POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal", asyn mkInput("multi-score for iidx is illegal", { criteria: { key: "scoreData.score", - value: 1, + value: 1000, }, - charts: { - type: "multi", - data: [Testing511SPA.chartID, "another_chart"], + charts: multiCharts, + }), + mkInput("folder-score for iidx is illegal", { + criteria: { + key: "scoreData.score", + value: 1000, + }, + charts: folderCharts, + }), + mkInput("any-score for iidx is illegal", { + criteria: { + key: "scoreData.score", + value: 1000, + }, + charts: anyCharts, + }), + mkInput("charts single but criteria not.", { + criteria: { + mode: "abs", + countNum: 2, }, }), ]; for (const input of invalidInput) { - // eslint-disable-next-line no-await-in-loop - const res = await mockApi - .post("/api/v1/users/1/games/iidx/SP/targets/goals/add-goal") - .set("Cookie", cookie) - .send(input.content); + await t.test(`Invalid Goal: ${input.caseName}`, async (t) => { + const res = await mockApi + .post("/api/v1/users/1/games/iidx/SP/targets/goals/add-goal") + .set("Cookie", cookie) + .send(input.content); - t.equal(res.statusCode, 400); + t.equal(res.statusCode, 400); - TestSnapshot(t, res.body.description, `Invalid Goal: ${input.caseName}`); + TestSnapshot(t, res.body.description, `Invalid Goal: ${input.caseName}`); + + t.end(); + }); } t.end(); }); + t.test("Should reject if user tries to subscribe to already subscribed goal.", async (t) => { + const res = await mockApi + .post("/api/v1/users/1/games/iidx/SP/targets/goals/add-goal") + .set("Cookie", cookie) + .send(baseInput); + + t.equal(res.statusCode, 200, "Should allow a subscription the first time."); + + const res2 = await mockApi + .post("/api/v1/users/1/games/iidx/SP/targets/goals/add-goal") + .set("Cookie", cookie) + .send(baseInput); + + t.equal(res2.statusCode, 409, "Should fail the second time."); + + t.end(); + }); + + t.test("Should reject if user tries to subscribe to immediately achieved goal.", async (t) => { + const res = await mockApi + .post("/api/v1/users/1/games/iidx/SP/targets/goals/add-goal") + .set("Cookie", cookie) + .send( + dm(baseInput, { + criteria: { + key: "scoreData.score", + value: 1, + }, + }) + ); + + t.equal( + res.statusCode, + 400, + "Should disallow goal subscriptions that would be instantly achieved." + ); + + t.end(); + }); + t.end(); }); diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts index b65b7b365..228db5b01 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts @@ -114,12 +114,15 @@ router.post( "Expected an array of 2 to 5 strings in charts.data due to charts.type being 'multi'." ); } else if (parent.type === "folder") { + /* istanbul ignore previous */ return ( typeof self === "string" || "Expected a string in charts.data due to charts.type being 'folder'." ); } + // impossible to reach, so doesn't count for coverage. + /* istanbul ignore next */ return "Unknown charts.type."; }, }, diff --git a/server/src/test-utils/mock-db/personal-bests.json b/server/src/test-utils/mock-db/personal-bests.json index 0637a088a..b9fab093c 100644 --- a/server/src/test-utils/mock-db/personal-bests.json +++ b/server/src/test-utils/mock-db/personal-bests.json @@ -1 +1,38 @@ -[] \ No newline at end of file +[ + { + "chartID": "c2311194e3897ddb5745b1760d2c0141f933e683", + "userID": 1, + "calculatedData": { + "ktLampRating": 0, + "BPI": 10.1 + }, + "composedFrom": { + "scorePB": "TESTING_SCORE_ID", + "lampPB": "TESTING_SCORE_ID" + }, + "highlight": false, + "isPrimary": true, + "scoreData": { + "score": 786, + "lamp": "HARD CLEAR", + "percent": 50, + "grade": "C", + "esd": 30, + "gradeIndex": 3, + "lampIndex": 4, + "judgements": { + "pgreat": 50, + "great": 10 + }, + "hitMeta": {} + }, + "rankingData": { + "rank": 1, + "outOf": 2 + }, + "songID": 1, + "game": "iidx", + "playtype": "SP", + "timeAchieved": 10000 + } +] \ No newline at end of file diff --git a/server/src/test-utils/snapshots/snapshot-data.json b/server/src/test-utils/snapshots/snapshot-data.json index ab2122bf6..ba173504b 100644 --- a/server/src/test-utils/snapshots/snapshot-data.json +++ b/server/src/test-utils/snapshots/snapshot-data.json @@ -9,11 +9,23 @@ "Invalid Goal: abs with countNum but charts.type == single": "Invalid countNum for goal with criteria.mode of 'abs'. Expected a whole number less than the total amount of charts available and greater than 1.", "Invalid Goal: nonsense charts.type": "[charts.type] Expected any of single, multi, folder, any. (Received nonsense)", "Invalid Goal: charts.data when type == any": "[charts.data] Invalid charts.data for type 'any'. Must not have any data! (Received foo)", - "Invalid Goal: charts.data array when type == single": "[charts.data] Expected a string in charts.data due to charts.type being 'single'. (Received c2311194e3897ddb5745b1760d2c0141f933e683,c2311194e3897ddb5745b1760d2c0141f933e683)", + "Invalid Goal: charts.data array when type == single": "[charts.data] Expected a string in charts.data due to charts.type being 'single'. (Received c2311194e3897ddb5745b1760d2c0141f933e683,another_chart)", "Invalid Goal: charts.data array of identical chartIDs": "Expected charts.data to match 2 charts. Instead, it only matched 1. Are all of these chartIDs valid?", "Invalid Goal: charts.data array of single chartID": "[charts.data] Expected an array of 2 to 5 strings in charts.data due to charts.type being 'multi'. (Received c2311194e3897ddb5745b1760d2c0141f933e683)", "Invalid Goal: charts.data array of chartIDs that don't exist": "Expected charts.data to match 2 charts. Instead, it only matched 1. Are all of these chartIDs valid?", "Invalid Goal: charts.data folder refers to folder that doesn't exist": "A folder with id fake-folder does not exist for iidx:SP.", "Invalid Goal: nonsense charts.data": "A chart with id nonsense does not exist for iidx:SP.", - "Invalid Goal: multi-score for iidx is illegal": "Invalid key for iidx with multiple charts. Creating score goals on multiple charts where score is relative to notecount is a terrible idea, and has been disabled." + "Invalid Goal: multi-score for iidx is illegal": "Invalid key for iidx with multiple charts. Creating score goals on multiple charts where score is relative to notecount is a terrible idea, and has been disabled.", + "Invalid Goal: too big grade": "Invalid value of 10 for grade goal. No such grade exists at that index.", + "Invalid Goal: invalid grade": "Invalid value of 0.5 for grade goal. No such grade exists at that index.", + "Invalid Goal: too big lamp": "Invalid value of 8 for lamp goal. No such lamp exists at that index.", + "Invalid Goal: invalid lamp": "Invalid value of 0.5 for lamp goal. No such lamp exists at that index.", + "Invalid Goal: too big score": "Invalid value of 9000 for grade goal. No such grade exists at that index.", + "Invalid Goal: proportion with negative countNum": "[criteria.countNum] Expected number to be greater than or equal to 0. (Received -0.5)", + "Invalid Goal: proportion with greater than 1 countNum": "Invalid countNum for goal with criteria.mode of 'proportion'. Expected a decimal in (0, 1]", + "Invalid Goal: proportion which results in countNum of 0": "countNum is too small for goal. Would result in requiring 0 charts to achieve the goal.", + "Invalid Goal: charts.data array of chartIDs where some don't exist": "Expected charts.data to match 2 charts. Instead, it only matched 1. Are all of these chartIDs valid?", + "Invalid Goal: folder-score for iidx is illegal": "Invalid key for iidx with multiple charts. Creating score goals on multiple charts where score is relative to notecount is a terrible idea, and has been disabled.", + "Invalid Goal: any-score for iidx is illegal": "Invalid key for iidx with multiple charts. Creating score goals on multiple charts where score is relative to notecount is a terrible idea, and has been disabled.", + "Invalid Goal: charts single but criteria not.": "Invalid countNum for goal with criteria.mode of 'abs'. Expected a whole number less than the total amount of charts available and greater than 1." } \ No newline at end of file From e68ee090a2bc9e3f9b9a14e109b2626ca395b64d Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Wed, 13 Apr 2022 21:38:05 +0100 Subject: [PATCH 070/106] Remove useless use of 'circular' --- .../_userID/games/_game/_playtype/targets/goals/router.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts index e4c28471e..2627630e6 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts @@ -213,7 +213,7 @@ t.test("POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal", asyn "Should return 200" ); - // don't bother doing circular db checks if this is good. + // don't bother doing db checks if this is good. if (res.statusCode !== 200) { return; } From 6b4a50ceb5a56635a809e463b1ff332324561e7f Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Wed, 13 Apr 2022 21:53:24 +0100 Subject: [PATCH 071/106] More exhaustive goal submission tests --- .../_playtype/targets/goals/router.test.ts | 130 ++++++++++++++++++ 1 file changed, 130 insertions(+) diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts index 2627630e6..674d5ccfc 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts @@ -101,6 +101,16 @@ t.test("POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal", asyn data: TestingIIDXFolderSP10.folderID, }; + const absModeCriteria = { + mode: "abs", + countNum: 2, + }; + + const proportionModeCriteria = { + mode: "proportion", + countNum: 1, + }; + t.test("Should set valid goals.", async (t) => { const validInput = [ mkInput("Percent:Single Case", {}), @@ -125,6 +135,14 @@ t.test("POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal", asyn mkInput("Percent:Multi Case", { charts: multiCharts, }), + mkInput("Percent:Multi:Abs Case", { + criteria: absModeCriteria, + charts: multiCharts, + }), + mkInput("Percent:Multi:Abs Case", { + criteria: proportionModeCriteria, + charts: multiCharts, + }), // NOT LEGAL FOR IIDX, due to being nonsense! NEEDS SPECIFIC TESTING. // mkInput("Score:Multi Case", { // criteria: { @@ -140,6 +158,22 @@ t.test("POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal", asyn }, charts: multiCharts, }), + mkInput("LampIndex:Multi:Abs Case", { + criteria: { + key: "scoreData.lampIndex", + value: IIDX_LAMPS.HARD_CLEAR, + ...absModeCriteria, + }, + charts: multiCharts, + }), + mkInput("LampIndex:Multi:Proportion Case", { + criteria: { + key: "scoreData.lampIndex", + value: IIDX_LAMPS.HARD_CLEAR, + ...proportionModeCriteria, + }, + charts: multiCharts, + }), mkInput("GradeIndex:Multi Case", { criteria: { key: "scoreData.gradeIndex", @@ -147,9 +181,33 @@ t.test("POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal", asyn }, charts: multiCharts, }), + mkInput("GradeIndex:Multi:Abs Case", { + criteria: { + key: "scoreData.gradeIndex", + value: IIDX_GRADES.AAA, + ...absModeCriteria, + }, + charts: multiCharts, + }), + mkInput("GradeIndex:Multi:Proportion Case", { + criteria: { + key: "scoreData.gradeIndex", + value: IIDX_GRADES.AAA, + ...proportionModeCriteria, + }, + charts: multiCharts, + }), mkInput("Percent:Any Case", { charts: anyCharts, }), + mkInput("Percent:Any:Abs Case", { + criteria: absModeCriteria, + charts: anyCharts, + }), + mkInput("Percent:Any:Proportion Case", { + criteria: proportionModeCriteria, + charts: anyCharts, + }), // also illegal in iidx, bms and pms. // mkInput("Score:Any Case", { // criteria: { @@ -165,6 +223,22 @@ t.test("POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal", asyn }, charts: anyCharts, }), + mkInput("LampIndex:Any:Abs Case", { + criteria: { + key: "scoreData.lampIndex", + value: IIDX_LAMPS.HARD_CLEAR, + ...absModeCriteria, + }, + charts: anyCharts, + }), + mkInput("LampIndex:Any:Proportion Case", { + criteria: { + key: "scoreData.lampIndex", + value: IIDX_LAMPS.HARD_CLEAR, + ...proportionModeCriteria, + }, + charts: anyCharts, + }), mkInput("GradeIndex:Any Case", { criteria: { key: "scoreData.gradeIndex", @@ -172,9 +246,33 @@ t.test("POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal", asyn }, charts: anyCharts, }), + mkInput("GradeIndex:Any:Abs Case", { + criteria: { + key: "scoreData.gradeIndex", + value: IIDX_GRADES.AAA, + ...absModeCriteria, + }, + charts: anyCharts, + }), + mkInput("GradeIndex:Any:Proportion Case", { + criteria: { + key: "scoreData.gradeIndex", + value: IIDX_GRADES.AAA, + ...proportionModeCriteria, + }, + charts: anyCharts, + }), mkInput("Percent:Folder Case", { charts: folderCharts, }), + mkInput("Percent:Folder:Abs Case", { + criteria: absModeCriteria, + charts: folderCharts, + }), + mkInput("Percent:Folder:Proportion Case", { + criteria: proportionModeCriteria, + charts: folderCharts, + }), // also illegal in iidx, bms and pms. // mkInput("Score:Folder Case", { // criteria: { @@ -190,6 +288,22 @@ t.test("POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal", asyn }, charts: folderCharts, }), + mkInput("LampIndex:Folder:Abs Case", { + criteria: { + key: "scoreData.lampIndex", + value: IIDX_LAMPS.HARD_CLEAR, + ...absModeCriteria, + }, + charts: folderCharts, + }), + mkInput("LampIndex:Folder:Proportion Case", { + criteria: { + key: "scoreData.lampIndex", + value: IIDX_LAMPS.HARD_CLEAR, + ...proportionModeCriteria, + }, + charts: folderCharts, + }), mkInput("GradeIndex:Folder Case", { criteria: { key: "scoreData.gradeIndex", @@ -197,6 +311,22 @@ t.test("POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal", asyn }, charts: folderCharts, }), + mkInput("GradeIndex:Folder:Abs Case", { + criteria: { + key: "scoreData.gradeIndex", + value: IIDX_GRADES.AAA, + ...absModeCriteria, + }, + charts: folderCharts, + }), + mkInput("GradeIndex:Folder:Proportion Case", { + criteria: { + key: "scoreData.gradeIndex", + value: IIDX_GRADES.AAA, + ...proportionModeCriteria, + }, + charts: folderCharts, + }), ]; for (const input of validInput) { From bec7e14aba9b09bf06467a353fa9f83f738dcb80 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Wed, 13 Apr 2022 21:53:48 +0100 Subject: [PATCH 072/106] Update "abs" to "absolute" for goal criteria. --- server/package.json | 2 +- server/pnpm-lock.yaml | 8 ++++---- server/src/external/mongo/schemas.ts | 4 ++-- server/src/lib/targets/goal-utils.ts | 4 ++-- server/src/lib/targets/goals.ts | 4 ++-- .../games/_game/_playtype/targets/goals/router.test.ts | 8 ++++---- .../_userID/games/_game/_playtype/targets/goals/router.ts | 4 ++-- 7 files changed, 17 insertions(+), 17 deletions(-) diff --git a/server/package.json b/server/package.json index 63acc6978..51869c0cd 100644 --- a/server/package.json +++ b/server/package.json @@ -84,7 +84,7 @@ "rimraf": "3.0.2", "safe-json-stringify": "1.2.0", "seq-logging": "1.1.1", - "tachi-common": "0.7.13", + "tachi-common": "0.7.14", "ts-node": "10.4.0", "tsconfig-paths": "3.12.0", "typescript": "4.5.5", diff --git a/server/pnpm-lock.yaml b/server/pnpm-lock.yaml index 46faf9cec..b451adbaa 100644 --- a/server/pnpm-lock.yaml +++ b/server/pnpm-lock.yaml @@ -65,7 +65,7 @@ specifiers: safe-json-stringify: 1.2.0 seq-logging: 1.1.1 supertest: 6.2.2 - tachi-common: 0.7.13 + tachi-common: 0.7.14 tap: 15.1.6 ts-node: 10.4.0 tsconfig-paths: 3.12.0 @@ -112,7 +112,7 @@ dependencies: rimraf: 3.0.2 safe-json-stringify: 1.2.0 seq-logging: 1.1.1 - tachi-common: 0.7.13 + tachi-common: 0.7.14 ts-node: 10.4.0_2615db9039ce432b4abf2fc39ef336ec tsconfig-paths: 3.12.0 typescript: 4.5.5 @@ -5363,8 +5363,8 @@ packages: engines: {node: '>= 0.4'} dev: true - /tachi-common/0.7.13: - resolution: {integrity: sha512-r0pB7FO6QZ7j16jR0YgMzojbGxo03uS+mDGrD59dpL0tbN2V7fR6vLx+mmWELFBqrDIjcOJu2AOTkz7fPYL3fA==} + /tachi-common/0.7.14: + resolution: {integrity: sha512-RZfmSvCrITGm3jClZZc4JJAJwo6lUU+Wkq2+PAQh7gHdlR5OjVlTNDQr67F3F2KTagz6Sw4y1BDKw78j2aPl8Q==} dependencies: '@types/mongodb': 3.6.18 dev: false diff --git a/server/src/external/mongo/schemas.ts b/server/src/external/mongo/schemas.ts index a0c21fac2..1cf4ce981 100644 --- a/server/src/external/mongo/schemas.ts +++ b/server/src/external/mongo/schemas.ts @@ -634,7 +634,7 @@ export const DatabaseSchemas: Record< value: "number", }, { - mode: p.isIn("abs", "proportion"), + mode: p.isIn("absolute", "proportion"), countNum: p.isPositive, key: p.isIn( "scoreData.percent", @@ -683,7 +683,7 @@ export const DatabaseSchemas: Record< type: "all", }, { - type: p.isIn("abs", "proportion"), + type: p.isIn("absolute", "proportion"), value: p.isPositive, } ), diff --git a/server/src/lib/targets/goal-utils.ts b/server/src/lib/targets/goal-utils.ts index f19a1e5e4..37a1df25b 100644 --- a/server/src/lib/targets/goal-utils.ts +++ b/server/src/lib/targets/goal-utils.ts @@ -35,7 +35,7 @@ export async function CreateGoalTitle( return `${formattedCriteria} any chart in ${datasetName}`; } // eslint-disable-next-line no-fallthrough - case "abs": + case "absolute": switch (charts.type) { case "any": return `${formattedCriteria} ${criteria.countNum} charts`; @@ -178,7 +178,7 @@ export async function ValidateGoalChartsAndCriteria( ); } } else if ( - criteria.mode === "abs" && + criteria.mode === "absolute" && (criteria.countNum > chartCount || !Number.isInteger(criteria.countNum) || criteria.countNum < 2) diff --git a/server/src/lib/targets/goals.ts b/server/src/lib/targets/goals.ts index a72b04778..a6e1386e4 100644 --- a/server/src/lib/targets/goals.ts +++ b/server/src/lib/targets/goals.ts @@ -148,11 +148,11 @@ export async function EvaluateGoalForUser( nextBestScore ), }; - } else if (goal.criteria.mode === "abs" || goal.criteria.mode === "proportion") { + } else if (goal.criteria.mode === "absolute" || goal.criteria.mode === "proportion") { let count; // abs -> Absolute mode, such as clear 10 charts. - if (goal.criteria.mode === "abs") { + if (goal.criteria.mode === "absolute") { count = goal.criteria.countNum; } else { // proportion -> Proportional mode, the value diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts index 674d5ccfc..f716cd6b7 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts @@ -102,7 +102,7 @@ t.test("POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal", asyn }; const absModeCriteria = { - mode: "abs", + mode: "absolute", countNum: 2, }; @@ -422,7 +422,7 @@ t.test("POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal", asyn }), mkInput("abs without countNum", { criteria: { - mode: "abs", + mode: "absolute", }, charts: multiCharts, }), @@ -466,7 +466,7 @@ t.test("POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal", asyn }), mkInput("abs with countNum but charts.type == single", { criteria: { - mode: "abs", + mode: "absolute", countNum: 1, }, charts: { @@ -542,7 +542,7 @@ t.test("POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal", asyn }), mkInput("charts single but criteria not.", { criteria: { - mode: "abs", + mode: "absolute", countNum: 2, }, }), diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts index 228db5b01..7d41c74b4 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts @@ -53,7 +53,7 @@ type GoalCreationBody = Pick; * * @param criteria.key - The key for the goal to be on. This is stuff like scoreData.percent. * @param criteria.value - The value the key must be greater than for it to count as achieved. - * @param criteria.mode - "single", "abs" or "proportion". If abs or proportion, countNum + * @param criteria.mode - "single", "absolute" or "proportion". If abs or proportion, countNum * must be supplied. * @param criteria.countNum - For abs/proportion mode. Atleast N scores must achieve the * key:value condition. @@ -79,7 +79,7 @@ router.post( ), // we do proper validation on this later. value: p.gte(0), - mode: p.isIn("single", "abs", "proportion"), + mode: p.isIn("single", "absolute", "proportion"), countNum: (self, parent) => { if (parent.mode === "single") { return ( From 3c7e422c85089a848ff6592ca2875d2ec7492b46 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Wed, 13 Apr 2022 22:10:12 +0100 Subject: [PATCH 073/106] Add a flushable parameter to CreateFolderChartLookup --- server/src/utils/folder.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/server/src/utils/folder.ts b/server/src/utils/folder.ts index 276fdecb1..a81495cb9 100644 --- a/server/src/utils/folder.ts +++ b/server/src/utils/folder.ts @@ -152,9 +152,15 @@ export async function GetFolderChartIDs(folderID: string) { return chartIDs.map((e) => e.chartID); } -export async function CreateFolderChartLookup(folder: FolderDocument) { +export async function CreateFolderChartLookup(folder: FolderDocument, flush = false) { const { charts } = await ResolveFolderToCharts(folder, {}, false); + if (flush) { + await db["folder-chart-lookup"].remove({ + folderID: folder.folderID, + }); + } + await db["folder-chart-lookup"].insert( charts.map((c) => ({ folderID: folder.folderID, @@ -180,7 +186,7 @@ export async function InitaliseFolderChartLookup() { }); logger.info(`Reloading ${folders.length} folders.`); - await Promise.all(folders.map(CreateFolderChartLookup)); + await Promise.all(folders.map((folder) => CreateFolderChartLookup(folder))); logger.info(`Completed InitialiseFolderChartLookup.`); } From f0dfef5dc0859cc132c70509e823b7bd720e952f Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Wed, 13 Apr 2022 22:10:18 +0100 Subject: [PATCH 074/106] Add a dedicated snap command --- server/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/server/package.json b/server/package.json index 51869c0cd..1ef5e28af 100644 --- a/server/package.json +++ b/server/package.json @@ -6,6 +6,7 @@ "private": true, "scripts": { "test": "tap", + "snap": "TAP_SNAPSHOT=1 tap", "watchtest": "tap --watch", "build": "tsc --project tsconfig.build.json", "lint": "eslint ./src --ext .ts --fix", From 37895e6f53798291ba855c84a16caf97d0dd03f1 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Wed, 13 Apr 2022 22:10:27 +0100 Subject: [PATCH 075/106] Improve error messages for certain invalid goals --- server/src/lib/targets/goal-utils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/lib/targets/goal-utils.ts b/server/src/lib/targets/goal-utils.ts index 37a1df25b..3141534bf 100644 --- a/server/src/lib/targets/goal-utils.ts +++ b/server/src/lib/targets/goal-utils.ts @@ -174,7 +174,7 @@ export async function ValidateGoalChartsAndCriteria( if (Math.floor(chartCount * criteria.countNum) === 0) { throw new Error( - `countNum is too small for goal. Would result in requiring 0 charts to achieve the goal.` + `countNum (${criteria.countNum}) is too small for a goal with ${chartCount} charts. Would result in requiring 0 charts to achieve the goal.` ); } } else if ( @@ -184,7 +184,7 @@ export async function ValidateGoalChartsAndCriteria( criteria.countNum < 2) ) { throw new Error( - `Invalid countNum for goal with criteria.mode of 'abs'. Expected a whole number less than the total amount of charts available and greater than 1.` + `Invalid countNum for goal with criteria.mode of 'absolute'. Expected a whole number less than the total amount of charts available and greater than 1. (Got ${criteria.countNum}, while total charts was ${chartCount}.)` ); } From 07e7c46dd2cace108e17ff057df3959d3ece230e Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Wed, 13 Apr 2022 22:11:40 +0100 Subject: [PATCH 076/106] Fix failing folder tests. --- .../games/_game/_playtype/targets/goals/router.test.ts | 5 ++++- server/src/test-utils/mock-db/folder-chart-lookup.json | 7 ++++++- server/src/test-utils/snapshots/snapshot-data.json | 10 +++++----- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts index f716cd6b7..24ed1aab7 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts @@ -14,6 +14,7 @@ import { TestingIIDXFolderSP10, TestingIIDXSPMilestone, } from "test-utils/test-data"; +import { CreateFolderChartLookup } from "utils/folder"; // @ts-expect-error Not sure why the types break here, but they do. const dupedGoal = dm({}, HC511Goal); @@ -86,6 +87,8 @@ t.test("POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal", asyn // @ts-expect-error weird type issues t.beforeEach(() => db.folders.insert(dm(TestingIIDXFolderSP10, {}))); + t.beforeEach(() => CreateFolderChartLookup(TestingIIDXFolderSP10, true)); + const multiCharts = { type: "multi", data: [Testing511SPA.chartID, "another_chart"], @@ -416,7 +419,7 @@ t.test("POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal", asyn }), mkInput("too big score", { criteria: { - key: "scoreData.gradeIndex", + key: "scoreData.score", value: 9_000, }, }), diff --git a/server/src/test-utils/mock-db/folder-chart-lookup.json b/server/src/test-utils/mock-db/folder-chart-lookup.json index 0637a088a..85f1a6ea3 100644 --- a/server/src/test-utils/mock-db/folder-chart-lookup.json +++ b/server/src/test-utils/mock-db/folder-chart-lookup.json @@ -1 +1,6 @@ -[] \ No newline at end of file +[ + { + "chartID": "c2311194e3897ddb5745b1760d2c0141f933e683", + "folderID": "ed9d8c734447ce67d7135c0067441a98cc81aeaf" + } +] \ No newline at end of file diff --git a/server/src/test-utils/snapshots/snapshot-data.json b/server/src/test-utils/snapshots/snapshot-data.json index ba173504b..f9be219bf 100644 --- a/server/src/test-utils/snapshots/snapshot-data.json +++ b/server/src/test-utils/snapshots/snapshot-data.json @@ -5,8 +5,8 @@ "Invalid Goal: abs without countNum": "[criteria.countNum] Expected number to be greater than or equal to 0. (Received nothing)", "Invalid Goal: proportion without countNum": "[criteria.countNum] Expected number to be greater than or equal to 0. (Received nothing)", "Invalid Goal: single with countNum": "[criteria.countNum] Invalid countNum for mode 'single'. Must not have one! (Received 123)", - "Invalid Goal: nonsense mode": "[criteria.mode] Expected any of single, abs, proportion. (Received nonsense)", - "Invalid Goal: abs with countNum but charts.type == single": "Invalid countNum for goal with criteria.mode of 'abs'. Expected a whole number less than the total amount of charts available and greater than 1.", + "Invalid Goal: nonsense mode": "[criteria.mode] Expected any of single, absolute, proportion. (Received nonsense)", + "Invalid Goal: abs with countNum but charts.type == single": "Invalid countNum for goal with criteria.mode of 'absolute'. Expected a whole number less than the total amount of charts available and greater than 1. (Got 1, while total charts was 1.)", "Invalid Goal: nonsense charts.type": "[charts.type] Expected any of single, multi, folder, any. (Received nonsense)", "Invalid Goal: charts.data when type == any": "[charts.data] Invalid charts.data for type 'any'. Must not have any data! (Received foo)", "Invalid Goal: charts.data array when type == single": "[charts.data] Expected a string in charts.data due to charts.type being 'single'. (Received c2311194e3897ddb5745b1760d2c0141f933e683,another_chart)", @@ -20,12 +20,12 @@ "Invalid Goal: invalid grade": "Invalid value of 0.5 for grade goal. No such grade exists at that index.", "Invalid Goal: too big lamp": "Invalid value of 8 for lamp goal. No such lamp exists at that index.", "Invalid Goal: invalid lamp": "Invalid value of 0.5 for lamp goal. No such lamp exists at that index.", - "Invalid Goal: too big score": "Invalid value of 9000 for grade goal. No such grade exists at that index.", "Invalid Goal: proportion with negative countNum": "[criteria.countNum] Expected number to be greater than or equal to 0. (Received -0.5)", "Invalid Goal: proportion with greater than 1 countNum": "Invalid countNum for goal with criteria.mode of 'proportion'. Expected a decimal in (0, 1]", - "Invalid Goal: proportion which results in countNum of 0": "countNum is too small for goal. Would result in requiring 0 charts to achieve the goal.", + "Invalid Goal: proportion which results in countNum of 0": "countNum (0.05) is too small for a goal with 2 charts. Would result in requiring 0 charts to achieve the goal.", "Invalid Goal: charts.data array of chartIDs where some don't exist": "Expected charts.data to match 2 charts. Instead, it only matched 1. Are all of these chartIDs valid?", "Invalid Goal: folder-score for iidx is illegal": "Invalid key for iidx with multiple charts. Creating score goals on multiple charts where score is relative to notecount is a terrible idea, and has been disabled.", "Invalid Goal: any-score for iidx is illegal": "Invalid key for iidx with multiple charts. Creating score goals on multiple charts where score is relative to notecount is a terrible idea, and has been disabled.", - "Invalid Goal: charts single but criteria not.": "Invalid countNum for goal with criteria.mode of 'abs'. Expected a whole number less than the total amount of charts available and greater than 1." + "Invalid Goal: charts single but criteria not.": "Invalid countNum for goal with criteria.mode of 'absolute'. Expected a whole number less than the total amount of charts available and greater than 1. (Got 2, while total charts was 1.)", + "Invalid Goal: too big score": "Invalid value of 9000 for goal. Maximum score possible on this chart is 1572." } \ No newline at end of file From 9f5316dfb410f9771a38c566d1bcf71412edfba2 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Wed, 13 Apr 2022 23:31:59 +0100 Subject: [PATCH 077/106] Changing personal-bests data results in half the test suite breaking. --- .../test-utils/mock-db/personal-bests.json | 39 +------------------ 1 file changed, 1 insertion(+), 38 deletions(-) diff --git a/server/src/test-utils/mock-db/personal-bests.json b/server/src/test-utils/mock-db/personal-bests.json index b9fab093c..0637a088a 100644 --- a/server/src/test-utils/mock-db/personal-bests.json +++ b/server/src/test-utils/mock-db/personal-bests.json @@ -1,38 +1 @@ -[ - { - "chartID": "c2311194e3897ddb5745b1760d2c0141f933e683", - "userID": 1, - "calculatedData": { - "ktLampRating": 0, - "BPI": 10.1 - }, - "composedFrom": { - "scorePB": "TESTING_SCORE_ID", - "lampPB": "TESTING_SCORE_ID" - }, - "highlight": false, - "isPrimary": true, - "scoreData": { - "score": 786, - "lamp": "HARD CLEAR", - "percent": 50, - "grade": "C", - "esd": 30, - "gradeIndex": 3, - "lampIndex": 4, - "judgements": { - "pgreat": 50, - "great": 10 - }, - "hitMeta": {} - }, - "rankingData": { - "rank": 1, - "outOf": 2 - }, - "songID": 1, - "game": "iidx", - "playtype": "SP", - "timeAchieved": 10000 - } -] \ No newline at end of file +[] \ No newline at end of file From 7d47feb1e30c672015a906b6b78d194d43dc3141 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Wed, 13 Apr 2022 23:32:10 +0100 Subject: [PATCH 078/106] Add custom PB data for add-goals. --- .../_game/_playtype/targets/goals/router.test.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts index 24ed1aab7..bbc58418d 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts @@ -1,7 +1,7 @@ /* eslint-disable no-await-in-loop */ import dm from "deepmerge"; import db from "external/mongo/db"; -import { ChartDocument, IIDX_GRADES, IIDX_LAMPS } from "tachi-common"; +import { ChartDocument, IIDX_GRADES, IIDX_LAMPS, PBScoreDocument } from "tachi-common"; import t from "tap"; import { CreateFakeAuthCookie } from "test-utils/fake-auth"; import mockApi from "test-utils/mock-api"; @@ -13,6 +13,8 @@ import { Testing511SPA, TestingIIDXFolderSP10, TestingIIDXSPMilestone, + TestingIIDXSPScore, + TestingIIDXSPScorePB, } from "test-utils/test-data"; import { CreateFolderChartLookup } from "utils/folder"; @@ -55,6 +57,15 @@ t.test("GET /api/v1/users/:userID/games/:game/:playtype/targets/goals", (t) => { t.test("POST /api/v1/users/:userID/games/:game/:playtype/targets/add-goal", async (t) => { t.beforeEach(ResetDBState); + const customScorePB = dm(TestingIIDXSPScorePB, { + scoreData: TestingIIDXSPScore.scoreData, + }) as PBScoreDocument; + + t.beforeEach(async () => { + await db["personal-bests"].remove({}); + await db["personal-bests"].insert(customScorePB); + }); + const cookie = await CreateFakeAuthCookie(mockApi); const baseInput = { From bc8bb2eeccf307b00a66e0ad6de4096118e60b8f Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Wed, 13 Apr 2022 23:33:53 +0100 Subject: [PATCH 079/106] Always flush foldercache in tests --- server/src/lib/score-import/framework/goals/goals.test.ts | 4 ++-- server/src/lib/showcase/evaluator.test.ts | 2 +- server/src/lib/showcase/get-stats.test.ts | 2 +- server/src/lib/targets/goals.test.ts | 2 +- .../api/v1/games/_game/_playtype/folders/router.test.ts | 2 +- .../games/_game/_playtype/folders/_folderID/router.test.ts | 6 +++--- .../_userID/games/_game/_playtype/showcase/router.test.ts | 2 +- .../_userID/games/_game/_playtype/tables/router.test.ts | 2 +- 8 files changed, 11 insertions(+), 11 deletions(-) diff --git a/server/src/lib/score-import/framework/goals/goals.test.ts b/server/src/lib/score-import/framework/goals/goals.test.ts index fd62fa29e..7ae5074d7 100644 --- a/server/src/lib/score-import/framework/goals/goals.test.ts +++ b/server/src/lib/score-import/framework/goals/goals.test.ts @@ -65,8 +65,8 @@ t.test("#GetRelevantFolderGoals", (t) => { await db.folders.insert(sp11folder); await db.goals.insert(fakeFolderGoalDocument); await db.goals.insert(notFolderGoalDocument); - await CreateFolderChartLookup(TestingIIDXFolderSP10); - await CreateFolderChartLookup(sp11folder); + await CreateFolderChartLookup(TestingIIDXFolderSP10, true); + await CreateFolderChartLookup(sp11folder, true); }); t.test("Should correctly find the goals on this folder.", async (t) => { diff --git a/server/src/lib/showcase/evaluator.test.ts b/server/src/lib/showcase/evaluator.test.ts index fb0dba4df..315f52e65 100644 --- a/server/src/lib/showcase/evaluator.test.ts +++ b/server/src/lib/showcase/evaluator.test.ts @@ -13,7 +13,7 @@ import { IIDX_LAMPS } from "tachi-common"; t.test("#EvaluateShowcaseStat", (t) => { t.beforeEach(ResetDBState); - t.beforeEach(async () => await CreateFolderChartLookup(TestingIIDXFolderSP10)); + t.beforeEach(async () => await CreateFolderChartLookup(TestingIIDXFolderSP10, true)); t.beforeEach( async () => await db["personal-bests"].insert(deepmerge(TestingIIDXSPScorePB, {})) ); diff --git a/server/src/lib/showcase/get-stats.test.ts b/server/src/lib/showcase/get-stats.test.ts index 78a54238f..e3b49f258 100644 --- a/server/src/lib/showcase/get-stats.test.ts +++ b/server/src/lib/showcase/get-stats.test.ts @@ -14,7 +14,7 @@ t.test("#EvalulateUsersStatsShowcase", (t) => { t.beforeEach(async () => { await db.folders.insert(TestingIIDXFolderSP10); - await CreateFolderChartLookup(TestingIIDXFolderSP10); + await CreateFolderChartLookup(TestingIIDXFolderSP10, true); await db["game-settings"].remove({}); diff --git a/server/src/lib/targets/goals.test.ts b/server/src/lib/targets/goals.test.ts index 98c0e82f8..28bae6d0d 100644 --- a/server/src/lib/targets/goals.test.ts +++ b/server/src/lib/targets/goals.test.ts @@ -226,7 +226,7 @@ t.test("#EvaluateGoalForUser", (t) => { }), ]); - await CreateFolderChartLookup(TestingIIDXFolderSP10); + await CreateFolderChartLookup(TestingIIDXFolderSP10, true); }); t.test("Should work if 511 is >= HARD CLEAR", async (t) => { diff --git a/server/src/server/router/api/v1/games/_game/_playtype/folders/router.test.ts b/server/src/server/router/api/v1/games/_game/_playtype/folders/router.test.ts index 03163a7dc..a1f11efde 100644 --- a/server/src/server/router/api/v1/games/_game/_playtype/folders/router.test.ts +++ b/server/src/server/router/api/v1/games/_game/_playtype/folders/router.test.ts @@ -54,7 +54,7 @@ t.test("GET /api/v1/games/:game/:playtype/folders/:folderID", (t) => { t.test("Should return the folder at this ID.", async (t) => { await db.folders.insert(deepmerge(mockFolder, {})); - await CreateFolderChartLookup(mockFolder); + await CreateFolderChartLookup(mockFolder, true); const res = await mockApi.get("/api/v1/games/iidx/SP/folders/foo"); diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/folders/_folderID/router.test.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/folders/_folderID/router.test.ts index e7557defa..be7cca9e2 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/folders/_folderID/router.test.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/folders/_folderID/router.test.ts @@ -20,7 +20,7 @@ t.test("GET /api/v1/users/:userID/games/:game/:playtype/folders/:folderID", (t) folderID: "testing_folder", }) as FolderDocument; await db.folders.insert(folder); - await CreateFolderChartLookup(folder); + await CreateFolderChartLookup(folder, true); await db["personal-bests"].insert(deepmerge(TestingIIDXSPScorePB, {})); }); @@ -56,7 +56,7 @@ t.test("GET /api/v1/users/:userID/games/:game/:playtype/folders/:folderID/timeli t.beforeEach(ResetDBState); t.beforeEach(async () => { await db.folders.insert(folder); - await CreateFolderChartLookup(folder); + await CreateFolderChartLookup(folder, true); await db["personal-bests"].insert(deepmerge(TestingIIDXSPScorePB, {})); }); @@ -68,7 +68,7 @@ t.test("GET /api/v1/users/:userID/games/:game/:playtype/folders/:folderID/timeli await db.songs.iidx.insert(GetKTDataJSON("./tachi/tachi-songs-iidx.json")); await db.charts.iidx.insert(GetKTDataJSON("./tachi/tachi-charts-iidx.json")); - await CreateFolderChartLookup(folder); + await CreateFolderChartLookup(folder, true); await db.scores.insert([ deepmerge(TestingIIDXSPScore, { diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/showcase/router.test.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/showcase/router.test.ts index f3ec80f82..ba385673c 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/showcase/router.test.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/showcase/router.test.ts @@ -9,7 +9,7 @@ import { CreateFolderChartLookup } from "utils/folder"; const SetFolders = async () => { await db.folders.insert(TestingIIDXFolderSP10); - await CreateFolderChartLookup(TestingIIDXFolderSP10); + await CreateFolderChartLookup(TestingIIDXFolderSP10, true); await db["game-settings"].remove({}); diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/tables/router.test.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/tables/router.test.ts index 5263e6d23..77b01499a 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/tables/router.test.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/tables/router.test.ts @@ -16,7 +16,7 @@ t.test("GET /api/v1/users/:userID/games/:game/:playtype/tables/:tableID", (t) => }) as FolderDocument; await db.folders.insert(folder); - await CreateFolderChartLookup(folder); + await CreateFolderChartLookup(folder, true); await db["personal-bests"].insert(TestingIIDXSPScorePB); From 83e7e37f4a356ea9094bca2c3e70df581abe7e67 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Wed, 13 Apr 2022 23:42:12 +0100 Subject: [PATCH 080/106] fix: Obscure undeleted monk props on test data --- .../games/_game/_playtype/targets/goals/router.test.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts index bbc58418d..ddc2f436d 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts @@ -43,6 +43,10 @@ t.test("GET /api/v1/users/:userID/games/:game/:playtype/targets/goals", (t) => { t.equal(res.statusCode, 200); + // due to poor design decisions, these props may be set anyway. + delete HC511Goal._id; + delete HC511UserGoal._id; + t.strictSame(res.body.body, { goals: [HC511Goal], goalSubs: [HC511UserGoal], @@ -664,6 +668,9 @@ t.test("GET /api/v1/users/:userID/games/:game/:playtype/targets/goals/:goalID", t.equal(res.statusCode, 200); + delete HC511Goal._id; + delete HC511UserGoal._id; + t.hasStrict(res.body.body, { goal: HC511Goal, goalSub: HC511UserGoal, From ffe57046e2261813f8ddb8be3800ff721c523fe6 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Thu, 14 Apr 2022 01:56:11 +0100 Subject: [PATCH 081/106] Add UGPT Targets tests --- .../_game/_playtype/targets/goals/router.ts | 16 +- .../_game/_playtype/targets/router.test.ts | 162 ++++++++++++++++++ server/src/test-utils/test-data.ts | 14 ++ server/src/utils/db.ts | 38 +++- 4 files changed, 213 insertions(+), 17 deletions(-) create mode 100644 server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/router.test.ts diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts index 7d41c74b4..6a3c5f5b5 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts @@ -113,8 +113,8 @@ router.post( self.length > 1) || "Expected an array of 2 to 5 strings in charts.data due to charts.type being 'multi'." ); + /* istanbul ignore next */ } else if (parent.type === "folder") { - /* istanbul ignore previous */ return ( typeof self === "string" || "Expected a string in charts.data due to charts.type being 'folder'." @@ -254,19 +254,7 @@ router.delete( const goalID = req.params.goalID; const { user, game, playtype } = GetUGPT(req); - const goalSub = await db["goal-subs"].findOne({ - goalID, - userID: user.id, - game, - playtype, - }); - - if (!goalSub) { - return res.status(400).json({ - success: false, - description: `You aren't subscribed to this goal.`, - }); - } + const goalSub = req[SYMBOL_TachiData]!.goalSubDoc!; if (goalSub.parentMilestones.length) { return res.status(400).json({ diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/router.test.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/router.test.ts new file mode 100644 index 000000000..f08bee5ab --- /dev/null +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/router.test.ts @@ -0,0 +1,162 @@ +import db from "external/mongo/db"; +import t from "tap"; +import mockApi from "test-utils/mock-api"; +import ResetDBState from "test-utils/resets"; +import dm from "deepmerge"; +import { + HC511Goal, + HC511UserGoal, + TestingIIDXSPMilestone, + TestingIIDXSPMilestoneSub, +} from "test-utils/test-data"; +import { + GoalDocument, + GoalSubscriptionDocument, + MilestoneDocument, + MilestoneSubscriptionDocument, +} from "tachi-common"; + +function mkGoalSub(merge: Partial) { + return dm(HC511UserGoal, merge); +} + +function mkGoal(merge: Partial) { + return dm(HC511Goal, merge); +} + +function mkMilestone(merge: Partial) { + return dm(TestingIIDXSPMilestone, merge); +} + +function mkMilestoneSub(merge: Partial) { + return dm(TestingIIDXSPMilestoneSub, merge); +} + +t.test("GET /api/v1/users/:userID/games/:game/:playtype/targets/recently-achieved", (t) => { + t.beforeEach(ResetDBState); + + t.test("Should return subscriptions that were recently achieved by this user.", async (t) => { + await db.goals.insert([ + mkGoal({ + goalID: "achieved_goal", + }), + mkGoal({ + goalID: "not_achieved_goal", + }), + ]); + + await db["goal-subs"].insert([ + mkGoalSub({ + achieved: true, + timeAchieved: 1000, + goalID: "achieved_goal", + }), + mkGoalSub({ + achieved: false, + goalID: "not_achieved_goal", + }), + ]); + + await db.milestones.insert([ + mkMilestone({}), + mkMilestone({ + milestoneID: "not_achieved_milestone", + }), + ]); + + await db["milestone-subs"].insert([ + mkMilestoneSub({ + achieved: true, + timeAchieved: 2000, + }), + mkMilestoneSub({ + achieved: false, + milestoneID: "not_achieved_milestone", + }), + ]); + + const res = await mockApi.get("/api/v1/users/1/games/iidx/SP/targets/recently-achieved"); + + t.equal(res.statusCode, 200); + + t.hasStrict(res.body.body, { + milestones: [{ milestoneID: "example_milestone_id" }], + goals: [{ goalID: "achieved_goal" }], + goalSubs: [{ goalID: "achieved_goal" }], + milestoneSubs: [{ milestoneID: "example_milestone_id" }], + user: { id: 1 }, + }); + + t.equal(res.body.body.goals.length, 1); + t.equal(res.body.body.milestones.length, 1); + t.equal(res.body.body.goalSubs.length, 1); + t.equal(res.body.body.milestoneSubs.length, 1); + + t.end(); + }); + + t.end(); +}); + +t.test("GET /api/v1/users/:userID/games/:game/:playtype/targets/recently-raised", (t) => { + t.beforeEach(ResetDBState); + + t.test("Should return subscriptions that were recently achieved by this user.", async (t) => { + await db.goals.insert([ + mkGoal({ + goalID: "achieved_goal", + }), + mkGoal({ + goalID: "not_achieved_goal", + }), + ]); + + await db["goal-subs"].insert([ + mkGoalSub({ + achieved: true, + timeAchieved: 1000, + goalID: "achieved_goal", + }), + mkGoalSub({ + achieved: false, + lastInteraction: 1000, + goalID: "not_achieved_goal", + }), + ]); + + await db.milestones.insert([ + mkMilestone({}), + mkMilestone({ + milestoneID: "not_achieved_milestone", + }), + ]); + + await db["milestone-subs"].insert([ + mkMilestoneSub({ + achieved: true, + timeAchieved: 2000, + }), + mkMilestoneSub({ + achieved: false, + lastInteraction: 1000, + milestoneID: "not_achieved_milestone", + }), + ]); + + const res = await mockApi.get("/api/v1/users/1/games/iidx/SP/targets/recently-raised"); + + t.equal(res.statusCode, 200); + + t.hasStrict(res.body.body, { + milestones: [{ milestoneID: "not_achieved_milestone" }], + goals: [{ goalID: "not_achieved_goal" }], + goalSubs: [{ goalID: "not_achieved_goal" }], + milestoneSubs: [{ milestoneID: "not_achieved_milestone" }], + user: { id: 1 }, + }); + + t.end(); + }); + + t.end(); +}); diff --git a/server/src/test-utils/test-data.ts b/server/src/test-utils/test-data.ts index f6b0a5f3d..209c08ccc 100644 --- a/server/src/test-utils/test-data.ts +++ b/server/src/test-utils/test-data.ts @@ -22,6 +22,7 @@ import { ScoreDocument, SongDocument, GoalSubscriptionDocument, + MilestoneSubscriptionDocument, } from "tachi-common"; import { ApplyNTimes, RFA } from "utils/misc"; @@ -489,6 +490,19 @@ export const TestingIIDXSPMilestone: MilestoneDocument = { ], }; +export const TestingIIDXSPMilestoneSub: MilestoneSubscriptionDocument = { + userID: 1, + achieved: false, + game: "iidx", + playtype: "SP", + lastInteraction: null, + milestoneID: "example_milestone_id", + progress: 4, + timeAchieved: null, + timeSet: 1900, + wasInstantlyAchieved: false, +}; + let KTDATA_CACHE: { songs: unknown[]; charts: unknown[] } | undefined; export async function LoadTachiIIDXData() { diff --git a/server/src/utils/db.ts b/server/src/utils/db.ts index f71a5d5a9..259403540 100644 --- a/server/src/utils/db.ts +++ b/server/src/utils/db.ts @@ -196,6 +196,14 @@ export async function GetRecentlyAchievedGoals( goalID: { $in: goalSubs.map((e) => e.goalID) }, }); + if (goals.length !== goals.length) { + logger.error( + `Found ${goals.length} goals when looking for parents of ${goalSubs.length} subscriptions. This mismatch implies a state desync.` + ); + + throw new Error("Failed to fetch goals."); + } + return { goals, goalSubs }; } @@ -230,6 +238,14 @@ export async function GetRecentlyInteractedGoals( goalID: { $in: goalSubs.map((e) => e.goalID) }, }); + if (goals.length !== goals.length) { + logger.error( + `Found ${goals.length} goals when looking for parents of ${goalSubs.length} subscriptions. This mismatch implies a state desync.` + ); + + throw new Error("Failed to fetch goals."); + } + return { goals, goalSubs }; } @@ -259,10 +275,18 @@ export async function GetRecentlyAchievedMilestones( limit, }); - const milestones = await db.goals.find({ - goalID: { $in: milestoneSubs.map((e) => e.milestoneID) }, + const milestones = await db.milestones.find({ + milestoneID: { $in: milestoneSubs.map((e) => e.milestoneID) }, }); + if (milestones.length !== milestoneSubs.length) { + logger.error( + `Found ${milestones.length} milestones when looking for parents of ${milestoneSubs.length} subscriptions. This mismatch implies a state desync.` + ); + + throw new Error("Failed to fetch milestones."); + } + return { milestones, milestoneSubs }; } @@ -293,10 +317,18 @@ export async function GetRecentlyInteractedMilestones( limit, }); - const milestones = await db.goals.find({ + const milestones = await db.milestones.find({ milestoneID: { $in: milestoneSubs.map((e) => e.milestoneID) }, }); + if (milestones.length !== milestoneSubs.length) { + logger.error( + `Found ${milestones.length} milestones when looking for parents of ${milestoneSubs.length} subscriptions. This mismatch implies a state desync.` + ); + + throw new Error("Failed to fetch milestones."); + } + return { milestones, milestoneSubs }; } From 0fc677dae073e4161291c59bb0cd79553fef0a03 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Thu, 14 Apr 2022 02:52:12 +0100 Subject: [PATCH 082/106] UGPT Milestone Tests --- .../targets/milestones/router.test.ts | 268 ++++++++++++++++++ .../_playtype/targets/milestones/router.ts | 10 +- server/src/test-utils/test-data.ts | 15 + 3 files changed, 291 insertions(+), 2 deletions(-) create mode 100644 server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/milestones/router.test.ts diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/milestones/router.test.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/milestones/router.test.ts new file mode 100644 index 000000000..db32884fb --- /dev/null +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/milestones/router.test.ts @@ -0,0 +1,268 @@ +import db from "external/mongo/db"; +import { PublicUserDocument, UserGameStats } from "tachi-common"; +import t from "tap"; +import { CreateFakeAuthCookie } from "test-utils/fake-auth"; +import mockApi from "test-utils/mock-api"; +import ResetDBState from "test-utils/resets"; +import { + IIDXSPMilestoneGoals, + IIDXSPMilestoneGoalSubs, + TestingIIDXSPMilestone, + TestingIIDXSPMilestoneSub, +} from "test-utils/test-data"; + +t.test("GET /api/v1/users/:userID/games/:game/:playtype/targets/milestones", (t) => { + t.beforeEach(ResetDBState); + + t.test("Should return all milestone subscriptions.", async (t) => { + await db.milestones.insert(TestingIIDXSPMilestone); + await db["milestone-subs"].insert(TestingIIDXSPMilestoneSub); + + const res = await mockApi.get("/api/v1/users/1/games/iidx/SP/targets/milestones"); + + delete TestingIIDXSPMilestone._id; + delete TestingIIDXSPMilestoneSub._id; + + t.strictSame(res.body.body, { + milestones: [TestingIIDXSPMilestone], + milestoneSubs: [TestingIIDXSPMilestoneSub], + }); + + t.end(); + }); + + t.test("Should panic if milestone subs are parentless.", async (t) => { + // await db.milestones.insert(TestingIIDXSPMilestone); + await db["milestone-subs"].insert(TestingIIDXSPMilestoneSub); + + const res = await mockApi.get("/api/v1/users/1/games/iidx/SP/targets/milestones"); + + t.equal(res.statusCode, 500); + + t.end(); + }); + + t.end(); +}); + +t.test("GET /api/v1/users/:userID/games/:game/:playtype/targets/milestones/:milestoneID", (t) => { + t.beforeEach(ResetDBState); + + t.test("Should return the user's specific milestone sub.", async (t) => { + await db.milestones.insert(TestingIIDXSPMilestone); + await db["milestone-subs"].insert(TestingIIDXSPMilestoneSub); + + await db.goals.insert(IIDXSPMilestoneGoals); + await db["goal-subs"].insert(IIDXSPMilestoneGoalSubs); + + const res = await mockApi.get( + `/api/v1/users/1/games/iidx/SP/targets/milestones/${TestingIIDXSPMilestone.milestoneID}` + ); + + // i hate the fact that monk just adds this to objects! terrible. + delete TestingIIDXSPMilestone._id; + delete TestingIIDXSPMilestoneSub._id; + + for (const goal of IIDXSPMilestoneGoals) { + delete goal._id; + } + + t.strictSame(res.body.body, { + milestone: TestingIIDXSPMilestone, + milestoneSub: TestingIIDXSPMilestoneSub, + results: IIDXSPMilestoneGoalSubs.map((e) => ({ + achieved: e.achieved, + progress: e.progress, + progressHuman: e.progressHuman, + outOf: e.outOf, + outOfHuman: e.outOfHuman, + goalID: e.goalID, + })), + goals: IIDXSPMilestoneGoals, + }); + + t.end(); + }); + + t.test("Should return 404 if the user is not subscribed to this milestone.", async (t) => { + const res = await mockApi.get( + `/api/v1/users/1/games/iidx/SP/targets/milestones/fake_milestone` + ); + + t.equal(res.statusCode, 404); + + t.end(); + }); + + t.end(); +}); + +t.test( + "PUT /api/v1/users/:userID/games/:game/:playtype/targets/milestones/:milestoneID", + async (t) => { + t.beforeEach(ResetDBState); + + const cookie = await CreateFakeAuthCookie(mockApi); + + t.test("Should return 404 if this milestone doesn't exist.", async (t) => { + const res = await mockApi + .put(`/api/v1/users/1/games/iidx/SP/targets/milestones/fake_milestone`) + .set("Cookie", cookie); + + t.equal(res.statusCode, 404); + + t.end(); + }); + + t.test("Should return 409 if user is already subscribed to this milestone.", async (t) => { + await db.milestones.insert(TestingIIDXSPMilestone); + await db["milestone-subs"].insert(TestingIIDXSPMilestoneSub); + + const res = await mockApi + .put( + `/api/v1/users/1/games/iidx/SP/targets/milestones/${TestingIIDXSPMilestone.milestoneID}` + ) + .set("Cookie", cookie); + + t.equal(res.statusCode, 409); + + t.end(); + }); + + t.test("Should subscribe to a milestone.", async (t) => { + await db.milestones.insert(TestingIIDXSPMilestone); + await db.goals.insert(IIDXSPMilestoneGoals); + + const res = await mockApi + .put( + `/api/v1/users/1/games/iidx/SP/targets/milestones/${TestingIIDXSPMilestone.milestoneID}` + ) + .set("Cookie", cookie); + + t.equal(res.statusCode, 200); + + const dbRes = await db["milestone-subs"].findOne({ + userID: 1, + milestoneID: TestingIIDXSPMilestone.milestoneID, + }); + + t.not(dbRes, null, "Should have subscribed the user to the milestone."); + + t.end(); + }); + + t.test("Should return 401 if the user is not authed.", async (t) => { + const res = await mockApi.put( + `/api/v1/users/1/games/iidx/SP/targets/milestones/fake_milestone` + ); + + t.equal(res.statusCode, 401); + + t.end(); + }); + + t.test("Should return 403 if the user is authed as someone else.", async (t) => { + // We have to make sure that the user exists, otherwise we'll just get + // a normal 404. + await db.users.insert({ + id: 2, + username: "fake_person", + usernameLowercase: "fake_person", + } as PublicUserDocument); + + await db["game-stats"].insert({ + game: "iidx", + playtype: "SP", + userID: 2, + } as UserGameStats); + + const res = await mockApi + .put(`/api/v1/users/2/games/iidx/SP/targets/milestones/fake_milestone`) + .set("Cookie", cookie); + + t.equal(res.statusCode, 403); + + t.end(); + }); + + t.end(); + } +); + +t.test( + "DELETE /api/v1/users/:userID/games/:game/:playtype/targets/milestones/:milestoneID", + async (t) => { + const cookie = await CreateFakeAuthCookie(mockApi); + + t.test("Should return 404 if the user is not subscribed to this milestone.", async (t) => { + const res = await mockApi + .delete(`/api/v1/users/1/games/iidx/SP/targets/milestones/fake_milestone`) + .set("Cookie", cookie); + + t.equal(res.statusCode, 404); + + t.end(); + }); + + t.test("Should return 401 if the user is not authed.", async (t) => { + const res = await mockApi.delete( + `/api/v1/users/1/games/iidx/SP/targets/milestones/fake_milestone` + ); + + t.equal(res.statusCode, 401); + + t.end(); + }); + + t.test("Should return 403 if the user is authed as someone else.", async (t) => { + // We have to make sure that the user exists, otherwise we'll just get + // a normal 404. + await db.users.insert({ + id: 2, + username: "fake_person", + usernameLowercase: "fake_person", + } as PublicUserDocument); + + await db["game-stats"].insert({ + game: "iidx", + playtype: "SP", + userID: 2, + } as UserGameStats); + + const res = await mockApi + .delete(`/api/v1/users/2/games/iidx/SP/targets/milestones/fake_milestone`) + .set("Cookie", cookie); + + t.equal(res.statusCode, 403); + + t.end(); + }); + + t.test("Should unsubscribe from a milestone.", async (t) => { + await db.milestones.insert(TestingIIDXSPMilestone); + await db["milestone-subs"].insert(TestingIIDXSPMilestoneSub); + + const res = await mockApi + .delete( + `/api/v1/users/1/games/iidx/SP/targets/milestones/${TestingIIDXSPMilestone.milestoneID}` + ) + .set("Cookie", cookie); + + t.equal(res.statusCode, 200); + + delete TestingIIDXSPMilestone._id; + t.strictSame(res.body.body.milestone, TestingIIDXSPMilestone); + + const dbRes = await db["milestone-subs"].findOne({ + userID: 1, + milestoneID: TestingIIDXSPMilestone.milestoneID, + }); + + t.equal(dbRes, null, "Should have removed the milestone from the database."); + + t.end(); + }); + + t.end(); + } +); diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/milestones/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/milestones/router.ts index b94147d9c..aae9ff40f 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/milestones/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/milestones/router.ts @@ -19,7 +19,7 @@ const logger = CreateLogCtx(__filename); const router: Router = Router({ mergeParams: true }); /** - * Retrieves this user's set milestones. + * Retrieves this user's subscribed milestones. * * @name GET /api/v1/users/:userID/games/:game/:playtype/targets/milestones */ @@ -36,6 +36,13 @@ router.get("/", async (req, res) => { milestoneID: { $in: milestoneSubs.map((e) => e.milestoneID) }, }); + if (milestones.length !== milestoneSubs.length) { + logger.error( + `Found ${milestoneSubs.length} subscriptions, but got ${milestones.length} parents. This is a state desync.` + ); + throw new Error("Failed to fetch milestones"); + } + return res.status(200).json({ success: true, description: `Retrieved ${milestoneSubs.length} milestone(s).`, @@ -165,7 +172,6 @@ router.put( // Users should be able to subscribe to milestones EVEN IF they would instantly // achieve them. - // This is impossible, as the third argument to the above function is false! // if (subResult === SubscribeFailReasons.ALREADY_ACHIEVED) { // return res.status(400).json({ // success: false, diff --git a/server/src/test-utils/test-data.ts b/server/src/test-utils/test-data.ts index 209c08ccc..0fa2d545a 100644 --- a/server/src/test-utils/test-data.ts +++ b/server/src/test-utils/test-data.ts @@ -1,3 +1,4 @@ +import dm from "deepmerge"; import db from "external/mongo/db"; import fs from "fs"; import { @@ -490,6 +491,20 @@ export const TestingIIDXSPMilestone: MilestoneDocument = { ], }; +export const IIDXSPMilestoneGoals: GoalDocument[] = [ + dm(HC511Goal, { goalID: "eg_goal_1" }) as GoalDocument, + dm(HC511Goal, { goalID: "eg_goal_2", criteria: { value: 2 } }), + dm(HC511Goal, { goalID: "eg_goal_3", criteria: { value: 300 } }), + dm(HC511Goal, { goalID: "eg_goal_4", criteria: { value: 1100 } }), +]; + +export const IIDXSPMilestoneGoalSubs: GoalSubscriptionDocument[] = [ + dm(HC511UserGoal, { goalID: "eg_goal_1" }) as GoalSubscriptionDocument, + dm(HC511UserGoal, { goalID: "eg_goal_2" }) as GoalSubscriptionDocument, + dm(HC511UserGoal, { goalID: "eg_goal_3" }) as GoalSubscriptionDocument, + dm(HC511UserGoal, { goalID: "eg_goal_4" }) as GoalSubscriptionDocument, +]; + export const TestingIIDXSPMilestoneSub: MilestoneSubscriptionDocument = { userID: 1, achieved: false, From c58aa14cac9d0a33c2abdb2716632d714309fcbd Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Thu, 14 Apr 2022 03:10:06 +0100 Subject: [PATCH 083/106] Remove parentMilestones as a feature. Handle it through database queries instead. --- server/package.json | 2 +- server/pnpm-lock.yaml | 8 +-- .../framework/goals/goals.test.ts | 3 - server/src/lib/targets/goals.ts | 59 +++++++++++++++++-- server/src/lib/targets/milestones.ts | 36 +---------- .../_game/_playtype/targets/goals/router.ts | 10 ++-- .../_playtype/targets/goals/router.test.ts | 11 +--- .../_game/_playtype/targets/goals/router.ts | 10 +++- server/src/test-utils/test-data.ts | 1 - 9 files changed, 78 insertions(+), 62 deletions(-) diff --git a/server/package.json b/server/package.json index 1ef5e28af..dc567c1c5 100644 --- a/server/package.json +++ b/server/package.json @@ -85,7 +85,7 @@ "rimraf": "3.0.2", "safe-json-stringify": "1.2.0", "seq-logging": "1.1.1", - "tachi-common": "0.7.14", + "tachi-common": "0.7.15", "ts-node": "10.4.0", "tsconfig-paths": "3.12.0", "typescript": "4.5.5", diff --git a/server/pnpm-lock.yaml b/server/pnpm-lock.yaml index b451adbaa..d279f9574 100644 --- a/server/pnpm-lock.yaml +++ b/server/pnpm-lock.yaml @@ -65,7 +65,7 @@ specifiers: safe-json-stringify: 1.2.0 seq-logging: 1.1.1 supertest: 6.2.2 - tachi-common: 0.7.14 + tachi-common: 0.7.15 tap: 15.1.6 ts-node: 10.4.0 tsconfig-paths: 3.12.0 @@ -112,7 +112,7 @@ dependencies: rimraf: 3.0.2 safe-json-stringify: 1.2.0 seq-logging: 1.1.1 - tachi-common: 0.7.14 + tachi-common: 0.7.15 ts-node: 10.4.0_2615db9039ce432b4abf2fc39ef336ec tsconfig-paths: 3.12.0 typescript: 4.5.5 @@ -5363,8 +5363,8 @@ packages: engines: {node: '>= 0.4'} dev: true - /tachi-common/0.7.14: - resolution: {integrity: sha512-RZfmSvCrITGm3jClZZc4JJAJwo6lUU+Wkq2+PAQh7gHdlR5OjVlTNDQr67F3F2KTagz6Sw4y1BDKw78j2aPl8Q==} + /tachi-common/0.7.15: + resolution: {integrity: sha512-rZNTYZFmDVUDcqaVJZAwLhH3Sskoks6Votc62JAOIrxVKEGu9JwGClEpU7YXtD4Uq2Qwt9ZesMtRatQ9rgcMcg==} dependencies: '@types/mongodb': 3.6.18 dev: false diff --git a/server/src/lib/score-import/framework/goals/goals.test.ts b/server/src/lib/score-import/framework/goals/goals.test.ts index 7ae5074d7..9d37b44a2 100644 --- a/server/src/lib/score-import/framework/goals/goals.test.ts +++ b/server/src/lib/score-import/framework/goals/goals.test.ts @@ -131,7 +131,6 @@ t.test("#GetRelevantGoals", (t) => { progressHuman: "NO DATA", timeSet: Date.now(), userID: 1, - parentMilestones: [], })) ); }); @@ -194,7 +193,6 @@ t.test("#UpdateGoalsForUser", (t) => { timeAchieved: null, timeSet: 0, userID: 1, - parentMilestones: [], }; t.test("Should correctly update goals when user achieves goal.", async (t) => { @@ -396,7 +394,6 @@ t.test("#ProcessGoal", (t) => { lastInteraction: null, outOf: 5, outOfHuman: "HARD CLEAR", - parentMilestones: [], playtype: "SP", progress: 6, progressHuman: "EX HARD CLEAR", diff --git a/server/src/lib/targets/goals.ts b/server/src/lib/targets/goals.ts index a6e1386e4..ca9ac8a40 100644 --- a/server/src/lib/targets/goals.ts +++ b/server/src/lib/targets/goals.ts @@ -11,6 +11,8 @@ import { PBScoreDocument, Playtypes, GoalSubscriptionDocument, + MilestoneSubscriptionDocument, + MilestoneDocument, } from "tachi-common"; import { GetFolderChartIDs } from "utils/folder"; import { CreateGoalTitle as CreateGoalName, ValidateGoalChartsAndCriteria } from "./goal-utils"; @@ -283,7 +285,6 @@ export async function ConstructGoal( export async function SubscribeToGoal( userID: integer, goalDocument: GoalDocument, - parentMilestone?: string, cancelIfAchieved = true ) { const goalExists = await db.goals.findOne({ goalID: goalDocument.goalID }); @@ -323,9 +324,6 @@ export async function SubscribeToGoal( lastInteraction: null, timeAchieved: result.achieved ? Date.now() : null, timeSet: Date.now(), - // if this goal subscription came from a milestone, add the milestone to - // the list of parents. - parentMilestones: parentMilestone ? [parentMilestone] : [], game: goalDocument.game, playtype: goalDocument.playtype, goalID: goalDocument.goalID, @@ -337,3 +335,56 @@ export async function SubscribeToGoal( return goalSub; } + +export function GetMilestonesThatContainGoal(goalID: string) { + return db.milestones.find({ + "milestoneData.goalID": goalID, + }); +} + +/** + * Unsubscribing from a goal may not be legal, because the goal might be part of + * a milestone the user is subscribed to. This function returns all milestones + * and milestoneSubs that a goal is attached to. + * + * If this query matches none, an empty array is returned. + */ +export async function GetBlockingParentMilestoneSubs( + goalSub: GoalSubscriptionDocument +): Promise<(MilestoneSubscriptionDocument & { milestone: MilestoneDocument })[]> { + const blockers = await db["milestone-subs"].aggregate([ + { + // find all milestones that this user is subscribed to + $match: { + userID: goalSub.userID, + game: goalSub.game, + playtype: goalSub.playtype, + }, + }, + { + // look up the parent milestones + $lookup: { + from: "milestones", + localField: "milestoneID", + foreignField: "milestoneID", + as: "parentMilestoneSubs", + }, + }, + { + // then project it onto the $milestone field. This will be null + // if the milestone has no parent, which we hopefully won't have + // to consider (illegal) + $set: { + milestone: { $arrayElemAt: ["$parentMilestoneSubs", 0] }, + }, + }, + { + // then finally, filter to only milestones that pertain to this goal. + $match: { + "$milestone.milestoneData.goalID": goalSub.goalID, + }, + }, + ]); + + return blockers; +} diff --git a/server/src/lib/targets/milestones.ts b/server/src/lib/targets/milestones.ts index 629baa426..0f16af1ed 100644 --- a/server/src/lib/targets/milestones.ts +++ b/server/src/lib/targets/milestones.ts @@ -230,25 +230,8 @@ export async function SubscribeToMilestone( // from result.goalResults ourselves. // evaluating goals is fairly cheap though. await Promise.all( - result.goals.map(async (goal) => { - const res = await SubscribeToGoal(userID, goal, milestone.milestoneID, false); - - // If the user is already subscribed to this goal -- i.e. manually or as part - // of another milestone - // add this milestoneID to the list of parents instead. - if (res === SubscribeFailReasons.ALREADY_SUBSCRIBED) { - await db["goal-subs"].update( - { - userID, - milestoneID: milestone.milestoneID, - }, - { - $push: { - parentMilestones: milestone.milestoneID, - }, - } - ); - } + result.goals.map((goal) => { + SubscribeToGoal(userID, goal, milestone.milestoneID, false); }) ); @@ -262,20 +245,7 @@ export async function SubscribeToMilestone( export async function UnsubscribeFromMilestone(userID: integer, milestone: MilestoneDocument) { const goalIDs = GetGoalIDsFromMilestone(milestone); - // Pull this milestone ID from all of the goalSubscriptions that have it. - // since it's no longer going to be their parent. - await db["goal-subs"].update( - { - goalID: { $in: goalIDs }, - userID, - parentMilestones: milestone.milestoneID, - }, - { - $pull: { - parentMilestones: milestone.milestoneID, - }, - } - ); + // TODO COME BACK HERE // then, remove all of the ones that now have no parent blocking their demise. // that's pretty morbid, jesus christ. diff --git a/server/src/server/router/api/v1/games/_game/_playtype/targets/goals/router.ts b/server/src/server/router/api/v1/games/_game/_playtype/targets/goals/router.ts index ca6cf1753..e828ac353 100644 --- a/server/src/server/router/api/v1/games/_game/_playtype/targets/goals/router.ts +++ b/server/src/server/router/api/v1/games/_game/_playtype/targets/goals/router.ts @@ -2,7 +2,11 @@ import { RequestHandler, Router } from "express"; import db from "external/mongo/db"; import { SYMBOL_TachiData } from "lib/constants/tachi"; import CreateLogCtx from "lib/logger/logger"; -import { EvaluatedGoalReturn, EvaluateGoalForUser } from "lib/targets/goals"; +import { + EvaluatedGoalReturn, + EvaluateGoalForUser, + GetMilestonesThatContainGoal, +} from "lib/targets/goals"; import prValidate from "server/middleware/prudence-validate"; import { FormatGame } from "tachi-common"; import { GetMostSubscribedGoals } from "utils/db"; @@ -66,9 +70,7 @@ router.get("/:goalID", ResolveGoalID, async (req, res) => { const users = await GetUsersWithIDs(goalSubs.map((e) => e.userID)); - const parentMilestones = await db.milestones.find({ - "milestoneData.goalID": goal.goalID, - }); + const parentMilestones = await GetMilestonesThatContainGoal(goal.goalID); return res.status(200).json({ success: true, diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts index ddc2f436d..2a8d35948 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts @@ -656,9 +656,7 @@ t.test("GET /api/v1/users/:userID/games/:game/:playtype/targets/goals/:goalID", await db.goals.insert(dupedGoal); await db["goal-subs"].insert( // @ts-expect-error Not sure why the types break here, but they do. - dm(HC511UserGoal, { - parentMilestones: [TestingIIDXSPMilestone.milestoneID], - }) + dm(HC511UserGoal) ); await db.milestones.insert(dm(TestingIIDXSPMilestone, {})); @@ -739,12 +737,7 @@ t.test("DELETE /api/v1/users/:userID/games/:game/:playtype/targets/goals/:goalID t.test("Should reject a goal deletion if goal has parent milestones.", async (t) => { await db.goals.insert(dupedGoal); - await db["goal-subs"].insert( - // @ts-expect-error deepmerge type error - dm(dupedGoalSub, { - parentMilestones: [TestingIIDXSPMilestone.milestoneID], - }) - ); + await db["goal-subs"].insert(dm(dupedGoalSub, {})); const res = await mockApi .delete(`/api/v1/users/1/games/iidx/SP/targets/goals/${dupedGoalSub.goalID}`) diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts index 6a3c5f5b5..6bd5d1c87 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts @@ -4,7 +4,7 @@ import { SubscribeFailReasons } from "lib/constants/err-codes"; import { SYMBOL_TachiData } from "lib/constants/tachi"; import CreateLogCtx from "lib/logger/logger"; import { ServerConfig } from "lib/setup/config"; -import { ConstructGoal, SubscribeToGoal } from "lib/targets/goals"; +import { ConstructGoal, GetBlockingParentMilestoneSubs, SubscribeToGoal } from "lib/targets/goals"; import p from "prudence"; import { RequirePermissions } from "server/middleware/auth"; import prValidate from "server/middleware/prudence-validate"; @@ -256,10 +256,14 @@ router.delete( const goalSub = req[SYMBOL_TachiData]!.goalSubDoc!; - if (goalSub.parentMilestones.length) { + const parentMilestones = await GetBlockingParentMilestoneSubs(goalSub); + + if (parentMilestones.length) { return res.status(400).json({ success: false, - description: `This goal is part of a milestone you are subscribed to. It can only be removed by unsubscribing from the relevant milestones.`, + description: `This goal is part of a milestone you are subscribed to. It can only be removed by unsubscribing from the relevant milestones: ${parentMilestones + .map((e) => `'${e.milestone.name}'`) + .join(", ")}.`, }); } diff --git a/server/src/test-utils/test-data.ts b/server/src/test-utils/test-data.ts index 0fa2d545a..c06cf2036 100644 --- a/server/src/test-utils/test-data.ts +++ b/server/src/test-utils/test-data.ts @@ -438,7 +438,6 @@ export const HC511UserGoal: GoalSubscriptionDocument = { progressHuman: "NO DATA", timeSet: 0, userID: 1, - parentMilestones: [], }; export const TestingIIDXFolderSP10: FolderDocument = { From e10cfbcc78174477bcb136b013f9bad465940c53 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Thu, 14 Apr 2022 03:11:37 +0100 Subject: [PATCH 084/106] Fix accidental argument passed to SubscribeToGoal --- server/src/lib/targets/milestones.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/lib/targets/milestones.ts b/server/src/lib/targets/milestones.ts index 0f16af1ed..07350e683 100644 --- a/server/src/lib/targets/milestones.ts +++ b/server/src/lib/targets/milestones.ts @@ -231,7 +231,7 @@ export async function SubscribeToMilestone( // evaluating goals is fairly cheap though. await Promise.all( result.goals.map((goal) => { - SubscribeToGoal(userID, goal, milestone.milestoneID, false); + SubscribeToGoal(userID, goal, false); }) ); From 532cb494dbc4487cede5c7c21f91766d4f91bcc2 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Thu, 14 Apr 2022 03:13:21 +0100 Subject: [PATCH 085/106] Fix mistake in GET UGPT goalID --- .../games/_game/_playtype/targets/goals/router.ts | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts index 6bd5d1c87..4ecdba83c 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts @@ -4,7 +4,12 @@ import { SubscribeFailReasons } from "lib/constants/err-codes"; import { SYMBOL_TachiData } from "lib/constants/tachi"; import CreateLogCtx from "lib/logger/logger"; import { ServerConfig } from "lib/setup/config"; -import { ConstructGoal, GetBlockingParentMilestoneSubs, SubscribeToGoal } from "lib/targets/goals"; +import { + ConstructGoal, + GetBlockingParentMilestoneSubs, + GetMilestonesThatContainGoal, + SubscribeToGoal, +} from "lib/targets/goals"; import p from "prudence"; import { RequirePermissions } from "server/middleware/auth"; import prValidate from "server/middleware/prudence-validate"; @@ -218,13 +223,7 @@ router.get("/:goalID", GetGoalSubscription, async (req, res) => { const goalSub = req[SYMBOL_TachiData]!.goalSubDoc!; - let milestones: MilestoneDocument[] = []; - - if (goalSub.parentMilestones.length !== 0) { - milestones = await Promise.all( - goalSub.parentMilestones.map((e) => GetMilestoneForIDGuaranteed(e)) - ); - } + const milestones: MilestoneDocument[] = await GetMilestonesThatContainGoal(goalSub.goalID); const goal = await GetGoalForIDGuaranteed(goalSub.goalID); From 19591514cfd17cef2bec7a61889731eef2b54354 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Thu, 14 Apr 2022 03:31:25 +0100 Subject: [PATCH 086/106] Patch faulty parentMilestone logic --- server/src/lib/targets/goals.ts | 4 +- .../_playtype/targets/goals/router.test.ts | 63 +++++++++---------- 2 files changed, 30 insertions(+), 37 deletions(-) diff --git a/server/src/lib/targets/goals.ts b/server/src/lib/targets/goals.ts index ca9ac8a40..88e0fb81f 100644 --- a/server/src/lib/targets/goals.ts +++ b/server/src/lib/targets/goals.ts @@ -338,7 +338,7 @@ export async function SubscribeToGoal( export function GetMilestonesThatContainGoal(goalID: string) { return db.milestones.find({ - "milestoneData.goalID": goalID, + "milestoneData.goals.goalID": goalID, }); } @@ -381,7 +381,7 @@ export async function GetBlockingParentMilestoneSubs( { // then finally, filter to only milestones that pertain to this goal. $match: { - "$milestone.milestoneData.goalID": goalSub.goalID, + "milestone.milestoneData.goals.goalID": goalSub.goalID, }, }, ]); diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts index 2a8d35948..60df6888d 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.test.ts @@ -1,7 +1,13 @@ /* eslint-disable no-await-in-loop */ import dm from "deepmerge"; import db from "external/mongo/db"; -import { ChartDocument, IIDX_GRADES, IIDX_LAMPS, PBScoreDocument } from "tachi-common"; +import { + ChartDocument, + GoalSubscriptionDocument, + IIDX_GRADES, + IIDX_LAMPS, + PBScoreDocument, +} from "tachi-common"; import t from "tap"; import { CreateFakeAuthCookie } from "test-utils/fake-auth"; import mockApi from "test-utils/mock-api"; @@ -10,9 +16,12 @@ import { TestSnapshot } from "test-utils/single-process-snapshot"; import { HC511Goal, HC511UserGoal, + IIDXSPMilestoneGoals, + IIDXSPMilestoneGoalSubs, Testing511SPA, TestingIIDXFolderSP10, TestingIIDXSPMilestone, + TestingIIDXSPMilestoneSub, TestingIIDXSPScore, TestingIIDXSPScorePB, } from "test-utils/test-data"; @@ -653,25 +662,23 @@ t.test("GET /api/v1/users/:userID/games/:game/:playtype/targets/goals/:goalID", }); t.test("Should return parent milestones if goal has any.", async (t) => { - await db.goals.insert(dupedGoal); - await db["goal-subs"].insert( - // @ts-expect-error Not sure why the types break here, but they do. - dm(HC511UserGoal) - ); - await db.milestones.insert(dm(TestingIIDXSPMilestone, {})); + await db.goals.insert(IIDXSPMilestoneGoals); + await db["goal-subs"].insert(IIDXSPMilestoneGoalSubs); + await db.milestones.insert(TestingIIDXSPMilestone); const res = await mockApi.get( - `/api/v1/users/1/games/iidx/SP/targets/goals/${HC511Goal.goalID}` + `/api/v1/users/1/games/iidx/SP/targets/goals/${IIDXSPMilestoneGoalSubs[0].goalID}` ); t.equal(res.statusCode, 200); - delete HC511Goal._id; - delete HC511UserGoal._id; + delete IIDXSPMilestoneGoals[0]._id; + delete IIDXSPMilestoneGoalSubs[0]._id; + delete TestingIIDXSPMilestone._id; t.hasStrict(res.body.body, { - goal: HC511Goal, - goalSub: HC511UserGoal, + goal: IIDXSPMilestoneGoals[0], + goalSub: IIDXSPMilestoneGoalSubs[0], milestones: [TestingIIDXSPMilestone], user: { id: 1, @@ -681,24 +688,6 @@ t.test("GET /api/v1/users/:userID/games/:game/:playtype/targets/goals/:goalID", t.end(); }); - t.test("Should panic if goal refers to a parent milestone that doesn't exist.", async (t) => { - await db.goals.insert(dupedGoal); - await db["goal-subs"].insert( - // @ts-expect-error Not sure why the types break here, but they do. - dm(HC511UserGoal, { - parentMilestones: [TestingIIDXSPMilestone.milestoneID], - }) - ); - - const res = await mockApi.get( - `/api/v1/users/1/games/iidx/SP/targets/goals/${HC511Goal.goalID}` - ); - - t.equal(res.statusCode, 500); - - t.end(); - }); - t.test("Should return 404 if the user is not subscribed to this goal ID.", async (t) => { const res = await mockApi.get(`/api/v1/users/1/games/iidx/SP/targets/goals/INVALID_GOAL`); @@ -736,22 +725,26 @@ t.test("DELETE /api/v1/users/:userID/games/:game/:playtype/targets/goals/:goalID }); t.test("Should reject a goal deletion if goal has parent milestones.", async (t) => { - await db.goals.insert(dupedGoal); - await db["goal-subs"].insert(dm(dupedGoalSub, {})); + await db.milestones.insert(TestingIIDXSPMilestone); + await db["milestone-subs"].insert(TestingIIDXSPMilestoneSub); + await db.goals.insert(IIDXSPMilestoneGoals); + await db["goal-subs"].insert( + dm(dupedGoalSub, { goalID: "eg_goal_1" }) as GoalSubscriptionDocument + ); const res = await mockApi - .delete(`/api/v1/users/1/games/iidx/SP/targets/goals/${dupedGoalSub.goalID}`) + .delete(`/api/v1/users/1/games/iidx/SP/targets/goals/eg_goal_1`) .set("Cookie", cookie); t.equal(res.statusCode, 400); t.equal( res.body.description, - "This goal is part of a milestone you are subscribed to. It can only be removed by unsubscribing from the relevant milestones." + `This goal is part of a milestone you are subscribed to. It can only be removed by unsubscribing from the relevant milestones: '${TestingIIDXSPMilestone.name}'.` ); const dbRes = await db["goal-subs"].findOne({ userID: 1, - goalID: dupedGoal.goalID, + goalID: "eg_goal_1", }); t.not(dbRes, null, "Should NOT delete the goal sub from the database."); From 83519ad590ecf8e92f9945d0c1a91e29e9515b17 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Thu, 14 Apr 2022 03:35:08 +0100 Subject: [PATCH 087/106] chore: remove unused import --- .../users/_userID/games/_game/_playtype/targets/goals/router.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts index 4ecdba83c..7410c943e 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/targets/goals/router.ts @@ -14,7 +14,7 @@ import p from "prudence"; import { RequirePermissions } from "server/middleware/auth"; import prValidate from "server/middleware/prudence-validate"; import { GoalDocument, MilestoneDocument } from "tachi-common"; -import { GetGoalForIDGuaranteed, GetMilestoneForIDGuaranteed } from "utils/db"; +import { GetGoalForIDGuaranteed } from "utils/db"; import { AssignToReqTachiData, GetUGPT } from "utils/req-tachi-data"; import { RequireAuthedAsUser } from "../../../../../middleware"; From 74950d7a8a2dd944c5cbd93df7a914e3a5575220 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Sat, 16 Apr 2022 04:33:16 +0100 Subject: [PATCH 088/106] test: Add test alternative user data --- server/src/test-utils/test-data.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/server/src/test-utils/test-data.ts b/server/src/test-utils/test-data.ts index c06cf2036..dbd299efc 100644 --- a/server/src/test-utils/test-data.ts +++ b/server/src/test-utils/test-data.ts @@ -24,6 +24,7 @@ import { SongDocument, GoalSubscriptionDocument, MilestoneSubscriptionDocument, + PublicUserDocument, } from "tachi-common"; import { ApplyNTimes, RFA } from "utils/misc"; @@ -600,3 +601,19 @@ export const uscScore: USCClientScore = { slam: USC_DEFAULT_SLAM, }, }; + +export const FakeOtherUser: PublicUserDocument = { + id: 2, + username: "other_user", + about: "", + authLevel: 0, + badges: [], + clan: null, + customBannerLocation: null, + customPfpLocation: null, + joinDate: 0, + lastSeen: 0, + socialMedia: {}, + status: null, + usernameLowercase: "other_user", +}; From e04f1aa508f7b5cddc88a388ea52574e3a5ba10d Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Sat, 16 Apr 2022 04:33:35 +0100 Subject: [PATCH 089/106] fix: GetMostSubscribedGoals shouldn't wrap goal in array --- server/src/utils/db.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/server/src/utils/db.ts b/server/src/utils/db.ts index 259403540..af2ac30b2 100644 --- a/server/src/utils/db.ts +++ b/server/src/utils/db.ts @@ -362,6 +362,11 @@ export async function GetMostSubscribedGoals( as: "goal", }, }, + { + $set: { + goal: { $arrayElemAt: ["$goal", 0] }, + }, + }, ])) as { goal: GoalDocument; subscriptions: integer }[]; return mostSubscribedGoals.map((e) => ({ From 912f575d83818d85d849bed4b7fb0fee6441b1c1 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Sat, 16 Apr 2022 04:33:44 +0100 Subject: [PATCH 090/106] test: tests for gpt/goals --- .../_playtype/targets/goals/router.test.ts | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 server/src/server/router/api/v1/games/_game/_playtype/targets/goals/router.test.ts diff --git a/server/src/server/router/api/v1/games/_game/_playtype/targets/goals/router.test.ts b/server/src/server/router/api/v1/games/_game/_playtype/targets/goals/router.test.ts new file mode 100644 index 000000000..ef950f2b5 --- /dev/null +++ b/server/src/server/router/api/v1/games/_game/_playtype/targets/goals/router.test.ts @@ -0,0 +1,98 @@ +import db from "external/mongo/db"; +import t from "tap"; +import ResetDBState from "test-utils/resets"; +import { + FakeOtherUser, + IIDXSPMilestoneGoals, + IIDXSPMilestoneGoalSubs, + TestingIIDXSPMilestone, +} from "test-utils/test-data"; +import dm from "deepmerge"; +import { GoalDocument, GoalSubscriptionDocument } from "tachi-common"; +import mockApi from "test-utils/mock-api"; + +// this is my lazy sample data for these tests. +const LoadLazySampleData = async () => { + await db.users.insert(FakeOtherUser); + await db.goals.insert(IIDXSPMilestoneGoals); + await db["goal-subs"].insert([ + ...IIDXSPMilestoneGoalSubs, + dm(IIDXSPMilestoneGoalSubs[0], { + userID: 2, + }), + ] as GoalSubscriptionDocument[]); +}; + +t.test("GET /api/v1/games/:game/:playtype/targets/goals/popular", (t) => { + t.beforeEach(ResetDBState); + t.beforeEach(LoadLazySampleData); + + t.test("Should return the most popular subscribed goals for this game.", async (t) => { + const res = await mockApi.get("/api/v1/games/iidx/SP/targets/goals/popular"); + + t.equal(res.statusCode, 200, "Should return 200."); + + for (const goalSub of IIDXSPMilestoneGoals) { + // i hate this monk 'feature'!!! + delete goalSub._id; + } + + // note: we have to sort the output here such that it's deterministic. + t.strictSame( + (res.body.body as GoalDocument[]).sort((a, b) => a.goalID.localeCompare(b.goalID)), + ( + [ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + dm(IIDXSPMilestoneGoals[0] as any, { __subscriptions: 2 }), + // eslint-disable-next-line @typescript-eslint/no-explicit-any + dm(IIDXSPMilestoneGoals[1] as any, { __subscriptions: 1 }), + // eslint-disable-next-line @typescript-eslint/no-explicit-any + dm(IIDXSPMilestoneGoals[2] as any, { __subscriptions: 1 }), + // eslint-disable-next-line @typescript-eslint/no-explicit-any + dm(IIDXSPMilestoneGoals[3] as any, { __subscriptions: 1 }), + ] as unknown as GoalDocument[] + ).sort((a, b) => a.goalID.localeCompare(b.goalID)), + "Should return the most subscribed goals." + ); + + t.end(); + }); + + t.test("Should return an empty array if nobody has done anything.", async (t) => { + const res = await mockApi.get("/api/v1/games/chunithm/Single/targets/goals/popular"); + + t.equal(res.statusCode, 200, "Should return 200."); + t.strictSame(res.body.body, []); + + t.end(); + }); + + t.end(); +}); + +t.test("GET /api/v1/games/:game/:playtype/targets/goals/:goalID", (t) => { + t.beforeEach(ResetDBState); + t.beforeEach(LoadLazySampleData); + + t.test("Should return information about the specified goal.", async (t) => { + await db.milestones.insert(TestingIIDXSPMilestone); + + const res = await mockApi.get("/api/v1/games/iidx/SP/targets/goals/eg_goal_1"); + + t.hasStrict(res.body.body, { + goal: { + goalID: "eg_goal_1", + }, + goalSubs: [ + { userID: 1, goalID: "eg_goal_1" }, + { userID: 2, goalID: "eg_goal_1" }, + ], + users: [{ id: 1 }, { id: 2 }], + parentMilestones: [{ milestoneID: TestingIIDXSPMilestone.milestoneID }], + }); + + t.end(); + }); + + t.end(); +}); From 0b8b7f5594c14d4c64d764bd82c23a310547ddd8 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Sat, 16 Apr 2022 05:05:21 +0100 Subject: [PATCH 091/106] fix: hide _id in GetMostSubscribedGoals output --- server/src/utils/db.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/server/src/utils/db.ts b/server/src/utils/db.ts index af2ac30b2..b349a66e0 100644 --- a/server/src/utils/db.ts +++ b/server/src/utils/db.ts @@ -367,6 +367,9 @@ export async function GetMostSubscribedGoals( goal: { $arrayElemAt: ["$goal", 0] }, }, }, + { + $unset: "goal._id", + }, ])) as { goal: GoalDocument; subscriptions: integer }[]; return mostSubscribedGoals.map((e) => ({ From b557d5a250ff9810ec989bb86dae2fefd5f46e09 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Sat, 16 Apr 2022 05:07:24 +0100 Subject: [PATCH 092/106] test: add tests for evaluate-for --- .../_playtype/targets/goals/router.test.ts | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/server/src/server/router/api/v1/games/_game/_playtype/targets/goals/router.test.ts b/server/src/server/router/api/v1/games/_game/_playtype/targets/goals/router.test.ts index ef950f2b5..294238e49 100644 --- a/server/src/server/router/api/v1/games/_game/_playtype/targets/goals/router.test.ts +++ b/server/src/server/router/api/v1/games/_game/_playtype/targets/goals/router.test.ts @@ -96,3 +96,52 @@ t.test("GET /api/v1/games/:game/:playtype/targets/goals/:goalID", (t) => { t.end(); }); + +t.test("GET /api/v1/games/:game/:playtype/targets/goals/:goalID/evaluate-for", (t) => { + t.beforeEach(ResetDBState); + t.beforeEach(LoadLazySampleData); + + t.test("Should evaluate a goal upon a user that exists.", async (t) => { + const res = await mockApi.get( + "/api/v1/games/iidx/SP/targets/goals/eg_goal_1/evaluate-for?userID=1" + ); + + t.equal(res.statusCode, 200, "Should return a status code of 200."); + + t.strictSame( + res.body.body, + { + achieved: false, + outOf: 5, + outOfHuman: "HARD CLEAR", + progress: null, + progressHuman: "NO DATA", + }, + "Should evaluate the goal for the user." + ); + + t.end(); + }); + + t.test("Should return 404 if the user doesn't exist.", async (t) => { + const res = await mockApi.get( + "/api/v1/games/iidx/SP/targets/goals/eg_goal_1/evaluate-for?userID=unknown" + ); + + t.equal(res.statusCode, 404); + + t.end(); + }); + + t.test("Should return 400 if the user exists but hasn't played this game.", async (t) => { + const res = await mockApi.get( + "/api/v1/games/iidx/SP/targets/goals/eg_goal_1/evaluate-for?userID=2" + ); + + t.equal(res.statusCode, 400); + + t.end(); + }); + + t.end(); +}); From 0a4858709f9f375988f5757f15d2529603cf4dcf Mon Sep 17 00:00:00 2001 From: zkldi Date: Sat, 16 Apr 2022 19:22:56 +0000 Subject: [PATCH 093/106] fix: add text index to milestone-sets --- server/src/external/mongo/indexes.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/server/src/external/mongo/indexes.ts b/server/src/external/mongo/indexes.ts index 0ecbea13b..cee11b818 100644 --- a/server/src/external/mongo/indexes.ts +++ b/server/src/external/mongo/indexes.ts @@ -56,6 +56,7 @@ const staticIndexes: Partial> = { index({ setID: 1 }, UNIQUE), index({ game: 1, playtype: 1 }), index({ milestones: 1 }), + index({ name: "text" }), ], imports: [index({ importID: 1 }, UNIQUE)], "import-timings": [ From 50e201ba0f011b6d7d8b9036479f261ba2da9784 Mon Sep 17 00:00:00 2001 From: zkldi Date: Sat, 16 Apr 2022 19:31:35 +0000 Subject: [PATCH 094/106] test: add gpt milestone-sets endpoint tests --- .../targets/milestone-sets/router.test.ts | 102 ++++++++++++++++++ .../test-utils/mock-db/milestone-sets.json | 1 + 2 files changed, 103 insertions(+) create mode 100644 server/src/server/router/api/v1/games/_game/_playtype/targets/milestone-sets/router.test.ts create mode 100644 server/src/test-utils/mock-db/milestone-sets.json diff --git a/server/src/server/router/api/v1/games/_game/_playtype/targets/milestone-sets/router.test.ts b/server/src/server/router/api/v1/games/_game/_playtype/targets/milestone-sets/router.test.ts new file mode 100644 index 000000000..a97dc3c65 --- /dev/null +++ b/server/src/server/router/api/v1/games/_game/_playtype/targets/milestone-sets/router.test.ts @@ -0,0 +1,102 @@ +import db from "external/mongo/db"; +import t from "tap"; +import ResetDBState from "test-utils/resets"; +import { TestingIIDXSPMilestone } from "test-utils/test-data"; +import dm from "deepmerge"; +import { MilestoneDocument, MilestoneSetDocument } from "tachi-common"; +import mockApi from "test-utils/mock-api"; + +const TestingIIDXSPMilestoneSet: MilestoneSetDocument = { + name: "Testing Milestone Set", + desc: "foo", + game: "iidx", + milestones: [TestingIIDXSPMilestone.milestoneID, "other_milestone"], + playtype: "SP", + setID: "milestone_set", +}; + +t.test("GET /api/v1/games/:game/:playtype/targets/milestone-sets", (t) => { + t.beforeEach(ResetDBState); + + function mkSet(merge: any) { + return dm(TestingIIDXSPMilestoneSet, merge) as MilestoneSetDocument; + } + + t.test("Should search the loaded milestone sets for this game.", async (t) => { + await db["milestone-sets"].insert([ + mkSet({ name: "Testing Set", setID: "name" }), + mkSet({ name: "Testing Other Set", setID: "similar_name" }), + mkSet({ name: "Different Name", setID: "radically_different_name" }), + mkSet({ + game: "chunithm", + playtype: "Single", + setID: "matching name but different gpt", + }), + mkSet({ playtype: "DP", setID: "matching name but different playtype" }), + ]); + + const res = await mockApi.get( + "/api/v1/games/iidx/SP/targets/milestone-sets?search=Testing" + ); + + t.hasStrict( + (res.body.body as MilestoneSetDocument[]).sort((a, b) => a.name.localeCompare(b.name)), + [{ setID: "similar_name" }, { setID: "name" }] + ); + + t.end(); + }); + + t.test("Should mandate a search field.", async (t) => { + const res = await mockApi.get("/api/v1/games/iidx/SP/targets/milestone-sets"); + + t.equal(res.statusCode, 400); + + t.end(); + }); + + t.end(); +}); + +t.test("GET /api/v1/games/:game/:playtype/targets/milestone-sets/:setID", (t) => { + t.beforeEach(ResetDBState); + t.beforeEach(async () => { + await db["milestone-sets"].insert(TestingIIDXSPMilestoneSet); + await db.milestones.insert([ + TestingIIDXSPMilestone, + dm(TestingIIDXSPMilestone, { milestoneID: "other_milestone" }) as MilestoneDocument, + ]); + }); + + t.test("Should return the milestone set and its milestones.", async (t) => { + const res = await mockApi.get("/api/v1/games/iidx/SP/targets/milestone-sets/milestone_set"); + + t.equal(res.statusCode, 200, "Should return 200."); + + t.hasStrict(res.body.body.milestoneSet, { + setID: TestingIIDXSPMilestoneSet.setID, + }); + + t.hasStrict( + (res.body.body.milestones as MilestoneDocument[]).sort((a, b) => + a.milestoneID.localeCompare(b.milestoneID) + ), + [ + { milestoneID: TestingIIDXSPMilestone.milestoneID }, + { milestoneID: "other_milestone" }, + ] + ); + + t.end(); + }); + + t.test("Should return 404 if the milestone set doesn't exist.", async (t) => { + const res = await mockApi.get("/api/v1/games/iidx/SP/targets/milestone-sets/foobar"); + + t.equal(res.statusCode, 404); + + t.end(); + }); + + t.end(); +}); diff --git a/server/src/test-utils/mock-db/milestone-sets.json b/server/src/test-utils/mock-db/milestone-sets.json new file mode 100644 index 000000000..0637a088a --- /dev/null +++ b/server/src/test-utils/mock-db/milestone-sets.json @@ -0,0 +1 @@ +[] \ No newline at end of file From aacdabe279d924138f0d2267e1af8ffb05409f13 Mon Sep 17 00:00:00 2001 From: zkldi Date: Sat, 16 Apr 2022 19:32:53 +0000 Subject: [PATCH 095/106] fix: SearchCollection no longer returns _id. --- server/src/lib/search/search.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/server/src/lib/search/search.ts b/server/src/lib/search/search.ts index a1942d152..dfb299ca1 100644 --- a/server/src/lib/search/search.ts +++ b/server/src/lib/search/search.ts @@ -49,6 +49,7 @@ export function SearchCollection( // hide nonsense { $match: { __textScore: { $gt: 0.25 } } }, { $limit: limit }, + { $unset: "_id" }, ]) .catch((err) => { logger.error( From f7988428d434f6df179d0966d07aad1b16b54d19 Mon Sep 17 00:00:00 2001 From: zkldi Date: Sat, 16 Apr 2022 19:44:38 +0000 Subject: [PATCH 096/106] test: add GPT milestone search tests --- .../targets/milestones/router.test.ts | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 server/src/server/router/api/v1/games/_game/_playtype/targets/milestones/router.test.ts diff --git a/server/src/server/router/api/v1/games/_game/_playtype/targets/milestones/router.test.ts b/server/src/server/router/api/v1/games/_game/_playtype/targets/milestones/router.test.ts new file mode 100644 index 000000000..db7d4fb1b --- /dev/null +++ b/server/src/server/router/api/v1/games/_game/_playtype/targets/milestones/router.test.ts @@ -0,0 +1,42 @@ +import { MilestoneDocument } from "tachi-common"; +import t from "tap"; +import mockApi from "test-utils/mock-api"; +import ResetDBState from "test-utils/resets"; +import dm from "deepmerge"; +import { TestingIIDXSPMilestone } from "test-utils/test-data"; +import db from "external/mongo/db"; + +t.test("GET /api/v1/games/:game/:playtype/targets/milestones", (t) => { + t.beforeEach(ResetDBState); + + function mkMilestone(merge: any) { + return dm(TestingIIDXSPMilestone, merge) as MilestoneDocument; + } + + t.test("Should search milestones.", async (t) => { + await db.milestones.insert([ + mkMilestone({ name: "Testing Set", milestoneID: "name" }), + mkMilestone({ name: "Testing Other Set", milestoneID: "similar_name" }), + mkMilestone({ name: "Different Name", milestoneID: "radically_different_name" }), + mkMilestone({ + game: "chunithm", + playtype: "Single", + milestoneID: "matching name but different gpt", + }), + mkMilestone({ playtype: "DP", milestoneID: "matching name but different playtype" }), + ]); + + const res = await mockApi.get("/api/v1/games/iidx/SP/targets/milestones?search=Testing"); + + t.equal(res.statusCode, 200); + + t.hasStrict( + (res.body.body as MilestoneDocument[]).sort((a, b) => a.name.localeCompare(b.name)), + [{ milestoneID: "similar_name" }, { milestoneID: "name" }] + ); + + t.end(); + }); + + t.end(); +}); From 918c26b2331738f4335d8a62869e2177624ad8a5 Mon Sep 17 00:00:00 2001 From: zkldi Date: Sat, 16 Apr 2022 20:01:19 +0000 Subject: [PATCH 097/106] fix: GetMostSubscribedMilestones shouldn't wrap milestones in array --- server/src/utils/db.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/server/src/utils/db.ts b/server/src/utils/db.ts index b349a66e0..fbc3a87df 100644 --- a/server/src/utils/db.ts +++ b/server/src/utils/db.ts @@ -408,6 +408,14 @@ export async function GetMostSubscribedMilestones( as: "milestone", }, }, + { + $set: { + milestone: { $arrayElemAt: ["$milestone", 0] }, + }, + }, + { + $unset: "milestone._id", + }, ])) as { milestone: MilestoneDocument; subscriptions: integer }[]; return mostSubscribedMilesones.map((e) => ({ From cd57962a8f231411803b6ef0fe64f3d3679192fc Mon Sep 17 00:00:00 2001 From: zkldi Date: Sat, 16 Apr 2022 20:02:28 +0000 Subject: [PATCH 098/106] test: add tests for gpt/milestones/popular --- .../targets/milestones/router.test.ts | 47 ++++++++++++++++++- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/server/src/server/router/api/v1/games/_game/_playtype/targets/milestones/router.test.ts b/server/src/server/router/api/v1/games/_game/_playtype/targets/milestones/router.test.ts index db7d4fb1b..e4b79aaa6 100644 --- a/server/src/server/router/api/v1/games/_game/_playtype/targets/milestones/router.test.ts +++ b/server/src/server/router/api/v1/games/_game/_playtype/targets/milestones/router.test.ts @@ -1,9 +1,13 @@ -import { MilestoneDocument } from "tachi-common"; +import { MilestoneDocument, MilestoneSubscriptionDocument } from "tachi-common"; import t from "tap"; import mockApi from "test-utils/mock-api"; import ResetDBState from "test-utils/resets"; import dm from "deepmerge"; -import { TestingIIDXSPMilestone } from "test-utils/test-data"; +import { + FakeOtherUser, + TestingIIDXSPMilestone, + TestingIIDXSPMilestoneSub, +} from "test-utils/test-data"; import db from "external/mongo/db"; t.test("GET /api/v1/games/:game/:playtype/targets/milestones", (t) => { @@ -40,3 +44,42 @@ t.test("GET /api/v1/games/:game/:playtype/targets/milestones", (t) => { t.end(); }); + +// this is my lazy sample data for these tests. +const LoadLazySampleData = async () => { + await db.users.insert(FakeOtherUser); + await db.milestones.insert([ + TestingIIDXSPMilestone, + dm(TestingIIDXSPMilestone, { milestoneID: "other_milestone" }) as MilestoneDocument, + ]); + await db["milestone-subs"].insert([ + TestingIIDXSPMilestoneSub, + dm(TestingIIDXSPMilestoneSub, { milestoneID: "other_milestone" }), + dm(TestingIIDXSPMilestoneSub, { + userID: 2, + }), + ] as MilestoneSubscriptionDocument[]); +}; + +t.test("GET /api/v1/games/:game/:playtype/targets/milestones/popular", (t) => { + t.beforeEach(ResetDBState); + t.beforeEach(LoadLazySampleData); + + t.test("Should return the most subscribed milestones for this GPT.", async (t) => { + const res = await mockApi.get("/api/v1/games/iidx/SP/targets/milestones/popular"); + + t.equal(res.statusCode, 200); + + t.hasStrict( + (res.body.body as MilestoneDocument[]).sort((a, b) => a.name.localeCompare(b.name)), + [ + { milestoneID: TestingIIDXSPMilestone.milestoneID, __subscriptions: 2 }, + { milestoneID: "other_milestone", __subscriptions: 1 }, + ] + ); + + t.end(); + }); + + t.end(); +}); From 696453a507c50d555c157d962a7bf8eec5c2f4f7 Mon Sep 17 00:00:00 2001 From: zkldi Date: Sat, 16 Apr 2022 20:20:20 +0000 Subject: [PATCH 099/106] test: add tests for gpt/milestones/milestoneID --- .../targets/milestones/router.test.ts | 64 ++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/server/src/server/router/api/v1/games/_game/_playtype/targets/milestones/router.test.ts b/server/src/server/router/api/v1/games/_game/_playtype/targets/milestones/router.test.ts index e4b79aaa6..565634ac3 100644 --- a/server/src/server/router/api/v1/games/_game/_playtype/targets/milestones/router.test.ts +++ b/server/src/server/router/api/v1/games/_game/_playtype/targets/milestones/router.test.ts @@ -1,10 +1,16 @@ -import { MilestoneDocument, MilestoneSubscriptionDocument } from "tachi-common"; +import { + MilestoneDocument, + MilestoneSetDocument, + MilestoneSubscriptionDocument, +} from "tachi-common"; import t from "tap"; import mockApi from "test-utils/mock-api"; import ResetDBState from "test-utils/resets"; import dm from "deepmerge"; import { FakeOtherUser, + IIDXSPMilestoneGoals, + IIDXSPMilestoneGoalSubs, TestingIIDXSPMilestone, TestingIIDXSPMilestoneSub, } from "test-utils/test-data"; @@ -83,3 +89,59 @@ t.test("GET /api/v1/games/:game/:playtype/targets/milestones/popular", (t) => { t.end(); }); + +t.test("GET /api/v1/games/:game/:playtype/targets/milestones/:milestoneID", (t) => { + t.beforeEach(ResetDBState); + t.beforeEach(async () => { + await Promise.all([ + db.goals.insert(IIDXSPMilestoneGoals), + db["goal-subs"].insert(IIDXSPMilestoneGoalSubs), + db["milestone-sets"].insert({ + setID: "set_id", + milestones: [TestingIIDXSPMilestone.milestoneID], + } as MilestoneSetDocument), + ]); + }); + t.beforeEach(LoadLazySampleData); + + t.test("Should return the milestone and its goals.", async (t) => { + const res = await mockApi.get( + `/api/v1/games/iidx/SP/targets/milestones/${TestingIIDXSPMilestone.milestoneID}` + ); + + t.hasStrict(res.body.body, { + milestone: { milestoneID: TestingIIDXSPMilestone.milestoneID }, + milestoneSubs: [{ userID: 1, milestoneID: TestingIIDXSPMilestone.milestoneID }], + users: [{ id: 1 }, { id: 2 }], + goals: [ + { goalID: "eg_goal_1" }, + { goalID: "eg_goal_2" }, + { goalID: "eg_goal_3" }, + { goalID: "eg_goal_4" }, + ], + parentMilestoneSets: [{ setID: "set_id" }], + }); + + t.end(); + }); + + t.test("Should return 404 if the requested milestone doesn't exist.", async (t) => { + const res = await mockApi.get("/api/v1/games/iidx/SP/targets/milestones/fake_milestone"); + + t.equal(res.statusCode, 404); + + t.end(); + }); + + t.test("Should return 404 if the milestone exists but for a different GPT.", async (t) => { + const res = await mockApi.get( + `/api/v1/games/iidx/DP/targets/milestones/${TestingIIDXSPMilestone.milestoneID}` + ); + + t.equal(res.statusCode, 404); + + t.end(); + }); + + t.end(); +}); From ecc5f9c4f2602a7d1458afdb1135bde43466f692 Mon Sep 17 00:00:00 2001 From: zkldi Date: Sat, 16 Apr 2022 20:58:02 +0000 Subject: [PATCH 100/106] scratch: BROKEN TESTS. PUSHING SO I CAN CONTINUE WORK AT HOME --- .../targets/milestones/router.test.ts | 169 +++++++++++++++++- 1 file changed, 168 insertions(+), 1 deletion(-) diff --git a/server/src/server/router/api/v1/games/_game/_playtype/targets/milestones/router.test.ts b/server/src/server/router/api/v1/games/_game/_playtype/targets/milestones/router.test.ts index 565634ac3..141c70a93 100644 --- a/server/src/server/router/api/v1/games/_game/_playtype/targets/milestones/router.test.ts +++ b/server/src/server/router/api/v1/games/_game/_playtype/targets/milestones/router.test.ts @@ -54,6 +54,7 @@ t.test("GET /api/v1/games/:game/:playtype/targets/milestones", (t) => { // this is my lazy sample data for these tests. const LoadLazySampleData = async () => { await db.users.insert(FakeOtherUser); + await db.goals.insert(IIDXSPMilestoneGoals); await db.milestones.insert([ TestingIIDXSPMilestone, dm(TestingIIDXSPMilestone, { milestoneID: "other_milestone" }) as MilestoneDocument, @@ -94,7 +95,6 @@ t.test("GET /api/v1/games/:game/:playtype/targets/milestones/:milestoneID", (t) t.beforeEach(ResetDBState); t.beforeEach(async () => { await Promise.all([ - db.goals.insert(IIDXSPMilestoneGoals), db["goal-subs"].insert(IIDXSPMilestoneGoalSubs), db["milestone-sets"].insert({ setID: "set_id", @@ -102,6 +102,7 @@ t.test("GET /api/v1/games/:game/:playtype/targets/milestones/:milestoneID", (t) } as MilestoneSetDocument), ]); }); + t.beforeEach(LoadLazySampleData); t.test("Should return the milestone and its goals.", async (t) => { @@ -145,3 +146,169 @@ t.test("GET /api/v1/games/:game/:playtype/targets/milestones/:milestoneID", (t) t.end(); }); + +t.test("GET /api/v1/games/:game/:playtype/targets/milestones/:milestoneID/evaluate-for", (t) => { + t.beforeEach(ResetDBState); + t.beforeEach(LoadLazySampleData); + + t.test("Should evaluate a milestone for a user.", async (t) => { + // remove all milestone subs -- the user isn't properly + // subscribed to this milestone due to test mocking. + // this will force a live calc. + await db["milestone-subs"].remove({}); + + const res = await mockApi.get( + `/api/v1/games/iidx/SP/targets/milestones/${TestingIIDXSPMilestone.milestoneID}/evaluate-for?userID=1` + ); + + t.equal(res.statusCode, 200); + + t.strictSame(res.body.body, { + goals: [ + { + charts: { + type: "single", + data: "c2311194e3897ddb5745b1760d2c0141f933e683", + }, + game: "iidx", + goalID: "eg_goal_1", + playtype: "SP", + timeAdded: 0, + name: "HC 5.1.1. SPA", + criteria: { + mode: "single", + value: 5, + key: "scoreData.lampIndex", + }, + }, + { + charts: { + type: "single", + data: "c2311194e3897ddb5745b1760d2c0141f933e683", + }, + game: "iidx", + goalID: "eg_goal_2", + playtype: "SP", + timeAdded: 0, + name: "HC 5.1.1. SPA", + criteria: { + mode: "single", + value: 2, + key: "scoreData.lampIndex", + }, + }, + { + charts: { + type: "single", + data: "c2311194e3897ddb5745b1760d2c0141f933e683", + }, + game: "iidx", + goalID: "eg_goal_3", + playtype: "SP", + timeAdded: 0, + name: "HC 5.1.1. SPA", + criteria: { + mode: "single", + value: 300, + key: "scoreData.lampIndex", + }, + }, + { + charts: { + type: "single", + data: "c2311194e3897ddb5745b1760d2c0141f933e683", + }, + game: "iidx", + goalID: "eg_goal_4", + playtype: "SP", + timeAdded: 0, + name: "HC 5.1.1. SPA", + criteria: { + mode: "single", + value: 1100, + key: "scoreData.lampIndex", + }, + }, + ], + goalResults: [ + { + achieved: false, + progress: null, + outOf: 5, + progressHuman: "NO DATA", + outOfHuman: "HARD CLEAR", + goalID: "eg_goal_1", + }, + { + achieved: false, + progress: null, + outOf: 2, + progressHuman: "NO DATA", + outOfHuman: "ASSIST CLEAR", + goalID: "eg_goal_2", + }, + { + achieved: false, + progress: null, + outOf: 300, + progressHuman: "NO DATA", + goalID: "eg_goal_3", + }, + { + achieved: false, + progress: null, + outOf: 1100, + progressHuman: "NO DATA", + goalID: "eg_goal_4", + }, + ], + achieved: false, + progress: 0, + outOf: 4, + }); + + t.end(); + }); + + t.test("Should return 404 if the requested milestone doesn't exist.", async (t) => { + const res = await mockApi.get( + "/api/v1/games/iidx/SP/targets/milestones/fake_milestone/evaluate-for?userID=1" + ); + + t.equal(res.statusCode, 404); + + t.end(); + }); + + t.test("Should return 404 if the milestone exists but for a different GPT.", async (t) => { + const res = await mockApi.get( + `/api/v1/games/iidx/DP/targets/milestones/${TestingIIDXSPMilestone.milestoneID}/evaluate-for?userID=1` + ); + + t.equal(res.statusCode, 404); + + t.end(); + }); + + t.test("Should return 404 if the requested user doesn't exist.", async (t) => { + const res = await mockApi.get( + `/api/v1/games/iidx/SP/targets/milestones/${TestingIIDXSPMilestone.milestoneID}/evaluate-for?userID=3` + ); + + t.equal(res.statusCode, 404); + + t.end(); + }); + + t.test("Should return 400 if the requested user hasn't played this gpt.", async (t) => { + const res = await mockApi.get( + `/api/v1/games/iidx/SP/targets/milestones/${TestingIIDXSPMilestone.milestoneID}/evaluate-for?userID=2` + ); + + t.equal(res.statusCode, 400); + + t.end(); + }); + + t.end(); +}); From 91297d6d2926b86ac6b1ff6e0ba25c190b2468c1 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Sun, 17 Apr 2022 03:26:36 +0100 Subject: [PATCH 101/106] fix: limit percent to 2dp in progressHuman --- server/src/lib/targets/goals.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/lib/targets/goals.ts b/server/src/lib/targets/goals.ts index 88e0fb81f..a5063cf38 100644 --- a/server/src/lib/targets/goals.ts +++ b/server/src/lib/targets/goals.ts @@ -227,7 +227,7 @@ export function HumaniseGoalProgress( switch (key) { case "scoreData.gradeIndex": - return `${gptConfig.grades[value]} (${userPB?.scoreData.percent ?? "0"}%)`; + return `${gptConfig.grades[value]} (${userPB?.scoreData.percent.toFixed(2) ?? "0"}%)`; case "scoreData.lampIndex": if (userPB && (game === "iidx" || game === "bms" || game === "pms")) { return `${gptConfig.lamps[value]} (BP: ${ From 9574ea80fdfd2d2b21c56df3509599acbb36bde6 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Sun, 17 Apr 2022 03:27:55 +0100 Subject: [PATCH 102/106] fix: de-corrupt test-data that requested user get a lamp of '1100' --- server/src/test-utils/test-data.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/test-utils/test-data.ts b/server/src/test-utils/test-data.ts index dbd299efc..2d09d4762 100644 --- a/server/src/test-utils/test-data.ts +++ b/server/src/test-utils/test-data.ts @@ -494,8 +494,8 @@ export const TestingIIDXSPMilestone: MilestoneDocument = { export const IIDXSPMilestoneGoals: GoalDocument[] = [ dm(HC511Goal, { goalID: "eg_goal_1" }) as GoalDocument, dm(HC511Goal, { goalID: "eg_goal_2", criteria: { value: 2 } }), - dm(HC511Goal, { goalID: "eg_goal_3", criteria: { value: 300 } }), - dm(HC511Goal, { goalID: "eg_goal_4", criteria: { value: 1100 } }), + dm(HC511Goal, { goalID: "eg_goal_3", criteria: { key: "scoreData.score", value: 300 } }), + dm(HC511Goal, { goalID: "eg_goal_4", criteria: { key: "scoreData.score", value: 1100 } }), ]; export const IIDXSPMilestoneGoalSubs: GoalSubscriptionDocument[] = [ From 2709fb1d32702d6b5fe7e66e1403ad349925b214 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Sun, 17 Apr 2022 03:34:01 +0100 Subject: [PATCH 103/106] fix(test): evaluate milestones against someone with a play --- .../targets/milestones/router.test.ts | 87 +++++-------------- 1 file changed, 20 insertions(+), 67 deletions(-) diff --git a/server/src/server/router/api/v1/games/_game/_playtype/targets/milestones/router.test.ts b/server/src/server/router/api/v1/games/_game/_playtype/targets/milestones/router.test.ts index 141c70a93..c90078b2a 100644 --- a/server/src/server/router/api/v1/games/_game/_playtype/targets/milestones/router.test.ts +++ b/server/src/server/router/api/v1/games/_game/_playtype/targets/milestones/router.test.ts @@ -13,6 +13,7 @@ import { IIDXSPMilestoneGoalSubs, TestingIIDXSPMilestone, TestingIIDXSPMilestoneSub, + TestingIIDXSPScorePB, } from "test-utils/test-data"; import db from "external/mongo/db"; @@ -157,113 +158,65 @@ t.test("GET /api/v1/games/:game/:playtype/targets/milestones/:milestoneID/evalua // this will force a live calc. await db["milestone-subs"].remove({}); + await db["personal-bests"].insert(TestingIIDXSPScorePB); + const res = await mockApi.get( `/api/v1/games/iidx/SP/targets/milestones/${TestingIIDXSPMilestone.milestoneID}/evaluate-for?userID=1` ); t.equal(res.statusCode, 200); - t.strictSame(res.body.body, { + t.hasStrict(res.body.body, { goals: [ { - charts: { - type: "single", - data: "c2311194e3897ddb5745b1760d2c0141f933e683", - }, - game: "iidx", goalID: "eg_goal_1", - playtype: "SP", - timeAdded: 0, - name: "HC 5.1.1. SPA", - criteria: { - mode: "single", - value: 5, - key: "scoreData.lampIndex", - }, }, { - charts: { - type: "single", - data: "c2311194e3897ddb5745b1760d2c0141f933e683", - }, - game: "iidx", goalID: "eg_goal_2", - playtype: "SP", - timeAdded: 0, - name: "HC 5.1.1. SPA", - criteria: { - mode: "single", - value: 2, - key: "scoreData.lampIndex", - }, }, { - charts: { - type: "single", - data: "c2311194e3897ddb5745b1760d2c0141f933e683", - }, - game: "iidx", goalID: "eg_goal_3", - playtype: "SP", - timeAdded: 0, - name: "HC 5.1.1. SPA", - criteria: { - mode: "single", - value: 300, - key: "scoreData.lampIndex", - }, }, { - charts: { - type: "single", - data: "c2311194e3897ddb5745b1760d2c0141f933e683", - }, - game: "iidx", goalID: "eg_goal_4", - playtype: "SP", - timeAdded: 0, - name: "HC 5.1.1. SPA", - criteria: { - mode: "single", - value: 1100, - key: "scoreData.lampIndex", - }, }, ], goalResults: [ { - achieved: false, - progress: null, + achieved: true, + progress: 6, outOf: 5, - progressHuman: "NO DATA", + progressHuman: "EX HARD CLEAR (BP: 2)", outOfHuman: "HARD CLEAR", goalID: "eg_goal_1", }, { - achieved: false, - progress: null, + achieved: true, + progress: 6, outOf: 2, - progressHuman: "NO DATA", + progressHuman: "EX HARD CLEAR (BP: 2)", outOfHuman: "ASSIST CLEAR", goalID: "eg_goal_2", }, { - achieved: false, - progress: null, + achieved: true, + progress: 1479, outOf: 300, - progressHuman: "NO DATA", + outOfHuman: "300", + progressHuman: "1479", goalID: "eg_goal_3", }, { - achieved: false, - progress: null, + achieved: true, + progress: 1479, outOf: 1100, - progressHuman: "NO DATA", + outOfHuman: "1100", + progressHuman: "1479", goalID: "eg_goal_4", }, ], - achieved: false, - progress: 0, + achieved: true, + progress: 4, outOf: 4, }); From d83366ca7d6efb7d73a7fef8e9dca74f0dc2a448 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Sun, 17 Apr 2022 03:34:10 +0100 Subject: [PATCH 104/106] style: remove unused import --- .../games/_game/_playtype/targets/milestones/router.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/server/router/api/v1/games/_game/_playtype/targets/milestones/router.test.ts b/server/src/server/router/api/v1/games/_game/_playtype/targets/milestones/router.test.ts index c90078b2a..9eebb3722 100644 --- a/server/src/server/router/api/v1/games/_game/_playtype/targets/milestones/router.test.ts +++ b/server/src/server/router/api/v1/games/_game/_playtype/targets/milestones/router.test.ts @@ -1,3 +1,5 @@ +import dm from "deepmerge"; +import db from "external/mongo/db"; import { MilestoneDocument, MilestoneSetDocument, @@ -6,7 +8,6 @@ import { import t from "tap"; import mockApi from "test-utils/mock-api"; import ResetDBState from "test-utils/resets"; -import dm from "deepmerge"; import { FakeOtherUser, IIDXSPMilestoneGoals, @@ -15,7 +16,6 @@ import { TestingIIDXSPMilestoneSub, TestingIIDXSPScorePB, } from "test-utils/test-data"; -import db from "external/mongo/db"; t.test("GET /api/v1/games/:game/:playtype/targets/milestones", (t) => { t.beforeEach(ResetDBState); From ac4f3798205f3494459198ab862d89ca8697fe9e Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Sun, 17 Apr 2022 03:35:58 +0100 Subject: [PATCH 105/106] fix: add failsafes incase a corrupt goal gets evaluated again --- server/src/lib/targets/goals.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/server/src/lib/targets/goals.ts b/server/src/lib/targets/goals.ts index a5063cf38..cb4b87d3b 100644 --- a/server/src/lib/targets/goals.ts +++ b/server/src/lib/targets/goals.ts @@ -227,8 +227,20 @@ export function HumaniseGoalProgress( switch (key) { case "scoreData.gradeIndex": + if (!gptConfig.grades[value]) { + throw new Error( + `Corrupt goal -- requested a grade of ${value}, which doesn't exist for this game.` + ); + } + return `${gptConfig.grades[value]} (${userPB?.scoreData.percent.toFixed(2) ?? "0"}%)`; case "scoreData.lampIndex": + if (!gptConfig.lamps[value]) { + throw new Error( + `Corrupt goal -- requested a lamp of ${value}, which doesn't exist for this game.` + ); + } + if (userPB && (game === "iidx" || game === "bms" || game === "pms")) { return `${gptConfig.lamps[value]} (BP: ${ (userPB as PBWithBadPoor).scoreData.hitMeta.bp ?? "N/A" From eaafb9e7585c8d7bcf47f4e12f32c853bf8cc3e1 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Sun, 17 Apr 2022 03:47:36 +0100 Subject: [PATCH 106/106] fix(lockfile): recreate lockfile, fix faulty merge commit --- server/pnpm-lock.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/pnpm-lock.yaml b/server/pnpm-lock.yaml index 86288c8a7..d279f9574 100644 --- a/server/pnpm-lock.yaml +++ b/server/pnpm-lock.yaml @@ -95,7 +95,7 @@ dependencies: fast-json-stable-hash: 1.0.3 fast-xml-parser: 4.0.2 helmet: 5.0.2 - html-entities: 2.3.3 + html-entities: 2.3.2 json5: 2.2.0 mkdirp: 1.0.4 mongodb: 3.6.3 @@ -2412,7 +2412,7 @@ packages: resolution: {integrity: sha512-/aUslKhzkTNCQUB2qTX84lVmfia9NyjP3WpDGtj/WxhwBzWBYUV3DgUpurHTme8UTPcPlAD1DJ+b0nN/t50zDQ==} dependencies: boolbase: 1.0.0 - css-what: 6.1.0 + css-what: 5.1.0 domhandler: 4.3.1 domutils: 2.8.0 nth-check: 2.0.1 @@ -4354,7 +4354,7 @@ packages: /node-html-parser/5.3.3: resolution: {integrity: sha512-ncg1033CaX9UexbyA7e1N0aAoAYRDiV8jkTvzEnfd1GDvzFdrsXLzR4p4ik8mwLgnaKP/jyUFWDy9q3jvRT2Jw==} dependencies: - css-select: 4.3.0 + css-select: 4.2.1 he: 1.2.0 dev: false