From c25961853106f359146f17c817ed0dd541ddfe95 Mon Sep 17 00:00:00 2001 From: Siddharth Rawat Date: Mon, 27 Nov 2023 20:13:54 -0500 Subject: [PATCH 1/2] chore: add elasticsearch and rabbitmq service boilerplate - Add new model for plan - Static plan JSON data --- .env.example | 10 + .github/workflows/test-suite.yml | 4 + README.md | 197 ++++++++++++++- docker-compose.yml | 36 +++ package-lock.json | 156 +++++++++++- package.json | 2 + src/api/controllers/health.controller.js | 7 +- src/api/controllers/plan.controller.js | 2 +- src/api/models/plan.model.js | 233 ++++++++++++++++++ src/api/routes/health.route.js | 2 +- src/api/services/elastic.service.js | 27 ++ src/api/services/rabbitmq.service.js | 22 ++ .../{redis.client.js => redis.service.js} | 18 +- src/certs/http_ca.crt | 3 + src/configs/app.config.js | 27 +- src/server.js | 2 +- use_case.json => static/plan.test.json | 0 17 files changed, 726 insertions(+), 22 deletions(-) create mode 100644 docker-compose.yml create mode 100644 src/api/models/plan.model.js create mode 100644 src/api/services/elastic.service.js create mode 100644 src/api/services/rabbitmq.service.js rename src/api/services/{redis.client.js => redis.service.js} (92%) create mode 100644 src/certs/http_ca.crt rename use_case.json => static/plan.test.json (100%) diff --git a/.env.example b/.env.example index 1fbede6..132f14a 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,14 @@ HOSTNAME=xxxxxxxxx PORT=xxxx +PLAN_TYPE=xxx +REDIS_HOST=xxxx REDIS_PORT=xxxx CLIENT_ID=xxxxxxx +ELASTIC_PASSWORD=xxxxx +CERT_PATH=xxxx +ELASTIC_BASE_URL=xxxx +ELASTICSEARCH_INDEX_NAME=xxxxx +RABBITMQ_QUEUE_NAME=xxxx +RABBITMQ_EXCHANGE_TYPE=xxxxx +RABBITMQ_EXCHANGE_NAME=xxxxx +RABBITMQ_KEY=xxxxx diff --git a/.github/workflows/test-suite.yml b/.github/workflows/test-suite.yml index cd97b12..ded19af 100644 --- a/.github/workflows/test-suite.yml +++ b/.github/workflows/test-suite.yml @@ -27,6 +27,10 @@ jobs: echo HOSTNAME=${{ vars.HOSTNAME }} >> .env echo PORT=${{ vars.PORT }} >> .env echo DATABASE=${{ vars.REDIS_PORT }} >> .env + echo CLIENT_ID=$${{ secrets.CLIENT_ID }} >> .env + echo ELASTIC_PASSWORD =${{ secrets.ELASTIC_PASSWORD }} > .env + echo CERT_PATH=${{ secrets.CERT_PATH }} > .env + echo ELASTIC_BASE_URL=${{ vars.ELASTIC_BASE_URL }} > .env - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v3 with: diff --git a/README.md b/README.md index d747ce2..accfdda 100644 --- a/README.md +++ b/README.md @@ -2,22 +2,211 @@ [![Unit Test Suite](https://github.com/sydrawat01/INFO7255/actions/workflows/test-suite.yml/badge.svg)](https://github.com/sydrawat01/INFO7255/actions/workflows/test-suite.yml) -## Setup +A course on distributed systems and big data indexing techniques. -1. Install the dependencies +## Automated Setup + +To install all application dependencies [Elasticsearch, Kibana and RabbitMQ], use the `docker-compose.yml` file to deploy all application dependencies using `docker compose`. + +- To create the cluster, run: + + ```bash + docker compose up + ``` + +- To delete the cluster and the network: + + ```bash + docker compose down + ``` + +- The default username and password for RabbitMQ is defined in the `docker-compose.yml` file. + - default username: `guest` + - default password: `guest` + +- The following is a list of the endpoints for the dependencies being created in docker: + - Elasticsearch: [http://localhost:9200](http://localhost:9200) + - Kibana: [http://localhost:5610](http://localhost:5610) + - RabbitMQ: [http://localhost:15672](http://localhost:15672) + +## Manual Setup + +- Install redis + + ```shell + # only for MacOs + brew install redis + ``` + +- Install the dependencies ```shell yarn # or if you are using npm: npm i ``` -2. Start the server locally +- Start the server locally ```shell yarn start:dev # npm run start:dev ``` +### [Elasticsearch Setup using Docker](https://www.elastic.co/guide/en/elasticsearch/reference/8.11/docker.html) + +We'll use a docker container to connect to the elasticsearch service. So make sure you have docker installed. + +To run the elasticsearch image in a docker container (single-node cluster): + +- Create a new docker network + + ```bash + # docker network create elasticnet + docker network create + ``` + +- Pull the elastic docker image + + ```bash + # Running an elastic docker container only works with a specified tag, not with + # latest versions. + # docker pull docker.elastic.co/elasticsearch/elasticsearch:8.11.1 + docker pull docker.elastic.co/elasticsearch/elasticsearch:[tag] + ``` + +> Use the -m flag to set a memory limit for the container. This removes the need to [manually set the JVM size](https://www.elastic.co/guide/en/elasticsearch/reference/8.11/docker.html#docker-set-heap-size). + +- Start an elasticsearch container + + ```bash + # docker run --name es01 --net elasticnet -p 9200:9200 -it -m 1GB docker.elastic.co/elasticsearch/elasticsearch:8.11.1 + docker run --name --net -p 9200:9200 -it -m 1GB \ + docker.elastic.co/elasticsearch/elasticsearch:[tag] + ``` + +> The command prints the `elastic` user password and an enrollment token for Kibana. + +- Copy the generated `elastic` password and enrollment token. These credentials are only shown when you start Elasticsearch for the first time. You can regenerate the credentials using the following commands. + + ```bash + docker exec -it es01 /usr/share/elasticsearch/bin/elasticsearch-reset-password -u elastic + docker exec -it es01 /usr/share/elasticsearch/bin/elasticsearch-create-enrollment-token -s kibana + ``` + +- We recommend storing the elastic password as an environment variable in your shell + + ```bash + export ELASTIC_PASSWORD="your_password" + ``` + +- Copy the `http_ca.crt` SSL certificate from the container to your local machine. + + ```bash + # docker cp es01:/usr/share/elasticsearch/config/certs/http_ca.crt /Users/sid/Developer/api/src/certs + docker cp :/usr/share/elasticsearch/config/certs/http_ca.crt /path/to/your/folder + ``` + +- Make a REST API call to Elasticsearch to ensure the Elasticsearch container is running. + + ```bash + curl --cacert /Users/sid/Developer/api/src/certs/http_ca.crt -u elastic:$ELASTIC_PASSWORD https://localhost:9200 + ``` + +This should printout a success message that looks similar to this: + +```json +{ + "name" : "686b14641876", + "cluster_name" : "docker-cluster", + "cluster_uuid" : "C4TdqRy3QKSQM9Cri2HBKA", + "version" : { + "number" : "8.11.1", + "build_flavor" : "default", + "build_type" : "docker", + "build_hash" : "6f9ff581fbcde658e6f69d6ce03050f060d1fd0c", + "build_date" : "2023-11-11T10:05:59.421038163Z", + "build_snapshot" : false, + "lucene_version" : "9.8.0", + "minimum_wire_compatibility_version" : "7.17.0", + "minimum_index_compatibility_version" : "7.0.0" + }, + "tagline" : "You Know, for Search" +} +``` + +### Kibana + +To install the Kibana docker image: + +- Pull the Kibana Docker image. + + ```bash + # docker pull docker.elastic.co/kibana/kibana:8.11.1 + docker pull docker.elastic.co/kibana/kibana:[tag] + ``` + +- Start a Kibana container. + + ```bash + # docker run --name kib01 --net elastic -p 5601:5601 docker.elastic.co/kibana/kibana:8.11.1 + docker run --name --net -p 5601:5601 docker.elastic.co/kibana/kibana:[tag] + ``` + +> NOTE: When Kibana starts, it outputs a unique generated link to the terminal. To access Kibana, open this link in a web browser. In your browser, enter the enrollment token that was generated when you started Elasticsearch. + +- To regenerate the token + + ```bash + docker exec -it es01 /usr/share/elasticsearch/bin/elasticsearch-create-enrollment-token -s kibana + ``` + +- Log in to Kibana as the `elastic` user with the password that was generated when you started Elasticsearch + + ```bash + # to regenerate the password, run: + docker exec -it es01 /usr/share/elasticsearch/bin/elasticsearch-reset-password -u elastic + ``` + +### RabbitMQ + +To install the RabbitMQ docker image: + +- Pull the RabbitMQ docker image + + ```bash + # docker pull rabbitmq:3.12-management-alpine + docker pull rabbitmq:[tag] + ``` + +- Create the RabbitMQ docker container + + ```bash + # docker run -it --rm --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3.12-management-alpine + docker run -it --rm --name -p 5672:5672 -p 15672:15672 rabbitmq:[tag] + ``` + +## Manual setup teardown + +To remove the containers and their network, run: + +```bash +# Remove the Elastic network +# docker network rm elastic +docker network rm + +# Remove Elasticsearch, Kibana and RabbitMQ containers +# docker rm es01 +docker rm +# docker rm kib01 +docker rm +# docker rm rabbitmq +docker rm +``` + ## Author -Siddharth Rawat +[Siddharth Rawat](https://sydrawat.live) + +## [License](./LICENSE) + +This project is licensed under the MIT License. ([see license file for details](./LICENSE)) diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..0379fe9 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,36 @@ +version: '3.0' +services: + elasticsearch: + container_name: elastic-container + image: docker.elastic.co/elasticsearch/elasticsearch:8.11.1 + restart: always + environment: + - xpack.security.enabled=false + - discovery.type=single-node + networks: + - ek-net + ports: + - '9200:9200' + kibana: + container_name: kibana-container + image: 'docker.elastic.co/kibana/kibana:8.11.1' + environment: + - 'ELASTICSEARCH_HOSTS=http://elastic-container:9200' + networks: + - ek-net + depends_on: + - elasticsearch + ports: + - '5601:5601' + rabbitmq3: + container_name: rabbitmq + image: 'rabbitmq:3.12-management' + environment: + - RABBITMQ_DEFAULT_USER=guest + - RABBITMQ_DEFAULT_PASS=guest + ports: + - '5672:5672' + - '15672:15672' +networks: + ek-net: + driver: bridge diff --git a/package-lock.json b/package-lock.json index d4fb61b..107eebb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,14 +1,16 @@ { "name": "api", - "version": "1.3.0", + "version": "1.4.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "api", - "version": "1.3.0", + "version": "1.4.0", "license": "MIT", "dependencies": { + "@elastic/elasticsearch": "^8.10.0", + "amqplib": "^0.10.3", "app-root-path": "^3.1.0", "crypto": "^1.0.1", "dotenv": "^16.3.1", @@ -51,6 +53,24 @@ "node": ">=0.10.0" } }, + "node_modules/@acuminous/bitsyntax": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@acuminous/bitsyntax/-/bitsyntax-0.1.2.tgz", + "integrity": "sha512-29lUK80d1muEQqiUsSo+3A0yP6CdspgC95EnKBMi22Xlwt79i/En4Vr67+cXhU+cZjbti3TgGGC5wy1stIywVQ==", + "dependencies": { + "buffer-more-ints": "~1.0.0", + "debug": "^4.3.4", + "safe-buffer": "~5.1.2" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/@acuminous/bitsyntax/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, "node_modules/@ampproject/remapping": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", @@ -1796,6 +1816,7 @@ "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", "dev": true, "optional": true, + "peer": true, "engines": { "node": ">=0.1.90" } @@ -1810,6 +1831,39 @@ "kuler": "^2.0.0" } }, + "node_modules/@elastic/elasticsearch": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/@elastic/elasticsearch/-/elasticsearch-8.10.0.tgz", + "integrity": "sha512-RIEyqz0D18bz/dK+wJltaak+7wKaxDELxuiwOJhuMrvbrBsYDFnEoTdP/TZ0YszHBgnRPGqBDBgH/FHNgHObiQ==", + "dependencies": { + "@elastic/transport": "^8.3.4", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@elastic/transport": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/@elastic/transport/-/transport-8.3.4.tgz", + "integrity": "sha512-+0o8o74sbzu3BO7oOZiP9ycjzzdOt4QwmMEjFc1zfO7M0Fh7QX1xrpKqZbSd8vBwihXNlSq/EnMPfgD2uFEmFg==", + "dependencies": { + "debug": "^4.3.4", + "hpagent": "^1.0.0", + "ms": "^2.1.3", + "secure-json-parse": "^2.4.0", + "tslib": "^2.4.0", + "undici": "^5.22.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@elastic/transport/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -1893,6 +1947,14 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@fastify/busboy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.0.tgz", + "integrity": "sha512-+KpH+QxZU7O4675t3mnkQKcZZg56u+K/Ct2K+N2AZYNVK8kyeo/bI18tI8aPm3tvNNRyTWfj6s5tnGNlcbQRsA==", + "engines": { + "node": ">=14" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.13", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", @@ -2844,6 +2906,41 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/amqplib": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/amqplib/-/amqplib-0.10.3.tgz", + "integrity": "sha512-UHmuSa7n8vVW/a5HGh2nFPqAEr8+cD4dEZ6u9GjP91nHfr1a54RyAKyra7Sb5NH7NBKOUlyQSMXIp0qAixKexw==", + "dependencies": { + "@acuminous/bitsyntax": "^0.1.2", + "buffer-more-ints": "~1.0.0", + "readable-stream": "1.x >=1.1.9", + "url-parse": "~1.5.10" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/amqplib/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" + }, + "node_modules/amqplib/node_modules/readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/amqplib/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==" + }, "node_modules/ansi-colors": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", @@ -3332,6 +3429,11 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, + "node_modules/buffer-more-ints": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-more-ints/-/buffer-more-ints-1.0.0.tgz", + "integrity": "sha512-EMetuGFz5SLsT0QTnXzINh4Ksr+oo4i+UGTXEshiGCQWnsgSs7ZhJ8fzlwQ+OzEMs0MpDAMr1hxnblp5a4vcHg==" + }, "node_modules/bundle-name": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-3.0.0.tgz", @@ -3488,6 +3590,7 @@ "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.3.tgz", "integrity": "sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==", "dev": true, + "peer": true, "dependencies": { "string-width": "^4.2.0" }, @@ -3811,8 +3914,7 @@ "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" }, "node_modules/cosmiconfig": { "version": "8.3.6", @@ -5922,6 +6024,14 @@ "node": "14 || >=16.14" } }, + "node_modules/hpagent": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/hpagent/-/hpagent-1.2.0.tgz", + "integrity": "sha512-A91dYTeIB6NoXG+PxTQpCCDDnfHsW9kc06Lvpu1TEe9gnd6ZFeiBoRO9JvzEv6xK7EX97/dUE8g/vBMTqTS3CA==", + "engines": { + "node": ">=14" + } + }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -11159,6 +11269,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -11516,6 +11631,11 @@ "node": ">=0.10.0" } }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" + }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -11675,6 +11795,11 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "node_modules/secure-json-parse": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", + "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==" + }, "node_modules/semantic-release": { "version": "22.0.6", "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-22.0.6.tgz", @@ -12947,8 +13072,7 @@ "node_modules/tslib": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "node_modules/type-check": { "version": "0.4.0", @@ -13085,6 +13209,17 @@ "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", "dev": true }, + "node_modules/undici": { + "version": "5.28.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.0.tgz", + "integrity": "sha512-gM12DkXhlAc5+/TPe60iy9P6ETgVfqTuRJ6aQ4w8RYu0MqKuXhaq3/b86GfzDQnNA3NUO6aUNdvevrKH59D0Nw==", + "dependencies": { + "@fastify/busboy": "^2.0.0" + }, + "engines": { + "node": ">=14.0" + } + }, "node_modules/unicode-canonical-property-names-ecmascript": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", @@ -13230,6 +13365,15 @@ "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/package.json b/package.json index 6647fbb..0a3dee9 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,8 @@ "test:dev": "mocha --require @babel/register './tests/*.test.js' --watch" }, "dependencies": { + "@elastic/elasticsearch": "^8.10.0", + "amqplib": "^0.10.3", "app-root-path": "^3.1.0", "crypto": "^1.0.1", "dotenv": "^16.3.1", diff --git a/src/api/controllers/health.controller.js b/src/api/controllers/health.controller.js index 3d2ec07..2fc80a9 100644 --- a/src/api/controllers/health.controller.js +++ b/src/api/controllers/health.controller.js @@ -8,7 +8,12 @@ const health = (req, res) => { `Requesting ${method} ${protocol}://${hostname}${originalUrl}`, metaData ) - res.sendStatus(200).json() + const data = { + message: 'OK', + date: new Date(), + uptime: process.uptime(), + } + res.status(200).json(data) } export { health } diff --git a/src/api/controllers/plan.controller.js b/src/api/controllers/plan.controller.js index e15391d..d8346bd 100644 --- a/src/api/controllers/plan.controller.js +++ b/src/api/controllers/plan.controller.js @@ -7,7 +7,7 @@ import { deletePlanService, patchObject, patchList, -} from '../services/redis.client' +} from '../services/redis.service' import { validateSchema } from '../utils/schemaValidation' import { BadRequestError, diff --git a/src/api/models/plan.model.js b/src/api/models/plan.model.js new file mode 100644 index 0000000..e387b60 --- /dev/null +++ b/src/api/models/plan.model.js @@ -0,0 +1,233 @@ +export const plan = { + definitions: {}, + $schema: 'http://json-schema.org/draft-07/schema#', + $id: 'http://example.com/root.json', + type: 'object', + title: 'The Plan Schema', + required: [ + 'planCostShares', + 'linkedPlanServices', + '_org', + 'objectId', + 'objectType', + 'planType', + 'creationDate', + ], + properties: { + planCostShares: { + $id: '#/properties/planCostShares', + type: 'object', + title: 'The PlanCostShares Schema', + required: ['deductible', '_org', 'copay', 'objectId', 'objectType'], + properties: { + deductible: { + $id: '#/properties/planCostShares/properties/deductible', + type: 'integer', + title: 'The deductible Schema', + default: 0, + examples: [2000], + }, + _org: { + $id: '#/properties/planCostShares/properties/_org', + type: 'string', + title: 'The _org Schema', + default: '', + examples: ['example.com'], + pattern: '^(.*)$', + }, + copay: { + $id: '#/properties/planCostShares/properties/copay', + type: 'integer', + title: 'The copay Schema', + default: 0, + examples: [23], + }, + objectId: { + $id: '#/properties/planCostShares/properties/objectId', + type: 'string', + title: 'The objectId Schema', + default: '', + examples: ['1234vxc2324sdf-501'], + pattern: '^(.*)$', + }, + objectType: { + $id: '#/properties/planCostShares/properties/objectType', + type: 'string', + title: 'The objectType Schema', + default: '', + examples: ['membercostshare'], + pattern: '^(.*)$', + }, + }, + }, + linkedPlanServices: { + $id: '#/properties/linkedPlanServices', + type: 'array', + title: 'The LinkedPlanServices Schema', + items: { + $id: '#/properties/linkedPlanServices/items', + type: 'object', + title: 'The Items Schema', + required: [ + 'linkedService', + 'planserviceCostShares', + '_org', + 'objectId', + 'objectType', + ], + properties: { + linkedService: { + $id: '#/properties/linkedPlanServices/items/properties/linkedService', + type: 'object', + title: 'The LinkedService Schema', + required: ['_org', 'objectId', 'objectType', 'name'], + properties: { + _org: { + $id: '#/properties/linkedPlanServices/items/properties/linkedService/properties/_org', + type: 'string', + title: 'The _org Schema', + default: '', + examples: ['example.com'], + pattern: '^(.*)$', + }, + objectId: { + $id: '#/properties/linkedPlanServices/items/properties/linkedService/properties/objectId', + type: 'string', + title: 'The objectId Schema', + default: '', + examples: ['1234520xvc30asdf-502'], + pattern: '^(.*)$', + }, + objectType: { + $id: '#/properties/linkedPlanServices/items/properties/linkedService/properties/objectType', + type: 'string', + title: 'The objectType Schema', + default: '', + examples: ['service'], + pattern: '^(.*)$', + }, + name: { + $id: '#/properties/linkedPlanServices/items/properties/linkedService/properties/name', + type: 'string', + title: 'The name Schema', + default: '', + examples: ['Yearly physical'], + pattern: '^(.*)$', + }, + }, + }, + planserviceCostShares: { + $id: '#/properties/linkedPlanServices/items/properties/planserviceCostShares', + type: 'object', + title: 'The planserviceCostShares Schema', + required: ['deductible', '_org', 'copay', 'objectId', 'objectType'], + properties: { + deductible: { + $id: '#/properties/linkedPlanServices/items/properties/planserviceCostShares/properties/deductible', + type: 'integer', + title: 'The Deductible Schema', + default: 0, + examples: [10], + }, + _org: { + $id: '#/properties/linkedPlanServices/items/properties/planserviceCostShares/properties/_org', + type: 'string', + title: 'The _org Schema', + default: '', + examples: ['example.com'], + pattern: '^(.*)$', + }, + copay: { + $id: '#/properties/linkedPlanServices/items/properties/planserviceCostShares/properties/copay', + type: 'integer', + title: 'The Copay Schema', + default: 0, + examples: [0], + }, + objectId: { + $id: '#/properties/linkedPlanServices/items/properties/planserviceCostShares/properties/objectId', + type: 'string', + title: 'The objectId Schema', + default: '', + examples: ['1234512xvc1314asdfs-503'], + pattern: '^(.*)$', + }, + objectType: { + $id: '#/properties/linkedPlanServices/items/properties/planserviceCostShares/properties/objectType', + type: 'string', + title: 'The objectType Schema', + default: '', + examples: ['membercostshare'], + pattern: '^(.*)$', + }, + }, + }, + _org: { + $id: '#/properties/linkedPlanServices/items/properties/_org', + type: 'string', + title: 'The _org Schema', + default: '', + examples: ['example.com'], + pattern: '^(.*)$', + }, + objectId: { + $id: '#/properties/linkedPlanServices/items/properties/objectId', + type: 'string', + title: 'The objectId Schema', + default: '', + examples: ['27283xvx9asdff-504'], + pattern: '^(.*)$', + }, + objectType: { + $id: '#/properties/linkedPlanServices/items/properties/objectType', + type: 'string', + title: 'The objectType Schema', + default: '', + examples: ['planservice'], + pattern: '^(.*)$', + }, + }, + }, + }, + _org: { + $id: '#/properties/_org', + type: 'string', + title: 'The _org Schema', + default: '', + examples: ['example.com'], + pattern: '^(.*)$', + }, + objectId: { + $id: '#/properties/objectId', + type: 'string', + title: 'The objectId Schema', + default: '', + examples: ['12xvxc345ssdsds-508'], + pattern: '^(.*)$', + }, + objectType: { + $id: '#/properties/objectType', + type: 'string', + title: 'The objectType Schema', + default: '', + examples: ['plan'], + pattern: '^(.*)$', + }, + planType: { + $id: '#/properties/planType', + type: 'string', + title: 'The planType Schema', + default: '', + examples: ['inNetwork'], + pattern: '^(.*)$', + }, + creationDate: { + $id: '#/properties/creationDate', + type: 'string', + title: 'The creationDate Schema', + default: '', + examples: ['12-12-2017'], + pattern: '^(.*)$', + }, + }, +} diff --git a/src/api/routes/health.route.js b/src/api/routes/health.route.js index 9edaa33..28d8722 100644 --- a/src/api/routes/health.route.js +++ b/src/api/routes/health.route.js @@ -4,6 +4,6 @@ import { health } from '../controllers/health.controller' const router = express.Router() router.get('/', health) -router.get('/health', health) +router.get('/healthz', health) export { router as healthRoute } diff --git a/src/api/services/elastic.service.js b/src/api/services/elastic.service.js new file mode 100644 index 0000000..e7fefc6 --- /dev/null +++ b/src/api/services/elastic.service.js @@ -0,0 +1,27 @@ +import { Client } from '@elastic/elasticsearch' +import fs from 'fs' +import appConfig from '../../configs/app.config' +import logger from '../../configs/logger.config' + +const { ELASTIC_PASSWORD, CERT_PATH, ELASTIC_BASE_URL } = appConfig + +// Elasticsearch client connection +const elasticClient = new Client({ + host: ELASTIC_BASE_URL, + // node: ELASTIC_BASE_URL, + // auth: { + // username: 'elastic', + // password: ELASTIC_PASSWORD, + // }, + // tls: { + // ca: fs.readFileSync(`${CERT_PATH}/http_ca.crt`), + // rejectUnauthorized: false, + // }, + log: 'trace', +}) + +elasticClient.cluster.health({}, (err, res, status) => { + if (res) logger.info(`## CLIENT HEALTH GREEN ##`) +}) + +export default elasticClient diff --git a/src/api/services/rabbitmq.service.js b/src/api/services/rabbitmq.service.js new file mode 100644 index 0000000..75551f1 --- /dev/null +++ b/src/api/services/rabbitmq.service.js @@ -0,0 +1,22 @@ +import { connect } from 'amqplib' +import appConfig from '../../configs/app.config' + +const { + RABBITMQ_QUEUE_NAME, + RABBITMQ_EXCHANGE_TYPE, + RABBITMQ_EXCHANGE_NAME, + RABBITMQ_KEY, +} = appConfig + +let channel + +const rabbitConnection = () => { + const connection = connect('amqp://localhost') + connection.then(async (conn) => { + channel = await conn.createChannel() + await channel.assertExchange(RABBITMQ_EXCHANGE_NAME, RABBITMQ_EXCHANGE_TYPE) + await channel.assertQueue(RABBITMQ_QUEUE_NAME) + channel.bindQueue(RABBITMQ_QUEUE_NAME, RABBITMQ_EXCHANGE_NAME, RABBITMQ_KEY) + }) +} +rabbitConnection() diff --git a/src/api/services/redis.client.js b/src/api/services/redis.service.js similarity index 92% rename from src/api/services/redis.client.js rename to src/api/services/redis.service.js index 6b00fb0..2573287 100644 --- a/src/api/services/redis.client.js +++ b/src/api/services/redis.service.js @@ -4,16 +4,19 @@ import crypto from 'crypto' import appConfig from '../../configs/app.config' import logger from '../../configs/logger.config' -const { REDIS_PORT } = appConfig +const { REDIS_HOST, REDIS_PORT } = appConfig + +const client = createClient(REDIS_PORT, REDIS_HOST) -const client = createClient(REDIS_PORT) -client.connect() -client.on('error', (err) => { - logger.error(`redis client error`, err) -}) client.on('connect', () => { - logger.info('Connected to redis') + logger.info('Connected to redis', { REDIS_HOST, REDIS_PORT }) }) +client.on('error', (error) => { + logger.error(`redis client error`, { error }) +}) + +// Connect to the Redis datastore client +client.connect() const createETag = (plan) => { try { @@ -137,4 +140,5 @@ export { deletePlanService, patchObject, patchList, + client, } diff --git a/src/certs/http_ca.crt b/src/certs/http_ca.crt new file mode 100644 index 0000000..1a810c7 --- /dev/null +++ b/src/certs/http_ca.crt @@ -0,0 +1,3 @@ +-----BEGIN RSA PRIVATE KEY----- +your-private-key +-----END RSA PRIVATE KEY----- \ No newline at end of file diff --git a/src/configs/app.config.js b/src/configs/app.config.js index b6a5967..3476775 100644 --- a/src/configs/app.config.js +++ b/src/configs/app.config.js @@ -1,12 +1,37 @@ import 'dotenv/config' -const { HOSTNAME, PORT, REDIS_PORT, CLIENT_ID } = process.env +const { + HOSTNAME, // Application hostname + PORT, // Application port number + PLAN_TYPE, // plan + REDIS_HOST, // Redis Datastore hostname + REDIS_PORT, // Redis Datastore port number + CLIENT_ID, // Google OAuth ClientID + ELASTIC_PASSWORD, // Elastic user password + CERT_PATH, // Elasticsearch certificate + ELASTIC_BASE_URL, // Elasticsearch base URL + ELASTICSEARCH_INDEX_NAME, // Elasticsearch index name + RABBITMQ_QUEUE_NAME, // RabbitMQ Queue Name + RABBITMQ_EXCHANGE_TYPE, // RabbitMQ exchange type + RABBITMQ_EXCHANGE_NAME, // RabbitMQ exchange name + RABBITMQ_KEY, // RabbitMQ key +} = process.env const appConfig = { HOSTNAME, PORT, + PLAN_TYPE, + REDIS_HOST, REDIS_PORT, CLIENT_ID, + ELASTIC_PASSWORD, + CERT_PATH, + ELASTIC_BASE_URL, + ELASTICSEARCH_INDEX_NAME, + RABBITMQ_QUEUE_NAME, + RABBITMQ_EXCHANGE_TYPE, + RABBITMQ_EXCHANGE_NAME, + RABBITMQ_KEY, } export default appConfig diff --git a/src/server.js b/src/server.js index 2694b81..4026e52 100644 --- a/src/server.js +++ b/src/server.js @@ -6,5 +6,5 @@ import logger from './configs/logger.config' const { HOSTNAME, PORT } = appConfig app.listen(PORT, () => { - logger.info(`Server running @ http://${HOSTNAME}:${PORT}`) + logger.info(`Server running @ http://${HOSTNAME}:${PORT}`, { HOSTNAME, PORT }) }) diff --git a/use_case.json b/static/plan.test.json similarity index 100% rename from use_case.json rename to static/plan.test.json From 9fa9d47416d1b61ff9165c953ddedbf5048504f9 Mon Sep 17 00:00:00 2001 From: Siddharth Rawat Date: Mon, 27 Nov 2023 20:16:51 -0500 Subject: [PATCH 2/2] chore: revert health endpoint --- src/api/routes/health.route.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/routes/health.route.js b/src/api/routes/health.route.js index 28d8722..9edaa33 100644 --- a/src/api/routes/health.route.js +++ b/src/api/routes/health.route.js @@ -4,6 +4,6 @@ import { health } from '../controllers/health.controller' const router = express.Router() router.get('/', health) -router.get('/healthz', health) +router.get('/health', health) export { router as healthRoute }