diff --git a/api/ormconfig.ts b/api/ormconfig.ts index 27afa05b4dda41d61eeae90c870720df0fd9fa92..c4d8d9f6ab4d251e3de02e035a54e01ca6d2cf26 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 a7e5df0f9910ef00e719ef633aafdd5eda4d332d..39f229d231389f7912f2ead8aa189b0fb27a9b3e 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 7e11e6620c4fd9c8cafe1ea3c5d93e90ee7d091d..1e71e563cc9c3fa4fd9c0f1b13328690e937541c 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 805f0dcba28a75eee88f5deaff33cc6ff5c31ad5..936e09cab00920b1f9d4494200b10cce62fb8b06 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 c109dbe8712230015317033214cd8f2870350c7e..e545b55cb221ba157e2f5afcf44f86ac6b7fa7c1 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 cfa8e714c43004d503eaf016e2f00a11503990d7..d01434af79f15ea82ac7168abc5f1b1b7c64e31d 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 18eaadfa1d35fbae3b6ca306b057e17ae02a225d..402dfc63663a790d20462090114550ace92095b0 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 0aa940d9371596e9902e9acf5b8c0dcee1307d2f..0000000000000000000000000000000000000000 --- 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 2ee1a63de580474c54270024e570cc9570788b1d..ecfd65e01ac3ae1a09b452ca74ff86b62cc27b7d 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 0000000000000000000000000000000000000000..f98cedecbbdbb0a2b5f05a426a5a15a0172b713a --- /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 0aa940d9371596e9902e9acf5b8c0dcee1307d2f..8db357e2c16742b69c5fe31e221a909fd39df3cd 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 79c592b3f882ebf19e12df8e687990a72431ef28..ebb2ee15b229eea60e6e75c3f70160f6a3fae249 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 f796f3c5a0427b895bdb56ab848eea74a2076983..153ceac93cb2968d1da019977b2b147345d3cd1b 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 73ac0f419c65e6ba459dfdadcd7d3a5a9c2bd6a3..a1c28d5727aa9e2b082b1abbf072a5eb7dd4b75e 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 2ad1c79e8384dfb1a12fe8dbe75511720dfe4e1f..6746a8f2b736a9e9668504e98f5b54aa83563cd9 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 8a9d84b638329ee3d79c0a4d0267300127774860..9420070b6047d25c3e578d6c477a33836f47e03b 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 8770e901a69a2cfbb5575f079e2d7c65bcd32b0f..7b4d577e146933477f7eb5507da49860f2a03463 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 447b8f457dafe655ee12d7d518bcf8b0fc7d37c7..2e696f066825ade2f7ba74e6fe3d091f8a77d4ca 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 f8b0085449c7a0229044d66cd73b09a727149ce1..550e75c2da6abb999d06b88a72d068d548d3b0e0 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 5808b159dae93067640e66ade63f82a15ab3debb..12737e18d39cf0bfd21dbd5828b36ea687627cbd 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 4ef963a6ca5ab93cbe07bf0ff422e40a94744413..6a34e183b7127d893019b7950c30228965feb417 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 57054721ff0675b01d5865ca312bcaa5335e143f..b754a068478c679d04158f4776d14ae041063fd0 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 b0766164343410b79c98588100c3a43d037b4df1..2defac942a734cf22d9bd2c5442d5d13012f2ad5 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 4ed7f222182726313aaaafde67af362e4f748cc1..e50a0ad83a18a9dadd2025daa2a06db7abe39b67 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 abc045cd32efb98cd61908c77bd5a122719d7a41..50bc9e66a37b0ed4d66c375690df53890d8a74e5 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 7604dd93bd27b021f0437fd5c87304135b80957d..8eba2965e0422dc52ab85785fb8841225d9b8dd0 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 024e41227e8642e6896882d09213b02b7510ec25..cefa59cb015af49116d53836e23541e393fbff9f 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 e8361797cd5805a1f84e4e8601d110e8ef344a9e..3f954f584b4397cf2d0c1e1a40904cde758314bb 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 79f65b81638c405455cdcf2288ce4fd46a6b3a59..36f78cfd0b876c1e2f42a22775d3fa2a634e0244 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 e1617dd43649650676c7eb41f7c3227158a1bec2..8bc3f92e4d1bbecdade74e1e71f200851ec37aba 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 2dee1858887a161e31f114f0343660847b676215..208516e44e45e53dabce70264c1593981850dc64 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 a08e6fb3c9a4ce8332e09fc0a4b02b7cf89f2df7..c9cf5af84eaa172671cf173be42e48c156498f9a 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 dd2b971e8dcb5668faa917f1b1886efb1104359b..45d2220683082f9c67656d4751f2990fc74e52d8 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 7b616e3266ed5ad805c74297c512c22f4c20c734..021723c69dad30ec292b656d128be0bfc34598a1 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 8ecf464f696b5e3e719e3817586b4570bfdfecdd..4a91590e13e2bf015107b2e3bfdb001358db1b57 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 600fcc9824046a54a017b76ca27033a0b15912de..ef95f6049d396ec95d6ac4d81269ccad7d0b16b3 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 8ca07651bcdc9883190d8ba7365546002c08ba1a..d9ef0e69706e3cf771dd0cd751b85546b1db5ade 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 efdf729c1ea27eb85028aee5d0f5c4b597ed6486..d636740a3a39e2e5901c96d60690ef365ac00b3c 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 d5acde6a200cda20f7105e610713c3cf407fac79..78b2abd48568732644e6e5dfe45dd32715987e6a 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 b0fed2aa4bd3649d7f2c6f7bba4ce8601364b1c6..bffd7e8f9743716299aec5a22e4da335bdf10c60 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 8f8592e52f1e3d85946d373a73b4edd00ac318f5..6d00ac535b57122ed782505fbd7d17b36423da65 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 0000000000000000000000000000000000000000..71e1db046b61b772876a8622bd5ba71f6b26530c --- /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 0000000000000000000000000000000000000000..122695790d795ca961caed42f14515aecc9b0641 --- /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 0000000000000000000000000000000000000000..0ff1cb091669713156af236d4b504706a29074a1 --- /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 0000000000000000000000000000000000000000..754761e0d8e733816f3d79ef81d7e4e99b66d6ef --- /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 0000000000000000000000000000000000000000..4ff6cbf202ce6248f3cee88498929bcf4d4bfe6a --- /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 0000000000000000000000000000000000000000..e452228979667e98903967f5c09d181c91a0173d --- /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 0000000000000000000000000000000000000000..541b089ff8080a78ccd7c89b06bac9997da2e5f7 --- /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 ebc1be97cb1be3d02a48c3ec4d561280df249b03..dd03343d458c661bc3aacd67615c17bcda10beab 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 0000000000000000000000000000000000000000..12a28c10f0df12c5fd9c091a76d60a2b80826ae6 --- /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 0000000000000000000000000000000000000000..0ad01fbe7b20792658b7a1cf84b95450cdf28f08 --- /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 61f22d7158f99c268aff1a71e37da107c692935a..bacb2edfe2278a64df352541366d29daab10b7a9 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 ad1fcd0e750bf635ab72bbd9c10a660e77658c18..494f8429aff546482b2ec7d7c4dc7b5f504d1ab6 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 d74ca14ce1d5096305d790d189eb6666b032733b..bd8105d0de4a4d1c215441563c57a3f871bd205d 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); } }