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
 
 [![Build Status](https://travis-ci.org/hagopj13/node-express-boilerplate.svg?branch=master)](https://travis-ci.org/hagopj13/node-express-boilerplate)
 [![Coverage Status](https://coveralls.io/repos/github/hagopj13/node-express-boilerplate/badge.svg?branch=master)](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
 
 [![Build Status](https://travis-ci.org/hagopj13/node-express-boilerplate.svg?branch=master)](https://travis-ci.org/hagopj13/node-express-boilerplate)
 [![Coverage Status](https://coveralls.io/repos/github/hagopj13/node-express-boilerplate/badge.svg?branch=master)](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
 
 [![Build Status](https://travis-ci.org/hagopj13/node-express-boilerplate.svg?branch=master)](https://travis-ci.org/hagopj13/node-express-boilerplate)
 [![Coverage Status](https://coveralls.io/repos/github/hagopj13/node-express-boilerplate/badge.svg?branch=master)](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
 
 [![Build Status](https://travis-ci.org/hagopj13/node-express-boilerplate.svg?branch=master)](https://travis-ci.org/hagopj13/node-express-boilerplate)
 [![Coverage Status](https://coveralls.io/repos/github/hagopj13/node-express-boilerplate/badge.svg?branch=master)](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