From adffadd9ac189fd8610b5a0ab53fcd30b5d2e587 Mon Sep 17 00:00:00 2001 From: tuanh <tuan.hoangdinhanh@vn.bosch.com> Date: Fri, 13 Sep 2024 17:47:37 +0700 Subject: [PATCH 01/61] fix error in popular prototype route: req.user can be undefined --- src/controllers/prototype.controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/prototype.controller.js b/src/controllers/prototype.controller.js index e615401..cfdf3d9 100644 --- a/src/controllers/prototype.controller.js +++ b/src/controllers/prototype.controller.js @@ -65,7 +65,7 @@ const executeCode = catchAsync(async (req, res) => { }); const listPopularPrototypes = catchAsync(async (req, res) => { - const prototypes = await prototypeService.listPopularPrototypes(req.user.id); + const prototypes = await prototypeService.listPopularPrototypes(req.user?.id); res.send(prototypes); }); -- GitLab From 0caf1fdc30e75e6416a968dfc80b07ecbbbe711c Mon Sep 17 00:00:00 2001 From: tuanh <tuan.hoangdinhanh@vn.bosch.com> Date: Tue, 17 Sep 2024 11:11:21 +0700 Subject: [PATCH 02/61] remove the dependency to bosch/upload image --- docker-compose.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 65a108e..8936680 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -48,7 +48,6 @@ services: platform: linux/amd64 container_name: ${ENV:-dev}-upload build: ./upload/ - image: boschvn/upload:${IMAGE_TAG:-latest} volumes: - '${UPLOAD_PATH}:/usr/src/upload/data' networks: -- GitLab From 40014ba7e8cd253140c788cd5d91de58419be1c6 Mon Sep 17 00:00:00 2001 From: tuanh <tuan.hoangdinhanh@vn.bosch.com> Date: Tue, 17 Sep 2024 11:29:36 +0700 Subject: [PATCH 03/61] add try catch for upload operations --- src/controllers/auth.controller.js | 2 +- src/services/user.service.js | 29 ++++++++++++++++++++--------- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/src/controllers/auth.controller.js b/src/controllers/auth.controller.js index 27ab779..622c91b 100644 --- a/src/controllers/auth.controller.js +++ b/src/controllers/auth.controller.js @@ -1,6 +1,6 @@ const httpStatus = require('http-status'); const catchAsync = require('../utils/catchAsync'); -const { authService, userService, tokenService, emailService, fileService } = require('../services'); +const { authService, userService, tokenService, emailService } = require('../services'); const config = require('../config/config'); const ApiError = require('../utils/ApiError'); const logger = require('../config/logger'); diff --git a/src/services/user.service.js b/src/services/user.service.js index 4493a5c..7787878 100644 --- a/src/services/user.service.js +++ b/src/services/user.service.js @@ -3,6 +3,7 @@ const { User } = require('../models'); const ApiError = require('../utils/ApiError'); const image = require('../utils/image'); const fileService = require('./file.service'); +const logger = require('../config/logger'); /** * Create a user @@ -140,13 +141,18 @@ const updateSSOUser = async (user, graphData) => { updateBody.name = graphData.displayName; } - if (userPhoto) { - const photoBuffer = await userPhoto.arrayBuffer(); - const diff = await image.diff(user?.image_file, photoBuffer); - if (diff > 0.1 || diff === -1) { - const { url } = await fileService.upload(userPhoto); - updateBody.image_file = url; + try { + if (userPhoto) { + const photoBuffer = await userPhoto.arrayBuffer(); + const diff = await image.diff(user?.image_file, photoBuffer); + if (diff > 0.1 || diff === -1) { + const { url } = await fileService.upload(userPhoto); + updateBody.image_file = url; + } } + } catch (error) { + logger.error('Error updating user photo'); + logger.error(error); } if (Object.keys(updateBody).length === 0) { @@ -170,9 +176,14 @@ const createSSOUser = async (graphData) => { provider_user_id: graphData.id, }; - if (userPhoto) { - const { url } = await fileService.upload(userPhoto); - userBody.image_file = url; + try { + if (userPhoto) { + const { url } = await fileService.upload(userPhoto); + userBody.image_file = url; + } + } catch (error) { + logger.error('Error creating user photo'); + logger.error(error); } return createUser(userBody); -- GitLab From 52a5e24c339b5cf45b5ac18b5a6eda684b80d1ea Mon Sep 17 00:00:00 2001 From: tuanh <tuan.hoangdinhanh@vn.bosch.com> Date: Tue, 17 Sep 2024 15:02:27 +0700 Subject: [PATCH 04/61] use boschvn/upload image for backend-core --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 8936680..5250760 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -47,7 +47,7 @@ services: upload: platform: linux/amd64 container_name: ${ENV:-dev}-upload - build: ./upload/ + image: boschvn/upload:${IMAGE_TAG:-latest} volumes: - '${UPLOAD_PATH}:/usr/src/upload/data' networks: -- GitLab From d793ac6e75d035e6227d15339d828fbcf876c9fe Mon Sep 17 00:00:00 2001 From: tuanh <tuan.hoangdinhanh@vn.bosch.com> Date: Tue, 17 Sep 2024 17:44:30 +0700 Subject: [PATCH 05/61] update tag to latest --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 5250760..e878173 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -47,7 +47,7 @@ services: upload: platform: linux/amd64 container_name: ${ENV:-dev}-upload - image: boschvn/upload:${IMAGE_TAG:-latest} + image: boschvn/upload:latest volumes: - '${UPLOAD_PATH}:/usr/src/upload/data' networks: -- GitLab From eff98913935dd129575b4d9d6a2b42fcc6dde519 Mon Sep 17 00:00:00 2001 From: tuanh <tuan.hoangdinhanh@vn.bosch.com> Date: Tue, 17 Sep 2024 17:51:18 +0700 Subject: [PATCH 06/61] hide db port --- docker-compose.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index e878173..3623f64 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -36,8 +36,6 @@ services: playground-db: container_name: ${DB_CONTAINER_NAME:-playground-db} image: mongo:4.4.6-bionic - ports: - - '${MONGO_EXPOSE_PORT}:27017' volumes: - dbdata:/data/db networks: -- GitLab From eb8ed460a70180fcf26045d376c9445db45c94d0 Mon Sep 17 00:00:00 2001 From: tuanh <tuan.hoangdinhanh@vn.bosch.com> Date: Thu, 19 Sep 2024 18:46:09 +0700 Subject: [PATCH 07/61] trigger pipeline --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 40e017d..374c5d6 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# RESTful API Node Server Boilerplate +# RESTful API Node Server Boilerplate - t [](https://travis-ci.org/hagopj13/node-express-boilerplate) [](https://coveralls.io/github/hagopj13/node-express-boilerplate?branch=master) -- GitLab From 3fe29898bd1cd7f8256d529aa4ac6caea899ed21 Mon Sep 17 00:00:00 2001 From: tuanh <tuan.hoangdinhanh@vn.bosch.com> Date: Thu, 19 Sep 2024 19:10:58 +0700 Subject: [PATCH 08/61] trigger pipeline --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 374c5d6..bc73802 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# RESTful API Node Server Boilerplate - t +# RESTful API Node Server Boilerplate - te [](https://travis-ci.org/hagopj13/node-express-boilerplate) [](https://coveralls.io/github/hagopj13/node-express-boilerplate?branch=master) -- GitLab From ae22b578e5c69cee9cd37fa20da4f3f35d617cde Mon Sep 17 00:00:00 2001 From: tuanh <tuan.hoangdinhanh@vn.bosch.com> Date: Thu, 19 Sep 2024 19:27:48 +0700 Subject: [PATCH 09/61] trigger pipeline --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bc73802..b48be1d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# RESTful API Node Server Boilerplate - te +# RESTful API Node Server Boilerplate - tes [](https://travis-ci.org/hagopj13/node-express-boilerplate) [](https://coveralls.io/github/hagopj13/node-express-boilerplate?branch=master) -- GitLab From 2bc6fbd30995774d01e6dc86e7d73a3592f67987 Mon Sep 17 00:00:00 2001 From: tuanh <tuan.hoangdinhanh@vn.bosch.com> Date: Thu, 19 Sep 2024 20:01:34 +0700 Subject: [PATCH 10/61] trigger pipeline --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b48be1d..bc73802 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# RESTful API Node Server Boilerplate - tes +# RESTful API Node Server Boilerplate - te [](https://travis-ci.org/hagopj13/node-express-boilerplate) [](https://coveralls.io/github/hagopj13/node-express-boilerplate?branch=master) -- GitLab From a4142079dd3c96acb7ec097f28807b2cd74d5593 Mon Sep 17 00:00:00 2001 From: tuanh <tuan.hoangdinhanh@gmail.com> Date: Fri, 20 Sep 2024 12:17:13 +0000 Subject: [PATCH 11/61] interpolate .env file to upload service --- docker-compose.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index 3623f64..26dcb0a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -46,6 +46,8 @@ services: platform: linux/amd64 container_name: ${ENV:-dev}-upload image: boschvn/upload:latest + env_file: + - .env volumes: - '${UPLOAD_PATH}:/usr/src/upload/data' networks: -- GitLab From 23bc4400d5da3eefd464e69e0fb301c82e0ea06b Mon Sep 17 00:00:00 2001 From: tuanh <tuan.hoangdinhanh@vn.bosch.com> Date: Wed, 25 Sep 2024 17:12:33 +0700 Subject: [PATCH 12/61] add etas genai stream --- package.json | 3 +- src/app.js | 3 + src/controllers/genai.controller.js | 3 +- src/utils/setupEtasStream.js | 35 +++++++++ yarn.lock | 114 +++++++++++++++++++++++++--- 5 files changed, 146 insertions(+), 12 deletions(-) create mode 100644 src/utils/setupEtasStream.js diff --git a/package.json b/package.json index 9aecb60..22c7775 100644 --- a/package.json +++ b/package.json @@ -76,11 +76,12 @@ "passport": "^0.4.0", "passport-jwt": "^4.0.0", "pm2": "^5.1.0", - "socket.io": "^4.7.5", + "socket.io": "^4.8.0", "swagger-jsdoc": "^6.0.8", "swagger-ui-express": "^4.1.6", "utf-8-validate": "^6.0.4", "validator": "^13.0.0", + "websocket": "^1.0.35", "winston": "^3.2.1", "xss-clean": "^0.1.1" }, diff --git a/src/app.js b/src/app.js index 97a3c4e..fadb9f5 100644 --- a/src/app.js +++ b/src/app.js @@ -16,6 +16,7 @@ const routesV2 = require('./routes/v2'); const { errorConverter, errorHandler } = require('./middlewares/error'); const ApiError = require('./utils/ApiError'); const setupProxy = require('./config/proxyHandler'); +const { init: initSocketIO } = require('./config/socket'); const app = express(); @@ -75,6 +76,8 @@ app.use('/v2', routesV2); // Setup proxy to other services setupProxy(app); +const server = require('http').createServer(app); +initSocketIO(server); // send back a 404 error for any unknown api request app.use((req, res, next) => { diff --git a/src/controllers/genai.controller.js b/src/controllers/genai.controller.js index 45b616f..78fe88e 100644 --- a/src/controllers/genai.controller.js +++ b/src/controllers/genai.controller.js @@ -10,6 +10,7 @@ const config = require('../config/config'); const axios = require('axios'); const etasAuthorizationData = require('../states/etasAuthorization'); const moment = require('moment'); +const { setupClient } = require('../utils/setupEtasStream'); dotenv.config(); @@ -267,7 +268,7 @@ const generateAIContent = async (req, res) => { const instance = config.etas.instanceEndpoint; - // console.log('ETAS_INSTANCE_ENDPOINT', instance); + setupClient(token); const response = await axios.post( `https://${instance}/r2mm/GENERATE_AI`, diff --git a/src/utils/setupEtasStream.js b/src/utils/setupEtasStream.js new file mode 100644 index 0000000..b4303ae --- /dev/null +++ b/src/utils/setupEtasStream.js @@ -0,0 +1,35 @@ +const WebSocketClient = require('websocket').client; +const { getIO } = require('../config/socket'); +let client = null; + +const setupClient = (token) => { + if (!client) { + const client = new WebSocketClient(); + + client.on('connectFailed', function (error) { + console.log('Connect etas websocket error: ' + error.toString()); + }); + client.on('connect', function (connection) { + console.log('Etas WebSocket Client Connected'); + connection.on('error', function (error) { + console.log('Connection Error: ' + error.toString()); + }); + connection.on('close', function () { + console.log('echo-protocol Connection Closed'); + }); + connection.on('message', function (message) { + const io = getIO(); + console.log(message.utf8Data); + io?.sockets?.emit('etas-stream', message.utf8Data); + }); + }); + + client.connect('wss://a96aee443681c44864878093f03f1586.app.pantaris.io/ws', null, null, { + authorization: `Bearer ${token}`, + }); + } +}; + +module.exports = { + setupClient, +}; diff --git a/yarn.lock b/yarn.lock index 7073154..f7115b7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3058,7 +3058,7 @@ buffer@^5.2.0, buffer@^5.5.0: base64-js "^1.3.1" ieee754 "^1.1.13" -bufferutil@^4.0.8: +bufferutil@^4.0.1, bufferutil@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-4.0.8.tgz#1de6a71092d65d7766c4d8a522b261a6e787e8ea" integrity sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw== @@ -3598,6 +3598,14 @@ culvert@^0.1.2: resolved "https://registry.yarnpkg.com/culvert/-/culvert-0.1.2.tgz#9502f5f0154a2d5a22a023e79f71cc936fa6ef6f" integrity sha1-lQL18BVKLVoioCPnn3HMk2+m728= +d@1, d@^1.0.1, d@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/d/-/d-1.0.2.tgz#2aefd554b81981e7dccf72d6842ae725cb17e5de" + integrity sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw== + dependencies: + es5-ext "^0.10.64" + type "^2.7.2" + dashdash@^1.12.0: version "1.14.1" resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" @@ -3939,10 +3947,10 @@ engine.io-parser@~5.2.1: resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-5.2.2.tgz#37b48e2d23116919a3453738c5720455e64e1c49" integrity sha512-RcyUFKA93/CXH20l4SoVvzZfrSDMOTUS3bWVpTt2FuFP+XYrL8i8oonHP7WInRyVHXh0n/ORtoeiE1os+8qkSw== -engine.io@~6.5.2: - version "6.5.5" - resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-6.5.5.tgz#430b80d8840caab91a50e9e23cb551455195fc93" - integrity sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA== +engine.io@~6.6.0: + version "6.6.1" + resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-6.6.1.tgz#a82b1e5511239a0e95fac14516870ee9138febc8" + integrity sha512-NEpDCw9hrvBW+hVEOK4T7v0jFJ++KgtPl4jKFwsZVfG1XhS0dCrSb3VMb9gPAd7VAdW52VT1EnaNiU2vM8C0og== dependencies: "@types/cookie" "^0.4.1" "@types/cors" "^2.8.12" @@ -4000,6 +4008,33 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" +es5-ext@^0.10.35, es5-ext@^0.10.62, es5-ext@^0.10.63, es5-ext@^0.10.64, es5-ext@~0.10.14: + version "0.10.64" + resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.64.tgz#12e4ffb48f1ba2ea777f1fcdd1918ef73ea21714" + integrity sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg== + dependencies: + es6-iterator "^2.0.3" + es6-symbol "^3.1.3" + esniff "^2.0.1" + next-tick "^1.1.0" + +es6-iterator@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" + integrity sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g== + dependencies: + d "1" + es5-ext "^0.10.35" + es6-symbol "^3.1.1" + +es6-symbol@^3.1.1, es6-symbol@^3.1.3: + version "3.1.4" + resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.4.tgz#f4e7d28013770b4208ecbf3e0bf14d3bcb557b8c" + integrity sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg== + dependencies: + d "^1.0.2" + ext "^1.7.0" + escalade@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" @@ -4204,6 +4239,16 @@ eslint@^7.0.0: text-table "^0.2.0" v8-compile-cache "^2.0.3" +esniff@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/esniff/-/esniff-2.0.1.tgz#a4d4b43a5c71c7ec51c51098c1d8a29081f9b308" + integrity sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg== + dependencies: + d "^1.0.1" + es5-ext "^0.10.62" + event-emitter "^0.3.5" + type "^2.7.2" + espree@^7.3.0, espree@^7.3.1: version "7.3.1" resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.1.tgz#f2df330b752c6f55019f8bd89b7660039c1bbbb6" @@ -4252,6 +4297,14 @@ etag@~1.8.1: resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= +event-emitter@^0.3.5: + version "0.3.5" + resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39" + integrity sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA== + dependencies: + d "1" + es5-ext "~0.10.14" + event-target-shim@^5.0.0: version "5.0.1" resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" @@ -4416,6 +4469,13 @@ express@^4.17.1: utils-merge "1.0.1" vary "~1.1.2" +ext@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/ext/-/ext-1.7.0.tgz#0ea4383c0103d60e70be99e9a7f11027a33c4f5f" + integrity sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw== + dependencies: + type "^2.7.2" + extend-shallow@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" @@ -7071,6 +7131,11 @@ netmask@^2.0.1: resolved "https://registry.yarnpkg.com/netmask/-/netmask-2.0.2.tgz#8b01a07644065d536383835823bc52004ebac5e7" integrity sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg== +next-tick@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb" + integrity sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ== + nice-try@^1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" @@ -8668,16 +8733,16 @@ socket.io-parser@~4.2.4: "@socket.io/component-emitter" "~3.1.0" debug "~4.3.1" -socket.io@^4.7.5: - version "4.7.5" - resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-4.7.5.tgz#56eb2d976aef9d1445f373a62d781a41c7add8f8" - integrity sha512-DmeAkF6cwM9jSfmp6Dr/5/mfMwb5Z5qRrSXLpo3Fq5SqyU8CMF15jIN4ZhfSwu35ksM1qmHZDQ/DK5XTccSTvA== +socket.io@^4.8.0: + version "4.8.0" + resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-4.8.0.tgz#33d05ae0915fad1670bd0c4efcc07ccfabebe3b1" + integrity sha512-8U6BEgGjQOfGz3HHTYaC/L1GaxDCJ/KM0XTkJly0EhZ5U/du9uNEZy4ZgYzEzIqlx2CMm25CrCqr1ck899eLNA== dependencies: accepts "~1.3.4" base64id "~2.0.0" cors "~2.8.5" debug "~4.3.2" - engine.io "~6.5.2" + engine.io "~6.6.0" socket.io-adapter "~2.5.2" socket.io-parser "~4.2.4" @@ -9392,6 +9457,11 @@ type-is@^1.6.18, type-is@~1.6.17, type-is@~1.6.18: media-typer "0.3.0" mime-types "~2.1.24" +type@^2.7.2: + version "2.7.3" + resolved "https://registry.yarnpkg.com/type/-/type-2.7.3.tgz#436981652129285cc3ba94f392886c2637ea0486" + integrity sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ== + typedarray-to-buffer@^3.1.5: version "3.1.5" resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" @@ -9499,6 +9569,13 @@ use@^3.1.0: resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== +utf-8-validate@^5.0.2: + version "5.0.10" + resolved "https://registry.yarnpkg.com/utf-8-validate/-/utf-8-validate-5.0.10.tgz#d7d10ea39318171ca982718b6b96a8d2442571a2" + integrity sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ== + dependencies: + node-gyp-build "^4.3.0" + utf-8-validate@^6.0.4: version "6.0.4" resolved "https://registry.yarnpkg.com/utf-8-validate/-/utf-8-validate-6.0.4.tgz#1305a1bfd94cecb5a866e6fc74fd07f3ed7292e5" @@ -9649,6 +9726,18 @@ websocket-extensions@>=0.1.1: resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== +websocket@^1.0.35: + version "1.0.35" + resolved "https://registry.yarnpkg.com/websocket/-/websocket-1.0.35.tgz#374197207d7d4cc4c36cbf8a1bb886ee52a07885" + integrity sha512-/REy6amwPZl44DDzvRCkaI1q1bIiQB0mEFQLUrhz3z2EK91cp3n72rAjUlrTP0zV22HJIUOVHQGPxhFRjxjt+Q== + dependencies: + bufferutil "^4.0.1" + debug "^2.2.0" + es5-ext "^0.10.63" + typedarray-to-buffer "^3.1.5" + utf-8-validate "^5.0.2" + yaeti "^0.0.6" + whatwg-encoding@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0" @@ -9879,6 +9968,11 @@ y18n@^5.0.5: resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== +yaeti@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/yaeti/-/yaeti-0.0.6.tgz#f26f484d72684cf42bedfb76970aa1608fbf9577" + integrity sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug== + yallist@^2.0.0: version "2.1.2" resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" -- GitLab From e8e88ef1c98f580454139dd6180083d926b5a8c2 Mon Sep 17 00:00:00 2001 From: tuanh <tuan.hoangdinhanh@vn.bosch.com> Date: Wed, 25 Sep 2024 17:44:52 +0700 Subject: [PATCH 13/61] add socket authentication --- src/config/passport.js | 1 + src/config/socket.js | 29 +++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/src/config/passport.js b/src/config/passport.js index d92ebf3..73feed0 100644 --- a/src/config/passport.js +++ b/src/config/passport.js @@ -31,4 +31,5 @@ const jwtStrategy = new JwtStrategy(jwtOptions, jwtVerify); module.exports = { jwtStrategy, + jwtVerify, }; diff --git a/src/config/socket.js b/src/config/socket.js index 5b9790a..9f4c32a 100644 --- a/src/config/socket.js +++ b/src/config/socket.js @@ -1,5 +1,11 @@ const { Server } = require('socket.io'); const logger = require('./logger'); +const ApiError = require('../utils/ApiError'); +const httpStatus = require('http-status'); +const jwt = require('jsonwebtoken'); +const config = require('./config'); +const { jwtVerify } = require('./passport'); +const { tokenTypes } = require('./tokens'); let io = null; @@ -17,6 +23,29 @@ const init = (server) => { }, }); + io.use(function (socket, next) { + if (socket.handshake.headers && socket.handshake.headers.access_token) { + jwt.verify(socket.handshake.headers.access_token, config.jwt.secret, async function (err, decoded) { + if (err) return next(new ApiError(httpStatus.UNAUTHORIZED, 'Please authenticate')); + await jwtVerify( + { + type: tokenTypes.ACCESS, + sub: decoded.sub, + }, + (error, user) => { + if (error || !user) { + return next(new ApiError(httpStatus.UNAUTHORIZED, 'Please authenticate')); + } + socket.user = user; + next(); + } + ); + }); + } else { + next(new ApiError(httpStatus.UNAUTHORIZED, 'Please authenticate')); + } + }); + io.on('connection', () => { logger.info('a user connected'); }); -- GitLab From 0f56c9ee64b286e98ac4a9e124e23c610d75d892 Mon Sep 17 00:00:00 2001 From: tuanh <tuan.hoangdinhanh@vn.bosch.com> Date: Thu, 26 Sep 2024 11:13:22 +0700 Subject: [PATCH 14/61] update cors --- src/app.js | 15 +-------------- src/config/config.js | 10 ++++++++++ src/config/socket.js | 8 +------- 3 files changed, 12 insertions(+), 21 deletions(-) diff --git a/src/app.js b/src/app.js index fadb9f5..9391ce7 100644 --- a/src/app.js +++ b/src/app.js @@ -47,14 +47,7 @@ app.use(compression()); // enable cors app.use( cors({ - origin: [ - /localhost:\d+/, - /\.digitalauto\.tech$/, - /\.digitalauto\.asia$/, - /\.digital\.auto$/, - 'https://digitalauto.netlify.app', - /127\.0\.0\.1:\d+/, - ], + origin: config.cors.regex, credentials: true, }) ); @@ -64,12 +57,6 @@ app.options('*', cors()); app.use(passport.initialize()); passport.use('jwt', jwtStrategy); -// limit repeated failed requests to auth endpoints -// if (config.env === 'production') { -// app.use('/v1/auth', authLimiter); -// app.use('/v2/auth', authLimiter); -// } - // v1 api routes app.use('/v1', routes); app.use('/v2', routesV2); diff --git a/src/config/config.js b/src/config/config.js index eddc37d..12ef795 100644 --- a/src/config/config.js +++ b/src/config/config.js @@ -62,6 +62,16 @@ const config = { env: envVars.NODE_ENV, port: envVars.PORT, strictAuth: envVars.STRICT_AUTH, + cors: { + regex: [ + /localhost:\d+/, + /\.digitalauto\.tech$/, + /\.digitalauto\.asia$/, + /\.digital\.auto$/, + 'https://digitalauto.netlify.app', + /127\.0\.0\.1:\d+/, + ], + }, mongoose: { url: envVars.MONGODB_URL + (envVars.NODE_ENV === 'test' ? '-test' : ''), options: { diff --git a/src/config/socket.js b/src/config/socket.js index 9f4c32a..3ab1d94 100644 --- a/src/config/socket.js +++ b/src/config/socket.js @@ -12,13 +12,7 @@ let io = null; const init = (server) => { io = new Server(server, { cors: { - origin: [ - /localhost:\d+/, - /\.digitalauto\.tech$/, - /\.digitalauto\.asia$/, - /\.digital\.auto$/, - 'https://digitalauto.netlify.app', - ], + origin: config.cors.regex, credentials: true, }, }); -- GitLab From c30678028263118324fcf156349f0ca4432d6b7a Mon Sep 17 00:00:00 2001 From: tuanh <tuan.hoangdinhanh@vn.bosch.com> Date: Thu, 26 Sep 2024 11:42:18 +0700 Subject: [PATCH 15/61] change token from header to query --- src/config/socket.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/config/socket.js b/src/config/socket.js index 3ab1d94..fec91dc 100644 --- a/src/config/socket.js +++ b/src/config/socket.js @@ -18,8 +18,8 @@ const init = (server) => { }); io.use(function (socket, next) { - if (socket.handshake.headers && socket.handshake.headers.access_token) { - jwt.verify(socket.handshake.headers.access_token, config.jwt.secret, async function (err, decoded) { + if (socket.handshake.query && socket.handshake.query.access_token) { + jwt.verify(socket.handshake.query.access_token, config.jwt.secret, async function (err, decoded) { if (err) return next(new ApiError(httpStatus.UNAUTHORIZED, 'Please authenticate')); await jwtVerify( { -- GitLab From ebf51f40859156649e8f0d8ee17950432e58e297 Mon Sep 17 00:00:00 2001 From: Tuan Hoang Dinh Anh <tuan.hoangdinhanh@vn.bosch.com> Date: Tue, 1 Oct 2024 12:02:46 +0000 Subject: [PATCH 16/61] Update setupEtasStream.js --- src/utils/setupEtasStream.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/utils/setupEtasStream.js b/src/utils/setupEtasStream.js index b4303ae..4e3e194 100644 --- a/src/utils/setupEtasStream.js +++ b/src/utils/setupEtasStream.js @@ -1,5 +1,6 @@ const WebSocketClient = require('websocket').client; const { getIO } = require('../config/socket'); +const config = require('../config/config.js'); let client = null; const setupClient = (token) => { @@ -24,7 +25,7 @@ const setupClient = (token) => { }); }); - client.connect('wss://a96aee443681c44864878093f03f1586.app.pantaris.io/ws', null, null, { + client.connect(`wss://${config.etas.instanceEndpoint}/ws`, null, null, { authorization: `Bearer ${token}`, }); } -- GitLab From b8e2ced46c592f2560cadf85ba9519380b6a279d Mon Sep 17 00:00:00 2001 From: tuanh <tuan.hoangdinhanh@vn.bosch.com> Date: Tue, 1 Oct 2024 16:20:14 +0200 Subject: [PATCH 17/61] clearer error log for genai bedrock --- src/controllers/genai.controller.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/controllers/genai.controller.js b/src/controllers/genai.controller.js index 45b616f..ee33903 100644 --- a/src/controllers/genai.controller.js +++ b/src/controllers/genai.controller.js @@ -169,6 +169,8 @@ async function BedrockGenCode({ endpointURL, publicKey, secretKey, inputPrompt, } } } catch (error) { + console.log("Error in BedrockGenCode"); + console.log(error) try { const bedrockResponse = await bedrock.send(new InvokeModelCommand(input)); const response = JSON.parse(new TextDecoder().decode(bedrockResponse.body)); @@ -176,7 +178,7 @@ async function BedrockGenCode({ endpointURL, publicKey, secretKey, inputPrompt, generation = response.completions[0].data.text; } } catch (err) { - throw new Error('Failed to generate response from Bedrock, please check your endpoint URL, region, keys'); + throw err; } } return generation; -- GitLab From af4e81b6d102c413b9deaa6bebe134b06bb18f9f Mon Sep 17 00:00:00 2001 From: tuanh <tuan.hoangdinhanh@vn.bosch.com> Date: Wed, 2 Oct 2024 15:08:25 +0200 Subject: [PATCH 18/61] increase timeout for kong --- kong.yml.template | 1 + 1 file changed, 1 insertion(+) diff --git a/kong.yml.template b/kong.yml.template index 657cfb6..5228ef8 100644 --- a/kong.yml.template +++ b/kong.yml.template @@ -18,6 +18,7 @@ services: - HEAD - CONNECT - TRACE + read_timeout: 600000 plugins: - name: rate-limiting enabled: true -- GitLab From 8f3e1517c7bf96454d0123b51761b98dac715c2a Mon Sep 17 00:00:00 2001 From: tuanh <tuan.hoangdinhanh@vn.bosch.com> Date: Tue, 8 Oct 2024 10:14:41 +0200 Subject: [PATCH 19/61] add permissions check --- kong.yml.template | 2 +- src/config/socket.js | 7 ++++++- src/routes/v2/genai.route.js | 1 + 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/kong.yml.template b/kong.yml.template index 5228ef8..c146d79 100644 --- a/kong.yml.template +++ b/kong.yml.template @@ -18,7 +18,7 @@ services: - HEAD - CONNECT - TRACE - read_timeout: 600000 + read_timeout: 180000 plugins: - name: rate-limiting enabled: true diff --git a/src/config/socket.js b/src/config/socket.js index fec91dc..cb7328d 100644 --- a/src/config/socket.js +++ b/src/config/socket.js @@ -6,6 +6,8 @@ const jwt = require('jsonwebtoken'); const config = require('./config'); const { jwtVerify } = require('./passport'); const { tokenTypes } = require('./tokens'); +const permissionService = require('../services/permission.service'); +const { PERMISSIONS } = require('./roles'); let io = null; @@ -26,10 +28,13 @@ const init = (server) => { type: tokenTypes.ACCESS, sub: decoded.sub, }, - (error, user) => { + async (error, user) => { if (error || !user) { return next(new ApiError(httpStatus.UNAUTHORIZED, 'Please authenticate')); } + if (!(await permissionService.hasPermission(user.id, PERMISSIONS.GENERATIVE_AI))) { + return next(new ApiError(httpStatus.UNAUTHORIZED, 'You are not authorized to access this')); + } socket.user = user; next(); } diff --git a/src/routes/v2/genai.route.js b/src/routes/v2/genai.route.js index d2af9a0..bbc5166 100644 --- a/src/routes/v2/genai.route.js +++ b/src/routes/v2/genai.route.js @@ -33,6 +33,7 @@ router.post( auth({ optional: !config.strictAuth, }), + genaiPermission, genaiController.generateAIContent ); -- GitLab From 72dd752ad0a33addc00e42a334d1638ba08d4b2f Mon Sep 17 00:00:00 2001 From: tuanh <tuan.hoangdinhanh@vn.bosch.com> Date: Fri, 18 Oct 2024 17:42:20 +0700 Subject: [PATCH 20/61] add script to check for vss versions update --- .gitignore | 2 ++ src/index.js | 3 ++ src/scripts/checkVSSUpdate.js | 54 +++++++++++++++++++++++++++++++++++ 3 files changed, 59 insertions(+) create mode 100644 src/scripts/checkVSSUpdate.js diff --git a/.gitignore b/.gitignore index 349f2b1..cfd5924 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,5 @@ coverage .netlify kong.yml + +clock.txt diff --git a/src/index.js b/src/index.js index dd72d88..54664d1 100644 --- a/src/index.js +++ b/src/index.js @@ -4,6 +4,7 @@ const config = require('./config/config'); const logger = require('./config/logger'); const initializeRoles = require('./utils/initializeRoles'); const { init } = require('./config/socket'); +const setupScheduledCheck = require('./scripts/checkVSSUpdate'); let server; mongoose.connect(config.mongoose.url, config.mongoose.options).then(() => { @@ -40,3 +41,5 @@ process.on('SIGTERM', () => { server.close(); } }); + +setupScheduledCheck(); diff --git a/src/scripts/checkVSSUpdate.js b/src/scripts/checkVSSUpdate.js new file mode 100644 index 0000000..3ab3b7c --- /dev/null +++ b/src/scripts/checkVSSUpdate.js @@ -0,0 +1,54 @@ +const axios = require('axios'); +const fs = require('fs'); +const moment = require('moment'); + +const setLastCheckTime = () => { + fs.writeFileSync('clock.txt', moment().format()); +}; + +const getLastCheckTime = () => { + try { + const lastCheckTime = fs.readFileSync('clock.txt', 'utf8'); + return moment(lastCheckTime); + } catch (error) { + return moment().subtract(1, 'hour'); + } +}; + +const updateVSS = async () => { + try { + const vssReleases = (await axios.get('https://api.github.com/repos/COVESA/vehicle_signal_specification/releases')).data; + console.log('vssReleases'); + console.log('Hi i just triggered'); + setLastCheckTime(); + + let filtered = vssReleases?.map((release) => { + return { + name: release.name, + published_at: release.published_at, + assetUrl: release.assets?.find((asset) => asset.name.endsWith('.json'))?.url, + }; + }); + + filtered = filtered?.filter((release) => !(release.name && release.name.includes('rc'))); + + console.log('filtered'); + console.log(filtered); + } catch (error) { + console.error(error); + } +}; + +let interval = null; + +const setupScheduledCheck = () => { + const lastCheckTime = getLastCheckTime(); + if (moment().diff(lastCheckTime, 'second') > 1) { + updateVSS(); + } + if (!interval) { + interval = setInterval(updateVSS, 1000 * 60 * 60 * 24); + } +}; + +module.exports = setupScheduledCheck; -- GitLab From ed800e9fe70393d515b2404ff0169a97232065d9 Mon Sep 17 00:00:00 2001 From: tuanh <tuan.hoangdinhanh@vn.bosch.com> Date: Mon, 21 Oct 2024 11:26:20 +0700 Subject: [PATCH 21/61] add feature to list vss versions --- .dockerignore | 1 + .eslintignore | 1 + .gitignore | 3 +- .prettierignore | 2 +- package.json | 4 +- src/controllers/api.controller.js | 12 ++++++ src/routes/v2/api.route.js | 3 ++ src/scripts/checkVSSUpdate.js | 66 ++++++++++++++++++++++--------- src/services/api.service.js | 26 ++++++++++++ src/services/file.service.js | 12 ++++++ src/validations/api.validation.js | 7 ++++ yarn.lock | 7 +++- 12 files changed, 121 insertions(+), 23 deletions(-) diff --git a/.dockerignore b/.dockerignore index b99e7de..f5a5e24 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,3 +1,4 @@ node_modules .git .gitignore +data diff --git a/.eslintignore b/.eslintignore index ed4598e..cff71ae 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,2 +1,3 @@ node_modules bin +data diff --git a/.gitignore b/.gitignore index cfd5924..c915689 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,5 @@ coverage kong.yml -clock.txt + +data diff --git a/.prettierignore b/.prettierignore index 6895bf0..161501f 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,3 +1,3 @@ node_modules coverage - +data diff --git a/package.json b/package.json index 22c7775..5205b1c 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ }, "scripts": { "start": "pm2 start ecosystem.config.json --no-daemon", - "dev": "cross-env NODE_ENV=development nodemon src/index.js", + "dev": "cross-env NODE_ENV=development nodemon --ignore 'data/*' src/index.js", "test": "jest -i --colors --verbose --detectOpenHandles", "test:watch": "jest -i --watchAll", "coverage": "jest -i --coverage", @@ -69,6 +69,7 @@ "jimp": "^0.22.12", "joi": "^17.3.0", "jsonwebtoken": "^8.5.1", + "lodash": "^4.17.21", "moment": "^2.24.0", "mongoose": "^5.7.7", "morgan": "^1.9.1", @@ -86,6 +87,7 @@ "xss-clean": "^0.1.1" }, "devDependencies": { + "@types/lodash": "^4.17.12", "@types/mongoose": "^5.11.97", "coveralls": "^3.0.7", "eslint": "^7.0.0", diff --git a/src/controllers/api.controller.js b/src/controllers/api.controller.js index 8cf7522..498268e 100644 --- a/src/controllers/api.controller.js +++ b/src/controllers/api.controller.js @@ -31,10 +31,22 @@ const deleteApi = catchAsync(async (req, res) => { res.status(httpStatus.NO_CONTENT).send(); }); +const listVSSVersions = catchAsync(async (req, res) => { + const versions = await apiService.listVSSVersions(); + res.send(versions); +}); + +const getVSSVersion = catchAsync(async (req, res) => { + const version = await apiService.getVSSVersion(req.params.name); + res.send(version); +}); + module.exports = { createApi, getApiByModelId, getApi, updateApi, deleteApi, + listVSSVersions, + getVSSVersion, }; diff --git a/src/routes/v2/api.route.js b/src/routes/v2/api.route.js index b9bd30b..b0874c8 100644 --- a/src/routes/v2/api.route.js +++ b/src/routes/v2/api.route.js @@ -7,6 +7,9 @@ const config = require('../../config/config'); const router = express.Router(); +router.route('/vss').get(apiController.listVSSVersions); +router.route('/vss/:name').get(validate(apiValidation.getVSSVersion), apiController.getVSSVersion); + router.route('/').post(auth(), validate(apiValidation.createApi), apiController.createApi); router .route('/:id') diff --git a/src/scripts/checkVSSUpdate.js b/src/scripts/checkVSSUpdate.js index 3ab3b7c..dc6cb42 100644 --- a/src/scripts/checkVSSUpdate.js +++ b/src/scripts/checkVSSUpdate.js @@ -1,41 +1,69 @@ const axios = require('axios'); const fs = require('fs'); +const path = require('path'); const moment = require('moment'); +const _ = require('lodash'); +const logger = require('../config/logger'); +const { fileService } = require('../services'); const setLastCheckTime = () => { - fs.writeFileSync('clock.txt', moment().format()); + fs.writeFileSync(path.join(__dirname, '../../data/clock.txt'), moment().format()); }; const getLastCheckTime = () => { try { - const lastCheckTime = fs.readFileSync('clock.txt', 'utf8'); + const lastCheckTime = fs.readFileSync(path.join(__dirname, '../../data/clock.txt'), 'utf8'); return moment(lastCheckTime); } catch (error) { return moment().subtract(1, 'hour'); } }; -const updateVSS = async () => { +/** + * + * @param {{name: string; published_at: string; browser_download_url: string}[]} releases + */ +const updateVSS = async (releases) => { + try { + fs.writeFileSync(path.join(__dirname, '../../data/vss.json'), JSON.stringify(releases, null, 2)); + logger.info('Updated VSS version list'); + logger.info('Downloading VSS versions data'); + const promises = releases.map((release) => + fileService.downloadFile(release.browser_download_url, path.join(__dirname, `../../data/${release.name}.json`)) + ); + await Promise.all(promises); + logger.info('Downloaded VSS versions data'); + } catch (error) { + logger.error(error); + } +}; + +const checkUpdateVSS = async () => { try { const vssReleases = (await axios.get('https://api.github.com/repos/COVESA/vehicle_signal_specification/releases')).data; - console.log('vssReleases'); - console.log('Hi i just triggered'); setLastCheckTime(); - let filtered = vssReleases?.map((release) => { - return { - name: release.name, - published_at: release.published_at, - assetUrl: release.assets?.find((asset) => asset.name.endsWith('.json'))?.url, - }; - }); + const filtered = vssReleases + ?.filter((release) => !(release.name && release.name.includes('rc'))) + ?.map((release) => { + return { + name: release.name, + published_at: release.published_at, + browser_download_url: release.assets?.find((asset) => asset.name.endsWith('.json'))?.browser_download_url, + }; + }); + + const vssFilePath = path.join(__dirname, '../../data/vss.json'); - filtered = filtered?.filter((release) => !(release.name && release.name.includes('rc'))); + try { + if (fs.existsSync(vssFilePath) && _.isEqual(filtered, JSON.parse(fs.readFileSync(vssFilePath, 'utf8')))) return; + } catch (error) { + logger.warn(error); + } - console.log('filtered'); - console.log(filtered); + await updateVSS(filtered); } catch (error) { - console.error(error); + logger.error(error); } }; @@ -43,11 +71,11 @@ let interval = null; const setupScheduledCheck = () => { const lastCheckTime = getLastCheckTime(); - if (moment().diff(lastCheckTime, 'second') > 1) { - updateVSS(); + if (moment().diff(lastCheckTime, 'seconds') > 120) { + checkUpdateVSS(); } if (!interval) { - interval = setInterval(updateVSS, 1000 * 60 * 60 * 24); + interval = setInterval(checkUpdateVSS, 1000 * 60 * 60 * 24); } }; diff --git a/src/services/api.service.js b/src/services/api.service.js index b7da797..2b05963 100644 --- a/src/services/api.service.js +++ b/src/services/api.service.js @@ -2,6 +2,9 @@ const httpStatus = require('http-status'); const { userService } = require('.'); const { Api } = require('../models'); const ApiError = require('../utils/ApiError'); +const fs = require('fs'); +const path = require('path'); +const logger = require('../config/logger'); /** * @@ -78,10 +81,33 @@ const deleteApi = async (apiId, userId) => { await api.remove(); }; +const listVSSVersions = async () => { + let versions; + try { + const rawData = fs.readFileSync(path.join(__dirname, '../../data/vss.json')); + versions = rawData ? JSON.parse(rawData, 'utf8') : []; + } catch (error) { + logger.error(error); + versions = []; + } + return versions; +}; + +const getVSSVersion = async (name) => { + const filePath = path.join(__dirname, `../../data/${name}.json`); + if (!fs.existsSync(filePath)) { + throw new ApiError(httpStatus.NOT_FOUND, 'VSS version not found'); + } + const data = JSON.parse(fs.readFileSync(filePath, 'utf8')); + return data; +}; + module.exports = { createApi, getApi, getApiByModelId, updateApi, deleteApi, + listVSSVersions, + getVSSVersion, }; diff --git a/src/services/file.service.js b/src/services/file.service.js index 3c49c71..fdcc13f 100644 --- a/src/services/file.service.js +++ b/src/services/file.service.js @@ -3,6 +3,7 @@ const ApiError = require('../utils/ApiError'); const httpStatus = require('http-status'); const logger = require('../config/logger'); const config = require('../config/config'); +const fs = require('fs'); /** * @@ -47,6 +48,16 @@ const getFileFromURL = async (url, encoding = 'File') => { } }; +const downloadFile = async (url, path) => { + try { + const arrayBuffer = await (await fetch(url)).arrayBuffer(); + fs.writeFileSync(path, new Uint8Array(arrayBuffer)); + } catch (error) { + logger.error(`Failed to download file from ${url}`); + logger.error(error); + } +}; + /** * * @param {File} file1 @@ -57,4 +68,5 @@ const compareImages = async (file1, file2) => {}; module.exports = { upload, getFileFromURL, + downloadFile, }; diff --git a/src/validations/api.validation.js b/src/validations/api.validation.js index 862cd5d..be0edfe 100644 --- a/src/validations/api.validation.js +++ b/src/validations/api.validation.js @@ -38,10 +38,17 @@ const deleteApi = { }), }; +const getVSSVersion = { + params: Joi.object().keys({ + name: Joi.string().required(), + }), +}; + module.exports = { createApi, getApi, getApiByModelId, updateApi, deleteApi, + getVSSVersion, }; diff --git a/yarn.lock b/yarn.lock index f7115b7..ab1277a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2245,6 +2245,11 @@ dependencies: "@types/node" "*" +"@types/lodash@^4.17.12": + version "4.17.12" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.12.tgz#25d71312bf66512105d71e55d42e22c36bcfc689" + integrity sha512-sviUmCE8AYdaF/KIHLDJBQgeYzPBI0vf/17NaYehBJfYD1j6/L95Slh07NlyK2iNyBNaEkb3En2jRt+a8y3xZQ== + "@types/long@^4.0.0": version "4.0.2" resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.2.tgz#b74129719fc8d11c01868010082d483b7545591a" @@ -6665,7 +6670,7 @@ lodash.truncate@^4.4.2: resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" integrity sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM= -lodash@^4.17.14, lodash@^4.7.0: +lodash@^4.17.14, lodash@^4.17.21, lodash@^4.7.0: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== -- GitLab From 8e11f18142384d24446ca9c860715ea0618426ef Mon Sep 17 00:00:00 2001 From: tuanh <tuan.hoangdinhanh@vn.bosch.com> Date: Tue, 22 Oct 2024 15:15:25 +0700 Subject: [PATCH 22/61] finish support for vss-version --- src/controllers/extendedApi.controller.js | 21 +++++++++-- src/controllers/model.controller.js | 9 +++++ src/models/extendedApi.model.js | 2 +- src/models/model.model.js | 6 +++ src/routes/v2/model.route.js | 8 ++++ src/services/api.service.js | 46 ++++++++++++++++++++++- src/services/extendedApi.service.js | 14 ++++++- src/services/permission.service.js | 16 ++++++++ src/utils/sort.js | 12 ++++++ src/validations/extendedApi.validation.js | 3 +- src/validations/model.validation.js | 7 ++++ 11 files changed, 135 insertions(+), 9 deletions(-) create mode 100644 src/utils/sort.js diff --git a/src/controllers/extendedApi.controller.js b/src/controllers/extendedApi.controller.js index 90fda16..fefe6a5 100644 --- a/src/controllers/extendedApi.controller.js +++ b/src/controllers/extendedApi.controller.js @@ -1,15 +1,23 @@ const httpStatus = require('http-status'); const catchAsync = require('../utils/catchAsync'); -const { extendedApiService } = require('../services'); +const { extendedApiService, permissionService } = require('../services'); const pick = require('../utils/pick'); const ApiError = require('../utils/ApiError'); +const { PERMISSIONS } = require('../config/roles'); const createExtendedApi = catchAsync(async (req, res) => { + if (!(await permissionService.hasPermission(req.user?.id, PERMISSIONS.WRITE_MODEL, req.body.model))) { + throw new ApiError(httpStatus.FORBIDDEN, 'Forbidden. You do not have permission to update extended API for this model'); + } const extendedApi = await extendedApiService.createExtendedApi(req.body); res.status(httpStatus.CREATED).send(extendedApi); }); const getExtendedApis = catchAsync(async (req, res) => { + const { model } = req.query; + if (!(await permissionService.canAccessModel(req.user?.id, model))) { + throw new ApiError(httpStatus.FORBIDDEN, 'Forbidden'); + } const filter = pick(req.query, ['apiName', 'model', 'skeleton']); const options = pick(req.query, ['sortBy', 'limit', 'page']); const result = await extendedApiService.queryExtendedApis(filter, options); @@ -18,6 +26,10 @@ const getExtendedApis = catchAsync(async (req, res) => { const getExtendedApi = catchAsync(async (req, res) => { const extendedApi = await extendedApiService.getExtendedApiById(req.params.id); + const { model } = extendedApi; + if (!(await permissionService.canAccessModel(req.user?.id, model))) { + throw new ApiError(httpStatus.FORBIDDEN, 'Forbidden'); + } if (!extendedApi) { throw new ApiError(httpStatus.NOT_FOUND, 'ExtendedApi not found'); } @@ -26,6 +38,9 @@ const getExtendedApi = catchAsync(async (req, res) => { const getExtendedApiByApiNameAndModel = catchAsync(async (req, res) => { const { apiName, model } = req.query; + if (!(await permissionService.canAccessModel(req.user?.id, model))) { + throw new ApiError(httpStatus.FORBIDDEN, 'Forbidden'); + } const extendedApi = await extendedApiService.getExtendedApiByApiNameAndModel(apiName, model); if (!extendedApi) { throw new ApiError(httpStatus.NOT_FOUND, 'ExtendedApi not found'); @@ -34,12 +49,12 @@ const getExtendedApiByApiNameAndModel = catchAsync(async (req, res) => { }); const updateExtendedApi = catchAsync(async (req, res) => { - const extendedApi = await extendedApiService.updateExtendedApiById(req.params.id, req.body); + const extendedApi = await extendedApiService.updateExtendedApiById(req.params.id, req.body, req.user.id); res.send(extendedApi); }); const deleteExtendedApi = catchAsync(async (req, res) => { - await extendedApiService.deleteExtendedApiById(req.params.id); + await extendedApiService.deleteExtendedApiById(req.params.id, req.user.id); res.status(httpStatus.NO_CONTENT).send(); }); diff --git a/src/controllers/model.controller.js b/src/controllers/model.controller.js index 978f373..0e2671e 100644 --- a/src/controllers/model.controller.js +++ b/src/controllers/model.controller.js @@ -94,6 +94,14 @@ const deleteAuthorizedUser = catchAsync(async (req, res) => { res.status(httpStatus.NO_CONTENT).send(); }); +const getComputedVSSApi = catchAsync(async (req, res) => { + if (!(await permissionService.canAccessModel(req.user?.id, req.params.id))) { + throw new ApiError(httpStatus.FORBIDDEN, 'Forbidden'); + } + const data = await apiService.computeVSSApi(req.params.id); + res.send(data); +}); + module.exports = { createModel, listModels, @@ -102,4 +110,5 @@ module.exports = { deleteModel, addAuthorizedUser, deleteAuthorizedUser, + getComputedVSSApi, }; diff --git a/src/models/extendedApi.model.js b/src/models/extendedApi.model.js index 19da0b1..ed479ba 100644 --- a/src/models/extendedApi.model.js +++ b/src/models/extendedApi.model.js @@ -29,7 +29,7 @@ const extendedApiSchema = mongoose.Schema( type: { type: String, }, - data_type: { + datatype: { type: String, }, description: String, diff --git a/src/models/model.model.js b/src/models/model.model.js index a427b79..2a82162 100644 --- a/src/models/model.model.js +++ b/src/models/model.model.js @@ -73,6 +73,12 @@ const modelSchema = mongoose.Schema( extend: { type: mongoose.SchemaTypes.Mixed, }, + api_version: { + type: String, + trim: true, + lowercase: true, + default: 'v4.1', + }, }, { timestamps: true, diff --git a/src/routes/v2/model.route.js b/src/routes/v2/model.route.js index e64fcc3..322016f 100644 --- a/src/routes/v2/model.route.js +++ b/src/routes/v2/model.route.js @@ -42,6 +42,14 @@ router modelController.deleteModel ); +router.route('/:id/api').get( + auth({ + optional: !config.strictAuth, + }), + validate(modelValidation.getApiByModelId), + modelController.getComputedVSSApi +); + router .route('/:id/permissions') .post( diff --git a/src/services/api.service.js b/src/services/api.service.js index 2b05963..26516a5 100644 --- a/src/services/api.service.js +++ b/src/services/api.service.js @@ -1,10 +1,12 @@ const httpStatus = require('http-status'); const { userService } = require('.'); -const { Api } = require('../models'); +const { Api, Model, ExtendedApi } = require('../models'); const ApiError = require('../utils/ApiError'); const fs = require('fs'); const path = require('path'); const logger = require('../config/logger'); +const { isArray } = require('lodash'); +const { sortObject } = require('../utils/sort'); /** * @@ -93,6 +95,11 @@ const listVSSVersions = async () => { return versions; }; +/** + * + * @param {string} name + * @returns {Promise<object>} + */ const getVSSVersion = async (name) => { const filePath = path.join(__dirname, `../../data/${name}.json`); if (!fs.existsSync(filePath)) { @@ -102,6 +109,42 @@ const getVSSVersion = async (name) => { return data; }; +/** + * + * @param {string} modelId + */ +const computeVSSApi = async (modelId) => { + const model = await Model.findById(modelId); + if (!model) { + throw new ApiError(httpStatus.NOT_FOUND, 'Model not found'); + } + const apiVersion = model.api_version || 'v4.1'; + const ret = await getVSSVersion(apiVersion); + + const extendedApis = await ExtendedApi.find({ + model: modelId, + }); + extendedApis.forEach((extendedApi) => { + try { + const name = extendedApi.apiName.split('.').slice(1).join('.'); + ret['Vehicle'].children[name] = { + description: extendedApi.description, + type: extendedApi.type, + id: extendedApi._id, + }; + } catch (error) { + logger.warn(`Error while processing extended API ${extendedApi._id} with name ${extendedApi.apiName}: ${error}`); + } + }); + + try { + ret['Vehicle'].children = sortObject(ret['Vehicle'].children); + } catch (error) { + logger.warn(`Error while sorting object: ${error}`); + } + return ret; +}; + module.exports = { createApi, getApi, @@ -110,4 +153,5 @@ module.exports = { deleteApi, listVSSVersions, getVSSVersion, + computeVSSApi, }; diff --git a/src/services/extendedApi.service.js b/src/services/extendedApi.service.js index 95ea869..7c9970f 100644 --- a/src/services/extendedApi.service.js +++ b/src/services/extendedApi.service.js @@ -1,6 +1,8 @@ const httpStatus = require('http-status'); const { ExtendedApi } = require('../models'); const ApiError = require('../utils/ApiError'); +const { permissionService } = require('.'); +const { PERMISSIONS } = require('../config/roles'); /** * Create a new ExtendedApi @@ -38,13 +40,17 @@ const getExtendedApiById = async (id) => { * Update ExtendedApi by id * @param {ObjectId} extendedApiId * @param {Object} updateBody + * @param {string} userId * @returns {Promise<ExtendedApi>} */ -const updateExtendedApiById = async (extendedApiId, updateBody) => { +const updateExtendedApiById = async (extendedApiId, updateBody, userId) => { const extendedApi = await getExtendedApiById(extendedApiId); if (!extendedApi) { throw new ApiError(httpStatus.NOT_FOUND, 'ExtendedApi not found'); } + if (!(await permissionService.hasPermission(userId, PERMISSIONS.WRITE_MODEL, extendedApi.model))) { + throw new ApiError(httpStatus.FORBIDDEN, 'Forbidden. You do not have permission to update extended API for this model'); + } Object.assign(extendedApi, updateBody); await extendedApi.save(); return extendedApi; @@ -53,13 +59,17 @@ const updateExtendedApiById = async (extendedApiId, updateBody) => { /** * Delete ExtendedApi by id * @param {ObjectId} extendedApiId + * @param {string} userId * @returns {Promise<ExtendedApi>} */ -const deleteExtendedApiById = async (extendedApiId) => { +const deleteExtendedApiById = async (extendedApiId, userId) => { const extendedApi = await getExtendedApiById(extendedApiId); if (!extendedApi) { throw new ApiError(httpStatus.NOT_FOUND, 'ExtendedApi not found'); } + if (!(await permissionService.hasPermission(userId, PERMISSIONS.WRITE_MODEL, extendedApi.model))) { + throw new ApiError(httpStatus.FORBIDDEN, 'Forbidden. You do not have permission to update extended API for this model'); + } await extendedApi.remove(); return extendedApi; }; diff --git a/src/services/permission.service.js b/src/services/permission.service.js index d9e914c..93a9ef9 100644 --- a/src/services/permission.service.js +++ b/src/services/permission.service.js @@ -256,6 +256,21 @@ const listReadableModelIds = async (userId) => { return Array.from(results); }; +/** + * + * @param {string} userId + * @param {string} modelId + * @returns {Promise<boolean>} + */ +const canAccessModel = async (userId, modelId) => { + const model = await Model.findById(modelId); + if (!model) { + throw new ApiError(httpStatus.NOT_FOUND, 'Model not found'); + } + if (model.visibility === 'public') return true; + return hasPermission(userId, PERMISSIONS.READ_MODEL, modelId); +}; + module.exports = { listAuthorizedUser, assignRoleToUser, @@ -268,4 +283,5 @@ module.exports = { getRoles, getPermissions, listReadableModelIds, + canAccessModel, }; diff --git a/src/utils/sort.js b/src/utils/sort.js new file mode 100644 index 0000000..a5e37d4 --- /dev/null +++ b/src/utils/sort.js @@ -0,0 +1,12 @@ +const sortObject = (obj) => { + return Object.keys(obj) + .sort() + .reduce((acc, key) => { + acc[key] = obj[key]; + return acc; + }, {}); +}; + +module.exports = { + sortObject, +}; diff --git a/src/validations/extendedApi.validation.js b/src/validations/extendedApi.validation.js index e589b7e..007370c 100644 --- a/src/validations/extendedApi.validation.js +++ b/src/validations/extendedApi.validation.js @@ -24,7 +24,7 @@ const createExtendedApi = { const getExtendedApis = { query: Joi.object().keys({ apiName: Joi.string(), - model: Joi.string().custom(objectId), + model: Joi.string().custom(objectId).required(), sortBy: Joi.string(), limit: Joi.number().integer(), page: Joi.number().integer(), @@ -46,7 +46,6 @@ const updateExtendedApi = { apiName: Joi.string() .regex(/^Vehicle\./) .message('apiName must start with Vehicle.'), - model: Joi.string().custom(objectId), skeleton: Joi.string().optional(), type: Joi.string(), data_type: Joi.string(), diff --git a/src/validations/model.validation.js b/src/validations/model.validation.js index adac0ac..a7759a6 100644 --- a/src/validations/model.validation.js +++ b/src/validations/model.validation.js @@ -108,6 +108,12 @@ const deleteAuthorizedUser = { }), }; +const getApiByModelId = { + params: Joi.object().keys({ + id: Joi.string().custom(objectId), + }), +}; + module.exports = { createModel, listModels, @@ -116,4 +122,5 @@ module.exports = { deleteModel, addAuthorizedUser, deleteAuthorizedUser, + getApiByModelId, }; -- GitLab From 1d44b740d9f81f1fbb1dd2f81e40afcb95b52bc2 Mon Sep 17 00:00:00 2001 From: tuanh <tuan.hoangdinhanh@vn.bosch.com> Date: Tue, 22 Oct 2024 15:16:25 +0700 Subject: [PATCH 23/61] increase default rate limit requests --- kong.yml.template | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kong.yml.template b/kong.yml.template index c146d79..1a60994 100644 --- a/kong.yml.template +++ b/kong.yml.template @@ -30,7 +30,7 @@ plugins: config: fault_tolerant: true hide_client_headers: false - minute: 60 + minute: 120 limit_by: header header_name: x-forwarded-for policy: local -- GitLab From f9cfb611ea30b0e16ba46ec0ac77793d1f9c5d01 Mon Sep 17 00:00:00 2001 From: tuanh <tuan.hoangdinhanh@vn.bosch.com> Date: Tue, 22 Oct 2024 16:11:54 +0700 Subject: [PATCH 24/61] make api_version optional and add field 'language' for Prototype schema --- src/models/model.model.js | 1 - src/models/prototype.model.js | 5 +++++ src/services/api.service.js | 6 +++++- src/validations/model.validation.js | 2 ++ src/validations/prototype.validation.js | 2 ++ 5 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/models/model.model.js b/src/models/model.model.js index 2a82162..a7c7c8f 100644 --- a/src/models/model.model.js +++ b/src/models/model.model.js @@ -77,7 +77,6 @@ const modelSchema = mongoose.Schema( type: String, trim: true, lowercase: true, - default: 'v4.1', }, }, { diff --git a/src/models/prototype.model.js b/src/models/prototype.model.js index 878efeb..0e257ad 100644 --- a/src/models/prototype.model.js +++ b/src/models/prototype.model.js @@ -190,6 +190,11 @@ const prototypeSchema = mongoose.Schema({ type: Number, default: 0, }, + language: { + type: String, + default: 'python', + maxLength: 20, + }, }); // add plugin that converts mongoose to json diff --git a/src/services/api.service.js b/src/services/api.service.js index 26516a5..89c3fb3 100644 --- a/src/services/api.service.js +++ b/src/services/api.service.js @@ -118,7 +118,11 @@ const computeVSSApi = async (modelId) => { if (!model) { throw new ApiError(httpStatus.NOT_FOUND, 'Model not found'); } - const apiVersion = model.api_version || 'v4.1'; + const apiVersion = model.api_version; + if (!apiVersion) { + throw new ApiError(httpStatus.BAD_REQUEST, 'This model does not have an API version'); + } + const ret = await getVSSVersion(apiVersion); const extendedApis = await ExtendedApi.find({ diff --git a/src/validations/model.validation.js b/src/validations/model.validation.js index a7759a6..0e033ef 100644 --- a/src/validations/model.validation.js +++ b/src/validations/model.validation.js @@ -6,6 +6,7 @@ const createModel = { body: Joi.object().keys({ extend: Joi.any(), custom_apis: Joi.string().custom(jsonString), + api_version: Joi.string().default('v4.1'), cvi: Joi.string().required().custom(jsonString), main_api: Joi.string().required().max(255), model_home_image_file: Joi.string() @@ -53,6 +54,7 @@ const updateModel = { .keys({ extend: Joi.any(), custom_apis: Joi.string().custom(jsonString), + api_version: Joi.string().default('v4.1'), cvi: Joi.string().custom(jsonString), main_api: Joi.string().max(255), model_home_image_file: Joi.string().allow(''), diff --git a/src/validations/prototype.validation.js b/src/validations/prototype.validation.js index e8f5bee..cffbc4f 100644 --- a/src/validations/prototype.validation.js +++ b/src/validations/prototype.validation.js @@ -41,6 +41,7 @@ const createPrototype = { autorun: Joi.boolean(), related_ea_components: Joi.string().allow(''), partner_logo: Joi.string().allow(''), + language: Joi.string().default('python'), // rated_by: Joi.object().pattern( // /^[0-9a-fA-F]{24}$/, // Joi.object() @@ -112,6 +113,7 @@ const updatePrototype = { autorun: Joi.boolean(), related_ea_components: Joi.string().allow(''), partner_logo: Joi.string().allow(''), + language: Joi.string().default('python'), // rated_by: Joi.object().pattern( // /^[0-9a-fA-F]{24}$/, // Joi.object() -- GitLab From 989562ba73c709eba837659108a901b0ad6dcea1 Mon Sep 17 00:00:00 2001 From: tuanh <tuan.hoangdinhanh@vn.bosch.com> Date: Tue, 22 Oct 2024 17:24:22 +0700 Subject: [PATCH 25/61] change data_type to datatype --- src/services/api.service.js | 6 +++++- src/validations/extendedApi.validation.js | 8 ++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/services/api.service.js b/src/services/api.service.js index 89c3fb3..b24e471 100644 --- a/src/services/api.service.js +++ b/src/services/api.service.js @@ -131,10 +131,14 @@ const computeVSSApi = async (modelId) => { extendedApis.forEach((extendedApi) => { try { const name = extendedApi.apiName.split('.').slice(1).join('.'); + if (!name) return; ret['Vehicle'].children[name] = { description: extendedApi.description, - type: extendedApi.type, + type: extendedApi.type || 'branch', id: extendedApi._id, + isWishlist: true, + datatype: extendedApi.datatype, + name: extendedApi.apiName, }; } catch (error) { logger.warn(`Error while processing extended API ${extendedApi._id} with name ${extendedApi.apiName}: ${error}`); diff --git a/src/validations/extendedApi.validation.js b/src/validations/extendedApi.validation.js index 007370c..d13d355 100644 --- a/src/validations/extendedApi.validation.js +++ b/src/validations/extendedApi.validation.js @@ -7,8 +7,8 @@ const createExtendedApi = { model: Joi.string().custom(objectId).required(), skeleton: Joi.string().optional(), type: Joi.string(), - data_type: Joi.string(), - description: Joi.string(), + datatype: Joi.string(), + description: Joi.string().allow('').default(''), tags: Joi.array() .items( Joi.object({ @@ -48,8 +48,8 @@ const updateExtendedApi = { .message('apiName must start with Vehicle.'), skeleton: Joi.string().optional(), type: Joi.string(), - data_type: Joi.string(), - description: Joi.string(), + datatype: Joi.string(), + description: Joi.string().allow(''), tags: Joi.array() .items( Joi.object({ -- GitLab From 9c4a560b3abb9c0da900ba6127de9d4eca8118c8 Mon Sep 17 00:00:00 2001 From: tuanh <tuan.hoangdinhanh@vn.bosch.com> Date: Tue, 22 Oct 2024 17:49:56 +0700 Subject: [PATCH 26/61] add check for existing extendedApi and remove default api_version --- src/services/extendedApi.service.js | 4 ++++ src/validations/model.validation.js | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/services/extendedApi.service.js b/src/services/extendedApi.service.js index 7c9970f..264bb5c 100644 --- a/src/services/extendedApi.service.js +++ b/src/services/extendedApi.service.js @@ -10,6 +10,10 @@ const { PERMISSIONS } = require('../config/roles'); * @returns {Promise<ExtendedApi>} */ const createExtendedApi = async (extendedApiBody) => { + const existingExtendedApi = await ExtendedApi.findOne({ apiName: extendedApiBody.apiName, model: extendedApiBody.model }); + if (existingExtendedApi) { + throw new ApiError(httpStatus.BAD_REQUEST, 'An extended API with the same name already exists for this model'); + } return ExtendedApi.create(extendedApiBody); }; diff --git a/src/validations/model.validation.js b/src/validations/model.validation.js index 0e033ef..2530208 100644 --- a/src/validations/model.validation.js +++ b/src/validations/model.validation.js @@ -6,7 +6,7 @@ const createModel = { body: Joi.object().keys({ extend: Joi.any(), custom_apis: Joi.string().custom(jsonString), - api_version: Joi.string().default('v4.1'), + api_version: Joi.string(), cvi: Joi.string().required().custom(jsonString), main_api: Joi.string().required().max(255), model_home_image_file: Joi.string() @@ -54,7 +54,7 @@ const updateModel = { .keys({ extend: Joi.any(), custom_apis: Joi.string().custom(jsonString), - api_version: Joi.string().default('v4.1'), + api_version: Joi.string(), cvi: Joi.string().custom(jsonString), main_api: Joi.string().max(255), model_home_image_file: Joi.string().allow(''), -- GitLab From 0e8838b59e7895163ad0487edaf19e7f159591bb Mon Sep 17 00:00:00 2001 From: tuanh <tuan.hoangdinhanh@vn.bosch.com> Date: Wed, 23 Oct 2024 11:11:22 +0700 Subject: [PATCH 27/61] automatically create data folder --- src/scripts/checkVSSUpdate.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/scripts/checkVSSUpdate.js b/src/scripts/checkVSSUpdate.js index dc6cb42..6b62eb2 100644 --- a/src/scripts/checkVSSUpdate.js +++ b/src/scripts/checkVSSUpdate.js @@ -70,6 +70,10 @@ const checkUpdateVSS = async () => { let interval = null; const setupScheduledCheck = () => { + const dataDirExist = fs.existsSync(path.join(__dirname, '../../data')); + if (!dataDirExist) { + fs.mkdirSync(path.join(__dirname, '../../data')); + } const lastCheckTime = getLastCheckTime(); if (moment().diff(lastCheckTime, 'seconds') > 120) { checkUpdateVSS(); -- GitLab From 187187eaac09f521656f7c092c0ce0d7d70499ca Mon Sep 17 00:00:00 2001 From: tuanh <tuan.hoangdinhanh@gmail.com> Date: Wed, 23 Oct 2024 08:37:10 +0000 Subject: [PATCH 28/61] update validation of prototype --- src/validations/prototype.validation.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/validations/prototype.validation.js b/src/validations/prototype.validation.js index cffbc4f..dcde891 100644 --- a/src/validations/prototype.validation.js +++ b/src/validations/prototype.validation.js @@ -113,7 +113,7 @@ const updatePrototype = { autorun: Joi.boolean(), related_ea_components: Joi.string().allow(''), partner_logo: Joi.string().allow(''), - language: Joi.string().default('python'), + language: Joi.string(), // rated_by: Joi.object().pattern( // /^[0-9a-fA-F]{24}$/, // Joi.object() -- GitLab From 22fa3f506da3bba61f71a658e1fee17153372bad Mon Sep 17 00:00:00 2001 From: tuanh <tuan.hoangdinhanh@vn.bosch.com> Date: Thu, 24 Oct 2024 14:17:41 +0700 Subject: [PATCH 29/61] add field requirements for prototype --- src/models/prototype.model.js | 3 +++ src/validations/prototype.validation.js | 2 ++ 2 files changed, 5 insertions(+) diff --git a/src/models/prototype.model.js b/src/models/prototype.model.js index 0e257ad..50eabcd 100644 --- a/src/models/prototype.model.js +++ b/src/models/prototype.model.js @@ -195,6 +195,9 @@ const prototypeSchema = mongoose.Schema({ default: 'python', maxLength: 20, }, + requirements: { + type: String, + }, }); // add plugin that converts mongoose to json diff --git a/src/validations/prototype.validation.js b/src/validations/prototype.validation.js index cffbc4f..4df55e4 100644 --- a/src/validations/prototype.validation.js +++ b/src/validations/prototype.validation.js @@ -42,6 +42,7 @@ const createPrototype = { related_ea_components: Joi.string().allow(''), partner_logo: Joi.string().allow(''), language: Joi.string().default('python'), + requirements: Joi.string().allow(''), // rated_by: Joi.object().pattern( // /^[0-9a-fA-F]{24}$/, // Joi.object() @@ -114,6 +115,7 @@ const updatePrototype = { related_ea_components: Joi.string().allow(''), partner_logo: Joi.string().allow(''), language: Joi.string().default('python'), + requirements: Joi.string().allow(''), // rated_by: Joi.object().pattern( // /^[0-9a-fA-F]{24}$/, // Joi.object() -- GitLab From e3e13205a9c72e68ff35fd1cdde50cf0b8888b21 Mon Sep 17 00:00:00 2001 From: tuanh <tuan.hoangdinhanh@vn.bosch.com> Date: Thu, 24 Oct 2024 16:57:56 +0700 Subject: [PATCH 30/61] fix error referencing permissions on undefined 'role.role.permissions' --- src/services/permission.service.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/services/permission.service.js b/src/services/permission.service.js index 93a9ef9..f01808d 100644 --- a/src/services/permission.service.js +++ b/src/services/permission.service.js @@ -121,12 +121,12 @@ const getMappedRoles = (roles) => { if (!Array.isArray(existingRolePermissions)) { existingRolePermissions = []; } else { - const newArray = existingRolePermissions.concat(role.role.permissions); + const newArray = existingRolePermissions.concat(role?.role?.permissions || []); existingRolePermissions = Array.from(new Set(newArray)); } map.set(roleRef, existingRolePermissions); } else { - map.set(roleRef, role.role.permissions); + map.set(roleRef, role?.role?.permissions || []); } }); return map; -- GitLab From 2eb33c619492ff77636da6bbce74967b3ff7fa76 Mon Sep 17 00:00:00 2001 From: tuanh <tuan.hoangdinhanh@vn.bosch.com> Date: Fri, 25 Oct 2024 10:47:58 +0700 Subject: [PATCH 31/61] allow login in strict auth mode --- src/routes/v2/auth.route.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/v2/auth.route.js b/src/routes/v2/auth.route.js index b4a9908..e5598ec 100644 --- a/src/routes/v2/auth.route.js +++ b/src/routes/v2/auth.route.js @@ -11,8 +11,8 @@ router.get('/github/callback', authController.githubCallback); router.post('/sso', validate(authValidation.sso), authController.sso); if (!config.strictAuth) { router.post('/register', validate(authValidation.register), authController.register); - router.post('/login', validate(authValidation.login), authController.login); } +router.post('/login', validate(authValidation.login), authController.login); router.post('/logout', authController.logout); router.post('/refresh-tokens', authController.refreshTokens); router.post('/forgot-password', validate(authValidation.forgotPassword), authController.forgotPassword); -- GitLab From 01751933eacd47705bd647191f0092d28305fa10 Mon Sep 17 00:00:00 2001 From: tuanh <tuan.hoangdinhanh@vn.bosch.com> Date: Fri, 25 Oct 2024 13:18:28 +0700 Subject: [PATCH 32/61] add support for creating model from scratch --- src/services/api.service.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/services/api.service.js b/src/services/api.service.js index b24e471..a05314d 100644 --- a/src/services/api.service.js +++ b/src/services/api.service.js @@ -118,13 +118,21 @@ const computeVSSApi = async (modelId) => { if (!model) { throw new ApiError(httpStatus.NOT_FOUND, 'Model not found'); } + let ret = null; + const apiVersion = model.api_version; if (!apiVersion) { - throw new ApiError(httpStatus.BAD_REQUEST, 'This model does not have an API version'); + ret = { + Vehicle: { + description: 'Vehicle', + type: 'branch', + children: {}, + }, + }; + } else { + ret = await getVSSVersion(apiVersion); } - const ret = await getVSSVersion(apiVersion); - const extendedApis = await ExtendedApi.find({ model: modelId, }); -- GitLab From 9ad6e8467c8e29daee16c6d3bc227b2180c11136 Mon Sep 17 00:00:00 2001 From: tuanh <tuan.hoangdinhanh@vn.bosch.com> Date: Fri, 25 Oct 2024 14:01:22 +0700 Subject: [PATCH 33/61] update logic of creating model --- src/controllers/model.controller.js | 43 +++++++++++++++++++++----- src/scripts/migrate-api.js | 47 +++++++++++++++++++++++++++++ src/validations/model.validation.js | 3 +- 3 files changed, 84 insertions(+), 9 deletions(-) create mode 100644 src/scripts/migrate-api.js diff --git a/src/controllers/model.controller.js b/src/controllers/model.controller.js index 0e2671e..1dd07c1 100644 --- a/src/controllers/model.controller.js +++ b/src/controllers/model.controller.js @@ -1,22 +1,49 @@ const httpStatus = require('http-status'); -const { modelService, apiService, permissionService } = require('../services'); +const { modelService, apiService, permissionService, extendedApiService } = require('../services'); const catchAsync = require('../utils/catchAsync'); const pick = require('../utils/pick'); const ApiError = require('../utils/ApiError'); const { PERMISSIONS } = require('../config/roles'); const createModel = catchAsync(async (req, res) => { - const { cvi, custom_apis, ...reqBody } = req.body; + const { cvi, custom_apis, extended_apis, ...reqBody } = req.body; const model = await modelService.createModel(req.user.id, { ...reqBody, - ...(reqBody.custom_apis && { custom_apis: JSON.parse(reqBody.custom_apis) }), - }); - await apiService.createApi({ - model: model._id, - cvi: JSON.parse(cvi), - created_by: req.user.id, }); + // if (cvi) { + // // await apiService.createApi(model._id, cvi); + // } + + if (extended_apis) { + await Promise.all(extended_apis.map((api) => extendedApiService.createExtendedApi(api))); + } + + if (custom_apis) { + let apis = custom_apis; + try { + apis = JSON.parse(custom_apis); + } catch (error) { + // Do nothing + } + + if (Array.isArray(apis)) { + await Promise.all( + apis.map((api) => + extendedApiService.createExtendedApi({ + model: model._id, + apiName: api.name || api.apiName || 'Vehicle', + description: api.description || '', + skeleton: api.skeleton || '{}', + tags: api.tags || [], + type: api.type || 'branch', + datatype: api.datatype || (api.type !== 'branch' ? 'string' : null), + }) + ) + ); + } + } + res.status(httpStatus.CREATED).send(model); }); diff --git a/src/scripts/migrate-api.js b/src/scripts/migrate-api.js new file mode 100644 index 0000000..44c64b1 --- /dev/null +++ b/src/scripts/migrate-api.js @@ -0,0 +1,47 @@ +const Client = require('mongodb').MongoClient; + +const connect = async (url) => { + const client = new Client(url, { useUnifiedTopology: true }); + await client.connect(); + return client.db(); +}; + +const main = async () => { + db = await connect('mongodb://etas-prod-playground-db:27017/playground-be'); + const models = await db + .collection('models') + .find({ + custom_apis: { + $exists: true, + }, + }) + .toArray(); + + await db.collection('models').updateMany({ api_version: { $exists: false } }, { $set: { api_version: 'v4.1' } }); + + const before = await db.collection('extendedapis').count(); + console.log('Before:', before); + const promises = []; + + models.forEach(async (model) => { + const custom_apis = model.custom_apis; + custom_apis.forEach(async (custom_api) => { + const newData = { + apiName: custom_api.name, + model: model._id, + skeleton: custom_api.skeleton || '{}', + tags: custom_api.tags || [], + type: custom_api.type || 'branch', + datatype: custom_api.datatype || (custom_api.type !== 'branch' ? 'string' : null), + description: custom_api.description || '', + }; + promises.push(db.collection('extendedapis').insertOne(newData)); + }); + }); + + await Promise.allSettled(promises).catch((err) => console.error(err)); + const after = await db.collection('extendedapis').count(); + console.log('after:', after); +}; + +main(); diff --git a/src/validations/model.validation.js b/src/validations/model.validation.js index 2530208..b102efc 100644 --- a/src/validations/model.validation.js +++ b/src/validations/model.validation.js @@ -7,7 +7,8 @@ const createModel = { extend: Joi.any(), custom_apis: Joi.string().custom(jsonString), api_version: Joi.string(), - cvi: Joi.string().required().custom(jsonString), + cvi: Joi.string().custom(jsonString), + extended_apis: Joi.array().items(Joi.any()), main_api: Joi.string().required().max(255), model_home_image_file: Joi.string() .allow('') -- GitLab From 823f693b0a6a98cb9357778b86521872257f82b5 Mon Sep 17 00:00:00 2001 From: tuanh <tuan.hoangdinhanh@vn.bosch.com> Date: Fri, 25 Oct 2024 14:22:48 +0700 Subject: [PATCH 34/61] add trycatch for creating model --- src/controllers/model.controller.js | 60 +++++++++++++++++++---------- 1 file changed, 40 insertions(+), 20 deletions(-) diff --git a/src/controllers/model.controller.js b/src/controllers/model.controller.js index 1dd07c1..ca0c0b5 100644 --- a/src/controllers/model.controller.js +++ b/src/controllers/model.controller.js @@ -15,33 +15,53 @@ const createModel = catchAsync(async (req, res) => { // // await apiService.createApi(model._id, cvi); // } - if (extended_apis) { - await Promise.all(extended_apis.map((api) => extendedApiService.createExtendedApi(api))); - } - - if (custom_apis) { - let apis = custom_apis; - try { - apis = JSON.parse(custom_apis); - } catch (error) { - // Do nothing - } - - if (Array.isArray(apis)) { + try { + if (extended_apis) { await Promise.all( - apis.map((api) => + extended_apis.map((api) => extendedApiService.createExtendedApi({ model: model._id, - apiName: api.name || api.apiName || 'Vehicle', - description: api.description || '', - skeleton: api.skeleton || '{}', - tags: api.tags || [], - type: api.type || 'branch', - datatype: api.datatype || (api.type !== 'branch' ? 'string' : null), + apiName: api.apiName, + description: api.description, + skeleton: api.skeleton, + tags: api.tags, + type: api.type, + datatype: api.datatype, }) ) ); } + } catch (error) { + console.warn('Error in creating model with extended_apis', error); + } + + try { + if (custom_apis) { + let apis = custom_apis; + try { + apis = JSON.parse(custom_apis); + } catch (error) { + // Do nothing + } + + if (Array.isArray(apis)) { + await Promise.all( + apis.map((api) => + extendedApiService.createExtendedApi({ + model: model._id, + apiName: api.name || api.apiName || 'Vehicle', + description: api.description || '', + skeleton: api.skeleton || '{}', + tags: api.tags || [], + type: api.type || 'branch', + datatype: api.datatype || (api.type !== 'branch' ? 'string' : null), + }) + ) + ); + } + } + } catch (error) { + console.warn('Error in creating model with custom_apis', error); } res.status(httpStatus.CREATED).send(model); -- GitLab From 32a7a350f3d43a192fbb11ef848e0356f99152fa Mon Sep 17 00:00:00 2001 From: tuanh <tuan.hoangdinhanh@vn.bosch.com> Date: Fri, 25 Oct 2024 15:02:57 +0700 Subject: [PATCH 35/61] add released constraint for popular prototypes --- src/services/prototype.service.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/services/prototype.service.js b/src/services/prototype.service.js index 6014f45..7d9c205 100644 --- a/src/services/prototype.service.js +++ b/src/services/prototype.service.js @@ -189,6 +189,7 @@ const listPopularPrototypes = async () => { ).map((model) => String(model._id)); return Prototype.find({ model_id: { $in: publicModelIds }, + state: 'Released', }) .sort({ executed_turns: -1 }) .limit(8) -- GitLab From ac560e8c3c87877c982bfd9519cf913b61489b61 Mon Sep 17 00:00:00 2001 From: tuanh <tuan.hoangdinhanh@vn.bosch.com> Date: Mon, 28 Oct 2024 16:11:38 +0700 Subject: [PATCH 36/61] remove xss-clean --- package.json | 3 +-- src/app.js | 2 -- yarn.lock | 12 ------------ 3 files changed, 1 insertion(+), 16 deletions(-) diff --git a/package.json b/package.json index 5205b1c..ba4e273 100644 --- a/package.json +++ b/package.json @@ -83,8 +83,7 @@ "utf-8-validate": "^6.0.4", "validator": "^13.0.0", "websocket": "^1.0.35", - "winston": "^3.2.1", - "xss-clean": "^0.1.1" + "winston": "^3.2.1" }, "devDependencies": { "@types/lodash": "^4.17.12", diff --git a/src/app.js b/src/app.js index 9391ce7..947ff0c 100644 --- a/src/app.js +++ b/src/app.js @@ -1,6 +1,5 @@ const express = require('express'); const helmet = require('helmet'); -const xss = require('xss-clean'); const cookies = require('cookie-parser'); const mongoSanitize = require('express-mongo-sanitize'); const compression = require('compression'); @@ -38,7 +37,6 @@ app.use(express.json({ limit: '50mb', strict: false })); app.use(express.urlencoded({ extended: true, limit: '50mb', parameterLimit: 10000 })); // sanitize request data -app.use(xss()); app.use(mongoSanitize()); // gzip compression diff --git a/yarn.lock b/yarn.lock index ab1277a..ac02d3d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9946,18 +9946,6 @@ xregexp@2.0.0: resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-2.0.0.tgz#52a63e56ca0b84a7f3a5f3d61872f126ad7a5943" integrity sha1-UqY+VsoLhKfzpfPWGHLxJq16WUM= -xss-clean@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/xss-clean/-/xss-clean-0.1.1.tgz#d3ba684d85ccd52054963d01ad6ab36d662db1a5" - integrity sha1-07poTYXM1SBUlj0BrWqzbWYtsaU= - dependencies: - xss-filters "1.2.6" - -xss-filters@1.2.6: - version "1.2.6" - resolved "https://registry.yarnpkg.com/xss-filters/-/xss-filters-1.2.6.tgz#68b39089cb1dff8b9dbc889484839b2f507f5c55" - integrity sha1-aLOQicsd/4udvIiUhIObL1B/XFU= - xtend@^4.0.0: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" -- GitLab From 84d13378796e17a7c299f8589afadb6e5845ece5 Mon Sep 17 00:00:00 2001 From: tuanh <tuan.hoangdinhanh@vn.bosch.com> Date: Tue, 29 Oct 2024 13:28:28 +0700 Subject: [PATCH 37/61] add created_at again --- src/models/plugins/toJSON.plugin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/models/plugins/toJSON.plugin.js b/src/models/plugins/toJSON.plugin.js index d511c9d..97a7df2 100644 --- a/src/models/plugins/toJSON.plugin.js +++ b/src/models/plugins/toJSON.plugin.js @@ -31,7 +31,7 @@ const toJSON = (schema) => { ret.id = ret._id.toString(); delete ret._id; delete ret.__v; - // ret.created_at = ret.createdAt; + ret.created_at = ret.createdAt; delete ret.createdAt; delete ret.updatedAt; if (transform) { -- GitLab From d6c78d69211cf9b79cc3a9cb1808216fea3d79c5 Mon Sep 17 00:00:00 2001 From: tuanh <tuan.hoangdinhanh@vn.bosch.com> Date: Tue, 29 Oct 2024 16:55:27 +0700 Subject: [PATCH 38/61] add timestamps for prototypes model --- src/models/prototype.model.js | 233 +++++++++++++++++----------------- 1 file changed, 119 insertions(+), 114 deletions(-) diff --git a/src/models/prototype.model.js b/src/models/prototype.model.js index 50eabcd..e831745 100644 --- a/src/models/prototype.model.js +++ b/src/models/prototype.model.js @@ -81,124 +81,129 @@ const ratingSchema = mongoose.Schema({ }, }); -const prototypeSchema = mongoose.Schema({ - apis: { - type: apiSchema, - default: { - VSC: [], - VSS: [], +const prototypeSchema = mongoose.Schema( + { + apis: { + type: apiSchema, + default: { + VSC: [], + VSS: [], + }, }, - }, - code: { - type: String, - required: true, - default: - 'from sdv_model import Vehicle import plugins from browser import aio vehicle = Vehicle() # write your code here', - }, - extend: { - type: mongoose.SchemaTypes.Mixed, - }, - complexity_level: { - type: Number, - required: true, - min: 1, - max: 5, - default: 3, - }, - customer_journey: { - type: String, - required: true, - default: - ' #Step 1 Who: Driver What: Wipers turned on manually Customer TouchPoints: Windshield wiper switch #Step 2 Who: User What: User opens the car door/trunk and the open status of door/trunk is set to true Customer TouchPoints: Door/trunk handle #Step 3 Who: System What: The wiping is immediately turned off by the software and user is notified Customer TouchPoints: Notification on car dashboard and mobile app ', - }, - description: { - type: descriptionSchema, - default: { - problem: '', - says_who: '', - solution: '', - status: '', + code: { + type: String, + required: true, + default: + 'from sdv_model import Vehicle import plugins from browser import aio vehicle = Vehicle() # write your code here', }, - }, - image_file: { - type: String, - }, - journey_image_file: { - type: String, - }, - analysis_image_file: { - type: String, - }, - model_id: { - type: mongoose.SchemaTypes.ObjectId, - ref: 'Model', - required: true, - }, - name: { - type: String, - required: true, - trim: true, - maxLength: 255, - }, - portfolio: { - type: portfolioSchema, - default: { - effort_estimation: 0, - needs_addressed: 0, - relevance: 0, + extend: { + type: mongoose.SchemaTypes.Mixed, + }, + complexity_level: { + type: Number, + required: true, + min: 1, + max: 5, + default: 3, + }, + customer_journey: { + type: String, + required: true, + default: + ' #Step 1 Who: Driver What: Wipers turned on manually Customer TouchPoints: Windshield wiper switch #Step 2 Who: User What: User opens the car door/trunk and the open status of door/trunk is set to true Customer TouchPoints: Door/trunk handle #Step 3 Who: System What: The wiping is immediately turned off by the software and user is notified Customer TouchPoints: Notification on car dashboard and mobile app ', + }, + description: { + type: descriptionSchema, + default: { + problem: '', + says_who: '', + solution: '', + status: '', + }, + }, + image_file: { + type: String, + }, + journey_image_file: { + type: String, + }, + analysis_image_file: { + type: String, + }, + model_id: { + type: mongoose.SchemaTypes.ObjectId, + ref: 'Model', + required: true, + }, + name: { + type: String, + required: true, + trim: true, + maxLength: 255, + }, + portfolio: { + type: portfolioSchema, + default: { + effort_estimation: 0, + needs_addressed: 0, + relevance: 0, + }, + }, + skeleton: { + type: String, + }, + state: { + type: String, + required: true, + default: stateTypes.DEVELOPMENT, + enums: Object.values(stateTypes), + }, + tags: { + type: [tagSchema], + }, + widget_config: { + type: String, + }, + last_viewed: { + type: Date, + }, + rated_by: { + type: Map, + of: ratingSchema, + default: {}, + }, + autorun: { + type: Boolean, + default: false, + }, + related_ea_components: { + type: String, + }, + partner_logo: { + type: String, + }, + created_by: { + type: mongoose.SchemaTypes.ObjectId, + ref: 'User', + required: true, + }, + executed_turns: { + type: Number, + default: 0, + }, + language: { + type: String, + default: 'python', + maxLength: 20, + }, + requirements: { + type: String, }, }, - skeleton: { - type: String, - }, - state: { - type: String, - required: true, - default: stateTypes.DEVELOPMENT, - enums: Object.values(stateTypes), - }, - tags: { - type: [tagSchema], - }, - widget_config: { - type: String, - }, - last_viewed: { - type: Date, - }, - rated_by: { - type: Map, - of: ratingSchema, - default: {}, - }, - autorun: { - type: Boolean, - default: false, - }, - related_ea_components: { - type: String, - }, - partner_logo: { - type: String, - }, - created_by: { - type: mongoose.SchemaTypes.ObjectId, - ref: 'User', - required: true, - }, - executed_turns: { - type: Number, - default: 0, - }, - language: { - type: String, - default: 'python', - maxLength: 20, - }, - requirements: { - type: String, - }, -}); + { + timestamps: true, + } +); // add plugin that converts mongoose to json prototypeSchema.plugin(toJSON); -- GitLab From 5d15b661cd0a538cf13776d40f15131690683944 Mon Sep 17 00:00:00 2001 From: tuanh <tuan.hoangdinhanh@vn.bosch.com> Date: Wed, 30 Oct 2024 11:24:48 +0700 Subject: [PATCH 39/61] update tag mechanism --- src/models/extendedApi.model.js | 12 ++++++--- src/models/model.model.js | 11 +++------ src/models/prototype.model.js | 11 +++------ src/routes/v1/index.js | 8 +++--- src/routes/v1/tag.route.js | 12 ++++----- src/validations/extendedApi.validation.js | 30 +++++++++-------------- src/validations/model.validation.js | 10 +++----- src/validations/prototype.validation.js | 10 +++----- 8 files changed, 47 insertions(+), 57 deletions(-) diff --git a/src/models/extendedApi.model.js b/src/models/extendedApi.model.js index ed479ba..6c9ac1b 100644 --- a/src/models/extendedApi.model.js +++ b/src/models/extendedApi.model.js @@ -3,9 +3,15 @@ const { toJSON, paginate } = require('./plugins'); const tagSchema = mongoose.Schema( { - tagCategoryId: String, - tagCategoryName: String, - tag: String, + title: { + type: String, + required: true, + trim: true, + }, + description: { + type: String, + trim: true, + }, }, { _id: false, diff --git a/src/models/model.model.js b/src/models/model.model.js index a7c7c8f..6cbeea2 100644 --- a/src/models/model.model.js +++ b/src/models/model.model.js @@ -4,17 +4,14 @@ const { visibilityTypes } = require('../config/enums'); const tagSchema = mongoose.Schema( { - tag: { - type: String, - required: true, - }, - tagCategoryId: { + title: { type: String, required: true, + trim: true, }, - tagCategoryName: { + description: { type: String, - required: true, + trim: true, }, }, { diff --git a/src/models/prototype.model.js b/src/models/prototype.model.js index e831745..4d692bc 100644 --- a/src/models/prototype.model.js +++ b/src/models/prototype.model.js @@ -51,17 +51,14 @@ const portfolioSchema = mongoose.Schema( const tagSchema = mongoose.Schema( { - tag: { - type: String, - required: true, - }, - tagCategoryId: { + title: { type: String, required: true, + trim: true, }, - tagCategoryName: { + description: { type: String, - required: true, + trim: true, }, }, { diff --git a/src/routes/v1/index.js b/src/routes/v1/index.js index 585ad5e..467c64c 100644 --- a/src/routes/v1/index.js +++ b/src/routes/v1/index.js @@ -56,10 +56,10 @@ const defaultRoutes = [ path: '/plugins', route: pluginsRoute, }, - { - path: '/tags', - route: tagsRoute, - }, + // { + // path: '/tags', + // route: tagsRoute, + // }, { path: '/medias', route: mediasRoute, diff --git a/src/routes/v1/tag.route.js b/src/routes/v1/tag.route.js index ab17489..327a1f4 100644 --- a/src/routes/v1/tag.route.js +++ b/src/routes/v1/tag.route.js @@ -6,12 +6,12 @@ const validate = require('../../middlewares/validate'); const router = express.Router(); -router.route('/').post(validate(tagValidation.createTag), tagController.createTag); -router - .route('/categories') - .get(validate(tagValidation.listTagCategories), tagController.listTagCategories) - .post(validate(tagValidation.createTagCategory), tagController.createTagCategory); -router.route('/categories/:id').put(validate(tagValidation.updateTagCategory), tagController.updateTagCategory); +// router.route('/').post(validate(tagValidation.createTag), tagController.createTag); +// router +// .route('/categories') +// .get(validate(tagValidation.listTagCategories), tagController.listTagCategories) +// .post(validate(tagValidation.createTagCategory), tagController.createTagCategory); +// router.route('/categories/:id').put(validate(tagValidation.updateTagCategory), tagController.updateTagCategory); // router.get('/categories/:tenantId', async (req, res) => { // try { diff --git a/src/validations/extendedApi.validation.js b/src/validations/extendedApi.validation.js index d13d355..1e1e5a1 100644 --- a/src/validations/extendedApi.validation.js +++ b/src/validations/extendedApi.validation.js @@ -9,15 +9,12 @@ const createExtendedApi = { type: Joi.string(), datatype: Joi.string(), description: Joi.string().allow('').default(''), - tags: Joi.array() - .items( - Joi.object({ - tagCategoryId: Joi.string(), - tagCategoryName: Joi.string(), - tag: Joi.string(), - }) - ) - .optional(), + tags: Joi.array().items( + Joi.object().keys({ + title: Joi.string().required(), + description: Joi.string().allow(''), + }) + ), }), }; @@ -50,15 +47,12 @@ const updateExtendedApi = { type: Joi.string(), datatype: Joi.string(), description: Joi.string().allow(''), - tags: Joi.array() - .items( - Joi.object({ - tagCategoryId: Joi.string(), - tagCategoryName: Joi.string(), - tag: Joi.string(), - }) - ) - .optional(), + tags: Joi.array().items( + Joi.object().keys({ + title: Joi.string().required(), + description: Joi.string().allow(''), + }) + ), }) .min(1), }; diff --git a/src/validations/model.validation.js b/src/validations/model.validation.js index b102efc..d2fd0b0 100644 --- a/src/validations/model.validation.js +++ b/src/validations/model.validation.js @@ -25,9 +25,8 @@ const createModel = { skeleton: Joi.string().custom(jsonString), tags: Joi.array().items( Joi.object().keys({ - tag: Joi.string().required(), - tagCategoryId: Joi.string().required().custom(slug), - tagCategoryName: Joi.string().required(), + title: Joi.string().required(), + description: Joi.string().allow(''), }) ), }), @@ -67,9 +66,8 @@ const updateModel = { skeleton: Joi.string().custom(jsonString), tags: Joi.array().items( Joi.object().keys({ - tag: Joi.string().required(), - tagCategoryId: Joi.string().required().custom(slug), - tagCategoryName: Joi.string().required(), + title: Joi.string().required(), + description: Joi.string().allow(''), }) ), }) diff --git a/src/validations/prototype.validation.js b/src/validations/prototype.validation.js index 2968574..e6cb8cc 100644 --- a/src/validations/prototype.validation.js +++ b/src/validations/prototype.validation.js @@ -32,9 +32,8 @@ const createPrototype = { skeleton: Joi.string().custom(jsonString), tags: Joi.array().items( Joi.object().keys({ - tag: Joi.string().required(), - tagCategoryId: Joi.string().required().custom(slug), - tagCategoryName: Joi.string().required(), + title: Joi.string().required(), + description: Joi.string().allow(''), }) ), widget_config: Joi.string().custom(jsonString), @@ -105,9 +104,8 @@ const updatePrototype = { skeleton: Joi.string().custom(jsonString), tags: Joi.array().items( Joi.object().keys({ - tag: Joi.string().required(), - tagCategoryId: Joi.string().required().custom(slug), - tagCategoryName: Joi.string().required(), + title: Joi.string().required(), + description: Joi.string().allow(''), }) ), widget_config: Joi.string().custom(jsonString), -- GitLab From 9c9a1927157f778b80216a8fb4a39559929c5e24 Mon Sep 17 00:00:00 2001 From: tuanh <tuan.hoangdinhanh@vn.bosch.com> Date: Thu, 31 Oct 2024 11:11:21 +0700 Subject: [PATCH 40/61] add alternative route for etas genai --- src/config/config.js | 2 ++ src/controllers/genai.controller.js | 18 +++++++++++++++--- src/routes/v2/genai.route.js | 9 +++++++++ 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/config/config.js b/src/config/config.js index 12ef795..885e069 100644 --- a/src/config/config.js +++ b/src/config/config.js @@ -45,6 +45,7 @@ const envVarsSchema = Joi.object() ETAS_CLIENT_SECRET: Joi.string().description('ETAS client secret'), ETAS_SCOPE: Joi.string().description('ETAS scope'), ETAS_INSTANCE_ENDPOINT: Joi.string().description('ETAS instance endpoint'), + ETAS_DEV_INSTANCE_ENDPOINT: Joi.string().description('ETAS dev instance endpoint'), // Certivity CERTIVITY_CLIENT_ID: Joi.string().required().description('Certivity client id'), CERTIVITY_CLIENT_SECRET: Joi.string().required().description('Certivity client secret'), @@ -150,6 +151,7 @@ const config = { clientSecret: envVars.ETAS_CLIENT_SECRET, scope: envVars.ETAS_SCOPE, instanceEndpoint: envVars.ETAS_INSTANCE_ENDPOINT, + developmentEndpoint: envVars.ETAS_DEV_INSTANCE_ENDPOINT, }, githubIssueSubmitUrl: 'https://api.github.com/repos/digital-auto/vehicle_signal_specification/issues', certivity: { diff --git a/src/controllers/genai.controller.js b/src/controllers/genai.controller.js index 659a8f5..c2c62c7 100644 --- a/src/controllers/genai.controller.js +++ b/src/controllers/genai.controller.js @@ -170,8 +170,8 @@ async function BedrockGenCode({ endpointURL, publicKey, secretKey, inputPrompt, } } } catch (error) { - console.log("Error in BedrockGenCode"); - console.log(error) + console.log('Error in BedrockGenCode'); + console.log(error); try { const bedrockResponse = await bedrock.send(new InvokeModelCommand(input)); const response = JSON.parse(new TextDecoder().decode(bedrockResponse.body)); @@ -254,8 +254,20 @@ const getAccessToken = async () => { } }; +const getInstance = (environment = 'prod') => { + switch (environment) { + case 'prod': + return config.etas.instanceEndpoint; + case 'dev': + return config.etas.developmentEndpoint; + default: + return config.etas.instanceEndpoint; + } +}; + const generateAIContent = async (req, res) => { try { + const { environment } = req.params; const { prompt } = req.body; const authorizationData = etasAuthorizationData.getAuthorizationData(); let token = authorizationData.accessToken; @@ -268,7 +280,7 @@ const generateAIContent = async (req, res) => { }); } - const instance = config.etas.instanceEndpoint; + const instance = getInstance(environment); setupClient(token); diff --git a/src/routes/v2/genai.route.js b/src/routes/v2/genai.route.js index bbc5166..991d05c 100644 --- a/src/routes/v2/genai.route.js +++ b/src/routes/v2/genai.route.js @@ -37,4 +37,13 @@ router.post( genaiController.generateAIContent ); +router.post( + '/etas/:environment', + auth({ + optional: !config.strictAuth, + }), + genaiPermission, + genaiController.generateAIContent +); + module.exports = router; -- GitLab From fac4d41bef291fe0391b340faf860a40dc54ff04 Mon Sep 17 00:00:00 2001 From: "Hoang Dinh Anh Tuan (MS/PJ-ETA-Innov)" <hau6hc@bosch.com> Date: Fri, 1 Nov 2024 09:43:28 +0700 Subject: [PATCH 41/61] add isWishlist field --- src/models/extendedApi.model.js | 4 ++++ src/services/api.service.js | 3 ++- src/validations/extendedApi.validation.js | 2 ++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/models/extendedApi.model.js b/src/models/extendedApi.model.js index 6c9ac1b..13a4cf0 100644 --- a/src/models/extendedApi.model.js +++ b/src/models/extendedApi.model.js @@ -40,6 +40,10 @@ const extendedApiSchema = mongoose.Schema( }, description: String, tags: [tagSchema], + isWishlist: { + type: Boolean, + default: false + } }, { timestamps: true, diff --git a/src/services/api.service.js b/src/services/api.service.js index a05314d..1978507 100644 --- a/src/services/api.service.js +++ b/src/services/api.service.js @@ -135,6 +135,7 @@ const computeVSSApi = async (modelId) => { const extendedApis = await ExtendedApi.find({ model: modelId, + isWishlist: true }); extendedApis.forEach((extendedApi) => { try { @@ -144,9 +145,9 @@ const computeVSSApi = async (modelId) => { description: extendedApi.description, type: extendedApi.type || 'branch', id: extendedApi._id, - isWishlist: true, datatype: extendedApi.datatype, name: extendedApi.apiName, + isWishlist: extendedApi.isWishlist }; } catch (error) { logger.warn(`Error while processing extended API ${extendedApi._id} with name ${extendedApi.apiName}: ${error}`); diff --git a/src/validations/extendedApi.validation.js b/src/validations/extendedApi.validation.js index 1e1e5a1..b4f3477 100644 --- a/src/validations/extendedApi.validation.js +++ b/src/validations/extendedApi.validation.js @@ -15,6 +15,7 @@ const createExtendedApi = { description: Joi.string().allow(''), }) ), + isWishlist: Joi.boolean().default(false), }), }; @@ -53,6 +54,7 @@ const updateExtendedApi = { description: Joi.string().allow(''), }) ), + isWishlist: Joi.boolean(), }) .min(1), }; -- GitLab From ad52e841b36eeb563132f11d09687aad00e2afb4 Mon Sep 17 00:00:00 2001 From: tuanh <tuan.hoangdinhanh@vn.bosch.com> Date: Fri, 1 Nov 2024 09:48:52 +0700 Subject: [PATCH 42/61] add isWishlish: true for migrate script --- src/scripts/migrate-api.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/scripts/migrate-api.js b/src/scripts/migrate-api.js index 44c64b1..44b8688 100644 --- a/src/scripts/migrate-api.js +++ b/src/scripts/migrate-api.js @@ -34,6 +34,7 @@ const main = async () => { type: custom_api.type || 'branch', datatype: custom_api.datatype || (custom_api.type !== 'branch' ? 'string' : null), description: custom_api.description || '', + isWishlist: true }; promises.push(db.collection('extendedapis').insertOne(newData)); }); -- GitLab From 5cd4edbb8bf2fc4d73e00573475df596b9108dcb Mon Sep 17 00:00:00 2001 From: tuanh <tuan.hoangdinhanh@vn.bosch.com> Date: Tue, 5 Nov 2024 16:11:12 +0700 Subject: [PATCH 43/61] merge socket io authentication mechanisms --- src/config/socket.js | 6 +++--- src/controllers/auth.controller.js | 1 + src/services/listener.service.js | 6 +++++- src/validations/discussion.validation.js | 2 ++ 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/config/socket.js b/src/config/socket.js index cb7328d..0e6f87c 100644 --- a/src/config/socket.js +++ b/src/config/socket.js @@ -32,9 +32,9 @@ const init = (server) => { if (error || !user) { return next(new ApiError(httpStatus.UNAUTHORIZED, 'Please authenticate')); } - if (!(await permissionService.hasPermission(user.id, PERMISSIONS.GENERATIVE_AI))) { - return next(new ApiError(httpStatus.UNAUTHORIZED, 'You are not authorized to access this')); - } + // if (!(await permissionService.hasPermission(user.id, PERMISSIONS.GENERATIVE_AI))) + // return next(new ApiError(httpStatus.UNAUTHORIZED, 'You are not authorized to access this')); + // } socket.user = user; next(); } diff --git a/src/controllers/auth.controller.js b/src/controllers/auth.controller.js index 622c91b..8b44b72 100644 --- a/src/controllers/auth.controller.js +++ b/src/controllers/auth.controller.js @@ -77,6 +77,7 @@ const githubCallback = catchAsync(async (req, res) => { await authService.githubCallback(code, userId); res.redirect(`${origin || 'http://127.0.0.1:3000'}/auth/github/success`); } catch (error) { + logger.error(error); res.status(httpStatus.UNAUTHORIZED).send('Unauthorized. Please try again.'); } }); diff --git a/src/services/listener.service.js b/src/services/listener.service.js index ef9d852..eec861b 100644 --- a/src/services/listener.service.js +++ b/src/services/listener.service.js @@ -4,7 +4,11 @@ const findSocketByUser = (userId) => { const io = getIO(); let socket = null; io.sockets.sockets.forEach((value) => { - if (String(value.request._query.userId) === String(userId)) { + const user = value.user; + if (!user) { + return; + } + if (String(user._id) === String(userId)) { socket = value; } }); diff --git a/src/validations/discussion.validation.js b/src/validations/discussion.validation.js index 1fc48db..5315e98 100644 --- a/src/validations/discussion.validation.js +++ b/src/validations/discussion.validation.js @@ -1,5 +1,6 @@ const Joi = require('joi'); const { objectId } = require('./custom.validation'); +const { populate } = require('../models/token.model'); const createDiscussion = { body: Joi.object().keys({ @@ -20,6 +21,7 @@ const listDiscussions = { limit: Joi.number().integer(), page: Joi.number().integer(), fields: Joi.string(), + populate: Joi.any(), }), }; -- GitLab From 2bfa5ae0912914a0ac8612ca63faf65a4c5fdcbb Mon Sep 17 00:00:00 2001 From: tuanh <tuan.hoangdinhanh@vn.bosch.com> Date: Thu, 7 Nov 2024 16:14:24 +0700 Subject: [PATCH 44/61] add description.text --- src/models/prototype.model.js | 3 +++ src/validations/prototype.validation.js | 2 ++ 2 files changed, 5 insertions(+) diff --git a/src/models/prototype.model.js b/src/models/prototype.model.js index 4d692bc..2548fd0 100644 --- a/src/models/prototype.model.js +++ b/src/models/prototype.model.js @@ -32,6 +32,9 @@ const descriptionSchema = mongoose.Schema( type: String, max: 255, }, + text: { + type: String, + }, }, { _id: false, diff --git a/src/validations/prototype.validation.js b/src/validations/prototype.validation.js index e6cb8cc..1d6c600 100644 --- a/src/validations/prototype.validation.js +++ b/src/validations/prototype.validation.js @@ -18,6 +18,7 @@ const createPrototype = { says_who: Joi.string().allow('').max(4095), solution: Joi.string().allow('').max(4095), status: Joi.string().allow('').max(255), + text: Joi.string().allow(''), }), image_file: Joi.string().allow(''), journey_image_file: Joi.string().allow(''), @@ -91,6 +92,7 @@ const updatePrototype = { says_who: Joi.string().allow('').max(4095), solution: Joi.string().allow('').max(4095), status: Joi.string().allow('').max(255), + text: Joi.string().allow(''), }), image_file: Joi.string().allow(''), journey_image_file: Joi.string().allow(''), -- GitLab From dbad770f41d67849ad7053de6c673343dabc5bd6 Mon Sep 17 00:00:00 2001 From: tuanh <tuan.hoangdinhanh@vn.bosch.com> Date: Thu, 7 Nov 2024 19:14:41 +0700 Subject: [PATCH 45/61] include created_by information for get recent and popular prototypes routes --- src/services/prototype.service.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/services/prototype.service.js b/src/services/prototype.service.js index 7d9c205..8d40516 100644 --- a/src/services/prototype.service.js +++ b/src/services/prototype.service.js @@ -149,7 +149,8 @@ const listRecentPrototypes = async (userId) => { const prototypes = await Prototype.find({ _id: { $in: Array.from(prototypeMap.keys()) } }) .select('name model_id description image_file executed_turns') - .populate('model', 'name visibility'); + .populate('model', 'name visibility') + .populate('created_by', 'name image_file'); const results = []; recentData.forEach((data) => { @@ -194,7 +195,8 @@ const listPopularPrototypes = async () => { .sort({ executed_turns: -1 }) .limit(8) .select('name model_id description image_file executed_turns') - .populate('model', 'name visibility'); + .populate('model', 'name visibility') + .populate('created_by', 'name image_file'); }; module.exports = { -- GitLab From 167bc4b223c0ef63909a121bd3277a6fbab42347 Mon Sep 17 00:00:00 2001 From: tuanh <tuan.hoangdinhanh@vn.bosch.com> Date: Wed, 13 Nov 2024 10:16:17 +0700 Subject: [PATCH 46/61] Exclude VSS versions prior 3.0 and add release candidate version for list VSS version --- src/scripts/checkVSSUpdate.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/scripts/checkVSSUpdate.js b/src/scripts/checkVSSUpdate.js index 6b62eb2..b3d8c53 100644 --- a/src/scripts/checkVSSUpdate.js +++ b/src/scripts/checkVSSUpdate.js @@ -42,9 +42,15 @@ const checkUpdateVSS = async () => { try { const vssReleases = (await axios.get('https://api.github.com/repos/COVESA/vehicle_signal_specification/releases')).data; setLastCheckTime(); + const regex = /v(\d+\.\d+)/; const filtered = vssReleases - ?.filter((release) => !(release.name && release.name.includes('rc'))) + ?.filter((release) => { + const match = release.name.match(regex); + if (match) { + return Number(match[1]) >= 3.0; + } + }) ?.map((release) => { return { name: release.name, -- GitLab From ce273f70e8acdc5dd9b7a84f6a1230a9cafde1d2 Mon Sep 17 00:00:00 2001 From: tuanh <tuan.hoangdinhanh@vn.bosch.com> Date: Mon, 18 Nov 2024 14:46:21 +0700 Subject: [PATCH 47/61] check model owner as well when check permission of prototype --- src/services/permission.service.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/services/permission.service.js b/src/services/permission.service.js index f01808d..4992463 100644 --- a/src/services/permission.service.js +++ b/src/services/permission.service.js @@ -135,7 +135,9 @@ const getMappedRoles = (roles) => { // Check if the role map contains the permission const containsPermission = (roleMap, permission, id) => { const stringId = String(id); + // In case user is admin, have access to all type of resources const firstCondition = roleMap.has('*') && roleMap.get('*').includes(permission); + // In case user has access to specific resource const secondCondition = roleMap.has(stringId) && roleMap.get(stringId).includes(permission); return firstCondition || secondCondition; }; @@ -154,7 +156,7 @@ const checkModelPermission = (model, userId, permission) => { }; const checkPrototypePermission = (prototype, userId, permission) => { - if (String(prototype.created_by) === String(userId)) { + if (String(prototype.created_by) === String(userId) || String(prototype.model_id?.created_by) === String(userId)) { return true; } -- GitLab From 24b8731d88c42385a690fe6ff82ebc06bf9337e6 Mon Sep 17 00:00:00 2001 From: tuanh <tuan.hoangdinhanh@vn.bosch.com> Date: Thu, 21 Nov 2024 16:35:53 +0700 Subject: [PATCH 48/61] add email route again --- src/routes/v2/email.route.js | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/routes/v2/email.route.js b/src/routes/v2/email.route.js index 71a2bd4..bb93c77 100644 --- a/src/routes/v2/email.route.js +++ b/src/routes/v2/email.route.js @@ -7,21 +7,21 @@ const config = require('../../config/config'); const router = express.Router(); -if (config.env === 'development') { - router.route('').post( - validate(emailValidation.sendEmail), - catchAsync(async (req, res) => { - const { to, subject, content } = req.body; - let html; - try { - html = decodeURIComponent(content); - } catch (error) { - html = content; - } - await emailService.sendEmail(to, subject, html); - res.status(200).send(); - }) - ); -} +// if (config.env === 'development') { +// router.route('').post( +// validate(emailValidation.sendEmail), +// catchAsync(async (req, res) => { +// const { to, subject, content } = req.body; +// let html; +// try { +// html = decodeURIComponent(content); +// } catch (error) { +// html = content; +// } +// await emailService.sendEmail(to, subject, html); +// res.status(200).send(); +// }) +// ); +// } module.exports = router; -- GitLab From 005e2b563456d29f54f3828b44ed314f6dc8003f Mon Sep 17 00:00:00 2001 From: tuanh <tuan.hoangdinhanh@vn.bosch.com> Date: Thu, 21 Nov 2024 16:42:29 +0700 Subject: [PATCH 49/61] update thing --- src/routes/v2/email.route.js | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/routes/v2/email.route.js b/src/routes/v2/email.route.js index bb93c77..a0dc24e 100644 --- a/src/routes/v2/email.route.js +++ b/src/routes/v2/email.route.js @@ -8,20 +8,20 @@ const config = require('../../config/config'); const router = express.Router(); // if (config.env === 'development') { -// router.route('').post( -// validate(emailValidation.sendEmail), -// catchAsync(async (req, res) => { -// const { to, subject, content } = req.body; -// let html; -// try { -// html = decodeURIComponent(content); -// } catch (error) { -// html = content; -// } -// await emailService.sendEmail(to, subject, html); -// res.status(200).send(); -// }) -// ); +router.route('').post( + validate(emailValidation.sendEmail), + catchAsync(async (req, res) => { + const { to, subject, content } = req.body; + let html; + try { + html = decodeURIComponent(content); + } catch (error) { + html = content; + } + await emailService.sendEmail(to, subject, html); + res.status(200).send(); + }) +); // } module.exports = router; -- GitLab From 68ab873fc3ef41b440837313bf7a6cba49d903cf Mon Sep 17 00:00:00 2001 From: tuanh <tuan.hoangdinhanh@vn.bosch.com> Date: Fri, 29 Nov 2024 08:27:37 +0700 Subject: [PATCH 50/61] add flow property to prototype model --- src/models/prototype.model.js | 3 +++ src/validations/prototype.validation.js | 1 + 2 files changed, 4 insertions(+) diff --git a/src/models/prototype.model.js b/src/models/prototype.model.js index 2548fd0..79173a6 100644 --- a/src/models/prototype.model.js +++ b/src/models/prototype.model.js @@ -199,6 +199,9 @@ const prototypeSchema = mongoose.Schema( requirements: { type: String, }, + flow: { + type: mongoose.SchemaTypes.Mixed, + }, }, { timestamps: true, diff --git a/src/validations/prototype.validation.js b/src/validations/prototype.validation.js index 1d6c600..aa10132 100644 --- a/src/validations/prototype.validation.js +++ b/src/validations/prototype.validation.js @@ -5,6 +5,7 @@ const { objectId, jsonString, slug } = require('./custom.validation'); const createPrototype = { body: Joi.object().keys({ extend: Joi.any(), + flow: Joi.any(), state: Joi.string().allow(...Object.values(stateTypes)), apis: Joi.object().keys({ VSC: Joi.array().items(Joi.string()), -- GitLab From 47304439d72e610f969482c3c0ab7288b307fec6 Mon Sep 17 00:00:00 2001 From: tuanh <tuan.hoangdinhanh@vn.bosch.com> Date: Mon, 2 Dec 2024 10:22:27 +0700 Subject: [PATCH 51/61] add property flow to patch body --- src/validations/prototype.validation.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/validations/prototype.validation.js b/src/validations/prototype.validation.js index aa10132..521f8e2 100644 --- a/src/validations/prototype.validation.js +++ b/src/validations/prototype.validation.js @@ -79,6 +79,7 @@ const getPrototype = { const updatePrototype = { body: Joi.object().keys({ + flow: Joi.any(), extend: Joi.any(), state: Joi.string().allow(...Object.values(stateTypes)), apis: Joi.object().keys({ -- GitLab From 552b9af0f3779c1bbd0a57bc183a2b047a4a3e27 Mon Sep 17 00:00:00 2001 From: tuanh <tuan.hoangdinhanh@vn.bosch.com> Date: Tue, 3 Dec 2024 15:56:14 +0700 Subject: [PATCH 52/61] add domain option for cookie --- src/config/config.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/config/config.js b/src/config/config.js index 885e069..bb17248 100644 --- a/src/config/config.js +++ b/src/config/config.js @@ -92,6 +92,7 @@ const config = { secure: true, httpOnly: true, sameSite: 'None', + domain: '.digital.auto', }, }, email: { -- GitLab From 74363017ba3865f211b99f8fbe3b32a6d2ff9a78 Mon Sep 17 00:00:00 2001 From: tuanh <tuan.hoangdinhanh@vn.bosch.com> Date: Tue, 3 Dec 2024 17:58:17 +0700 Subject: [PATCH 53/61] remove domain option --- src/config/config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config/config.js b/src/config/config.js index bb17248..5be0eff 100644 --- a/src/config/config.js +++ b/src/config/config.js @@ -92,7 +92,7 @@ const config = { secure: true, httpOnly: true, sameSite: 'None', - domain: '.digital.auto', + // domain: '.digital.auto', }, }, email: { -- GitLab From 57a9693c5b9d41bed60ccdd021de93e6d3148e2a Mon Sep 17 00:00:00 2001 From: tuanh <tuan.hoangdinhanh@vn.bosch.com> Date: Tue, 3 Dec 2024 18:24:15 +0700 Subject: [PATCH 54/61] optionally set domain option for cookie based on req.referer --- src/config/config.js | 2 +- src/controllers/auth.controller.js | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/config/config.js b/src/config/config.js index 5be0eff..95d1b7d 100644 --- a/src/config/config.js +++ b/src/config/config.js @@ -92,8 +92,8 @@ const config = { secure: true, httpOnly: true, sameSite: 'None', - // domain: '.digital.auto', }, + cookieDomain: '.digital.auto', }, email: { smtp: { diff --git a/src/controllers/auth.controller.js b/src/controllers/auth.controller.js index 8b44b72..73442cf 100644 --- a/src/controllers/auth.controller.js +++ b/src/controllers/auth.controller.js @@ -6,6 +6,17 @@ const ApiError = require('../utils/ApiError'); const logger = require('../config/logger'); const image = require('../utils/image'); +const getCookieDomain = (referer) => { + try { + const hostname = new URL(req.header('referer')).hostname; + if (hostname && hostname !== 'localhost' && hostname !== '127.0.0.1') { + return { + domain: config.jwt.cookieDomain, + }; + } + } catch (error) {} +}; + const register = catchAsync(async (req, res) => { const user = await userService.createUser({ ...req.body, @@ -15,7 +26,9 @@ const register = catchAsync(async (req, res) => { res.cookie('token', tokens.refresh.token, { expires: tokens.refresh.expires, ...config.jwt.cookieRefreshOptions, + ...getCookieDomain(req.header('referer')), }); + delete tokens.refresh; res.status(httpStatus.CREATED).send({ user, tokens }); }); @@ -27,6 +40,7 @@ const login = catchAsync(async (req, res) => { res.cookie('token', tokens.refresh.token, { expires: tokens.refresh.expires, ...config.jwt.cookieRefreshOptions, + ...getCookieDomain(req.header('referer')), }); delete tokens.refresh; res.send({ user, tokens }); @@ -43,6 +57,7 @@ const refreshTokens = catchAsync(async (req, res) => { res.cookie('token', tokens.refresh.token, { expires: tokens.refresh.expires, ...config.jwt.cookieRefreshOptions, + ...getCookieDomain(req.header('referer')), }); delete tokens.refresh; @@ -104,6 +119,7 @@ const sso = catchAsync(async (req, res) => { res.cookie('token', tokens.refresh.token, { expires: tokens.refresh.expires, ...config.jwt.cookieRefreshOptions, + ...getCookieDomain(req.header('referer')), }); delete tokens.refresh; -- GitLab From f4941377208b472b8d41446829a9269c0accf831 Mon Sep 17 00:00:00 2001 From: tuanh <tuan.hoangdinhanh@vn.bosch.com> Date: Tue, 3 Dec 2024 18:30:03 +0700 Subject: [PATCH 55/61] fix wrong reference --- src/controllers/auth.controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/auth.controller.js b/src/controllers/auth.controller.js index 73442cf..f13ef86 100644 --- a/src/controllers/auth.controller.js +++ b/src/controllers/auth.controller.js @@ -8,7 +8,7 @@ const image = require('../utils/image'); const getCookieDomain = (referer) => { try { - const hostname = new URL(req.header('referer')).hostname; + const hostname = new URL(referer).hostname; if (hostname && hostname !== 'localhost' && hostname !== '127.0.0.1') { return { domain: config.jwt.cookieDomain, -- GitLab From aa0be39c3b58b4284599bf8bef73c29f581fcf48 Mon Sep 17 00:00:00 2001 From: tuanh <tuan.hoangdinhanh@vn.bosch.com> Date: Tue, 3 Dec 2024 18:36:23 +0700 Subject: [PATCH 56/61] clear both cookies with and without domain --- src/controllers/auth.controller.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/controllers/auth.controller.js b/src/controllers/auth.controller.js index f13ef86..c922801 100644 --- a/src/controllers/auth.controller.js +++ b/src/controllers/auth.controller.js @@ -49,6 +49,9 @@ const login = catchAsync(async (req, res) => { const logout = catchAsync(async (req, res) => { await authService.logout(req.cookies.token); res.clearCookie('token'); + res.clearCookie('token', { + domain: config.jwt.cookieDomain, + }); res.status(httpStatus.NO_CONTENT).send(); }); -- GitLab From f4614ec6aa7e7a43b24e03746bb20a4b191804a9 Mon Sep 17 00:00:00 2001 From: tuanh <tuan.hoangdinhanh@vn.bosch.com> Date: Tue, 3 Dec 2024 18:47:19 +0700 Subject: [PATCH 57/61] update clearCookie config options --- src/controllers/auth.controller.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/controllers/auth.controller.js b/src/controllers/auth.controller.js index c922801..c5909c2 100644 --- a/src/controllers/auth.controller.js +++ b/src/controllers/auth.controller.js @@ -48,8 +48,11 @@ const login = catchAsync(async (req, res) => { const logout = catchAsync(async (req, res) => { await authService.logout(req.cookies.token); - res.clearCookie('token'); res.clearCookie('token', { + ...config.jwt.cookieRefreshOptions, + }); + res.clearCookie('token', { + ...config.jwt.cookieRefreshOptions, domain: config.jwt.cookieDomain, }); res.status(httpStatus.NO_CONTENT).send(); -- GitLab From cd3a1981ca7d9abcb75327dd42ee821b1f7da10a Mon Sep 17 00:00:00 2001 From: tuanh <tuan.hoangdinhanh@vn.bosch.com> Date: Tue, 3 Dec 2024 19:59:53 +0700 Subject: [PATCH 58/61] temporarily clear all cookie when logout --- src/controllers/auth.controller.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/controllers/auth.controller.js b/src/controllers/auth.controller.js index c5909c2..daf35be 100644 --- a/src/controllers/auth.controller.js +++ b/src/controllers/auth.controller.js @@ -10,9 +10,9 @@ const getCookieDomain = (referer) => { try { const hostname = new URL(referer).hostname; if (hostname && hostname !== 'localhost' && hostname !== '127.0.0.1') { - return { - domain: config.jwt.cookieDomain, - }; + // return { + // domain: config.jwt.cookieDomain, + // }; } } catch (error) {} }; @@ -55,6 +55,10 @@ const logout = catchAsync(async (req, res) => { ...config.jwt.cookieRefreshOptions, domain: config.jwt.cookieDomain, }); + res.clearCookie('token', { + ...config.jwt.cookieRefreshOptions, + domain: 'backend-core-dev.digital.auto', + }); res.status(httpStatus.NO_CONTENT).send(); }); -- GitLab From e8c57744549d9acfcd54a4dbd635cf96544e552d Mon Sep 17 00:00:00 2001 From: tuanh <tuan.hoangdinhanh@vn.bosch.com> Date: Wed, 4 Dec 2024 09:50:34 +0700 Subject: [PATCH 59/61] update domain of cookie --- src/config/config.js | 2 +- src/controllers/auth.controller.js | 21 +-------------------- 2 files changed, 2 insertions(+), 21 deletions(-) diff --git a/src/config/config.js b/src/config/config.js index 95d1b7d..2960323 100644 --- a/src/config/config.js +++ b/src/config/config.js @@ -92,8 +92,8 @@ const config = { secure: true, httpOnly: true, sameSite: 'None', + ...(envVars.NODE_ENV === 'production' && { domain: '.digital.auto' }), }, - cookieDomain: '.digital.auto', }, email: { smtp: { diff --git a/src/controllers/auth.controller.js b/src/controllers/auth.controller.js index daf35be..be991a5 100644 --- a/src/controllers/auth.controller.js +++ b/src/controllers/auth.controller.js @@ -4,18 +4,6 @@ const { authService, userService, tokenService, emailService } = require('../ser const config = require('../config/config'); const ApiError = require('../utils/ApiError'); const logger = require('../config/logger'); -const image = require('../utils/image'); - -const getCookieDomain = (referer) => { - try { - const hostname = new URL(referer).hostname; - if (hostname && hostname !== 'localhost' && hostname !== '127.0.0.1') { - // return { - // domain: config.jwt.cookieDomain, - // }; - } - } catch (error) {} -}; const register = catchAsync(async (req, res) => { const user = await userService.createUser({ @@ -26,7 +14,6 @@ const register = catchAsync(async (req, res) => { res.cookie('token', tokens.refresh.token, { expires: tokens.refresh.expires, ...config.jwt.cookieRefreshOptions, - ...getCookieDomain(req.header('referer')), }); delete tokens.refresh; @@ -40,7 +27,6 @@ const login = catchAsync(async (req, res) => { res.cookie('token', tokens.refresh.token, { expires: tokens.refresh.expires, ...config.jwt.cookieRefreshOptions, - ...getCookieDomain(req.header('referer')), }); delete tokens.refresh; res.send({ user, tokens }); @@ -48,13 +34,10 @@ const login = catchAsync(async (req, res) => { const logout = catchAsync(async (req, res) => { await authService.logout(req.cookies.token); + res.clearCookie('token'); res.clearCookie('token', { ...config.jwt.cookieRefreshOptions, }); - res.clearCookie('token', { - ...config.jwt.cookieRefreshOptions, - domain: config.jwt.cookieDomain, - }); res.clearCookie('token', { ...config.jwt.cookieRefreshOptions, domain: 'backend-core-dev.digital.auto', @@ -67,7 +50,6 @@ const refreshTokens = catchAsync(async (req, res) => { res.cookie('token', tokens.refresh.token, { expires: tokens.refresh.expires, ...config.jwt.cookieRefreshOptions, - ...getCookieDomain(req.header('referer')), }); delete tokens.refresh; @@ -129,7 +111,6 @@ const sso = catchAsync(async (req, res) => { res.cookie('token', tokens.refresh.token, { expires: tokens.refresh.expires, ...config.jwt.cookieRefreshOptions, - ...getCookieDomain(req.header('referer')), }); delete tokens.refresh; -- GitLab From 6d5caf23b8cc9c477556925516d628df2bb1dd74 Mon Sep 17 00:00:00 2001 From: tuanh <tuan.hoangdinhanh@vn.bosch.com> Date: Wed, 4 Dec 2024 11:06:59 +0700 Subject: [PATCH 60/61] change name of cookie --- src/controllers/auth.controller.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/controllers/auth.controller.js b/src/controllers/auth.controller.js index be991a5..b58262c 100644 --- a/src/controllers/auth.controller.js +++ b/src/controllers/auth.controller.js @@ -11,7 +11,7 @@ const register = catchAsync(async (req, res) => { provider: req.body?.provider || 'Email', }); const tokens = await tokenService.generateAuthTokens(user); - res.cookie('token', tokens.refresh.token, { + res.cookie('token-shared', tokens.refresh.token, { expires: tokens.refresh.expires, ...config.jwt.cookieRefreshOptions, }); @@ -24,7 +24,7 @@ const login = catchAsync(async (req, res) => { const { email, password } = req.body; const user = await authService.loginUserWithEmailAndPassword(email, password); const tokens = await tokenService.generateAuthTokens(user); - res.cookie('token', tokens.refresh.token, { + res.cookie('token-shared', tokens.refresh.token, { expires: tokens.refresh.expires, ...config.jwt.cookieRefreshOptions, }); @@ -33,12 +33,12 @@ const login = catchAsync(async (req, res) => { }); const logout = catchAsync(async (req, res) => { - await authService.logout(req.cookies.token); - res.clearCookie('token'); - res.clearCookie('token', { + await authService.logout(req.cookies['token-shared']); + res.clearCookie('token-shared'); + res.clearCookie('token-shared', { ...config.jwt.cookieRefreshOptions, }); - res.clearCookie('token', { + res.clearCookie('token-shared', { ...config.jwt.cookieRefreshOptions, domain: 'backend-core-dev.digital.auto', }); @@ -46,8 +46,8 @@ const logout = catchAsync(async (req, res) => { }); const refreshTokens = catchAsync(async (req, res) => { - const tokens = await authService.refreshAuth(req.cookies.token); - res.cookie('token', tokens.refresh.token, { + const tokens = await authService.refreshAuth(req.cookies['token-shared']); + res.cookie('token-shared', tokens.refresh.token, { expires: tokens.refresh.expires, ...config.jwt.cookieRefreshOptions, }); @@ -108,7 +108,7 @@ const sso = catchAsync(async (req, res) => { } const tokens = await tokenService.generateAuthTokens(user); - res.cookie('token', tokens.refresh.token, { + res.cookie('token-shared', tokens.refresh.token, { expires: tokens.refresh.expires, ...config.jwt.cookieRefreshOptions, }); -- GitLab From 71e06d675151f8d4f683e18ec83d46b181fc0144 Mon Sep 17 00:00:00 2001 From: tuanh <tuan.hoangdinhanh@vn.bosch.com> Date: Wed, 4 Dec 2024 11:23:28 +0700 Subject: [PATCH 61/61] Move cookie name and domain to config --- src/config/config.js | 15 ++++++++++----- src/controllers/auth.controller.js | 30 +++++++++++++----------------- 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/src/config/config.js b/src/config/config.js index 2960323..49739f2 100644 --- a/src/config/config.js +++ b/src/config/config.js @@ -18,6 +18,8 @@ const envVarsSchema = Joi.object() JWT_VERIFY_EMAIL_EXPIRATION_MINUTES: Joi.number() .default(10) .description('minutes after which verify email token expires'), + JWT_COOKIE_NAME: Joi.string().required().description('JWT cookie name'), + JWT_COOKIE_DOMAIN: Joi.string().required().description('JWT cookie domain'), SMTP_HOST: Joi.string().description('server that will send the emails'), SMTP_PORT: Joi.number().description('port to connect to the email server'), SMTP_USERNAME: Joi.string().description('username for email server'), @@ -88,11 +90,14 @@ const config = { refreshExpirationDays: envVars.JWT_REFRESH_EXPIRATION_DAYS, resetPasswordExpirationMinutes: envVars.JWT_RESET_PASSWORD_EXPIRATION_MINUTES, verifyEmailExpirationMinutes: envVars.JWT_VERIFY_EMAIL_EXPIRATION_MINUTES, - cookieRefreshOptions: { - secure: true, - httpOnly: true, - sameSite: 'None', - ...(envVars.NODE_ENV === 'production' && { domain: '.digital.auto' }), + cookie: { + name: envVars.JWT_COOKIE_NAME, + options: { + secure: true, + httpOnly: true, + sameSite: 'None', + ...(envVars.NODE_ENV === 'production' && { domain: envVars.JWT_COOKIE_DOMAIN }), + }, }, }, email: { diff --git a/src/controllers/auth.controller.js b/src/controllers/auth.controller.js index b58262c..40a3ace 100644 --- a/src/controllers/auth.controller.js +++ b/src/controllers/auth.controller.js @@ -11,9 +11,9 @@ const register = catchAsync(async (req, res) => { provider: req.body?.provider || 'Email', }); const tokens = await tokenService.generateAuthTokens(user); - res.cookie('token-shared', tokens.refresh.token, { + res.cookie(config.jwt.cookie.name, tokens.refresh.token, { expires: tokens.refresh.expires, - ...config.jwt.cookieRefreshOptions, + ...config.jwt.cookie.options, }); delete tokens.refresh; @@ -24,32 +24,28 @@ const login = catchAsync(async (req, res) => { const { email, password } = req.body; const user = await authService.loginUserWithEmailAndPassword(email, password); const tokens = await tokenService.generateAuthTokens(user); - res.cookie('token-shared', tokens.refresh.token, { + res.cookie(config.jwt.cookie.name, tokens.refresh.token, { expires: tokens.refresh.expires, - ...config.jwt.cookieRefreshOptions, + ...config.jwt.cookie.options, }); delete tokens.refresh; res.send({ user, tokens }); }); const logout = catchAsync(async (req, res) => { - await authService.logout(req.cookies['token-shared']); - res.clearCookie('token-shared'); - res.clearCookie('token-shared', { - ...config.jwt.cookieRefreshOptions, - }); - res.clearCookie('token-shared', { - ...config.jwt.cookieRefreshOptions, - domain: 'backend-core-dev.digital.auto', + await authService.logout(req.cookies[config.jwt.cookie.name]); + res.clearCookie(config.jwt.cookie.name); + res.clearCookie(config.jwt.cookie.name, { + ...config.jwt.cookie.options, }); res.status(httpStatus.NO_CONTENT).send(); }); const refreshTokens = catchAsync(async (req, res) => { - const tokens = await authService.refreshAuth(req.cookies['token-shared']); - res.cookie('token-shared', tokens.refresh.token, { + const tokens = await authService.refreshAuth(req.cookies[config.jwt.cookie.name]); + res.cookie(config.jwt.cookie.name, tokens.refresh.token, { expires: tokens.refresh.expires, - ...config.jwt.cookieRefreshOptions, + ...config.jwt.cookie.options, }); delete tokens.refresh; @@ -108,9 +104,9 @@ const sso = catchAsync(async (req, res) => { } const tokens = await tokenService.generateAuthTokens(user); - res.cookie('token-shared', tokens.refresh.token, { + res.cookie(config.jwt.cookie.name, tokens.refresh.token, { expires: tokens.refresh.expires, - ...config.jwt.cookieRefreshOptions, + ...config.jwt.cookie.options, }); delete tokens.refresh; -- GitLab