From c70639d914d07df41789c5acc4df33db4e57e614 Mon Sep 17 00:00:00 2001 From: sgfost Date: Thu, 1 Feb 2024 19:21:50 -0700 Subject: [PATCH 1/9] deps(WIP): upgrade typeorm to 0.3.20, fix API breaking changes https://typeorm.io/changelog#breaking-changes-2 this commit addresses find(), findOne(), and similar methods changes TODO: migrate from manager/connection to DataSource (https://typeorm.io/changelog#deprecations) [no ci] --- server/package-lock.json | 1014 ++++++++++++++------ server/package.json | 4 +- server/src/entity/TournamentRoundSignup.ts | 12 +- server/src/services/account.ts | 26 +- server/src/services/admin.ts | 6 +- server/src/services/game.ts | 6 +- server/src/services/persistence.ts | 4 +- server/src/services/quiz.ts | 12 +- server/src/services/sologame.ts | 17 +- server/src/services/stats.ts | 39 +- server/src/services/survey.ts | 7 +- server/src/services/tournament.ts | 12 +- server/tests/services/account.test.ts | 8 +- 13 files changed, 813 insertions(+), 354 deletions(-) diff --git a/server/package-lock.json b/server/package-lock.json index e6e3747c7..cc1cd6154 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -47,7 +47,7 @@ "redis": "^3.1.1", "reflect-metadata": "^0.1.10", "tinyqueue": "^2.0.3", - "typeorm": "^0.2.30", + "typeorm": "^0.3.20", "uuid": "^8.3.2", "validator": "^13.5.2" }, @@ -86,7 +86,7 @@ "ts-node": "^10.7.0", "ts-node-dev": "^2.0.0", "tsconfig-paths": "^4.0.0", - "typeorm-fixtures-cli": "^1.9.0", + "typeorm-fixtures-cli": "^4.0.0", "typescript": "^4.6.4", "yamljs": "^0.3.0" }, @@ -1008,7 +1008,7 @@ "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, + "devOptional": true, "dependencies": { "@jridgewell/trace-mapping": "0.3.9" }, @@ -1020,7 +1020,7 @@ "version": "0.3.9", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, + "devOptional": true, "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" @@ -1080,13 +1080,19 @@ } }, "node_modules/@faker-js/faker": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-6.3.1.tgz", - "integrity": "sha512-8YXBE2ZcU/pImVOHX7MWrSR/X5up7t6rPWZlk34RwZEcdr3ua6X+32pSd6XuOQRN+vbuvYNfA6iey8NbrjuMFQ==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-8.4.0.tgz", + "integrity": "sha512-htW87352wzUCdX1jyUQocUcmAaFqcR/w082EC8iP/gtkF0K+aKcBp0hR5Arb7dzR8tQ1TrhE9DNa5EbJELm84w==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/fakerjs" + } + ], "engines": { - "node": ">=14.0.0", - "npm": ">=6.0.0" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0", + "npm": ">=6.14.13" } }, "node_modules/@gamestdio/clock": { @@ -1102,49 +1108,12 @@ "@gamestdio/clock": "^1.1.9" } }, - "node_modules/@hapi/address": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@hapi/address/-/address-4.1.0.tgz", - "integrity": "sha512-SkszZf13HVgGmChdHo/PxchnSaCJ6cetVqLzyciudzZRT0jcOouIF/Q93mgjw8cce+D+4F4C1Z/WrfFN+O3VHQ==", - "deprecated": "Moved to 'npm install @sideway/address'", - "dev": true, - "dependencies": { - "@hapi/hoek": "^9.0.0" - } - }, - "node_modules/@hapi/formula": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@hapi/formula/-/formula-2.0.0.tgz", - "integrity": "sha512-V87P8fv7PI0LH7LiVi8Lkf3x+KCO7pQozXRssAHNXXL9L1K+uyu4XypLXwxqVDKgyQai6qj3/KteNlrqDx4W5A==", - "deprecated": "Moved to 'npm install @sideway/formula'", - "dev": true - }, "node_modules/@hapi/hoek": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", "dev": true }, - "node_modules/@hapi/joi": { - "version": "17.1.1", - "resolved": "https://registry.npmjs.org/@hapi/joi/-/joi-17.1.1.tgz", - "integrity": "sha512-p4DKeZAoeZW4g3u7ZeRo+vCDuSDgSvtsB/NpfjXEHTUjSeINAi/RrVOWiVQ1isaoLzMvFEhe8n5065mQq1AdQg==", - "deprecated": "Switch to 'npm install joi'", - "dev": true, - "dependencies": { - "@hapi/address": "^4.0.1", - "@hapi/formula": "^2.0.0", - "@hapi/hoek": "^9.0.0", - "@hapi/pinpoint": "^2.0.0", - "@hapi/topo": "^5.0.0" - } - }, - "node_modules/@hapi/pinpoint": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@hapi/pinpoint/-/pinpoint-2.0.1.tgz", - "integrity": "sha512-EKQmr16tM8s16vTT3cA5L0kZZcTMU5DUOZTuvpnY738m+jyP3JIUj+Mm1xc1rsLkGBQ/gVnfKYPwOmPg1tUR4Q==", - "dev": true - }, "node_modules/@hapi/topo": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", @@ -1174,6 +1143,95 @@ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -1538,7 +1596,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", - "dev": true, + "devOptional": true, "engines": { "node": ">=6.0.0" } @@ -1556,7 +1614,7 @@ "version": "1.4.15", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true + "devOptional": true }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.20", @@ -1568,6 +1626,16 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@mongodb-js/saslprep": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.4.tgz", + "integrity": "sha512-8zJ8N1x51xo9hwPh6AWnKdLGEC5N3lDa6kms1YHmFBoRhTpJR6HG8wWk0td1MVCu9cD4YBrvjZEtd5Obw0Fbnw==", + "optional": true, + "peer": true, + "dependencies": { + "sparse-bitfield": "^3.0.3" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1603,6 +1671,15 @@ "node": ">= 8" } }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@sentry/core": { "version": "5.29.2", "resolved": "https://registry.npmjs.org/@sentry/core/-/core-5.29.2.tgz", @@ -1698,6 +1775,27 @@ "node": ">=6" } }, + "node_modules/@sideway/address": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", + "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", + "dev": true, + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@sideway/formula": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", + "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", + "dev": true + }, + "node_modules/@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", + "dev": true + }, "node_modules/@sinclair/typebox": { "version": "0.24.51", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz", @@ -1731,25 +1829,25 @@ "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", - "dev": true + "devOptional": true }, "node_modules/@tsconfig/node12": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true + "devOptional": true }, "node_modules/@tsconfig/node14": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true + "devOptional": true }, "node_modules/@tsconfig/node16": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "dev": true + "devOptional": true }, "node_modules/@types/babel__core": { "version": "7.20.5", @@ -1913,12 +2011,6 @@ "@types/node": "*" } }, - "node_modules/@types/hapi__joi": { - "version": "17.1.14", - "resolved": "https://registry.npmjs.org/@types/hapi__joi/-/hapi__joi-17.1.14.tgz", - "integrity": "sha512-elV1VhwXUfA1sw59ij75HWyCH+3cA7xLbaOY9GQ+iQo/S+jSSf22LNZAmsXMdfV8DZwquCZaCT+F43Xf6/txrQ==", - "dev": true - }, "node_modules/@types/http-errors": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", @@ -2225,6 +2317,24 @@ "integrity": "sha512-q0JomTsJ2I5Mv7dhHhQLGjMvX0JJm5dyZ1DXQySIUzU1UlwzB8bt+R6+LODUbz0UDIOvEzGc28tk27gBJw2N8Q==", "dev": true }, + "node_modules/@types/webidl-conversions": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", + "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==", + "optional": true, + "peer": true + }, + "node_modules/@types/whatwg-url": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.2.tgz", + "integrity": "sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==", + "optional": true, + "peer": true, + "dependencies": { + "@types/node": "*", + "@types/webidl-conversions": "*" + } + }, "node_modules/@types/ws": { "version": "7.4.7", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.7.tgz", @@ -2248,11 +2358,6 @@ "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", "dev": true }, - "node_modules/@types/zen-observable": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/@types/zen-observable/-/zen-observable-0.8.3.tgz", - "integrity": "sha512-fbF6oTd4sGGy0xjHPKAt+eS2CrxJ3+6gQ3FGcBoIJR2TLAyCkCyI8JqZNy+FeON0AhVgNJoUumVoZQjBFUqHkw==" - }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "5.62.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", @@ -2489,7 +2594,7 @@ "version": "8.3.1", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.1.tgz", "integrity": "sha512-TgUZgYvqZprrl7YldZNoa9OciCAyZR+Ejm9eXzKCmjsF5IKp/wgQ7Z/ZpjpGTIUPwrHQIcYeI8qDh4PsEwxMbw==", - "dev": true, + "devOptional": true, "engines": { "node": ">=0.4.0" } @@ -2609,7 +2714,7 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true + "devOptional": true }, "node_modules/argparse": { "version": "1.0.10", @@ -2898,6 +3003,7 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -3180,9 +3286,9 @@ "dev": true }, "node_modules/class-transformer": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.3.1.tgz", - "integrity": "sha512-cKFwohpJbuMovS8xVLmn8N2AUbAuc8pVo4zEfsUVo8qgECOogns1WVk/FkOZoxhOPTyTYFckuoH+13FO+MQ8GA==", + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", + "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==", "dev": true }, "node_modules/cli-highlight": { @@ -3265,16 +3371,6 @@ "node": ">=12" } }, - "node_modules/cluster-key-slot": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", - "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", - "optional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -3348,7 +3444,8 @@ "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true }, "node_modules/connect-redis": { "version": "6.1.3", @@ -3449,7 +3546,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true + "devOptional": true }, "node_modules/cron-parser": { "version": "2.18.0", @@ -3467,7 +3564,6 @@ "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -3518,6 +3614,11 @@ "node": "*" } }, + "node_modules/dayjs": { + "version": "1.11.10", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", + "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==" + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -3654,7 +3755,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, + "devOptional": true, "engines": { "node": ">=0.3.1" } @@ -3717,6 +3818,11 @@ "xtend": "^4.0.0" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" + }, "node_modules/ecdsa-sig-formatter": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", @@ -4549,6 +4655,32 @@ } } }, + "node_modules/foreground-child": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", + "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/form-data": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", @@ -4581,7 +4713,8 @@ "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true }, "node_modules/fsevents": { "version": "2.3.3", @@ -4675,6 +4808,7 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -4772,6 +4906,48 @@ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, + "node_modules/graphology": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/graphology/-/graphology-0.25.4.tgz", + "integrity": "sha512-33g0Ol9nkWdD6ulw687viS8YJQBxqG5LWII6FI6nul0pq6iM2t5EKquOTFDbyTblRB3O9I+7KX4xI8u5ffekAQ==", + "dev": true, + "dependencies": { + "events": "^3.3.0", + "obliterator": "^2.0.2" + }, + "peerDependencies": { + "graphology-types": ">=0.24.0" + } + }, + "node_modules/graphology-dag": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/graphology-dag/-/graphology-dag-0.3.0.tgz", + "integrity": "sha512-dg4JPb+/LDEbDinZIj7ezWzlEXDRokshdpTL8oAuftE9Uy0uTKGOKSmYULY8p3j/vw0HB31Wog9T/kpqprUQpg==", + "dev": true, + "dependencies": { + "graphology-utils": "^2.4.1", + "mnemonist": "^0.39.0" + }, + "peerDependencies": { + "graphology-types": ">=0.19.0" + } + }, + "node_modules/graphology-types": { + "version": "0.24.7", + "resolved": "https://registry.npmjs.org/graphology-types/-/graphology-types-0.24.7.tgz", + "integrity": "sha512-tdcqOOpwArNjEr0gNQKCXwaNCWnQJrog14nJNQPeemcLnXQUUGrsCWpWkVKt46zLjcS6/KGoayeJfHHyPDlvwA==", + "dev": true, + "peer": true + }, + "node_modules/graphology-utils": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/graphology-utils/-/graphology-utils-2.5.2.tgz", + "integrity": "sha512-ckHg8MXrXJkOARk56ZaSCM1g1Wihe2d6iTmz1enGOz4W/l831MBCKSayeFQfowgF8wd+PQ4rlch/56Vs/VZLDQ==", + "dev": true, + "peerDependencies": { + "graphology-types": ">=0.23.0" + } + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -4974,6 +5150,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -5005,32 +5182,12 @@ "node": ">= 0.10" } }, - "node_modules/ioredis": { - "version": "4.28.5", - "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-4.28.5.tgz", - "integrity": "sha512-3GYo0GJtLqgNXj4YhrisLaNNvWSNwSS2wS4OELGfGxH8I69+XfNdnmV1AyN+ZqMh0i7eX+SWjrwFKDBDgfBC1A==", + "node_modules/ip": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", + "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", "optional": true, - "peer": true, - "dependencies": { - "cluster-key-slot": "^1.1.0", - "debug": "^4.3.1", - "denque": "^1.1.0", - "lodash.defaults": "^4.2.0", - "lodash.flatten": "^4.4.0", - "lodash.isarguments": "^3.1.0", - "p-map": "^2.1.0", - "redis-commands": "1.7.0", - "redis-errors": "^1.2.0", - "redis-parser": "^3.0.0", - "standard-as-callback": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ioredis" - } + "peer": true }, "node_modules/ip-regex": { "version": "2.1.0", @@ -5239,6 +5396,23 @@ "node": ">=8" } }, + "node_modules/jackspeak": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", + "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/jake": { "version": "10.8.7", "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.7.tgz", @@ -6350,6 +6524,19 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/joi": { + "version": "17.12.1", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.12.1.tgz", + "integrity": "sha512-vtxmq+Lsc5SlfqotnfVjlViWfOL9nt/avKNbKYizwf6gsCfq9NYY/ceYRMFD8XDdrjJ9abJyScWmhmIiy+XRtQ==", + "dev": true, + "dependencies": { + "@hapi/hoek": "^9.3.0", + "@hapi/topo": "^5.1.0", + "@sideway/address": "^4.1.5", + "@sideway/formula": "^3.0.1", + "@sideway/pinpoint": "^2.0.0" + } + }, "node_modules/joycon": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", @@ -6612,32 +6799,11 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, - "node_modules/lodash.defaults": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", - "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", - "optional": true, - "peer": true - }, - "node_modules/lodash.flatten": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", - "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", - "optional": true, - "peer": true - }, "node_modules/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" }, - "node_modules/lodash.isarguments": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", - "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==", - "optional": true, - "peer": true - }, "node_modules/lodash.isboolean": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", @@ -6751,7 +6917,7 @@ "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true + "devOptional": true }, "node_modules/makeerror": { "version": "1.0.12", @@ -6860,6 +7026,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -6875,10 +7042,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minipass": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", + "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, "bin": { "mkdirp": "bin/cmd.js" }, @@ -6886,6 +7062,15 @@ "node": ">=10" } }, + "node_modules/mnemonist": { + "version": "0.39.7", + "resolved": "https://registry.npmjs.org/mnemonist/-/mnemonist-0.39.7.tgz", + "integrity": "sha512-ix3FwHWZgdXUt0dHM8bCrI4r1KMeYx8bCunPCYmvKXq4tn6gbNsqrsb4q0kDbDqbpIOvEaW5Sn+dmDwGydfrwA==", + "dev": true, + "dependencies": { + "obliterator": "^2.0.1" + } + }, "node_modules/moment": { "version": "2.29.4", "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", @@ -6906,27 +7091,34 @@ } }, "node_modules/mongodb": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.7.4.tgz", - "integrity": "sha512-K5q8aBqEXMwWdVNh94UQTwZ6BejVbFhh1uB6c5FKtPE9eUMZPUO3sRZdgIEcHSrAWmxzpG/FeODDKL388sqRmw==", + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-5.9.2.tgz", + "integrity": "sha512-H60HecKO4Bc+7dhOv4sJlgvenK4fQNqqUIlXxZYQNbfEWSALGAwGoyJd/0Qwk4TttFXUOHJ2ZJQe/52ScaUwtQ==", + "optional": true, + "peer": true, "dependencies": { - "bl": "^2.2.1", - "bson": "^1.1.4", - "denque": "^1.4.1", - "optional-require": "^1.1.8", - "safe-buffer": "^5.1.2" + "bson": "^5.5.0", + "mongodb-connection-string-url": "^2.6.0", + "socks": "^2.7.1" }, "engines": { - "node": ">=4" + "node": ">=14.20.1" }, "optionalDependencies": { - "saslprep": "^1.0.0" + "@mongodb-js/saslprep": "^1.1.0" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.0.0", + "kerberos": "^1.0.0 || ^2.0.0", + "mongodb-client-encryption": ">=2.3.0 <3", + "snappy": "^7.2.2" }, "peerDependenciesMeta": { - "aws4": { + "@aws-sdk/credential-providers": { "optional": true }, - "bson-ext": { + "@mongodb-js/zstd": { "optional": true }, "kerberos": { @@ -6935,23 +7127,30 @@ "mongodb-client-encryption": { "optional": true }, - "mongodb-extjson": { - "optional": true - }, "snappy": { "optional": true } } }, - "node_modules/mongodb/node_modules/optional-require": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/optional-require/-/optional-require-1.1.8.tgz", - "integrity": "sha512-jq83qaUb0wNg9Krv1c5OQ+58EK+vHde6aBPzLvPPqJm89UQWsvSuFy9X/OSNJnFeSOKo7btE0n8Nl2+nE+z5nA==", + "node_modules/mongodb-connection-string-url": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.6.0.tgz", + "integrity": "sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ==", + "optional": true, + "peer": true, "dependencies": { - "require-at": "^1.0.6" - }, + "@types/whatwg-url": "^8.2.1", + "whatwg-url": "^11.0.0" + } + }, + "node_modules/mongodb/node_modules/bson": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/bson/-/bson-5.5.1.tgz", + "integrity": "sha512-ix0EwukN2EpC0SRWIj/7B5+A6uQMQy6KMREI9qQqvgpkV2frH63T0UDVd1SYedL6dNCmDBYB3QtXi4ISk9YT+g==", + "optional": true, + "peer": true, "engines": { - "node": ">=4" + "node": ">=14.20.1" } }, "node_modules/mongoose": { @@ -6990,8 +7189,57 @@ "mongoose": "*" } }, - "node_modules/mpath": { - "version": "0.8.4", + "node_modules/mongoose/node_modules/mongodb": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.7.4.tgz", + "integrity": "sha512-K5q8aBqEXMwWdVNh94UQTwZ6BejVbFhh1uB6c5FKtPE9eUMZPUO3sRZdgIEcHSrAWmxzpG/FeODDKL388sqRmw==", + "dependencies": { + "bl": "^2.2.1", + "bson": "^1.1.4", + "denque": "^1.4.1", + "optional-require": "^1.1.8", + "safe-buffer": "^5.1.2" + }, + "engines": { + "node": ">=4" + }, + "optionalDependencies": { + "saslprep": "^1.0.0" + }, + "peerDependenciesMeta": { + "aws4": { + "optional": true + }, + "bson-ext": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "mongodb-extjson": { + "optional": true + }, + "snappy": { + "optional": true + } + } + }, + "node_modules/mongoose/node_modules/mongodb/node_modules/optional-require": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/optional-require/-/optional-require-1.1.8.tgz", + "integrity": "sha512-jq83qaUb0wNg9Krv1c5OQ+58EK+vHde6aBPzLvPPqJm89UQWsvSuFy9X/OSNJnFeSOKo7btE0n8Nl2+nE+z5nA==", + "dependencies": { + "require-at": "^1.0.6" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mpath": { + "version": "0.8.4", "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.8.4.tgz", "integrity": "sha512-DTxNZomBcTWlrMW76jy1wvV37X/cNNxPW1y2Jzd4DZkAaC5ZGsm8bfGfNOthcDuRJujXLqiuS6o3Tpy0JEoh7g==", "engines": { @@ -7216,6 +7464,12 @@ "node": ">= 0.4" } }, + "node_modules/obliterator": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/obliterator/-/obliterator-2.0.4.tgz", + "integrity": "sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ==", + "dev": true + }, "node_modules/on-exit-leak-free": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", @@ -7350,16 +7604,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-map": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", - "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", - "optional": true, - "peer": true, - "engines": { - "node": ">=6" - } - }, "node_modules/p-retry": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", @@ -7541,6 +7785,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -7549,7 +7794,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, "engines": { "node": ">=8" } @@ -7560,6 +7804,29 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, + "node_modules/path-scurry": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", + "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", + "dependencies": { + "lru-cache": "^9.1.1 || ^10.0.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", + "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", + "engines": { + "node": "14 || >=16.14" + } + }, "node_modules/path-to-regexp": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", @@ -7970,7 +8237,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, + "devOptional": true, "engines": { "node": ">=6" } @@ -8346,11 +8613,6 @@ "node": ">=6" } }, - "node_modules/sax": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz", - "integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==" - }, "node_modules/secure-json-parse": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", @@ -8476,7 +8738,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "dependencies": { "shebang-regex": "^3.0.0" }, @@ -8488,7 +8749,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, "engines": { "node": ">=8" } @@ -8570,6 +8830,32 @@ "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz", "integrity": "sha512-VZBmZP8WU3sMOZm1bdgTadsQbcscK0UM8oKxKVBs4XAhUo2Xxzm/OFMGBkPusxw9xL3Uy8LrzEqGqJhclsr0yA==" }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "optional": true, + "peer": true, + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", + "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", + "optional": true, + "peer": true, + "dependencies": { + "ip": "^2.0.0", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.13.0", + "npm": ">= 3.0.0" + } + }, "node_modules/sonic-boom": { "version": "3.7.0", "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.7.0.tgz", @@ -8654,13 +8940,6 @@ "node": ">=8" } }, - "node_modules/standard-as-callback": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz", - "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==", - "optional": true, - "peer": true - }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -8708,6 +8987,20 @@ "node": ">=8" } }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -8719,6 +9012,18 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", @@ -8936,6 +9241,19 @@ "node": ">=0.6" } }, + "node_modules/tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "optional": true, + "peer": true, + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/tree-kill": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", @@ -8992,7 +9310,7 @@ "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "dev": true, + "devOptional": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -9081,7 +9399,7 @@ "version": "8.11.2", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", - "dev": true, + "devOptional": true, "bin": { "acorn": "bin/acorn" }, @@ -9204,52 +9522,60 @@ } }, "node_modules/typeorm": { - "version": "0.2.45", - "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.2.45.tgz", - "integrity": "sha512-c0rCO8VMJ3ER7JQ73xfk0zDnVv0WDjpsP6Q1m6CVKul7DB9iVdWLRjPzc8v2eaeBuomsbZ2+gTaYr8k1gm3bYA==", + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.3.20.tgz", + "integrity": "sha512-sJ0T08dV5eoZroaq9uPKBoNcGslHBR4E4y+EBHs//SiGbblGe7IeduP/IH4ddCcj0qp3PHwDwGnuvqEAnKlq/Q==", "dependencies": { - "@sqltools/formatter": "^1.2.2", - "app-root-path": "^3.0.0", + "@sqltools/formatter": "^1.2.5", + "app-root-path": "^3.1.0", "buffer": "^6.0.3", - "chalk": "^4.1.0", + "chalk": "^4.1.2", "cli-highlight": "^2.1.11", - "debug": "^4.3.1", - "dotenv": "^8.2.0", - "glob": "^7.1.6", - "js-yaml": "^4.0.0", - "mkdirp": "^1.0.4", - "reflect-metadata": "^0.1.13", + "dayjs": "^1.11.9", + "debug": "^4.3.4", + "dotenv": "^16.0.3", + "glob": "^10.3.10", + "mkdirp": "^2.1.3", + "reflect-metadata": "^0.2.1", "sha.js": "^2.4.11", - "tslib": "^2.1.0", - "uuid": "^8.3.2", - "xml2js": "^0.4.23", - "yargs": "^17.0.1", - "zen-observable-ts": "^1.0.0" + "tslib": "^2.5.0", + "uuid": "^9.0.0", + "yargs": "^17.6.2" }, "bin": { - "typeorm": "cli.js" + "typeorm": "cli.js", + "typeorm-ts-node-commonjs": "cli-ts-node-commonjs.js", + "typeorm-ts-node-esm": "cli-ts-node-esm.js" + }, + "engines": { + "node": ">=16.13.0" }, "funding": { "url": "https://opencollective.com/typeorm" }, "peerDependencies": { - "@sap/hana-client": "^2.11.14", - "better-sqlite3": "^7.1.2", + "@google-cloud/spanner": "^5.18.0", + "@sap/hana-client": "^2.12.25", + "better-sqlite3": "^7.1.2 || ^8.0.0 || ^9.0.0", "hdb-pool": "^0.1.6", - "ioredis": "^4.28.3", - "mongodb": "^3.6.0", - "mssql": "^6.3.1", - "mysql2": "^2.2.5", - "oracledb": "^5.1.0", + "ioredis": "^5.0.4", + "mongodb": "^5.8.0", + "mssql": "^9.1.1 || ^10.0.1", + "mysql2": "^2.2.5 || ^3.0.1", + "oracledb": "^6.3.0", "pg": "^8.5.1", "pg-native": "^3.0.0", "pg-query-stream": "^4.0.0", - "redis": "^3.1.1", + "redis": "^3.1.1 || ^4.0.0", "sql.js": "^1.4.0", - "sqlite3": "^5.0.2", + "sqlite3": "^5.0.3", + "ts-node": "^10.7.0", "typeorm-aurora-data-api-driver": "^2.0.0" }, "peerDependenciesMeta": { + "@google-cloud/spanner": { + "optional": true + }, "@sap/hana-client": { "optional": true }, @@ -9292,49 +9618,104 @@ "sqlite3": { "optional": true }, + "ts-node": { + "optional": true + }, "typeorm-aurora-data-api-driver": { "optional": true } } }, "node_modules/typeorm-fixtures-cli": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/typeorm-fixtures-cli/-/typeorm-fixtures-cli-1.11.1.tgz", - "integrity": "sha512-7wnB7q5oOYvzTMStJR7+O9MDfNZVHC4vo87q1sApRQsh5vPyueZQUk4QlVA2TBgRzejJEUjCJBnnhAD4SfzAxQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/typeorm-fixtures-cli/-/typeorm-fixtures-cli-4.0.0.tgz", + "integrity": "sha512-AHymSkNQngnUG2dUwg0249H6v3WLASPzfvJUWjA8C6qYWKAEqJdZehjmvkzXQrKW4zwBs4JgEuq7c9OhpqOLrQ==", "dev": true, "hasInstallScript": true, "dependencies": { - "@faker-js/faker": "^6.0.0", - "@hapi/joi": "^17.1.1", - "@types/hapi__joi": "^17.1.0", + "@faker-js/faker": ">=7.4.0", "chai": "^4.2.0", - "chalk": "^4.1.0", - "class-transformer": "^0.3.1", + "chalk": "^4.0.0", + "class-transformer": "^0.5.0", "cli-progress": "^3.10.0", - "commander": "^6.2.1", "ejs": "^3.1.5", - "glob": "^7.1.6", - "js-yaml": "3.14.1", - "lodash": "^4.17.20", + "glob": "^8.0.1", + "graphology": "^0.25.4", + "graphology-dag": "^0.3.0", + "joi": "^17.0.0", + "js-yaml": "^4.0.0", + "lodash": "^4.0.0", "opencollective-postinstall": "^2.0.3", "reflect-metadata": "^0.1.13", "resolve-from": "^5.0.0", - "yargs-parser": "^20.2.4" + "typescript-collections": "^1.3.3", + "yargs": "^17.5.1" }, "bin": { - "fixtures": "dist/cli.js" + "fixtures": "dist/cli.js", + "fixtures-ts-node-commonjs": "dist/cli-ts-node-commonjs.js", + "fixtures-ts-node-esm": "dist/cli-ts-node-esm.js" }, "peerDependencies": { - "typeorm": "^0.2.11" + "typeorm": "^0.3.0" } }, - "node_modules/typeorm-fixtures-cli/node_modules/commander": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", - "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "node_modules/typeorm-fixtures-cli/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/typeorm-fixtures-cli/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/typeorm-fixtures-cli/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, "engines": { - "node": ">= 6" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/typeorm-fixtures-cli/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/typeorm-fixtures-cli/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" } }, "node_modules/typeorm-fixtures-cli/node_modules/resolve-from": { @@ -9346,49 +9727,90 @@ "node": ">=8" } }, - "node_modules/typeorm-fixtures-cli/node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/typeorm/node_modules/argparse": { + "node_modules/typeorm/node_modules/brace-expansion": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } }, - "node_modules/typeorm/node_modules/dotenv": { - "version": "8.6.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", - "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==", + "node_modules/typeorm/node_modules/glob": { + "version": "10.3.10", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", + "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.5", + "minimatch": "^9.0.1", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", + "path-scurry": "^1.10.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, "engines": { - "node": ">=10" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/typeorm/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "node_modules/typeorm/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", "dependencies": { - "argparse": "^2.0.1" + "brace-expansion": "^2.0.1" }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/typeorm/node_modules/mkdirp": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-2.1.6.tgz", + "integrity": "sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A==", "bin": { - "js-yaml": "bin/js-yaml.js" + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/typeorm/node_modules/reflect-metadata": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.1.tgz", + "integrity": "sha512-i5lLI6iw9AU3Uu4szRNPPEkomnkjRTaVt9hy/bn5g/oSzekBSMeLZblcjP74AW0vBabqERLLIrz+gR8QYR54Tw==" + }, "node_modules/typeorm/node_modules/tslib": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, + "node_modules/typeorm/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/typescript": { "version": "4.9.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", - "dev": true, + "devOptional": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -9397,6 +9819,12 @@ "node": ">=4.2.0" } }, + "node_modules/typescript-collections": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/typescript-collections/-/typescript-collections-1.3.3.tgz", + "integrity": "sha512-7sI4e/bZijOzyURng88oOFZCISQPTHozfE2sUu5AviFYk5QV7fYGb6YiDl+vKjF/pICA354JImBImL9XJWUvdQ==", + "dev": true + }, "node_modules/uid-safe": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", @@ -9496,7 +9924,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true + "devOptional": true }, "node_modules/v8-to-istanbul": { "version": "9.2.0", @@ -9543,11 +9971,34 @@ "makeerror": "1.0.12" } }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "optional": true, + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "optional": true, + "peer": true, + "dependencies": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "dependencies": { "isexe": "^2.0.0" }, @@ -9574,6 +10025,23 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -9612,26 +10080,6 @@ } } }, - "node_modules/xml2js": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", - "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", - "dependencies": { - "sax": ">=0.6.0", - "xmlbuilder": "~11.0.0" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/xmlbuilder": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", - "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", - "engines": { - "node": ">=4.0" - } - }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", @@ -9697,7 +10145,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, + "devOptional": true, "engines": { "node": ">=6" } @@ -9713,20 +10161,6 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } - }, - "node_modules/zen-observable": { - "version": "0.8.15", - "resolved": "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.15.tgz", - "integrity": "sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ==" - }, - "node_modules/zen-observable-ts": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/zen-observable-ts/-/zen-observable-ts-1.1.0.tgz", - "integrity": "sha512-1h4zlLSqI2cRLPJUHJFL8bCWHhkpuXkF+dbGkRaWjgDIG26DmzyshUMrdV/rL3UnR+mhaX4fRq8LPouq0MYYIA==", - "dependencies": { - "@types/zen-observable": "0.8.3", - "zen-observable": "0.8.15" - } } } } diff --git a/server/package.json b/server/package.json index 0ba38a1af..6e056bf66 100644 --- a/server/package.json +++ b/server/package.json @@ -68,7 +68,7 @@ "ts-node": "^10.7.0", "ts-node-dev": "^2.0.0", "tsconfig-paths": "^4.0.0", - "typeorm-fixtures-cli": "^1.9.0", + "typeorm-fixtures-cli": "^4.0.0", "typescript": "^4.6.4", "yamljs": "^0.3.0" }, @@ -111,7 +111,7 @@ "redis": "^3.1.1", "reflect-metadata": "^0.1.10", "tinyqueue": "^2.0.3", - "typeorm": "^0.2.30", + "typeorm": "^0.3.20", "uuid": "^8.3.2", "validator": "^13.5.2" }, diff --git a/server/src/entity/TournamentRoundSignup.ts b/server/src/entity/TournamentRoundSignup.ts index 1ffacb150..4bb408a53 100644 --- a/server/src/entity/TournamentRoundSignup.ts +++ b/server/src/entity/TournamentRoundSignup.ts @@ -1,12 +1,18 @@ -import { Entity, ManyToOne } from "typeorm"; +import { Entity, ManyToOne, PrimaryColumn } from "typeorm"; import { TournamentRoundInvite } from "./TournamentRoundInvite"; import { TournamentRoundDate } from "./TournamentRoundDate"; @Entity() export class TournamentRoundSignup { - @ManyToOne(type => TournamentRoundInvite, invite => invite.signups, { primary: true }) + @PrimaryColumn() + tournamentRoundInviteId!: number; + + @ManyToOne(() => TournamentRoundInvite, invite => invite.signups) tournamentRoundInvite!: TournamentRoundInvite; - @ManyToOne(type => TournamentRoundDate, date => date.signups, { primary: true }) + @PrimaryColumn() + tournamentRoundDateId!: number; + + @ManyToOne(() => TournamentRoundDate, date => date.signups) tournamentRoundDate!: TournamentRoundDate; } diff --git a/server/src/services/account.ts b/server/src/services/account.ts index 9b3bc23a7..e3b4cdb41 100644 --- a/server/src/services/account.ts +++ b/server/src/services/account.ts @@ -105,7 +105,7 @@ export class AccountService extends BaseService { } async findByUsername(username: string): Promise { - return await this.getRepository().findOneOrFail({ username }); + return await this.getRepository().findOneByOrFail({ username }); } async findUsers(usernames: Array): Promise> { @@ -117,7 +117,7 @@ export class AccountService extends BaseService { } async isEmailAvailable(user: User, email: string): Promise { - const otherUser = await this.getRepository().findOne({ email }); + const otherUser = await this.getRepository().findOneBy({ email }); if (otherUser) { return otherUser.id === user.id; } @@ -125,7 +125,7 @@ export class AccountService extends BaseService { } async isUsernameAvailable(username: string, user?: User): Promise { - const otherUser = await this.getRepository().findOne({ username }); + const otherUser = await this.getRepository().findOneBy({ username }); if (otherUser) { if (!user) { return false; @@ -166,12 +166,12 @@ export class AccountService extends BaseService { } async findUserById(id: number): Promise { - return await this.getRepository().findOneOrFail(id); + return await this.getRepository().findOneByOrFail({ id }); } async denyConsent(id: number): Promise { const repo = this.getRepository(); - const user = await repo.findOneOrFail(id); + const user = await repo.findOneByOrFail({ id }); user.dateConsented = undefined; await repo.save(user); return toClientSafeUser(user); @@ -179,7 +179,7 @@ export class AccountService extends BaseService { async grantConsent(id: number): Promise { const repo = this.getRepository(); - const user = await repo.findOneOrFail(id); + const user = await repo.findOneByOrFail({ id }); user.dateConsented = new Date(); await repo.save(user); return toClientSafeUser(user); @@ -283,10 +283,8 @@ export class AccountService extends BaseService { return; } - async findUnregisteredUserByRegistrationToken( - registrationToken: string - ): Promise { - return await this.em.getRepository(User).findOne({ registrationToken }); + async findUnregisteredUserByRegistrationToken(registrationToken: string): Promise { + return await this.em.getRepository(User).findOneBy({ registrationToken }); } async verifyUnregisteredUser(u: User, registrationToken: string): Promise { @@ -327,7 +325,7 @@ export class AccountService extends BaseService { } async getOrCreateTestUser(username: string, shouldSkipVerification = true): Promise { - let user = await this.getRepository().findOne({ username }); + let user = await this.getRepository().findOneBy({ username }); if (!user) { user = new User(); user.username = username; @@ -347,14 +345,14 @@ export class AccountService extends BaseService { } async getOrCreateUser(userData: { email: string; passportId?: string }): Promise { - let user: User | undefined; + let user: User | null = null; // try to find user by id if (userData.passportId) { - user = await this.getRepository().findOne({ passportId: userData.passportId }); + user = await this.getRepository().findOneBy({ passportId: userData.passportId }); } // if no id or find by id turned up empty, try to find user by email if (!userData.passportId || !user) { - user = await this.getRepository().findOne({ email: userData.email }); + user = await this.getRepository().findOneBy({ email: userData.email }); } if (!user) { user = new User(); diff --git a/server/src/services/admin.ts b/server/src/services/admin.ts index ad30bb806..ba17569da 100644 --- a/server/src/services/admin.ts +++ b/server/src/services/admin.ts @@ -140,7 +140,7 @@ export class AdminService extends BaseService { async takeModerationAction(data: ModerationActionData) { const moderationActionRepo = this.em.getRepository(ModerationAction); const reportRepo = this.em.getRepository(ChatReport); - const report = await reportRepo.findOneOrFail({ id: data.reportId }); + const report = await reportRepo.findOneByOrFail({ id: data.reportId }); const user = await this.sp.account.findByUsername(data.username); const admin = await this.sp.account.findByUsername(data.adminUsername); const { username, adminUsername, ...moderationActionData } = data; @@ -187,10 +187,10 @@ export class AdminService extends BaseService { async undoModerationAction(data: { moderationActionId: number; username: string }) { const moderationActionRepo = this.em.getRepository(ModerationAction); const reportRepo = this.em.getRepository(ChatReport); - const moderationAction = await moderationActionRepo.findOneOrFail({ + const moderationAction = await moderationActionRepo.findOneByOrFail({ id: data.moderationActionId, }); - const report = await reportRepo.findOneOrFail({ id: moderationAction.reportId }); + const report = await reportRepo.findOneByOrFail({ id: moderationAction.reportId }); // mark moderationAction as revoked moderationAction.revoked = true; await moderationActionRepo.save(moderationAction); diff --git a/server/src/services/game.ts b/server/src/services/game.ts index 10a5510f5..a63f0107c 100644 --- a/server/src/services/game.ts +++ b/server/src/services/game.ts @@ -5,7 +5,7 @@ import { IsNull } from "typeorm"; export class GameService extends BaseService { async findById(id: number): Promise { - return await this.em.getRepository(Game).findOneOrFail(id); + return await this.em.getRepository(Game).findOneByOrFail({ id }); } async findByRoomId(roomId: string): Promise { @@ -16,7 +16,9 @@ export class GameService extends BaseService { async getTotalActiveGames(): Promise { return await this.em.getRepository(Game).count({ - dateFinalized: IsNull(), + where: { + dateFinalized: IsNull(), + }, }); } diff --git a/server/src/services/persistence.ts b/server/src/services/persistence.ts index 478b05125..6fa7e55dd 100644 --- a/server/src/services/persistence.ts +++ b/server/src/services/persistence.ts @@ -126,14 +126,14 @@ export class DBPersister implements Persister { where: { type: In(DBPersister.FINAL_EVENTS), gameId }, order: { id: "DESC", dateCreated: "DESC" }, }); - const game = await em.getRepository(Game).findOneOrFail(gameId); + const game = await em.getRepository(Game).findOneByOrFail({ id: gameId }); game.status = event.type === "entered-defeat-phase" ? "defeat" : "victory"; game.dateFinalized = this.sp.time.now(); if (!shouldFinalizePlayers) { const res: [Game, Array] = [await em.save(game), []]; return res; } - const players = await em.getRepository(Player).find({ gameId }); + const players = await em.getRepository(Player).findBy({ gameId }); for (const p of players) { p.points = (event.payload as any)[p.role]; } diff --git a/server/src/services/quiz.ts b/server/src/services/quiz.ts index a37ecb40f..63620b91b 100644 --- a/server/src/services/quiz.ts +++ b/server/src/services/quiz.ts @@ -26,7 +26,7 @@ export class QuizService extends BaseService { await this.em.getRepository(QuestionResponse).save(questionResponse); questionResponse.question = await this.em .getRepository(Question) - .findOneOrFail({ id: questionId }); + .findOneByOrFail({ id: questionId }); return questionResponse; } @@ -40,8 +40,8 @@ export class QuizService extends BaseService { async findQuizSubmission( id: number, opts?: FindOneOptions - ): Promise { - return this.em.getRepository(QuizSubmission).findOne(id, opts); + ): Promise { + return this.em.getRepository(QuizSubmission).findOneBy({ ...opts, id }); } async getDefaultQuiz(opts?: FindOneOptions): Promise { @@ -50,7 +50,7 @@ export class QuizService extends BaseService { } async getQuizById(id: number, opts?: FindOneOptions): Promise { - return await this.em.getRepository(Quiz).findOneOrFail(id, opts); + return await this.em.getRepository(Quiz).findOneByOrFail({ ...opts, id }); } async getQuizByName(name: string, opts?: FindOneOptions): Promise { @@ -65,7 +65,7 @@ export class QuizService extends BaseService { userId: number, quizId: number, opts: FindOneOptions = {} - ): Promise { + ): Promise { opts = { order: { dateCreated: "DESC" }, ...opts }; return await this.em .getRepository(QuizSubmission) @@ -82,7 +82,7 @@ export class QuizService extends BaseService { } async findQuestion(id: number): Promise { - return await this.em.getRepository(Question).findOneOrFail(id); + return await this.em.getRepository(Question).findOneByOrFail({ id }); } async isCorrect(questionResponse: QuestionResponse): Promise { diff --git a/server/src/services/sologame.ts b/server/src/services/sologame.ts index 45ac59051..45863e574 100644 --- a/server/src/services/sologame.ts +++ b/server/src/services/sologame.ts @@ -75,13 +75,13 @@ export class SoloGameService extends BaseService { ).max; if (highestPlayedTreatment < numTreatments) { - return treatmentRepo.findOneOrFail(highestPlayedTreatment + 1); + return treatmentRepo.findOneByOrFail({ id: highestPlayedTreatment + 1 }); } - return treatmentRepo.findOneOrFail(getRandomIntInclusive(1, numTreatments)); + return treatmentRepo.findOneByOrFail({ id: getRandomIntInclusive(1, numTreatments) }); } async getTreatmentById(id: number): Promise { - return this.em.getRepository(SoloGameTreatment).findOneOrFail(id); + return this.em.getRepository(SoloGameTreatment).findOneByOrFail({ id }); } async createGame(state: SoloGameState): Promise { @@ -103,7 +103,10 @@ export class SoloGameService extends BaseService { await gameRepo.save(game); player.gameId = game.id; await playerRepo.save(player); - return gameRepo.findOneOrFail(game.id, { relations: ["deck", "deck.cards"] }); + return gameRepo.findOneOrFail({ + where: { id: game.id }, + relations: ["deck", "deck.cards"], + }); } async createPlayer(userId: number): Promise { @@ -147,7 +150,7 @@ export class SoloGameService extends BaseService { async updateGameStatus(gameId: number, status: SoloGameStatus) { const repo = this.em.getRepository(SoloGame); - const game = await repo.findOneOrFail(gameId); + const game = await repo.findOneByOrFail({ id: gameId }); game.status = status; await repo.save(game); } @@ -159,7 +162,7 @@ export class SoloGameService extends BaseService { status: SoloGameStatus ) { const repo = this.em.getRepository(SoloPlayer); - const player = await repo.findOneOrFail({ gameId }); + const player = await repo.findOneByOrFail({ gameId }); player.points = points; await repo.save(player); // if the game was a success, update the player's highscore table as well @@ -197,7 +200,7 @@ export class SoloGameService extends BaseService { // additionally, set the round on all cards that were drawn this round const cards = state.roundEventCards; for (const card of cards) { - const deckCard = await deckCardRepo.findOneOrFail({ id: card.deckCardId }); + const deckCard = await deckCardRepo.findOneByOrFail({ id: card.deckCardId }); if (deckCard) { deckCard.round = round; await deckCardRepo.save(deckCard); diff --git a/server/src/services/stats.ts b/server/src/services/stats.ts index bd3b0ed54..746bf8d76 100644 --- a/server/src/services/stats.ts +++ b/server/src/services/stats.ts @@ -12,17 +12,29 @@ import { SoloHighScore } from "@port-of-mars/server/entity/SoloHighScore"; export class StatsService extends BaseService { /* Player stats */ - async getGamesWithUser(user: User): Promise> { - return this.em.getRepository(Game).find({ - join: { alias: "games", innerJoin: { players: "games.players" } }, - where: (qb: SelectQueryBuilder) => { - qb.where({ dateFinalized: Not(IsNull()) }).andWhere("players.user.id = :userId", { - userId: user.id, - }); - }, - relations: ["players"], - order: { dateCreated: "DESC" }, - }); + // async getGamesWithUser(user: User): Promise> { + // return this.em.getRepository(Game).find({ + // join: { alias: "games", innerJoin: { players: "games.players" } }, + // where: (qb: SelectQueryBuilder) => { + // qb.where({ dateFinalized: Not(IsNull()) }).andWhere("players.user.id = :userId", { + // userId: user.id, + // }); + // }, + // relations: ["players"], + // order: { dateCreated: "DESC" }, + // }); + // } + // TODO: verify this + async getGamesWithUser(user: User): Promise { + const games = await this.em + .createQueryBuilder(Game, "game") + .innerJoin("game.players", "player") + .where("game.dateFinalized IS NOT NULL") + .andWhere("player.userId = :userId", { userId: user.id }) + .orderBy("game.dateCreated", "DESC") + .getMany(); + + return games; } async getPlayerHistory(user: User): Promise> { @@ -112,11 +124,12 @@ export class StatsService extends BaseService { // update the solo highscores table with data from a victory game and return the player's entry with rank const highscoreRepo = this.em.getRepository(SoloHighScore); const pointsPerRound = points / maxRound; - const game = await this.em.getRepository(SoloGame).findOneOrFail(gameId, { + const game = await this.em.getRepository(SoloGame).findOneOrFail({ + where: { id: gameId }, relations: ["player", "player.user"], }); const user = game.player.user; - let highscore = await highscoreRepo.findOne({ user }); + let highscore = await highscoreRepo.findOneBy({ user }); if (highscore) { if (pointsPerRound > highscore.pointsPerRound) { diff --git a/server/src/services/survey.ts b/server/src/services/survey.ts index 5a95b368f..e2bfe28eb 100644 --- a/server/src/services/survey.ts +++ b/server/src/services/survey.ts @@ -43,9 +43,10 @@ export class SurveyService extends BaseService { } async setSurveyComplete(data: { inviteId: number; surveyId: string }) { - const invite = await this.em - .getRepository(TournamentRoundInvite) - .findOneOrFail(data.inviteId, { relations: ["user", "tournamentRound"] }); + const invite = await this.em.getRepository(TournamentRoundInvite).findOneOrFail({ + where: { id: data.inviteId }, + relations: ["user", "tournamentRound"], + }); const tournamentRound = invite.tournamentRound; const introSurveyUrl = tournamentRound.introSurveyUrl; const exitSurveyUrl = tournamentRound.exitSurveyUrl; diff --git a/server/src/services/tournament.ts b/server/src/services/tournament.ts index df962147f..25d144865 100644 --- a/server/src/services/tournament.ts +++ b/server/src/services/tournament.ts @@ -65,7 +65,7 @@ export class TournamentService extends BaseService { } async getTournamentByName(name: string): Promise { - return this.em.getRepository(Tournament).findOneOrFail({ name }); + return this.em.getRepository(Tournament).findOneByOrFail({ name }); } async getCurrentTournamentRoundByType(type: GameType) { @@ -171,7 +171,9 @@ export class TournamentService extends BaseService { } async getAndConfirmValidInvite(user: User, inviteId: number): Promise { - const invite = await this.em.getRepository(TournamentRoundInvite).findOneOrFail(inviteId); + const invite = await this.em + .getRepository(TournamentRoundInvite) + .findOneByOrFail({ id: inviteId }); if (invite.userId !== user.id) { throw new ValidationError({ displayMessage: "Invalid tournament invitation", @@ -189,7 +191,7 @@ export class TournamentService extends BaseService { const invite = await this.getAndConfirmValidInvite(user, inviteId); const roundDate = await this.em .getRepository(TournamentRoundDate) - .findOneOrFail(tournamentRoundDateId); + .findOneByOrFail({ id: tournamentRoundDateId }); const signup = this.em.getRepository(TournamentRoundSignup).create({ tournamentRoundInvite: invite, tournamentRoundDate: roundDate, @@ -201,7 +203,7 @@ export class TournamentService extends BaseService { const invite = await this.getAndConfirmValidInvite(user, inviteId); const roundDate = await this.em .getRepository(TournamentRoundDate) - .findOneOrFail(tournamentRoundDateId); + .findOneByOrFail({ id: tournamentRoundDateId }); await this.em.getRepository(TournamentRoundSignup).delete({ tournamentRoundInvite: invite, tournamentRoundDate: roundDate, @@ -590,7 +592,7 @@ export class TournamentService extends BaseService { async getTournamentRound(id?: number): Promise { if (id) { - return await this.em.getRepository(TournamentRound).findOneOrFail(id); + return await this.em.getRepository(TournamentRound).findOneByOrFail({ id }); } else { return await this.getCurrentTournamentRound(); } diff --git a/server/tests/services/account.test.ts b/server/tests/services/account.test.ts index 604e8ea11..c3466e10f 100644 --- a/server/tests/services/account.test.ts +++ b/server/tests/services/account.test.ts @@ -78,7 +78,7 @@ describe("a potential user", () => { }); it("can be sent an email verification email", async () => { - const u = await qr.manager.getRepository(User).findOneOrFail({ username }); + const u = await qr.manager.getRepository(User).findOneByOrFail({ username }); await accountService.sendEmailVerification(u); expect(settings.emailer.lastEmail?.from).toBe("Port of Mars "); expect(settings.emailer.lastEmail?.text).toContain( @@ -90,19 +90,19 @@ describe("a potential user", () => { }); it("can verify their email using a valid verification token", async () => { - let u = await qr.manager.getRepository(User).findOneOrFail({ username }); + let u = await qr.manager.getRepository(User).findOneByOrFail({ username }); await expect( accountService.verifyUnregisteredUser(u, "invalid-registration-token") ).rejects.toThrow(`Invalid registration token invalid-registration-token`); await accountService.verifyUnregisteredUser(u, u.registrationToken); - u = await qr.manager.getRepository(User).findOneOrFail({ username }); + u = await qr.manager.getRepository(User).findOneByOrFail({ username }); expect(u.isVerified).toBeTruthy(); }); it("rejects user verification using an invalid verification token", async () => { - const u = await qr.manager.getRepository(User).findOneOrFail({ username }); + const u = await qr.manager.getRepository(User).findOneByOrFail({ username }); await expect( accountService.verifyUnregisteredUser(u, "invalid-registration-token") ).rejects.toThrowError(ServerError); From 8f4ddf142ceae0130105c3e4a68b9377098daf9a Mon Sep 17 00:00:00 2001 From: sgfost Date: Fri, 2 Feb 2024 14:05:46 -0700 Subject: [PATCH 2/9] fix: migrate ormconfig/connection to datasource TODO: test setup is still broken [no ci] --- Makefile | 12 ++-- base.yml | 1 - server/.env.template | 2 + server/ormconfig.template.json | 52 ---------------- server/package.json | 4 +- server/src/cli.ts | 59 ++++++++++--------- server/src/datasource.ts | 29 +++++++++ server/src/index.ts | 11 ++-- ...805620516-ComputeSoloRoundInitialValues.ts | 7 ++- server/src/routes/quiz.ts | 10 +++- server/src/services/account.ts | 7 ++- server/src/services/index.ts | 4 +- server/src/services/persistence.ts | 6 +- server/src/services/quiz.ts | 6 +- server/src/services/sologame.ts | 6 +- server/src/services/stats.ts | 25 +++----- server/src/services/survey.ts | 5 +- server/src/services/tournament.ts | 10 ++-- server/src/util.ts | 5 -- server/tests/common.ts | 24 ++++---- server/tests/rooms/sologame.test.ts | 7 +-- server/tests/services/account.test.ts | 7 +-- server/tests/services/admin.test.ts | 7 +-- server/tests/services/replay.test.ts | 7 +-- server/tests/services/tournament.test.ts | 7 +-- 25 files changed, 153 insertions(+), 167 deletions(-) delete mode 100644 server/ormconfig.template.json create mode 100644 server/src/datasource.ts diff --git a/Makefile b/Makefile index c3b48f211..56d2b49b1 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,6 @@ DATA_DUMP_PATH=docker/dump LOG_DATA_PATH=docker/logs DB_PASSWORD_PATH=keys/pom_db_password REDIS_SETTINGS_PATH=keys/settings.json -ORMCONFIG_PATH=keys/ormconfig.json SERVER_ENV_TEMPLATE=server/.env.template SERVER_ENV=server/.env PGPASS_PATH=keys/.pgpass @@ -15,7 +14,7 @@ SECRET_KEY_PATH=keys/secret_key SENTRY_DSN_PATH=keys/sentry_dsn SENTRY_DSN=$(shell cat $(SENTRY_DSN_PATH)) MAIL_API_KEY_PATH=keys/mail_api_key -SECRETS=$(MAIL_API_KEY_PATH) $(DB_PASSWORD_PATH) $(ORMCONFIG_PATH) $(PGPASS_PATH) $(SENTRY_DSN_PATH) $(SECRET_KEY_PATH) +SECRETS=$(MAIL_API_KEY_PATH) $(DB_PASSWORD_PATH) $(PGPASS_PATH) $(SENTRY_DSN_PATH) $(SECRET_KEY_PATH) SHARED_CONFIG_PATH=shared/src/assets/config.ts BUILD_ID=$(shell git describe --tags --abbrev=1) GA_TAG_PATH=keys/ga_tag @@ -68,12 +67,9 @@ $(DATA_DUMP_PATH): $(REDIS_SETTINGS_PATH): server/deploy/settings.template.json | keys cp server/deploy/settings.template.json $(REDIS_SETTINGS_PATH) -$(ORMCONFIG_PATH): server/ormconfig.template.json $(DB_PASSWORD_PATH) - DB_PASSWORD=$$(cat $(DB_PASSWORD_PATH)); \ - sed "s|DB_PASSWORD|$$DB_PASSWORD|g" server/ormconfig.template.json > $(ORMCONFIG_PATH) - $(SERVER_ENV): $(SERVER_ENV_TEMPLATE) $(SECRETS) POM_BASE_URL=${POM_BASE_URL} \ + DB_PASSWORD=$$(cat $(DB_PASSWORD_PATH)); \ envsubst < $(SERVER_ENV_TEMPLATE) > $(SERVER_ENV) $(PGPASS_PATH): $(DB_PASSWORD_PATH) server/deploy/pgpass.template | keys @@ -110,7 +106,7 @@ settings: $(SENTRY_DSN_PATH) $(SECRET_KEY_PATH) | keys initialize: build docker compose run --rm server npm run initdb -docker-compose.yml: base.yml $(ENVIR).yml config.mk $(DB_DATA_PATH) $(DATA_DUMP_PATH) $(LOG_DATA_PATH) $(REDIS_SETTINGS_PATH) $(ORMCONFIG_PATH) $(NUXT_ORMCONFIG_PATH) $(PGPASS_PATH) $(SERVER_ENV) settings +docker-compose.yml: base.yml $(ENVIR).yml config.mk $(DB_DATA_PATH) $(DATA_DUMP_PATH) $(LOG_DATA_PATH) $(REDIS_SETTINGS_PATH) $(PGPASS_PATH) $(SERVER_ENV) settings case "$(ENVIR)" in \ dev|staging|prod) docker compose -f base.yml -f "$(ENVIR).yml" config > docker-compose.yml;; \ *) echo "invalid environment. must be either dev, staging or prod" 1>&2; exit 1;; \ @@ -118,7 +114,7 @@ docker-compose.yml: base.yml $(ENVIR).yml config.mk $(DB_DATA_PATH) $(DATA_DUMP_ .PHONY: test-setup test-setup: docker-compose.yml - docker compose run --rm server bash -c "dropdb --if-exists -h db -U ${DB_USER} ${TEST_DB_NAME} && createdb -h db -U ${DB_USER} ${TEST_DB_NAME} && npm run typeorm -- schema:sync -c test && npm run load-fixtures -- ./fixtures/sologame -cn test" + docker compose run --rm server bash -c "dropdb --if-exists -h db -U ${DB_USER} ${TEST_DB_NAME} && createdb -h db -U ${DB_USER} ${TEST_DB_NAME} && npm run typeorm -- schema:sync && npm run load-fixtures ./fixtures/sologame" .PHONY: test test: test-setup diff --git a/base.yml b/base.yml index 880fef79a..a1a095dad 100644 --- a/base.yml +++ b/base.yml @@ -12,7 +12,6 @@ services: volumes: - ./docker/dump:/dump - ./docker/logs:/var/log/port-of-mars - - ./keys/ormconfig.json:/code/server/ormconfig.json - ./keys/.pgpass:/root/.pgpass - ./keys:/run/secrets - ./scripts:/scripts diff --git a/server/.env.template b/server/.env.template index 947611450..474395cc3 100644 --- a/server/.env.template +++ b/server/.env.template @@ -1,5 +1,7 @@ BASE_URL=${POM_BASE_URL} +DB_PASSWORD=${DB_PASSWORD} + GOOGLE_CLIENT_ID=changeme GOOGLE_CLIENT_SECRET=changeme diff --git a/server/ormconfig.template.json b/server/ormconfig.template.json deleted file mode 100644 index 8bc909c2d..000000000 --- a/server/ormconfig.template.json +++ /dev/null @@ -1,52 +0,0 @@ -[ - { - "name": "default", - "type": "postgres", - "host": "db", - "port": 5432, - "username": "marsmadness", - "database": "port_of_mars", - "password": "DB_PASSWORD", - "synchronize": false, - "logging": false, - "entities": [ - "src/entity/**/*.{js,ts}" - ], - "migrations": [ - "src/migration/**/*.{js,ts}" - ], - "subscribers": [ - "src/subscriber/**/*.{js,ts}" - ], - "cli": { - "entitiesDir": "src/entity", - "migrationsDir": "src/migration", - "subscribersDir": "src/subscriber" - } - }, - { - "name": "test", - "type": "postgres", - "host": "db", - "port": 5432, - "username": "marsmadness", - "database": "pom_testing", - "password": "DB_PASSWORD", - "synchronize": false, - "logging": false, - "entities": [ - "src/entity/**/*.{js,ts}" - ], - "migrations": [ - "src/migration/**/*.{js,ts}" - ], - "subscribers": [ - "src/subscriber/**/*.{js,ts}" - ], - "cli": { - "entitiesDir": "src/entity", - "migrationsDir": "src/migration", - "subscribersDir": "src/subscriber" - } - } -] diff --git a/server/package.json b/server/package.json index 6e056bf66..b6dab56d3 100644 --- a/server/package.json +++ b/server/package.json @@ -12,7 +12,7 @@ "cli:prod": "node -r tsconfig-paths/register src/cli.js", "start:prod": "node -r tsconfig-paths/register src/index.js | tee -a /var/log/port-of-mars/index.log", "start": "ts-node-dev -r tsconfig-paths/register src/index.ts | tee -a /var/log/port-of-mars/index.log", - "typeorm": "ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli.js", + "typeorm": "ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli.js --dataSource src/datasource.ts", "dangerously-dropdb": "dropdb -U marsmadness -h db port_of_mars", "initdb": "createdb -U marsmadness -h db port_of_mars ; npm run typeorm migration:run && /scripts/freeplay/setup.sh ; npm run load-fixtures ./fixtures/sologame", "dangerously-loaddb": "psql -h db -U marsmadness port_of_mars < pom-db.sql", @@ -25,7 +25,7 @@ "lint:fix": "eslint --fix -c .eslintrc.js ./ ../shared --ext .ts", "style": "prettier --config ../.prettierrc --check './**/*.ts' '../shared/**/*.ts'", "style:fix": "prettier --config ../.prettierrc --write './**/*.ts' '../shared/**/*.ts'", - "load-fixtures": "fixtures --config ormconfig.json --require ts-node/register --require tsconfig-paths/register" + "load-fixtures": "fixtures load --require ts-node/register --require tsconfig-paths/register -d src/datasource.ts" }, "author": "Center for Behavior, Institutions, and the Environment (https://cbie.asu.edu)", "license": "MIT", diff --git a/server/src/cli.ts b/server/src/cli.ts index 7ec851f16..d412f45cb 100644 --- a/server/src/cli.ts +++ b/server/src/cli.ts @@ -25,10 +25,11 @@ import { } from "@port-of-mars/server/entity"; import { DYNAMIC_SETTINGS_PATH, RedisSettings } from "@port-of-mars/server/services/settings"; import { generateUsername } from "@port-of-mars/server/util"; +import appDataSource from "@port-of-mars/server/datasource"; import { program } from "commander"; import { mkdir, readFile, writeFile } from "fs/promises"; -import { createConnection, EntityManager } from "typeorm"; +import { EntityManager } from "typeorm"; /* import { promisify } from "util"; @@ -43,16 +44,6 @@ function getRandomInt(min: number, max: number) { return Math.floor(Math.random() * (max - min) + min); } -async function withConnection(f: (em: EntityManager) => Promise): Promise { - const conn = await createConnection("default"); - const em = conn.createEntityManager(); - try { - await f(em); - } finally { - await conn.close(); - } -} - async function exportSoloData(em: EntityManager, start?: string, end?: string) { const soloGameService = getServices().sologame; await mkdir("/dump/solo", { recursive: true }); @@ -483,7 +474,9 @@ program .option("--tournamentId ", "ID of the tournament", customParseInt) .description("link existing Treatments to a given Tournament") .action(async cmd => { - await withConnection(em => addTreatments(em, cmd.treatmentIds, cmd.tournamentId)); + await appDataSource.transaction(async em => + addTreatments(em, cmd.treatmentIds, cmd.tournamentId) + ); }) ) .addCommand( @@ -498,7 +491,7 @@ program .option("--tournamentId ", "ID of the tournament", customParseInt) .description("add a Treatment (set of mars event overrides) to a Tournament") .action(async cmd => { - await withConnection(em => + await appDataSource.transaction(async em => createTournamentTreatment( em, cmd.name, @@ -529,7 +522,7 @@ program ) .description("add a TournamentRoundDate for the given date") .action(async cmd => { - await withConnection(em => + await appDataSource.transaction(async em => createTournamentRoundDate(em, cmd.date, cmd.tournamentRoundId) ); }) @@ -544,7 +537,9 @@ program ) .description("report emails for all users in the given tournament round") .action(async cmd => { - await withConnection(em => exportTournamentRoundEmails(em, cmd.tournamentRoundId)); + await appDataSource.transaction(async em => + exportTournamentRoundEmails(em, cmd.tournamentRoundId) + ); }) ) .addCommand( @@ -567,7 +562,7 @@ program ) .description("create invitations for the given users in the given tournament round") .action(async cmd => { - await withConnection(em => + await appDataSource.transaction(async em => createTournamentRoundInvites( em, cmd.tournamentRoundId, @@ -597,7 +592,7 @@ program .option("--announcement ", "Tournament Round announcement message", "") .description("create a tournament round") .action(async cmd => { - await withConnection(em => + await appDataSource.transaction(async em => createRound( em, cmd.open, @@ -627,7 +622,7 @@ program .option("--description ", "Description of the tournament") .description("create a tournament") .action(async cmd => { - await withConnection(em => + await appDataSource.transaction(async em => createTournament( em, cmd.tournamentName, @@ -650,7 +645,7 @@ program .description("set a user as an administrator") .requiredOption("--username ", "username of the user") .action(async cmd => { - await withConnection(em => setAdminUser(em, cmd.username)); + await appDataSource.transaction(async em => setAdminUser(em, cmd.username)); }) ) .addCommand( @@ -660,7 +655,9 @@ program .requiredOption("--startId ", "initial user ID in range", customParseInt, 1) .requiredOption("--endId ", "end user ID in range", customParseInt, 1942) .action(async cmd => { - await withConnection(em => anonymizeUsernames(em, cmd.startId, cmd.endId)); + await appDataSource.transaction(async em => + anonymizeUsernames(em, cmd.startId, cmd.endId) + ); }) ) ) @@ -674,7 +671,7 @@ program .description("finalize a game that wasn't finalized properly") .requiredOption("--gameId ", "id of game", customParseInt) .action(async cmd => { - await withConnection(em => finalize(em, cmd.gameId)); + await appDataSource.transaction(async em => finalize(em, cmd.gameId)); }) ) .addCommand( @@ -685,7 +682,7 @@ program ) .requiredOption("--gameId ", "id of game", customParseInt) .action(async cmd => { - await withConnection(em => validate(em, cmd.gameId)); + await appDataSource.transaction(async em => validate(em, cmd.gameId)); }) ) ) @@ -701,7 +698,7 @@ program .description("dump game data for a given tournament round id to a pile of CSV files") .requiredOption("--tournamentId ", "tournament id", customParseInt) .action(async cmd => { - await withConnection(em => exportTournament(em, cmd.tournamentId)); + await appDataSource.transaction(async em => exportTournament(em, cmd.tournamentId)); }) ) .addCommand( @@ -720,7 +717,9 @@ program [] as Array ) .action(async cmd => { - await withConnection(em => exportTournamentRound(em, cmd.tournamentId, cmd.gids)); + await appDataSource.transaction(async em => + exportTournamentRound(em, cmd.tournamentId, cmd.gids) + ); }) ) .addCommand( @@ -730,7 +729,7 @@ program .option("-s, --start ", "Start date (YYYY-MM-DD)") .option("-e, --end ", "End date (YYYY-MM-DD)") .action(async cmd => { - await withConnection(em => exportSoloData(em, cmd.start, cmd.end)); + await appDataSource.transaction(async em => exportSoloData(em, cmd.start, cmd.end)); }) ) ) @@ -745,7 +744,7 @@ program [] as Array ) .action(async cmd => { - await withConnection(em => checkQuizCompletion(em, cmd.ids)); + await appDataSource.transaction(async em => checkQuizCompletion(em, cmd.ids)); }) ) .addCommand( @@ -759,7 +758,7 @@ program [] as Array ) .action(async cmd => { - await withConnection(em => completeQuizCompletion(em, cmd.ids)); + await appDataSource.transaction(async em => completeQuizCompletion(em, cmd.ids)); }) ) .addCommand( @@ -776,7 +775,9 @@ program "generate a CSV for mailchimp import of all active users with a valid email address" ) .action(async cmd => { - await withConnection(em => exportActiveEmails(em, cmd.after, cmd.enableAmdf)); + await appDataSource.transaction(async em => + exportActiveEmails(em, cmd.after, cmd.enableAmdf) + ); }) ) .addCommand( @@ -789,7 +790,7 @@ program ) .description("Deactivate users who have unsubscribed from emails (currently from mailchimp).") .action(async cmd => { - await withConnection(em => deactivateUsers(em, cmd.filename)); + await appDataSource.transaction(async em => deactivateUsers(em, cmd.filename)); }) ) .addCommand( diff --git a/server/src/datasource.ts b/server/src/datasource.ts new file mode 100644 index 000000000..aa5761194 --- /dev/null +++ b/server/src/datasource.ts @@ -0,0 +1,29 @@ +import { DataSourceOptions, DataSource } from "typeorm"; +import * as dotenv from "dotenv"; + +dotenv.config(); + +const postgresConnectionOptions: DataSourceOptions = { + type: "postgres", + host: "db", + port: 5432, + username: "marsmadness", + password: process.env.DB_PASSWORD, + synchronize: false, + logging: false, + entities: ["src/entity/**/*.{js,ts}"], + migrations: ["src/migration/**/*.{js,ts}"], +}; + +const appDataSource = new DataSource({ + ...postgresConnectionOptions, + database: "port_of_mars", +}); + +const testDataSource = new DataSource({ + ...postgresConnectionOptions, + database: "pom_testing", +}); + +const dataSource = process.env.NODE_ENV === "test" ? testDataSource : appDataSource; +export default dataSource; diff --git a/server/src/index.ts b/server/src/index.ts index 757a987cb..9f2c22151 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -1,4 +1,3 @@ -import { createConnection } from "typeorm"; import http from "http"; import express, { Response } from "express"; import helmet from "helmet"; @@ -35,11 +34,11 @@ import { statusRouter, statsRouter, } from "@port-of-mars/server/routes"; -import { ServerError } from "./util"; +import { ServerError } from "@port-of-mars/server/util"; +import dataSource from "@port-of-mars/server/datasource"; const logger = settings.logging.getLogger(__filename); const NODE_ENV = process.env.NODE_ENV || "development"; -const CONNECTION_NAME = NODE_ENV === "test" ? "test" : "default"; const RedisStore = connectRedis(session); const store = new RedisStore({ host: "redis", client: getRedis() }); @@ -251,12 +250,14 @@ async function createApp() { // connect to the database and start the server, retrying if the connection fails pRetry( async () => { - await createConnection(CONNECTION_NAME); + await dataSource.initialize(); await createApp(); }, { onFailedAttempt: error => { - logger.warn(`Connection to db failed on attempt number ${error.attemptNumber}, retrying...`); + logger.warn( + `Connection to db failed on attempt number ${error.attemptNumber}, retrying ${error.retriesLeft} more times...` + ); }, retries: 10, minTimeout: 1 * 1000, diff --git a/server/src/migration/1701805620516-ComputeSoloRoundInitialValues.ts b/server/src/migration/1701805620516-ComputeSoloRoundInitialValues.ts index 7e868c46f..ca3612e96 100644 --- a/server/src/migration/1701805620516-ComputeSoloRoundInitialValues.ts +++ b/server/src/migration/1701805620516-ComputeSoloRoundInitialValues.ts @@ -51,7 +51,12 @@ export class ComputeSoloRoundInitialValues1701805620516 implements MigrationInte private async computeMissingInitialValues(queryRunner: QueryRunner): Promise { // fetch all existing games with rounds, cards, decisions const games = await queryRunner.manager.find(SoloGame, { - relations: ["rounds", "rounds.cards", "rounds.decision"], + relations: { + rounds: { + cards: true, + decision: true, + }, + }, }); for (const game of games) { let initialSystemHealth = MAX_SYSTEM_HEALTH; diff --git a/server/src/routes/quiz.ts b/server/src/routes/quiz.ts index 492a07cf2..d59945b3e 100644 --- a/server/src/routes/quiz.ts +++ b/server/src/routes/quiz.ts @@ -24,12 +24,16 @@ quizRouter.get("/submission/:id", async (req: Request, res: Response, next: Next const id = parseInt(req.params.id); let submission = await quizService.findQuizSubmission(id, { where: { userId: user.id }, - relations: ["quiz", "quiz.questions"], + relations: { + quiz: { + questions: true, + }, + }, }); let quiz = submission?.quiz; let statusCode = 200; if (!submission) { - quiz = await quizService.getDefaultQuiz({ relations: ["questions"] }); + quiz = await quizService.getDefaultQuiz({ relations: { questions: true } }); logger.debug("created new submission after looking up id %d", id); submission = await quizService.createQuizSubmission(user.id, quiz.id); statusCode = 201; @@ -47,7 +51,7 @@ quizRouter.post("/submission", async (req: Request, res: Response, next: NextFun const user = req.user as User; if (user) { const userId = user!.id; - const quiz = await quizService.getDefaultQuiz({ relations: ["questions"] }); + const quiz = await quizService.getDefaultQuiz({ relations: { questions: true } }); const quizId = quiz!.id; const quizQuestions = quiz.questions; const submission = await quizService.createQuizSubmission(userId, quizId); diff --git a/server/src/services/account.ts b/server/src/services/account.ts index e3b4cdb41..80b6e6168 100644 --- a/server/src/services/account.ts +++ b/server/src/services/account.ts @@ -137,7 +137,12 @@ export class AccountService extends BaseService { async getActiveUsers(after: Date): Promise> { return await this.getRepository().find({ - select: ["name", "email", "username", "dateCreated"], + select: { + name: true, + email: true, + username: true, + dateCreated: true, + }, where: { isActive: true, email: Not(IsNull()), diff --git a/server/src/services/index.ts b/server/src/services/index.ts index 1f4200c8e..b9e61d179 100644 --- a/server/src/services/index.ts +++ b/server/src/services/index.ts @@ -6,11 +6,11 @@ import { TournamentService } from "@port-of-mars/server/services/tournament"; import { QuizService } from "@port-of-mars/server/services/quiz"; import { SurveyService } from "@port-of-mars/server/services/survey"; import { StatsService } from "@port-of-mars/server/services/stats"; -import { getConnection } from "@port-of-mars/server/util"; import { TimeService } from "@port-of-mars/server/services/time"; import { GameService } from "@port-of-mars/server/services/game"; import { SoloGameService } from "@port-of-mars/server/services/sologame"; import { RedisSettings } from "@port-of-mars/server/services/settings"; +import dataSource from "@port-of-mars/server/datasource"; import { createClient, RedisClient } from "redis"; export class ServiceProvider { @@ -114,5 +114,5 @@ export function getRedis(): RedisClient { } export function getServices(em?: EntityManager) { - return new ServiceProvider(em ?? getConnection().manager); + return new ServiceProvider(em ?? dataSource.manager); } diff --git a/server/src/services/persistence.ts b/server/src/services/persistence.ts index 6fa7e55dd..f38b0ec49 100644 --- a/server/src/services/persistence.ts +++ b/server/src/services/persistence.ts @@ -49,7 +49,11 @@ export class DBPersister implements Persister { async selectUsersByUsername(em: EntityManager, usernames: Array) { const userRepo = em.getRepository(User); const rawUsers = await userRepo.find({ - select: ["id", "username", "lastPlayerIp"], + select: { + id: true, + username: true, + lastPlayerIp: true, + }, where: { username: In(usernames), }, diff --git a/server/src/services/quiz.ts b/server/src/services/quiz.ts index 63620b91b..42b0a5f27 100644 --- a/server/src/services/quiz.ts +++ b/server/src/services/quiz.ts @@ -97,7 +97,7 @@ export class QuizService extends BaseService { async getQuizResponses(submissionId: number): Promise> { return await this.em .getRepository(QuestionResponse) - .find({ where: { submissionId }, relations: ["question"] }); + .find({ where: { submissionId }, relations: { question: true } }); } async getIncompleteQuizUsers(): Promise> { @@ -114,8 +114,8 @@ export class QuizService extends BaseService { */ async checkQuizCompletion(userId: number, quizId?: number): Promise> { const quiz: Quiz = quizId - ? await this.getQuizById(quizId, { relations: ["questions"] }) - : await this.getDefaultQuiz({ relations: ["questions"] }); + ? await this.getQuizById(quizId, { relations: { questions: true } }) + : await this.getDefaultQuiz({ relations: { questions: true } }); const quizSubmission = await this.getLatestQuizSubmission(userId, quiz.id); const questionIds = quiz.questions.map(q => q.id); if (!quizSubmission) { diff --git a/server/src/services/sologame.ts b/server/src/services/sologame.ts index 45863e574..de3b8f9bd 100644 --- a/server/src/services/sologame.ts +++ b/server/src/services/sologame.ts @@ -105,7 +105,11 @@ export class SoloGameService extends BaseService { await playerRepo.save(player); return gameRepo.findOneOrFail({ where: { id: game.id }, - relations: ["deck", "deck.cards"], + relations: { + deck: { + cards: true, + }, + }, }); } diff --git a/server/src/services/stats.ts b/server/src/services/stats.ts index 746bf8d76..07fd387e4 100644 --- a/server/src/services/stats.ts +++ b/server/src/services/stats.ts @@ -12,25 +12,14 @@ import { SoloHighScore } from "@port-of-mars/server/entity/SoloHighScore"; export class StatsService extends BaseService { /* Player stats */ - // async getGamesWithUser(user: User): Promise> { - // return this.em.getRepository(Game).find({ - // join: { alias: "games", innerJoin: { players: "games.players" } }, - // where: (qb: SelectQueryBuilder) => { - // qb.where({ dateFinalized: Not(IsNull()) }).andWhere("players.user.id = :userId", { - // userId: user.id, - // }); - // }, - // relations: ["players"], - // order: { dateCreated: "DESC" }, - // }); - // } - // TODO: verify this async getGamesWithUser(user: User): Promise { const games = await this.em .createQueryBuilder(Game, "game") - .innerJoin("game.players", "player") + .leftJoinAndSelect("game.players", "player") + .innerJoin("game.players", "playerFilter", "playerFilter.userId = :userId", { + userId: user.id, + }) .where("game.dateFinalized IS NOT NULL") - .andWhere("player.userId = :userId", { userId: user.id }) .orderBy("game.dateCreated", "DESC") .getMany(); @@ -126,7 +115,11 @@ export class StatsService extends BaseService { const pointsPerRound = points / maxRound; const game = await this.em.getRepository(SoloGame).findOneOrFail({ where: { id: gameId }, - relations: ["player", "player.user"], + relations: { + player: { + user: true, + }, + }, }); const user = game.player.user; let highscore = await highscoreRepo.findOneBy({ user }); diff --git a/server/src/services/survey.ts b/server/src/services/survey.ts index e2bfe28eb..33bb3c70a 100644 --- a/server/src/services/survey.ts +++ b/server/src/services/survey.ts @@ -45,7 +45,10 @@ export class SurveyService extends BaseService { async setSurveyComplete(data: { inviteId: number; surveyId: string }) { const invite = await this.em.getRepository(TournamentRoundInvite).findOneOrFail({ where: { id: data.inviteId }, - relations: ["user", "tournamentRound"], + relations: { + user: true, + tournamentRound: true, + }, }); const tournamentRound = invite.tournamentRound; const introSurveyUrl = tournamentRound.introSurveyUrl; diff --git a/server/src/services/tournament.ts b/server/src/services/tournament.ts index 25d144865..e11f2cb0c 100644 --- a/server/src/services/tournament.ts +++ b/server/src/services/tournament.ts @@ -45,7 +45,7 @@ export class TournamentService extends BaseService { where: { id: id, }, - relations: ["rounds"], + relations: { rounds: true }, }); } return this.em.getRepository(Tournament).findOneOrFail({ @@ -80,7 +80,7 @@ export class TournamentService extends BaseService { async getCurrentTournamentRound(tournamentId?: number): Promise { tournamentId = (await this.getTournament(tournamentId)).id; return await this.em.getRepository(TournamentRound).findOneOrFail({ - relations: ["tournament"], + relations: { tournament: true }, where: { tournamentId }, order: { roundNumber: "DESC" }, }); @@ -89,7 +89,7 @@ export class TournamentService extends BaseService { async getFreePlayTournamentRound(): Promise { const tournamentId = (await this.getFreePlayTournament()).id; return await this.em.getRepository(TournamentRound).findOneOrFail({ - relations: ["tournament"], + relations: { tournament: true }, where: { tournamentId }, order: { roundNumber: "DESC" }, }); @@ -243,7 +243,7 @@ export class TournamentService extends BaseService { if (!afterOffset) afterOffset = await this.getAfterOffset(); const offsetTime = new Date().getTime() - afterOffset; const schedule = await this.em.getRepository(TournamentRoundDate).find({ - select: ["date"], + select: { date: true }, where: { tournamentRoundId: tournamentRound.id, date: MoreThanOrEqual(new Date(offsetTime)) }, order: { date: "ASC" }, }); @@ -484,7 +484,7 @@ export class TournamentService extends BaseService { const tournament = await this.em.getRepository(Tournament).findOne({ where: tournamentId ? { id: tournamentId } : { active: true, name: Not("freeplay") }, - relations: ["treatments"], + relations: { treatments: true }, order: { id: "DESC", }, diff --git a/server/src/util.ts b/server/src/util.ts index 255ab4b6e..5d814f45b 100644 --- a/server/src/util.ts +++ b/server/src/util.ts @@ -15,11 +15,6 @@ import { ClientSafeUser } from "@port-of-mars/shared/types"; const logger = getLogger(__filename); -export function getConnection(): to.Connection { - const connectionName = process.env.NODE_ENV === "test" ? "test" : "default"; - return to.getConnection(connectionName); -} - export function toUrl(page: Page): string { const pagePath = getPagePath(page); // getPagePath returns a string with initial slash diff --git a/server/tests/common.ts b/server/tests/common.ts index e5a36950f..6737bee32 100644 --- a/server/tests/common.ts +++ b/server/tests/common.ts @@ -1,21 +1,23 @@ -import { Connection, createConnection, EntityManager, QueryRunner } from "typeorm"; +import { EntityManager, QueryRunner } from "typeorm"; import { ServiceProvider } from "@port-of-mars/server/services"; import { Tournament, User } from "@port-of-mars/server/entity"; import { Client } from "colyseus"; import EventEmitter from "events"; +import testDataSource from "@port-of-mars/server/datasource"; -export async function initTransaction(): Promise<[Connection, QueryRunner, EntityManager]> { - const conn = await createConnection("test"); - const qs = conn.createQueryRunner(); - await qs.startTransaction(); - const manager = qs.manager; - return [conn, qs, manager]; +export async function initTransaction(): Promise<[QueryRunner, EntityManager]> { + await testDataSource.initialize(); + await testDataSource.synchronize(); + const qr = testDataSource.createQueryRunner(); + await qr.startTransaction(); + const manager = qr.manager; + return [qr, manager]; } -export async function rollbackTransaction(conn: Connection, qs: QueryRunner) { - await qs.rollbackTransaction(); - await qs.release(); - await conn.close(); +export async function rollbackTransaction(qr: QueryRunner) { + await qr.rollbackTransaction(); + await qr.release(); + await testDataSource.destroy(); } export async function createUsers( diff --git a/server/tests/rooms/sologame.test.ts b/server/tests/rooms/sologame.test.ts index f377c9622..e61c23191 100644 --- a/server/tests/rooms/sologame.test.ts +++ b/server/tests/rooms/sologame.test.ts @@ -10,7 +10,7 @@ import { User, } from "@port-of-mars/server/entity"; import { SoloGameState } from "@port-of-mars/server/rooms/sologame/state"; -import { Connection, EntityManager, QueryRunner } from "typeorm"; +import { EntityManager, QueryRunner } from "typeorm"; import { ServiceProvider } from "@port-of-mars/server/services"; import { createUsers, initTransaction, rollbackTransaction } from "../common"; import { @@ -34,7 +34,6 @@ const logger = getLogger(__filename); */ describe("a solo game", () => { - let conn: Connection; let qr: QueryRunner; let manager: EntityManager; let sp: ServiceProvider; @@ -43,13 +42,13 @@ describe("a solo game", () => { let user3: User; beforeAll(async () => { - [conn, qr, manager] = await initTransaction(); + [qr, manager] = await initTransaction(); sp = new ServiceProvider(qr.manager); // create users paul1 paul2 paul3 [user1, user2, user3] = await createUsers(manager, "paul", [1, 2, 3]); }); - afterAll(async () => await rollbackTransaction(conn, qr)); + afterAll(async () => await rollbackTransaction(qr)); describe("a solo game room", () => { let room: SoloGameRoom; diff --git a/server/tests/services/account.test.ts b/server/tests/services/account.test.ts index c3466e10f..2d13accca 100644 --- a/server/tests/services/account.test.ts +++ b/server/tests/services/account.test.ts @@ -1,14 +1,13 @@ import { AccountService } from "@port-of-mars/server/services/account"; import { User, Tournament } from "@port-of-mars/server/entity"; import { settings } from "@port-of-mars/server/settings"; -import { Connection, EntityManager, QueryRunner } from "typeorm"; +import { EntityManager, QueryRunner } from "typeorm"; import { ServiceProvider } from "@port-of-mars/server/services"; import { ServerError } from "@port-of-mars/server/util"; import { createTournament, initTransaction, rollbackTransaction } from "../common"; describe("a potential user", () => { const username = "ahacker"; - let conn: Connection; let qr: QueryRunner; let manager: EntityManager; let sp: ServiceProvider; @@ -17,7 +16,7 @@ describe("a potential user", () => { let accountService: AccountService; beforeAll(async () => { - [conn, qr, manager] = await initTransaction(); + [qr, manager] = await initTransaction(); sp = new ServiceProvider(qr.manager); t = await createTournament(sp); // tr = await createRound(sp, { tournamentId: t.id }); @@ -107,5 +106,5 @@ describe("a potential user", () => { accountService.verifyUnregisteredUser(u, "invalid-registration-token") ).rejects.toThrowError(ServerError); }); - afterAll(async () => rollbackTransaction(conn, qr)); + afterAll(async () => rollbackTransaction(qr)); }); diff --git a/server/tests/services/admin.test.ts b/server/tests/services/admin.test.ts index 48b980074..8e0e3efbd 100644 --- a/server/tests/services/admin.test.ts +++ b/server/tests/services/admin.test.ts @@ -1,5 +1,5 @@ import { Tournament, TournamentRound, Game } from "@port-of-mars/server/entity"; -import { Connection, EntityManager, QueryRunner } from "typeorm"; +import { EntityManager, QueryRunner } from "typeorm"; import { ServiceProvider } from "@port-of-mars/server/services"; import { createRound, @@ -11,7 +11,6 @@ import { import { BAN, ModerationActionType, MUTE } from "@port-of-mars/shared/types"; describe("users in a game", () => { - let conn: Connection; let qr: QueryRunner; let manager: EntityManager; let sp: ServiceProvider; @@ -19,7 +18,7 @@ describe("users in a game", () => { let tr: TournamentRound; beforeAll(async () => { - [conn, qr, manager] = await initTransaction(); + [qr, manager] = await initTransaction(); sp = new ServiceProvider(qr.manager); t = await createTournament(sp, { name: "freeplay" }); tr = await createRound(sp, { tournamentId: t.id }); @@ -160,7 +159,7 @@ describe("users in a game", () => { expect(user.muteStrikes).toBe(1); }); }); - afterAll(async () => await rollbackTransaction(conn, qr)); + afterAll(async () => await rollbackTransaction(qr)); }); const createChatReports = async (sp: ServiceProvider, usernames: Array) => { diff --git a/server/tests/services/replay.test.ts b/server/tests/services/replay.test.ts index ff286701a..a413cffc3 100644 --- a/server/tests/services/replay.test.ts +++ b/server/tests/services/replay.test.ts @@ -35,7 +35,7 @@ import { import { DBPersister, toDBGameEvent } from "@port-of-mars/server/services/persistence"; import { GameReplayer, MarsEventSummarizer } from "@port-of-mars/server/services/replay"; import { GameEvent } from "@port-of-mars/server/rooms/game/events/types"; -import { Connection, EntityManager, QueryRunner } from "typeorm"; +import { EntityManager, QueryRunner } from "typeorm"; import { Persister } from "@port-of-mars/server/rooms/game/types"; import { ServiceProvider } from "@port-of-mars/server/services"; import { Player, Tournament, TournamentRound, User } from "@port-of-mars/server/entity"; @@ -302,7 +302,6 @@ describe("a game", () => { ]; describe("event stream", () => { - let conn: Connection; let manager: EntityManager; let persister: Persister; let qs: QueryRunner; @@ -313,7 +312,7 @@ describe("a game", () => { let users: Array; beforeAll(async () => { - [conn, qs, manager] = await initTransaction(); + [qs, manager] = await initTransaction(); sp = new ServiceProvider(manager); persister = new DBPersister(sp); t = await createTournament(sp); @@ -351,7 +350,7 @@ describe("a game", () => { } }); - afterAll(async () => rollbackTransaction(conn, qs)); + afterAll(async () => rollbackTransaction(qs)); }); it("correctly isolates all mars events", () => { diff --git a/server/tests/services/tournament.test.ts b/server/tests/services/tournament.test.ts index 414e7e8cd..fef7316bf 100644 --- a/server/tests/services/tournament.test.ts +++ b/server/tests/services/tournament.test.ts @@ -1,6 +1,6 @@ import { TournamentRound } from "@port-of-mars/server/entity/TournamentRound"; import { Tournament } from "@port-of-mars/server/entity/Tournament"; -import { Connection, EntityManager, QueryRunner } from "typeorm"; +import { EntityManager, QueryRunner } from "typeorm"; import { ServiceProvider } from "@port-of-mars/server/services"; import { createRound, @@ -17,7 +17,6 @@ import { settings } from "@port-of-mars/server/settings"; import { TournamentRoundSignup } from "@port-of-mars/server/entity/TournamentRoundSignup"; describe("a tournament", () => { - let conn: Connection; let qr: QueryRunner; let manager: EntityManager; let services: ServiceProvider; @@ -34,7 +33,7 @@ describe("a tournament", () => { const afterOffset = 30 * 60 * 1000; beforeAll(async () => { - [conn, qr, manager] = await initTransaction(); + [qr, manager] = await initTransaction(); services = new ServiceProvider(qr.manager); // create special freeplay tournament const freeplayTournament = await createTournament(services, { name: "freeplay" }); @@ -330,5 +329,5 @@ describe("a tournament", () => { }); }); - afterAll(async () => rollbackTransaction(conn, qr)); + afterAll(async () => rollbackTransaction(qr)); }); From 457da0595ee05778a7182754b7b31ef5624bd670 Mon Sep 17 00:00:00 2001 From: sgfost Date: Fri, 2 Feb 2024 19:06:17 -0700 Subject: [PATCH 3/9] test: fix test setup with new DataSource api I think this functions more or less the same as the previous setup with createConnection * some minor clean up in terms of the setup routine --- Makefile | 2 +- server/fixtures/Question.yml | 90 ------------------- server/fixtures/Quiz.yml | 4 - .../{sologame => }/SoloGameTreatment.yml | 0 .../{sologame => }/SoloMarsEventCard.yml | 0 server/jest.config.js | 11 +-- server/package.json | 5 +- server/src/services/stats.ts | 1 - server/src/util.ts | 15 +++- server/tests/common.ts | 7 ++ server/tests/rooms/sologame.test.ts | 2 +- server/tests/services/account.test.ts | 2 +- server/tests/services/admin.test.ts | 2 +- server/tests/services/replay.test.ts | 6 +- server/tests/services/tournament.test.ts | 2 +- server/tests/setup.ts | 13 +++ 16 files changed, 49 insertions(+), 113 deletions(-) delete mode 100644 server/fixtures/Question.yml delete mode 100644 server/fixtures/Quiz.yml rename server/fixtures/{sologame => }/SoloGameTreatment.yml (100%) rename server/fixtures/{sologame => }/SoloMarsEventCard.yml (100%) create mode 100644 server/tests/setup.ts diff --git a/Makefile b/Makefile index 56d2b49b1..6b374a454 100644 --- a/Makefile +++ b/Makefile @@ -114,7 +114,7 @@ docker-compose.yml: base.yml $(ENVIR).yml config.mk $(DB_DATA_PATH) $(DATA_DUMP_ .PHONY: test-setup test-setup: docker-compose.yml - docker compose run --rm server bash -c "dropdb --if-exists -h db -U ${DB_USER} ${TEST_DB_NAME} && createdb -h db -U ${DB_USER} ${TEST_DB_NAME} && npm run typeorm -- schema:sync && npm run load-fixtures ./fixtures/sologame" + docker compose run --rm server bash -c "dropdb --if-exists -h db -U ${DB_USER} ${TEST_DB_NAME} && createdb -h db -U ${DB_USER} ${TEST_DB_NAME} && npm run test-setup" .PHONY: test test: test-setup diff --git a/server/fixtures/Question.yml b/server/fixtures/Question.yml deleted file mode 100644 index de1d3462a..000000000 --- a/server/fixtures/Question.yml +++ /dev/null @@ -1,90 +0,0 @@ -entity: Question -items: - chat: - quiz: '@tutorialquiz' - question: 'Can you chat with other participants?' - correctAnswer: 2 - options: - - 'No, communication is not possible in this game.' - - 'Yes, in the chat box but only during specially designated phases.' - - 'Yes, in the chat box for the duration of the entire game.' - - 'Yes, as long as System Health is greater than 35.' - order: 2 - tutorialElementId: 'chat' - trade: - quiz: '@tutorialquiz' - question: 'How do you trade Influence Resources with others?' - correctAnswer: 2 - options: - - "During the trading phase, say what Influence Resources you want and then you'll receive them." - - 'By communicating your trade preferences in the chat box.' - - 'Discuss potential trades with the other members of your group in the chat and then issue Trade Requests during the trading phase to execute a Trade.' - - 'By investing your time blocks.' - order: 3 - tutorialElementId: 'trade' - upkeep65: - quiz: '@tutorialquiz' - question: 'What happens if the System Health falls between 35 and 65?' - correctAnswer: 2 - options: - - 'You can no longer chat with your group.' - - 'You get an additional four time blocks.' - - 'Two events will occur in this round.' - - 'You receive an extra Accomplishment.' - order: 4 - tutorialElementId: 'upkeep65' - # accCardInfluence: - # quiz: '@tutorialquiz' - # question: 'The following card is a special accomplishment card [image screwcard]. What does this card do?' - # correctAnswer: 3 - # options: - # - 'It costs x influence cards, y influence cards , and ..' - # - 'You earn x points' - # - 'You can only purchase this card if you have the required influence cards' - # - 'All above three answers are correctAnswer' - # order: 5 - # tutorialElementId: 'accCardInfluence' - # accCardUpkeep: - # quiz: '@tutorialquiz' - # question: 'The following card is a special accomplishment card [image screwcard]. What does this card do?' - # correctAnswer: 3 - # options: - # - 'It cost the player nothing individually' - # - 'The Upkeep level of the group will be reduced with x points' - # - 'The individual player will earn x points' - # - 'All above three answers are correctAnswer' - # order: 6 - # tutorialElementId: 'accCardUpkeep' - timeblocks: - quiz: '@tutorialquiz' - question: 'In each round you will have 10 time blocks to invest (unless otherwise instructed). Which of the following statements is INCORRECT?' - correctAnswer: 3 - options: - - 'You can spend as many time blocks as you want.' - - 'Each player has unique skills that change the costs of specific Influence Resources.' - - "Each time block invested in System Health increases your group's System Health by 1." - - 'Every time block you do not spend can be saved and used in the next round.' - order: 7 - tutorialElementId: 'timeblocks' - upkeep35: - quiz: '@tutorialquiz' - question: "If your group's System Health drops below 35 which of the following statements is true?" - correctAnswer: 1 - options: - - 'The game ends.' - - 'The group will encounter three Events this round.' - - 'The player with the least points is eliminated.' - - 'Nothing, life as usual on Mars.' - order: 8 - tutorialElementId: 'upkeep35' - upkeep25: - quiz: '@tutorialquiz' - question: 'At the start of each round, 25 units is subtracted from System Health. What does this represent?' - correctAnswer: 3 - options: - - 'A lack of contributions from the other members of your group.' - - 'Solar flares striking your habitat.' - - 'The costs of trade.' - - 'Standard wear and tear.' - order: 9 - tutorialElementId: 'upkeep25' diff --git a/server/fixtures/Quiz.yml b/server/fixtures/Quiz.yml deleted file mode 100644 index a1d7b9142..000000000 --- a/server/fixtures/Quiz.yml +++ /dev/null @@ -1,4 +0,0 @@ -entity: Quiz -items: - tutorialquiz: - name: TutorialQuiz diff --git a/server/fixtures/sologame/SoloGameTreatment.yml b/server/fixtures/SoloGameTreatment.yml similarity index 100% rename from server/fixtures/sologame/SoloGameTreatment.yml rename to server/fixtures/SoloGameTreatment.yml diff --git a/server/fixtures/sologame/SoloMarsEventCard.yml b/server/fixtures/SoloMarsEventCard.yml similarity index 100% rename from server/fixtures/sologame/SoloMarsEventCard.yml rename to server/fixtures/SoloMarsEventCard.yml diff --git a/server/jest.config.js b/server/jest.config.js index 45e75d775..ac565157a 100644 --- a/server/jest.config.js +++ b/server/jest.config.js @@ -1,15 +1,12 @@ module.exports = { preset: "ts-jest", - roots: [ - "/src", - "/tests" - ], + roots: ["/src", "/tests"], testMatch: ["/tests/**/*.test.ts"], transform: { - "^.+\\.tsx?$": "ts-jest" + "^.+\\.tsx?$": "ts-jest", }, moduleNameMapper: { - '^@port-of-mars/server/(.*)$': '/src/$1', - '^@port-of-mars/shared/(.*)$': '/../shared/src/$1' + "^@port-of-mars/server/(.*)$": "/src/$1", + "^@port-of-mars/shared/(.*)$": "/../shared/src/$1", }, }; diff --git a/server/package.json b/server/package.json index b6dab56d3..4febe9659 100644 --- a/server/package.json +++ b/server/package.json @@ -14,18 +14,19 @@ "start": "ts-node-dev -r tsconfig-paths/register src/index.ts | tee -a /var/log/port-of-mars/index.log", "typeorm": "ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli.js --dataSource src/datasource.ts", "dangerously-dropdb": "dropdb -U marsmadness -h db port_of_mars", - "initdb": "createdb -U marsmadness -h db port_of_mars ; npm run typeorm migration:run && /scripts/freeplay/setup.sh ; npm run load-fixtures ./fixtures/sologame", + "initdb": "createdb -U marsmadness -h db port_of_mars ; npm run typeorm migration:run && /scripts/freeplay/setup.sh ; npm run load-fixtures", "dangerously-loaddb": "psql -h db -U marsmadness port_of_mars < pom-db.sql", "dangerously-resetdb": "dropdb -U marsmadness -h db port_of_mars && createdb -U marsmadness -h db port_of_mars", "migrate": "npm run typeorm migration:run", "repl": "npm run ts-node -r tsconfig-paths/register", "sql": "pgcli -U marsmadness -d port_of_mars -h db", "test": "NODE_ENV=test jest", + "test-setup": "NODE_ENV=test ts-node -r tsconfig-paths/register tests/setup.ts", "lint": "eslint -c .eslintrc.js ./ ../shared --ext .ts", "lint:fix": "eslint --fix -c .eslintrc.js ./ ../shared --ext .ts", "style": "prettier --config ../.prettierrc --check './**/*.ts' '../shared/**/*.ts'", "style:fix": "prettier --config ../.prettierrc --write './**/*.ts' '../shared/**/*.ts'", - "load-fixtures": "fixtures load --require ts-node/register --require tsconfig-paths/register -d src/datasource.ts" + "load-fixtures": "fixtures load --require ts-node/register --require tsconfig-paths/register -d src/datasource.ts ./fixtures" }, "author": "Center for Behavior, Institutions, and the Environment (https://cbie.asu.edu)", "license": "MIT", diff --git a/server/src/services/stats.ts b/server/src/services/stats.ts index 07fd387e4..db0e7c90c 100644 --- a/server/src/services/stats.ts +++ b/server/src/services/stats.ts @@ -7,7 +7,6 @@ import { } from "@port-of-mars/shared/types"; import { Game, Player, SoloGame, SoloPlayer } from "@port-of-mars/server/entity"; import { BaseService } from "@port-of-mars/server/services/db"; -import { IsNull, Not, SelectQueryBuilder } from "typeorm"; import { SoloHighScore } from "@port-of-mars/server/entity/SoloHighScore"; export class StatsService extends BaseService { diff --git a/server/src/util.ts b/server/src/util.ts index 5d814f45b..23d43607a 100644 --- a/server/src/util.ts +++ b/server/src/util.ts @@ -1,6 +1,6 @@ import _ from "lodash"; import * as assert from "assert"; -import * as to from "typeorm"; +import { Builder, Loader, Parser, Resolver, fixturesIterator } from "typeorm-fixtures-cli/dist"; import { ROLES, DashboardMessage, GameType } from "@port-of-mars/shared/types"; import { GameOpts, GameStateOpts } from "@port-of-mars/server/rooms/game/types"; import { @@ -12,9 +12,22 @@ import { getLogger, settings } from "@port-of-mars/server/settings"; import { getServices } from "@port-of-mars/server/services"; import { User } from "@port-of-mars/server/entity"; import { ClientSafeUser } from "@port-of-mars/shared/types"; +import dataSource from "@port-of-mars/server/datasource"; const logger = getLogger(__filename); +export async function loadFixtures() { + const loader = new Loader(); + loader.load(__dirname + "/../fixtures"); + const resolver = new Resolver(); + const fixtures = resolver.resolve(loader.fixtureConfigs); + const builder = new Builder(dataSource, new Parser(), false); + for (const fixture of fixturesIterator(fixtures)) { + const entity = await builder.build(fixture); + await dataSource.getRepository(fixture.entity).save(entity); + } +} + export function toUrl(page: Page): string { const pagePath = getPagePath(page); // getPagePath returns a string with initial slash diff --git a/server/tests/common.ts b/server/tests/common.ts index 6737bee32..57416f21f 100644 --- a/server/tests/common.ts +++ b/server/tests/common.ts @@ -6,6 +6,13 @@ import EventEmitter from "events"; import testDataSource from "@port-of-mars/server/datasource"; export async function initTransaction(): Promise<[QueryRunner, EntityManager]> { + /** + * sets up the test db datasource and returns a queryrunner and that queryrunner's manager + * which should be used for interacting with the db in tests + */ + // FIXME: we need to initialize the datasource + sync for each test suite + // after already doing so in the test setup. may or may not be faster to + // just setup once and run sequentially await testDataSource.initialize(); await testDataSource.synchronize(); const qr = testDataSource.createQueryRunner(); diff --git a/server/tests/rooms/sologame.test.ts b/server/tests/rooms/sologame.test.ts index e61c23191..a93f14df8 100644 --- a/server/tests/rooms/sologame.test.ts +++ b/server/tests/rooms/sologame.test.ts @@ -43,7 +43,7 @@ describe("a solo game", () => { beforeAll(async () => { [qr, manager] = await initTransaction(); - sp = new ServiceProvider(qr.manager); + sp = new ServiceProvider(manager); // create users paul1 paul2 paul3 [user1, user2, user3] = await createUsers(manager, "paul", [1, 2, 3]); }); diff --git a/server/tests/services/account.test.ts b/server/tests/services/account.test.ts index 2d13accca..ce401aa93 100644 --- a/server/tests/services/account.test.ts +++ b/server/tests/services/account.test.ts @@ -17,7 +17,7 @@ describe("a potential user", () => { beforeAll(async () => { [qr, manager] = await initTransaction(); - sp = new ServiceProvider(qr.manager); + sp = new ServiceProvider(manager); t = await createTournament(sp); // tr = await createRound(sp, { tournamentId: t.id }); accountService = sp.account; diff --git a/server/tests/services/admin.test.ts b/server/tests/services/admin.test.ts index 8e0e3efbd..e741443b0 100644 --- a/server/tests/services/admin.test.ts +++ b/server/tests/services/admin.test.ts @@ -19,7 +19,7 @@ describe("users in a game", () => { beforeAll(async () => { [qr, manager] = await initTransaction(); - sp = new ServiceProvider(qr.manager); + sp = new ServiceProvider(manager); t = await createTournament(sp, { name: "freeplay" }); tr = await createRound(sp, { tournamentId: t.id }); // create users diff --git a/server/tests/services/replay.test.ts b/server/tests/services/replay.test.ts index a413cffc3..68ccd1b0f 100644 --- a/server/tests/services/replay.test.ts +++ b/server/tests/services/replay.test.ts @@ -304,7 +304,7 @@ describe("a game", () => { describe("event stream", () => { let manager: EntityManager; let persister: Persister; - let qs: QueryRunner; + let qr: QueryRunner; let sp: ServiceProvider; let t: Tournament; let tr: TournamentRound; @@ -312,7 +312,7 @@ describe("a game", () => { let users: Array; beforeAll(async () => { - [qs, manager] = await initTransaction(); + [qr, manager] = await initTransaction(); sp = new ServiceProvider(manager); persister = new DBPersister(sp); t = await createTournament(sp); @@ -350,7 +350,7 @@ describe("a game", () => { } }); - afterAll(async () => rollbackTransaction(qs)); + afterAll(async () => rollbackTransaction(qr)); }); it("correctly isolates all mars events", () => { diff --git a/server/tests/services/tournament.test.ts b/server/tests/services/tournament.test.ts index fef7316bf..858d445ca 100644 --- a/server/tests/services/tournament.test.ts +++ b/server/tests/services/tournament.test.ts @@ -34,7 +34,7 @@ describe("a tournament", () => { beforeAll(async () => { [qr, manager] = await initTransaction(); - services = new ServiceProvider(qr.manager); + services = new ServiceProvider(manager); // create special freeplay tournament const freeplayTournament = await createTournament(services, { name: "freeplay" }); await createRound(services, { tournamentId: freeplayTournament.id }); diff --git a/server/tests/setup.ts b/server/tests/setup.ts new file mode 100644 index 000000000..426703e99 --- /dev/null +++ b/server/tests/setup.ts @@ -0,0 +1,13 @@ +import testDataSource from "@port-of-mars/server/datasource"; +import { loadFixtures } from "@port-of-mars/server/util"; + +async function setup() { + await testDataSource.initialize(); + await testDataSource.synchronize(); // Be cautious with this in a real DB + await loadFixtures(); +} + +setup().catch(err => { + console.error("Failed to set up tests:", err); + process.exit(1); +}); From d8bed377ca648c5439d2275b90a69bc70fcc67ac Mon Sep 17 00:00:00 2001 From: sgfost <46429375+sgfost@users.noreply.github.com> Date: Mon, 5 Feb 2024 12:15:34 -0700 Subject: [PATCH 4/9] fix: cli process still needs to connect to db (datasource innit) --- server/src/cli.ts | 52 +++++++++++++++++++++++++---------------------- 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/server/src/cli.ts b/server/src/cli.ts index d412f45cb..66e6d7ed0 100644 --- a/server/src/cli.ts +++ b/server/src/cli.ts @@ -44,6 +44,16 @@ function getRandomInt(min: number, max: number) { return Math.floor(Math.random() * (max - min) + min); } +async function withDataSource(func: (em: EntityManager) => Promise): Promise { + await appDataSource.initialize(); + const em = appDataSource.manager; + try { + await func(em); + } finally { + await appDataSource.destroy(); + } +} + async function exportSoloData(em: EntityManager, start?: string, end?: string) { const soloGameService = getServices().sologame; await mkdir("/dump/solo", { recursive: true }); @@ -474,7 +484,7 @@ program .option("--tournamentId ", "ID of the tournament", customParseInt) .description("link existing Treatments to a given Tournament") .action(async cmd => { - await appDataSource.transaction(async em => + await withDataSource(async em => addTreatments(em, cmd.treatmentIds, cmd.tournamentId) ); }) @@ -491,7 +501,7 @@ program .option("--tournamentId ", "ID of the tournament", customParseInt) .description("add a Treatment (set of mars event overrides) to a Tournament") .action(async cmd => { - await appDataSource.transaction(async em => + await withDataSource(async em => createTournamentTreatment( em, cmd.name, @@ -522,7 +532,7 @@ program ) .description("add a TournamentRoundDate for the given date") .action(async cmd => { - await appDataSource.transaction(async em => + await withDataSource(async em => createTournamentRoundDate(em, cmd.date, cmd.tournamentRoundId) ); }) @@ -537,7 +547,7 @@ program ) .description("report emails for all users in the given tournament round") .action(async cmd => { - await appDataSource.transaction(async em => + await withDataSource(async em => exportTournamentRoundEmails(em, cmd.tournamentRoundId) ); }) @@ -562,7 +572,7 @@ program ) .description("create invitations for the given users in the given tournament round") .action(async cmd => { - await appDataSource.transaction(async em => + await withDataSource(async em => createTournamentRoundInvites( em, cmd.tournamentRoundId, @@ -592,7 +602,7 @@ program .option("--announcement ", "Tournament Round announcement message", "") .description("create a tournament round") .action(async cmd => { - await appDataSource.transaction(async em => + await withDataSource(async em => createRound( em, cmd.open, @@ -622,7 +632,7 @@ program .option("--description ", "Description of the tournament") .description("create a tournament") .action(async cmd => { - await appDataSource.transaction(async em => + await withDataSource(async em => createTournament( em, cmd.tournamentName, @@ -645,7 +655,7 @@ program .description("set a user as an administrator") .requiredOption("--username ", "username of the user") .action(async cmd => { - await appDataSource.transaction(async em => setAdminUser(em, cmd.username)); + await withDataSource(async em => setAdminUser(em, cmd.username)); }) ) .addCommand( @@ -655,9 +665,7 @@ program .requiredOption("--startId ", "initial user ID in range", customParseInt, 1) .requiredOption("--endId ", "end user ID in range", customParseInt, 1942) .action(async cmd => { - await appDataSource.transaction(async em => - anonymizeUsernames(em, cmd.startId, cmd.endId) - ); + await withDataSource(async em => anonymizeUsernames(em, cmd.startId, cmd.endId)); }) ) ) @@ -671,7 +679,7 @@ program .description("finalize a game that wasn't finalized properly") .requiredOption("--gameId ", "id of game", customParseInt) .action(async cmd => { - await appDataSource.transaction(async em => finalize(em, cmd.gameId)); + await withDataSource(async em => finalize(em, cmd.gameId)); }) ) .addCommand( @@ -682,7 +690,7 @@ program ) .requiredOption("--gameId ", "id of game", customParseInt) .action(async cmd => { - await appDataSource.transaction(async em => validate(em, cmd.gameId)); + await withDataSource(async em => validate(em, cmd.gameId)); }) ) ) @@ -698,7 +706,7 @@ program .description("dump game data for a given tournament round id to a pile of CSV files") .requiredOption("--tournamentId ", "tournament id", customParseInt) .action(async cmd => { - await appDataSource.transaction(async em => exportTournament(em, cmd.tournamentId)); + await withDataSource(async em => exportTournament(em, cmd.tournamentId)); }) ) .addCommand( @@ -717,9 +725,7 @@ program [] as Array ) .action(async cmd => { - await appDataSource.transaction(async em => - exportTournamentRound(em, cmd.tournamentId, cmd.gids) - ); + await withDataSource(async em => exportTournamentRound(em, cmd.tournamentId, cmd.gids)); }) ) .addCommand( @@ -729,7 +735,7 @@ program .option("-s, --start ", "Start date (YYYY-MM-DD)") .option("-e, --end ", "End date (YYYY-MM-DD)") .action(async cmd => { - await appDataSource.transaction(async em => exportSoloData(em, cmd.start, cmd.end)); + await withDataSource(async em => exportSoloData(em, cmd.start, cmd.end)); }) ) ) @@ -744,7 +750,7 @@ program [] as Array ) .action(async cmd => { - await appDataSource.transaction(async em => checkQuizCompletion(em, cmd.ids)); + await withDataSource(async em => checkQuizCompletion(em, cmd.ids)); }) ) .addCommand( @@ -758,7 +764,7 @@ program [] as Array ) .action(async cmd => { - await appDataSource.transaction(async em => completeQuizCompletion(em, cmd.ids)); + await withDataSource(async em => completeQuizCompletion(em, cmd.ids)); }) ) .addCommand( @@ -775,9 +781,7 @@ program "generate a CSV for mailchimp import of all active users with a valid email address" ) .action(async cmd => { - await appDataSource.transaction(async em => - exportActiveEmails(em, cmd.after, cmd.enableAmdf) - ); + await withDataSource(async em => exportActiveEmails(em, cmd.after, cmd.enableAmdf)); }) ) .addCommand( @@ -790,7 +794,7 @@ program ) .description("Deactivate users who have unsubscribed from emails (currently from mailchimp).") .action(async cmd => { - await appDataSource.transaction(async em => deactivateUsers(em, cmd.filename)); + await withDataSource(async em => deactivateUsers(em, cmd.filename)); }) ) .addCommand( From 44a4b3802221b9e7c9367ca2c42630006ad6f284 Mon Sep 17 00:00:00 2001 From: sgfost Date: Tue, 6 Feb 2024 20:41:28 -0700 Subject: [PATCH 5/9] build: fix generated vars missing from .env changed the server/.env generation from envsubst to sed and includes remaining server secrets (DB_PASSWORD which is needed for datasource.ts SECRET_KEY and MAIL_API_KEY which were previously read in from a file) assumes that if .env exists it should not be overwritten. this does mean managing additions to the versioned .env.template manually.. --- Makefile | 19 ++++++++++++++----- server/.env.template | 15 +++++++++------ server/src/settings.ts | 8 +++----- 3 files changed, 26 insertions(+), 16 deletions(-) diff --git a/Makefile b/Makefile index 6b374a454..d744e313d 100644 --- a/Makefile +++ b/Makefile @@ -68,10 +68,19 @@ $(REDIS_SETTINGS_PATH): server/deploy/settings.template.json | keys cp server/deploy/settings.template.json $(REDIS_SETTINGS_PATH) $(SERVER_ENV): $(SERVER_ENV_TEMPLATE) $(SECRETS) - POM_BASE_URL=${POM_BASE_URL} \ - DB_PASSWORD=$$(cat $(DB_PASSWORD_PATH)); \ - envsubst < $(SERVER_ENV_TEMPLATE) > $(SERVER_ENV) - + if [ ! -f $(SERVER_ENV) ]; then \ + cp $(SERVER_ENV_TEMPLATE) $(SERVER_ENV); \ + DB_PASSWORD=$$(cat $(DB_PASSWORD_PATH)); \ + SECRET_KEY=$$(cat $(SECRET_KEY_PATH)); \ + sed \ + -e "s|BASE_URL=.*|BASE_URL=${POM_BASE_URL}|" \ + -e "s|DB_PASSWORD=.*|DB_PASSWORD=$${DB_PASSWORD}|" \ + -e "s|SECRET_KEY=.*|SECRET_KEY=$${SECRET_KEY}|" \ + $(SERVER_ENV_TEMPLATE) > $(SERVER_ENV); \ + else \ + echo "$(SERVER_ENV) already exists. skipping"; \ + fi + $(PGPASS_PATH): $(DB_PASSWORD_PATH) server/deploy/pgpass.template | keys DB_PASSWORD=$$(cat $(DB_PASSWORD_PATH)); \ sed "s|DB_PASSWORD|$$DB_PASSWORD|g" server/deploy/pgpass.template > $(PGPASS_PATH) @@ -97,7 +106,7 @@ $(SECRET_KEY_PATH): | keys echo $${SECRET_KEY} > $(SECRET_KEY_PATH) .PHONY: settings -settings: $(SENTRY_DSN_PATH) $(SECRET_KEY_PATH) | keys +settings: $(SENTRY_DSN_PATH) $(SECRET_KEY_PATH) $(GA_TAG_PATH) | keys echo 'export const BUILD_ID = "${BUILD_ID}";' > $(SHARED_CONFIG_PATH) echo 'export const SENTRY_DSN = "${SENTRY_DSN}";' >> $(SHARED_CONFIG_PATH) echo 'export const GA_TAG = "${GA_TAG}";' >> $(SHARED_CONFIG_PATH) diff --git a/server/.env.template b/server/.env.template index 474395cc3..912d254ad 100644 --- a/server/.env.template +++ b/server/.env.template @@ -1,9 +1,12 @@ -BASE_URL=${POM_BASE_URL} +BASE_URL=base-url -DB_PASSWORD=${DB_PASSWORD} +DB_PASSWORD=db-password +SECRET_KEY=secret-key -GOOGLE_CLIENT_ID=changeme -GOOGLE_CLIENT_SECRET=changeme +MAIL_API_KEY=mail-api-key -FACEBOOK_CLIENT_ID=changeme -FACEBOOK_CLIENT_SECRET=changeme +GOOGLE_CLIENT_ID=google-client-id +GOOGLE_CLIENT_SECRET=google-client-secret + +FACEBOOK_CLIENT_ID=facebook-client-id +FACEBOOK_CLIENT_SECRET=facbook-client-secret diff --git a/server/src/settings.ts b/server/src/settings.ts index dca51b32c..80ad49d18 100644 --- a/server/src/settings.ts +++ b/server/src/settings.ts @@ -9,8 +9,6 @@ import * as dotenv from "dotenv"; dotenv.config(); -export const SECRET_KEY: string = fs.readFileSync("/run/secrets/secret_key", "utf8").trim(); - export interface AppSettings { emailer: Emailer; host: string; @@ -34,7 +32,7 @@ const dev: () => AppSettings = () => ({ host: process.env.BASE_URL || "http://localhost:8081", serverHost: "http://localhost:2567", logging: new DevLogging(), - secret: SECRET_KEY, + secret: process.env.SECRET_KEY || "", googleAuth: { clientId: process.env.GOOGLE_CLIENT_ID || "", clientSecret: process.env.GOOGLE_CLIENT_SECRET || "", @@ -49,11 +47,11 @@ const dev: () => AppSettings = () => ({ const staging: () => AppSettings = () => { const devSettings = dev(); - const apiKey = fs.readFileSync("/run/secrets/mail_api_key", "utf-8").trim(); + const mailApiKey = process.env.MAIL_API_KEY || ""; const domain = "mg.comses.net"; return { ...devSettings, - emailer: new MailgunEmailer({ api_key: apiKey, domain }), + emailer: new MailgunEmailer({ api_key: mailApiKey, domain }), host: process.env.BASE_URL || "https://staging.portofmars.asu.edu", serverHost: process.env.BASE_URL || "https://staging.portofmars.asu.edu", }; From 94852000d400dd047fd1796c11c7a8562bcc161f Mon Sep 17 00:00:00 2001 From: sgfost Date: Fri, 23 Feb 2024 15:29:42 -0700 Subject: [PATCH 6/9] build: clean up configuration and build mirrors improvements in comses/comses.net#696 * use one shared .env for all non-secret config (replaces server/.env and client/.../config.ts) * continue to use files for secrets, but with docker compose secrets * move base_url mapping to shared/settings.ts from `configure` script * replace server/deploy/ with top level deploy dir * clean up anything unused or unecessary from Makefile --- .dockerignore | 3 +- Makefile | 165 +++++++----------- base.yml | 30 +++- client/src/components/global/Footer.vue | 2 +- client/src/main.ts | 4 +- client/src/util.ts | 3 +- client/vite.config.ts | 18 +- configure | 16 +- deploy/conf/.env.template | 12 ++ {server/deploy => deploy/conf}/redis.conf | 0 .../conf}/settings.template.json | 0 deploy/scripts/envreplace | 26 +++ dev.yml | 5 +- prod.yml | 2 +- server/.env.template | 12 -- server/{deploy => }/Dockerfile.dev | 0 server/{deploy => }/Dockerfile.prod | 0 server/deploy/pgpass.template | 1 - server/src/datasource.ts | 6 +- server/src/index.ts | 5 +- server/src/services/persistence.ts | 4 +- server/src/settings.ts | 26 +-- shared/src/settings.ts | 24 ++- staging.yml | 2 +- 24 files changed, 186 insertions(+), 180 deletions(-) create mode 100644 deploy/conf/.env.template rename {server/deploy => deploy/conf}/redis.conf (100%) rename {server/deploy => deploy/conf}/settings.template.json (100%) create mode 100755 deploy/scripts/envreplace delete mode 100644 server/.env.template rename server/{deploy => }/Dockerfile.dev (100%) rename server/{deploy => }/Dockerfile.prod (100%) delete mode 100644 server/deploy/pgpass.template diff --git a/.dockerignore b/.dockerignore index 4b26b5a70..215cff0ba 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,6 +1,7 @@ client/node_modules server/node_modules -server/deploy/Dockerfile* +server/Dockerfile.dev +server/Dockerfile.prod server/output docker keys diff --git a/Makefile b/Makefile index d744e313d..d719e0ecd 100644 --- a/Makefile +++ b/Makefile @@ -1,54 +1,39 @@ -include config.mk - -DB_USER=marsmadness -TEST_DB_NAME=pom_testing DB_DATA_PATH=docker/data DATA_DUMP_PATH=docker/dump LOG_DATA_PATH=docker/logs + DB_PASSWORD_PATH=keys/pom_db_password -REDIS_SETTINGS_PATH=keys/settings.json -SERVER_ENV_TEMPLATE=server/.env.template -SERVER_ENV=server/.env PGPASS_PATH=keys/.pgpass SECRET_KEY_PATH=keys/secret_key -SENTRY_DSN_PATH=keys/sentry_dsn -SENTRY_DSN=$(shell cat $(SENTRY_DSN_PATH)) -MAIL_API_KEY_PATH=keys/mail_api_key -SECRETS=$(MAIL_API_KEY_PATH) $(DB_PASSWORD_PATH) $(PGPASS_PATH) $(SENTRY_DSN_PATH) $(SECRET_KEY_PATH) -SHARED_CONFIG_PATH=shared/src/assets/config.ts -BUILD_ID=$(shell git describe --tags --abbrev=1) -GA_TAG_PATH=keys/ga_tag -GA_TAG=$(shell cat $(GA_TAG_PATH)) +GENERATED_SECRETS=$(DB_PASSWORD_PATH) $(PGPASS_PATH) $(SECRET_KEY_PATH) +EXT_SECRETS=mail_api_key google_client_secret facebook_client_secret -.PHONY: build -build: docker-compose.yml - docker compose pull db redis - docker compose build --pull +ENVREPLACE := deploy/scripts/envreplace +DEPLOY_CONF_DIR=deploy/conf +ENV_TEMPLATE=${DEPLOY_CONF_DIR}/.env.template +DYNAMIC_SETTINGS_TEMPLATE=${DEPLOY_CONF_DIR}/settings.template.json +DYNAMIC_SETTINGS_PATH=keys/settings.json + +include config.mk +include .env -.PHONY: browser -browser: - firefox --new-tab --url 'ext+container:name=Bob&url=http://localhost:8081/#/game' \ - --new-tab --url 'ext+container:name=Amanda&url=http://localhost:8081/#/game' \ - --new-tab --url 'ext+container:name=Frank&url=http://localhost:8081/#/game' \ - --new-tab --url 'ext+container:name=Sydney&url=http://localhost:8081/#/game' \ - --new-tab --url 'ext+container:name=Adison&url=http://localhost:8081/#/game' \ - --new-tab --url 'ext+container:name=Bob2&url=http://localhost:8081/#/game' \ - --new-tab --url 'ext+container:name=Amanda2&url=http://localhost:8081/#/game' \ - --new-tab --url 'ext+container:name=Frank2&url=http://localhost:8081/#/game' \ - --new-tab --url 'ext+container:name=Sydney2&url=http://localhost:8081/#/game' \ - --new-tab --url 'ext+container:name=Adison2&url=http://localhost:8081/#/game' - -.PHONY: browser-staging -browser-staging: - firefox --new-tab --url 'ext+container:name=Bob&url=http://alpha.portofmars.asu.edu' \ - --new-tab --url 'ext+container:name=Amanda&url=http://alpha.portofmars.asu.edu' \ - --new-tab --url 'ext+container:name=Frank&url=http://alpha.portofmars.asu.edu' \ - --new-tab --url 'ext+container:name=Sydney&url=http://alpha.portofmars.asu.edu' \ - --new-tab --url 'ext+container:name=Adison&url=http://alpha.portofmars.asu.edu' +.EXPORT_ALL_VARIABLES: + +$(LOG_DATA_PATH): + mkdir -p $(LOG_DATA_PATH) + +$(DB_DATA_PATH): + mkdir -p "$(DB_DATA_PATH)" + +$(DATA_DUMP_PATH): + mkdir -p $(DATA_DUMP_PATH) keys: mkdir -p keys +$(DYNAMIC_SETTINGS_PATH): $(DYNAMIC_SETTINGS_TEMPLATE) | keys + cp $(DYNAMIC_SETTINGS_TEMPLATE) $(DYNAMIC_SETTINGS_PATH) + $(DB_PASSWORD_PATH): | keys DB_PASSWORD=$$(openssl rand -base64 48); \ TODAY=$$(date +%Y-%m-%d-%H:%M:%S); \ @@ -58,72 +43,56 @@ $(DB_PASSWORD_PATH): | keys fi; \ echo "$${DB_PASSWORD}" > $(DB_PASSWORD_PATH) -$(LOG_DATA_PATH): - mkdir -p $(LOG_DATA_PATH) - -$(DATA_DUMP_PATH): - mkdir -p $(DATA_DUMP_PATH) - -$(REDIS_SETTINGS_PATH): server/deploy/settings.template.json | keys - cp server/deploy/settings.template.json $(REDIS_SETTINGS_PATH) - -$(SERVER_ENV): $(SERVER_ENV_TEMPLATE) $(SECRETS) - if [ ! -f $(SERVER_ENV) ]; then \ - cp $(SERVER_ENV_TEMPLATE) $(SERVER_ENV); \ - DB_PASSWORD=$$(cat $(DB_PASSWORD_PATH)); \ - SECRET_KEY=$$(cat $(SECRET_KEY_PATH)); \ - sed \ - -e "s|BASE_URL=.*|BASE_URL=${POM_BASE_URL}|" \ - -e "s|DB_PASSWORD=.*|DB_PASSWORD=$${DB_PASSWORD}|" \ - -e "s|SECRET_KEY=.*|SECRET_KEY=$${SECRET_KEY}|" \ - $(SERVER_ENV_TEMPLATE) > $(SERVER_ENV); \ - else \ - echo "$(SERVER_ENV) already exists. skipping"; \ - fi - -$(PGPASS_PATH): $(DB_PASSWORD_PATH) server/deploy/pgpass.template | keys - DB_PASSWORD=$$(cat $(DB_PASSWORD_PATH)); \ - sed "s|DB_PASSWORD|$$DB_PASSWORD|g" server/deploy/pgpass.template > $(PGPASS_PATH) +$(PGPASS_PATH): $(DB_PASSWORD_PATH) | keys + echo "${DB_HOST}:5432:*:${DB_USER}:$$(cat $(DB_PASSWORD_PATH))" > $(PGPASS_PATH) chmod 0600 $(PGPASS_PATH) -$(MAIL_API_KEY_PATH): | keys - touch "$(MAIL_API_KEY_PATH)" +$(SECRET_KEY_PATH): | keys + SECRET_KEY=$$(openssl rand -base64 48); \ + echo $${SECRET_KEY} > $(SECRET_KEY_PATH) -$(SENTRY_DSN_PATH): | keys - touch "$(SENTRY_DSN_PATH)" +.PHONY: secrets +secrets: keys $(GENERATED_SECRETS) + for secret_path in $(EXT_SECRETS); do \ + touch keys/$$secret_path; \ + done + +.env: $(ENV_TEMPLATE) + if [ ! -f .env ]; then \ + cp $(ENV_TEMPLATE) .env; \ + fi -$(GA_TAG_PATH): | keys - touch "$(GA_TAG_PATH)" +.PHONY: release-version +release-version: .env + $(ENVREPLACE) RELEASE_VERSION $$(git describe --tags --abbrev=1) .env -$(DB_DATA_PATH): - mkdir -p "$(DB_DATA_PATH)" - -.PHONY: secrets -secrets: $(SECRETS) +docker-compose.yml: base.yml $(DEPLOY_ENVIRONMENT).yml config.mk $(DB_DATA_PATH) $(DATA_DUMP_PATH) $(LOG_DATA_PATH) $(DYNAMIC_SETTINGS_PATH) secrets $(PGPASS_PATH) release-version + case "$(DEPLOY_ENVIRONMENT)" in \ + dev|staging|prod) docker compose -f base.yml -f "$(DEPLOY_ENVIRONMENT).yml" config > docker-compose.yml;; \ + *) echo "invalid environment. must be either dev, staging or prod" 1>&2; exit 1;; \ + esac -$(SECRET_KEY_PATH): | keys - SECRET_KEY=$$(openssl rand -base64 48); \ - echo $${SECRET_KEY} > $(SECRET_KEY_PATH) +.PHONY: build +build: docker-compose.yml + docker compose pull db redis + docker compose build --pull -.PHONY: settings -settings: $(SENTRY_DSN_PATH) $(SECRET_KEY_PATH) $(GA_TAG_PATH) | keys - echo 'export const BUILD_ID = "${BUILD_ID}";' > $(SHARED_CONFIG_PATH) - echo 'export const SENTRY_DSN = "${SENTRY_DSN}";' >> $(SHARED_CONFIG_PATH) - echo 'export const GA_TAG = "${GA_TAG}";' >> $(SHARED_CONFIG_PATH) +.PHONY: deploy +deploy: build + docker compose up -d +.PHONY: buildprod +buildprod: docker-compose.yml + docker compose run --rm client npm run build + docker compose run --rm server npm run build +.PHONY: initialize initialize: build docker compose run --rm server npm run initdb -docker-compose.yml: base.yml $(ENVIR).yml config.mk $(DB_DATA_PATH) $(DATA_DUMP_PATH) $(LOG_DATA_PATH) $(REDIS_SETTINGS_PATH) $(PGPASS_PATH) $(SERVER_ENV) settings - case "$(ENVIR)" in \ - dev|staging|prod) docker compose -f base.yml -f "$(ENVIR).yml" config > docker-compose.yml;; \ - *) echo "invalid environment. must be either dev, staging or prod" 1>&2; exit 1;; \ - esac - .PHONY: test-setup test-setup: docker-compose.yml - docker compose run --rm server bash -c "dropdb --if-exists -h db -U ${DB_USER} ${TEST_DB_NAME} && createdb -h db -U ${DB_USER} ${TEST_DB_NAME} && npm run test-setup" + docker compose run --rm server bash -c "dropdb --if-exists -h ${DB_HOST} -U ${DB_USER} ${TEST_DB_NAME} && createdb -h db -U ${DB_USER} ${TEST_DB_NAME} && npm run test-setup" .PHONY: test test: test-setup @@ -132,16 +101,7 @@ test: test-setup .PHONY: test-server test-server: test-setup - docker compose run --rm server npm run test $(tests) - -.PHONY: deploy -deploy: build - docker compose up -d - -.PHONY: buildprod -buildprod: docker-compose.yml - docker compose run --rm client npm run build - docker compose run --rm server npm run build + docker compose run --rm server npm run test .PHONY: docker-clean docker-clean: @@ -151,4 +111,5 @@ docker-clean: .PHONY: clean clean: - rm -f server/.env # any other generated resources? SHARED_CONFIG_PATH? + @echo "Backing up generated files to /tmp directory" + mv .env config.mk docker-compose.yml $(shell mktemp -d) diff --git a/base.yml b/base.yml index a1a095dad..dd329ba39 100644 --- a/base.yml +++ b/base.yml @@ -4,6 +4,14 @@ services: context: . restart: always image: port-of-mars/server:dev + secrets: + - pom_db_password + - mail_api_key + - secret_key + - google_client_secret + - facebook_client_secret + env_file: + - .env depends_on: redis: condition: service_started @@ -13,9 +21,8 @@ services: - ./docker/dump:/dump - ./docker/logs:/var/log/port-of-mars - ./keys/.pgpass:/root/.pgpass - - ./keys:/run/secrets + - ./keys/settings.json:/run/secrets/settings.json - ./scripts:/scripts - - ./server/.env:/code/server/.env - ./.prettierrc:/code/.prettierrc redis: image: redis:7 @@ -27,12 +34,25 @@ services: timeout: 5s retries: 5 image: postgres:12 + secrets: + - pom_db_password restart: always environment: - POSTGRES_USER: marsmadness + POSTGRES_DB: ${DB_NAME} + POSTGRES_USER: ${DB_USER} POSTGRES_PASSWORD_FILE: /run/secrets/pom_db_password - POSTGRES_DB: port_of_mars PGDATA: /var/lib/postgresql/data/pgdata volumes: - - ./keys/pom_db_password:/run/secrets/pom_db_password:ro - ./docker/data:/var/lib/postgresql/data/pgdata + +secrets: + pom_db_password: + file: ./keys/pom_db_password + mail_api_key: + file: ./keys/mail_api_key + secret_key: + file: ./keys/secret_key + google_client_secret: + file: ./keys/google_client_secret + facebook_client_secret: + file: ./keys/facebook_client_secret diff --git a/client/src/components/global/Footer.vue b/client/src/components/global/Footer.vue index ec7d25ba6..de437cad4 100644 --- a/client/src/components/global/Footer.vue +++ b/client/src/components/global/Footer.vue @@ -93,7 +93,7 @@ ccarra1@asu.edu © 2020-{{ currentYear }} Arizona Board of Regents | - {{ constants.BUILD_ID }} + {{ constants.RELEASE_VERSION }} diff --git a/client/src/main.ts b/client/src/main.ts index 0fb91be64..db36426ed 100644 --- a/client/src/main.ts +++ b/client/src/main.ts @@ -7,7 +7,7 @@ import Vuex from "vuex"; import * as Sentry from "@sentry/browser"; import { Vue as VueIntegration } from "@sentry/integrations"; import { Integrations } from "@sentry/tracing"; -import { isStagingOrProduction, Constants } from "@port-of-mars/shared/settings"; +import { isStagingOrProduction, Constants, SERVER_URL_WS } from "@port-of-mars/shared/settings"; import { Ajax } from "@port-of-mars/client/plugins/ajax"; import { TypedStore } from "@port-of-mars/client/plugins/tstore"; import { getAssetUrl, SfxManager } from "@port-of-mars/client/util"; @@ -40,7 +40,7 @@ if (isStagingOrProduction()) { ); } -const $client = new Colyseus.Client(process.env.SERVER_URL_WS || undefined); +const $client = new Colyseus.Client(SERVER_URL_WS || undefined); const $sfx = new SfxManager(); Vue.prototype.$getAssetUrl = getAssetUrl; diff --git a/client/src/util.ts b/client/src/util.ts index 3304943cb..76f395b4b 100644 --- a/client/src/util.ts +++ b/client/src/util.ts @@ -1,10 +1,11 @@ import { SetSfx } from "@port-of-mars/shared/game/responses"; import { Sfx } from "@port-of-mars/shared/game/responses"; +import { SERVER_URL_HTTP } from "@port-of-mars/shared/settings"; import { Howl } from "howler"; export function url(path: string) { // workaround to connect to localhost:2567 server endpoints - return `${process.env.SERVER_URL_HTTP}${path}`; + return `${SERVER_URL_HTTP}${path}`; } export function getAssetUrl(path: string) { diff --git a/client/vite.config.ts b/client/vite.config.ts index d55a856b1..bced4f44c 100644 --- a/client/vite.config.ts +++ b/client/vite.config.ts @@ -1,14 +1,8 @@ import vue from "@vitejs/plugin-vue2"; import { defineConfig } from "vite"; import path from "path"; -import fs from "fs"; import autoprefixer from "autoprefixer"; -let SENTRY_DSN = ""; -if (fs.existsSync("/run/secrets/sentry_dsn")) { - SENTRY_DSN = fs.readFileSync("/run/secrets/sentry_dsn", "utf8").trim(); -} - export default defineConfig({ plugins: [vue()], server: { @@ -36,12 +30,10 @@ export default defineConfig({ }, }, define: { - "process.env.SERVER_URL_WS": JSON.stringify( - process.env.NODE_ENV === "development" ? "ws://localhost:2567" : "" - ), - "process.env.SERVER_URL_HTTP": JSON.stringify( - process.env.NODE_ENV === "development" ? "http://localhost:2567" : "" - ), - "process.env.SENTRY_DSN": JSON.stringify(SENTRY_DSN), + // make process.env work on the client + ...Object.keys(process.env).reduce((acc, key) => { + acc[`process.env.${key}`] = JSON.stringify(process.env[key]); + return acc; + }, {}), }, }); diff --git a/configure b/configure index 917a0a59c..b8828b881 100755 --- a/configure +++ b/configure @@ -4,18 +4,8 @@ set -o nounset set -o pipefail set -o errexit -declare -A url_map +DEPLOY_ENVIRONMENT=${1:-dev} # dev|staging|prod -url_map['dev']='http://localhost:8081' -url_map['staging']='https://staging.portofmars.asu.edu' -url_map['prod']='https://portofmars.asu.edu' +echo "configuring for **${DEPLOY_ENVIRONMENT}** environment" -ENVIR=${1:-dev} # dev|staging|prod -POM_BASE_URL=${url_map[$ENVIR]} - -echo "configuring for **${ENVIR}** environment with base url $POM_BASE_URL" - -envsubst > config.mk < config.mk diff --git a/deploy/conf/.env.template b/deploy/conf/.env.template new file mode 100644 index 000000000..e6d37a6e5 --- /dev/null +++ b/deploy/conf/.env.template @@ -0,0 +1,12 @@ +RELEASE_VERSION= + +DB_HOST=db +DB_NAME=port_of_mars +DB_USER=marsmadness +TEST_DB_NAME=pom_testing + +SENTRY_DSN= +GA_TAG= + +GOOGLE_CLIENT_ID=changeme +FACEBOOK_CLIENT_ID=changeme diff --git a/server/deploy/redis.conf b/deploy/conf/redis.conf similarity index 100% rename from server/deploy/redis.conf rename to deploy/conf/redis.conf diff --git a/server/deploy/settings.template.json b/deploy/conf/settings.template.json similarity index 100% rename from server/deploy/settings.template.json rename to deploy/conf/settings.template.json diff --git a/deploy/scripts/envreplace b/deploy/scripts/envreplace new file mode 100755 index 000000000..c77d3034f --- /dev/null +++ b/deploy/scripts/envreplace @@ -0,0 +1,26 @@ +#!/usr/bin/bash + +# envreplace: replace environment variables in a file +# usage: envreplace [file (default: .env)] + +VAR_NAME=$1 +NEW_VALUE=$2 +ENV_FILE=${3:-".env"} + +if [ -z "$VAR_NAME" ] || [ -z "$NEW_VALUE" ]; then + echo "usage: envreplace [file (default: .env)]" + exit 1 +fi + +if sed --version 2>&1 | grep -q "GNU"; then + SED_CMD="sed -i" # GNU sed +else + SED_CMD="sed -i ''" # BSD sed +fi + +if grep -q "^$VAR_NAME=" "$ENV_FILE"; then + $SED_CMD "s|^$VAR_NAME=.*|$VAR_NAME=$NEW_VALUE|" "$ENV_FILE" +else + echo "$VAR_NAME not found in $ENV_FILE" + # echo "$VAR_NAME=$NEW_VALUE" >> $ENV_FILE +fi diff --git a/dev.yml b/dev.yml index 9c47c43f7..7f51f57b7 100644 --- a/dev.yml +++ b/dev.yml @@ -5,9 +5,10 @@ services: context: . restart: always image: port-of-mars/client:dev + env_file: + - .env volumes: - ./client:/code/client - - ./keys/sentry_dsn:/run/secrets/sentry_dsn - ./shared/src:/code/shared/src - /code/client/node_modules - ./.prettierrc:/code/.prettierrc @@ -15,7 +16,7 @@ services: - "127.0.0.1:8081:8080" server: build: - dockerfile: server/deploy/Dockerfile.dev + dockerfile: server/Dockerfile.dev ports: - "127.0.0.1:2567:2567" volumes: diff --git a/prod.yml b/prod.yml index 4405a287d..29707ef9d 100644 --- a/prod.yml +++ b/prod.yml @@ -4,7 +4,7 @@ services: - ./docker/redis:/data server: build: - dockerfile: server/deploy/Dockerfile.prod + dockerfile: server/Dockerfile.prod args: NODE_ARG: production image: port-of-mars/server:prod diff --git a/server/.env.template b/server/.env.template deleted file mode 100644 index 912d254ad..000000000 --- a/server/.env.template +++ /dev/null @@ -1,12 +0,0 @@ -BASE_URL=base-url - -DB_PASSWORD=db-password -SECRET_KEY=secret-key - -MAIL_API_KEY=mail-api-key - -GOOGLE_CLIENT_ID=google-client-id -GOOGLE_CLIENT_SECRET=google-client-secret - -FACEBOOK_CLIENT_ID=facebook-client-id -FACEBOOK_CLIENT_SECRET=facbook-client-secret diff --git a/server/deploy/Dockerfile.dev b/server/Dockerfile.dev similarity index 100% rename from server/deploy/Dockerfile.dev rename to server/Dockerfile.dev diff --git a/server/deploy/Dockerfile.prod b/server/Dockerfile.prod similarity index 100% rename from server/deploy/Dockerfile.prod rename to server/Dockerfile.prod diff --git a/server/deploy/pgpass.template b/server/deploy/pgpass.template deleted file mode 100644 index 788074315..000000000 --- a/server/deploy/pgpass.template +++ /dev/null @@ -1 +0,0 @@ -db:5432:*:marsmadness:DB_PASSWORD \ No newline at end of file diff --git a/server/src/datasource.ts b/server/src/datasource.ts index aa5761194..eed10a8c1 100644 --- a/server/src/datasource.ts +++ b/server/src/datasource.ts @@ -1,14 +1,12 @@ import { DataSourceOptions, DataSource } from "typeorm"; -import * as dotenv from "dotenv"; - -dotenv.config(); +import * as fs from "fs"; const postgresConnectionOptions: DataSourceOptions = { type: "postgres", host: "db", port: 5432, username: "marsmadness", - password: process.env.DB_PASSWORD, + password: fs.readFileSync("/run/secrets/pom_db_password", "utf8").trim(), synchronize: false, logging: false, entities: ["src/entity/**/*.{js,ts}"], diff --git a/server/src/index.ts b/server/src/index.ts index 9f2c22151..6dd5f387b 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -141,9 +141,9 @@ async function createApp() { })(); logger.info( - "starting (%s) server: [build id: %s, settings.host %s]", + "starting (%s) server: [release version: %s, settings.host %s]", NODE_ENV, - Constants.BUILD_ID, + Constants.RELEASE_VERSION, settings.host ); const port = Number(process.env.PORT || 2567); @@ -258,6 +258,7 @@ pRetry( logger.warn( `Connection to db failed on attempt number ${error.attemptNumber}, retrying ${error.retriesLeft} more times...` ); + logger.warn(`cause: ${error.message}`); }, retries: 10, minTimeout: 1 * 1000, diff --git a/server/src/services/persistence.ts b/server/src/services/persistence.ts index f38b0ec49..0b42cea13 100644 --- a/server/src/services/persistence.ts +++ b/server/src/services/persistence.ts @@ -8,7 +8,7 @@ import * as ge from "@port-of-mars/server/rooms/game/events/types"; import { GameOpts, Metadata, Persister } from "@port-of-mars/server/rooms/game/types"; import { getServices, ServiceProvider } from "@port-of-mars/server/services/index"; import { getLogger } from "@port-of-mars/server/settings"; -import { BUILD_ID } from "@port-of-mars/shared/settings"; +import { RELEASE_VERSION } from "@port-of-mars/shared/settings"; const logger = getLogger(__filename); @@ -98,7 +98,7 @@ export class DBPersister implements Persister { if (_.isNull(options.tournamentRoundId)) { throw new Error("could not find matching tournament round"); } - game.buildId = BUILD_ID; + game.buildId = RELEASE_VERSION; game.tournamentRoundId = options.tournamentRoundId; game.roomId = roomId; game.type = options.type; diff --git a/server/src/settings.ts b/server/src/settings.ts index 80ad49d18..f18babb2a 100644 --- a/server/src/settings.ts +++ b/server/src/settings.ts @@ -4,10 +4,12 @@ import { MailgunEmailer, } from "@port-of-mars/server/services/email/emailers"; import { LogService, DevLogging, Logging } from "@port-of-mars/server/services/logging"; +import { BASE_URL, SERVER_URL_HTTP } from "@port-of-mars/shared/settings"; import * as fs from "fs"; -import * as dotenv from "dotenv"; -dotenv.config(); +const readSecret = (filename: string): string => { + return fs.readFileSync(`/run/secrets/${filename}`, "utf8").trim(); +}; export interface AppSettings { emailer: Emailer; @@ -29,17 +31,17 @@ export interface AppSettings { const dev: () => AppSettings = () => ({ emailer: new MemoryEmailer(), - host: process.env.BASE_URL || "http://localhost:8081", - serverHost: "http://localhost:2567", + host: BASE_URL || "http://localhost:8081", + serverHost: SERVER_URL_HTTP || "http://localhost:2567", logging: new DevLogging(), - secret: process.env.SECRET_KEY || "", + secret: readSecret("secret_key"), googleAuth: { clientId: process.env.GOOGLE_CLIENT_ID || "", - clientSecret: process.env.GOOGLE_CLIENT_SECRET || "", + clientSecret: readSecret("google_client_secret"), }, facebookAuth: { clientId: process.env.FACEBOOK_CLIENT_ID || "", - clientSecret: process.env.FACEBOOK_CLIENT_SECRET || "", + clientSecret: readSecret("facebook_client_secret"), }, supportEmail: "portmars@asu.edu", isProduction: false, @@ -47,13 +49,13 @@ const dev: () => AppSettings = () => ({ const staging: () => AppSettings = () => { const devSettings = dev(); - const mailApiKey = process.env.MAIL_API_KEY || ""; + const mailApiKey = readSecret("mail_api_key"); const domain = "mg.comses.net"; return { ...devSettings, emailer: new MailgunEmailer({ api_key: mailApiKey, domain }), - host: process.env.BASE_URL || "https://staging.portofmars.asu.edu", - serverHost: process.env.BASE_URL || "https://staging.portofmars.asu.edu", + host: BASE_URL || "https://staging.portofmars.asu.edu", + serverHost: BASE_URL || "https://staging.portofmars.asu.edu", }; }; @@ -61,8 +63,8 @@ const prod: () => AppSettings = () => { const stagingSettings = staging(); return { ...stagingSettings, - host: process.env.BASE_URL || "https://portofmars.asu.edu", - serverHost: process.env.BASE_URL || "https://portofmars.asu.edu", + host: BASE_URL || "https://portofmars.asu.edu", + serverHost: BASE_URL || "https://portofmars.asu.edu", isProduction: true, }; }; diff --git a/shared/src/settings.ts b/shared/src/settings.ts index ebbfc6777..2c3543d7c 100644 --- a/shared/src/settings.ts +++ b/shared/src/settings.ts @@ -1,7 +1,20 @@ -import { BUILD_ID, SENTRY_DSN, GA_TAG } from "./assets/config"; -export { BUILD_ID, SENTRY_DSN, GA_TAG } from "./assets/config"; +type Environment = "development" | "staging" | "production" | "test"; -export const ENVIRONMENT = process.env.NODE_ENV || "development"; +export const ENVIRONMENT = (process.env.NODE_ENV || "development") as Environment; +export const RELEASE_VERSION = process.env.RELEASE_VERSION || "unknown"; +export const SENTRY_DSN = process.env.SENTRY_DSN || ""; +export const GA_TAG = process.env.GA_TAG || ""; + +const baseUrlMap = { + development: "http://localhost:8081", + staging: "https://staging.portofmars.com", + production: "https://portofmars.com", + test: "http://localhost:8081", +}; + +export const BASE_URL = baseUrlMap[ENVIRONMENT]; +export const SERVER_URL_WS = isDev() ? "ws://localhost:2567" : ""; +export const SERVER_URL_HTTP = isDev() ? "http://localhost:2567" : ""; export function isDev(): boolean { return ENVIRONMENT === "development"; @@ -28,7 +41,8 @@ export function isStagingOrProduction(): boolean { } export class Constants { - // FIXME: we could support reading values from a settings file as well/instead? + // FIXME: this is rather cumbersome to use, rather just export consts directly + // also consider putting some of these in .env config public static readonly TRAILER_VIDEO_URL = "https://www.youtube.com/embed/CiB4q3CnyCY"; public static readonly TUTORIAL_VIDEO_URL = "https://www.youtube.com/embed/D4FfofyrlkA"; public static readonly DISCORD_URL = "https://discord.gg/AFEtAJZfEM"; @@ -37,7 +51,7 @@ export class Constants { public static readonly GITHUB_URL = "https://github.com/virtualcommons/port-of-mars"; public static readonly CONTACT_EMAIL = "portmars@asu.edu"; public static readonly ENVIRONMENT = ENVIRONMENT; - public static readonly BUILD_ID = BUILD_ID; + public static readonly RELEASE_VERSION = RELEASE_VERSION; public static readonly SENTRY_DSN = SENTRY_DSN; public static readonly GA_TAG = GA_TAG; public static readonly GIFT_CARD_AMOUNT = 10; diff --git a/staging.yml b/staging.yml index e0c019dd2..0571b50c7 100644 --- a/staging.yml +++ b/staging.yml @@ -1,7 +1,7 @@ services: server: build: - dockerfile: server/deploy/Dockerfile.prod + dockerfile: server/Dockerfile.prod args: NODE_ARG: staging image: port-of-mars/server:staging From 490210b62dfc5f773cb35c0155ca44a4341f1873 Mon Sep 17 00:00:00 2001 From: Allen Lee Date: Sat, 8 Jun 2024 14:38:17 -0700 Subject: [PATCH 7/9] fix: urls --- shared/src/settings.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shared/src/settings.ts b/shared/src/settings.ts index 2c3543d7c..73d5ae83e 100644 --- a/shared/src/settings.ts +++ b/shared/src/settings.ts @@ -7,8 +7,8 @@ export const GA_TAG = process.env.GA_TAG || ""; const baseUrlMap = { development: "http://localhost:8081", - staging: "https://staging.portofmars.com", - production: "https://portofmars.com", + staging: "https://staging.portofmars.asu.edu", + production: "https://portofmars.asu.edu", test: "http://localhost:8081", }; From 5123a0933a4344cb58a4ce6b8e0ab3cc6226de4a Mon Sep 17 00:00:00 2001 From: sgfost Date: Mon, 10 Jun 2024 16:12:33 -0700 Subject: [PATCH 8/9] fix: shared settings rework - use vite import.meta.env for things on the client that come from .env * would've been nice to just integrate this into shared/settings such that when called from the client, import.meta.env would be used instead of process.env but this is extremely difficult to do - Constants -> settings - add $settings vue global for accessing shared settings easier --- Makefile | 2 +- client/src/components/game/Inventory.vue | 3 +- .../game/phases/investment/InvestmentCard.vue | 3 +- client/src/components/global/Footer.vue | 15 +++---- client/src/components/global/Navbar.vue | 11 ++--- .../global/TournamentOnboardingSteps.vue | 8 +--- client/src/components/lobby/HelpPanel.vue | 7 +-- client/src/components/lobby/LobbyRoomList.vue | 5 --- client/src/main.ts | 13 ++++-- client/src/plugins/settings.ts | 31 +++++++++++++ client/src/views/FreePlayLobby.vue | 5 --- client/src/views/Home.vue | 11 ++--- client/src/views/Privacy.vue | 9 +--- client/src/views/TournamentDashboard.vue | 7 +-- client/src/views/TournamentLobby.vue | 5 --- client/tsconfig.json | 2 +- client/vite.config.ts | 8 ++-- deploy/conf/.env.template | 8 ++-- server/src/index.ts | 8 ++-- .../src/rooms/game/state/marsevents/state.ts | 6 +-- server/src/rooms/game/state/resource.ts | 22 +++++----- .../src/rooms/game/state/roundintroduction.ts | 4 +- server/src/services/persistence.ts | 4 +- shared/src/game/client/state.ts | 4 +- shared/src/settings.ts | 44 +++++++++---------- 25 files changed, 117 insertions(+), 128 deletions(-) create mode 100644 client/src/plugins/settings.ts diff --git a/Makefile b/Makefile index d719e0ecd..4ab1471b4 100644 --- a/Makefile +++ b/Makefile @@ -64,7 +64,7 @@ secrets: keys $(GENERATED_SECRETS) .PHONY: release-version release-version: .env - $(ENVREPLACE) RELEASE_VERSION $$(git describe --tags --abbrev=1) .env + $(ENVREPLACE) SHARED_RELEASE_VERSION $$(git describe --tags --abbrev=1) .env docker-compose.yml: base.yml $(DEPLOY_ENVIRONMENT).yml config.mk $(DB_DATA_PATH) $(DATA_DUMP_PATH) $(LOG_DATA_PATH) $(DYNAMIC_SETTINGS_PATH) secrets $(PGPASS_PATH) release-version case "$(DEPLOY_ENVIRONMENT)" in \ diff --git a/client/src/components/game/Inventory.vue b/client/src/components/game/Inventory.vue index 7fe1eec8f..d7668e244 100644 --- a/client/src/components/game/Inventory.vue +++ b/client/src/components/game/Inventory.vue @@ -46,7 +46,6 @@ import { Vue, Component, Prop } from "vue-property-decorator"; import { Role, RESEARCHER } from "@port-of-mars/shared/types"; import { Investment, Resource, RESOURCES, Phase } from "@port-of-mars/shared/types"; -import { Constants } from "@port-of-mars/shared/settings"; @Component({ components: {}, @@ -96,7 +95,7 @@ export default class Inventory extends Vue { } canInvest(cost: number): boolean { - return cost < Constants.MAXIMUM_COST; + return cost < this.$settings.MAXIMUM_COST; } toggleCosts() { diff --git a/client/src/components/game/phases/investment/InvestmentCard.vue b/client/src/components/game/phases/investment/InvestmentCard.vue index 7e41cacaf..e358a5d4b 100644 --- a/client/src/components/game/phases/investment/InvestmentCard.vue +++ b/client/src/components/game/phases/investment/InvestmentCard.vue @@ -56,7 +56,6 @@ diff --git a/client/src/components/lobby/LobbyRoomList.vue b/client/src/components/lobby/LobbyRoomList.vue index 5b898e5b3..c8b70a4b2 100644 --- a/client/src/components/lobby/LobbyRoomList.vue +++ b/client/src/components/lobby/LobbyRoomList.vue @@ -101,7 +101,6 @@ import { Component, Inject, Vue } from "vue-property-decorator"; import { Client } from "colyseus.js"; import { FREE_PLAY_LOBBY_NAME } from "@port-of-mars/shared/lobby"; -import { Constants } from "@port-of-mars/shared/settings"; import MuteBanWarning from "@port-of-mars/client/components/lobby/MuteBanWarning.vue"; @Component({ @@ -123,10 +122,6 @@ export default class LobbyRoomList extends Vue { refreshingRoomList = false; pollingIntervalId = 0; - get constants() { - return Constants; - } - get isBanned() { return this.$store.state.user.isBanned; } diff --git a/client/src/main.ts b/client/src/main.ts index db36426ed..c4fca3ce2 100644 --- a/client/src/main.ts +++ b/client/src/main.ts @@ -7,7 +7,7 @@ import Vuex from "vuex"; import * as Sentry from "@sentry/browser"; import { Vue as VueIntegration } from "@sentry/integrations"; import { Integrations } from "@sentry/tracing"; -import { isStagingOrProduction, Constants, SERVER_URL_WS } from "@port-of-mars/shared/settings"; +import { settings, isStagingOrProduction, SERVER_URL_WS } from "@port-of-mars/shared/settings"; import { Ajax } from "@port-of-mars/client/plugins/ajax"; import { TypedStore } from "@port-of-mars/client/plugins/tstore"; import { getAssetUrl, SfxManager } from "@port-of-mars/client/util"; @@ -25,7 +25,7 @@ Vue.config.productionTip = false; if (isStagingOrProduction()) { Sentry.init({ - dsn: Constants.SENTRY_DSN, + dsn: import.meta.env.SHARED_SENTRY_DSN, integrations: [new VueIntegration({ Vue, tracing: true }), new Integrations.BrowserTracing()], tracesSampleRate: 1, }); @@ -33,7 +33,7 @@ if (isStagingOrProduction()) { VueGtag, { config: { - id: Constants.GA_TAG, + id: import.meta.env.SHARED_GA_TAG, }, }, router @@ -44,6 +44,13 @@ const $client = new Colyseus.Client(SERVER_URL_WS || undefined); const $sfx = new SfxManager(); Vue.prototype.$getAssetUrl = getAssetUrl; +Vue.prototype.$settings = settings; +declare module "vue/types/vue" { + interface Vue { + $settings: typeof settings; + $getAssetUrl: typeof getAssetUrl; + } +} new Vue({ router, diff --git a/client/src/plugins/settings.ts b/client/src/plugins/settings.ts new file mode 100644 index 000000000..63d1a27fd --- /dev/null +++ b/client/src/plugins/settings.ts @@ -0,0 +1,31 @@ +import { State } from "@port-of-mars/shared/game/client/state"; +import Getters from "@port-of-mars/client/store/getters"; +import Mutations from "@port-of-mars/client/store/mutations"; +import { VueConstructor } from "vue"; +import { Store } from "vuex"; + +export interface TStore { + state: State; + readonly getters: { [K in keyof typeof Getters]: ReturnType<(typeof Getters)[K]> }; + + commit( + name: K, + payload?: Parameters<(typeof Mutations)[K]>[1] + ): void; +} + +declare module "vue/types/vue" { + interface Vue { + $tstore: TStore; + } +} + +export const TypedStore = { + install(instance: VueConstructor) { + Object.defineProperty(instance.prototype, "$tstore", { + get: function (this: { $store: Store }) { + return this.$store; + }, + }); + }, +}; diff --git a/client/src/views/FreePlayLobby.vue b/client/src/views/FreePlayLobby.vue index eb00f2708..6957026f4 100644 --- a/client/src/views/FreePlayLobby.vue +++ b/client/src/views/FreePlayLobby.vue @@ -26,7 +26,6 @@ import { FreePlayLobbyRequestAPI } from "@port-of-mars/client/api/lobby/request" import { AccountAPI } from "@port-of-mars/client/api/account/request"; import { FREE_PLAY_LOBBY_NAME } from "@port-of-mars/shared/lobby"; import { GAME_PAGE, CONSENT_PAGE, MANUAL_PAGE } from "@port-of-mars/shared/routes"; -import { Constants } from "@port-of-mars/shared/settings"; import Countdown from "@port-of-mars/client/components/global/Countdown.vue"; import HelpPanel from "@port-of-mars/client/components/lobby/HelpPanel.vue"; import Messages from "@port-of-mars/client/components/global/Messages.vue"; @@ -47,10 +46,6 @@ export default class FreePlayLobby extends Vue { manual = { name: MANUAL_PAGE }; consent = { name: CONSENT_PAGE }; - get constants() { - return Constants; - } - async created() { this.accountApi = new AccountAPI(this.$store, this.$ajax); await this.checkCanPlay(); diff --git a/client/src/views/Home.vue b/client/src/views/Home.vue index 996a7c482..a2697a80d 100644 --- a/client/src/views/Home.vue +++ b/client/src/views/Home.vue @@ -78,7 +78,7 @@ class="p-1" type="iframe" aspect="16by9" - :src="constants.TUTORIAL_VIDEO_URL" + :src="$settings.TUTORIAL_VIDEO_URL" allowfullscreen > @@ -88,7 +88,7 @@ class="p-1" type="iframe" aspect="16by9" - :src="constants.TRAILER_VIDEO_URL" + :src="$settings.TRAILER_VIDEO_URL" allowfullscreen > @@ -117,7 +117,7 @@

Community

Discuss the game, find a game to play, or connect with other players in our - community Discord. + community Discord.

Keep track of your performance with your @@ -155,7 +155,6 @@ import { TOURNAMENT_DASHBOARD_PAGE, MANUAL_PAGE, } from "@port-of-mars/shared/routes"; -import { Constants } from "@port-of-mars/shared/settings"; import Footer from "@port-of-mars/client/components/global/Footer.vue"; import CharCarousel from "@port-of-mars/client/components/global/CharCarousel.vue"; import AgeTooltip from "@port-of-mars/client/components/global/AgeTooltip.vue"; @@ -185,10 +184,6 @@ export default class Home extends Vue { solo = { name: SOLO_GAME_PAGE }; manual = { name: MANUAL_PAGE }; - get constants() { - return Constants; - } - get shouldShowTournamentBanner() { return this.$tstore.state.isTournamentEnabled && this.$tstore.getters.tournamentStatus; } diff --git a/client/src/views/Privacy.vue b/client/src/views/Privacy.vue index 186b5b6fb..824f5d25b 100644 --- a/client/src/views/Privacy.vue +++ b/client/src/views/Privacy.vue @@ -78,7 +78,7 @@

If you have any questions or concerns about our Privacy Policy or the collection, use, or sharing of your personal information, please contact us at - {{ constants.CONTACT_EMAIL }}{{ $settings.CONTACT_EMAIL }}.

@@ -88,7 +88,6 @@