From d689bb5e5a9579488f53c03e11a7ae263267241e Mon Sep 17 00:00:00 2001 From: Steve Reis <stevereis93@gmail.com> Date: Thu, 2 Jun 2022 07:10:57 +0000 Subject: [PATCH] feat: Possibility to save experiments locally --- api/ormconfig.ts | 4 +- api/package-lock.json | 974 ++++++++---------- api/package.json | 11 +- api/src/auth/auth.module.ts | 2 + api/src/auth/auth.resolver.spec.ts | 4 +- api/src/auth/auth.resolver.ts | 12 +- api/src/auth/auth.service.ts | 2 +- api/src/auth/decorators/user.decorator.ts | 14 - ...jwt-auth.guard.ts => global-auth.guard.ts} | 14 +- api/src/auth/strategies/engine.strategy.ts | 22 + api/src/common/decorators/user.decorator.ts | 2 +- .../engine/connectors/csv/main.connector.ts | 66 +- .../connectors/datashield/main.connector.ts | 45 +- .../engine/connectors/exareme/converters.ts | 13 +- .../exareme/interfaces/test-utilities.ts | 4 +- .../connectors/exareme/main.connector.ts | 39 +- .../exareme/tests/e2e/3c.e2e-spec.ts | 2 +- .../tests/e2e/calibration-belt.e2e-spec.ts | 2 +- .../exareme/tests/e2e/cart.e2e-spec.ts | 2 +- .../e2e/descriptiveStatistics.e2e-spec.ts | 2 +- .../exareme/tests/e2e/id3.e2e-spec.ts | 2 +- .../exareme/tests/e2e/k-means.e2e-spec.ts | 2 +- .../tests/e2e/kaplan-meier.e2e-spec.ts | 2 +- .../tests/e2e/linear-regression.e2e-spec.ts | 2 +- .../tests/e2e/logistic-regression.e2e-spec.ts | 2 +- .../tests/e2e/multiple-histograms.e2e-spec.ts | 2 +- .../exareme/tests/e2e/naive-bayes.e2e-spec.ts | 2 +- .../tests/e2e/one-way-anova.e2e-spec.ts | 2 +- .../exareme/tests/e2e/pca.e2e-spec.ts | 2 +- .../tests/e2e/pearson-correlation.e2e-spec.ts | 2 +- .../tests/e2e/t-test-independant.e2e-spec.ts | 2 +- .../tests/e2e/t-test-one-sample.e2e-spec.ts | 2 +- .../tests/e2e/t-test-paired.e2e-spec.ts | 2 +- .../tests/e2e/two-way-anova.e2e-spec.ts | 2 +- .../connectors/exareme/transformations.ts | 12 +- .../engine/connectors/local/main.connector.ts | 39 +- api/src/engine/engine.interfaces.ts | 80 +- api/src/engine/engine.module.ts | 36 +- api/src/engine/engine.resolver.ts | 70 +- .../models/experiment/experiment.model.ts | 53 +- .../experiment/list-experiments.model.ts | 2 +- .../experiments/dto/experiment-update.dto.ts | 3 + api/src/experiments/experiments.module.ts | 11 + .../experiments/experiments.resolver.spec.ts | 248 +++++ api/src/experiments/experiments.resolver.ts | 137 +++ .../experiments/experiments.service.spec.ts | 184 ++++ api/src/experiments/experiments.service.ts | 145 +++ .../input/algorithm-parameter.input.ts | 0 .../models}/input/algorithm.input.ts | 0 .../models}/input/experiment-create.input.ts | 0 .../models}/input/experiment-edit.input.ts | 0 .../models/input/pagination-args.input.ts | 10 + api/src/main/app.module.ts | 2 + .../1653484967792-CreateExperimentDB.ts | 19 + .../1653487335545-ExperimentDateTypeUpdate.ts | 29 + api/src/schema.gql | 54 +- api/src/users/users.resolver.ts | 8 +- api/src/users/users.service.ts | 2 +- 58 files changed, 1509 insertions(+), 899 deletions(-) delete mode 100644 api/src/auth/decorators/user.decorator.ts rename api/src/auth/guards/{jwt-auth.guard.ts => global-auth.guard.ts} (73%) create mode 100644 api/src/auth/strategies/engine.strategy.ts create mode 100644 api/src/experiments/dto/experiment-update.dto.ts create mode 100644 api/src/experiments/experiments.module.ts create mode 100644 api/src/experiments/experiments.resolver.spec.ts create mode 100644 api/src/experiments/experiments.resolver.ts create mode 100644 api/src/experiments/experiments.service.spec.ts create mode 100644 api/src/experiments/experiments.service.ts rename api/src/{engine/models/experiment => experiments/models}/input/algorithm-parameter.input.ts (100%) rename api/src/{engine/models/experiment => experiments/models}/input/algorithm.input.ts (100%) rename api/src/{engine/models/experiment => experiments/models}/input/experiment-create.input.ts (100%) rename api/src/{engine/models/experiment => experiments/models}/input/experiment-edit.input.ts (100%) create mode 100644 api/src/experiments/models/input/pagination-args.input.ts create mode 100644 api/src/migrations/1653484967792-CreateExperimentDB.ts create mode 100644 api/src/migrations/1653487335545-ExperimentDateTypeUpdate.ts diff --git a/api/ormconfig.ts b/api/ormconfig.ts index 27afa05..c4d8d9f 100644 --- a/api/ormconfig.ts +++ b/api/ormconfig.ts @@ -7,7 +7,7 @@ ConfigModule.forRoot({ load: [dbConfiguration], }); -const config = { +const ormconfig = { ...dbConfiguration(), entities: ['dist/**/*.entity.js', 'dist/**/*.model.js'], migrations: ['dist/migrations/*{.ts,.js}'], @@ -17,4 +17,4 @@ const config = { }, }; -export default config; +export default ormconfig; diff --git a/api/package-lock.json b/api/package-lock.json index a7e5df0..39f229d 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -26,6 +26,7 @@ "graphql-type-json": "^0.3.2", "jsonata": "^1.8.5", "passport": "^0.5.2", + "passport-custom": "^1.1.1", "passport-jwt": "^4.0.0", "passport-local": "^1.0.0", "pg": "^8.7.3", @@ -145,16 +146,16 @@ } }, "node_modules/@angular-devkit/schematics-cli": { - "version": "13.2.5", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics-cli/-/schematics-cli-13.2.5.tgz", - "integrity": "sha512-/3Q1+wtE+l5XXoXX/7157yh4Wpi+FNEryx5gDcfPJchgtovxj28nzquD0vXnvpyr3Wd8OaMwg6vW4EfL82jRKg==", + "version": "13.3.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics-cli/-/schematics-cli-13.3.5.tgz", + "integrity": "sha512-ARX20ebtfwzef8GdXIcB6uv0sjTsaEniZyXBFchEKD6kR5EYZVaBL+ZVUbmsU1d0XY///WzW7pqwCyu5H1u+vw==", "dev": true, "dependencies": { - "@angular-devkit/core": "13.2.5", - "@angular-devkit/schematics": "13.2.5", + "@angular-devkit/core": "13.3.5", + "@angular-devkit/schematics": "13.3.5", "ansi-colors": "4.1.1", "inquirer": "8.2.0", - "minimist": "1.2.5", + "minimist": "1.2.6", "symbol-observable": "4.0.0" }, "bin": { @@ -166,6 +167,75 @@ "yarn": ">= 1.13.0" } }, + "node_modules/@angular-devkit/schematics-cli/node_modules/@angular-devkit/core": { + "version": "13.3.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.5.tgz", + "integrity": "sha512-w7vzK4VoYP9rLgxJ2SwEfrkpKybdD+QgQZlsDBzT0C6Ebp7b4gkNcNVFo8EiZvfDl6Yplw2IAP7g7fs3STn0hQ==", + "dev": true, + "dependencies": { + "ajv": "8.9.0", + "ajv-formats": "2.1.1", + "fast-json-stable-stringify": "2.1.0", + "magic-string": "0.25.7", + "rxjs": "6.6.7", + "source-map": "0.7.3" + }, + "engines": { + "node": "^12.20.0 || ^14.15.0 || >=16.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^3.5.2" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/@angular-devkit/schematics-cli/node_modules/@angular-devkit/core/node_modules/rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "dependencies": { + "tslib": "^1.9.0" + }, + "engines": { + "npm": ">=2.0.0" + } + }, + "node_modules/@angular-devkit/schematics-cli/node_modules/@angular-devkit/schematics": { + "version": "13.3.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-13.3.5.tgz", + "integrity": "sha512-0N/kL/Vfx0yVAEwa3HYxNx9wYb+G9r1JrLjJQQzDp+z9LtcojNf7j3oey6NXrDUs1WjVZOa/AIdRl3/DuaoG5w==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "13.3.5", + "jsonc-parser": "3.0.0", + "magic-string": "0.25.7", + "ora": "5.4.1", + "rxjs": "6.6.7" + }, + "engines": { + "node": "^12.20.0 || ^14.15.0 || >=16.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular-devkit/schematics-cli/node_modules/@angular-devkit/schematics/node_modules/rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "dependencies": { + "tslib": "^1.9.0" + }, + "engines": { + "npm": ">=2.0.0" + } + }, "node_modules/@angular-devkit/schematics-cli/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -207,6 +277,12 @@ "node": ">=8.0.0" } }, + "node_modules/@angular-devkit/schematics-cli/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, "node_modules/@angular-devkit/schematics/node_modules/rxjs": { "version": "6.6.7", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", @@ -883,6 +959,16 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, "node_modules/@cspotcode/source-map-consumer": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", @@ -1514,20 +1600,20 @@ } }, "node_modules/@nestjs/cli": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-8.2.2.tgz", - "integrity": "sha512-ZonmNLCHfTVrZGgYf4mrpivnKGaRzVRAcux+WDbzhQDNIz70s7mdOPShXW1Vpq+7uRJDxlgO1vOMhmg4uEUIDg==", + "version": "8.2.6", + "resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-8.2.6.tgz", + "integrity": "sha512-uvwKbUZJmgdJu1D24e+uUqHnwoB/0R9hLfUJjr5pTvLlP/RJugHAdJr7m1dQe92Xzdyi36kBN4Id3RXHgfz1UA==", "dev": true, "dependencies": { - "@angular-devkit/core": "13.2.5", - "@angular-devkit/schematics": "13.2.5", - "@angular-devkit/schematics-cli": "13.2.5", + "@angular-devkit/core": "13.3.5", + "@angular-devkit/schematics": "13.3.5", + "@angular-devkit/schematics-cli": "13.3.5", "@nestjs/schematics": "^8.0.3", "chalk": "3.0.0", "chokidar": "3.5.3", - "cli-table3": "0.6.1", + "cli-table3": "0.6.2", "commander": "4.1.1", - "fork-ts-checker-webpack-plugin": "7.2.1", + "fork-ts-checker-webpack-plugin": "7.2.11", "inquirer": "7.3.3", "node-emoji": "1.11.0", "ora": "5.4.1", @@ -1536,10 +1622,10 @@ "shelljs": "0.8.5", "source-map-support": "0.5.21", "tree-kill": "1.2.2", - "tsconfig-paths": "3.12.0", + "tsconfig-paths": "3.14.1", "tsconfig-paths-webpack-plugin": "3.5.2", - "typescript": "4.6.2", - "webpack": "5.66.0", + "typescript": "4.6.4", + "webpack": "5.72.1", "webpack-node-externals": "3.0.0" }, "bin": { @@ -1550,162 +1636,69 @@ "npm": ">= 6.11.0" } }, - "node_modules/@nestjs/cli/node_modules/@types/estree": { - "version": "0.0.50", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.50.tgz", - "integrity": "sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw==", - "dev": true - }, - "node_modules/@nestjs/cli/node_modules/acorn": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", - "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", + "node_modules/@nestjs/cli/node_modules/@angular-devkit/core": { + "version": "13.3.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.5.tgz", + "integrity": "sha512-w7vzK4VoYP9rLgxJ2SwEfrkpKybdD+QgQZlsDBzT0C6Ebp7b4gkNcNVFo8EiZvfDl6Yplw2IAP7g7fs3STn0hQ==", "dev": true, - "bin": { - "acorn": "bin/acorn" + "dependencies": { + "ajv": "8.9.0", + "ajv-formats": "2.1.1", + "fast-json-stable-stringify": "2.1.0", + "magic-string": "0.25.7", + "rxjs": "6.6.7", + "source-map": "0.7.3" }, "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/@nestjs/cli/node_modules/acorn-import-assertions": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", - "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", - "dev": true, - "peerDependencies": { - "acorn": "^8" - } - }, - "node_modules/@nestjs/cli/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "node": "^12.20.0 || ^14.15.0 || >=16.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/@nestjs/cli/node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, "peerDependencies": { - "ajv": "^6.9.1" - } - }, - "node_modules/@nestjs/cli/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/@nestjs/cli/node_modules/json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "dependencies": { - "minimist": "^1.2.0" + "chokidar": "^3.5.2" }, - "bin": { - "json5": "lib/cli.js" + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } } }, - "node_modules/@nestjs/cli/node_modules/schema-utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", - "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "node_modules/@nestjs/cli/node_modules/@angular-devkit/schematics": { + "version": "13.3.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-13.3.5.tgz", + "integrity": "sha512-0N/kL/Vfx0yVAEwa3HYxNx9wYb+G9r1JrLjJQQzDp+z9LtcojNf7j3oey6NXrDUs1WjVZOa/AIdRl3/DuaoG5w==", "dev": true, "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" + "@angular-devkit/core": "13.3.5", + "jsonc-parser": "3.0.0", + "magic-string": "0.25.7", + "ora": "5.4.1", + "rxjs": "6.6.7" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/@nestjs/cli/node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true, "engines": { - "node": ">=4" - } - }, - "node_modules/@nestjs/cli/node_modules/tsconfig-paths": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.12.0.tgz", - "integrity": "sha512-e5adrnOYT6zqVnWqZu7i/BQ3BnhzvGbjEjejFXO20lKIKpwTaupkCPgEfv4GZK1IBciJUEhYs3J3p75FdaTFVg==", - "dev": true, - "dependencies": { - "@types/json5": "^0.0.29", - "json5": "^1.0.1", - "minimist": "^1.2.0", - "strip-bom": "^3.0.0" + "node": "^12.20.0 || ^14.15.0 || >=16.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" } }, - "node_modules/@nestjs/cli/node_modules/webpack": { - "version": "5.66.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.66.0.tgz", - "integrity": "sha512-NJNtGT7IKpGzdW7Iwpn/09OXz9inIkeIQ/ibY6B+MdV1x6+uReqz/5z1L89ezWnpPDWpXF0TY5PCYKQdWVn8Vg==", + "node_modules/@nestjs/cli/node_modules/rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", "dev": true, "dependencies": { - "@types/eslint-scope": "^3.7.0", - "@types/estree": "^0.0.50", - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/wasm-edit": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "acorn": "^8.4.1", - "acorn-import-assertions": "^1.7.6", - "browserslist": "^4.14.5", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.8.3", - "es-module-lexer": "^0.9.0", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.9", - "json-parse-better-errors": "^1.0.2", - "loader-runner": "^4.2.0", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^3.1.0", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.1.3", - "watchpack": "^2.3.1", - "webpack-sources": "^3.2.2" - }, - "bin": { - "webpack": "bin/webpack.js" + "tslib": "^1.9.0" }, "engines": { - "node": ">=10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependenciesMeta": { - "webpack-cli": { - "optional": true - } + "npm": ">=2.0.0" } }, + "node_modules/@nestjs/cli/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, "node_modules/@nestjs/common": { "version": "8.4.0", "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-8.4.0.tgz", @@ -3335,18 +3328,6 @@ } } }, - "node_modules/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.3" - }, - "peerDependencies": { - "ajv": "^8.8.2" - } - }, "node_modules/ansi-colors": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", @@ -4340,9 +4321,9 @@ } }, "node_modules/cli-table3": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.1.tgz", - "integrity": "sha512-w0q/enDHhPLq44ovMGdQeeDLvwxwavsJX7oQGYt/LrBlYsyaxyDnp6z3QzFut/6kLLKnlcUVJLrpB7KBfgG/RA==", + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.2.tgz", + "integrity": "sha512-QyavHCaIC80cMivimWu4aWHilIpiDpfm3hGmqAmXVL1UsnbLuBSMd21hTX6VY4ZSDSM73ESLeF8TOYId3rBTbw==", "dev": true, "dependencies": { "string-width": "^4.2.0" @@ -4351,7 +4332,7 @@ "node": "10.* || >= 12.*" }, "optionalDependencies": { - "colors": "1.4.0" + "@colors/colors": "1.5.0" } }, "node_modules/cli-width": { @@ -4423,16 +4404,6 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, - "node_modules/colors": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", - "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.1.90" - } - }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -5272,9 +5243,9 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.9.2", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.9.2.tgz", - "integrity": "sha512-GIm3fQfwLJ8YZx2smuHpBKkXC1yOk+OBEmKckVyL0i/ea8mqDEykK3ld5dgH1QYPNyT/lIllxV2LULnxCHaHkA==", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.9.3.tgz", + "integrity": "sha512-Bq9VSor+kjvW3f9/MiiR4eE3XYgOl7/rS8lnSxbRbF3kS0B2r+Y9w5krBWxZgDxASVZbdYrn5wT4j/Wb0J9qow==", "dev": true, "dependencies": { "graceful-fs": "^4.2.4", @@ -6105,9 +6076,9 @@ } }, "node_modules/fork-ts-checker-webpack-plugin": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-7.2.1.tgz", - "integrity": "sha512-uOfQdg/iQ8iokQ64qcbu8iZb114rOmaKLQFu7hU14/eJaKgsP91cQ7ts7v2iiDld6TzDe84Meksha8/MkWiCyw==", + "version": "7.2.11", + "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-7.2.11.tgz", + "integrity": "sha512-2e5+NyTUTE1Xq4fWo7KFEQblCaIvvINQwUX3jRmEGlgCTc1Ecqw/975EfQrQ0GEraxJTnp8KB9d/c8hlCHUMJA==", "dev": true, "dependencies": { "@babel/code-frame": "^7.16.7", @@ -6118,7 +6089,7 @@ "fs-extra": "^10.0.0", "memfs": "^3.4.1", "minimatch": "^3.0.4", - "schema-utils": "4.0.0", + "schema-utils": "^3.1.1", "semver": "^7.3.5", "tapable": "^2.2.1" }, @@ -8924,9 +8895,9 @@ } }, "node_modules/memfs": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.4.1.tgz", - "integrity": "sha512-1c9VPVvW5P7I85c35zAdEr1TD5+F11IToIHIlrVIcflfnzPkJa0ZoYEoEdYDP8KgPFoSZ/opDrUsAoZWym3mtw==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.4.3.tgz", + "integrity": "sha512-eivjfi7Ahr6eQTn44nvTnR60e4a1Fs1Via2kCR5lHo/kyNoiMWaXCNJ/GpSd0ilXas2JSOl9B5FTIhflXu0hlg==", "dev": true, "dependencies": { "fs-monkey": "1.0.3" @@ -9081,9 +9052,9 @@ } }, "node_modules/minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" }, "node_modules/minimist-options": { "version": "4.1.0", @@ -12096,6 +12067,17 @@ "url": "https://github.com/sponsors/jaredhanson" } }, + "node_modules/passport-custom": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/passport-custom/-/passport-custom-1.1.1.tgz", + "integrity": "sha512-/2m7jUGxmCYvoqenLB9UrmkCgPt64h8ZtV+UtuQklZ/Tn1NpKBeOorCYkB/8lMRoiZ5hUrCoMmDtxCS/d38mlg==", + "dependencies": { + "passport-strategy": "1.x.x" + }, + "engines": { + "node": ">= 0.10.0" + } + }, "node_modules/passport-jwt": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/passport-jwt/-/passport-jwt-4.0.0.tgz", @@ -13066,27 +13048,57 @@ } }, "node_modules/schema-utils": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", - "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", "dev": true, "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.8.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.0.0" + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" }, "engines": { - "node": ">= 12.13.0" + "node": ">= 10.13.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" } }, - "node_modules/semantic-release": { - "version": "19.0.2", - "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-19.0.2.tgz", + "node_modules/schema-utils/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/schema-utils/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/schema-utils/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/semantic-release": { + "version": "19.0.2", + "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-19.0.2.tgz", "integrity": "sha512-7tPonjZxukKECmClhsfyMKDt0GR38feIC2HxgyYaBi+9tDySBLjK/zYDLhh+m6yjnHIJa9eBTKYE7k63ZQcYbw==", "dev": true, "dependencies": { @@ -14092,55 +14104,6 @@ } } }, - "node_modules/terser-webpack-plugin/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/terser-webpack-plugin/node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, - "peerDependencies": { - "ajv": "^6.9.1" - } - }, - "node_modules/terser-webpack-plugin/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/terser-webpack-plugin/node_modules/schema-utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", - "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, "node_modules/terser-webpack-plugin/node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -14491,14 +14454,14 @@ } }, "node_modules/tsconfig-paths": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.13.0.tgz", - "integrity": "sha512-nWuffZppoaYK0vQ1SQmkSsQzJoHA4s6uzdb2waRpD806x9yfq153AdVsWz4je2qZcW+pENrMQXbGQ3sMCkXuhw==", + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", + "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", "dev": true, "dependencies": { "@types/json5": "^0.0.29", "json5": "^1.0.1", - "minimist": "^1.2.0", + "minimist": "^1.2.6", "strip-bom": "^3.0.0" } }, @@ -14834,9 +14797,9 @@ } }, "node_modules/typescript": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.2.tgz", - "integrity": "sha512-HM/hFigTBHZhLXshn9sN37H085+hQGeJHJ/X7LpBWLID/fbc2acUMfU+lGD98X81sKP+pFa9f0DZmCwB9GnbAg==", + "version": "4.6.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.4.tgz", + "integrity": "sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -15054,11 +15017,10 @@ } }, "node_modules/webpack": { - "version": "5.70.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.70.0.tgz", - "integrity": "sha512-ZMWWy8CeuTTjCxbeaQI21xSswseF2oNOwc70QSKNePvmxE7XW36i7vpBMYZFAUHPwQiEbNGCEYIOOlyRbdGmxw==", + "version": "5.72.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.72.1.tgz", + "integrity": "sha512-dXG5zXCLspQR4krZVR6QgajnZOjW2K/djHvdcRaDQvsjV9z9vaW6+ja5dZOYbqBBjF6kGXka/2ZyxNdc+8Jung==", "dev": true, - "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.3", "@types/estree": "^0.0.51", @@ -15069,13 +15031,13 @@ "acorn-import-assertions": "^1.7.6", "browserslist": "^4.14.5", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.9.2", + "enhanced-resolve": "^5.9.3", "es-module-lexer": "^0.9.0", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.2.9", - "json-parse-better-errors": "^1.0.2", + "json-parse-even-better-errors": "^2.3.1", "loader-runner": "^4.2.0", "mime-types": "^2.1.27", "neo-async": "^2.6.2", @@ -15124,7 +15086,6 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", "dev": true, - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -15137,64 +15098,10 @@ "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", "dev": true, - "peer": true, "peerDependencies": { "acorn": "^8" } }, - "node_modules/webpack/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "peer": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/webpack/node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, - "peer": true, - "peerDependencies": { - "ajv": "^6.9.1" - } - }, - "node_modules/webpack/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "peer": true - }, - "node_modules/webpack/node_modules/schema-utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", - "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", - "dev": true, - "peer": true, - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, "node_modules/whatwg-encoding": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", @@ -15572,19 +15479,68 @@ } }, "@angular-devkit/schematics-cli": { - "version": "13.2.5", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics-cli/-/schematics-cli-13.2.5.tgz", - "integrity": "sha512-/3Q1+wtE+l5XXoXX/7157yh4Wpi+FNEryx5gDcfPJchgtovxj28nzquD0vXnvpyr3Wd8OaMwg6vW4EfL82jRKg==", + "version": "13.3.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics-cli/-/schematics-cli-13.3.5.tgz", + "integrity": "sha512-ARX20ebtfwzef8GdXIcB6uv0sjTsaEniZyXBFchEKD6kR5EYZVaBL+ZVUbmsU1d0XY///WzW7pqwCyu5H1u+vw==", "dev": true, "requires": { - "@angular-devkit/core": "13.2.5", - "@angular-devkit/schematics": "13.2.5", + "@angular-devkit/core": "13.3.5", + "@angular-devkit/schematics": "13.3.5", "ansi-colors": "4.1.1", "inquirer": "8.2.0", - "minimist": "1.2.5", + "minimist": "1.2.6", "symbol-observable": "4.0.0" }, "dependencies": { + "@angular-devkit/core": { + "version": "13.3.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.5.tgz", + "integrity": "sha512-w7vzK4VoYP9rLgxJ2SwEfrkpKybdD+QgQZlsDBzT0C6Ebp7b4gkNcNVFo8EiZvfDl6Yplw2IAP7g7fs3STn0hQ==", + "dev": true, + "requires": { + "ajv": "8.9.0", + "ajv-formats": "2.1.1", + "fast-json-stable-stringify": "2.1.0", + "magic-string": "0.25.7", + "rxjs": "6.6.7", + "source-map": "0.7.3" + }, + "dependencies": { + "rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + } + } + }, + "@angular-devkit/schematics": { + "version": "13.3.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-13.3.5.tgz", + "integrity": "sha512-0N/kL/Vfx0yVAEwa3HYxNx9wYb+G9r1JrLjJQQzDp+z9LtcojNf7j3oey6NXrDUs1WjVZOa/AIdRl3/DuaoG5w==", + "dev": true, + "requires": { + "@angular-devkit/core": "13.3.5", + "jsonc-parser": "3.0.0", + "magic-string": "0.25.7", + "ora": "5.4.1", + "rxjs": "6.6.7" + }, + "dependencies": { + "rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + } + } + }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -15616,6 +15572,12 @@ "strip-ansi": "^6.0.0", "through": "^2.3.6" } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true } } }, @@ -16128,6 +16090,13 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, + "@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, + "optional": true + }, "@cspotcode/source-map-consumer": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", @@ -16612,20 +16581,20 @@ } }, "@nestjs/cli": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-8.2.2.tgz", - "integrity": "sha512-ZonmNLCHfTVrZGgYf4mrpivnKGaRzVRAcux+WDbzhQDNIz70s7mdOPShXW1Vpq+7uRJDxlgO1vOMhmg4uEUIDg==", + "version": "8.2.6", + "resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-8.2.6.tgz", + "integrity": "sha512-uvwKbUZJmgdJu1D24e+uUqHnwoB/0R9hLfUJjr5pTvLlP/RJugHAdJr7m1dQe92Xzdyi36kBN4Id3RXHgfz1UA==", "dev": true, "requires": { - "@angular-devkit/core": "13.2.5", - "@angular-devkit/schematics": "13.2.5", - "@angular-devkit/schematics-cli": "13.2.5", + "@angular-devkit/core": "13.3.5", + "@angular-devkit/schematics": "13.3.5", + "@angular-devkit/schematics-cli": "13.3.5", "@nestjs/schematics": "^8.0.3", "chalk": "3.0.0", "chokidar": "3.5.3", - "cli-table3": "0.6.1", + "cli-table3": "0.6.2", "commander": "4.1.1", - "fork-ts-checker-webpack-plugin": "7.2.1", + "fork-ts-checker-webpack-plugin": "7.2.11", "inquirer": "7.3.3", "node-emoji": "1.11.0", "ora": "5.4.1", @@ -16634,126 +16603,54 @@ "shelljs": "0.8.5", "source-map-support": "0.5.21", "tree-kill": "1.2.2", - "tsconfig-paths": "3.12.0", + "tsconfig-paths": "3.14.1", "tsconfig-paths-webpack-plugin": "3.5.2", - "typescript": "4.6.2", - "webpack": "5.66.0", + "typescript": "4.6.4", + "webpack": "5.72.1", "webpack-node-externals": "3.0.0" }, "dependencies": { - "@types/estree": { - "version": "0.0.50", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.50.tgz", - "integrity": "sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw==", - "dev": true - }, - "acorn": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", - "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", - "dev": true - }, - "acorn-import-assertions": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", - "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", - "dev": true, - "requires": {} - }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "@angular-devkit/core": { + "version": "13.3.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.5.tgz", + "integrity": "sha512-w7vzK4VoYP9rLgxJ2SwEfrkpKybdD+QgQZlsDBzT0C6Ebp7b4gkNcNVFo8EiZvfDl6Yplw2IAP7g7fs3STn0hQ==", "dev": true, "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "ajv": "8.9.0", + "ajv-formats": "2.1.1", + "fast-json-stable-stringify": "2.1.0", + "magic-string": "0.25.7", + "rxjs": "6.6.7", + "source-map": "0.7.3" } }, - "ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, - "requires": {} - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "@angular-devkit/schematics": { + "version": "13.3.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-13.3.5.tgz", + "integrity": "sha512-0N/kL/Vfx0yVAEwa3HYxNx9wYb+G9r1JrLjJQQzDp+z9LtcojNf7j3oey6NXrDUs1WjVZOa/AIdRl3/DuaoG5w==", "dev": true, "requires": { - "minimist": "^1.2.0" + "@angular-devkit/core": "13.3.5", + "jsonc-parser": "3.0.0", + "magic-string": "0.25.7", + "ora": "5.4.1", + "rxjs": "6.6.7" } }, - "schema-utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", - "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", "dev": true, "requires": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" + "tslib": "^1.9.0" } }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true - }, - "tsconfig-paths": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.12.0.tgz", - "integrity": "sha512-e5adrnOYT6zqVnWqZu7i/BQ3BnhzvGbjEjejFXO20lKIKpwTaupkCPgEfv4GZK1IBciJUEhYs3J3p75FdaTFVg==", - "dev": true, - "requires": { - "@types/json5": "^0.0.29", - "json5": "^1.0.1", - "minimist": "^1.2.0", - "strip-bom": "^3.0.0" - } - }, - "webpack": { - "version": "5.66.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.66.0.tgz", - "integrity": "sha512-NJNtGT7IKpGzdW7Iwpn/09OXz9inIkeIQ/ibY6B+MdV1x6+uReqz/5z1L89ezWnpPDWpXF0TY5PCYKQdWVn8Vg==", - "dev": true, - "requires": { - "@types/eslint-scope": "^3.7.0", - "@types/estree": "^0.0.50", - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/wasm-edit": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "acorn": "^8.4.1", - "acorn-import-assertions": "^1.7.6", - "browserslist": "^4.14.5", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.8.3", - "es-module-lexer": "^0.9.0", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.9", - "json-parse-better-errors": "^1.0.2", - "loader-runner": "^4.2.0", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^3.1.0", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.1.3", - "watchpack": "^2.3.1", - "webpack-sources": "^3.2.2" - } } } }, @@ -18070,15 +17967,6 @@ "ajv": "^8.0.0" } }, - "ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.3" - } - }, "ansi-colors": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", @@ -18837,12 +18725,12 @@ "dev": true }, "cli-table3": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.1.tgz", - "integrity": "sha512-w0q/enDHhPLq44ovMGdQeeDLvwxwavsJX7oQGYt/LrBlYsyaxyDnp6z3QzFut/6kLLKnlcUVJLrpB7KBfgG/RA==", + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.2.tgz", + "integrity": "sha512-QyavHCaIC80cMivimWu4aWHilIpiDpfm3hGmqAmXVL1UsnbLuBSMd21hTX6VY4ZSDSM73ESLeF8TOYId3rBTbw==", "dev": true, "requires": { - "colors": "1.4.0", + "@colors/colors": "1.5.0", "string-width": "^4.2.0" } }, @@ -18902,13 +18790,6 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, - "colors": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", - "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", - "dev": true, - "optional": true - }, "combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -19593,9 +19474,9 @@ } }, "enhanced-resolve": { - "version": "5.9.2", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.9.2.tgz", - "integrity": "sha512-GIm3fQfwLJ8YZx2smuHpBKkXC1yOk+OBEmKckVyL0i/ea8mqDEykK3ld5dgH1QYPNyT/lIllxV2LULnxCHaHkA==", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.9.3.tgz", + "integrity": "sha512-Bq9VSor+kjvW3f9/MiiR4eE3XYgOl7/rS8lnSxbRbF3kS0B2r+Y9w5krBWxZgDxASVZbdYrn5wT4j/Wb0J9qow==", "dev": true, "requires": { "graceful-fs": "^4.2.4", @@ -20225,9 +20106,9 @@ "integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==" }, "fork-ts-checker-webpack-plugin": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-7.2.1.tgz", - "integrity": "sha512-uOfQdg/iQ8iokQ64qcbu8iZb114rOmaKLQFu7hU14/eJaKgsP91cQ7ts7v2iiDld6TzDe84Meksha8/MkWiCyw==", + "version": "7.2.11", + "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-7.2.11.tgz", + "integrity": "sha512-2e5+NyTUTE1Xq4fWo7KFEQblCaIvvINQwUX3jRmEGlgCTc1Ecqw/975EfQrQ0GEraxJTnp8KB9d/c8hlCHUMJA==", "dev": true, "requires": { "@babel/code-frame": "^7.16.7", @@ -20238,7 +20119,7 @@ "fs-extra": "^10.0.0", "memfs": "^3.4.1", "minimatch": "^3.0.4", - "schema-utils": "4.0.0", + "schema-utils": "^3.1.1", "semver": "^7.3.5", "tapable": "^2.2.1" }, @@ -22351,9 +22232,9 @@ "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" }, "memfs": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.4.1.tgz", - "integrity": "sha512-1c9VPVvW5P7I85c35zAdEr1TD5+F11IToIHIlrVIcflfnzPkJa0ZoYEoEdYDP8KgPFoSZ/opDrUsAoZWym3mtw==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.4.3.tgz", + "integrity": "sha512-eivjfi7Ahr6eQTn44nvTnR60e4a1Fs1Via2kCR5lHo/kyNoiMWaXCNJ/GpSd0ilXas2JSOl9B5FTIhflXu0hlg==", "dev": true, "requires": { "fs-monkey": "1.0.3" @@ -22462,9 +22343,9 @@ } }, "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" }, "minimist-options": { "version": "4.1.0", @@ -24622,6 +24503,14 @@ "pause": "0.0.1" } }, + "passport-custom": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/passport-custom/-/passport-custom-1.1.1.tgz", + "integrity": "sha512-/2m7jUGxmCYvoqenLB9UrmkCgPt64h8ZtV+UtuQklZ/Tn1NpKBeOorCYkB/8lMRoiZ5hUrCoMmDtxCS/d38mlg==", + "requires": { + "passport-strategy": "1.x.x" + } + }, "passport-jwt": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/passport-jwt/-/passport-jwt-4.0.0.tgz", @@ -25321,15 +25210,41 @@ } }, "schema-utils": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", - "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", "dev": true, "requires": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.8.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.0.0" + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "requires": {} + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + } } }, "semantic-release": { @@ -26121,42 +26036,6 @@ "terser": "^5.7.2" }, "dependencies": { - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, - "requires": {} - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "schema-utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", - "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - } - }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -26386,14 +26265,14 @@ } }, "tsconfig-paths": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.13.0.tgz", - "integrity": "sha512-nWuffZppoaYK0vQ1SQmkSsQzJoHA4s6uzdb2waRpD806x9yfq153AdVsWz4je2qZcW+pENrMQXbGQ3sMCkXuhw==", + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", + "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", "dev": true, "requires": { "@types/json5": "^0.0.29", "json5": "^1.0.1", - "minimist": "^1.2.0", + "minimist": "^1.2.6", "strip-bom": "^3.0.0" }, "dependencies": { @@ -26596,9 +26475,9 @@ } }, "typescript": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.2.tgz", - "integrity": "sha512-HM/hFigTBHZhLXshn9sN37H085+hQGeJHJ/X7LpBWLID/fbc2acUMfU+lGD98X81sKP+pFa9f0DZmCwB9GnbAg==", + "version": "4.6.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.4.tgz", + "integrity": "sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==", "dev": true }, "uglify-js": { @@ -26767,11 +26646,10 @@ "dev": true }, "webpack": { - "version": "5.70.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.70.0.tgz", - "integrity": "sha512-ZMWWy8CeuTTjCxbeaQI21xSswseF2oNOwc70QSKNePvmxE7XW36i7vpBMYZFAUHPwQiEbNGCEYIOOlyRbdGmxw==", + "version": "5.72.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.72.1.tgz", + "integrity": "sha512-dXG5zXCLspQR4krZVR6QgajnZOjW2K/djHvdcRaDQvsjV9z9vaW6+ja5dZOYbqBBjF6kGXka/2ZyxNdc+8Jung==", "dev": true, - "peer": true, "requires": { "@types/eslint-scope": "^3.7.3", "@types/estree": "^0.0.51", @@ -26782,13 +26660,13 @@ "acorn-import-assertions": "^1.7.6", "browserslist": "^4.14.5", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.9.2", + "enhanced-resolve": "^5.9.3", "es-module-lexer": "^0.9.0", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.2.9", - "json-parse-better-errors": "^1.0.2", + "json-parse-even-better-errors": "^2.3.1", "loader-runner": "^4.2.0", "mime-types": "^2.1.27", "neo-async": "^2.6.2", @@ -26803,56 +26681,14 @@ "version": "8.7.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", - "dev": true, - "peer": true + "dev": true }, "acorn-import-assertions": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", "dev": true, - "peer": true, "requires": {} - }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "peer": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, - "peer": true, - "requires": {} - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "peer": true - }, - "schema-utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", - "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", - "dev": true, - "peer": true, - "requires": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - } } } }, diff --git a/api/package.json b/api/package.json index 7e11e66..1e71e56 100644 --- a/api/package.json +++ b/api/package.json @@ -43,6 +43,7 @@ "graphql-type-json": "^0.3.2", "jsonata": "^1.8.5", "passport": "^0.5.2", + "passport-custom": "^1.1.1", "passport-jwt": "^4.0.0", "passport-local": "^1.0.0", "pg": "^8.7.3", @@ -98,12 +99,12 @@ "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ - "**/*.(t|j)s", - "!**/*e2e-spec.ts", - "!**/*.module.ts", + "**/*.(t|j)s", + "!**/*e2e-spec.ts", + "!**/*.module.ts", "!**/*.decorator.ts", - "!**/*.model.ts", - "!**/*.input.ts", + "!**/*.model.ts", + "!**/*.input.ts", "!**/jest.config.ts", "!**/main.ts" ], diff --git a/api/src/auth/auth.module.ts b/api/src/auth/auth.module.ts index 805f0dc..936e09c 100644 --- a/api/src/auth/auth.module.ts +++ b/api/src/auth/auth.module.ts @@ -5,6 +5,7 @@ import { PassportModule } from '@nestjs/passport'; import { authConstants } from './auth-constants'; import { AuthResolver } from './auth.resolver'; import { AuthService } from './auth.service'; +import { EngineStrategy } from './strategies/engine.strategy'; import { JwtBearerStrategy } from './strategies/jwt-bearer.strategy'; import { JwtCookiesStrategy } from './strategies/jwt-cookies.strategy'; import { LocalStrategy } from './strategies/local.strategy'; @@ -30,6 +31,7 @@ import { LocalStrategy } from './strategies/local.strategy'; LocalStrategy, JwtBearerStrategy, JwtCookiesStrategy, + EngineStrategy, AuthResolver, ], exports: [AuthService], diff --git a/api/src/auth/auth.resolver.spec.ts b/api/src/auth/auth.resolver.spec.ts index c109dbe..e545b55 100644 --- a/api/src/auth/auth.resolver.spec.ts +++ b/api/src/auth/auth.resolver.spec.ts @@ -79,9 +79,9 @@ describe('AuthResolver', () => { expect(data.accessToken).toBe(authData.accessToken); }); - it('logout', () => { + it('logout', async () => { const request: any = jest.fn(); - resolver.logout(request, res, user); + await resolver.logout(request, res, user); expect(mockClearCookie.mock.calls[0][0]).toBe(authConstants.cookie.name); }); diff --git a/api/src/auth/auth.resolver.ts b/api/src/auth/auth.resolver.ts index cfa8e71..d01434a 100644 --- a/api/src/auth/auth.resolver.ts +++ b/api/src/auth/auth.resolver.ts @@ -7,6 +7,7 @@ import { import { ConfigService } from '@nestjs/config'; import { Args, Mutation, Resolver } from '@nestjs/graphql'; import { Response, Request } from 'express'; +import { CurrentUser } from '../common/decorators/user.decorator'; import { GQLRequest } from '../common/decorators/gql-request.decoractor'; import { GQLResponse } from '../common/decorators/gql-response.decoractor'; import { parseToBoolean } from '../common/utilities'; @@ -18,8 +19,7 @@ import { IEngineOptions, IEngineService } from '../engine/engine.interfaces'; import { User } from '../users/models/user.model'; import { authConstants } from './auth-constants'; import { AuthService } from './auth.service'; -import { CurrentUser } from './decorators/user.decorator'; -import { JwtAuthGuard } from './guards/jwt-auth.guard'; +import { GlobalAuthGuard } from './guards/global-auth.guard'; import { LocalAuthGuard } from './guards/local-auth.guard'; import { AuthenticationInput } from './inputs/authentication.input'; import { AuthenticationOutput } from './outputs/authentication.output'; @@ -73,16 +73,16 @@ export class AuthResolver { } @Mutation(() => Boolean) - @UseGuards(JwtAuthGuard) - logout( + @UseGuards(GlobalAuthGuard) + async logout( @GQLRequest() req: Request, @GQLResponse() res: Response, @CurrentUser() user: User, - ): boolean { + ): Promise<boolean> { if (user) { this.logger.verbose(`${user.username} logged out`); try { - this.engineService.logout?.(req); + await this.engineService.logout?.(req); } catch (e) { this.logger.debug( `Service ${this.engineOptions.type} produce an error when logging out ${user.username}`, diff --git a/api/src/auth/auth.service.ts b/api/src/auth/auth.service.ts index 18eaadf..402dfc6 100644 --- a/api/src/auth/auth.service.ts +++ b/api/src/auth/auth.service.ts @@ -14,7 +14,7 @@ export class AuthService { async validateUser(username: string, password: string): Promise<User> { if (!this.engineService.login) throw new NotImplementedException(); - return await this.engineService.login?.(username, password); + return this.engineService.login?.(username, password); } /** diff --git a/api/src/auth/decorators/user.decorator.ts b/api/src/auth/decorators/user.decorator.ts deleted file mode 100644 index 0aa940d..0000000 --- a/api/src/auth/decorators/user.decorator.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { createParamDecorator, ExecutionContext } from '@nestjs/common'; -import { GqlExecutionContext } from '@nestjs/graphql'; -import { User } from '../../users/models/user.model'; - -/** - * Retrieve the current user within the graphQL request - * @returns instance of User or undefined - */ -export const CurrentUser = createParamDecorator( - (data: unknown, context: ExecutionContext): User | undefined => { - const ctx = GqlExecutionContext.create(context); - return ctx.getContext().req.user; - }, -); diff --git a/api/src/auth/guards/jwt-auth.guard.ts b/api/src/auth/guards/global-auth.guard.ts similarity index 73% rename from api/src/auth/guards/jwt-auth.guard.ts rename to api/src/auth/guards/global-auth.guard.ts index 2ee1a63..ecfd65e 100644 --- a/api/src/auth/guards/jwt-auth.guard.ts +++ b/api/src/auth/guards/global-auth.guard.ts @@ -4,11 +4,13 @@ import { Reflector } from '@nestjs/core'; import { GqlExecutionContext } from '@nestjs/graphql'; import { AuthGuard } from '@nestjs/passport'; import { Observable } from 'rxjs'; -import { parseToBoolean } from '../../common/utilities'; -import { authConstants } from '../auth-constants'; @Injectable() -export class JwtAuthGuard extends AuthGuard(['jwt-cookies', 'jwt-bearer']) { +export class GlobalAuthGuard extends AuthGuard([ + 'jwt-cookies', + 'jwt-bearer', + 'engine', +]) { constructor( private readonly configService: ConfigService, private readonly reflector: Reflector, @@ -31,11 +33,7 @@ export class JwtAuthGuard extends AuthGuard(['jwt-cookies', 'jwt-bearer']) { context.getHandler(), ); - const skipAuth = parseToBoolean( - this.configService.get(authConstants.skipAuth, 'false'), - ); - - if (skipAuth || isPublic) { + if (isPublic) { return true; } diff --git a/api/src/auth/strategies/engine.strategy.ts b/api/src/auth/strategies/engine.strategy.ts new file mode 100644 index 0000000..f98cede --- /dev/null +++ b/api/src/auth/strategies/engine.strategy.ts @@ -0,0 +1,22 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { PassportStrategy } from '@nestjs/passport'; +import { ENGINE_SERVICE } from 'src/engine/engine.constants'; +import { IEngineService } from 'src/engine/engine.interfaces'; +import { Request } from 'express'; +import { Strategy } from 'passport-custom'; + +@Injectable() +export class EngineStrategy extends PassportStrategy(Strategy, 'engine') { + constructor( + @Inject(ENGINE_SERVICE) private readonly engineService: IEngineService, + ) { + super(); + } + + async validate(req: Request) { + if (!this.engineService.getActiveUser) return false; + const user = this.engineService.getActiveUser(req); + + return user ?? false; + } +} diff --git a/api/src/common/decorators/user.decorator.ts b/api/src/common/decorators/user.decorator.ts index 0aa940d..8db357e 100644 --- a/api/src/common/decorators/user.decorator.ts +++ b/api/src/common/decorators/user.decorator.ts @@ -7,7 +7,7 @@ import { User } from '../../users/models/user.model'; * @returns instance of User or undefined */ export const CurrentUser = createParamDecorator( - (data: unknown, context: ExecutionContext): User | undefined => { + (_data: unknown, context: ExecutionContext): User | undefined => { const ctx = GqlExecutionContext.create(context); return ctx.getContext().req.user; }, diff --git a/api/src/engine/connectors/csv/main.connector.ts b/api/src/engine/connectors/csv/main.connector.ts index 79c592b..ebb2ee1 100644 --- a/api/src/engine/connectors/csv/main.connector.ts +++ b/api/src/engine/connectors/csv/main.connector.ts @@ -1,17 +1,14 @@ +import { HttpService } from '@nestjs/axios'; +import { NotImplementedException } from '@nestjs/common'; import { firstValueFrom } from 'rxjs'; +import { + Dictionary, + ExperimentResult, +} from 'src/common/interfaces/utilities.interface'; import { IEngineOptions, IEngineService } from 'src/engine/engine.interfaces'; import { Domain } from 'src/engine/models/domain.model'; -import { ExperimentCreateInput } from 'src/engine/models/experiment/input/experiment-create.input'; -import { - Experiment, - PartialExperiment, -} from 'src/engine/models/experiment/experiment.model'; -import { ListExperiments } from 'src/engine/models/experiment/list-experiments.model'; -import { ExperimentEditInput } from 'src/engine/models/experiment/input/experiment-edit.input'; import { Algorithm } from 'src/engine/models/experiment/algorithm.model'; -import { HttpService } from '@nestjs/axios'; import { Group } from 'src/engine/models/group.model'; -import { Dictionary } from 'src/common/interfaces/utilities.interface'; import { User } from 'src/users/models/user.model'; export default class CSVService implements IEngineService { @@ -21,51 +18,15 @@ export default class CSVService implements IEngineService { ) {} async logout() { - throw new Error('Method not implemented.'); + throw new NotImplementedException(); } async getAlgorithms(): Promise<Algorithm[]> { - throw new Error('Method not implemented.'); + throw new NotImplementedException(); } - async createExperiment( - data: ExperimentCreateInput, - isTransient: boolean, - ): Promise<Experiment> { - return { - id: '', - domain: '', - datasets: [], - algorithm: { - name: '', - }, - name: 'test', - variables: [], - }; - } - - async listExperiments(page: number, name: string): Promise<ListExperiments> { - return { - experiments: [], - currentPage: 0, - totalExperiments: 0, - totalPages: 0, - }; - } - - async getExperiment(id: string): Promise<Experiment> { - throw new Error('Method not implemented.'); - } - - async removeExperiment(id: string): Promise<PartialExperiment> { - throw new Error('Method not implemented.'); - } - - async editExperient( - id: string, - expriment: ExperimentEditInput, - ): Promise<Experiment> { - throw new Error('Method not implemented.'); + async runExperiment(): Promise<ExperimentResult[]> { + throw new NotImplementedException(); } async getDomains(): Promise<Domain[]> { @@ -154,17 +115,12 @@ export default class CSVService implements IEngineService { } async getActiveUser(): Promise<User> { - const dummyUser = { + return { username: 'anonymous', id: 'anonymousId', fullname: 'anonymous', email: 'anonymous@anonymous.com', agreeNDA: true, }; - return dummyUser; - } - - getAlgorithmsREST(): string { - return '[]'; } } diff --git a/api/src/engine/connectors/datashield/main.connector.ts b/api/src/engine/connectors/datashield/main.connector.ts index f796f3c..153ceac 100644 --- a/api/src/engine/connectors/datashield/main.connector.ts +++ b/api/src/engine/connectors/datashield/main.connector.ts @@ -7,7 +7,10 @@ import { } from '@nestjs/common'; import { Request } from 'express'; import { catchError, firstValueFrom } from 'rxjs'; -import { MIME_TYPES } from 'src/common/interfaces/utilities.interface'; +import { + ExperimentResult, + MIME_TYPES, +} from 'src/common/interfaces/utilities.interface'; import { errorAxiosHandler } from 'src/common/utilities'; import { ENGINE_MODULE_OPTIONS } from 'src/engine/engine.constants'; import { @@ -18,13 +21,12 @@ import { import { Domain } from 'src/engine/models/domain.model'; import { Algorithm } from 'src/engine/models/experiment/algorithm.model'; import { Experiment } from 'src/engine/models/experiment/experiment.model'; -import { ExperimentCreateInput } from 'src/engine/models/experiment/input/experiment-create.input'; -import { ListExperiments } from 'src/engine/models/experiment/list-experiments.model'; import { RawResult } from 'src/engine/models/result/raw-result.model'; import { TableResult, TableStyle, } from 'src/engine/models/result/table-result.model'; +import { ExperimentCreateInput } from 'src/experiments/models/input/experiment-create.input'; import { User } from 'src/users/models/user.model'; import { dataToGroups, @@ -65,7 +67,7 @@ export default class DataShieldService implements IEngineService { .pipe(catchError((e) => errorAxiosHandler(e))), ); - const cookies = (loginData.headers['set-cookie'] as string[]) ?? []; + const cookies = loginData.headers['set-cookie'] ?? []; if (loginData.headers && loginData.headers['set-cookie']) { cookies.forEach((cookie) => { const [key, value] = cookie.split(/={1}/); @@ -143,11 +145,10 @@ export default class DataShieldService implements IEngineService { }; } - async createExperiment( + async runExperiment( data: ExperimentCreateInput, - isTransient: boolean, request: Request, - ): Promise<Experiment> { + ): Promise<ExperimentResult[]> { const user = request.user as User; const cookie = [`sid=${user.extraFields['sid']}`, `user=${user.id}`].join( ';', @@ -166,37 +167,21 @@ export default class DataShieldService implements IEngineService { switch (data.algorithm.id) { case 'MULTIPLE_HISTOGRAMS': { expResult.results = await Promise.all<RawResult>( - data.variables.map( - async (variable) => await this.getHistogram(variable, cookie), - ), + data.variables.map((variable) => this.getHistogram(variable, cookie)), ); break; } case 'DESCRIPTIVE_STATS': { expResult.results = await Promise.all<TableResult>( - [...data.variables, ...data.coVariables].map( - async (variable) => - await this.getDescriptiveStats(variable, cookie), + [...data.variables, ...data.coVariables].map((variable) => + this.getDescriptiveStats(variable, cookie), ), ); break; } } - return expResult; - } - - async listExperiments(): Promise<ListExperiments> { - return { - totalExperiments: 0, - experiments: [], - totalPages: 0, - currentPage: 0, - }; - } - - async getExperiment(id: string): Promise<Experiment> { - throw new NotImplementedException(); + return expResult.results; } async logout(request: Request): Promise<void> { @@ -214,7 +199,7 @@ export default class DataShieldService implements IEngineService { }); } - async getDomains(ids: string[], request: Request): Promise<Domain[]> { + async getDomains(_ids: string[], request: Request): Promise<Domain[]> { const user = request.user as User; const sid = user && user.extraFields && user.extraFields['sid']; @@ -249,8 +234,4 @@ export default class DataShieldService implements IEngineService { fullname: user.id, }; } - - getAlgorithmsREST(): string { - return '[]'; - } } diff --git a/api/src/engine/connectors/exareme/converters.ts b/api/src/engine/connectors/exareme/converters.ts index 73ac0f4..a1c28d5 100644 --- a/api/src/engine/connectors/exareme/converters.ts +++ b/api/src/engine/connectors/exareme/converters.ts @@ -1,9 +1,12 @@ import { MIME_TYPES } from 'src/common/interfaces/utilities.interface'; import { Category } from 'src/engine/models/category.model'; import { Dataset } from 'src/engine/models/dataset.model'; -import { Experiment } from 'src/engine/models/experiment/experiment.model'; -import { AlgorithmParamInput } from 'src/engine/models/experiment/input/algorithm-parameter.input'; -import { ExperimentCreateInput } from 'src/engine/models/experiment/input/experiment-create.input'; +import { + Experiment, + ExperimentStatus, +} from 'src/engine/models/experiment/experiment.model'; +import { AlgorithmParamInput } from 'src/experiments/models/input/algorithm-parameter.input'; +import { ExperimentCreateInput } from 'src/experiments/models/input/experiment-create.input'; import { Group } from 'src/engine/models/group.model'; import { ResultUnion } from 'src/engine/models/result/common/result-union.model'; import { @@ -36,7 +39,7 @@ export const dataToGroup = (data: Hierarchy): Group => { ? data.groups.map(dataToGroup).map((group) => group.id) : [], variables: data.variables - ? data.variables.map((data: VariableEntity) => data.code) + ? data.variables.map((v: VariableEntity) => v.code) : [], }; }; @@ -223,7 +226,7 @@ export const dataToExperiment = ( return { id: data.uuid, name: data.name, - status: 'error', + status: ExperimentStatus.ERROR, variables: [], domain: data['domain'] ?? '', results: [ diff --git a/api/src/engine/connectors/exareme/interfaces/test-utilities.ts b/api/src/engine/connectors/exareme/interfaces/test-utilities.ts index 2ad1c79..6746a8f 100644 --- a/api/src/engine/connectors/exareme/interfaces/test-utilities.ts +++ b/api/src/engine/connectors/exareme/interfaces/test-utilities.ts @@ -1,6 +1,6 @@ import { IEngineService } from 'src/engine/engine.interfaces'; import { Experiment } from 'src/engine/models/experiment/experiment.model'; -import { ExperimentCreateInput } from 'src/engine/models/experiment/input/experiment-create.input'; +import { ExperimentCreateInput } from 'src/experiments/models/input/experiment-create.input'; const TIMEOUT_DURATION_SECONDS = 60 * 10; @@ -30,7 +30,7 @@ const createExperiment = async ( input: ExperimentCreateInput, service: IEngineService, ): Promise<Experiment | undefined> => { - return await service.createExperiment(input, false); + return service.createExperiment(input, false); }; const waitForResult = ( diff --git a/api/src/engine/connectors/exareme/main.connector.ts b/api/src/engine/connectors/exareme/main.connector.ts index 8a9d84b..9420070 100644 --- a/api/src/engine/connectors/exareme/main.connector.ts +++ b/api/src/engine/connectors/exareme/main.connector.ts @@ -22,8 +22,8 @@ import { Experiment, PartialExperiment, } from 'src/engine/models/experiment/experiment.model'; -import { ExperimentCreateInput } from 'src/engine/models/experiment/input/experiment-create.input'; -import { ExperimentEditInput } from 'src/engine/models/experiment/input/experiment-edit.input'; +import { ExperimentCreateInput } from 'src/experiments/models/input/experiment-create.input'; +import { ExperimentEditInput } from 'src/experiments/models/input/experiment-edit.input'; import { ListExperiments } from 'src/engine/models/experiment/list-experiments.model'; import { Group } from 'src/engine/models/group.model'; import { Variable } from 'src/engine/models/variable.model'; @@ -118,7 +118,7 @@ export default class ExaremeService implements IEngineService { return dataToExperiment(resultAPI.data); } - async editExperient( + async editExperiment( id: string, expriment: ExperimentEditInput, request: Request, @@ -156,18 +156,18 @@ export default class ExaremeService implements IEngineService { return ( data?.data - .filter((data) => !ids || ids.length == 0 || ids.includes(data.code)) - .map((data): Domain => { - const groups = this.flattenGroups(data.metadataHierarchy); + .filter((d) => !ids || ids.length == 0 || ids.includes(d.code)) + .map((d): Domain => { + const groups = this.flattenGroups(d.metadataHierarchy); return { - id: data.code, - label: data.label, + id: d.code, + label: d.label, groups: groups, - rootGroup: dataToGroup(data.metadataHierarchy), - datasets: data.datasets ? data.datasets.map(dataToDataset) : [], - variables: data.metadataHierarchy - ? this.flattenVariables(data.metadataHierarchy, groups) + rootGroup: dataToGroup(d.metadataHierarchy), + datasets: d.datasets ? d.datasets.map(dataToDataset) : [], + variables: d.metadataHierarchy + ? this.flattenVariables(d.metadataHierarchy, groups) : [], }; }) ?? [] @@ -187,7 +187,10 @@ export default class ExaremeService implements IEngineService { try { return transformToUser.evaluate(response.data); } catch (e) { - new InternalServerErrorException('Cannot parse user data from Engine', e); + throw new InternalServerErrorException( + 'Cannot parse user data from Engine', + e, + ); } } @@ -202,14 +205,6 @@ export default class ExaremeService implements IEngineService { return undefined; //we don't want to manage data locally } - getAlgorithmsREST(request: Request): Observable<string> { - const path = this.options.baseurl + 'algorithms'; - - return this.get<string>(request, path, { params: request.query }).pipe( - map((response) => response.data), - ); - } - getPassthrough( suffix: string, request: Request, @@ -233,7 +228,7 @@ export default class ExaremeService implements IEngineService { }; private flattenVariables = (data: Hierarchy, groups: Group[]): Variable[] => { - const group = groups.find((group) => group.id == data.code); + const group = groups.find((g) => g.id == data.code); let variables = data.variables ? data.variables.map(dataToVariable) : []; variables.forEach((variable) => (variable.groups = group ? [group] : [])); diff --git a/api/src/engine/connectors/exareme/tests/e2e/3c.e2e-spec.ts b/api/src/engine/connectors/exareme/tests/e2e/3c.e2e-spec.ts index 8770e90..7b4d577 100644 --- a/api/src/engine/connectors/exareme/tests/e2e/3c.e2e-spec.ts +++ b/api/src/engine/connectors/exareme/tests/e2e/3c.e2e-spec.ts @@ -2,7 +2,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import { AppModule } from '../../../../../main/app.module'; import { ENGINE_SERVICE } from '../../../../engine.constants'; import { IEngineService } from '../../../../engine.interfaces'; -import { ExperimentCreateInput } from '../../../../models/experiment/input/experiment-create.input'; +import { ExperimentCreateInput } from '../../../../../experiments/models/input/experiment-create.input'; import { createExperiment, generateNumber, diff --git a/api/src/engine/connectors/exareme/tests/e2e/calibration-belt.e2e-spec.ts b/api/src/engine/connectors/exareme/tests/e2e/calibration-belt.e2e-spec.ts index 447b8f4..2e696f0 100644 --- a/api/src/engine/connectors/exareme/tests/e2e/calibration-belt.e2e-spec.ts +++ b/api/src/engine/connectors/exareme/tests/e2e/calibration-belt.e2e-spec.ts @@ -2,7 +2,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import { AppModule } from '../../../../../main/app.module'; import { ENGINE_SERVICE } from '../../../../engine.constants'; import { IEngineService } from '../../../../engine.interfaces'; -import { ExperimentCreateInput } from '../../../../models/experiment/input/experiment-create.input'; +import { ExperimentCreateInput } from '../../../../../experiments/models/input/experiment-create.input'; import { createExperiment, generateNumber, diff --git a/api/src/engine/connectors/exareme/tests/e2e/cart.e2e-spec.ts b/api/src/engine/connectors/exareme/tests/e2e/cart.e2e-spec.ts index f8b0085..550e75c 100644 --- a/api/src/engine/connectors/exareme/tests/e2e/cart.e2e-spec.ts +++ b/api/src/engine/connectors/exareme/tests/e2e/cart.e2e-spec.ts @@ -3,7 +3,7 @@ import { RawResult } from 'src/engine/models/result/raw-result.model'; import { AppModule } from '../../../../../main/app.module'; import { ENGINE_SERVICE } from '../../../../engine.constants'; import { IEngineService } from '../../../../engine.interfaces'; -import { ExperimentCreateInput } from '../../../../models/experiment/input/experiment-create.input'; +import { ExperimentCreateInput } from '../../../../../experiments/models/input/experiment-create.input'; import { createExperiment, generateNumber, diff --git a/api/src/engine/connectors/exareme/tests/e2e/descriptiveStatistics.e2e-spec.ts b/api/src/engine/connectors/exareme/tests/e2e/descriptiveStatistics.e2e-spec.ts index 5808b15..12737e1 100644 --- a/api/src/engine/connectors/exareme/tests/e2e/descriptiveStatistics.e2e-spec.ts +++ b/api/src/engine/connectors/exareme/tests/e2e/descriptiveStatistics.e2e-spec.ts @@ -4,7 +4,7 @@ import { TableResult } from 'src/engine/models/result/table-result.model'; import { AppModule } from '../../../../../main/app.module'; import { ENGINE_SERVICE } from '../../../../engine.constants'; import { IEngineService } from '../../../../engine.interfaces'; -import { ExperimentCreateInput } from '../../../../models/experiment/input/experiment-create.input'; +import { ExperimentCreateInput } from '../../../../../experiments/models/input/experiment-create.input'; import { createExperiment, generateNumber, diff --git a/api/src/engine/connectors/exareme/tests/e2e/id3.e2e-spec.ts b/api/src/engine/connectors/exareme/tests/e2e/id3.e2e-spec.ts index 4ef963a..6a34e18 100644 --- a/api/src/engine/connectors/exareme/tests/e2e/id3.e2e-spec.ts +++ b/api/src/engine/connectors/exareme/tests/e2e/id3.e2e-spec.ts @@ -3,7 +3,7 @@ import { RawResult } from 'src/engine/models/result/raw-result.model'; import { AppModule } from '../../../../../main/app.module'; import { ENGINE_SERVICE } from '../../../../engine.constants'; import { IEngineService } from '../../../../engine.interfaces'; -import { ExperimentCreateInput } from '../../../../models/experiment/input/experiment-create.input'; +import { ExperimentCreateInput } from '../../../../../experiments/models/input/experiment-create.input'; import { createExperiment, generateNumber, diff --git a/api/src/engine/connectors/exareme/tests/e2e/k-means.e2e-spec.ts b/api/src/engine/connectors/exareme/tests/e2e/k-means.e2e-spec.ts index 5705472..b754a06 100644 --- a/api/src/engine/connectors/exareme/tests/e2e/k-means.e2e-spec.ts +++ b/api/src/engine/connectors/exareme/tests/e2e/k-means.e2e-spec.ts @@ -3,7 +3,7 @@ import { RawResult } from 'src/engine/models/result/raw-result.model'; import { AppModule } from '../../../../../main/app.module'; import { ENGINE_SERVICE } from '../../../../engine.constants'; import { IEngineService } from '../../../../engine.interfaces'; -import { ExperimentCreateInput } from '../../../../models/experiment/input/experiment-create.input'; +import { ExperimentCreateInput } from '../../../../../experiments/models/input/experiment-create.input'; import { createExperiment, generateNumber, diff --git a/api/src/engine/connectors/exareme/tests/e2e/kaplan-meier.e2e-spec.ts b/api/src/engine/connectors/exareme/tests/e2e/kaplan-meier.e2e-spec.ts index b076616..2defac9 100644 --- a/api/src/engine/connectors/exareme/tests/e2e/kaplan-meier.e2e-spec.ts +++ b/api/src/engine/connectors/exareme/tests/e2e/kaplan-meier.e2e-spec.ts @@ -2,7 +2,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import { AppModule } from '../../../../../main/app.module'; import { ENGINE_SERVICE } from '../../../../engine.constants'; import { IEngineService } from '../../../../engine.interfaces'; -import { ExperimentCreateInput } from '../../../../models/experiment/input/experiment-create.input'; +import { ExperimentCreateInput } from '../../../../../experiments/models/input/experiment-create.input'; import { createExperiment, generateNumber, diff --git a/api/src/engine/connectors/exareme/tests/e2e/linear-regression.e2e-spec.ts b/api/src/engine/connectors/exareme/tests/e2e/linear-regression.e2e-spec.ts index 4ed7f22..e50a0ad 100644 --- a/api/src/engine/connectors/exareme/tests/e2e/linear-regression.e2e-spec.ts +++ b/api/src/engine/connectors/exareme/tests/e2e/linear-regression.e2e-spec.ts @@ -3,7 +3,7 @@ import { RawResult } from 'src/engine/models/result/raw-result.model'; import { AppModule } from '../../../../../main/app.module'; import { ENGINE_SERVICE } from '../../../../engine.constants'; import { IEngineService } from '../../../../engine.interfaces'; -import { ExperimentCreateInput } from '../../../../models/experiment/input/experiment-create.input'; +import { ExperimentCreateInput } from '../../../../../experiments/models/input/experiment-create.input'; import { createExperiment, generateNumber, diff --git a/api/src/engine/connectors/exareme/tests/e2e/logistic-regression.e2e-spec.ts b/api/src/engine/connectors/exareme/tests/e2e/logistic-regression.e2e-spec.ts index abc045c..50bc9e6 100644 --- a/api/src/engine/connectors/exareme/tests/e2e/logistic-regression.e2e-spec.ts +++ b/api/src/engine/connectors/exareme/tests/e2e/logistic-regression.e2e-spec.ts @@ -3,7 +3,7 @@ import { RawResult } from 'src/engine/models/result/raw-result.model'; import { AppModule } from '../../../../../main/app.module'; import { ENGINE_SERVICE } from '../../../../engine.constants'; import { IEngineService } from '../../../../engine.interfaces'; -import { ExperimentCreateInput } from '../../../../models/experiment/input/experiment-create.input'; +import { ExperimentCreateInput } from '../../../../../experiments/models/input/experiment-create.input'; import { createExperiment, generateNumber, diff --git a/api/src/engine/connectors/exareme/tests/e2e/multiple-histograms.e2e-spec.ts b/api/src/engine/connectors/exareme/tests/e2e/multiple-histograms.e2e-spec.ts index 7604dd9..8eba296 100644 --- a/api/src/engine/connectors/exareme/tests/e2e/multiple-histograms.e2e-spec.ts +++ b/api/src/engine/connectors/exareme/tests/e2e/multiple-histograms.e2e-spec.ts @@ -2,7 +2,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import { AppModule } from '../../../../../main/app.module'; import { ENGINE_SERVICE } from '../../../../engine.constants'; import { IEngineService } from '../../../../engine.interfaces'; -import { ExperimentCreateInput } from '../../../../models/experiment/input/experiment-create.input'; +import { ExperimentCreateInput } from '../../../../../experiments/models/input/experiment-create.input'; import { createExperiment, generateNumber, diff --git a/api/src/engine/connectors/exareme/tests/e2e/naive-bayes.e2e-spec.ts b/api/src/engine/connectors/exareme/tests/e2e/naive-bayes.e2e-spec.ts index 024e412..cefa59c 100644 --- a/api/src/engine/connectors/exareme/tests/e2e/naive-bayes.e2e-spec.ts +++ b/api/src/engine/connectors/exareme/tests/e2e/naive-bayes.e2e-spec.ts @@ -2,7 +2,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import { AppModule } from '../../../../../main/app.module'; import { ENGINE_SERVICE } from '../../../../engine.constants'; import { IEngineService } from '../../../../engine.interfaces'; -import { ExperimentCreateInput } from '../../../../models/experiment/input/experiment-create.input'; +import { ExperimentCreateInput } from '../../../../../experiments/models/input/experiment-create.input'; import { RawResult } from '../../../../models/result/raw-result.model'; import { createExperiment, diff --git a/api/src/engine/connectors/exareme/tests/e2e/one-way-anova.e2e-spec.ts b/api/src/engine/connectors/exareme/tests/e2e/one-way-anova.e2e-spec.ts index e836179..3f954f5 100644 --- a/api/src/engine/connectors/exareme/tests/e2e/one-way-anova.e2e-spec.ts +++ b/api/src/engine/connectors/exareme/tests/e2e/one-way-anova.e2e-spec.ts @@ -2,7 +2,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import { AppModule } from '../../../../../main/app.module'; import { ENGINE_SERVICE } from '../../../../engine.constants'; import { IEngineService } from '../../../../engine.interfaces'; -import { ExperimentCreateInput } from '../../../../models/experiment/input/experiment-create.input'; +import { ExperimentCreateInput } from '../../../../../experiments/models/input/experiment-create.input'; import { RawResult } from '../../../../models/result/raw-result.model'; import { createExperiment, diff --git a/api/src/engine/connectors/exareme/tests/e2e/pca.e2e-spec.ts b/api/src/engine/connectors/exareme/tests/e2e/pca.e2e-spec.ts index 79f65b8..36f78cf 100644 --- a/api/src/engine/connectors/exareme/tests/e2e/pca.e2e-spec.ts +++ b/api/src/engine/connectors/exareme/tests/e2e/pca.e2e-spec.ts @@ -2,7 +2,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import { AppModule } from '../../../../../main/app.module'; import { ENGINE_SERVICE } from '../../../../engine.constants'; import { IEngineService } from '../../../../engine.interfaces'; -import { ExperimentCreateInput } from '../../../../models/experiment/input/experiment-create.input'; +import { ExperimentCreateInput } from '../../../../../experiments/models/input/experiment-create.input'; import { RawResult } from '../../../../models/result/raw-result.model'; import { createExperiment, diff --git a/api/src/engine/connectors/exareme/tests/e2e/pearson-correlation.e2e-spec.ts b/api/src/engine/connectors/exareme/tests/e2e/pearson-correlation.e2e-spec.ts index e1617dd..8bc3f92 100644 --- a/api/src/engine/connectors/exareme/tests/e2e/pearson-correlation.e2e-spec.ts +++ b/api/src/engine/connectors/exareme/tests/e2e/pearson-correlation.e2e-spec.ts @@ -2,7 +2,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import { AppModule } from '../../../../../main/app.module'; import { ENGINE_SERVICE } from '../../../../engine.constants'; import { IEngineService } from '../../../../engine.interfaces'; -import { ExperimentCreateInput } from '../../../../models/experiment/input/experiment-create.input'; +import { ExperimentCreateInput } from '../../../../../experiments/models/input/experiment-create.input'; import { RawResult } from '../../../../models/result/raw-result.model'; import { createExperiment, diff --git a/api/src/engine/connectors/exareme/tests/e2e/t-test-independant.e2e-spec.ts b/api/src/engine/connectors/exareme/tests/e2e/t-test-independant.e2e-spec.ts index 2dee185..208516e 100644 --- a/api/src/engine/connectors/exareme/tests/e2e/t-test-independant.e2e-spec.ts +++ b/api/src/engine/connectors/exareme/tests/e2e/t-test-independant.e2e-spec.ts @@ -2,7 +2,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import { AppModule } from '../../../../../main/app.module'; import { ENGINE_SERVICE } from '../../../../engine.constants'; import { IEngineService } from '../../../../engine.interfaces'; -import { ExperimentCreateInput } from '../../../../models/experiment/input/experiment-create.input'; +import { ExperimentCreateInput } from '../../../../../experiments/models/input/experiment-create.input'; import { RawResult } from '../../../../models/result/raw-result.model'; import { createExperiment, diff --git a/api/src/engine/connectors/exareme/tests/e2e/t-test-one-sample.e2e-spec.ts b/api/src/engine/connectors/exareme/tests/e2e/t-test-one-sample.e2e-spec.ts index a08e6fb..c9cf5af 100644 --- a/api/src/engine/connectors/exareme/tests/e2e/t-test-one-sample.e2e-spec.ts +++ b/api/src/engine/connectors/exareme/tests/e2e/t-test-one-sample.e2e-spec.ts @@ -2,7 +2,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import { AppModule } from '../../../../../main/app.module'; import { ENGINE_SERVICE } from '../../../../engine.constants'; import { IEngineService } from '../../../../engine.interfaces'; -import { ExperimentCreateInput } from '../../../../models/experiment/input/experiment-create.input'; +import { ExperimentCreateInput } from '../../../../../experiments/models/input/experiment-create.input'; import { RawResult } from '../../../../models/result/raw-result.model'; import { createExperiment, diff --git a/api/src/engine/connectors/exareme/tests/e2e/t-test-paired.e2e-spec.ts b/api/src/engine/connectors/exareme/tests/e2e/t-test-paired.e2e-spec.ts index dd2b971..45d2220 100644 --- a/api/src/engine/connectors/exareme/tests/e2e/t-test-paired.e2e-spec.ts +++ b/api/src/engine/connectors/exareme/tests/e2e/t-test-paired.e2e-spec.ts @@ -2,7 +2,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import { AppModule } from '../../../../../main/app.module'; import { ENGINE_SERVICE } from '../../../../engine.constants'; import { IEngineService } from '../../../../engine.interfaces'; -import { ExperimentCreateInput } from '../../../../models/experiment/input/experiment-create.input'; +import { ExperimentCreateInput } from '../../../../../experiments/models/input/experiment-create.input'; import { RawResult } from '../../../../models/result/raw-result.model'; import { createExperiment, diff --git a/api/src/engine/connectors/exareme/tests/e2e/two-way-anova.e2e-spec.ts b/api/src/engine/connectors/exareme/tests/e2e/two-way-anova.e2e-spec.ts index 7b616e3..021723c 100644 --- a/api/src/engine/connectors/exareme/tests/e2e/two-way-anova.e2e-spec.ts +++ b/api/src/engine/connectors/exareme/tests/e2e/two-way-anova.e2e-spec.ts @@ -2,7 +2,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import { AppModule } from '../../../../../main/app.module'; import { ENGINE_SERVICE } from '../../../../engine.constants'; import { IEngineService } from '../../../../engine.interfaces'; -import { ExperimentCreateInput } from '../../../../models/experiment/input/experiment-create.input'; +import { ExperimentCreateInput } from '../../../../../experiments/models/input/experiment-create.input'; import { RawResult } from '../../../../models/result/raw-result.model'; import { createExperiment, diff --git a/api/src/engine/connectors/exareme/transformations.ts b/api/src/engine/connectors/exareme/transformations.ts index 8ecf464..4a91590 100644 --- a/api/src/engine/connectors/exareme/transformations.ts +++ b/api/src/engine/connectors/exareme/transformations.ts @@ -7,7 +7,7 @@ export const transformToExperiment = jsonata(` ( $params := ["y", "pathology", "dataset", "filter", "x", "formula"]; $toArray := function($x) { $type($x) = 'array' ? $x : [$x]}; - $convDate := function($v) { $type($v) = 'string' ? $toMillis($v) : $v }; + $convDate := function($v) { $type($v) = 'string' ? $v : $fromMillis($v) }; $rp := function($v) {$replace($v, /(\\+|\\*|-)/, ',')}; $strSafe := function($v) { $type($v) = 'string' ? $v : "" }; $formula := $eval(algorithm.parameters[name = "formula"].value); @@ -171,12 +171,10 @@ dataToHeatmap.registerFunction( (a) => { const matrix = []; - a.forEach( - (elem: { y: number | number; x: number | number; value: number }) => { - matrix[elem.y] = matrix[elem.y] ?? []; - matrix[elem.y][elem.x] = elem.value; - }, - ); + a.forEach((elem: { y: number; x: number; value: number }) => { + matrix[elem.y] = matrix[elem.y] ?? []; + matrix[elem.y][elem.x] = elem.value; + }); return matrix; }, diff --git a/api/src/engine/connectors/local/main.connector.ts b/api/src/engine/connectors/local/main.connector.ts index 600fcc9..ef95f60 100644 --- a/api/src/engine/connectors/local/main.connector.ts +++ b/api/src/engine/connectors/local/main.connector.ts @@ -1,13 +1,7 @@ import { IEngineService } from 'src/engine/engine.interfaces'; import { Domain } from 'src/engine/models/domain.model'; import { Algorithm } from 'src/engine/models/experiment/algorithm.model'; -import { - Experiment, - PartialExperiment, -} from 'src/engine/models/experiment/experiment.model'; -import { ExperimentCreateInput } from 'src/engine/models/experiment/input/experiment-create.input'; -import { ExperimentEditInput } from 'src/engine/models/experiment/input/experiment-edit.input'; -import { ListExperiments } from 'src/engine/models/experiment/list-experiments.model'; +import { ResultUnion } from 'src/engine/models/result/common/result-union.model'; import { User } from 'src/users/models/user.model'; export default class LocalService implements IEngineService { @@ -22,29 +16,7 @@ export default class LocalService implements IEngineService { throw new Error('Method not implemented.'); } - async createExperiment( - data: ExperimentCreateInput, - isTransient: boolean, - ): Promise<Experiment> { - throw new Error('Method not implemented.'); - } - - async listExperiments(page: number, name: string): Promise<ListExperiments> { - throw new Error('Method not implemented.'); - } - - async getExperiment(id: string): Promise<Experiment> { - throw new Error('Method not implemented.'); - } - - async removeExperiment(id: string): Promise<PartialExperiment> { - throw new Error('Method not implemented.'); - } - - async editExperient( - id: string, - expriment: ExperimentEditInput, - ): Promise<Experiment> { + async runExperiment(): Promise<Array<typeof ResultUnion>> { throw new Error('Method not implemented.'); } @@ -68,17 +40,12 @@ export default class LocalService implements IEngineService { } async getActiveUser(): Promise<User> { - const dummyUser = { + return { username: 'anonymous', id: 'anonymousId', fullname: 'anonymous', email: 'anonymous@anonymous.com', agreeNDA: true, }; - return dummyUser; - } - - getAlgorithmsREST(): string { - return '[]'; } } diff --git a/api/src/engine/engine.interfaces.ts b/api/src/engine/engine.interfaces.ts index 8ca0765..d9ef0e6 100644 --- a/api/src/engine/engine.interfaces.ts +++ b/api/src/engine/engine.interfaces.ts @@ -9,9 +9,10 @@ import { Experiment, PartialExperiment, } from './models/experiment/experiment.model'; -import { ExperimentCreateInput } from './models/experiment/input/experiment-create.input'; -import { ExperimentEditInput } from './models/experiment/input/experiment-edit.input'; +import { ExperimentCreateInput } from '../experiments/models/input/experiment-create.input'; +import { ExperimentEditInput } from '../experiments/models/input/experiment-edit.input'; import { ListExperiments } from './models/experiment/list-experiments.model'; +import { ResultUnion } from './models/result/common/result-union.model'; export interface IEngineOptions { type: string; @@ -23,45 +24,108 @@ export type IConfiguration = Pick<Configuration, 'contactLink' | 'hasGalaxy'>; export interface IEngineService { /** * Allow specific configuration for the engine - * - * `connectorId` is always overwrite by the engine module */ getConfiguration?(): IConfiguration; + /** + * Get the list of domains along with a list of variables + * @param ids - Ids to filter the domain needed + * @param req - Request - this is the request object from the HTTP request. + */ getDomains(ids: string[], req?: Request): Domain[] | Promise<Domain[]>; - createExperiment( + /** + * Create and return a full detailed experiment + * @param {ExperimentCreateInput} data - ExperimentCreateInput - this is the data that you want to + * send to the API. + * @param [isTransient=false] - If true, the experiment will be created as a transient experiment. + * @param {Request} req - Request - this is the request object from the HTTP request. + * @returns An experiment object + */ + createExperiment?( data: ExperimentCreateInput, isTransient: boolean, req?: Request, ): Promise<Experiment>; + /** + * Run an experiment and return results (Transient only) + * @param {ExperimentCreateInput} data - ExperimentCreateInput - Data context for the experiment. + * @param {Request} req - Request - this is the request object from the HTTP request. + * @returns ResultUnion + */ + runExperiment?( + data: ExperimentCreateInput, + req?: Request, + ): Promise<Array<typeof ResultUnion>>; + + /** + * Get a list of experiment (limited to 10 per page) + * @param page - the page number + * @param name - the name of the experiment you are looking for + * @param req - Request - this is the request object from the HTTP request. + */ listExperiments?( page: number, name: string, req?: Request, ): Promise<ListExperiments>; - getExperiment(id: string, req?: Request): Promise<Experiment>; + /** + * It takes an experiment id and a request object, and returns a promise of an experiment + * @param {string} id - the id of the experiment you want to get + * @param {Request} req - Request - this is the request object from the HTTP request. + * @returns An experiment object + */ + getExperiment?(id: string, req?: Request): Promise<Experiment>; + /** + * Remove an experiment + * @param id - the id of the experiment you want to remove + * @param req - this is the request object from the user HTTP request + */ removeExperiment?(id: string, req?: Request): Promise<PartialExperiment>; - editExperient?( + /** + * Update an experiment + * @param id - the id of the experiment you want to remove + * @param data - this is the data object containing the updated fields + * @param req - this is the request object from the user HTTP request + */ + editExperiment?( id: string, - expriment: ExperimentEditInput, + data: ExperimentEditInput, req?: Request, ): Promise<Experiment>; + /** + * Retrieve the list of available algorithms + * @param req - Request - this is the request object from the HTTP request. + */ getAlgorithms(req?: Request): Promise<Algorithm[]>; + /** + * Get the current user logged in + * @param req - Request - this is the request object from the HTTP request. + */ getActiveUser?(req?: Request): Promise<User>; + /** + * Update the current logged in user + * @param req - Request - this is the request object from the HTTP request. + * @param userId - the id to update + * @param data - Data object with the updated fields + */ updateUser?( req?: Request, userId?: string, data?: UpdateUserInput, ): Promise<UpdateUserInput | undefined>; + /** + * Perform a logout on the current logged in user + * @param req - Request - this is the request object from the HTTP request. + */ logout?(req?: Request): Promise<void>; /** diff --git a/api/src/engine/engine.module.ts b/api/src/engine/engine.module.ts index efdf729..d636740 100644 --- a/api/src/engine/engine.module.ts +++ b/api/src/engine/engine.module.ts @@ -1,5 +1,11 @@ import { HttpModule, HttpService } from '@nestjs/axios'; -import { DynamicModule, Global, Logger, Module } from '@nestjs/common'; +import { + DynamicModule, + Global, + InternalServerErrorException, + Logger, + Module, +} from '@nestjs/common'; import { ENGINE_MODULE_OPTIONS, ENGINE_SERVICE } from './engine.constants'; import { EngineController } from './engine.controller'; import { IEngineOptions, IEngineService } from './engine.interfaces'; @@ -23,7 +29,7 @@ export class EngineModule { const engineProvider = { provide: ENGINE_SERVICE, useFactory: async (httpService: HttpService) => { - return await this.createEngineConnection( + return this.createEngineConnection( optionsProvider.useValue, httpService, ); @@ -44,14 +50,24 @@ export class EngineModule { opt: IEngineOptions, httpService: HttpService, ): Promise<IEngineService> { - try { - const service = await import(`./connectors/${opt.type}/main.connector`); - const engine = new service.default(opt, httpService); + const service = await import(`./connectors/${opt.type}/main.connector`); + const instance: IEngineService = new service.default(opt, httpService); - return engine; - } catch (e) { - this.logger.error(`There is a problem with the connector '${opt.type}'`); - this.logger.verbose(e); - } + if (instance.createExperiment && instance.runExperiment) + throw new InternalServerErrorException( + `Connector ${opt.type} should declare either createExperiment or runExperiment not both`, + ); + + if ( + instance.createExperiment && + (!instance.getExperiment || + !instance.listExperiments || + !instance.removeExperiment || + !instance.editExperiment) + ) + throw new InternalServerErrorException( + `Connector ${opt.type} has 'createExperiment' implemented it implies that getExperiment, listExperiments, removeExperiment and editExperiment methods must also be implemented.`, + ); + return instance; } } diff --git a/api/src/engine/engine.resolver.ts b/api/src/engine/engine.resolver.ts index d5acde6..78b2abd 100644 --- a/api/src/engine/engine.resolver.ts +++ b/api/src/engine/engine.resolver.ts @@ -1,9 +1,13 @@ import { Inject, UseGuards, UseInterceptors } from '@nestjs/common'; -import { Args, Mutation, Query, Resolver } from '@nestjs/graphql'; +import { ConfigService } from '@nestjs/config'; +import { Args, Query, Resolver } from '@nestjs/graphql'; import { Request } from 'express'; -import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard'; -import { GQLRequest } from '../common/decorators/gql-request.decoractor'; +import { Public } from 'src/auth/decorators/public.decorator'; +import { GlobalAuthGuard } from 'src/auth/guards/global-auth.guard'; import { Md5 } from 'ts-md5'; +import { authConstants } from '../auth/auth-constants'; +import { GQLRequest } from '../common/decorators/gql-request.decoractor'; +import { parseToBoolean } from '../common/utilities'; import { ENGINE_MODULE_OPTIONS, ENGINE_ONTOLOGY_URL, @@ -11,24 +15,13 @@ import { ENGINE_SKIP_TOS, } from './engine.constants'; import { IEngineOptions, IEngineService } from './engine.interfaces'; +import { ErrorsInterceptor } from './interceptors/errors.interceptor'; import { Configuration } from './models/configuration.model'; import { Domain } from './models/domain.model'; import { Algorithm } from './models/experiment/algorithm.model'; -import { - Experiment, - PartialExperiment, -} from './models/experiment/experiment.model'; -import { ExperimentCreateInput } from './models/experiment/input/experiment-create.input'; -import { ExperimentEditInput } from './models/experiment/input/experiment-edit.input'; -import { ListExperiments } from './models/experiment/list-experiments.model'; -import { ConfigService } from '@nestjs/config'; -import { parseToBoolean } from '../common/utilities'; -import { authConstants } from '../auth/auth-constants'; -import { Public } from 'src/auth/decorators/public.decorator'; -import { ErrorsInterceptor } from './interceptors/errors.interceptor'; @UseInterceptors(ErrorsInterceptor) -@UseGuards(JwtAuthGuard) +@UseGuards(GlobalAuthGuard) @Resolver() export class EngineResolver { constructor( @@ -76,53 +69,8 @@ export class EngineResolver { return this.engineService.getDomains(ids, req); } - @Query(() => ListExperiments) - async experimentList( - @Args('page', { nullable: true, defaultValue: 0 }) page: number, - @Args('name', { nullable: true, defaultValue: '' }) name: string, - @GQLRequest() req: Request, - ) { - return this.engineService.listExperiments(page, name, req); - } - - @Query(() => Experiment) - async experiment(@Args('id') id: string, @GQLRequest() req: Request) { - return this.engineService.getExperiment(id, req); - } - @Query(() => [Algorithm]) async algorithms(@GQLRequest() req: Request) { return this.engineService.getAlgorithms(req); } - - @Mutation(() => Experiment) - async createExperiment( - @GQLRequest() req: Request, - @Args('data') experimentCreateInput: ExperimentCreateInput, - @Args('isTransient', { nullable: true, defaultValue: false }) - isTransient: boolean, - ) { - return this.engineService.createExperiment( - experimentCreateInput, - isTransient, - req, - ); - } - - @Mutation(() => Experiment) - async editExperiment( - @GQLRequest() req: Request, - @Args('id') id: string, - @Args('data') experiment: ExperimentEditInput, - ) { - return this.engineService.editExperient(id, experiment, req); - } - - @Mutation(() => PartialExperiment) - async removeExperiment( - @Args('id') id: string, - @GQLRequest() req: Request, - ): Promise<PartialExperiment> { - return this.engineService.removeExperiment(id, req); - } } diff --git a/api/src/engine/models/experiment/experiment.model.ts b/api/src/engine/models/experiment/experiment.model.ts index b0fed2a..bffd7e8 100644 --- a/api/src/engine/models/experiment/experiment.model.ts +++ b/api/src/engine/models/experiment/experiment.model.ts @@ -1,7 +1,25 @@ -import { Field, ObjectType, PartialType } from '@nestjs/graphql'; +import { + Field, + ObjectType, + PartialType, + registerEnumType, +} from '@nestjs/graphql'; +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; import { ResultUnion } from '../result/common/result-union.model'; import { Author } from './author.model'; +export enum ExperimentStatus { + INIT = 'init', + PENDING = 'pending', + SUCCESS = 'success', + WARN = 'warn', + ERROR = 'error', +} + +registerEnumType(ExperimentStatus, { + name: 'ExperimentStatus', +}); + @ObjectType() export class Transformation { @Field({ description: "Variable's id on which to apply the transformation" }) @@ -37,59 +55,82 @@ export class AlgorithmResult { parameters?: ParamValue[]; } +@Entity() @ObjectType() export class Experiment { + @PrimaryGeneratedColumn('uuid') @Field() id: string; + @Column() @Field() name: string; + @Column('jsonb', { nullable: true }) @Field(() => Author, { nullable: true, defaultValue: '' }) author?: Author; + @Column() @Field({ nullable: true }) - createdAt?: number; + createdAt?: string; + @Column({ nullable: true }) @Field({ nullable: true }) - updateAt?: number; + updateAt?: string; + @Column({ nullable: true }) @Field({ nullable: true }) - finishedAt?: number; + finishedAt?: string; + @Column({ nullable: true, default: false }) @Field({ nullable: true, defaultValue: false }) viewed?: boolean; - @Field({ nullable: true }) - status?: string; + @Column({ + type: 'enum', + enum: ExperimentStatus, + default: ExperimentStatus.INIT, + }) + @Field(() => ExperimentStatus, { nullable: true }) + status?: ExperimentStatus; + @Column({ nullable: true, default: false }) @Field({ defaultValue: false }) shared?: boolean; + @Column('jsonb', { nullable: true }) @Field(() => [ResultUnion], { nullable: true, defaultValue: [] }) results?: Array<typeof ResultUnion>; + @Column('text', { array: true }) @Field(() => [String]) datasets: string[]; + @Column({ nullable: true }) @Field(() => String, { nullable: true }) filter?: string; + @Column() @Field() domain: string; + @Column('text', { array: true }) @Field(() => [String]) variables: string[]; + @Column('text', { nullable: true, array: true }) @Field(() => [String], { nullable: true, defaultValue: [] }) coVariables?: string[]; + @Column('text', { nullable: true, array: true }) @Field(() => [String], { nullable: true, defaultValue: [] }) filterVariables?: string[]; + @Column('jsonb', { nullable: true }) @Field(() => Formula, { nullable: true }) formula?: Formula; + @Column('jsonb', { nullable: true }) @Field() algorithm: AlgorithmResult; } diff --git a/api/src/engine/models/experiment/list-experiments.model.ts b/api/src/engine/models/experiment/list-experiments.model.ts index 8f8592e..6d00ac5 100644 --- a/api/src/engine/models/experiment/list-experiments.model.ts +++ b/api/src/engine/models/experiment/list-experiments.model.ts @@ -12,6 +12,6 @@ export class ListExperiments { @Field({ nullable: true }) totalExperiments?: number; - @Field(() => [Experiment]) + @Field(() => [Experiment], { nullable: true, defaultValue: [] }) experiments: Experiment[]; } diff --git a/api/src/experiments/dto/experiment-update.dto.ts b/api/src/experiments/dto/experiment-update.dto.ts new file mode 100644 index 0000000..71e1db0 --- /dev/null +++ b/api/src/experiments/dto/experiment-update.dto.ts @@ -0,0 +1,3 @@ +import { Experiment } from '../../engine/models/experiment/experiment.model'; + +export type ExperimentUpdateDto = Partial<Omit<Experiment, 'id' | 'author'>>; diff --git a/api/src/experiments/experiments.module.ts b/api/src/experiments/experiments.module.ts new file mode 100644 index 0000000..1226957 --- /dev/null +++ b/api/src/experiments/experiments.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { ExperimentsService } from './experiments.service'; +import { ExperimentsResolver } from './experiments.resolver'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { Experiment } from 'src/engine/models/experiment/experiment.model'; + +@Module({ + imports: [TypeOrmModule.forFeature([Experiment])], + providers: [ExperimentsService, ExperimentsResolver], +}) +export class ExperimentsModule {} diff --git a/api/src/experiments/experiments.resolver.spec.ts b/api/src/experiments/experiments.resolver.spec.ts new file mode 100644 index 0000000..0ff1cb0 --- /dev/null +++ b/api/src/experiments/experiments.resolver.spec.ts @@ -0,0 +1,248 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { ExperimentStatus } from '../engine/models/experiment/experiment.model'; +import { User } from '../users/models/user.model'; +import { ENGINE_SERVICE } from '../engine/engine.constants'; +import { IEngineService } from '../engine/engine.interfaces'; +import { ExperimentsResolver } from './experiments.resolver'; +import { ExperimentsService } from './experiments.service'; +import { ExperimentCreateInput } from './models/input/experiment-create.input'; +import { ExperimentEditInput } from './models/input/experiment-edit.input'; + +type MockEngineService = Partial<Record<keyof IEngineService, jest.Mock>>; +type MockExperimentService = Partial< + Record<keyof ExperimentsService, jest.Mock> +>; + +const createEngineService = (): MockEngineService => ({ + getDomains: jest.fn(), + getAlgorithms: jest.fn(), + createExperiment: jest.fn(), + runExperiment: jest.fn(), + getExperiment: jest.fn(), + editExperiment: jest.fn(), + listExperiments: jest.fn(), + removeExperiment: jest.fn(), +}); + +const createExperimentsService = (): MockExperimentService => ({ + findAll: jest.fn(), + findOne: jest.fn(), + dataToExperiment: jest.fn(), + create: jest.fn(), + update: jest.fn(), + remove: jest.fn(), +}); + +describe('ExperimentsResolver', () => { + let resolver: ExperimentsResolver; + let engineService: MockEngineService; + let experimentsService: MockExperimentService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + ExperimentsResolver, + { provide: ExperimentsService, useValue: createExperimentsService() }, + { + provide: ENGINE_SERVICE, + useValue: createEngineService(), + }, + ], + }).compile(); + + engineService = module.get<MockEngineService>(ENGINE_SERVICE); + experimentsService = module.get<ExperimentsService>( + ExperimentsService, + ) as unknown as MockExperimentService; + resolver = module.get<ExperimentsResolver>(ExperimentsResolver); + }); + + it('should be defined', () => { + expect(resolver).toBeDefined(); + }); + + describe('experimentList', () => { + describe('when engine method exist', () => { + it('should call engine method', async () => { + const request: any = jest.fn(); + engineService.listExperiments.mockReturnValue({}); + await resolver.experimentList(0, '', request); + + expect(engineService.listExperiments.mock.calls.length).toBeGreaterThan( + 0, + ); + }); + }); + + describe('when engine method does not exist', () => { + it('should call service method', async () => { + const request: any = jest.fn(); + engineService.listExperiments = undefined; + experimentsService.findAll.mockReturnValue([[], 9]); + await resolver.experimentList(0, '', request); + + expect(experimentsService.findAll.mock.calls.length).toBeGreaterThan(0); + }); + }); + }); + + describe('experiment', () => { + describe('when engine method exist', () => { + it('should call engine method', async () => { + const request: any = jest.fn(); + const user: User = { + id: 'dummyUser', + username: 'test', + }; + await resolver.experiment('test', request, user); + + expect(experimentsService.findOne.mock.calls.length).toBe(0); + expect(engineService.getExperiment.mock.calls.length).toBeGreaterThan( + 0, + ); + }); + }); + + describe('when engine method does not exist', () => { + it('should call service method', async () => { + const request: any = jest.fn(); + const user: User = { + id: 'dummyUser', + username: 'test', + }; + engineService.getExperiment = undefined; + await resolver.experiment('test', request, user); + + expect(experimentsService.findOne.mock.calls.length).toBeGreaterThan(0); + }); + }); + }); + + describe('createExperiment', () => { + describe('when engine method exist', () => { + it('should call engine method', async () => { + const request: any = jest.fn(); + const data: ExperimentCreateInput = + {} as unknown as ExperimentCreateInput; + const user: User = { + id: 'dummyUser', + username: 'test', + }; + await resolver.createExperiment(request, user, data, true); + + expect(experimentsService.create.mock.calls.length).toBe(0); + expect( + engineService.createExperiment.mock.calls.length, + ).toBeGreaterThan(0); + }); + }); + + describe('when engine method does not exist', () => { + it('should call service method', async () => { + const request: any = jest.fn(); + const data: ExperimentCreateInput = + {} as unknown as ExperimentCreateInput; + const user: User = { + id: 'dummyUser', + username: 'test', + }; + engineService.createExperiment = undefined; + engineService.runExperiment.mockResolvedValue([]); + experimentsService.create.mockReturnValue({ id: 'test' }); + await resolver.createExperiment(request, user, data, false); + + expect(experimentsService.create.mock.calls.length).toBeGreaterThan(0); + }); + + it('should only call runExperiment if transient', async () => { + const request: any = jest.fn(); + const data: ExperimentCreateInput = + {} as unknown as ExperimentCreateInput; + const user: User = { + id: 'dummyUser', + username: 'test', + }; + engineService.createExperiment = undefined; + engineService.runExperiment.mockResolvedValue([]); + experimentsService.create.mockReturnValue({ id: 'test' }); + const result = await resolver.createExperiment( + request, + user, + data, + true, + ); + + expect(engineService.runExperiment.mock.calls.length).toBeGreaterThan( + 0, + ); + expect(result.status).toBe(ExperimentStatus.SUCCESS); + }); + }); + }); + + describe('editExperiment', () => { + describe('when engine method exist', () => { + it('should call engine method', async () => { + const request: any = jest.fn(); + const data: ExperimentEditInput = {} as unknown as ExperimentEditInput; + const user: User = { + id: 'dummyUser', + username: 'test', + }; + await resolver.editExperiment(request, 'test', data, user); + + expect(experimentsService.update.mock.calls.length).toBe(0); + expect(engineService.editExperiment.mock.calls.length).toBeGreaterThan( + 0, + ); + }); + }); + + describe('when engine method does not exist', () => { + it('should call service method', async () => { + const request: any = jest.fn(); + const data: ExperimentEditInput = {} as unknown as ExperimentEditInput; + const user: User = { + id: 'dummyUser', + username: 'test', + }; + engineService.editExperiment = undefined; + await resolver.editExperiment(request, 'test', data, user); + + expect(experimentsService.update.mock.calls.length).toBeGreaterThan(0); + }); + }); + }); + + describe('removeExperiment', () => { + describe('when engine method exist', () => { + it('should call engine method', async () => { + const request: any = jest.fn(); + const user: User = { + id: 'dummyUser', + username: 'test', + }; + await resolver.removeExperiment('test', request, user); + + expect(experimentsService.remove.mock.calls.length).toBe(0); + expect( + engineService.removeExperiment.mock.calls.length, + ).toBeGreaterThan(0); + }); + }); + + describe('when engine method does not exist', () => { + it('should call service method', async () => { + const request: any = jest.fn(); + const user: User = { + id: 'dummyUser', + username: 'test', + }; + engineService.removeExperiment = undefined; + await resolver.removeExperiment('test', request, user); + + expect(experimentsService.remove.mock.calls.length).toBeGreaterThan(0); + }); + }); + }); +}); diff --git a/api/src/experiments/experiments.resolver.ts b/api/src/experiments/experiments.resolver.ts new file mode 100644 index 0000000..754761e --- /dev/null +++ b/api/src/experiments/experiments.resolver.ts @@ -0,0 +1,137 @@ +import { Inject, Logger, UseGuards } from '@nestjs/common'; +import { Args, Mutation, Query, Resolver } from '@nestjs/graphql'; +import { Request } from 'express'; +import { GlobalAuthGuard } from '../auth/guards/global-auth.guard'; +import { GQLRequest } from '../common/decorators/gql-request.decoractor'; +import { CurrentUser } from '../common/decorators/user.decorator'; +import { ENGINE_SERVICE } from '../engine/engine.constants'; +import { IEngineService } from '../engine/engine.interfaces'; +import { + Experiment, + ExperimentStatus, + PartialExperiment, +} from '../engine/models/experiment/experiment.model'; +import { ListExperiments } from '../engine/models/experiment/list-experiments.model'; +import { User } from '../users/models/user.model'; +import { ExperimentsService } from './experiments.service'; +import { ExperimentCreateInput } from './models/input/experiment-create.input'; +import { ExperimentEditInput } from './models/input/experiment-edit.input'; + +const LIMIT_EXP_BY_PAGE = 10; // TODO Consider refactoring to allow offset and limit in API call + +@UseGuards(GlobalAuthGuard) +@Resolver() +export class ExperimentsResolver { + private readonly logger = new Logger(ExperimentsResolver.name); + + constructor( + @Inject(ENGINE_SERVICE) private readonly engineService: IEngineService, + private readonly experimentService: ExperimentsService, + ) {} + + @Query(() => ListExperiments) + async experimentList( + @Args('page', { nullable: true, defaultValue: 0 }) page: number, + @Args('name', { nullable: true, defaultValue: '' }) name: string, + @GQLRequest() req: Request, + ): Promise<ListExperiments> { + if (this.engineService.listExperiments) { + return this.engineService.listExperiments(page, name, req); + } + + const [results, total] = await this.experimentService.findAll( + { + limit: LIMIT_EXP_BY_PAGE, + offset: LIMIT_EXP_BY_PAGE * page, + }, + name, + ); + return { + experiments: results, + currentPage: page, + totalExperiments: total, + totalPages: Math.ceil(total / LIMIT_EXP_BY_PAGE), + }; + } + + @Query(() => Experiment) + async experiment( + @Args('id') id: string, + @GQLRequest() req: Request, + @CurrentUser() user: User, + ) { + if (this.engineService.getExperiment) + return this.engineService.getExperiment(id, req); + + return this.experimentService.findOne(id, user); + } + + @Mutation(() => Experiment) + async createExperiment( + @GQLRequest() req: Request, + @CurrentUser() user: User, + @Args('data') data: ExperimentCreateInput, + @Args('isTransient', { nullable: true, defaultValue: false }) + isTransient: boolean, + ) { + if (this.engineService.createExperiment) { + return this.engineService.createExperiment(data, isTransient, req); + } + + //if the experiment is transient we wait a response before returning a response + if (isTransient) { + const results = await this.engineService.runExperiment(data, req); + const expTransient = this.experimentService.dataToExperiment(data, user); + return { ...expTransient, results, status: ExperimentStatus.SUCCESS }; + } + + //if not we will create an experiment in local db + const experiment = await this.experimentService.create( + data, + user, + ExperimentStatus.PENDING, + ); + + //create an async query that will update the result when it's done + this.engineService.runExperiment(data, req).then((results) => { + this.experimentService.update( + experiment.id, + { + results, + finishedAt: new Date().toISOString(), + status: ExperimentStatus.SUCCESS, + }, + user, + ); + }); + + //we return the experiment before finishing the runExperiment + return experiment; + } + + @Mutation(() => Experiment) + async editExperiment( + @GQLRequest() req: Request, + @Args('id') id: string, + @Args('data') experiment: ExperimentEditInput, + @CurrentUser() user: User, + ) { + console.log(this.engineService.editExperiment); + if (this.engineService.editExperiment) + return this.engineService.editExperiment(id, experiment, req); + + return this.experimentService.update(id, experiment, user); + } + + @Mutation(() => PartialExperiment) + async removeExperiment( + @Args('id') id: string, + @GQLRequest() req: Request, + @CurrentUser() user: User, + ): Promise<PartialExperiment> { + if (this.engineService.removeExperiment) + return this.engineService.removeExperiment(id, req); + + return this.experimentService.remove(id, user); + } +} diff --git a/api/src/experiments/experiments.service.spec.ts b/api/src/experiments/experiments.service.spec.ts new file mode 100644 index 0000000..4ff6cbf --- /dev/null +++ b/api/src/experiments/experiments.service.spec.ts @@ -0,0 +1,184 @@ +import { ForbiddenException, NotFoundException } from '@nestjs/common'; +import { Test, TestingModule } from '@nestjs/testing'; +import { getRepositoryToken } from '@nestjs/typeorm'; +import { User } from 'src/users/models/user.model'; +import { Repository } from 'typeorm'; +import { Experiment } from '../engine/models/experiment/experiment.model'; +import { ExperimentsService } from './experiments.service'; +import { ExperimentCreateInput } from './models/input/experiment-create.input'; + +type MockRepository<T = any> = Partial<Record<keyof Repository<T>, jest.Mock>>; +const createMockRepository = <T = any>(): MockRepository<T> => ({ + findOne: jest.fn(), + findAndCount: jest.fn(), + create: jest.fn(), + save: jest.fn(), + remove: jest.fn(), + update: jest.fn(), +}); + +describe('ExperimentsService', () => { + let service: ExperimentsService; + let experimentRepository: MockRepository; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + ExperimentsService, + { + provide: getRepositoryToken(Experiment), + useValue: createMockRepository(), + }, + ], + }).compile(); + + service = module.get<ExperimentsService>(ExperimentsService); + experimentRepository = module.get<MockRepository>( + getRepositoryToken(Experiment), + ); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); + + describe('findOne', () => { + describe('when experiment with ID exists', () => { + it('should return the experiment object', async () => { + const experimentId = '1'; + const expectedExperiment = {}; + + experimentRepository.findOne.mockReturnValue(expectedExperiment); + const experiment = await service.findOne(experimentId); + expect(experiment).toEqual(expectedExperiment); + }); + it('should throw an error if user does not match ', async () => { + const experimentId = '1'; + const expectedExperiment = { + author: { + username: 'differentUsername', + }, + }; + const user: User = { + username: 'test', + id: 'dummyid', + }; + + experimentRepository.findOne.mockReturnValue(expectedExperiment); + + try { + await service.findOne(experimentId, user); + } catch (err) { + expect(err).toBeInstanceOf(ForbiddenException); + } + }); + }); + describe('otherwise', () => { + it('should throw the "NotFoundException"', async () => { + const experimentId = '1'; + experimentRepository.findOne.mockReturnValue(undefined); + + try { + await service.findOne(experimentId); + } catch (err) { + expect(err).toBeInstanceOf(NotFoundException); + expect(err.message).toEqual(`Experiment #${experimentId} not found`); + } + }); + }); + }); + + describe('findAll', () => { + describe('Should return a list of experiments', () => { + it('should return the experiment object', async () => { + const excpectedList = [{}, {}, {}]; + const expectedOutput = [excpectedList, excpectedList.length]; + + experimentRepository.findAndCount.mockReturnValue(expectedOutput); + const [experimentList, count] = await service.findAll(); + expect(count).toEqual(excpectedList.length); + expect(experimentList).toStrictEqual(excpectedList); + }); + }); + }); + + describe('create', () => { + it('Should return an experiment', async () => { + const data: ExperimentCreateInput = { + domain: 'dummyDomain', + variables: [], + algorithm: { + id: 'dummyAlgo', + parameters: [], + }, + datasets: ['dummyDataset'], + filter: '', + name: 'dummyExperiment', + }; + const user: User = { + id: 'userId', + username: 'username', + }; + + const expectedExperiment: Experiment = { + ...data, + author: { + fullname: user.username, + username: user.username, + }, + algorithm: { + name: data.algorithm.id, + parameters: [], + }, + id: 'dummyid', + }; + + experimentRepository.create.mockReturnValue(expectedExperiment); + const experiment = await experimentRepository.create(data, user); + + expect(experiment).toStrictEqual(expectedExperiment); + }); + }); + + describe('update', () => { + it('should return updated experiment', async () => { + const user: User = { + id: 'userId', + username: 'username', + }; + const expectedExperiment: Experiment = { + domain: 'dummyDomain', + variables: [], + datasets: ['dummyDataset'], + filter: '', + name: 'dummyExperiment', + author: { + fullname: user.username, + username: user.username, + }, + algorithm: { + name: 'dummyAlgo', + parameters: [], + }, + id: 'dummyid', + }; + + const updateData = { name: 'test' }; + + experimentRepository.findOne.mockReturnValue({ + author: { + ...user, + }, + }); + experimentRepository.save.mockReturnValue(expectedExperiment); + + const experiment = await service.update( + expectedExperiment.id, + updateData, + user, + ); + + expect(experiment).toStrictEqual(expectedExperiment); + }); + }); +}); diff --git a/api/src/experiments/experiments.service.ts b/api/src/experiments/experiments.service.ts new file mode 100644 index 0000000..e452228 --- /dev/null +++ b/api/src/experiments/experiments.service.ts @@ -0,0 +1,145 @@ +import { + ForbiddenException, + Injectable, + NotFoundException, +} from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { + Experiment, + ExperimentStatus, +} from '../engine/models/experiment/experiment.model'; +import { User } from 'src/users/models/user.model'; +import { FindManyOptions, Like, Repository } from 'typeorm'; +import { ExperimentCreateInput } from './models/input/experiment-create.input'; +import { PaginationArgsInput } from './models/input/pagination-args.input'; +import { ExperimentUpdateDto } from './dto/experiment-update.dto'; + +@Injectable() +export class ExperimentsService { + constructor( + @InjectRepository(Experiment) + private readonly experimentRepository: Repository<Experiment>, + ) {} + + /** + * It takes in a pagination object and a name, and returns a promise that resolves to an array of + * experiments and the total number of experiments + * @param {PaginationArgsInput} pagination - PaginationArgsInput = {} + * @param [name] - The name of the experiment. + * @returns An array of experiments and the total count of experiments. + */ + async findAll(pagination: PaginationArgsInput = {}, name = '') { + const options: FindManyOptions<Experiment> = {}; + + if (name && name != '') { + options.where = { name: Like(`%${name}%`) }; + } + options.order = { + createdAt: 'DESC', + }; + options.skip = pagination.offset ?? 0; + options.take = pagination.limit ?? 10; + + return this.experimentRepository.findAndCount(options); + } + + /** + * It finds an experiment by its id, if the experiment does not exist throws a NotFoundException + * @param {string} id - string - the id of the experiment we want to find + * @returns The experiment object + */ + async findOne(id: string): Promise<Experiment>; + + /** + * It finds an experiment by its id, and if the user is not the author of the experiment, it throws a + * ForbiddenException + * @param {string} id - string - the id of the experiment we want to find + * @param {User} [user] - User - the user that is making the request + * @returns The experiment object + */ + async findOne(id: string, user: User): Promise<Experiment>; + async findOne(id: string, user?: User): Promise<Experiment> { + const experiment = await this.experimentRepository.findOne(id); + + if (!experiment) throw new NotFoundException(`Experiment #${id} not found`); + + if (user && experiment.author.username !== user.username) + throw new ForbiddenException( + `Experiment #${id} is not available for user ${user.username}`, + ); + + return experiment; + } + + dataToExperiment( + data: ExperimentCreateInput, + user: User, + status?: ExperimentStatus, + ): Partial<Experiment> { + return { + ...data, + status, + author: { + username: user.username, + fullname: user.fullname ?? user.username, + }, + createdAt: new Date().toISOString(), + algorithm: { + name: data.algorithm.id, + parameters: data.algorithm.parameters.map((p) => ({ + name: p.id, + value: p.value, + })), + }, + }; + } + + /** + * It creates a new experiment and saves it to the database + * @param {ExperimentCreateInput} data - ExperimentCreateInput + * @param {User} user - User - This is the user that is currently logged in. + * @returns The experiment that was created. + */ + create( + data: ExperimentCreateInput, + user: User, + status = ExperimentStatus.INIT, + ): Promise<Experiment> { + const experiment = this.experimentRepository.create( + this.dataToExperiment(data, user, status), + ); + + return this.experimentRepository.save(experiment); + } + + /** + * It finds an experiment by id and user, then updates it with the new data + * @param {string} id - The id of the experiment to update + * @param {ExperimentUpdateDto} data - ExperimentUpdateDto + * @param {User} user - User - this is the user that is currently logged in. + * @returns The updated experiment + */ + async update(id: string, data: ExperimentUpdateDto, user: User) { + const experiment = await this.findOne(id, user); + + return this.experimentRepository.save({ + ...experiment, + ...data, + id, + updateAt: new Date().toISOString(), + }); + } + + /** + * Find an experiment by id, then remove it. + * + * @param {string} id - The id of the experiment to be deleted. + * @param {User} user - User - This is the user that is currently logged in. + * @returns The experiment that was removed. + */ + async remove(id: string, user: User): Promise<Experiment> { + const experiment = await this.findOne(id, user); + + return this.experimentRepository.remove(experiment); + } +} diff --git a/api/src/engine/models/experiment/input/algorithm-parameter.input.ts b/api/src/experiments/models/input/algorithm-parameter.input.ts similarity index 100% rename from api/src/engine/models/experiment/input/algorithm-parameter.input.ts rename to api/src/experiments/models/input/algorithm-parameter.input.ts diff --git a/api/src/engine/models/experiment/input/algorithm.input.ts b/api/src/experiments/models/input/algorithm.input.ts similarity index 100% rename from api/src/engine/models/experiment/input/algorithm.input.ts rename to api/src/experiments/models/input/algorithm.input.ts diff --git a/api/src/engine/models/experiment/input/experiment-create.input.ts b/api/src/experiments/models/input/experiment-create.input.ts similarity index 100% rename from api/src/engine/models/experiment/input/experiment-create.input.ts rename to api/src/experiments/models/input/experiment-create.input.ts diff --git a/api/src/engine/models/experiment/input/experiment-edit.input.ts b/api/src/experiments/models/input/experiment-edit.input.ts similarity index 100% rename from api/src/engine/models/experiment/input/experiment-edit.input.ts rename to api/src/experiments/models/input/experiment-edit.input.ts diff --git a/api/src/experiments/models/input/pagination-args.input.ts b/api/src/experiments/models/input/pagination-args.input.ts new file mode 100644 index 0000000..541b089 --- /dev/null +++ b/api/src/experiments/models/input/pagination-args.input.ts @@ -0,0 +1,10 @@ +import { Field, InputType } from '@nestjs/graphql'; + +@InputType() +export class PaginationArgsInput { + @Field() + limit?: number; + + @Field() + offset?: number; +} diff --git a/api/src/main/app.module.ts b/api/src/main/app.module.ts index ebc1be9..dd03343 100644 --- a/api/src/main/app.module.ts +++ b/api/src/main/app.module.ts @@ -9,6 +9,7 @@ import { AuthModule } from 'src/auth/auth.module'; import dbConfig from 'src/config/db.config'; import matomoConfig from 'src/config/matomo.config'; import { EngineModule } from 'src/engine/engine.module'; +import { ExperimentsModule } from 'src/experiments/experiments.module'; import { FilesModule } from 'src/files/files.module'; import { UsersModule } from 'src/users/users.module'; import { AppController } from './app.controller'; @@ -59,6 +60,7 @@ import { AppService } from './app.service'; }), AuthModule, UsersModule, + ExperimentsModule, FilesModule, ], controllers: [AppController], diff --git a/api/src/migrations/1653484967792-CreateExperimentDB.ts b/api/src/migrations/1653484967792-CreateExperimentDB.ts new file mode 100644 index 0000000..12a28c1 --- /dev/null +++ b/api/src/migrations/1653484967792-CreateExperimentDB.ts @@ -0,0 +1,19 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class CreateExperimentDB1653484967792 implements MigrationInterface { + name = 'CreateExperimentDB1653484967792'; + + public async up(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query( + `CREATE TYPE "public"."experiment_status_enum" AS ENUM('init', 'pending', 'success', 'warn', 'error')`, + ); + await queryRunner.query( + `CREATE TABLE "experiment" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "name" character varying NOT NULL, "author" jsonb, "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updateAt" TIMESTAMP NOT NULL DEFAULT now(), "finishedAt" character varying, "viewed" boolean DEFAULT false, "status" "public"."experiment_status_enum" NOT NULL DEFAULT 'init', "shared" boolean DEFAULT false, "results" jsonb, "datasets" text array NOT NULL, "filter" character varying, "domain" character varying NOT NULL, "variables" text array NOT NULL, "coVariables" text array, "filterVariables" text array, "formula" jsonb, "algorithm" jsonb, CONSTRAINT "PK_4f6eec215c62eec1e0fde987caf" PRIMARY KEY ("id"))`, + ); + } + + public async down(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(`DROP TABLE "experiment"`); + await queryRunner.query(`DROP TYPE "public"."experiment_status_enum"`); + } +} diff --git a/api/src/migrations/1653487335545-ExperimentDateTypeUpdate.ts b/api/src/migrations/1653487335545-ExperimentDateTypeUpdate.ts new file mode 100644 index 0000000..0ad01fb --- /dev/null +++ b/api/src/migrations/1653487335545-ExperimentDateTypeUpdate.ts @@ -0,0 +1,29 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class ExperimentDateTypeUpdate1653487335545 + implements MigrationInterface +{ + name = 'ExperimentDateTypeUpdate1653487335545'; + + public async up(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(`ALTER TABLE "experiment" DROP COLUMN "createdAt"`); + await queryRunner.query( + `ALTER TABLE "experiment" ADD "createdAt" character varying NOT NULL`, + ); + await queryRunner.query(`ALTER TABLE "experiment" DROP COLUMN "updateAt"`); + await queryRunner.query( + `ALTER TABLE "experiment" ADD "updateAt" character varying`, + ); + } + + public async down(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(`ALTER TABLE "experiment" DROP COLUMN "updateAt"`); + await queryRunner.query( + `ALTER TABLE "experiment" ADD "updateAt" TIMESTAMP NOT NULL DEFAULT now()`, + ); + await queryRunner.query(`ALTER TABLE "experiment" DROP COLUMN "createdAt"`); + await queryRunner.query( + `ALTER TABLE "experiment" ADD "createdAt" TIMESTAMP NOT NULL DEFAULT now()`, + ); + } +} diff --git a/api/src/schema.gql b/api/src/schema.gql index 61f22d7..bacb2ed 100644 --- a/api/src/schema.gql +++ b/api/src/schema.gql @@ -301,11 +301,11 @@ type Experiment { id: String! name: String! author: Author - createdAt: Float - updateAt: Float - finishedAt: Float + createdAt: String + updateAt: String + finishedAt: String viewed: Boolean - status: String + status: ExperimentStatus shared: Boolean! results: [ResultUnion!] datasets: [String!]! @@ -318,15 +318,23 @@ type Experiment { algorithm: AlgorithmResult! } +enum ExperimentStatus { + INIT + PENDING + SUCCESS + WARN + ERROR +} + type PartialExperiment { id: String name: String author: Author - createdAt: Float - updateAt: Float - finishedAt: Float + createdAt: String + updateAt: String + finishedAt: String viewed: Boolean - status: String + status: ExperimentStatus shared: Boolean results: [ResultUnion!] datasets: [String!] @@ -343,25 +351,34 @@ type ListExperiments { currentPage: Float totalPages: Float totalExperiments: Float - experiments: [Experiment!]! + experiments: [Experiment!] } type Query { configuration: Configuration! domains(ids: [String!] = []): [Domain!]! - experimentList(page: Float = 0, name: String = ""): ListExperiments! - experiment(id: String!): Experiment! algorithms: [Algorithm!]! user: User! + experimentList(page: Float = 0, name: String = ""): ListExperiments! + experiment(id: String!): Experiment! } type Mutation { - createExperiment(data: ExperimentCreateInput!, isTransient: Boolean = false): Experiment! - editExperiment(id: String!, data: ExperimentEditInput!): Experiment! - removeExperiment(id: String!): PartialExperiment! login(variables: AuthenticationInput!): AuthenticationOutput! logout: Boolean! updateUser(updateUserInput: UpdateUserInput!): User! + createExperiment(data: ExperimentCreateInput!, isTransient: Boolean = false): Experiment! + editExperiment(id: String!, data: ExperimentEditInput!): Experiment! + removeExperiment(id: String!): PartialExperiment! +} + +input AuthenticationInput { + username: String! + password: String! +} + +input UpdateUserInput { + agreeNDA: Boolean! } input ExperimentCreateInput { @@ -397,12 +414,3 @@ input ExperimentEditInput { shared: Boolean viewed: Boolean } - -input AuthenticationInput { - username: String! - password: String! -} - -input UpdateUserInput { - agreeNDA: Boolean! -} diff --git a/api/src/users/users.resolver.ts b/api/src/users/users.resolver.ts index ad1fcd0..494f842 100644 --- a/api/src/users/users.resolver.ts +++ b/api/src/users/users.resolver.ts @@ -7,15 +7,15 @@ import { import { Args, Mutation, Query, Resolver } from '@nestjs/graphql'; import { ENGINE_SERVICE } from '../engine/engine.constants'; import { IEngineService } from '../engine/engine.interfaces'; -import { CurrentUser } from '../auth/decorators/user.decorator'; -import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard'; import { UpdateUserInput } from './inputs/update-user.input'; import { User } from './models/user.model'; import { UsersService } from './users.service'; import { GQLRequest } from '../common/decorators/gql-request.decoractor'; import { Request } from 'express'; +import { CurrentUser } from '../common/decorators/user.decorator'; +import { GlobalAuthGuard } from '../auth/guards/global-auth.guard'; -@UseGuards(JwtAuthGuard) +@UseGuards(GlobalAuthGuard) @Resolver() export class UsersResolver { private readonly logger = new Logger(UsersResolver.name); @@ -87,6 +87,6 @@ export class UsersResolver { if (updateData && Object.keys(updateData).length > 0) await this.usersService.update(user.id, updateData); - return await this.getUser(request, user); + return this.getUser(request, user); } } diff --git a/api/src/users/users.service.ts b/api/src/users/users.service.ts index d74ca14..bd8105d 100644 --- a/api/src/users/users.service.ts +++ b/api/src/users/users.service.ts @@ -38,6 +38,6 @@ export class UsersService { ...data, }; - return await this.userRepository.save(updateData); + return this.userRepository.save(updateData); } } -- GitLab