diff --git a/api/.env.defaults b/api/.env.defaults index 0320f1402f9ae2e2f1cfa4380a4445073470dece..8fb46c2daed05d83125ffa7a0d2cb1de23c364cc 100644 --- a/api/.env.defaults +++ b/api/.env.defaults @@ -10,4 +10,11 @@ AUTH_SKIP=false AUTH_JWT_SECRET=SecretForDevPurposeOnly AUTH_JWT_TOKEN_EXPIRES_IN=2d AUTH_COOKIE_SAME_SITE=strict -AUTH_COOKIE_SECURE=true \ No newline at end of file +AUTH_COOKIE_SECURE=true + +#DB +DB_HOST=localhost +DB_PORT=5454 +DB_USERNAME=postgres +DB_PASSWORD=pass123 +DB_NAME=postgres diff --git a/api/docker-compose.yml b/api/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..dee08d3392e474fc1124072cc287d6c2f342d72d --- /dev/null +++ b/api/docker-compose.yml @@ -0,0 +1,43 @@ +services: + db: + image: postgres + restart: always + ports: + - "5454:5432" + environment: + POSTGRES_PASSWORD: pass123 + volumes: + - db-volume:/var/lib/postgres + networks: + - back + + # dev: + # container_name: backend-dev + # build: + # context: . + # target: development + # dockerfile: ./dockerfile + # volumes: + # - .:/usr/src/app + # - /usr/src/app/node_modules + # links: + # - db + # ports: + # - ${BE_PORT}:${BE_PORT} + # expose: + # - 9229 + # command: npm run start:dev + # env_file: + # - .env.defaults + # - .env + # networks: + # - back + # depends_on: + # - db + # environment: + # - DB_URL=mongodb://mongo/backdev + +networks: + back: +volumes: + db-volume: \ No newline at end of file diff --git a/api/package-lock.json b/api/package-lock.json index 8eef2433f6b4fe6ff3de3b3ffa3bfe2939bb1d0a..a7e5df0f9910ef00e719ef633aafdd5eda4d332d 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -18,7 +18,7 @@ "@nestjs/jwt": "^8.0.0", "@nestjs/passport": "^8.2.1", "@nestjs/platform-express": "^8.0.0", - "@nestjs/typeorm": "^8.0.2", + "@nestjs/typeorm": "^8.0.3", "apollo-server-express": "^3.6.3", "axios": "^0.21.1", "cookie-parser": "^1.4.6", @@ -28,10 +28,12 @@ "passport": "^0.5.2", "passport-jwt": "^4.0.0", "passport-local": "^1.0.0", + "pg": "^8.7.3", "reflect-metadata": "^0.1.13", "rimraf": "^3.0.2", "rxjs": "^7.2.0", - "ts-md5": "^1.2.11" + "ts-md5": "^1.2.11", + "typeorm": "^0.2.45" }, "devDependencies": { "@eclass/semantic-release-docker": "^3.0.0", @@ -2473,8 +2475,7 @@ "node_modules/@sqltools/formatter": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.3.tgz", - "integrity": "sha512-O3uyB/JbkAEMZaP3YqyHH7TMnex7tWyCbCI4EfJdOCoN6HIhqdJBWTM6aCCiWQ/5f5wxjgU735QAIpJbjDvmzg==", - "peer": true + "integrity": "sha512-O3uyB/JbkAEMZaP3YqyHH7TMnex7tWyCbCI4EfJdOCoN6HIhqdJBWTM6aCCiWQ/5f5wxjgU735QAIpJbjDvmzg==" }, "node_modules/@szmarczak/http-timer": { "version": "4.0.6", @@ -2901,8 +2902,7 @@ "node_modules/@types/zen-observable": { "version": "0.8.3", "resolved": "https://registry.npmjs.org/@types/zen-observable/-/zen-observable-0.8.3.tgz", - "integrity": "sha512-fbF6oTd4sGGy0xjHPKAt+eS2CrxJ3+6gQ3FGcBoIJR2TLAyCkCyI8JqZNy+FeON0AhVgNJoUumVoZQjBFUqHkw==", - "peer": true + "integrity": "sha512-fbF6oTd4sGGy0xjHPKAt+eS2CrxJ3+6gQ3FGcBoIJR2TLAyCkCyI8JqZNy+FeON0AhVgNJoUumVoZQjBFUqHkw==" }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "4.33.0", @@ -3414,8 +3414,7 @@ "node_modules/any-promise": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=", - "peer": true + "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=" }, "node_modules/anymatch": { "version": "3.1.2", @@ -3574,7 +3573,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-3.0.0.tgz", "integrity": "sha512-qMcx+Gy2UZynHjOHOIXPNvpf+9cjvk3cWrBBK7zg4gH9+clobJRb9NGzcT7mQTcV/6Gm/1WelUtqxVXnNlrwcw==", - "peer": true, "engines": { "node": ">= 6.0.0" } @@ -4003,6 +4001,14 @@ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" }, + "node_modules/buffer-writer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", + "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==", + "engines": { + "node": ">=4" + } + }, "node_modules/busboy": { "version": "0.2.14", "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz", @@ -4243,9 +4249,9 @@ "dev": true }, "node_modules/class-transformer": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.4.0.tgz", - "integrity": "sha512-ETWD/H2TbWbKEi7m9N4Km5+cw1hNcqJSxlSYhsLsNjQzWWiZIYA1zafxpK9PwVfaZ6AqR5rrjPVUBGESm5tQUA==", + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", + "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==", "optional": true, "peer": true }, @@ -4285,7 +4291,6 @@ "version": "2.1.11", "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz", "integrity": "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==", - "peer": true, "dependencies": { "chalk": "^4.0.0", "highlight.js": "^10.7.1", @@ -4306,7 +4311,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "peer": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -4321,8 +4325,7 @@ "node_modules/cli-highlight/node_modules/parse5": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", - "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", - "peer": true + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==" }, "node_modules/cli-spinners": { "version": "2.6.1", @@ -6689,7 +6692,6 @@ "version": "10.7.3", "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", - "peer": true, "engines": { "node": "*" } @@ -9156,7 +9158,6 @@ "version": "2.7.0", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", - "peer": true, "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", @@ -12011,6 +12012,11 @@ "node": ">=6" } }, + "node_modules/packet-reader": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", + "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -12062,7 +12068,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", - "peer": true, "dependencies": { "parse5": "^6.0.1" } @@ -12170,6 +12175,88 @@ "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", "integrity": "sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10=" }, + "node_modules/pg": { + "version": "8.7.3", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.7.3.tgz", + "integrity": "sha512-HPmH4GH4H3AOprDJOazoIcpI49XFsHCe8xlrjHkWiapdbHK+HLtbm/GQzXYAZwmPju/kzKhjaSfMACG+8cgJcw==", + "dependencies": { + "buffer-writer": "2.0.0", + "packet-reader": "1.0.0", + "pg-connection-string": "^2.5.0", + "pg-pool": "^3.5.1", + "pg-protocol": "^1.5.0", + "pg-types": "^2.1.0", + "pgpass": "1.x" + }, + "engines": { + "node": ">= 8.0.0" + }, + "peerDependencies": { + "pg-native": ">=2.0.0" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-connection-string": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz", + "integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==" + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-pool": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.5.1.tgz", + "integrity": "sha512-6iCR0wVrro6OOHFsyavV+i6KYL4lVNyYAB9RD18w66xSzN+d8b66HiwuP30Gp1SH5O9T82fckkzsRjlrhD0ioQ==", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.5.0.tgz", + "integrity": "sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ==" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "dependencies": { + "split2": "^4.1.0" + } + }, + "node_modules/pgpass/node_modules/split2": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.1.0.tgz", + "integrity": "sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ==", + "engines": { + "node": ">= 10.x" + } + }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -12306,6 +12393,41 @@ "node": ">=4" } }, + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha1-AntTPAqokOJtFy1Hz5zOzFIazTU=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -12929,8 +13051,7 @@ "node_modules/sax": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", - "peer": true + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" }, "node_modules/saxes": { "version": "5.0.1", @@ -14080,7 +14201,6 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", - "peer": true, "dependencies": { "any-promise": "^1.0.0" } @@ -14089,7 +14209,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", "integrity": "sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY=", - "peer": true, "dependencies": { "thenify": ">= 3.1.0 < 4" }, @@ -14526,7 +14645,6 @@ "version": "0.2.45", "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.2.45.tgz", "integrity": "sha512-c0rCO8VMJ3ER7JQ73xfk0zDnVv0WDjpsP6Q1m6CVKul7DB9iVdWLRjPzc8v2eaeBuomsbZ2+gTaYr8k1gm3bYA==", - "peer": true, "dependencies": { "@sqltools/formatter": "^1.2.2", "app-root-path": "^3.0.0", @@ -14620,8 +14738,7 @@ "node_modules/typeorm/node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "peer": true + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, "node_modules/typeorm/node_modules/buffer": { "version": "6.0.3", @@ -14641,7 +14758,6 @@ "url": "https://feross.org/support" } ], - "peer": true, "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" @@ -14651,7 +14767,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "peer": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -14667,7 +14782,6 @@ "version": "8.6.0", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==", - "peer": true, "engines": { "node": ">=10" } @@ -14676,7 +14790,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "peer": true, "dependencies": { "argparse": "^2.0.1" }, @@ -14688,7 +14801,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "peer": true, "bin": { "mkdirp": "bin/cmd.js" }, @@ -14700,7 +14812,6 @@ "version": "17.3.1", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.3.1.tgz", "integrity": "sha512-WUANQeVgjLbNsEmGk20f+nlHgOqzRFpiGWVaBrYGYIGANIIu3lWjoyi0fNlFmJkvfhCZ6BXINe7/W2O2bV4iaA==", - "peer": true, "dependencies": { "cliui": "^7.0.2", "escalade": "^3.1.1", @@ -14718,7 +14829,6 @@ "version": "21.0.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.0.1.tgz", "integrity": "sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg==", - "peer": true, "engines": { "node": ">=12" } @@ -15269,7 +15379,6 @@ "version": "0.4.23", "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", - "peer": true, "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" @@ -15282,7 +15391,6 @@ "version": "11.0.1", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", - "peer": true, "engines": { "node": ">=4.0" } @@ -15380,14 +15488,12 @@ "node_modules/zen-observable": { "version": "0.8.15", "resolved": "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.15.tgz", - "integrity": "sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ==", - "peer": true + "integrity": "sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ==" }, "node_modules/zen-observable-ts": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/zen-observable-ts/-/zen-observable-ts-1.1.0.tgz", "integrity": "sha512-1h4zlLSqI2cRLPJUHJFL8bCWHhkpuXkF+dbGkRaWjgDIG26DmzyshUMrdV/rL3UnR+mhaX4fRq8LPouq0MYYIA==", - "peer": true, "dependencies": { "@types/zen-observable": "0.8.3", "zen-observable": "0.8.15" @@ -17216,8 +17322,7 @@ "@sqltools/formatter": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.3.tgz", - "integrity": "sha512-O3uyB/JbkAEMZaP3YqyHH7TMnex7tWyCbCI4EfJdOCoN6HIhqdJBWTM6aCCiWQ/5f5wxjgU735QAIpJbjDvmzg==", - "peer": true + "integrity": "sha512-O3uyB/JbkAEMZaP3YqyHH7TMnex7tWyCbCI4EfJdOCoN6HIhqdJBWTM6aCCiWQ/5f5wxjgU735QAIpJbjDvmzg==" }, "@szmarczak/http-timer": { "version": "4.0.6", @@ -17638,8 +17743,7 @@ "@types/zen-observable": { "version": "0.8.3", "resolved": "https://registry.npmjs.org/@types/zen-observable/-/zen-observable-0.8.3.tgz", - "integrity": "sha512-fbF6oTd4sGGy0xjHPKAt+eS2CrxJ3+6gQ3FGcBoIJR2TLAyCkCyI8JqZNy+FeON0AhVgNJoUumVoZQjBFUqHkw==", - "peer": true + "integrity": "sha512-fbF6oTd4sGGy0xjHPKAt+eS2CrxJ3+6gQ3FGcBoIJR2TLAyCkCyI8JqZNy+FeON0AhVgNJoUumVoZQjBFUqHkw==" }, "@typescript-eslint/eslint-plugin": { "version": "4.33.0", @@ -18020,8 +18124,7 @@ "any-promise": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=", - "peer": true + "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=" }, "anymatch": { "version": "3.1.2", @@ -18137,8 +18240,7 @@ "app-root-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-3.0.0.tgz", - "integrity": "sha512-qMcx+Gy2UZynHjOHOIXPNvpf+9cjvk3cWrBBK7zg4gH9+clobJRb9NGzcT7mQTcV/6Gm/1WelUtqxVXnNlrwcw==", - "peer": true + "integrity": "sha512-qMcx+Gy2UZynHjOHOIXPNvpf+9cjvk3cWrBBK7zg4gH9+clobJRb9NGzcT7mQTcV/6Gm/1WelUtqxVXnNlrwcw==" }, "append-field": { "version": "1.0.0", @@ -18479,6 +18581,11 @@ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" }, + "buffer-writer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", + "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==" + }, "busboy": { "version": "0.2.14", "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz", @@ -18662,9 +18769,9 @@ "dev": true }, "class-transformer": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.4.0.tgz", - "integrity": "sha512-ETWD/H2TbWbKEi7m9N4Km5+cw1hNcqJSxlSYhsLsNjQzWWiZIYA1zafxpK9PwVfaZ6AqR5rrjPVUBGESm5tQUA==", + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", + "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==", "optional": true, "peer": true }, @@ -18698,7 +18805,6 @@ "version": "2.1.11", "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz", "integrity": "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==", - "peer": true, "requires": { "chalk": "^4.0.0", "highlight.js": "^10.7.1", @@ -18712,7 +18818,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "peer": true, "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -18721,8 +18826,7 @@ "parse5": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", - "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", - "peer": true + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==" } } }, @@ -20562,8 +20666,7 @@ "highlight.js": { "version": "10.7.3", "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", - "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", - "peer": true + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==" }, "hook-std": { "version": "2.0.0", @@ -22424,7 +22527,6 @@ "version": "2.7.0", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", - "peer": true, "requires": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", @@ -24455,6 +24557,11 @@ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true }, + "packet-reader": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", + "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==" + }, "parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -24497,7 +24604,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", - "peer": true, "requires": { "parse5": "^6.0.1" } @@ -24577,6 +24683,68 @@ "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", "integrity": "sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10=" }, + "pg": { + "version": "8.7.3", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.7.3.tgz", + "integrity": "sha512-HPmH4GH4H3AOprDJOazoIcpI49XFsHCe8xlrjHkWiapdbHK+HLtbm/GQzXYAZwmPju/kzKhjaSfMACG+8cgJcw==", + "requires": { + "buffer-writer": "2.0.0", + "packet-reader": "1.0.0", + "pg-connection-string": "^2.5.0", + "pg-pool": "^3.5.1", + "pg-protocol": "^1.5.0", + "pg-types": "^2.1.0", + "pgpass": "1.x" + } + }, + "pg-connection-string": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz", + "integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==" + }, + "pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==" + }, + "pg-pool": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.5.1.tgz", + "integrity": "sha512-6iCR0wVrro6OOHFsyavV+i6KYL4lVNyYAB9RD18w66xSzN+d8b66HiwuP30Gp1SH5O9T82fckkzsRjlrhD0ioQ==", + "requires": {} + }, + "pg-protocol": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.5.0.tgz", + "integrity": "sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ==" + }, + "pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "requires": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + } + }, + "pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "requires": { + "split2": "^4.1.0" + }, + "dependencies": { + "split2": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.1.0.tgz", + "integrity": "sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ==" + } + } + }, "picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -24676,6 +24844,29 @@ "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", "dev": true }, + "postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==" + }, + "postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha1-AntTPAqokOJtFy1Hz5zOzFIazTU=" + }, + "postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==" + }, + "postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "requires": { + "xtend": "^4.0.0" + } + }, "prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -25118,8 +25309,7 @@ "sax": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", - "peer": true + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" }, "saxes": { "version": "5.0.1", @@ -26002,7 +26192,6 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", - "peer": true, "requires": { "any-promise": "^1.0.0" } @@ -26011,7 +26200,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", "integrity": "sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY=", - "peer": true, "requires": { "thenify": ">= 3.1.0 < 4" } @@ -26325,7 +26513,6 @@ "version": "0.2.45", "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.2.45.tgz", "integrity": "sha512-c0rCO8VMJ3ER7JQ73xfk0zDnVv0WDjpsP6Q1m6CVKul7DB9iVdWLRjPzc8v2eaeBuomsbZ2+gTaYr8k1gm3bYA==", - "peer": true, "requires": { "@sqltools/formatter": "^1.2.2", "app-root-path": "^3.0.0", @@ -26349,14 +26536,12 @@ "argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "peer": true + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, "buffer": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "peer": true, "requires": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" @@ -26366,7 +26551,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "peer": true, "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -26375,14 +26559,12 @@ "dotenv": { "version": "8.6.0", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", - "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==", - "peer": true + "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==" }, "js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "peer": true, "requires": { "argparse": "^2.0.1" } @@ -26390,14 +26572,12 @@ "mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "peer": true + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" }, "yargs": { "version": "17.3.1", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.3.1.tgz", "integrity": "sha512-WUANQeVgjLbNsEmGk20f+nlHgOqzRFpiGWVaBrYGYIGANIIu3lWjoyi0fNlFmJkvfhCZ6BXINe7/W2O2bV4iaA==", - "peer": true, "requires": { "cliui": "^7.0.2", "escalade": "^3.1.1", @@ -26411,8 +26591,7 @@ "yargs-parser": { "version": "21.0.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.0.1.tgz", - "integrity": "sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg==", - "peer": true + "integrity": "sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg==" } } }, @@ -26822,7 +27001,6 @@ "version": "0.4.23", "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", - "peer": true, "requires": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" @@ -26831,8 +27009,7 @@ "xmlbuilder": { "version": "11.0.1", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", - "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", - "peer": true + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==" }, "xmlchars": { "version": "2.2.0", @@ -26905,14 +27082,12 @@ "zen-observable": { "version": "0.8.15", "resolved": "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.15.tgz", - "integrity": "sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ==", - "peer": true + "integrity": "sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ==" }, "zen-observable-ts": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/zen-observable-ts/-/zen-observable-ts-1.1.0.tgz", "integrity": "sha512-1h4zlLSqI2cRLPJUHJFL8bCWHhkpuXkF+dbGkRaWjgDIG26DmzyshUMrdV/rL3UnR+mhaX4fRq8LPouq0MYYIA==", - "peer": true, "requires": { "@types/zen-observable": "0.8.3", "zen-observable": "0.8.15" diff --git a/api/package.json b/api/package.json index ac9d8e2879f8235ecbf9d47acfb6cb7e6eea8d12..0672afa19528de8fdf23d8820fb4fffc7108fa62 100644 --- a/api/package.json +++ b/api/package.json @@ -32,7 +32,7 @@ "@nestjs/jwt": "^8.0.0", "@nestjs/passport": "^8.2.1", "@nestjs/platform-express": "^8.0.0", - "@nestjs/typeorm": "^8.0.2", + "@nestjs/typeorm": "^8.0.3", "apollo-server-express": "^3.6.3", "axios": "^0.21.1", "cookie-parser": "^1.4.6", @@ -42,10 +42,12 @@ "passport": "^0.5.2", "passport-jwt": "^4.0.0", "passport-local": "^1.0.0", + "pg": "^8.7.3", "reflect-metadata": "^0.1.13", "rimraf": "^3.0.2", "rxjs": "^7.2.0", - "ts-md5": "^1.2.11" + "ts-md5": "^1.2.11", + "typeorm": "^0.2.45" }, "devDependencies": { "@eclass/semantic-release-docker": "^3.0.0", diff --git a/api/src/auth/auth.resolver.spec.ts b/api/src/auth/auth.resolver.spec.ts index cc74383f2ad204a9b0e6a8c4503456e361081193..ca6c620bddc48e94292a126f95d39452caedc3a6 100644 --- a/api/src/auth/auth.resolver.spec.ts +++ b/api/src/auth/auth.resolver.spec.ts @@ -6,7 +6,7 @@ import { ENGINE_SERVICE } from '../engine/engine.constants'; import { authConstants } from './auth-constants'; import { AuthResolver } from './auth.resolver'; import { AuthService } from './auth.service'; -import { User } from './models/user.model'; +import { User } from '../users/models/user.model'; const moduleMocker = new ModuleMocker(global); @@ -68,7 +68,6 @@ describe('AuthResolver', () => { expect(mockCookie.mock.calls[0][0]).toBe(authConstants.cookie.name); expect(mockCookie.mock.calls[0][1]).toBe(authData.accessToken); expect(data.accessToken).toBe(authData.accessToken); - expect(data.user).toBe(user); }); it('logout', () => { diff --git a/api/src/auth/auth.resolver.ts b/api/src/auth/auth.resolver.ts index 6a5f5698a787dea72e3836b3ebc197aef7fea8cf..5853fe0ad0e76d9a8e5cdf647e93ff2f270c9d82 100644 --- a/api/src/auth/auth.resolver.ts +++ b/api/src/auth/auth.resolver.ts @@ -12,13 +12,13 @@ import { ENGINE_SERVICE } from '../engine/engine.constants'; import { IEngineService } from '../engine/engine.interfaces'; import { authConstants } from './auth-constants'; import { AuthService } from './auth.service'; -import { GQLResponse } from './decorators/gql-request.decoractor'; import { CurrentUser } from './decorators/user.decorator'; import { JwtAuthGuard } from './guards/jwt-auth.guard'; import { LocalAuthGuard } from './guards/local-auth.guard'; import { AuthenticationInput } from './inputs/authentication.input'; -import { User } from './models/user.model'; +import { User } from '../users/models/user.model'; import { AuthenticationOutput } from './outputs/authentication.output'; +import { GQLResponse } from '../common/decorators/gql-response.decoractor'; //Custom defined type because Pick<CookieOptions, 'sameSite'> does not work type SameSiteType = boolean | 'lax' | 'strict' | 'none' | undefined; @@ -62,7 +62,6 @@ export class AuthResolver { }); return { - user, accessToken: data.accessToken, }; } diff --git a/api/src/auth/auth.service.spec.ts b/api/src/auth/auth.service.spec.ts index 4d85bad910e98b7b0adc88ed2d51d18a27d20d73..454ce9230371e3a5e8bd01d7469ae25252dbd859 100644 --- a/api/src/auth/auth.service.spec.ts +++ b/api/src/auth/auth.service.spec.ts @@ -7,7 +7,7 @@ import { ENGINE_SERVICE, } from '../engine/engine.constants'; import { AuthService } from './auth.service'; -import { User } from './models/user.model'; +import { User } from '../users/models/user.model'; const moduleMocker = new ModuleMocker(global); diff --git a/api/src/auth/auth.service.ts b/api/src/auth/auth.service.ts index e992fa760c238d0d475115e87cf52e5e07a0d5f3..2c09c5b35d6443d78a40ca38d4e72a3706538dec 100644 --- a/api/src/auth/auth.service.ts +++ b/api/src/auth/auth.service.ts @@ -2,7 +2,7 @@ import { Inject, Injectable, NotImplementedException } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; import { ENGINE_SERVICE } from '../engine/engine.constants'; import { IEngineService } from '../engine/engine.interfaces'; -import { User } from './models/user.model'; +import { User } from '../users/models/user.model'; import { AuthenticationOutput } from './outputs/authentication.output'; @Injectable() diff --git a/api/src/auth/decorators/user.decorator.ts b/api/src/auth/decorators/user.decorator.ts index cbb566332c2d214cb3d33ec1b9ff7c458f9992e3..0aa940d9371596e9902e9acf5b8c0dcee1307d2f 100644 --- a/api/src/auth/decorators/user.decorator.ts +++ b/api/src/auth/decorators/user.decorator.ts @@ -1,6 +1,6 @@ import { createParamDecorator, ExecutionContext } from '@nestjs/common'; import { GqlExecutionContext } from '@nestjs/graphql'; -import { User } from '../models/user.model'; +import { User } from '../../users/models/user.model'; /** * Retrieve the current user within the graphQL request diff --git a/api/src/auth/outputs/authentication.output.ts b/api/src/auth/outputs/authentication.output.ts index 7f4590c2ddbfb3e1a7a4cb69e8127b3cfc07c3c0..f6a3dabfb1fcb672ae57129854fc6f8c26d3b9c8 100644 --- a/api/src/auth/outputs/authentication.output.ts +++ b/api/src/auth/outputs/authentication.output.ts @@ -1,11 +1,7 @@ import { Field, ObjectType } from '@nestjs/graphql'; -import { User } from '../models/user.model'; @ObjectType() export class AuthenticationOutput { - @Field(() => User) - user: User; - @Field() accessToken: string; } diff --git a/api/src/auth/strategies/local.strategy.ts b/api/src/auth/strategies/local.strategy.ts index 166e9efb0d75dcb31a56ab1c4e09ceb9c8dc846a..78ab0df58c7cb34764c0298a771adadb262258fe 100644 --- a/api/src/auth/strategies/local.strategy.ts +++ b/api/src/auth/strategies/local.strategy.ts @@ -1,26 +1,17 @@ -import { Strategy } from 'passport-local'; -import { PassportStrategy } from '@nestjs/passport'; import { Injectable, UnauthorizedException } from '@nestjs/common'; +import { PassportStrategy } from '@nestjs/passport'; +import { Strategy } from 'passport-local'; +import { User } from '../../users/models/user.model'; import { AuthService } from '../auth.service'; -import { User } from '../models/user.model'; -import { ContextIdFactory, ModuleRef } from '@nestjs/core'; @Injectable() export class LocalStrategy extends PassportStrategy(Strategy, 'local') { - constructor(private moduleRef: ModuleRef) { - super({ - passReqToCallback: true, - }); + constructor(private readonly authService: AuthService) { + super(); } - async validate( - request: Request, - username: string, - password: string, - ): Promise<User> { - const contextId = ContextIdFactory.getByRequest(request); - const authService = await this.moduleRef.resolve(AuthService, contextId); - const user = await authService.validateUser(username, password); + async validate(username: string, password: string): Promise<User> { + const user = await this.authService.validateUser(username, password); if (!user) { throw new UnauthorizedException(); } diff --git a/api/src/common/decorators/gql-request.decoractor.ts b/api/src/common/decorators/gql-request.decoractor.ts new file mode 100644 index 0000000000000000000000000000000000000000..5f9a58b04caedfee3d0fba2082887595881a296a --- /dev/null +++ b/api/src/common/decorators/gql-request.decoractor.ts @@ -0,0 +1,14 @@ +import { createParamDecorator, ExecutionContext } from '@nestjs/common'; +import { GqlExecutionContext } from '@nestjs/graphql'; +import { Request } from 'express'; + +/** + * Access to graphQL request through the context + * @returns request from graphql + */ +export const GQLRequest = createParamDecorator( + (data: unknown, context: ExecutionContext): Request => { + const ctx = GqlExecutionContext.create(context); + return ctx.getContext().req; + }, +); diff --git a/api/src/auth/decorators/gql-request.decoractor.ts b/api/src/common/decorators/gql-response.decoractor.ts similarity index 81% rename from api/src/auth/decorators/gql-request.decoractor.ts rename to api/src/common/decorators/gql-response.decoractor.ts index 4de3409deea7848aa33e7a66d11a842b029ac5a9..0b9aae9689f0aa4c1e64e60e1bb6432226ab9220 100644 --- a/api/src/auth/decorators/gql-request.decoractor.ts +++ b/api/src/common/decorators/gql-response.decoractor.ts @@ -3,8 +3,8 @@ import { GqlExecutionContext } from '@nestjs/graphql'; import { Response } from 'express'; /** - * Access to graphQL request through the context - * @returns request from graphql + * Access to graphQL response through the context + * @returns response from graphql */ export const GQLResponse = createParamDecorator( (data: unknown, context: ExecutionContext): Response => { diff --git a/api/src/common/decorators/user.decorator.ts b/api/src/common/decorators/user.decorator.ts new file mode 100644 index 0000000000000000000000000000000000000000..0aa940d9371596e9902e9acf5b8c0dcee1307d2f --- /dev/null +++ b/api/src/common/decorators/user.decorator.ts @@ -0,0 +1,14 @@ +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/engine/connectors/csv/main.connector.ts b/api/src/engine/connectors/csv/main.connector.ts index 789ddfc8ab266cf6ad836eba6c719e2d18f0d01d..d725496356e3133f54a364df1fa2158e13b14616 100644 --- a/api/src/engine/connectors/csv/main.connector.ts +++ b/api/src/engine/connectors/csv/main.connector.ts @@ -1,4 +1,4 @@ -import { firstValueFrom, Observable } from 'rxjs'; +import { firstValueFrom } from 'rxjs'; 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'; @@ -12,6 +12,7 @@ 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 { constructor( @@ -19,18 +20,18 @@ export default class CSVService implements IEngineService { private readonly httpService: HttpService, ) {} - logout() { + async logout() { throw new Error('Method not implemented.'); } - getAlgorithms(): Algorithm[] | Promise<Algorithm[]> { + async getAlgorithms(): Promise<Algorithm[]> { throw new Error('Method not implemented.'); } - createExperiment( + async createExperiment( data: ExperimentCreateInput, isTransient: boolean, - ): Experiment | Promise<Experiment> { + ): Promise<Experiment> { return { id: '', domain: '', @@ -44,10 +45,7 @@ export default class CSVService implements IEngineService { }; } - listExperiments( - page: number, - name: string, - ): ListExperiments | Promise<ListExperiments> { + async listExperiments(page: number, name: string): Promise<ListExperiments> { return { experiments: [], currentPage: 0, @@ -56,18 +54,18 @@ export default class CSVService implements IEngineService { }; } - getExperiment(id: string): Experiment | Promise<Experiment> { + async getExperiment(id: string): Promise<Experiment> { throw new Error('Method not implemented.'); } - removeExperiment(id: string): PartialExperiment | Promise<PartialExperiment> { + async removeExperiment(id: string): Promise<PartialExperiment> { throw new Error('Method not implemented.'); } - editExperient( + async editExperient( id: string, expriment: ExperimentEditInput, - ): Experiment | Promise<Experiment> { + ): Promise<Experiment> { throw new Error('Method not implemented.'); } @@ -156,43 +154,15 @@ export default class CSVService implements IEngineService { ]; } - getActiveUser(): string { + async getActiveUser(): Promise<User> { const dummyUser = { username: 'anonymous', - subjectId: 'anonymousId', + id: 'anonymousId', fullname: 'anonymous', email: 'anonymous@anonymous.com', agreeNDA: true, }; - return JSON.stringify(dummyUser); - } - - editActiveUser(): Observable<string> { - throw new Error('Method not implemented.'); - } - - getExperimentREST(): Observable<string> { - throw new Error('Method not implemented.'); - } - - deleteExperiment(): Observable<string> { - throw new Error('Method not implemented.'); - } - - editExperimentREST(): Observable<string> { - throw new Error('Method not implemented.'); - } - - startExperimentTransient(): Observable<string> { - throw new Error('Method not implemented.'); - } - - startExperiment(): Observable<string> { - throw new Error('Method not implemented.'); - } - - getExperiments(): string { - return '[]'; + return dummyUser; } getAlgorithmsREST(): string { diff --git a/api/src/engine/connectors/datashield/main.connector.ts b/api/src/engine/connectors/datashield/main.connector.ts index 2e4f3be2ebce9f21319426a07cda073005fcd4ec..3c34f0f23ad0f27347ae6b9de3c538c0d5e2d180 100644 --- a/api/src/engine/connectors/datashield/main.connector.ts +++ b/api/src/engine/connectors/datashield/main.connector.ts @@ -1,9 +1,13 @@ import { HttpService } from '@nestjs/axios'; -import { Inject, Logger, NotImplementedException } from '@nestjs/common'; -import { REQUEST } from '@nestjs/core'; +import { + Inject, + InternalServerErrorException, + Logger, + NotImplementedException, + UnauthorizedException, +} from '@nestjs/common'; import { Request } from 'express'; -import { catchError, firstValueFrom, Observable } from 'rxjs'; -import { User } from 'src/auth/models/user.model'; +import { catchError, firstValueFrom } from 'rxjs'; import { MIME_TYPES } from 'src/common/interfaces/utilities.interface'; import { errorAxiosHandler } from 'src/common/utilities'; import { ENGINE_MODULE_OPTIONS } from 'src/engine/engine.constants'; @@ -26,6 +30,7 @@ import { TableResult, ThemeType, } from 'src/engine/models/result/table-result.model'; +import { User } from 'src/users/models/user.model'; import { transformToDomains, transformToHisto, @@ -38,7 +43,6 @@ export default class DataShieldService implements IEngineService { constructor( @Inject(ENGINE_MODULE_OPTIONS) private readonly options: IEngineOptions, private readonly httpService: HttpService, - @Inject(REQUEST) private readonly req: Request, ) {} getConfiguration(): IConfiguration { @@ -77,7 +81,7 @@ export default class DataShieldService implements IEngineService { return user; } - getAlgorithms(): Algorithm[] | Promise<Algorithm[]> { + async getAlgorithms(): Promise<Algorithm[]> { throw new NotImplementedException(); } @@ -143,8 +147,9 @@ export default class DataShieldService implements IEngineService { async createExperiment( data: ExperimentCreateInput, isTransient: boolean, + request: Request, ): Promise<Experiment> { - const user = this.req.user as User; + const user = request.user as User; const cookie = [`sid=${user.extraFields['sid']}`, `user=${user.id}`].join( ';', ); @@ -182,10 +187,7 @@ export default class DataShieldService implements IEngineService { return expResult; } - listExperiments( - page: number, - name: string, - ): ListExperiments | Promise<ListExperiments> { + async listExperiments(page: number, name: string): Promise<ListExperiments> { return { totalExperiments: 0, experiments: [], @@ -194,23 +196,30 @@ export default class DataShieldService implements IEngineService { }; } - getExperiment(id: string): Experiment | Promise<Experiment> { + async getExperiment(id: string): Promise<Experiment> { throw new NotImplementedException(); } - removeExperiment(id: string): PartialExperiment | Promise<PartialExperiment> { + async removeExperiment(id: string): Promise<PartialExperiment> { throw new NotImplementedException(); } - editExperient( + async editExperient( id: string, expriment: ExperimentEditInput, - ): Experiment | Promise<Experiment> { + ): Promise<Experiment> { throw new NotImplementedException(); } - async getDomains(): Promise<Domain[]> { - const user = this.req.user as User; + async getDomains(ids: string[], request: Request): Promise<Domain[]> { + const user = request.user as User; + const sid = user && user.extraFields && user.extraFields['sid']; + + if (!sid) + throw new InternalServerErrorException( + 'Datashield sid is missing from the user', + ); + const cookies = [`sid=${user.extraFields['sid']}`, `user=${user.id}`]; const path = this.options.baseurl + 'getvars'; @@ -225,43 +234,15 @@ export default class DataShieldService implements IEngineService { return [transformToDomains.evaluate(response.data)]; } - getActiveUser(): string { + async getActiveUser(): Promise<User> { const dummyUser = { username: 'anonymous', - subjectId: 'anonymousId', + id: 'anonymousId', fullname: 'anonymous', email: 'anonymous@anonymous.com', agreeNDA: true, }; - return JSON.stringify(dummyUser); - } - - editActiveUser(): Observable<string> { - throw new NotImplementedException(); - } - - getExperimentREST(): Observable<string> { - throw new NotImplementedException(); - } - - deleteExperiment(): Observable<string> { - throw new NotImplementedException(); - } - - editExperimentREST(): Observable<string> { - throw new NotImplementedException(); - } - - startExperimentTransient(): Observable<string> { - throw new NotImplementedException(); - } - - startExperiment(): Observable<string> { - throw new NotImplementedException(); - } - - getExperiments(): string { - return '[]'; + return dummyUser; } getAlgorithmsREST(): string { diff --git a/api/src/engine/connectors/datashield/transformations.ts b/api/src/engine/connectors/datashield/transformations.ts index 0a664d3809e96135fb5a7b711f874a4cef9bd31a..065040b6611b020130709bdb7f0734b9d505da12 100644 --- a/api/src/engine/connectors/datashield/transformations.ts +++ b/api/src/engine/connectors/datashield/transformations.ts @@ -78,3 +78,7 @@ export const transformToTable = jsonata(` }) }) `); + +export const transformToUser = jsonata(` +$ ~> |$|{'id': subjectId}, ['subjectId']| +`); diff --git a/api/src/engine/connectors/exareme/main.connector.ts b/api/src/engine/connectors/exareme/main.connector.ts index 8dde760c67b40bdbd0291626b9d7bf4900d2ee1e..daa996d8e7c1c2398ff2e9af0ff0a4c31c253a1c 100644 --- a/api/src/engine/connectors/exareme/main.connector.ts +++ b/api/src/engine/connectors/exareme/main.connector.ts @@ -6,9 +6,8 @@ import { Inject, Injectable, } from '@nestjs/common'; -import { REQUEST } from '@nestjs/core'; +import { AxiosRequestConfig } from 'axios'; import { Request } from 'express'; -import { IncomingMessage } from 'http'; import { firstValueFrom, map, Observable } from 'rxjs'; import { ENGINE_MODULE_OPTIONS } from 'src/engine/engine.constants'; import { @@ -27,6 +26,8 @@ import { ExperimentEditInput } from 'src/engine/models/experiment/input/experime 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'; +import { User } from 'src/users/models/user.model'; +import { transformToUser } from '../datashield/transformations'; import { dataToAlgorithms, dataToDataset, @@ -40,20 +41,15 @@ import { ExperimentsData } from './interfaces/experiment/experiments.interface'; import { Hierarchy } from './interfaces/hierarchy.interface'; import { Pathology } from './interfaces/pathology.interface'; +type Headers = Record<string, string>; + @Injectable() export default class ExaremeService implements IEngineService { headers = {}; constructor( @Inject(ENGINE_MODULE_OPTIONS) private readonly options: IEngineOptions, private readonly httpService: HttpService, - @Inject(REQUEST) private readonly req: Request, //TODO: remove inject, set request from manually take care of graphql request - ) { - const gqlRequest = req['req']; // graphql headers exception - this.headers = - gqlRequest && gqlRequest instanceof IncomingMessage - ? gqlRequest.headers - : req.headers; - } + ) {} getConfiguration(): IConfiguration { return { @@ -62,15 +58,16 @@ export default class ExaremeService implements IEngineService { }; } - async logout() { + async logout(request: Request) { const path = `${this.options.baseurl}logout`; - await firstValueFrom(this.httpService.get(path, { headers: this.headers })); + await firstValueFrom(this.get(request, path)); } async createExperiment( data: ExperimentCreateInput, isTransient = false, + request: Request, ): Promise<Experiment> { const form = experimentInputToData(data); @@ -78,7 +75,7 @@ export default class ExaremeService implements IEngineService { this.options.baseurl + `experiments${isTransient ? '/transient' : ''}`; const resultAPI = await firstValueFrom( - this.httpService.post<ExperimentData>(path, form, { + this.post<ExperimentData>(request, path, form, { headers: this.headers, }), ); @@ -86,13 +83,16 @@ export default class ExaremeService implements IEngineService { return dataToExperiment(resultAPI.data); } - async listExperiments(page: number, name: string): Promise<ListExperiments> { + async listExperiments( + page: number, + name: string, + request: Request, + ): Promise<ListExperiments> { const path = this.options.baseurl + 'experiments'; const resultAPI = await firstValueFrom( - this.httpService.get<ExperimentsData>(path, { + this.get<ExperimentsData>(request, path, { params: { page, name }, - headers: this.headers, }), ); @@ -102,25 +102,19 @@ export default class ExaremeService implements IEngineService { }; } - async getAlgorithms(): Promise<Algorithm[]> { + async getAlgorithms(request: Request): Promise<Algorithm[]> { const path = this.options.baseurl + 'algorithms'; - const resultAPI = await firstValueFrom( - this.httpService.get<string>(path, { - headers: this.headers, - }), - ); + const resultAPI = await firstValueFrom(this.get<string>(request, path)); return dataToAlgorithms(resultAPI.data); } - async getExperiment(id: string): Promise<Experiment> { + async getExperiment(id: string, request: Request): Promise<Experiment> { const path = this.options.baseurl + `experiments/${id}`; const resultAPI = await firstValueFrom( - this.httpService.get<ExperimentData>(path, { - headers: this.headers, - }), + this.get<ExperimentData>(request, path), ); return dataToExperiment(resultAPI.data); @@ -129,24 +123,26 @@ export default class ExaremeService implements IEngineService { async editExperient( id: string, expriment: ExperimentEditInput, + request: Request, ): Promise<Experiment> { const path = this.options.baseurl + `experiments/${id}`; const resultAPI = await firstValueFrom( - this.httpService.patch<ExperimentData>(path, expriment, { - headers: this.headers, - }), + this.patch<ExperimentData>(request, path, expriment), ); return dataToExperiment(resultAPI.data); } - async removeExperiment(id: string): Promise<PartialExperiment> { + async removeExperiment( + id: string, + request: Request, + ): Promise<PartialExperiment> { const path = this.options.baseurl + `experiments/${id}`; try { await firstValueFrom( - this.httpService.delete(path, { + this.delete(request, path, { headers: this.headers, }), ); @@ -158,12 +154,12 @@ export default class ExaremeService implements IEngineService { } } - async getDomains(ids: string[]): Promise<Domain[]> { + async getDomains(ids: string[], request: Request): Promise<Domain[]> { const path = this.options.baseurl + 'pathologies'; try { const data = await firstValueFrom( - this.httpService.get<Pathology[]>(path, { + this.get<Pathology[]>(request, path, { headers: this.headers, }), ); @@ -194,98 +190,41 @@ export default class ExaremeService implements IEngineService { } } - getActiveUser(): Observable<string> { + async getActiveUser(request: Request): Promise<User> { const path = this.options.baseurl + 'activeUser'; - return this.httpService - .get<string>(path, { - headers: this.headers, - }) - .pipe(map((response) => response.data)); - } - - editActiveUser(): Observable<string> { - const path = this.options.baseurl + 'activeUser/agreeNDA'; + const response = await firstValueFrom(this.get<string>(request, path)); - return this.httpService - .post<string>(path, this.req.body, { - headers: this.headers, - }) - .pipe(map((response) => response.data)); - } - - getExperimentREST(id: string): Observable<string> { - const path = this.options.baseurl + `experiments/${id}`; - - return this.httpService - .get<string>(path, { - headers: this.headers, - }) - .pipe(map((response) => response.data)); - } - - deleteExperiment(id: string): Observable<string> { - const path = this.options.baseurl + `experiments/${id}`; - - return this.httpService - .delete(path, { - headers: this.headers, - }) - .pipe(map((response) => response.data)); - } - - editExperimentREST(id: string): Observable<string> { - const path = this.options.baseurl + `experiments/${id}`; - - return this.httpService - .patch(path, this.req.body, { - headers: this.headers, - }) - .pipe(map((response) => response.data)); + return transformToUser.evaluate(response.data); } - startExperimentTransient(): Observable<string> { - const path = this.options.baseurl + 'experiments/transient'; - - return this.httpService - .post(path, this.req.body, { - headers: this.headers, - }) - .pipe(map((response) => response.data)); - } - - startExperiment(): Observable<string> { - const path = this.options.baseurl + 'experiments'; - - return this.httpService - .post(path, this.req.body, { - headers: this.headers, - }) - .pipe(map((response) => response.data)); - } + async updateUser(request: Request): Promise<User> { + const path = this.options.baseurl + 'activeUser/agreeNDA'; - getExperiments(): Observable<string> { - const path = this.options.baseurl + 'experiments'; + this.post<string>(request, path, request.body).pipe( + map((response) => response.data), + ); - return this.httpService - .get<string>(path, { params: this.req.query, headers: this.headers }) - .pipe(map((response) => response.data)); + return this.getActiveUser(request); } - getAlgorithmsREST(): Observable<string> { + getAlgorithmsREST(request: Request): Observable<string> { const path = this.options.baseurl + 'algorithms'; - return this.httpService - .get<string>(path, { params: this.req.query, headers: this.headers }) - .pipe(map((response) => response.data)); + return this.get<string>(request, path, { params: request.query }).pipe( + map((response) => response.data), + ); } - getPassthrough(suffix: string): string | Observable<string> { + getPassthrough( + suffix: string, + request: Request, + ): string | Observable<string> { const path = this.options.baseurl + suffix; - return this.httpService - .get<string>(path, { params: this.req.query, headers: this.headers }) - .pipe(map((response) => response.data)); + return this.get<string>(request, path, { params: request.query }).pipe( + map((response) => response.data), + ); } // UTILITIES @@ -315,4 +254,61 @@ export default class ExaremeService implements IEngineService { return variables; }; + + private getHeadersFromRequest(request: Request): Headers { + if (!request || request.headers) return {}; + + return request.headers as Headers; + } + + private mergeHeaders( + request: Request, + config: AxiosRequestConfig, + ): AxiosRequestConfig { + return { + ...config, + headers: { + ...this.getHeadersFromRequest(request), + ...(config.headers ?? {}), + }, + }; + } + + private get<T = any>( + request: Request, + path: string, + config: AxiosRequestConfig = {}, + ) { + const conf = this.mergeHeaders(request, config); + return this.httpService.get<T>(path, conf); + } + + private post<T = any>( + request: Request, + path: string, + data?: any, + config: AxiosRequestConfig = {}, + ) { + const conf = this.mergeHeaders(request, config); + return this.httpService.post<T>(path, data, conf); + } + + private patch<T = any>( + request: Request, + path: string, + data?: any, + config: AxiosRequestConfig = {}, + ) { + const conf = this.mergeHeaders(request, config); + return this.httpService.patch<T>(path, data, conf); + } + + private delete<T = any>( + request: Request, + path: string, + config: AxiosRequestConfig = {}, + ) { + const conf = this.mergeHeaders(request, config); + return this.httpService.delete<T>(path, conf); + } } diff --git a/api/src/engine/connectors/local/main.connector.ts b/api/src/engine/connectors/local/main.connector.ts index af5cd6b66443af0f60c8d0db97e596f3b2810fa5..d46e1ae80105b5ca1e8a1d073707862631152543 100644 --- a/api/src/engine/connectors/local/main.connector.ts +++ b/api/src/engine/connectors/local/main.connector.ts @@ -1,15 +1,14 @@ -import { Observable } from 'rxjs'; import { 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 { Algorithm } from 'src/engine/models/experiment/algorithm.model'; import { Experiment, PartialExperiment, } from 'src/engine/models/experiment/experiment.model'; -import { ListExperiments } from 'src/engine/models/experiment/list-experiments.model'; +import { ExperimentCreateInput } from 'src/engine/models/experiment/input/experiment-create.input'; import { ExperimentEditInput } from 'src/engine/models/experiment/input/experiment-edit.input'; -import { Algorithm } from 'src/engine/models/experiment/algorithm.model'; -import { User } from 'src/auth/models/user.model'; +import { ListExperiments } from 'src/engine/models/experiment/list-experiments.model'; +import { User } from 'src/users/models/user.model'; export default class LocalService implements IEngineService { login(): User | Promise<User> { @@ -19,36 +18,33 @@ export default class LocalService implements IEngineService { }; } - getAlgorithms(): Algorithm[] | Promise<Algorithm[]> { + async getAlgorithms(): Promise<Algorithm[]> { throw new Error('Method not implemented.'); } - createExperiment( + async createExperiment( data: ExperimentCreateInput, isTransient: boolean, - ): Experiment | Promise<Experiment> { + ): Promise<Experiment> { throw new Error('Method not implemented.'); } - listExperiments( - page: number, - name: string, - ): ListExperiments | Promise<ListExperiments> { + async listExperiments(page: number, name: string): Promise<ListExperiments> { throw new Error('Method not implemented.'); } - getExperiment(id: string): Experiment | Promise<Experiment> { + async getExperiment(id: string): Promise<Experiment> { throw new Error('Method not implemented.'); } - removeExperiment(id: string): PartialExperiment | Promise<PartialExperiment> { + async removeExperiment(id: string): Promise<PartialExperiment> { throw new Error('Method not implemented.'); } - editExperient( + async editExperient( id: string, expriment: ExperimentEditInput, - ): Experiment | Promise<Experiment> { + ): Promise<Experiment> { throw new Error('Method not implemented.'); } @@ -71,43 +67,15 @@ export default class LocalService implements IEngineService { ]; } - getActiveUser(): string { + async getActiveUser(): Promise<User> { const dummyUser = { username: 'anonymous', - subjectId: 'anonymousId', + id: 'anonymousId', fullname: 'anonymous', email: 'anonymous@anonymous.com', agreeNDA: true, }; - return JSON.stringify(dummyUser); - } - - editActiveUser(): Observable<string> { - throw new Error('Method not implemented.'); - } - - getExperimentREST(): Observable<string> { - throw new Error('Method not implemented.'); - } - - deleteExperiment(): Observable<string> { - throw new Error('Method not implemented.'); - } - - editExperimentREST(): Observable<string> { - throw new Error('Method not implemented.'); - } - - startExperimentTransient(): Observable<string> { - throw new Error('Method not implemented.'); - } - - startExperiment(): Observable<string> { - throw new Error('Method not implemented.'); - } - - getExperiments(): string { - return '[]'; + return dummyUser; } getAlgorithmsREST(): string { diff --git a/api/src/engine/engine.controller.ts b/api/src/engine/engine.controller.ts index ffd77ead399601950d98d82433a5967deec7f71a..8a704c0032c0ece83f85df2e3158e2fa2fac5c64 100644 --- a/api/src/engine/engine.controller.ts +++ b/api/src/engine/engine.controller.ts @@ -1,9 +1,20 @@ -import { Controller, Get, Inject, Post, UseInterceptors } from '@nestjs/common'; +import { + Controller, + Get, + Inject, + Post, + Req, + UseGuards, + UseInterceptors, +} from '@nestjs/common'; +import { Request } from 'express'; import { Observable } from 'rxjs'; +import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard'; import { ENGINE_SERVICE } from './engine.constants'; import { IEngineService } from './engine.interfaces'; import { ErrorsInterceptor } from './interceptors/errors.interceptor'; +@UseGuards(JwtAuthGuard) @UseInterceptors(ErrorsInterceptor) @Controller() export class EngineController { @@ -12,27 +23,27 @@ export class EngineController { ) {} @Get('/algorithms') - getAlgorithms(): Observable<string> | string { - return this.engineService.getAlgorithmsREST(); + getAlgorithms(@Req() request: Request): Observable<string> | string { + return this.engineService.getAlgorithmsREST(request); } @Get('activeUser') - getActiveUser(): Observable<string> | string { - return this.engineService.getActiveUser(); + async getActiveUser(@Req() request: Request) { + return await this.engineService.getActiveUser(request); } @Post('activeUser/agreeNDA') - agreeNDA(): Observable<string> | string { - return this.engineService.editActiveUser(); + async agreeNDA(@Req() request: Request) { + return await this.engineService.updateUser(request); } @Get('logout') - logout(): void { - this.engineService.logout(); + logout(@Req() request: Request): void { + this.engineService.logout(request); } @Get('galaxy') - galaxy(): Observable<string> | string { - return this.engineService.getPassthrough?.('galaxy'); + galaxy(@Req() request: Request): Observable<string> | string { + return this.engineService.getPassthrough?.('galaxy', request); } } diff --git a/api/src/engine/engine.interfaces.ts b/api/src/engine/engine.interfaces.ts index 281ae1f3f5e51ba1c42ec28b91ede0f6637c668a..7d745e4559f96b582c873a7045491e89f8c0e83e 100644 --- a/api/src/engine/engine.interfaces.ts +++ b/api/src/engine/engine.interfaces.ts @@ -1,5 +1,7 @@ +import { Request } from 'express'; import { Observable } from 'rxjs'; -import { User } from 'src/auth/models/user.model'; +import { UpdateUserInput } from 'src/users/inputs/update-user.input'; +import { User } from '../users/models/user.model'; import { Configuration } from './models/configuration.model'; import { Domain } from './models/domain.model'; import { Algorithm } from './models/experiment/algorithm.model'; @@ -28,49 +30,44 @@ export interface IEngineService { */ getConfiguration?(): IConfiguration; - getDomains(ids: string[]): Domain[] | Promise<Domain[]>; + getDomains(ids: string[], req?: Request): Domain[] | Promise<Domain[]>; createExperiment( data: ExperimentCreateInput, isTransient: boolean, - ): Promise<Experiment> | Experiment; + req?: Request, + ): Promise<Experiment>; listExperiments( page: number, name: string, - ): Promise<ListExperiments> | ListExperiments; + req?: Request, + ): Promise<ListExperiments>; - getExperiment(id: string): Promise<Experiment> | Experiment; + getExperiment(id: string, req?: Request): Promise<Experiment>; - removeExperiment(id: string): Promise<PartialExperiment> | PartialExperiment; + removeExperiment(id: string, req?: Request): Promise<PartialExperiment>; editExperient( id: string, expriment: ExperimentEditInput, - ): Promise<Experiment> | Experiment; + req?: Request, + ): Promise<Experiment>; - getAlgorithms(): Promise<Algorithm[]> | Algorithm[]; + getAlgorithms(req?: Request): Promise<Algorithm[]>; // Standard REST API call - getAlgorithmsREST(): Observable<string> | string; + getAlgorithmsREST(req?: Request): Observable<string> | string; - getExperiments(): Observable<string> | string; + getActiveUser?(req?: Request): Promise<User>; - getExperimentREST(id: string): Observable<string> | string; + updateUser?( + req?: Request, + userId?: string, + data?: UpdateUserInput, + ): Promise<User>; - deleteExperiment(id: string): Observable<string> | string; - - editExperimentREST(id: string): Observable<string> | string; - - startExperimentTransient(): Observable<string> | string; - - startExperiment(): Observable<string> | string; - - getActiveUser(): Observable<string> | string; - - editActiveUser(): Observable<string> | string; - - logout?(): void; + logout?(req?: Request): Promise<void>; /** * Method that login a user with username and password @@ -83,5 +80,5 @@ export interface IEngineService { password: string, ): Promise<User | undefined> | User | undefined; - getPassthrough?(suffix: string): Observable<string> | string; + getPassthrough?(suffix: string, req?: Request): Observable<string> | string; } diff --git a/api/src/engine/engine.module.ts b/api/src/engine/engine.module.ts index 3f5cf47b16136f4485649ad6ef81b96ed2bc32e5..35c413825e7fe45ec4d98504c154199e83c68b06 100644 --- a/api/src/engine/engine.module.ts +++ b/api/src/engine/engine.module.ts @@ -25,14 +25,13 @@ export class EngineModule { const engineProvider = { provide: ENGINE_SERVICE, - useFactory: async (httpService: HttpService, req: Request) => { + useFactory: async (httpService: HttpService) => { return await this.createEngineConnection( optionsProvider.useValue, httpService, - req, ); }, - inject: [HttpService, REQUEST], + inject: [HttpService], }; return { @@ -47,14 +46,10 @@ export class EngineModule { private static async createEngineConnection( opt: IEngineOptions, httpService: HttpService, - req: Request, ): Promise<IEngineService> { try { const service = await import(`./connectors/${opt.type}/main.connector`); - const gqlRequest = req && req['req']; // graphql headers exception - const request = - gqlRequest && gqlRequest instanceof IncomingMessage ? gqlRequest : req; - const engine = new service.default(opt, httpService, request); + const engine = new service.default(opt, httpService); return engine; } catch (e) { diff --git a/api/src/engine/engine.resolver.ts b/api/src/engine/engine.resolver.ts index ac8845c7e04e77ddb05f56cebf1b1e02ad316fdd..a6a845dbec74d576d0468a4d1434f6373c886e0a 100644 --- a/api/src/engine/engine.resolver.ts +++ b/api/src/engine/engine.resolver.ts @@ -1,6 +1,8 @@ import { Inject, UseGuards } from '@nestjs/common'; import { Args, Mutation, Query, Resolver } from '@nestjs/graphql'; -import { JwtAuthGuard } from 'src/auth/guards/jwt-auth.guard'; +import { Request } from 'express'; +import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard'; +import { GQLRequest } from '../common/decorators/gql-request.decoractor'; import { Md5 } from 'ts-md5'; import { ENGINE_MODULE_OPTIONS, ENGINE_SERVICE } from './engine.constants'; import { IEngineOptions, IEngineService } from './engine.interfaces'; @@ -43,32 +45,35 @@ export class EngineResolver { @Query(() => [Domain]) async domains( + @GQLRequest() req: Request, @Args('ids', { nullable: true, type: () => [String], defaultValue: [] }) ids: string[], ) { - return this.engineService.getDomains(ids); + 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); + return this.engineService.listExperiments(page, name, req); } @Query(() => Experiment) - async experiment(@Args('id') id: string) { - return this.engineService.getExperiment(id); + async experiment(@Args('id') id: string, @GQLRequest() req: Request) { + return this.engineService.getExperiment(id, req); } @Query(() => [Algorithm]) - async algorithms() { - return this.engineService.getAlgorithms(); + 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, @@ -76,19 +81,24 @@ export class EngineResolver { 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); + return this.engineService.editExperient(id, experiment, req); } @Mutation(() => PartialExperiment) - async removeExperiment(@Args('id') id: string): Promise<PartialExperiment> { - return this.engineService.removeExperiment(id); + async removeExperiment( + @Args('id') id: string, + @GQLRequest() req: Request, + ): Promise<PartialExperiment> { + return this.engineService.removeExperiment(id, req); } } diff --git a/api/src/main/app.controller.spec.ts b/api/src/main/app.controller.spec.ts index d22f3890a380cea30641cfecc329b5c794ef5fb2..b39897fe6fb57d2e5dd63e65633fa19203de281a 100644 --- a/api/src/main/app.controller.spec.ts +++ b/api/src/main/app.controller.spec.ts @@ -15,8 +15,8 @@ describe('AppController', () => { }); describe('root', () => { - it('should return "Hello World!"', () => { - expect(appController.getHello()).toBe('Hello World!'); + it('should return status "OK"', () => { + expect(appController.getStatus()).toBe('OK'); }); }); }); diff --git a/api/src/main/app.controller.ts b/api/src/main/app.controller.ts index cce879ee622146012901c9adb47ef40c0fd3a555..1d102bb353ccbbbcc489ad727588b44b4c06e9a1 100644 --- a/api/src/main/app.controller.ts +++ b/api/src/main/app.controller.ts @@ -6,7 +6,7 @@ export class AppController { constructor(private readonly appService: AppService) {} @Get() - getHello(): string { - return this.appService.getHello(); + getStatus(): string { + return this.appService.getStatus(); } } diff --git a/api/src/main/app.module.ts b/api/src/main/app.module.ts index dc204f2c50b100bccff9430051c040538c50da4e..9ec49e6ee1808cc605e8e8eae247ddaa53210d27 100644 --- a/api/src/main/app.module.ts +++ b/api/src/main/app.module.ts @@ -2,10 +2,12 @@ import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo'; import { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { GraphQLModule } from '@nestjs/graphql'; +import { TypeOrmModule } from '@nestjs/typeorm'; import { join } from 'path'; import { AuthModule } from 'src/auth/auth.module'; import { EngineModule } from 'src/engine/engine.module'; import { FilesModule } from 'src/files/files.module'; +import { UsersModule } from 'src/users/users.module'; import { AppController } from './app.controller'; import { AppService } from './app.service'; @@ -28,7 +30,18 @@ import { AppService } from './app.service'; type: process.env.ENGINE_TYPE, baseurl: process.env.ENGINE_BASE_URL, }), + TypeOrmModule.forRoot({ + type: 'postgres', // type of our database + host: process.env.DB_HOST, // database host + port: parseInt(process.env.DB_PORT), // database host + username: process.env.DB_USERNAME, // username + password: process.env.DB_PASSWORD, // user password + database: process.env.DB_NAME, // name of our database, + autoLoadEntities: true, // models will be loaded automatically + synchronize: process.env.NODE_ENV !== 'production', // your entities will be synced with the database(recommended: disable in prod) + }), AuthModule, + UsersModule, FilesModule, ], controllers: [AppController], diff --git a/api/src/main/app.service.ts b/api/src/main/app.service.ts index 927d7cca0badb13577152bf8753ce3552358f53b..5deb38b10e98ccb9e4a34d8f34062c5c078dc807 100644 --- a/api/src/main/app.service.ts +++ b/api/src/main/app.service.ts @@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common'; @Injectable() export class AppService { - getHello(): string { - return 'Hello World!'; + getStatus(): string { + return 'OK'; } } diff --git a/api/src/schema.gql b/api/src/schema.gql index 0804f393996f71a257f74595bc84f138262bc2f4..5abb5414234e66fada8b5b8e582ab74f6144e228 100644 --- a/api/src/schema.gql +++ b/api/src/schema.gql @@ -11,7 +11,6 @@ type User { } type AuthenticationOutput { - user: User! accessToken: String! } @@ -231,6 +230,7 @@ type Query { experimentList(page: Float = 0, name: String = ""): ListExperiments! experiment(id: String!): Experiment! algorithms: [Algorithm!]! + user: User! } type Mutation { @@ -239,6 +239,7 @@ type Mutation { removeExperiment(id: String!): PartialExperiment! login(variables: AuthenticationInput!): AuthenticationOutput! logout: Boolean! + updateUser(updateUserInput: UpdateUserInput!): User! } input ExperimentCreateInput { @@ -285,3 +286,7 @@ input AuthenticationInput { username: String! password: String! } + +input UpdateUserInput { + agreeNDA: Boolean! +} diff --git a/api/src/users/inputs/update-user.input.ts b/api/src/users/inputs/update-user.input.ts new file mode 100644 index 0000000000000000000000000000000000000000..91a78d2e4b5618f4166ea07d6e72224687a584a3 --- /dev/null +++ b/api/src/users/inputs/update-user.input.ts @@ -0,0 +1,7 @@ +import { Field, InputType } from '@nestjs/graphql'; + +@InputType() +export class UpdateUserInput { + @Field() + agreeNDA?: boolean; +} diff --git a/api/src/auth/models/user.model.ts b/api/src/users/models/user.model.ts similarity index 77% rename from api/src/auth/models/user.model.ts rename to api/src/users/models/user.model.ts index 2352d59008cc9bf68feefd7b954108cf161730ed..fc6305264def3c8452e8fea306b8679378d90dc6 100644 --- a/api/src/auth/models/user.model.ts +++ b/api/src/users/models/user.model.ts @@ -1,7 +1,10 @@ import { Field, ObjectType } from '@nestjs/graphql'; +import { Entity, PrimaryColumn, Column } from 'typeorm'; +@Entity() @ObjectType() export class User { + @PrimaryColumn() @Field() id: string; @@ -14,6 +17,7 @@ export class User { @Field({ nullable: true }) email?: string; + @Column() @Field({ nullable: true }) agreeNDA?: boolean; diff --git a/api/src/users/users.module.ts b/api/src/users/users.module.ts new file mode 100644 index 0000000000000000000000000000000000000000..86e30c7f355ff7f1c3e0c3beb3f4f4bbcdbe7296 --- /dev/null +++ b/api/src/users/users.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { User } from './models/user.model'; +import { UsersResolver } from './users.resolver'; +import { UsersService } from './users.service'; + +@Module({ + imports: [TypeOrmModule.forFeature([User])], + providers: [UsersResolver, UsersService], +}) +export class UsersModule {} diff --git a/api/src/users/users.resolver.spec.ts b/api/src/users/users.resolver.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..e9ba43d9f33e895ea73a33e1b1ca77d5c15d5f6d --- /dev/null +++ b/api/src/users/users.resolver.spec.ts @@ -0,0 +1,80 @@ +import { getMockReq } from '@jest-mock/express'; +import { Test, TestingModule } from '@nestjs/testing'; +import { MockFunctionMetadata, ModuleMocker } from 'jest-mock'; +import { ENGINE_SERVICE } from '../engine/engine.constants'; +import { UpdateUserInput } from './inputs/update-user.input'; +import { User } from './models/user.model'; +import { UsersResolver } from './users.resolver'; +import { InternalUser, UsersService } from './users.service'; + +const moduleMocker = new ModuleMocker(global); + +describe('UsersResolver', () => { + let resolver: UsersResolver; + const req = getMockReq(); + const user: User = { + id: 'guest', + username: 'guest', + fullname: 'This is la Peste', + }; + + const updateData: UpdateUserInput = { + agreeNDA: true, + }; + + const internUser: InternalUser = { + id: 'guest', + agreeNDA: false, + }; + + const internUserWrong: InternalUser = { + id: 'guest1', + agreeNDA: false, + }; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [UsersResolver], + }) + .useMocker((token) => { + if (token == UsersService) { + return { + findOne: jest + .fn() + .mockResolvedValue(internUser) + .mockResolvedValueOnce(internUserWrong), + update: jest.fn().mockResolvedValue({ ...user, ...internUser }), + }; + } + if (token == ENGINE_SERVICE) { + return { + getActiveUser: jest.fn().mockResolvedValue(user), + }; + } + if (typeof token === 'function') { + const mockMetadata = moduleMocker.getMetadata( + token, + ) as MockFunctionMetadata<any, any>; + const Mock = moduleMocker.generateFromMetadata(mockMetadata); + return new Mock(); + } + }) + .compile(); + + resolver = module.get<UsersResolver>(UsersResolver); + }); + + it('getUser', async () => { + expect(await resolver.getUser(req, user)).toStrictEqual({ + ...user, + }); + expect(await resolver.getUser(req, user)).toStrictEqual({ + ...user, + ...internUser, + }); + }); + + it('updateUser', async () => { + expect(await resolver.updateUser(req, updateData, user)).toBeDefined(); + }); +}); diff --git a/api/src/users/users.resolver.ts b/api/src/users/users.resolver.ts new file mode 100644 index 0000000000000000000000000000000000000000..46c77cc1fb0260c9f0f7b6776a0661592ed2b912 --- /dev/null +++ b/api/src/users/users.resolver.ts @@ -0,0 +1,83 @@ +import { + Inject, + InternalServerErrorException, + Logger, + UseGuards, +} from '@nestjs/common'; +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'; + +@UseGuards(JwtAuthGuard) +@Resolver() +export class UsersResolver { + private readonly logger = new Logger(UsersResolver.name); + + constructor( + private readonly usersService: UsersService, + @Inject(ENGINE_SERVICE) private readonly engineService: IEngineService, + ) {} + + @Query(() => User, { name: 'user' }) + /** + * It returns the user from the engine, if it exists. Same from the internal database + * merge internal object over engine one to have a final user. + * @param {Request} request - Request + * @param {User} reqUser - The user that is currently logged in. + * @returns A user object. + */ + async getUser(@GQLRequest() request: Request, @CurrentUser() reqUser: User) { + const user: Partial<User> = {}; + + if (this.engineService.getActiveUser) { + const engineUser = await this.engineService.getActiveUser(request); + if (engineUser) Object.assign(user, engineUser); + } + + // Checking if the user exists in the internal database. If it does, it will assign the user to the `user` object. + try { + const internalUser = await this.usersService.findOne(reqUser.id); + + if (internalUser && (!user.id || internalUser.id === user.id)) { + Object.assign(user, internalUser); + } + } catch (e) { + this.logger.verbose(e); + } + + if (!user.id || !user.username) + throw new InternalServerErrorException( + 'The user cannot be construct from the engine', + ); + + return user as User; + } + + /** + * Update a user + * @param {Request} request - The incoming request object. + * @param {UpdateUserInput} updateUserInput - The input object that was passed to the mutation. + * @param {User} user - The user that is currently logged in. + * @returns The updated user. + */ + @Mutation(() => User) + async updateUser( + @GQLRequest() request: Request, + @Args('updateUserInput') updateUserInput: UpdateUserInput, + @CurrentUser() user: User, + ) { + if (this.engineService.updateUser) + return this.engineService.updateUser(request, user.id, updateUserInput); + + await this.usersService.update(user.id, updateUserInput); + + return await this.getUser(request, user); + } +} diff --git a/api/src/users/users.service.spec.ts b/api/src/users/users.service.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..f75bffee9617e6e0f29804d72e8f1cf199c7e684 --- /dev/null +++ b/api/src/users/users.service.spec.ts @@ -0,0 +1,60 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { getRepositoryToken } from '@nestjs/typeorm'; +import { MockFunctionMetadata, ModuleMocker } from 'jest-mock'; +import { UpdateUserInput } from './inputs/update-user.input'; +import { User } from './models/user.model'; +import { UsersService } from './users.service'; + +const moduleMocker = new ModuleMocker(global); + +describe('UsersService', () => { + let service: UsersService; + const user: User = { + id: 'guest', + username: 'guest', + agreeNDA: false, + }; + + const updateData: UpdateUserInput = { + agreeNDA: true, + }; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [UsersService], + }) + .useMocker((token) => { + if (token === getRepositoryToken(User)) { + return { + findOne: jest + .fn() + .mockResolvedValue(user) + .mockResolvedValueOnce(undefined), + save: jest.fn().mockResolvedValue({ ...user, ...updateData }), //todo + }; + } + if (typeof token === 'function') { + const mockMetadata = moduleMocker.getMetadata( + token, + ) as MockFunctionMetadata<any, any>; + const Mock = moduleMocker.generateFromMetadata(mockMetadata); + return new Mock(); + } + }) + .compile(); + + service = module.get<UsersService>(UsersService); + }); + + it('getUser', async () => { + expect(service.findOne('IdThatDoesNotExist')).rejects.toThrow(); + expect(await service.findOne('idThatExist')).toBe(user); + }); + + it('updateUser', async () => { + expect(await service.update('idThatExist', updateData)).toStrictEqual({ + ...user, + ...updateData, + }); + }); +}); diff --git a/api/src/users/users.service.ts b/api/src/users/users.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..273abf6de249a4f618f1039d4f00bc6cc29cfcd4 --- /dev/null +++ b/api/src/users/users.service.ts @@ -0,0 +1,43 @@ +import { Injectable, NotFoundException } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { UpdateUserInput } from './inputs/update-user.input'; +import { User } from './models/user.model'; + +export type InternalUser = Pick<User, 'id' | 'agreeNDA'>; + +@Injectable() +export class UsersService { + constructor( + @InjectRepository(User) + private readonly userRepository: Repository<InternalUser>, + ) {} + + /** + * Get a user by id + * @param {string} id - The id of the user to be retrieved. + * @returns The user object. + */ + async findOne(id: string): Promise<InternalUser> { + const user = await this.userRepository.findOne(id); + + if (!user) throw new NotFoundException(`User cannot be found in database.`); + + return user; + } + + /** + * Update a user + * @param {string} id - The id of the user to update. + * @param {UpdateUserInput} data - update params + * @returns The updated user. + */ + async update(id: string, data: UpdateUserInput): Promise<InternalUser> { + const test = { + id, + ...data, + }; + + return await this.userRepository.save(test); + } +}