├── microservices ├── podData │ ├── .dockerignore │ ├── package.json │ ├── Dockerfile │ └── app.js ├── apiservice │ ├── .dockerignore │ ├── Dockerfile │ ├── package.json │ └── app.js ├── frontend │ ├── .dockerignore │ ├── components │ │ ├── clientHelpers │ │ │ ├── backendConfig.js │ │ │ ├── kitchenAPI.js │ │ │ ├── favoritesAPI.js │ │ │ └── ordersAPI.js │ │ ├── architecture.html │ │ ├── navigationbutton.html │ │ ├── order.html │ │ ├── menuitem.html │ │ ├── order.js │ │ ├── navigation.html │ │ ├── graph.html │ │ ├── restaurant.html │ │ ├── phone.js │ │ ├── navigation.js │ │ ├── navigationbutton.js │ │ ├── controls │ │ │ └── architectureupdater.js │ │ ├── slider.js │ │ ├── receipt.js │ │ ├── restaurant.js │ │ ├── phone.html │ │ ├── menuitem.js │ │ ├── slider.html │ │ └── receipt.html │ ├── package.json │ ├── images │ │ ├── taste-deselected.svg │ │ ├── taste-selected.svg │ │ ├── fish.svg │ │ ├── sprout.svg │ │ ├── noodle-bowl.svg │ │ ├── cafe.svg │ │ ├── receipt-deselected.svg │ │ └── receipt-selected.svg │ ├── Dockerfile │ ├── app.js │ ├── style │ │ └── style.css │ └── index.html ├── realtimedata │ ├── .dockerignore │ ├── package.json │ ├── Dockerfile │ ├── package-lock.json │ └── KafkaWrapper.js ├── ksqlcontroller │ ├── .dockerignore │ ├── package.json │ ├── Dockerfile │ └── app.js ├── statusService │ ├── .dockerignore │ ├── package.json │ ├── Dockerfile │ ├── app.js │ ├── KafkaWrapper.js │ └── package-lock.json ├── consumers │ ├── courierConsumer │ │ ├── .dockerignore │ │ ├── package.json │ │ ├── Dockerfile │ │ ├── models │ │ │ └── order.js │ │ └── KafkaWrapper.js │ ├── kitchenConsumer │ │ ├── .dockerignore │ │ ├── package.json │ │ ├── Dockerfile │ │ ├── models │ │ │ └── kitchen.js │ │ └── KafkaWrapper.js │ └── orderConsumer │ │ ├── .dockerignore │ │ ├── package.json │ │ ├── Dockerfile │ │ ├── models │ │ └── order.js │ │ └── KafkaWrapper.js └── eem │ ├── eemSubscriber │ ├── __tests__ │ │ ├── utils │ │ │ ├── stopContainers.sh │ │ │ └── startContainers.sh │ │ ├── client-helper-tests.js │ │ ├── kafka-messages.js │ │ └── server-api-tests.js │ ├── dashboard │ │ ├── restaurantDropdown.js │ │ ├── webhookURLeditor.js │ │ ├── clientHelper.js │ │ ├── index.html │ │ └── style │ │ │ └── style.css │ ├── Dockerfile │ ├── sendNotificationHelper.js │ ├── package.json │ ├── app.js │ ├── server.js │ └── KafkaWrapper.js │ ├── asyncapi-secret.yaml │ ├── exampleNotificationReceiver │ ├── example-pos │ │ ├── websocketReceiverController.js │ │ ├── clientHelper.js │ │ ├── index.html │ │ └── style │ │ │ └── style.css │ ├── package.json │ ├── Dockerfile │ ├── server.js │ ├── app.js │ └── __tests__ │ │ ├── websocket-test.js │ │ ├── client-helper-tests.js │ │ └── server-api-tests.js │ ├── building-container-images.md │ ├── example-restaurant-pos.yaml │ └── eem-subscriber.yaml ├── .DS_Store ├── docs └── images │ ├── flow.png │ ├── keda.png │ ├── apikey.png │ ├── topic.png │ ├── blank-pos.png │ ├── ccloudcli.png │ ├── ksqlname.png │ ├── output-1.png │ ├── output-2.png │ ├── api-portal.png │ ├── executeksql.png │ ├── ksqlcreate.png │ ├── ksqleditor.png │ ├── ksqlstatus.png │ ├── pos-ordered.png │ ├── api-products.png │ ├── architecture.png │ ├── createcluster.png │ ├── kedacontroller.png │ ├── api-product-plan.png │ ├── architecture-eem.png │ ├── create-async-api.png │ ├── output-animation.gif │ ├── sample-output-1.png │ ├── sample-output-2.png │ ├── api-product-gateway.png │ ├── api-product-select.png │ ├── api-product-client-id.png │ ├── api-product-subscribe.png │ ├── output-random-initial.png │ ├── architecture-with-ksqldb.png │ ├── async-api-configuration.png │ ├── eem-subscriber-webhook.png │ ├── api-product-api-key-secret.png │ ├── api-product-subscribe-selected.png │ ├── async-api-configuration-publish.png │ └── async-api-configuration-summary.png ├── concept └── scaling-apps-kafka.png ├── deployments ├── kafka-secret.yaml ├── keda-auth.yaml ├── orderconsumer.yaml ├── courierconsumer.yaml ├── kitchenconsumer.yaml ├── podconsumerdata.yaml ├── realtimedata.yaml ├── redis-dev.yaml ├── mongo-dev.yaml ├── frontend.yaml ├── apiservice.yaml ├── statusservice.yaml ├── routes.md ├── ksqlcontroller.yaml ├── routes.yaml └── keda-scaler.yaml ├── .vscode └── launch.json └── .gitignore /microservices/podData/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules/ -------------------------------------------------------------------------------- /microservices/apiservice/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules/ -------------------------------------------------------------------------------- /microservices/frontend/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules/ -------------------------------------------------------------------------------- /microservices/realtimedata/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules/ -------------------------------------------------------------------------------- /microservices/ksqlcontroller/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules/ -------------------------------------------------------------------------------- /microservices/statusService/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules/ -------------------------------------------------------------------------------- /microservices/consumers/courierConsumer/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules/ -------------------------------------------------------------------------------- /microservices/consumers/kitchenConsumer/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules/ -------------------------------------------------------------------------------- /microservices/consumers/orderConsumer/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules/ -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/scaling-apps-with-kafka/HEAD/.DS_Store -------------------------------------------------------------------------------- /docs/images/flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/scaling-apps-with-kafka/HEAD/docs/images/flow.png -------------------------------------------------------------------------------- /docs/images/keda.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/scaling-apps-with-kafka/HEAD/docs/images/keda.png -------------------------------------------------------------------------------- /docs/images/apikey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/scaling-apps-with-kafka/HEAD/docs/images/apikey.png -------------------------------------------------------------------------------- /docs/images/topic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/scaling-apps-with-kafka/HEAD/docs/images/topic.png -------------------------------------------------------------------------------- /docs/images/blank-pos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/scaling-apps-with-kafka/HEAD/docs/images/blank-pos.png -------------------------------------------------------------------------------- /docs/images/ccloudcli.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/scaling-apps-with-kafka/HEAD/docs/images/ccloudcli.png -------------------------------------------------------------------------------- /docs/images/ksqlname.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/scaling-apps-with-kafka/HEAD/docs/images/ksqlname.png -------------------------------------------------------------------------------- /docs/images/output-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/scaling-apps-with-kafka/HEAD/docs/images/output-1.png -------------------------------------------------------------------------------- /docs/images/output-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/scaling-apps-with-kafka/HEAD/docs/images/output-2.png -------------------------------------------------------------------------------- /microservices/frontend/components/clientHelpers/backendConfig.js: -------------------------------------------------------------------------------- 1 | let API_URL = "" 2 | let STATIC_DATA = false; -------------------------------------------------------------------------------- /docs/images/api-portal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/scaling-apps-with-kafka/HEAD/docs/images/api-portal.png -------------------------------------------------------------------------------- /docs/images/executeksql.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/scaling-apps-with-kafka/HEAD/docs/images/executeksql.png -------------------------------------------------------------------------------- /docs/images/ksqlcreate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/scaling-apps-with-kafka/HEAD/docs/images/ksqlcreate.png -------------------------------------------------------------------------------- /docs/images/ksqleditor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/scaling-apps-with-kafka/HEAD/docs/images/ksqleditor.png -------------------------------------------------------------------------------- /docs/images/ksqlstatus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/scaling-apps-with-kafka/HEAD/docs/images/ksqlstatus.png -------------------------------------------------------------------------------- /docs/images/pos-ordered.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/scaling-apps-with-kafka/HEAD/docs/images/pos-ordered.png -------------------------------------------------------------------------------- /concept/scaling-apps-kafka.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/scaling-apps-with-kafka/HEAD/concept/scaling-apps-kafka.png -------------------------------------------------------------------------------- /docs/images/api-products.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/scaling-apps-with-kafka/HEAD/docs/images/api-products.png -------------------------------------------------------------------------------- /docs/images/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/scaling-apps-with-kafka/HEAD/docs/images/architecture.png -------------------------------------------------------------------------------- /docs/images/createcluster.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/scaling-apps-with-kafka/HEAD/docs/images/createcluster.png -------------------------------------------------------------------------------- /docs/images/kedacontroller.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/scaling-apps-with-kafka/HEAD/docs/images/kedacontroller.png -------------------------------------------------------------------------------- /docs/images/api-product-plan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/scaling-apps-with-kafka/HEAD/docs/images/api-product-plan.png -------------------------------------------------------------------------------- /docs/images/architecture-eem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/scaling-apps-with-kafka/HEAD/docs/images/architecture-eem.png -------------------------------------------------------------------------------- /docs/images/create-async-api.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/scaling-apps-with-kafka/HEAD/docs/images/create-async-api.png -------------------------------------------------------------------------------- /docs/images/output-animation.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/scaling-apps-with-kafka/HEAD/docs/images/output-animation.gif -------------------------------------------------------------------------------- /docs/images/sample-output-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/scaling-apps-with-kafka/HEAD/docs/images/sample-output-1.png -------------------------------------------------------------------------------- /docs/images/sample-output-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/scaling-apps-with-kafka/HEAD/docs/images/sample-output-2.png -------------------------------------------------------------------------------- /docs/images/api-product-gateway.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/scaling-apps-with-kafka/HEAD/docs/images/api-product-gateway.png -------------------------------------------------------------------------------- /docs/images/api-product-select.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/scaling-apps-with-kafka/HEAD/docs/images/api-product-select.png -------------------------------------------------------------------------------- /docs/images/api-product-client-id.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/scaling-apps-with-kafka/HEAD/docs/images/api-product-client-id.png -------------------------------------------------------------------------------- /docs/images/api-product-subscribe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/scaling-apps-with-kafka/HEAD/docs/images/api-product-subscribe.png -------------------------------------------------------------------------------- /docs/images/output-random-initial.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/scaling-apps-with-kafka/HEAD/docs/images/output-random-initial.png -------------------------------------------------------------------------------- /docs/images/architecture-with-ksqldb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/scaling-apps-with-kafka/HEAD/docs/images/architecture-with-ksqldb.png -------------------------------------------------------------------------------- /docs/images/async-api-configuration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/scaling-apps-with-kafka/HEAD/docs/images/async-api-configuration.png -------------------------------------------------------------------------------- /docs/images/eem-subscriber-webhook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/scaling-apps-with-kafka/HEAD/docs/images/eem-subscriber-webhook.png -------------------------------------------------------------------------------- /docs/images/api-product-api-key-secret.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/scaling-apps-with-kafka/HEAD/docs/images/api-product-api-key-secret.png -------------------------------------------------------------------------------- /docs/images/api-product-subscribe-selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/scaling-apps-with-kafka/HEAD/docs/images/api-product-subscribe-selected.png -------------------------------------------------------------------------------- /docs/images/async-api-configuration-publish.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/scaling-apps-with-kafka/HEAD/docs/images/async-api-configuration-publish.png -------------------------------------------------------------------------------- /docs/images/async-api-configuration-summary.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/scaling-apps-with-kafka/HEAD/docs/images/async-api-configuration-summary.png -------------------------------------------------------------------------------- /microservices/eem/eemSubscriber/__tests__/utils/stopContainers.sh: -------------------------------------------------------------------------------- 1 | echo "Stopping REDIS container named redis-code-pattern-test." 2 | docker stop redis-code-pattern-test -------------------------------------------------------------------------------- /microservices/frontend/components/architecture.html: -------------------------------------------------------------------------------- 1 | 2 | Your browser does not support the HTML canvas tag. 3 | -------------------------------------------------------------------------------- /deployments/kafka-secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: kafka-credentials 5 | stringData: 6 | BOOTSTRAP_SERVERS: 'kafka:9092' 7 | SECURITY_PROTOCOL: 'SASL_SSL' 8 | SASL_MECHANISMS: 'PLAIN' 9 | SASL_USERNAME: 'user' 10 | SASL_PASSWORD: 'pas5word' 11 | TRIGGER_SASL: 'plaintext' 12 | TRIGGER_TLS: 'enable' -------------------------------------------------------------------------------- /microservices/eem/asyncapi-secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: asyncapi-endpoint-credentials 5 | stringData: 6 | BOOTSTRAP_SERVERS: '' 7 | SASL_USERNAME: '' 8 | SASL_PASSWORD: '' 9 | CLIENT_ID: '' 10 | SECURITY_PROTOCOL: 'SASL_SSL' 11 | SASL_MECHANISMS: 'PLAIN' 12 | -------------------------------------------------------------------------------- /microservices/eem/eemSubscriber/dashboard/restaurantDropdown.js: -------------------------------------------------------------------------------- 1 | let dropdownDOM = document.getElementById('restaurantDropdown'); 2 | 3 | async function startGettingRestaurants() { 4 | let backend_url = await getBackendURL(); 5 | let restaurants = await getRestaurants(250, backend_url); 6 | 7 | showRestaurants(dropdownDOM, restaurants); 8 | } 9 | 10 | startGettingRestaurants(); -------------------------------------------------------------------------------- /microservices/consumers/courierConsumer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "courier", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "mongoose": "^5.11.15", 13 | "node-rdkafka": "^2.10.1", 14 | "uuid-mongodb": "^2.4.1" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /microservices/ksqlcontroller/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "apiservice", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "cors": "^2.8.5", 13 | "express": "^4.17.1", 14 | "needle": "^2.6.0", 15 | "ws": "^7.5.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /microservices/consumers/orderConsumer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "orderconsumer", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "mongoose": "^5.11.15", 13 | "node-rdkafka": "^2.10.1", 14 | "uuid-mongodb": "^2.4.1" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /microservices/consumers/kitchenConsumer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kitchenconsumer", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "mongoose": "^5.11.15", 13 | "node-rdkafka": "^2.10.1", 14 | "uuid-mongodb": "^2.4.1" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /microservices/frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "phoneproject", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "start": "node app.js" 7 | }, 8 | "dependencies": { 9 | "dotenv": "^2.0.0", 10 | "express": "^4.16.4", 11 | "needle": "^2.6.0", 12 | "pino": "^6.11.0", 13 | "pino-http": "^5.4.0" 14 | }, 15 | "repository": {}, 16 | "engines": { 17 | "node": "12.x" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /microservices/podData/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "poddata", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@kubernetes/client-node": "^0.14.0", 13 | "cors": "^2.8.5", 14 | "express": "^4.17.1", 15 | "uuid": "^8.3.2" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /microservices/statusService/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "orderconsumer", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "cors": "^2.8.5", 13 | "express": "^4.17.1", 14 | "ioredis": "^4.22.0", 15 | "node-rdkafka": "^2.10.1" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /microservices/realtimedata/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "realtimedatawebsocket", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "http": "0.0.1-security", 13 | "kafkajs": "^1.15.0", 14 | "node-rdkafka": "^2.10.1", 15 | "ws": "^7.4.3" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /microservices/apiservice/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12-alpine 2 | 3 | RUN apk --no-cache add \ 4 | bash \ 5 | g++ \ 6 | ca-certificates \ 7 | lz4-dev \ 8 | musl-dev \ 9 | cyrus-sasl-dev \ 10 | openssl-dev \ 11 | make \ 12 | python 13 | 14 | RUN apk add --no-cache --virtual .build-deps gcc zlib-dev libc-dev bsd-compat-headers py-setuptools bash 15 | 16 | WORKDIR /usr/src/app 17 | 18 | COPY . ./ 19 | 20 | RUN npm install 21 | CMD [ "node", "app.js" ] -------------------------------------------------------------------------------- /microservices/podData/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:14-alpine 2 | 3 | RUN apk --no-cache add \ 4 | bash \ 5 | g++ \ 6 | ca-certificates \ 7 | lz4-dev \ 8 | musl-dev \ 9 | cyrus-sasl-dev \ 10 | openssl-dev \ 11 | make \ 12 | python 13 | 14 | RUN apk add --no-cache --virtual .build-deps gcc zlib-dev libc-dev bsd-compat-headers py-setuptools bash 15 | 16 | WORKDIR /usr/src/app 17 | 18 | COPY . ./ 19 | 20 | RUN npm install 21 | CMD [ "node", "app.js" ] -------------------------------------------------------------------------------- /microservices/ksqlcontroller/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12-alpine 2 | 3 | RUN apk --no-cache add \ 4 | bash \ 5 | g++ \ 6 | ca-certificates \ 7 | lz4-dev \ 8 | musl-dev \ 9 | cyrus-sasl-dev \ 10 | openssl-dev \ 11 | make \ 12 | python 13 | 14 | RUN apk add --no-cache --virtual .build-deps gcc zlib-dev libc-dev bsd-compat-headers py-setuptools bash 15 | 16 | WORKDIR /usr/src/app 17 | 18 | COPY . ./ 19 | 20 | RUN npm install 21 | CMD [ "node", "app.js" ] -------------------------------------------------------------------------------- /microservices/realtimedata/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12-alpine 2 | 3 | RUN apk --no-cache add \ 4 | bash \ 5 | g++ \ 6 | ca-certificates \ 7 | lz4-dev \ 8 | musl-dev \ 9 | cyrus-sasl-dev \ 10 | openssl-dev \ 11 | make \ 12 | python 13 | 14 | RUN apk add --no-cache --virtual .build-deps gcc zlib-dev libc-dev bsd-compat-headers py-setuptools bash 15 | 16 | WORKDIR /usr/src/app 17 | 18 | COPY . ./ 19 | 20 | RUN npm install 21 | CMD [ "node", "app.js" ] -------------------------------------------------------------------------------- /microservices/statusService/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12-alpine 2 | 3 | RUN apk --no-cache add \ 4 | bash \ 5 | g++ \ 6 | ca-certificates \ 7 | lz4-dev \ 8 | musl-dev \ 9 | cyrus-sasl-dev \ 10 | openssl-dev \ 11 | make \ 12 | python 13 | 14 | RUN apk add --no-cache --virtual .build-deps gcc zlib-dev libc-dev bsd-compat-headers py-setuptools bash 15 | 16 | WORKDIR /usr/src/app 17 | 18 | COPY . ./ 19 | 20 | RUN npm install 21 | CMD [ "node", "app.js" ] -------------------------------------------------------------------------------- /microservices/consumers/courierConsumer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12-alpine 2 | 3 | RUN apk --no-cache add \ 4 | bash \ 5 | g++ \ 6 | ca-certificates \ 7 | lz4-dev \ 8 | musl-dev \ 9 | cyrus-sasl-dev \ 10 | openssl-dev \ 11 | make \ 12 | python 13 | 14 | RUN apk add --no-cache --virtual .build-deps gcc zlib-dev libc-dev bsd-compat-headers py-setuptools bash 15 | 16 | WORKDIR /usr/src/app 17 | 18 | COPY . ./ 19 | 20 | RUN npm install 21 | CMD [ "node", "app.js" ] -------------------------------------------------------------------------------- /microservices/consumers/kitchenConsumer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12-alpine 2 | 3 | RUN apk --no-cache add \ 4 | bash \ 5 | g++ \ 6 | ca-certificates \ 7 | lz4-dev \ 8 | musl-dev \ 9 | cyrus-sasl-dev \ 10 | openssl-dev \ 11 | make \ 12 | python 13 | 14 | RUN apk add --no-cache --virtual .build-deps gcc zlib-dev libc-dev bsd-compat-headers py-setuptools bash 15 | 16 | WORKDIR /usr/src/app 17 | 18 | COPY . ./ 19 | 20 | RUN npm install 21 | CMD [ "node", "app.js" ] -------------------------------------------------------------------------------- /microservices/consumers/orderConsumer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12-alpine 2 | 3 | RUN apk --no-cache add \ 4 | bash \ 5 | g++ \ 6 | ca-certificates \ 7 | lz4-dev \ 8 | musl-dev \ 9 | cyrus-sasl-dev \ 10 | openssl-dev \ 11 | make \ 12 | python 13 | 14 | RUN apk add --no-cache --virtual .build-deps gcc zlib-dev libc-dev bsd-compat-headers py-setuptools bash 15 | 16 | WORKDIR /usr/src/app 17 | 18 | COPY . ./ 19 | 20 | RUN npm install 21 | CMD [ "node", "app.js" ] -------------------------------------------------------------------------------- /microservices/eem/eemSubscriber/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12-alpine3.9 2 | 3 | RUN apk --no-cache add \ 4 | bash \ 5 | g++ \ 6 | ca-certificates \ 7 | lz4-dev \ 8 | musl-dev \ 9 | cyrus-sasl-dev \ 10 | openssl-dev \ 11 | make \ 12 | python 13 | 14 | RUN apk add --no-cache --virtual .build-deps gcc zlib-dev libc-dev bsd-compat-headers py-setuptools bash 15 | 16 | WORKDIR /usr/src/app 17 | 18 | COPY . ./ 19 | 20 | RUN npm install 21 | CMD [ "node", "server.js" ] 22 | -------------------------------------------------------------------------------- /deployments/keda-auth.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: keda.sh/v1alpha1 3 | kind: TriggerAuthentication 4 | metadata: 5 | name: keda-trigger-auth-kafka-credential 6 | spec: 7 | secretTargetRef: 8 | - parameter: sasl 9 | name: kafka-credentials 10 | key: TRIGGER_SASL 11 | - parameter: username 12 | name: kafka-credentials 13 | key: SASL_USERNAME 14 | - parameter: password 15 | name: kafka-credentials 16 | key: SASL_PASSWORD 17 | - parameter: tls 18 | name: kafka-credentials 19 | key: TRIGGER_TLS -------------------------------------------------------------------------------- /microservices/apiservice/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "apiservice", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "cors": "^2.8.5", 13 | "express": "^4.17.1", 14 | "http-proxy-middleware": "^1.0.6", 15 | "needle": "^2.6.0", 16 | "node-rdkafka": "^2.10.0", 17 | "openssl": "^1.1.0", 18 | "uuid": "^8.3.2" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /microservices/frontend/images/taste-deselected.svg: -------------------------------------------------------------------------------- 1 | taste-deselected -------------------------------------------------------------------------------- /microservices/frontend/images/taste-selected.svg: -------------------------------------------------------------------------------- 1 | taste-selected -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "pwa-node", 9 | "request": "launch", 10 | "name": "Launch Program", 11 | "skipFiles": [ 12 | "/**" 13 | ], 14 | "program": "${workspaceFolder}/app.js" 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /microservices/eem/exampleNotificationReceiver/example-pos/websocketReceiverController.js: -------------------------------------------------------------------------------- 1 | const notificationContainer = document.getElementById('notificationArea'); 2 | 3 | let loc = window.location; 4 | let wsurl = "ws://" + loc.host + loc.pathname 5 | 6 | let client = new WebSocket(wsurl); 7 | 8 | client.onmessage = (event) => { 9 | console.log('received'); 10 | console.log(event.data); 11 | let messageObject = processMessage(JSON.parse(event.data)); 12 | console.log(messageObject); 13 | if (messageObject.status) { 14 | showMessageDOM(notificationContainer, messageObject.message); 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /deployments/orderconsumer.yaml: -------------------------------------------------------------------------------- 1 | # single replica - no persistence 2 | --- 3 | apiVersion: apps/v1 4 | kind: Deployment 5 | metadata: 6 | name: orders 7 | spec: 8 | selector: 9 | matchLabels: 10 | name: orders 11 | replicas: 1 12 | template: 13 | metadata: 14 | labels: 15 | name: orders 16 | spec: 17 | containers: 18 | - image: anthonyamanse/orderconsumer:1.0 19 | name: orders 20 | env: 21 | - name: MONGODB_REPLICA_HOSTNAMES 22 | value: 'mongo:27017' 23 | envFrom: 24 | - secretRef: 25 | name: kafka-credentials -------------------------------------------------------------------------------- /microservices/eem/exampleNotificationReceiver/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "examplenotificationreceiver", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "server.js", 6 | "scripts": { 7 | "test": "TEST_JEST=true jest --detectOpenHandles --verbose", 8 | "start": "node server.js" 9 | }, 10 | "author": "", 11 | "license": "ISC", 12 | "devDependencies": { 13 | "ioredis-mock": "^5.6.0", 14 | "jest": "^27.3.0", 15 | "supertest": "^6.1.6" 16 | }, 17 | "dependencies": { 18 | "cors": "^2.8.5", 19 | "express": "^4.17.1", 20 | "ioredis": "^4.28.0", 21 | "ws": "^8.2.3" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /deployments/courierconsumer.yaml: -------------------------------------------------------------------------------- 1 | # single replica - no persistence 2 | --- 3 | apiVersion: apps/v1 4 | kind: Deployment 5 | metadata: 6 | name: courier 7 | spec: 8 | selector: 9 | matchLabels: 10 | name: courier 11 | replicas: 1 12 | template: 13 | metadata: 14 | labels: 15 | name: courier 16 | spec: 17 | containers: 18 | - image: anthonyamanse/courierconsumer:1.0 19 | name: courier 20 | env: 21 | - name: MONGODB_REPLICA_HOSTNAMES 22 | value: 'mongo:27017' 23 | envFrom: 24 | - secretRef: 25 | name: kafka-credentials -------------------------------------------------------------------------------- /deployments/kitchenconsumer.yaml: -------------------------------------------------------------------------------- 1 | # single replica - no persistence 2 | --- 3 | apiVersion: apps/v1 4 | kind: Deployment 5 | metadata: 6 | name: kitchen 7 | spec: 8 | selector: 9 | matchLabels: 10 | name: kitchen 11 | replicas: 1 12 | template: 13 | metadata: 14 | labels: 15 | name: kitchen 16 | spec: 17 | containers: 18 | - image: anthonyamanse/kitchenconsumer:1.0 19 | name: kitchen 20 | env: 21 | - name: MONGODB_REPLICA_HOSTNAMES 22 | value: 'mongo:27017' 23 | envFrom: 24 | - secretRef: 25 | name: kafka-credentials -------------------------------------------------------------------------------- /microservices/frontend/images/fish.svg: -------------------------------------------------------------------------------- 1 | fish -------------------------------------------------------------------------------- /microservices/frontend/images/sprout.svg: -------------------------------------------------------------------------------- 1 | sprout -------------------------------------------------------------------------------- /microservices/eem/eemSubscriber/sendNotificationHelper.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios').default; 2 | 3 | function payloadToRequestBody(payload, eventType) { 4 | let message; 5 | if (eventType == "orderCreated") { 6 | message = "A new order of " + payload.dish + " has been placed." 7 | } else if (eventType == "delivered") { 8 | message = "Delivered! " + payload.dish + " has been delivered." 9 | } 10 | return { 11 | payload: payload, 12 | kitchenId: payload.kitchenId, 13 | message 14 | } 15 | } 16 | 17 | function notifyOnCallbackURL(url, body) { 18 | return axios.post(url, body); 19 | } 20 | module.exports = { 21 | notifyOnCallbackURL, 22 | payloadToRequestBody 23 | } -------------------------------------------------------------------------------- /deployments/podconsumerdata.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: podconsumerdata 6 | spec: 7 | selector: 8 | matchLabels: 9 | name: podconsumerdata 10 | replicas: 1 11 | template: 12 | metadata: 13 | labels: 14 | name: podconsumerdata 15 | spec: 16 | serviceAccountName: deployer 17 | containers: 18 | - image: anthonyamanse/poddata:0.1 19 | ports: 20 | - containerPort: 8080 21 | name: podconsumerdata 22 | imagePullPolicy: Always 23 | --- 24 | apiVersion: v1 25 | kind: Service 26 | metadata: 27 | name: podconsumerdata 28 | spec: 29 | ports: 30 | - port: 8080 31 | targetPort: 8080 32 | selector: 33 | name: podconsumerdata -------------------------------------------------------------------------------- /microservices/eem/eemSubscriber/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eemsubscriber", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "server.js", 6 | "scripts": { 7 | "test": "jest --detectOpenHandles --verbose" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "axios": "^0.23.0", 13 | "cors": "^2.8.5", 14 | "express": "^4.17.1", 15 | "ioredis": "^4.27.11", 16 | "needle": "^3.0.0", 17 | "node-rdkafka": "^2.10.1", 18 | "uuid": "^8.3.2" 19 | }, 20 | "devDependencies": { 21 | "dockerode": "^3.3.1", 22 | "ioredis-mock": "^5.6.0", 23 | "jest": "^27.2.5", 24 | "supertest": "^6.1.6" 25 | }, 26 | "jest": { 27 | "testPathIgnorePatterns": [ 28 | "__tests__/utils" 29 | ] 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /microservices/frontend/images/noodle-bowl.svg: -------------------------------------------------------------------------------- 1 | noodle-bowl -------------------------------------------------------------------------------- /microservices/eem/exampleNotificationReceiver/Dockerfile: -------------------------------------------------------------------------------- 1 | # Use the official lightweight Node.js 12 image. 2 | # https://hub.docker.com/_/node 3 | FROM node:12-slim 4 | 5 | # Create and change to the app directory. 6 | WORKDIR /usr/src/app 7 | 8 | # Copy application dependency manifests to the container image. 9 | # A wildcard is used to ensure both package.json AND package-lock.json are copied. 10 | # Copying this separately prevents re-running npm install on every code change. 11 | COPY package*.json ./ 12 | 13 | # Install production dependencies. 14 | RUN npm install --only=production 15 | 16 | # Copy local code to the container image. 17 | COPY example-pos ./example-pos 18 | COPY app.js ./ 19 | COPY server.js ./ 20 | 21 | # Run the web service on container startup. 22 | CMD [ "node", "server.js" ] -------------------------------------------------------------------------------- /microservices/frontend/images/cafe.svg: -------------------------------------------------------------------------------- 1 | coffee -------------------------------------------------------------------------------- /microservices/frontend/components/navigationbutton.html: -------------------------------------------------------------------------------- 1 | 2 | 24 | 27 | -------------------------------------------------------------------------------- /deployments/realtimedata.yaml: -------------------------------------------------------------------------------- 1 | # single replica - no persistence 2 | --- 3 | apiVersion: apps/v1 4 | kind: Deployment 5 | metadata: 6 | name: realtimedata 7 | spec: 8 | selector: 9 | matchLabels: 10 | name: realtimedata 11 | replicas: 1 12 | template: 13 | metadata: 14 | labels: 15 | name: realtimedata 16 | spec: 17 | containers: 18 | - image: anthonyamanse/realtimedata:1.0 19 | name: realtimedata 20 | ports: 21 | - containerPort: 8080 22 | envFrom: 23 | - secretRef: 24 | name: kafka-credentials 25 | --- 26 | apiVersion: v1 27 | kind: Service 28 | metadata: 29 | name: realtimedata 30 | spec: 31 | ports: 32 | - port: 8080 33 | targetPort: 8080 34 | selector: 35 | name: realtimedata -------------------------------------------------------------------------------- /deployments/redis-dev.yaml: -------------------------------------------------------------------------------- 1 | # no persistence 2 | --- 3 | apiVersion: apps/v1 4 | kind: Deployment 5 | metadata: 6 | name: redis 7 | spec: 8 | selector: 9 | matchLabels: 10 | name: redis 11 | replicas: 1 12 | template: 13 | metadata: 14 | labels: 15 | name: redis 16 | version: v1 17 | spec: 18 | containers: 19 | - image: redis 20 | name: redis 21 | ports: 22 | - containerPort: 6379 23 | volumeMounts: 24 | - mountPath: /data 25 | name: data 26 | volumes: 27 | - name: data 28 | emptyDir: {} 29 | --- 30 | apiVersion: v1 31 | kind: Service 32 | metadata: 33 | name: redis 34 | spec: 35 | ports: 36 | - port: 6379 37 | targetPort: 6379 38 | selector: 39 | name: redis -------------------------------------------------------------------------------- /microservices/frontend/Dockerfile: -------------------------------------------------------------------------------- 1 | # Use the official lightweight Node.js 12 image. 2 | # https://hub.docker.com/_/node 3 | FROM node:12-slim 4 | 5 | # Create and change to the app directory. 6 | WORKDIR /usr/src/app 7 | 8 | # Copy application dependency manifests to the container image. 9 | # A wildcard is used to ensure both package.json AND package-lock.json are copied. 10 | # Copying this separately prevents re-running npm install on every code change. 11 | COPY package*.json ./ 12 | 13 | # Install production dependencies. 14 | RUN npm install --only=production 15 | 16 | # Copy local code to the container image. 17 | COPY components ./components 18 | COPY style ./style 19 | COPY app.js ./ 20 | COPY index.html ./ 21 | COPY images ./images 22 | 23 | # Run the web service on container startup. 24 | CMD [ "node", "app.js" ] -------------------------------------------------------------------------------- /deployments/mongo-dev.yaml: -------------------------------------------------------------------------------- 1 | # single replica - no persistence 2 | --- 3 | apiVersion: apps/v1 4 | kind: Deployment 5 | metadata: 6 | name: mongo 7 | spec: 8 | selector: 9 | matchLabels: 10 | name: mongo 11 | replicas: 1 12 | template: 13 | metadata: 14 | labels: 15 | name: mongo 16 | version: v1 17 | spec: 18 | containers: 19 | - image: mongo 20 | name: mongo 21 | ports: 22 | - containerPort: 27017 23 | volumeMounts: 24 | - mountPath: /data/db 25 | name: data 26 | volumes: 27 | - name: data 28 | emptyDir: {} 29 | --- 30 | apiVersion: v1 31 | kind: Service 32 | metadata: 33 | name: mongo 34 | spec: 35 | ports: 36 | - port: 27017 37 | targetPort: 27017 38 | selector: 39 | name: mongo -------------------------------------------------------------------------------- /microservices/frontend/images/receipt-deselected.svg: -------------------------------------------------------------------------------- 1 | receipt-deselected -------------------------------------------------------------------------------- /microservices/frontend/images/receipt-selected.svg: -------------------------------------------------------------------------------- 1 | receipt-selected -------------------------------------------------------------------------------- /deployments/frontend.yaml: -------------------------------------------------------------------------------- 1 | # no persistence 2 | --- 3 | apiVersion: apps/v1 4 | kind: Deployment 5 | metadata: 6 | name: example-food 7 | spec: 8 | selector: 9 | matchLabels: 10 | name: example-food 11 | replicas: 1 12 | template: 13 | metadata: 14 | labels: 15 | name: example-food 16 | version: v1 17 | spec: 18 | containers: 19 | - image: anthonyamanse/example-food-frontend:1.0 20 | name: example-food 21 | imagePullPolicy: Always 22 | ports: 23 | - containerPort: 8090 24 | volumeMounts: 25 | - mountPath: /data 26 | name: data 27 | volumes: 28 | - name: data 29 | emptyDir: {} 30 | --- 31 | apiVersion: v1 32 | kind: Service 33 | metadata: 34 | name: example-food 35 | spec: 36 | ports: 37 | - port: 8090 38 | targetPort: 8090 39 | selector: 40 | name: example-food -------------------------------------------------------------------------------- /deployments/apiservice.yaml: -------------------------------------------------------------------------------- 1 | # single replica - no persistence 2 | --- 3 | apiVersion: apps/v1 4 | kind: Deployment 5 | metadata: 6 | name: apiservice 7 | spec: 8 | selector: 9 | matchLabels: 10 | name: apiservice 11 | replicas: 1 12 | template: 13 | metadata: 14 | labels: 15 | name: apiservice 16 | spec: 17 | containers: 18 | - image: anthonyamanse/apiservice:1.0 19 | name: apiservice 20 | imagePullPolicy: Always 21 | ports: 22 | - containerPort: 8080 23 | env: 24 | - name: STATUS_SERVICE 25 | value: 'http://status:8080' 26 | envFrom: 27 | - secretRef: 28 | name: kafka-credentials 29 | --- 30 | apiVersion: v1 31 | kind: Service 32 | metadata: 33 | name: apiservice 34 | spec: 35 | ports: 36 | - port: 8080 37 | targetPort: 8080 38 | selector: 39 | name: apiservice -------------------------------------------------------------------------------- /microservices/frontend/app.js: -------------------------------------------------------------------------------- 1 | /*eslint-env node*/ 2 | 3 | //------------------------------------------------------------------------------ 4 | // node.js starter application for Bluemix 5 | //------------------------------------------------------------------------------ 6 | 7 | // This application uses express as its web server 8 | // for more info, see: http://expressjs.com 9 | var express = require('express'); 10 | var app = express(); 11 | var port = process.env.PORT || 8090; 12 | 13 | // serve the files out of ./public as our main files 14 | 15 | app.use(express.static('.')); 16 | const pino = require('pino-http')() 17 | 18 | app.use(pino) 19 | 20 | const logger = require('pino')() 21 | 22 | // const child = logger.child({ a: 'property' }) 23 | // child.info('hello child!') 24 | 25 | 26 | // start server on the specified port and binding host 27 | app.listen(port); 28 | logger.info('scaling apps with kafka, starting up on port: ' + port); 29 | -------------------------------------------------------------------------------- /microservices/eem/exampleNotificationReceiver/example-pos/clientHelper.js: -------------------------------------------------------------------------------- 1 | function processMessage(message_from_ws) { 2 | if (message_from_ws.status == 200 && message_from_ws.data) { 3 | if (message_from_ws.data.message_sent && message_from_ws.data.status == 'message_sent') 4 | return { 5 | status: true, 6 | message: message_from_ws.data.message_sent 7 | } 8 | } 9 | 10 | return { 11 | status: false, 12 | reason: "Unable to process message received from server.", 13 | message: message_from_ws 14 | } 15 | } 16 | 17 | function showMessageDOM(container, notification_text) { 18 | let divNotification = document.createElement('div'); 19 | divNotification.className = "notificationText"; 20 | divNotification.innerHTML = notification_text; 21 | container.prepend(divNotification); 22 | } 23 | 24 | module.exports = { 25 | processMessage, 26 | showMessageDOM 27 | } 28 | -------------------------------------------------------------------------------- /deployments/statusservice.yaml: -------------------------------------------------------------------------------- 1 | # single replica - no persistence 2 | --- 3 | apiVersion: apps/v1 4 | kind: Deployment 5 | metadata: 6 | name: status 7 | spec: 8 | selector: 9 | matchLabels: 10 | name: status 11 | replicas: 1 12 | template: 13 | metadata: 14 | labels: 15 | name: status 16 | spec: 17 | containers: 18 | - image: anthonyamanse/statusservice:1.0 19 | name: status 20 | imagePullPolicy: Always 21 | ports: 22 | - containerPort: 8080 23 | env: 24 | - name: REDIS_URL 25 | value: 'redis' 26 | - name: REDIS_PORT 27 | value: '6379' 28 | envFrom: 29 | - secretRef: 30 | name: kafka-credentials 31 | --- 32 | apiVersion: v1 33 | kind: Service 34 | metadata: 35 | name: status 36 | spec: 37 | ports: 38 | - port: 8080 39 | targetPort: 8080 40 | selector: 41 | name: status -------------------------------------------------------------------------------- /deployments/routes.md: -------------------------------------------------------------------------------- 1 | oc expose svc/apiservice --hostname example-food-food-delivery.anthonyamanse-4-5-f2c6cdc6801be85fd188b09d006f13e3-0000.us-east.containers.appdomain.cloud --path /createOrder --name path-createorder 2 | 3 | oc expose svc/apiservice --hostname example-food-food-delivery.anthonyamanse-4-5-f2c6cdc6801be85fd188b09d006f13e3-0000.us-east.containers.appdomain.cloud --path /status --name path-status 4 | 5 | oc expose svc/apiservice --hostname example-food-food-delivery.anthonyamanse-4-5-f2c6cdc6801be85fd188b09d006f13e3-0000.us-east.containers.appdomain.cloud --path /restaurants --name path-restaurants 6 | 7 | oc expose svc/apiservice --hostname example-food-food-delivery.anthonyamanse-4-5-f2c6cdc6801be85fd188b09d006f13e3-0000.us-east.containers.appdomain.cloud --path /user --name path-user 8 | 9 | oc expose svc/realtimedata --hostname example-food-food-delivery.anthonyamanse-4-5-f2c6cdc6801be85fd188b09d006f13e3-0000.us-east.containers.appdomain.cloud --path /events --name path-realtimedata -------------------------------------------------------------------------------- /microservices/frontend/components/clientHelpers/kitchenAPI.js: -------------------------------------------------------------------------------- 1 | function createKitchenList() { 2 | 3 | } 4 | 5 | async function getKitchenList() { 6 | if (STATIC_DATA) { 7 | return await fetch('./components/restaurants.json') 8 | } else { 9 | let invocationRequest = await fetch(API_URL.concat("/restaurants")) 10 | let invocationResponse = await invocationRequest.json() 11 | let invocationId = invocationResponse.requestId 12 | let count = 10 // 10 tries 13 | while (count > 0) { 14 | let inlineDelay = await new Promise(resolve => setTimeout(resolve, 250)) // 250ms 15 | statusRequest = await fetch(API_URL.concat("/status/").concat(invocationId)) 16 | statusJson = await statusRequest.json() 17 | if (statusJson.status != "processing") { 18 | console.log(statusJson) 19 | return statusJson.docs 20 | } 21 | count-- 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /deployments/ksqlcontroller.yaml: -------------------------------------------------------------------------------- 1 | # no persistence 2 | --- 3 | apiVersion: apps/v1 4 | kind: Deployment 5 | metadata: 6 | name: ksql-controller 7 | spec: 8 | selector: 9 | matchLabels: 10 | name: ksql-controller 11 | replicas: 1 12 | template: 13 | metadata: 14 | labels: 15 | name: ksql-controller 16 | version: v1 17 | spec: 18 | containers: 19 | - image: YOUR_DOCKER_USER/ksqlcontroller:1.0 20 | name: ksql-controller 21 | imagePullPolicy: Always 22 | ports: 23 | - containerPort: 8080 24 | env: 25 | - name: KSQL_ENDPOINT 26 | value: 'https://pksqlc-xyz.confluent.cloud:443' 27 | - name: KSQL_API_KEY 28 | value: 'APIXYZ' 29 | - name: KSQL_API_SECRET 30 | value: 'APISECRETXYZ' 31 | --- 32 | apiVersion: v1 33 | kind: Service 34 | metadata: 35 | name: ksql-controller 36 | spec: 37 | ports: 38 | - port: 8080 39 | targetPort: 8080 40 | selector: 41 | name: ksql-controller -------------------------------------------------------------------------------- /microservices/eem/exampleNotificationReceiver/server.js: -------------------------------------------------------------------------------- 1 | const {app, redis} = require("./app"); 2 | const WebSocket = require('ws'); 3 | const http = require('http'); 4 | 5 | const PORT = process.env.PORT || 8080 6 | let server; 7 | 8 | const CHANNEL = process.env.REDIS_CHANNEL || "notification-pubsub" 9 | 10 | let startServer = function () { 11 | server = http.createServer(app); 12 | let wss = new WebSocket.Server({ server }); 13 | redis.on('message', (channel, message) => { 14 | if (channel == CHANNEL) { 15 | wss.clients.forEach(client => { 16 | client.send(message); 17 | }) 18 | } 19 | }); 20 | redis.subscribe(CHANNEL); 21 | return new Promise(resolve => { 22 | server.listen(PORT, () => { 23 | resolve(server); 24 | console.log(`listening on port ${PORT}`); 25 | }); 26 | }); 27 | } 28 | 29 | let TEST_JEST = process.env.TEST_JEST || false; 30 | 31 | if (!TEST_JEST) startServer(); 32 | 33 | module.exports = { 34 | app, 35 | startServer 36 | } -------------------------------------------------------------------------------- /microservices/consumers/kitchenConsumer/models/kitchen.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const Schema = mongoose.Schema 3 | const MUUID = require('uuid-mongodb').mode('relaxed'); 4 | 5 | let uuidValidator = (id) => { 6 | if (id != null) { 7 | return MUUID.from(id.toObject()) 8 | } 9 | } 10 | let Menu = new Schema({ 11 | _id: false, 12 | item: {type: String}, 13 | price: {type: mongoose.Types.Decimal128} 14 | }) 15 | 16 | let Kitchen = new Schema({ 17 | kitchenId: {type: Buffer, unique: true, subtype: 4, default: () => MUUID.v4(), validate: {validator: uuidValidator}}, 18 | name: {type: String}, 19 | image: {type: String}, 20 | type: {type: String}, 21 | menu: [Menu] 22 | }) 23 | 24 | Kitchen.path('kitchenId').get((kitchenId) => { 25 | return MUUID.from(kitchenId.toObject()); 26 | }) 27 | Menu.path('price').get((price) => { 28 | return Number(price.toString()); 29 | }) 30 | Menu.set('toJSON', { getters: true, virtuals: false}); 31 | Kitchen.set('toJSON', { getters: true, virtuals: false}); 32 | 33 | module.exports = mongoose.model("Kitchen", Kitchen) -------------------------------------------------------------------------------- /microservices/eem/eemSubscriber/__tests__/utils/startContainers.sh: -------------------------------------------------------------------------------- 1 | check_docker_execution() { 2 | if [ $? -ne 0 ]; 3 | then 4 | echo "\033[0;31mFailed to start $1 container\033[0m" 5 | "$(dirname "$0")"/stopContainers.sh 6 | exit 1 7 | fi 8 | } 9 | docker run --rm -d --name redis-code-pattern-test -p 6379:6379 redis:6.2 10 | check_docker_execution REDIS 11 | # docker run --rm -d -e MYSQL_ROOT_PASSWORD=password -p 3306:3306 --name mysql-code-pattern-test mysql:5.6 12 | # check_docker_execution MYSQL 13 | 14 | # wait for redis to start 15 | echo -n "Waiting for redis to start" 16 | while [ "`docker logs redis-code-pattern-test | tail -n 1 | grep "Ready to accept connections" | wc -l`" != "1" ]; 17 | do 18 | echo -n "." 19 | sleep 2; 20 | done 21 | echo "" 22 | echo "redis container is ready" 23 | 24 | # # wait for mysql to start 25 | # echo -n "Waiting for mysql to start" 26 | # while [ "`docker logs mysql-code-pattern-test 2>&1 | grep "socket: '/var/run/mysqld/mysqld.sock' port: 3306 MySQL Community Server (GPL)" | wc -l`" != "1" ]; 27 | # do 28 | # echo -n "." 29 | # sleep 2; 30 | # done 31 | # echo "" 32 | # echo "mysql container is ready" 33 | 34 | -------------------------------------------------------------------------------- /microservices/frontend/components/order.html: -------------------------------------------------------------------------------- 1 | 43 | 44 | -------------------------------------------------------------------------------- /microservices/frontend/components/menuitem.html: -------------------------------------------------------------------------------- 1 | 43 | 44 | -------------------------------------------------------------------------------- /microservices/eem/building-container-images.md: -------------------------------------------------------------------------------- 1 | # Build your own container images 2 | 3 | You will need a Docker Hub account and the Docker client installed in your environment. 4 | 5 | 1. Build the container images 6 | 7 | ``` 8 | export DOCKER_HUB_USERNAME=PLACE_YOUR_DOCKERHUB_USERNAME_HERE 9 | 10 | docker build -t $DOCKER_HUB_USERNAME/eem-subscriber:1.0 eemSubscriber 11 | docker build -t $DOCKER_HUB_USERNAME/example-restaurant-pos:1.0 exampleNotificationReceiver 12 | ``` 13 | 14 | 2. Push the container images to Docker Hub 15 | 16 | ``` 17 | docker push $DOCKER_HUB_USERNAME/eem-subscriber:1.0 18 | docker push $DOCKER_HUB_USERNAME/example-restaurant-pos:1.0 19 | ``` 20 | 21 | 3. Modify the yaml files to use your own images 22 | 23 | These are the container images respective yaml files in the deployments folder in this github repo. Modify the values for their container images spec (`image: TARGET_CONTAINER_IMAGE`) 24 | 25 | > Note: remember to replace `` 26 | 27 | * eem-subscriber.yaml - `/eem-subscriber:1.0` 28 | * example-restaurant-pos.yaml - `/example-restaurant-pos:1.0` 29 | 30 | You can now prodeed back to the [README.md](README.md#5-Deploy-the-microservices) 31 | -------------------------------------------------------------------------------- /microservices/frontend/components/clientHelpers/favoritesAPI.js: -------------------------------------------------------------------------------- 1 | 2 | async function getFavoriteRestaurants() { 3 | if (STATIC_DATA) { 4 | // return await fetch('./components/restaurants.json') 5 | return new Promise(resolve => { 6 | resolve("noop") 7 | }) 8 | } else { 9 | try { 10 | let fetchrequest = await fetch(API_URL.concat("/favorites/sync")) 11 | let fetchresponse = await fetchrequest.json() 12 | console.log(fetchresponse) 13 | return fetchresponse 14 | } catch (error) { 15 | throw error 16 | } 17 | } 18 | } 19 | function socketFavoriteRestaurants(callback) { 20 | // listen to socket 21 | let loc = window.location; 22 | let wsurl = "ws://" + loc.host + loc.pathname + "favorites" 23 | // let wsurl = "ws://example-food-demo.anthonyamanse-4-5-f2c6cdc6801be85fd188b09d006f13e3-0000.us-east.containers.appdomain.cloud/favorites" 24 | const socket = new WebSocket(wsurl); 25 | socket.addEventListener('message', function (event) { 26 | try { 27 | let dataObject = JSON.parse(event.data) 28 | callback(dataObject) 29 | } catch (err) { 30 | console.error(err) 31 | } 32 | }); 33 | } -------------------------------------------------------------------------------- /microservices/consumers/courierConsumer/models/order.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const Schema = mongoose.Schema 3 | const MUUID = require('uuid-mongodb').mode('relaxed'); 4 | 5 | let uuidValidator = (id) => { 6 | if (id != null) { 7 | return MUUID.from(id.toObject()) 8 | } 9 | } 10 | 11 | let Order = new Schema({ 12 | orderId: {type: Buffer, subtype: 4, default: () => MUUID.v4(), validate: {validator: uuidValidator}}, 13 | userId: {type: Buffer, subtype: 4, default: () => MUUID.v4(), validate: {validator: uuidValidator}}, 14 | kitchenId: {type: Buffer, subtype: 4, default: () => MUUID.v4(), validate: {validator: uuidValidator}}, 15 | status: {type: String}, 16 | totalPrice: {type: mongoose.Types.Decimal128 , required: true} 17 | }) 18 | 19 | Order.path('orderId').get((orderId) => { 20 | return MUUID.from(orderId.toObject()); 21 | }) 22 | Order.path('userId').get((userId) => { 23 | return MUUID.from(userId.toObject()); 24 | }) 25 | Order.path('kitchenId').get((kitchenId) => { 26 | return MUUID.from(kitchenId.toObject()); 27 | }) 28 | Order.path('totalPrice').get((price) => { 29 | return Number(price.toString()); 30 | }) 31 | Order.set('toJSON', { getters: true, virtuals: false}); 32 | 33 | module.exports = mongoose.model("Order", Order) -------------------------------------------------------------------------------- /microservices/eem/exampleNotificationReceiver/app.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const app = express(); 3 | const cors = require('cors'); 4 | const Redis = require('ioredis'); 5 | let redis_config = { 6 | host: process.env.REDIS_URL, 7 | port: process.env.REDIS_PORT, 8 | db: 1 9 | } 10 | const redis = new Redis(redis_config); 11 | const publisher = new Redis(redis_config); 12 | const channel = process.env.REDIS_CHANNEL || "notification-pubsub" 13 | 14 | const PORT = process.env.PORT || 8080 15 | 16 | app.use(express.json()); 17 | app.use(cors()); 18 | 19 | app.use('/', express.static('example-pos')); 20 | 21 | 22 | // example callback to receive notification 23 | app.post("/callback", (req, res) => { 24 | // req.body.message is the notification 25 | // req.body.kitchenId 26 | // req.body.payload 27 | if (req.body && req.body.kitchenId && req.body.payload) { 28 | let response_data = { 29 | data: { 30 | message_sent: req.body.message, 31 | status: "message_sent" 32 | }, 33 | status: 200 34 | } 35 | publisher.publish(channel, JSON.stringify(response_data)); 36 | res.status('200').send(response_data); 37 | } else { 38 | res.status('404').send("Invalid Request body."); 39 | } 40 | }); 41 | 42 | // redis subscribe will send to websocket 43 | module.exports = { 44 | app, 45 | redis 46 | } -------------------------------------------------------------------------------- /microservices/frontend/components/order.js: -------------------------------------------------------------------------------- 1 | class Order extends HTMLElement { 2 | 3 | static get observedAttributes() { 4 | return ['dish', 'cost', 'status', 'icon']; 5 | } 6 | 7 | constructor() { 8 | super(); 9 | console.log('Initializing Order Component'); 10 | let templateContent = '
'; 11 | this.labels = []; 12 | this.datapath = ""; 13 | const shadow = this.attachShadow({ 14 | mode: 'open' 15 | }) 16 | } 17 | 18 | async connectedCallback() { 19 | let res = await fetch('./components/order.html') 20 | var sr = this.shadowRoot; 21 | sr.innerHTML = await res.text(); 22 | 23 | var dish = this.getAttribute('dish'); 24 | var cost = this.getAttribute('cost'); 25 | var type = this.getAttribute('type'); 26 | var status = this.getAttribute('status'); 27 | 28 | var dishElement = sr.getElementById('dish'); 29 | dishElement.innerHTML = dish; 30 | 31 | var costElement = sr.getElementById('cost'); 32 | costElement.innerHTML = '$' + cost; 33 | 34 | var statusElement = sr.getElementById('status'); 35 | statusElement.innerHTML = status; 36 | 37 | 38 | } 39 | } 40 | 41 | 42 | try { 43 | customElements.define('order-element', Order); 44 | } catch (err) { 45 | const h3 = document.createElement('h3') 46 | h3.innerHTML = err 47 | document.body.appendChild(h3) 48 | } -------------------------------------------------------------------------------- /microservices/consumers/orderConsumer/models/order.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const Schema = mongoose.Schema 3 | const MUUID = require('uuid-mongodb').mode('relaxed'); 4 | 5 | let uuidValidator = (id) => { 6 | if (id != null) { 7 | return MUUID.from(id.toObject()) 8 | } 9 | } 10 | 11 | let Status = new Schema({ 12 | _id: false, 13 | status: {type: String}, 14 | timestamp: {type: Date, default: Date.now } 15 | }) 16 | 17 | let Order = new Schema({ 18 | orderId: {type: Buffer, unique: true, subtype: 4, default: () => MUUID.v4(), validate: {validator: uuidValidator}}, 19 | userId: {type: Buffer, subtype: 4, default: () => MUUID.v4(), validate: {validator: uuidValidator}}, 20 | kitchenId: {type: Buffer, subtype: 4, default: () => MUUID.v4(), validate: {validator: uuidValidator}}, 21 | status: {type: String}, 22 | dish: {type: String}, 23 | statusHistory: [Status], 24 | totalPrice: {type: mongoose.Types.Decimal128 , required: true} 25 | }) 26 | 27 | Order.path('orderId').get((orderId) => { 28 | return MUUID.from(orderId.toObject()); 29 | }) 30 | Order.path('userId').get((userId) => { 31 | return MUUID.from(userId.toObject()); 32 | }) 33 | Order.path('kitchenId').get((kitchenId) => { 34 | return MUUID.from(kitchenId.toObject()); 35 | }) 36 | Order.path('totalPrice').get((price) => { 37 | return Number(price.toString()); 38 | }) 39 | Order.set('toJSON', { getters: true, virtuals: false}); 40 | 41 | module.exports = mongoose.model("Order", Order) -------------------------------------------------------------------------------- /microservices/eem/exampleNotificationReceiver/example-pos/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Example Restaurant POS 5 | 6 | 9 | 10 | 11 | 12 | 13 |
14 |
15 | 21 |
22 |
23 |
24 | 31 | 32 |
33 |
34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /microservices/frontend/components/navigation.html: -------------------------------------------------------------------------------- 1 | 2 | 40 | 46 | -------------------------------------------------------------------------------- /microservices/frontend/components/graph.html: -------------------------------------------------------------------------------- 1 | 50 |
51 |
52 |
53 | 54 | 55 |
56 |
57 | 58 | 59 |
60 |
61 | 62 |
63 |

-------------------------------------------------------------------------------- /deployments/routes.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Route 3 | metadata: 4 | name: apiservice-path-creatorder 5 | spec: 6 | host: HOSTNAME 7 | path: "/createOrder" 8 | port: 9 | targetPort: 8080 10 | to: 11 | kind: Service 12 | name: apiservice 13 | --- 14 | apiVersion: v1 15 | kind: Route 16 | metadata: 17 | name: apiservice-path-status 18 | spec: 19 | host: HOSTNAME 20 | path: "/status" 21 | port: 22 | targetPort: 8080 23 | to: 24 | kind: Service 25 | name: apiservice 26 | --- 27 | apiVersion: v1 28 | kind: Route 29 | metadata: 30 | name: apiservice-path-restaurants 31 | spec: 32 | host: HOSTNAME 33 | path: "/restaurants" 34 | port: 35 | targetPort: 8080 36 | to: 37 | kind: Service 38 | name: apiservice 39 | --- 40 | apiVersion: v1 41 | kind: Route 42 | metadata: 43 | name: apiservice-path-user 44 | spec: 45 | host: HOSTNAME 46 | path: "/user" 47 | port: 48 | targetPort: 8080 49 | to: 50 | kind: Service 51 | name: apiservice 52 | --- 53 | apiVersion: v1 54 | kind: Route 55 | metadata: 56 | name: realtimedata-path-events 57 | spec: 58 | host: HOSTNAME 59 | path: "/events" 60 | port: 61 | targetPort: 8080 62 | to: 63 | kind: Service 64 | name: realtimedata 65 | --- 66 | apiVersion: v1 67 | kind: Route 68 | metadata: 69 | name: podconsumerdata-path-consumers 70 | spec: 71 | host: HOSTNAME 72 | path: "/consumers" 73 | port: 74 | targetPort: 8080 75 | to: 76 | kind: Service 77 | name: podconsumerdata 78 | --- 79 | apiVersion: v1 80 | kind: Route 81 | metadata: 82 | name: favorites-path 83 | spec: 84 | host: HOSTNAME 85 | path: "/favorites" 86 | port: 87 | targetPort: 8080 88 | to: 89 | kind: Service 90 | name: ksql-controller 91 | --- 92 | -------------------------------------------------------------------------------- /microservices/eem/example-restaurant-pos.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: example-restaurant-pos 6 | spec: 7 | selector: 8 | matchLabels: 9 | name: example-restaurant-pos 10 | replicas: 1 11 | template: 12 | metadata: 13 | labels: 14 | name: example-restaurant-pos 15 | spec: 16 | containers: 17 | - image: anthonyamanse/example-restaurant-pos:1.0 18 | name: example-restaurant-pos 19 | imagePullPolicy: Always 20 | ports: 21 | - containerPort: 8080 22 | env: 23 | - name: REDIS_URL 24 | value: 'redis-entity-c' 25 | - name: REDIS_PORT 26 | value: '6379' 27 | --- 28 | apiVersion: v1 29 | kind: Service 30 | metadata: 31 | name: example-restaurant-pos 32 | spec: 33 | ports: 34 | - port: 8080 35 | targetPort: 8080 36 | selector: 37 | name: example-restaurant-pos 38 | --- 39 | apiVersion: apps/v1 40 | kind: Deployment 41 | metadata: 42 | name: redis-entity-c 43 | spec: 44 | selector: 45 | matchLabels: 46 | name: redis-entity-c 47 | replicas: 1 48 | template: 49 | metadata: 50 | labels: 51 | name: redis-entity-c 52 | version: v1 53 | spec: 54 | containers: 55 | - image: redis 56 | name: redis 57 | ports: 58 | - containerPort: 6379 59 | volumeMounts: 60 | - mountPath: /data 61 | name: data 62 | volumes: 63 | - name: data 64 | emptyDir: {} 65 | --- 66 | apiVersion: v1 67 | kind: Service 68 | metadata: 69 | name: redis-entity-c 70 | spec: 71 | ports: 72 | - port: 6379 73 | targetPort: 6379 74 | selector: 75 | name: redis-entity-c 76 | -------------------------------------------------------------------------------- /microservices/eem/eemSubscriber/dashboard/webhookURLeditor.js: -------------------------------------------------------------------------------- 1 | let inputDOM = document.getElementById('webhookInput'); 2 | let editButtonDOM = document.getElementById('editWebhook'); 3 | let saveButtonDOM = document.getElementById('saveWebhook'); 4 | 5 | editButtonDOM.hidden = true; 6 | saveButtonDOM.hidden = true; 7 | 8 | function showWebhook(container, webhook) { 9 | if (webhook == "Kitchen not found.") { 10 | container.placeholder = "No webhook set for this kitchen."; 11 | container.disabled = false; 12 | } else { 13 | container.value = webhook; 14 | editButtonDOM.hidden = false; 15 | } 16 | } 17 | 18 | dropdownDOM.addEventListener("change", async () => { 19 | inputDOM.disabled = true; 20 | editButtonDOM.hidden = true; 21 | saveButtonDOM.hidden = true; 22 | 23 | inputDOM.placeholder = 'Fetching webhook...'; 24 | inputDOM.value = ''; 25 | let kitchenID = dropdownDOM.children[dropdownDOM.selectedIndex].value; 26 | 27 | let currentWebhook = await getCurrentWebhook(kitchenID); 28 | showWebhook(inputDOM, currentWebhook); 29 | }); 30 | 31 | inputDOM.addEventListener("input", () => { 32 | if (inputDOM.value == "") { 33 | saveButtonDOM.hidden = true; 34 | } else { 35 | saveButtonDOM.hidden = false; 36 | } 37 | }); 38 | 39 | editButtonDOM.addEventListener("click", () => { 40 | editButtonDOM.hidden = true; 41 | saveButtonDOM.hidden = false; 42 | inputDOM.disabled = false; 43 | }); 44 | 45 | saveButtonDOM.addEventListener("click", async () => { 46 | let kitchenID = dropdownDOM.children[dropdownDOM.selectedIndex].value; 47 | saveButtonDOM.hidden = true; 48 | let response = await saveWebhook(kitchenID, inputDOM.value); 49 | 50 | if (response == "OK") { 51 | inputDOM.disabled = true; 52 | editButtonDOM.hidden = false; 53 | } else { 54 | inputDOM.placeholder = "Failed to save webhook. Please check logs." 55 | } 56 | }); 57 | -------------------------------------------------------------------------------- /microservices/frontend/components/restaurant.html: -------------------------------------------------------------------------------- 1 | 69 |
70 |
71 | 72 |
73 |
74 |
25 min - 99c deliver over $15 order
75 |
76 |
77 |
-------------------------------------------------------------------------------- /microservices/realtimedata/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "realtimedatawebsocket", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "bindings": { 8 | "version": "1.5.0", 9 | "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", 10 | "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", 11 | "requires": { 12 | "file-uri-to-path": "1.0.0" 13 | } 14 | }, 15 | "file-uri-to-path": { 16 | "version": "1.0.0", 17 | "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", 18 | "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" 19 | }, 20 | "http": { 21 | "version": "0.0.1-security", 22 | "resolved": "https://registry.npmjs.org/http/-/http-0.0.1-security.tgz", 23 | "integrity": "sha512-RnDvP10Ty9FxqOtPZuxtebw1j4L/WiqNMDtuc1YMH1XQm5TgDRaR1G9u8upL6KD1bXHSp9eSXo/ED+8Q7FAr+g==" 24 | }, 25 | "nan": { 26 | "version": "2.14.2", 27 | "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz", 28 | "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==" 29 | }, 30 | "node-rdkafka": { 31 | "version": "2.10.1", 32 | "resolved": "https://registry.npmjs.org/node-rdkafka/-/node-rdkafka-2.10.1.tgz", 33 | "integrity": "sha512-yRb9Y90ipef4X+S/UbvQedUNtKZONa9RR6hCpAaGD83NqUga/uxTofdRQG8bm7SEh/DNuaifIJjRzLcoG9nUSQ==", 34 | "requires": { 35 | "bindings": "^1.3.1", 36 | "nan": "^2.14.0" 37 | } 38 | }, 39 | "ws": { 40 | "version": "7.4.3", 41 | "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.3.tgz", 42 | "integrity": "sha512-hr6vCR76GsossIRsr8OLR9acVVm1jyfEWvhbNjtgPOrfvAlKzvyeg/P6r8RuDjRyrcQoPQT7K0DGEPc7Ae6jzA==" 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /microservices/frontend/components/phone.js: -------------------------------------------------------------------------------- 1 | class Phone extends HTMLElement { 2 | 3 | constructor() { 4 | super(); 5 | console.log('Initializing Phone Component'); 6 | let templateContent = '
'; 7 | this.labels = []; 8 | this.datapath = ""; 9 | const shadow = this.attachShadow({ 10 | mode: 'open' 11 | }) 12 | } 13 | 14 | async connectedCallback() { 15 | let res = await fetch('./components/phone.html') 16 | var sr = this.shadowRoot; 17 | sr.innerHTML = await res.text(); 18 | this.showPhone(); 19 | this.createUserUUID(); 20 | } 21 | 22 | getMobileView(){ 23 | var sr = this.shadowRoot; 24 | var mobileview = sr.getElementById('mobileview'); 25 | return mobileview; 26 | } 27 | 28 | showNavigation(){ 29 | var sr = this.shadowRoot; 30 | var nav = sr.getElementById("mobilenavigation"); 31 | nav.style.display = "flex"; 32 | } 33 | 34 | hideNavigation(){ 35 | var sr = this.shadowRoot; 36 | var nav = sr.getElementById("mobilenavigation"); 37 | nav.style.display = "none"; 38 | } 39 | 40 | createUserUUID() { 41 | if (localStorage.getItem('userId') == null) { 42 | localStorage.setItem('userId', uuidv4()) 43 | } 44 | } 45 | 46 | async showPhone() { 47 | var sr = this.shadowRoot; 48 | var phone = this; 49 | var basebutton = sr.getElementById('basebutton'); 50 | var mobileview = sr.getElementById('mobileview'); 51 | var navigation = sr.getElementById('mobilenavigation'); 52 | // var apptiles = sr.getElementById('APPTILES'); 53 | basebutton.addEventListener('click', e => { 54 | mobileview.innerHTML = ''; 55 | phone.hideNavigation(); 56 | }); 57 | phone.showNavigation(); 58 | } 59 | } 60 | 61 | try { 62 | customElements.define('phone-element', Phone); 63 | } catch (err) { 64 | const h3 = document.createElement('h3') 65 | h3.innerHTML = err 66 | document.body.appendChild(h3) 67 | } 68 | -------------------------------------------------------------------------------- /microservices/eem/eemSubscriber/app.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const app = express(); 3 | const cors = require('cors'); 4 | const Redis = require('ioredis'); 5 | const { v4: uuidv4, validate: uuidValidate } = require('uuid'); 6 | const redis = new Redis({ 7 | host: process.env.REDIS_URL, 8 | port: process.env.REDIS_PORT, 9 | db: 1 10 | }); 11 | 12 | const BACKEND_URL = process.env.BACKEND_URL 13 | 14 | app.use(express.json()); 15 | app.use((err, req, res, next) => { 16 | if (err) { 17 | if (err.type == "entity.parse.failed") { 18 | res.status(400).send("Invalid JSON."); 19 | } else { 20 | res.status(400).send(err.type) 21 | } 22 | } else { 23 | next(); 24 | } 25 | }) 26 | app.use(cors()); 27 | app.use('/dashboard', express.static('dashboard')); 28 | 29 | app.get("/backend_url", (req, res) => { 30 | res.send(BACKEND_URL); 31 | }); 32 | 33 | // result is callback url for kitchen 34 | app.get("/notification/:kitchenId", (req, res) => { 35 | let kitchenId = req.params.kitchenId 36 | if (!uuidValidate(kitchenId)) { 37 | res.status('404').send('Invalid KitchenID format'); 38 | return; 39 | } 40 | redis.get(kitchenId).then(result => { 41 | if (result) { 42 | res.status('200').send(result); 43 | } else { 44 | res.status('404').send('Kitchen not found.'); 45 | } 46 | }).catch(err => { 47 | console.log(err) 48 | res.status('404').send(err); 49 | }); 50 | }); 51 | 52 | 53 | // value is callback url 54 | app.post("/notification/:kitchenId", (req, res) => { 55 | let kitchenId = req.params.kitchenId 56 | let value = req.body.url // TODO: add url validation 57 | let url 58 | try { 59 | url = new URL(value); 60 | } catch { 61 | res.status('404').send('Invalid URL.'); 62 | } 63 | if (!url) return; 64 | redis.get(kitchenId).then(result => { 65 | redis.set(kitchenId, value); 66 | }).catch(err => { 67 | console.log(err) 68 | }); 69 | res.status('200').send('OK'); 70 | }); 71 | 72 | module.exports = { 73 | app, 74 | redis, 75 | disconnect: (done) => { 76 | redis.disconnect(false); 77 | done(); 78 | } 79 | } -------------------------------------------------------------------------------- /microservices/frontend/components/navigation.js: -------------------------------------------------------------------------------- 1 | class Navigation extends HTMLElement { 2 | 3 | activeview = ''; 4 | 5 | getMobileView(){ 6 | var sr = this.shadowRoot; 7 | 8 | // I don't like this being hard coded, but have stuggled to find a dynamic way for exampe: .childNodes.item("mobileview"); 9 | 10 | var mobileview = sr.host.parentElement.childNodes[3]; 11 | return mobileview; 12 | } 13 | 14 | constructor() { 15 | super(); 16 | console.log('Initializing Navigation Component'); 17 | let templateContent = '
'; 18 | this.labels = []; 19 | this.datapath = ""; 20 | const shadow = this.attachShadow({ 21 | mode: 'open' 22 | }) 23 | } 24 | 25 | async connectedCallback() { 26 | let res = await fetch('./components/navigation.html') 27 | var sr = this.shadowRoot; 28 | sr.innerHTML = await res.text(); 29 | this.showNavigation(); 30 | } 31 | 32 | setAllButtonsDisabled(){ 33 | var sr = this.shadowRoot; 34 | this.buttonRow = sr.getElementById('buttonrow'); 35 | 36 | const buttonlist = Array.from(this.buttonRow.children); 37 | 38 | 39 | buttonlist.forEach(function(node){ 40 | node.setDisabled(); 41 | }) 42 | } 43 | 44 | showNavigation(){ 45 | var sr = this.shadowRoot; 46 | this.buttonRow = sr.getElementById('buttonrow'); 47 | 48 | var navelement = this; 49 | 50 | this.buttonRow.addEventListener('NAV', e => { 51 | 52 | console.log(e) 53 | 54 | var id = e.detail.eventData.id; 55 | 56 | // console.log('HOMESCREEN RECIEVED EVENT FROM NAV BUTTON: ' + id.toLocaleUpperCase()); 57 | 58 | this.setAllButtonsDisabled(); 59 | 60 | var button = sr.getElementById(id); 61 | button.setEnabled(); 62 | navelement.activeview = id; 63 | 64 | var mobileview = this.getMobileView(); 65 | mobileview.innerHTML = "<" + id + "-element>"; 66 | }); 67 | } 68 | } 69 | 70 | try { 71 | customElements.define('navigation-element', Navigation); 72 | } catch (err) { 73 | const h3 = document.createElement('h3') 74 | h3.innerHTML = err 75 | document.body.appendChild(h3) 76 | } 77 | -------------------------------------------------------------------------------- /microservices/eem/eem-subscriber.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: external-app-eem-subscriber 6 | spec: 7 | selector: 8 | matchLabels: 9 | name: external-app-eem-subscriber 10 | replicas: 1 11 | template: 12 | metadata: 13 | labels: 14 | name: external-app-eem-subscriber 15 | spec: 16 | containers: 17 | - image: anthonyamanse/eem-subscriber:1.0 18 | name: external-app-eem-subscriber 19 | env: 20 | - name: REDIS_URL 21 | value: 'redis-entity-b' 22 | - name: REDIS_PORT 23 | value: '6379' 24 | - name: BACKEND_URL 25 | value: 'http://PREVIOUS_CODE_PATTERN_URL' 26 | # - name: CA_LOCATION 27 | # value: '/etc/event-gateway-tls/ca.pem' 28 | envFrom: 29 | - secretRef: 30 | name: asyncapi-endpoint-credentials 31 | # volumeMounts: 32 | # - name: eg-ca-pem 33 | # mountPath: "/etc/event-gateway-tls" 34 | # readOnly: true 35 | # volumes: 36 | # - name: eg-ca-pem 37 | # secret: 38 | # secretName: event-gateway-ca 39 | # items: 40 | # - key: ca.pem 41 | # path: ca.pem 42 | --- 43 | apiVersion: v1 44 | kind: Service 45 | metadata: 46 | name: external-app-eem-subscriber 47 | spec: 48 | ports: 49 | - port: 8080 50 | targetPort: 8080 51 | selector: 52 | name: external-app-eem-subscriber 53 | --- 54 | apiVersion: apps/v1 55 | kind: Deployment 56 | metadata: 57 | name: redis-entity-b 58 | spec: 59 | selector: 60 | matchLabels: 61 | name: redis-entity-b 62 | replicas: 1 63 | template: 64 | metadata: 65 | labels: 66 | name: redis-entity-b 67 | version: v1 68 | spec: 69 | containers: 70 | - image: redis 71 | name: redis 72 | ports: 73 | - containerPort: 6379 74 | volumeMounts: 75 | - mountPath: /data 76 | name: data 77 | volumes: 78 | - name: data 79 | emptyDir: {} 80 | --- 81 | apiVersion: v1 82 | kind: Service 83 | metadata: 84 | name: redis-entity-b 85 | spec: 86 | ports: 87 | - port: 6379 88 | targetPort: 6379 89 | selector: 90 | name: redis-entity-b 91 | -------------------------------------------------------------------------------- /microservices/eem/eemSubscriber/dashboard/clientHelper.js: -------------------------------------------------------------------------------- 1 | async function getRestaurants(delayBetweenRequests, backend_url) { 2 | delayBetweenRequests = delayBetweenRequests === undefined ? 250 : delayBetweenRequests 3 | backend_url = backend_url === undefined ? '' : backend_url 4 | // console.log(delayBetweenRequests) 5 | let invocationRequest = await fetch(backend_url + '/restaurants') 6 | let invocationResponse = await invocationRequest.json() 7 | let invocationId = invocationResponse.requestId 8 | let count = 10 // 10 tries 9 | // console.log(invocationResponse) 10 | while (count > 0) { 11 | let inlineDelay = await new Promise(resolve => setTimeout(resolve, delayBetweenRequests)) // 250ms 12 | statusRequest = await fetch(backend_url + "/status/".concat(invocationId)) 13 | statusJson = await statusRequest.json() 14 | if (statusJson.status != "processing") { 15 | // console.log(statusJson) 16 | return statusJson.docs 17 | } 18 | count-- 19 | } 20 | return "Failed to get restaurants list in 10 tries" 21 | } 22 | 23 | async function getBackendURL() { 24 | let invocationRequest = await fetch("/backend_url") 25 | let backend_url = await invocationRequest.text() 26 | return backend_url 27 | } 28 | 29 | function showRestaurants(container, restaurants) { 30 | restaurants.forEach(element => { 31 | let optionElement = document.createElement('option'); 32 | optionElement.value = element.kitchenId; 33 | optionElement.text = element.name; 34 | container.appendChild(optionElement); 35 | }) 36 | } 37 | 38 | async function saveWebhook(kitchenId, webhook) { 39 | let saveWebhookRequest = await fetch('/notification/' + kitchenId, { 40 | method: 'POST', 41 | headers: { 42 | 'Content-Type': 'application/json' 43 | }, 44 | body: JSON.stringify({url: webhook}) 45 | }); 46 | let saveWebhookResponse = await saveWebhookRequest.text(); 47 | return saveWebhookResponse; 48 | } 49 | 50 | async function getCurrentWebhook(kitchenId) { 51 | let currentWebhookRequest = await fetch('/notification/' + kitchenId); 52 | let currentWebhook = await currentWebhookRequest.text(); 53 | return currentWebhook; 54 | } 55 | 56 | module.exports = { 57 | getRestaurants, 58 | showRestaurants 59 | } -------------------------------------------------------------------------------- /microservices/eem/eemSubscriber/dashboard/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Restaurant Webhook Manager 5 | 6 | 9 | 10 | 11 | 12 | 13 |
14 |
15 | 21 |
22 | 23 |
24 | 25 |
26 | 30 |
31 | 32 |
33 | 34 |
35 | 36 |
37 | 38 | 39 |
40 | 41 |
42 | 43 |
44 |
OpenShift Apps With Kafka
45 | 46 | 47 | 48 | 49 |
50 | 51 |
52 |
53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /microservices/frontend/components/clientHelpers/ordersAPI.js: -------------------------------------------------------------------------------- 1 | async function createOrder(order, config, sendOnly) { 2 | if (STATIC_DATA) { 3 | console.log(STATIC_DATA) 4 | return new Promise(resolve => { 5 | resolve("noop") 6 | }) 7 | } else { 8 | let temp = config || {} 9 | let kitchenSpeed = temp.kitchenSpeed || 1000, courierSpeed = temp.courierSpeed || 1000 10 | let jsonBodyRequest = {...order, kitchenSpeed, courierSpeed} 11 | let invocationRequest = await fetch(API_URL.concat("/createOrder"), { 12 | method: 'POST', 13 | headers: { 14 | 'Content-Type': 'application/json' 15 | }, 16 | body: JSON.stringify(jsonBodyRequest) 17 | }) 18 | let invocationResponse = await invocationRequest.json() 19 | let invocationId = invocationResponse.requestId 20 | let count = 10 // 10 tries 21 | if (sendOnly) { 22 | count = 0 23 | return invocationResponse 24 | } 25 | while (count > 0) { 26 | let inlineDelay = await new Promise(resolve => setTimeout(resolve, 250)) // 250ms 27 | statusRequest = await fetch(API_URL.concat("/status/").concat(invocationId)) 28 | statusJson = await statusRequest.json() 29 | if (statusJson.status != "processing") { 30 | return {...statusJson, ...invocationResponse} 31 | } 32 | count-- 33 | } 34 | } 35 | } 36 | 37 | async function getOrdersOfUser(userId) { 38 | if (STATIC_DATA) { 39 | console.log(STATIC_DATA) 40 | return new Promise(resolve => { 41 | resolve("noop") 42 | }) 43 | } else { 44 | let invocationRequest = await fetch(API_URL.concat(`/user/${userId}/orders`)) 45 | let invocationResponse = await invocationRequest.json() 46 | let invocationId = invocationResponse.requestId 47 | let count = 10 // 10 tries 48 | while (count > 0) { 49 | let inlineDelay = await new Promise(resolve => setTimeout(resolve, 250)) // 250ms 50 | statusRequest = await fetch(API_URL.concat("/status/").concat(invocationId)) 51 | statusJson = await statusRequest.json() 52 | if (statusJson.status != "processing") { 53 | return statusJson 54 | } 55 | count-- 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # Snowpack dependency directory (https://snowpack.dev/) 45 | web_modules/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | .parcel-cache 78 | 79 | # Next.js build output 80 | .next 81 | out 82 | 83 | # Nuxt.js build / generate output 84 | .nuxt 85 | dist 86 | 87 | # Gatsby files 88 | .cache/ 89 | # Comment in the public line in if your project uses Gatsby and not Next.js 90 | # https://nextjs.org/blog/next-9-1#public-directory-support 91 | # public 92 | 93 | # vuepress build output 94 | .vuepress/dist 95 | 96 | # Serverless directories 97 | .serverless/ 98 | 99 | # FuseBox cache 100 | .fusebox/ 101 | 102 | # DynamoDB Local files 103 | .dynamodb/ 104 | 105 | # TernJS port file 106 | .tern-port 107 | 108 | # Stores VSCode versions used for testing VSCode extensions 109 | .vscode-test 110 | 111 | # yarn v2 112 | .yarn/cache 113 | .yarn/unplugged 114 | .yarn/build-state.yml 115 | .yarn/install-state.gz 116 | .pnp.* 117 | -------------------------------------------------------------------------------- /deployments/keda-scaler.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: keda.sh/v1alpha1 3 | kind: ScaledObject 4 | metadata: 5 | name: orderconsumer-scaler 6 | spec: 7 | scaleTargetRef: 8 | kind: Deployment 9 | name: orders 10 | pollingInterval: 1 # example 11 | cooldownPeriod: 300 # example 12 | minReplicaCount: 2 13 | maxReplicaCount: 6 14 | triggers: 15 | - type: kafka 16 | metadata: 17 | topic: orders 18 | bootstrapServers: pkc-419q3.us-east4.gcp.confluent.cloud:9092 19 | consumerGroup: orders-consumer-group 20 | lagThreshold: '5' 21 | authenticationRef: 22 | name: keda-trigger-auth-kafka-credential 23 | --- 24 | apiVersion: keda.sh/v1alpha1 25 | kind: ScaledObject 26 | metadata: 27 | name: kitchenconsumer-scaler 28 | spec: 29 | scaleTargetRef: 30 | kind: Deployment 31 | name: kitchen 32 | pollingInterval: 1 # example 33 | cooldownPeriod: 300 # example 34 | minReplicaCount: 2 35 | maxReplicaCount: 6 36 | triggers: 37 | - type: kafka 38 | metadata: 39 | topic: orders 40 | bootstrapServers: pkc-419q3.us-east4.gcp.confluent.cloud:9092 41 | consumerGroup: kitchen-consumer-group 42 | lagThreshold: '5' 43 | authenticationRef: 44 | name: keda-trigger-auth-kafka-credential 45 | --- 46 | apiVersion: keda.sh/v1alpha1 47 | kind: ScaledObject 48 | metadata: 49 | name: courierconsumer-scaler 50 | spec: 51 | scaleTargetRef: 52 | kind: Deployment 53 | name: courier 54 | pollingInterval: 1 # example 55 | cooldownPeriod: 300 # example 56 | minReplicaCount: 2 57 | maxReplicaCount: 6 58 | triggers: 59 | - type: kafka 60 | metadata: 61 | topic: orders 62 | bootstrapServers: pkc-419q3.us-east4.gcp.confluent.cloud:9092 63 | consumerGroup: courier-consumer-group 64 | lagThreshold: '5' 65 | authenticationRef: 66 | name: keda-trigger-auth-kafka-credential 67 | --- 68 | apiVersion: keda.sh/v1alpha1 69 | kind: ScaledObject 70 | metadata: 71 | name: statusconsumer-scaler 72 | spec: 73 | scaleTargetRef: 74 | kind: Deployment 75 | name: status 76 | pollingInterval: 1 # example 77 | cooldownPeriod: 300 # example 78 | minReplicaCount: 2 79 | maxReplicaCount: 6 80 | triggers: 81 | - type: kafka 82 | metadata: 83 | topic: orders 84 | bootstrapServers: pkc-419q3.us-east4.gcp.confluent.cloud:9092 85 | consumerGroup: status-consumer-group 86 | lagThreshold: '5' 87 | authenticationRef: 88 | name: keda-trigger-auth-kafka-credential -------------------------------------------------------------------------------- /microservices/statusService/app.js: -------------------------------------------------------------------------------- 1 | const KafkaWrapper = require('./KafkaWrapper.js') 2 | const Redis = require('ioredis'); 3 | const express = require('express'); 4 | const app = express(); 5 | const cors = require('cors'); 6 | 7 | // connect to redis localhost 8 | const redis = new Redis({ 9 | host: process.env.REDIS_URL, 10 | port: process.env.REDIS_PORT, 11 | db: 0 12 | }) 13 | 14 | const PORT = process.env.PORT || 8080 15 | 16 | app.use(express.json()); 17 | app.use(cors()) 18 | 19 | app.get("/status/:requestId", (req, res) => { 20 | let requestId = req.params.requestId 21 | redis.get(requestId).then(result => { 22 | res.status('200').send(JSON.parse(result)) 23 | }).catch(err => { 24 | console.log(err) 25 | res.status('404').send(err) 26 | }) 27 | }) 28 | 29 | app.post("/status/:requestId", (req, res) => { 30 | let requestId = req.params.requestId 31 | let value = req.body.value 32 | redis.get(requestId).then(result => { 33 | if (!result) { 34 | redis.set(requestId, value, 'EX', 3600) 35 | } 36 | }).catch(err => { 37 | console.log(err) 38 | }) 39 | res.status('200').send('OK') 40 | }) 41 | 42 | app.listen(PORT, () => { 43 | console.log(`listening on port ${PORT}`); 44 | }); 45 | 46 | KafkaWrapper.consumer.on('ready', function() { 47 | console.log('The consumer has connected.'); 48 | KafkaWrapper.consumer.subscribe(['orders']); 49 | KafkaWrapper.consumer.consume() 50 | }).on('data', function(data) { 51 | try { 52 | let dataObject = JSON.parse(data.value.toString()) 53 | // dataObject 54 | // {eventType, payload: {requestId, message}} 55 | let eventType = dataObject.eventType 56 | let payload = dataObject.payload 57 | switch (eventType) { 58 | case "updateHttpResponse": 59 | redis.set(payload.requestId, payload.message, 'EX', 3600) 60 | KafkaWrapper.consumer.commitMessage(data) 61 | break; 62 | default: 63 | // console.log(`${dataObject.eventType} is not handled in this service`) 64 | KafkaWrapper.consumer.commitMessage(data) 65 | } 66 | } catch (err) { 67 | console.error(err) 68 | // add error response to redis 69 | KafkaWrapper.consumer.commitMessage(data) 70 | } 71 | }); 72 | 73 | KafkaWrapper.consumer.connect() 74 | // KafkaWrapper.producer.on('ready', () => { 75 | // console.log('The producer has connected.') 76 | // KafkaWrapper.consumer.connect() 77 | // }) -------------------------------------------------------------------------------- /microservices/statusService/KafkaWrapper.js: -------------------------------------------------------------------------------- 1 | const Kafka = require('node-rdkafka'); 2 | 3 | class KafkaWrapper { 4 | constructor(brokers, protocol, mechanism, username, password) { 5 | // ibm cloud service credentials 6 | // let jsonCredentials = JSON.parse(ibmcloud_credentials) 7 | // let brokers = jsonCredentials.kafka_brokers_sasl 8 | // let apiKey = jsonCredentials.api_key 9 | // producer 10 | // let driver_options = { 11 | // //'debug': 'all', 12 | // 'metadata.broker.list': brokers, 13 | // 'security.protocol': 'SASL_SSL', 14 | // 'sasl.mechanisms': 'PLAIN', 15 | // 'sasl.username': 'token', 16 | // 'sasl.password': apiKey, 17 | // 'log.connection.close' : false, 18 | // 'enable.auto.commit': false 19 | // }; 20 | let driver_options = { 21 | //'debug': 'all', 22 | 'metadata.broker.list': brokers, 23 | 'security.protocol': protocol, 24 | 'sasl.mechanisms': mechanism, 25 | 'sasl.username': username, 26 | 'sasl.password': password, 27 | 'log.connection.close' : false, 28 | 'enable.auto.commit': false 29 | }; 30 | let consumerConfig = { 31 | 'client.id': 'status-consumer', 32 | 'group.id': 'status-consumer-group', 33 | } 34 | 35 | for (var key in driver_options) { 36 | consumerConfig[key] = driver_options[key] 37 | } 38 | 39 | // create kafka consumer 40 | let topicConfig = { 41 | 'auto.offset.reset': 'earliest' 42 | } 43 | let consumer = new Kafka.KafkaConsumer(consumerConfig, topicConfig) 44 | 45 | // Register error listener 46 | consumer.on('event.error', function(err) { 47 | console.error('Error from consumer:' + JSON.stringify(err)); 48 | }); 49 | 50 | this.consumer = consumer 51 | } 52 | 53 | on(event, callback) { 54 | this.consumer.on(event, callback) 55 | } 56 | } 57 | 58 | // const kafkaWrapper = new KafkaWrapper(process.env.KAFKA_CREDENTIALS) 59 | const kafkaWrapper = new KafkaWrapper(process.env.BOOTSTRAP_SERVERS, 60 | process.env.SECURITY_PROTOCOL, 61 | process.env.SASL_MECHANISMS, 62 | process.env.SASL_USERNAME, 63 | process.env.SASL_PASSWORD) 64 | Object.freeze(kafkaWrapper) 65 | 66 | module.exports = kafkaWrapper -------------------------------------------------------------------------------- /microservices/frontend/components/navigationbutton.js: -------------------------------------------------------------------------------- 1 | class NavigationButton extends HTMLElement { 2 | 3 | 4 | SELECTEDSUFFIX = '-selected.svg'; 5 | DESELECTEDSUFFIX = '-deselected.svg' 6 | 7 | static get observedAttributes() { 8 | return ['imagename','viewname','mode']; 9 | } 10 | 11 | constructor() { 12 | super(); 13 | let templateContent = '
'; 14 | this.labels = []; 15 | this.datapath = ""; 16 | const shadow = this.attachShadow({ 17 | mode: 'open' 18 | }) 19 | } 20 | 21 | async connectedCallback() { 22 | let res = await fetch('./components/navigationbutton.html') 23 | var sr = this.shadowRoot; 24 | sr.innerHTML = await res.text(); 25 | this.showButton(); 26 | } 27 | 28 | setMode(mode){ 29 | this.mode = mode; 30 | 31 | var imagestring = this.imagename; 32 | 33 | if(this.mode=='active'){ 34 | imagestring = imagestring + this.SELECTEDSUFFIX; 35 | }else{ 36 | imagestring = imagestring + this.DESELECTEDSUFFIX; 37 | } 38 | 39 | this.buttonimage.src = './images/' + imagestring; 40 | } 41 | 42 | setEnabled(){ 43 | this.setMode('active'); 44 | } 45 | 46 | setDisabled(){ 47 | this.setMode('inactive'); 48 | } 49 | 50 | showButton(){ 51 | var customElement = this; 52 | var sr = this.shadowRoot; 53 | this.buttonimage = sr.getElementById('navbuttonimage'); 54 | this.button = sr.getElementById('navbutton'); 55 | 56 | // this.mode = customElement.getAttribute('mode'); 57 | this.viewname = customElement.getAttribute('viewname'); 58 | this.imagename = customElement.getAttribute('imagename') 59 | 60 | this.setMode(customElement.getAttribute('mode')); 61 | 62 | this.button.onclick = function () { 63 | console.log('CLICKING NAV BUTTON: ' + customElement.viewname.toLocaleUpperCase()); 64 | var customEvent = new CustomEvent( 'NAV', { 65 | detail: { 66 | eventData: {"id":customElement.viewname} 67 | }, 68 | bubbles: true 69 | }); 70 | customElement.dispatchEvent(customEvent); 71 | } 72 | 73 | console.log('Adding Navigation Button : ' + this.viewname.toLocaleUpperCase()); 74 | } 75 | } 76 | 77 | 78 | try { 79 | customElements.define('navigationbutton-element', NavigationButton); 80 | } catch (err) { 81 | const h3 = document.createElement('h3') 82 | h3.innerHTML = err 83 | document.body.appendChild(h3) 84 | } -------------------------------------------------------------------------------- /microservices/frontend/components/controls/architectureupdater.js: -------------------------------------------------------------------------------- 1 | let architectureElement = document.getElementsByTagName('architecture-element')[0] 2 | 3 | // test random 4 | // setInterval(() => { 5 | // architectureElement.setAttribute('statusworkers', Math.floor((Math.random() * 5) + 1)) 6 | // architectureElement.setAttribute('kitchenworkers', Math.floor((Math.random() * 5) + 1)) 7 | // architectureElement.setAttribute('orderworkers', Math.floor((Math.random() * 5) + 1)) 8 | // architectureElement.setAttribute('driverworkers', Math.floor((Math.random() * 5) + 1)) 9 | // },5000) 10 | 11 | let eventSource = new EventSource('/consumers') 12 | eventSource.onmessage = ({ data }) => { 13 | if (data) { 14 | try { 15 | let parsedData = JSON.parse(data) 16 | architectureElement.setAttribute('statusworkers', parsedData.statusconsumers) 17 | architectureElement.setAttribute('kitchenworkers', parsedData.kitchenconsumers) 18 | architectureElement.setAttribute('orderworkers', parsedData.ordersconsumers) 19 | architectureElement.setAttribute('driverworkers', parsedData.courierconsumers) 20 | architectureElement.updateArchitecture() 21 | } catch (err) { 22 | console.error(err) 23 | } 24 | } 25 | } 26 | 27 | function getOffsetsConsumerGroups() { 28 | // refactor server to SSE 29 | // listen to socket 30 | let loc = window.location; 31 | let wsurl = "ws://" + loc.host + loc.pathname + "events/consumergroupsoffset" 32 | // let wsurl = "ws://localhost:8080/events/consumergroupsoffset" 33 | // let wsurl = "ws://example-food-food-delivery.anthonyamanse-4-5-f2c6cdc6801be85fd188b09d006f13e3-0000.us-east.containers.appdomain.cloud/events/consumergroupsoffset" 34 | const socket = new WebSocket(wsurl); 35 | socket.addEventListener('message', function (event) { 36 | try { 37 | let eventObject = JSON.parse(event.data) 38 | // console.log(eventObject) 39 | architectureElement.setAttribute('statusworkers-offsetdifference', eventObject.status) 40 | architectureElement.setAttribute('kitchenworkers-offsetdifference', eventObject.kitchen) 41 | architectureElement.setAttribute('orderworkers-offsetdifference', eventObject.orders) 42 | architectureElement.setAttribute('driverworkers-offsetdifference', eventObject.courier) 43 | architectureElement.updateArchitecture() 44 | // calculate consumed messages per second per consumer group? 45 | } catch (err) { 46 | console.error(err) 47 | } 48 | }); 49 | } 50 | 51 | getOffsetsConsumerGroups() -------------------------------------------------------------------------------- /microservices/frontend/components/slider.js: -------------------------------------------------------------------------------- 1 | class Slider extends HTMLElement { 2 | 3 | static get observedAttributes() { 4 | return ['title','left','right']; 5 | } 6 | 7 | constructor() { 8 | super(); 9 | let templateContent = '
'; 10 | this.labels = []; 11 | this.datapath = ""; 12 | const shadow = this.attachShadow({ 13 | mode: 'open' 14 | }) 15 | } 16 | 17 | async connectedCallback() { 18 | let res = await fetch('./components/slider.html') 19 | var sr = this.shadowRoot; 20 | sr.innerHTML = await res.text(); 21 | this.titletext = this.getAttribute('title'); 22 | console.log('Initializing ' + this.titletext.toLocaleUpperCase() + ' Slider Component'); 23 | 24 | var sliderlabel = sr.getElementById("sliderlabel"); 25 | sliderlabel.innerHTML = this.titletext; 26 | 27 | this.lefttext = this.getAttribute('left'); 28 | var left = sr.getElementById("left"); 29 | left.innerHTML = this.lefttext; 30 | 31 | this.righttext = this.getAttribute('right'); 32 | var right = sr.getElementById("right"); 33 | right.innerHTML = this.righttext; 34 | 35 | this.showSlider(); 36 | 37 | let input = sr.getElementById("myRange") 38 | 39 | // set range of rate of orders 40 | if (this.titletext === 'rate of orders') { 41 | input.setAttribute('max', 20) 42 | input.setAttribute('value', 1) 43 | } 44 | 45 | // add slider connected event to assign default values 46 | let customEvent = new CustomEvent('slider-input-connected', { 47 | detail: { 48 | eventData: { 49 | "value": input.value 50 | } 51 | }, 52 | bubbles: true 53 | }); 54 | this.dispatchEvent(customEvent) 55 | 56 | // add input change event 57 | input.addEventListener('change', e => { 58 | let customEvent = new CustomEvent('slider-input-change', { 59 | detail: { 60 | eventData: { 61 | "value": e.target.value 62 | } 63 | }, 64 | bubbles: true 65 | }); 66 | this.dispatchEvent(customEvent) 67 | }) 68 | } 69 | 70 | async showSlider() { 71 | var sr = this.shadowRoot; 72 | } 73 | } 74 | 75 | try { 76 | customElements.define('slider-element', Slider); 77 | } catch (err) { 78 | const h3 = document.createElement('h3') 79 | h3.innerHTML = err 80 | document.body.appendChild(h3) 81 | } 82 | -------------------------------------------------------------------------------- /microservices/eem/eemSubscriber/server.js: -------------------------------------------------------------------------------- 1 | const KafkaWrapper = require('./KafkaWrapper.js'); 2 | const {app, redis} = require("./app"); 3 | const axios = require('axios').default; 4 | const NotifyHelper = require('./sendNotificationHelper'); 5 | 6 | const PORT = process.env.PORT || 8080 7 | 8 | KafkaWrapper.consumer.on('ready', function() { 9 | console.log('The consumer has connected.'); 10 | KafkaWrapper.consumer.subscribe(['orders']); 11 | KafkaWrapper.consumer.consume(); 12 | 13 | app.listen(PORT, () => { 14 | console.log(`listening on port ${PORT}`); 15 | }); 16 | }).on('data', function(data) { 17 | try { 18 | let dataObject = JSON.parse(data.value.toString()) 19 | // dataObject for orders 20 | // {eventType, payload: {orderId,userId,kitchenId,requestId}, simulatorConfig: {}} 21 | let eventType = dataObject.eventType; 22 | let payload = dataObject.payload; 23 | let kitchenId = payload.kitchenId; 24 | switch (eventType) { 25 | case "orderCreated": 26 | // get callback url from redis 27 | // send notification 28 | redis.get(kitchenId).then(result => { 29 | // result is callback url 30 | if (result) { 31 | let requestBody = NotifyHelper.payloadToRequestBody(payload, eventType); 32 | NotifyHelper.notifyOnCallbackURL(result, requestBody); 33 | } 34 | }).catch(err => { 35 | console.log(err) 36 | }) 37 | KafkaWrapper.consumer.commitMessage(data) 38 | break; 39 | case "delivered": 40 | // get callback url from redis 41 | // send notification 42 | redis.get(kitchenId).then(result => { 43 | // result is callback url 44 | if (result) { 45 | let requestBody = NotifyHelper.payloadToRequestBody(payload, eventType); 46 | NotifyHelper.notifyOnCallbackURL(result, requestBody); 47 | } 48 | }).catch(err => { 49 | console.log(err) 50 | }) 51 | KafkaWrapper.consumer.commitMessage(data) 52 | break; 53 | default: 54 | console.log(`${dataObject.eventType} is not handled in this service`) 55 | KafkaWrapper.consumer.commitMessage(data) 56 | } 57 | } catch (err) { 58 | console.error(err) 59 | // add error response to redis 60 | KafkaWrapper.consumer.commitMessage(data) 61 | } 62 | }); 63 | 64 | KafkaWrapper.consumer.connect(); -------------------------------------------------------------------------------- /microservices/frontend/components/receipt.js: -------------------------------------------------------------------------------- 1 | class Receipt extends HTMLElement { 2 | 3 | static get observedAttributes() { 4 | return ['imagename','viewname','mode']; 5 | } 6 | 7 | constructor() { 8 | super(); 9 | console.log('Initializing Receipt Component'); 10 | let templateContent = '
'; 11 | this.labels = []; 12 | this.datapath = ""; 13 | const shadow = this.attachShadow({ 14 | mode: 'open' 15 | }) 16 | } 17 | 18 | async connectedCallback() { 19 | let res = await fetch('./components/receipt.html') 20 | var sr = this.shadowRoot; 21 | sr.innerHTML = await res.text(); 22 | // show stored locally then update 23 | this.showOrders(); 24 | this.updateLocalOrdersFromServer(); 25 | } 26 | 27 | showOrders() { 28 | var orders = localStorage.getItem('KAFKA-ORDERS'); 29 | 30 | var orderlist = JSON.parse(orders); 31 | this.renderOrders(orderlist) 32 | } 33 | 34 | renderOrders(orderlist) { 35 | var sr = this.shadowRoot; 36 | 37 | var anchor = sr.getElementById('orderList'); 38 | 39 | // remove currently rendered orders 40 | while (anchor.firstChild) { 41 | anchor.removeChild(anchor.firstChild); 42 | } 43 | 44 | try { 45 | orderlist.forEach(element => { 46 | console.log(element); 47 | var order = document.createElement('order-element'); 48 | order.setAttribute('dish', element.dish); 49 | order.setAttribute('cost', element.totalPrice); 50 | order.setAttribute('type', element.type); 51 | order.setAttribute('status', element.status); 52 | anchor.appendChild(order); 53 | }); 54 | } catch (err) { 55 | console.error(err) 56 | } 57 | } 58 | 59 | async updateLocalOrdersFromServer() { 60 | // check for orders on server 61 | let userId = localStorage.getItem("userId") 62 | let ordersResponse = await getOrdersOfUser(userId) 63 | 64 | if (ordersResponse != "noop") { 65 | if (ordersResponse.status == "success") { 66 | let orderlist = ordersResponse.docs 67 | // clear and store current orders from server 68 | localStorage.removeItem('KAFKA-ORDERS') 69 | localStorage.setItem('KAFKA-ORDERS', JSON.stringify(orderlist)) 70 | this.renderOrders(orderlist) 71 | } 72 | } 73 | } 74 | } 75 | 76 | 77 | try { 78 | customElements.define('receipt-element', Receipt); 79 | } catch (err) { 80 | const h3 = document.createElement('h3') 81 | h3.innerHTML = err 82 | document.body.appendChild(h3) 83 | } -------------------------------------------------------------------------------- /microservices/eem/eemSubscriber/__tests__/client-helper-tests.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @jest-environment jsdom 3 | */ 4 | 5 | const { default: axios } = require('axios'); 6 | const ClientHelper = require('../dashboard/clientHelper'); 7 | 8 | let example_response = {"status":"success","docs": 9 | [{"type":"vegan","image":"sprout.svg","name":"Pure Kitchen", 10 | "menu": 11 | [{"item":"Radical Cauliflour Wings","price":16.5},{"item":"Thrive Bowl","price":17.5},{"item":"Warrior Bowl","price":21},{"item":"Artisinal Sandwich","price":18.75}], 12 | "kitchenId":"acd45280-4e12-4b97-865c-a44396d905c8"}, 13 | {"type":"vegan","image":"sprout.svg","name":"Chipotle", 14 | "menu": 15 | [{"item":"Burrito","price":11},{"item":"Tacos","price":11.5},{"item":"Salad","price":12}], 16 | "kitchenId":"2c1be047-6b1f-4312-80e3-975966ae70d3"} 17 | ] 18 | } 19 | 20 | describe("Test Client requests", () => { 21 | global.fetch = jest.fn(() => { 22 | return Promise.resolve({ 23 | json: () => Promise.resolve(example_response) 24 | }) 25 | }) 26 | 27 | test("Get Restaurants should return lists of restaurants", async () => { 28 | let response = await ClientHelper.getRestaurants(0); 29 | expect(response).toEqual(example_response.docs) 30 | }); 31 | 32 | test("showRestaurants should update \"select\" DOM with restaurant list", () => { 33 | document.body.innerHTML = ` 34 | 36 | `; 37 | 38 | 39 | const selectContainer = document.getElementById('restaurantDropdown'); 40 | let restaurants = example_response.docs 41 | ClientHelper.showRestaurants(selectContainer, restaurants); 42 | 43 | let tempSelectContainter = document.createElement('select'); 44 | restaurants.forEach(element => { 45 | let optionElement = document.createElement('option'); 46 | optionElement.value = element.kitchenId; 47 | optionElement.text = element.name; 48 | tempSelectContainter.appendChild(optionElement); 49 | }) 50 | 51 | expect(selectContainer.children).toEqual(tempSelectContainter.children); 52 | }) 53 | 54 | test("Get Restaurants failed to return lists of restaurants", async () => { 55 | example_response = {status: "processing"} 56 | let response = await ClientHelper.getRestaurants(0); 57 | expect(response).toEqual("Failed to get restaurants list in 10 tries") 58 | }); 59 | 60 | test.todo('Add tests here for REST calls to server'); 61 | test.todo('Add test here to show current webhook url when restaurant of webhook url to enable when edit 114 | 115 | 116 | -------------------------------------------------------------------------------- /microservices/frontend/components/menuitem.js: -------------------------------------------------------------------------------- 1 | class MenuItem extends HTMLElement { 2 | 3 | static get observedAttributes() { 4 | return ['entry', 'cost', 'restaurant', 'type', 'kitchenId']; 5 | } 6 | 7 | constructor() { 8 | super(); 9 | console.log('Initializing MenuItem Component'); 10 | let templateContent = '
'; 11 | this.labels = []; 12 | this.datapath = ""; 13 | const shadow = this.attachShadow({ 14 | mode: 'open' 15 | }) 16 | } 17 | 18 | async connectedCallback() { 19 | let res = await fetch('./components/menuitem.html', {cache: "force-cache"}) 20 | var sr = this.shadowRoot; 21 | sr.innerHTML = await res.text(); 22 | this.showItem(); 23 | } 24 | 25 | showItem() { 26 | var sr = this.shadowRoot; 27 | var dish = sr.getElementById('dish'); 28 | var cost = sr.getElementById('cost'); 29 | 30 | var d = this.getAttribute('dish'); 31 | var c = this.getAttribute('cost'); 32 | 33 | dish.innerHTML = d; 34 | cost.innerHTML = '$' + c; 35 | 36 | var button = sr.getElementById('order'); 37 | button.onclick = this.placeOrder.bind(this); 38 | } 39 | 40 | storeOrder(order){ 41 | 42 | var orders = localStorage.getItem('KAFKA-ORDERS'); 43 | 44 | var orderlist; 45 | 46 | if(orders == null){ 47 | orderlist = []; 48 | }else{ 49 | orderlist = JSON.parse(orders); 50 | } 51 | 52 | orderlist.push(order); 53 | localStorage.setItem('KAFKA-ORDERS',JSON.stringify(orderlist)); 54 | } 55 | 56 | 57 | async placeOrder(e) { 58 | 59 | var dish = this.getAttribute('dish'); 60 | var cost = this.getAttribute('cost'); 61 | var type = this.getAttribute('type'); 62 | var restaurant = this.getAttribute('restaurant'); 63 | var kitchenId = this.getAttribute('kitchenId'); 64 | 65 | let userId = localStorage.getItem("userId") 66 | let orderinfo = { 67 | 'dish': dish, 68 | 'totalPrice': cost, 69 | 'restaurant': restaurant, 70 | 'type': type, 71 | kitchenId, 72 | userId, 73 | 'status': 'ORDERED' 74 | }; 75 | 76 | let createOrderRequest = await createOrder(orderinfo) 77 | if (createOrderRequest == "noop") { 78 | this.storeOrder(orderinfo); 79 | } else { 80 | if (createOrderRequest.status == "orderCreated") { 81 | console.log(createOrderRequest) 82 | this.storeOrder(orderinfo); // change orderinfo to backend data instead 83 | } 84 | } 85 | 86 | var component = this; 87 | 88 | /* Send an event for this order, so that other parts of the system can react */ 89 | 90 | var customEvent = new CustomEvent('ORDER-PLACED', { 91 | detail: { 92 | eventData: { 93 | "order": orderinfo 94 | } 95 | }, 96 | bubbles: true 97 | }); 98 | 99 | component.dispatchEvent(customEvent); 100 | 101 | console.log(orderinfo); 102 | 103 | } 104 | } 105 | 106 | 107 | try { 108 | customElements.define('menuitem-element', MenuItem); 109 | } catch (err) { 110 | const h3 = document.createElement('h3') 111 | h3.innerHTML = err 112 | document.body.appendChild(h3) 113 | } -------------------------------------------------------------------------------- /microservices/frontend/components/slider.html: -------------------------------------------------------------------------------- 1 | 2 | 129 | 130 |
131 |
132 | 133 |
134 | 135 |
136 | 137 |
138 | 139 |
140 | -------------------------------------------------------------------------------- /microservices/eem/exampleNotificationReceiver/__tests__/websocket-test.js: -------------------------------------------------------------------------------- 1 | const request = require("supertest"); 2 | const WebSocket = require('ws'); 3 | const {app, startServer} = require("../server"); 4 | 5 | jest.mock('ioredis', () => { 6 | const Redis = require('ioredis-mock'); 7 | 8 | if (typeof Redis === 'object') { 9 | // the first mock is an ioredis shim because ioredis-mock depends on it 10 | // https://github.com/stipsan/ioredis-mock/blob/master/src/index.js#L101-L111 11 | return { 12 | Command: { _transformer: { argument: {}, reply: {} } }, 13 | }; 14 | } 15 | 16 | let instance = null; 17 | 18 | // second mock for our code 19 | return function (...args) { 20 | if (instance) { 21 | return instance.createConnectedClient(); 22 | } 23 | 24 | instance = new Redis(args); 25 | 26 | return instance; 27 | }; 28 | }); 29 | 30 | const PORT = process.env.PORT || 8080 31 | 32 | function waitForSocketState(socket, state) { 33 | return new Promise(function (resolve) { 34 | setTimeout(function () { 35 | if (socket.readyState === state) { 36 | resolve(); 37 | } else { 38 | waitForSocketState(socket, state).then(resolve); 39 | } 40 | }, 5); 41 | }); 42 | } 43 | describe("Test API and Websocket", () => { 44 | let example_payload = { 45 | "payload": { 46 | "orderId": "b1e1a549-7187-443e-a9b6-cdc8f16679a9", 47 | "userId": "a0297e97-202a-44aa-b69e-40b6310c9c5f", 48 | "kitchenId": "e68a75fa-4861-4635-96f8-4e3ae2d14da2", 49 | "dish": "10 Alaska Rolls", 50 | "totalPrice": "30", 51 | "requestId": "da016387-bc4a-4631-93b9-7f3a16f8e815" 52 | } 53 | } 54 | 55 | let example_response = { 56 | status: 200, 57 | data: { 58 | status: "message_sent", 59 | message_sent: "A new order of " + example_payload.payload.dish + " has been placed." 60 | } 61 | } 62 | 63 | let example_request_body = { 64 | message: "A new order of " + example_payload.payload.dish + " has been placed.", 65 | kitchenId: example_payload.payload.kitchenId, 66 | payload: example_payload 67 | } 68 | 69 | let server; 70 | let client; 71 | 72 | beforeAll(async () => { 73 | server = await startServer(); 74 | }); 75 | 76 | test("Websocket client should receive message when POST '/callback' succeeds", async () => { 77 | 78 | client = new WebSocket(`ws://localhost:${PORT}`); 79 | await waitForSocketState(client, client.OPEN); 80 | let websocket_message; 81 | client.on("message", (data) => { 82 | websocket_message = data.toString(); 83 | client.close(); 84 | }); 85 | 86 | // Execute API /callback 87 | let response = await request(app) 88 | .post("/callback") 89 | .send(example_request_body) 90 | .set('Content-type', 'application/json'); 91 | 92 | 93 | expect(response.statusCode).toBe(200); 94 | expect(response.body).toEqual(example_response); 95 | await waitForSocketState(client, client.CLOSED); 96 | 97 | // Test received message from websocket here 98 | let parsed_message; 99 | expect(() => { 100 | try { 101 | parsed_message = JSON.parse(websocket_message); 102 | } catch (error) { 103 | throw new Error("Not JSON parsable: " + websocket_message + "\n Error Message: " + error.message) 104 | } 105 | }).not.toThrow(); 106 | expect(parsed_message).toEqual(example_response); 107 | }); 108 | 109 | afterAll((done) => { 110 | server.close(); 111 | done(); 112 | }); 113 | }) -------------------------------------------------------------------------------- /microservices/frontend/style/style.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --loyalty-background-color: white; 3 | --loyalty-app-color: white; 4 | --loyalty-text-color: #494A4B; 5 | --loyalty-button-color: #FF7E00; 6 | --loyalty-highlight-color: #FF002E; 7 | --loyalty-phone-color: #569BC6; 8 | --loyalty-sensor-color: #FFFFFF; 9 | --loyalty-divide-color: aliceblue; 10 | --loyalty-highlight-color: #fff2e5; 11 | } 12 | 13 | body { 14 | padding: 0; 15 | margin: 0; 16 | color: var(--lyrics-analysis-ink-color); 17 | font-family: 'Roboto', sans-serif; 18 | background: #2C3A42; 19 | /* font-family: 'IBM Plex Sans', sans-serif; */ 20 | } 21 | 22 | .panel { 23 | background: var(--farrow-panel-background); 24 | } 25 | 26 | .appname { 27 | /* font-weight:bold; */ 28 | color: white; 29 | } 30 | 31 | .info { 32 | /* font-weight:bold; */ 33 | color: white; 34 | } 35 | 36 | .container { 37 | padding: 0; 38 | margin: 0; 39 | /* #F5F8FA; #33729b; #9f805e; */ 40 | /* font-family: 'Open Sans', sans-serif;*/ 41 | /* font-family: 'IBM Plex Sans', sans-serif; */ 42 | /* font-family: 'Questrial', sans-serif; */ 43 | -webkit-font-smoothing: antialiased; 44 | display: flex; 45 | flex-direction: column; 46 | justify-content: center; 47 | align-items: center; 48 | background: var(--farrow-background-color); 49 | /* padding-bottom: 100px; */ 50 | } 51 | 52 | .grid-container { 53 | display: grid; 54 | grid-template-columns: 262px 600px; 55 | grid-template-rows: 80px 482px 325px; 56 | gap: 0px 0px; 57 | /* height:1000px; */ 58 | grid-template-areas: 59 | "Banner Banner" 60 | "Phone ." 61 | "Phone ."; 62 | } 63 | 64 | .Banner { 65 | grid-area: Banner; 66 | height: 0px; 67 | } 68 | 69 | .Phone { 70 | grid-area: Phone; 71 | /* padding: 20px;; */ 72 | } 73 | 74 | 75 | .bar { 76 | display: flex; 77 | flex-direction: row; 78 | justify-content: space-between; 79 | align-items: center; 80 | width: 100%; 81 | height: 80px; 82 | } 83 | 84 | .appname { 85 | /* margin: 20px; */ 86 | } 87 | 88 | .info { 89 | margin: 10px; 90 | } 91 | 92 | .architecture { 93 | /* margin:20px; */ 94 | /* border-radius:5px; 95 | border:1px solid var(--loyalty-phone-color); 96 | box-shadow: 0 5px 5px -3px rgb(0 0 0 / 20%), 0 8px 10px 1px rgb(0 0 0 / 14%), 0 3px 14px 2px rgb(0 0 0 / 12%); */ 97 | } 98 | 99 | .management { 100 | display: flex; 101 | flex-direction: row; 102 | } 103 | 104 | .connector { 105 | width: 90px; 106 | } 107 | 108 | .controls { 109 | border: 1px solid #ACC9D8; 110 | width: 238px; 111 | height: 280px; 112 | margin: 20px; 113 | margin-left: 0; 114 | } 115 | 116 | .analysis { 117 | border: 1px solid #ACC9D8; 118 | width: 238px; 119 | height: 280px; 120 | margin: 20px; 121 | margin-right: 0; 122 | margin-left: 0; 123 | display: flex; 124 | flex-direction: column; 125 | justify-content: center; 126 | align-items: center; 127 | } 128 | 129 | .linksarea { 130 | background: #f3f3f3; 131 | margin-top: 40px; 132 | height: 280px; 133 | border-radius: 4px; 134 | } 135 | 136 | .linkstitle { 137 | font-size: 12px; 138 | padding: 10px; 139 | padding-top: 12px; 140 | font-weight: bold; 141 | } 142 | 143 | .scalinglink { 144 | font-size: 12px; 145 | padding: 10px; 146 | } 147 | 148 | .controlbuttons { 149 | display: flex; 150 | flex-direction: row; 151 | justify-content: center; 152 | align-items: center; 153 | border-top: 1px dotted #ACC9D8; 154 | margin-top: 10px; 155 | height: 36px; 156 | } 157 | 158 | .controlbutton { 159 | background: #ACC9D8; 160 | border: none; 161 | font-size: 10px; 162 | margin: 10px; 163 | padding: 6px; 164 | border-radius: 4px; 165 | outline: none; 166 | cursor: pointer; 167 | } -------------------------------------------------------------------------------- /microservices/eem/eemSubscriber/dashboard/style/style.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --loyalty-background-color: white; 3 | --loyalty-app-color: white; 4 | --loyalty-text-color: #494A4B; 5 | --loyalty-button-color: #FF7E00; 6 | --loyalty-highlight-color: #FF002E; 7 | --loyalty-phone-color: #569BC6; 8 | --loyalty-sensor-color: #FFFFFF; 9 | --loyalty-divide-color: aliceblue; 10 | --loyalty-highlight-color: #fff2e5; 11 | } 12 | 13 | body { 14 | padding: 0; 15 | margin: 0; 16 | color: var(--lyrics-analysis-ink-color); 17 | font-family: 'Roboto', sans-serif; 18 | background: #2C3A42; 19 | /* font-family: 'IBM Plex Sans', sans-serif; */ 20 | } 21 | 22 | .panel { 23 | background: var(--farrow-panel-background); 24 | } 25 | 26 | .appname { 27 | /* font-weight:bold; */ 28 | color: white; 29 | } 30 | 31 | .info { 32 | /* font-weight:bold; */ 33 | color: white; 34 | } 35 | 36 | .container { 37 | padding: 0; 38 | margin: 0; 39 | /* #F5F8FA; #33729b; #9f805e; */ 40 | /* font-family: 'Open Sans', sans-serif;*/ 41 | /* font-family: 'IBM Plex Sans', sans-serif; */ 42 | /* font-family: 'Questrial', sans-serif; */ 43 | -webkit-font-smoothing: antialiased; 44 | display: flex; 45 | flex-direction: column; 46 | justify-content: center; 47 | align-items: center; 48 | background: var(--farrow-background-color); 49 | /* padding-bottom: 100px; */ 50 | } 51 | 52 | .grid-container { 53 | display: grid; 54 | grid-template-columns: 262px 600px; 55 | grid-template-rows: 80px 482px 325px; 56 | gap: 0px 0px; 57 | /* height:1000px; */ 58 | grid-template-areas: 59 | "Banner Banner" 60 | "Phone ." 61 | "Phone ."; 62 | } 63 | 64 | .Banner { 65 | grid-area: Banner; 66 | height: 0px; 67 | } 68 | 69 | .Phone { 70 | grid-area: Phone; 71 | /* padding: 20px;; */ 72 | } 73 | 74 | 75 | .bar { 76 | display: flex; 77 | flex-direction: row; 78 | justify-content: space-between; 79 | align-items: center; 80 | width: 100%; 81 | height: 80px; 82 | } 83 | 84 | .appname { 85 | /* margin: 20px; */ 86 | } 87 | 88 | .info { 89 | margin: 10px; 90 | } 91 | 92 | .architecture { 93 | /* margin:20px; */ 94 | /* border-radius:5px; 95 | border:1px solid var(--loyalty-phone-color); 96 | box-shadow: 0 5px 5px -3px rgb(0 0 0 / 20%), 0 8px 10px 1px rgb(0 0 0 / 14%), 0 3px 14px 2px rgb(0 0 0 / 12%); */ 97 | } 98 | 99 | .management { 100 | display: flex; 101 | flex-direction: row; 102 | } 103 | 104 | .connector { 105 | width: 90px; 106 | } 107 | 108 | .controls { 109 | border: 1px solid #ACC9D8; 110 | width: 238px; 111 | height: 280px; 112 | margin: 20px; 113 | margin-left: 0; 114 | } 115 | 116 | .analysis { 117 | border: 1px solid #ACC9D8; 118 | width: 238px; 119 | height: 280px; 120 | margin: 20px; 121 | margin-right: 0; 122 | margin-left: 0; 123 | display: flex; 124 | flex-direction: column; 125 | justify-content: center; 126 | align-items: center; 127 | } 128 | 129 | .linksarea { 130 | background: #f3f3f3; 131 | margin-top: 40px; 132 | height: 280px; 133 | border-radius: 4px; 134 | } 135 | 136 | .linkstitle { 137 | font-size: 12px; 138 | padding: 10px; 139 | padding-top: 12px; 140 | font-weight: bold; 141 | } 142 | 143 | .scalinglink { 144 | font-size: 12px; 145 | padding: 10px; 146 | } 147 | 148 | .controlbuttons { 149 | display: flex; 150 | flex-direction: row; 151 | justify-content: center; 152 | align-items: center; 153 | border-top: 1px dotted #ACC9D8; 154 | margin-top: 10px; 155 | height: 36px; 156 | } 157 | 158 | .controlbutton { 159 | background: #ACC9D8; 160 | border: none; 161 | font-size: 10px; 162 | margin: 10px; 163 | padding: 6px; 164 | border-radius: 4px; 165 | outline: none; 166 | cursor: pointer; 167 | } 168 | 169 | #webhookInput { 170 | width: 500px; 171 | } -------------------------------------------------------------------------------- /microservices/eem/exampleNotificationReceiver/__tests__/client-helper-tests.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @jest-environment jsdom 3 | */ 4 | 5 | const request = require("supertest"); 6 | const WebSocket = require('ws'); 7 | const {app, startServer} = require("../server"); 8 | const ClientHelper = require('../example-pos/clientHelper'); 9 | 10 | jest.mock('ioredis', () => { 11 | const Redis = require('ioredis-mock'); 12 | 13 | if (typeof Redis === 'object') { 14 | // the first mock is an ioredis shim because ioredis-mock depends on it 15 | // https://github.com/stipsan/ioredis-mock/blob/master/src/index.js#L101-L111 16 | return { 17 | Command: { _transformer: { argument: {}, reply: {} } }, 18 | }; 19 | } 20 | 21 | let instance = null; 22 | 23 | // second mock for our code 24 | return function (...args) { 25 | if (instance) { 26 | return instance.createConnectedClient(); 27 | } 28 | 29 | instance = new Redis(args); 30 | 31 | return instance; 32 | }; 33 | }); 34 | 35 | const PORT = process.env.PORT || 8080 36 | 37 | function waitForSocketState(socket, state) { 38 | return new Promise(function (resolve) { 39 | setTimeout(function () { 40 | if (socket.readyState === state) { 41 | resolve(); 42 | } else { 43 | waitForSocketState(socket, state).then(resolve); 44 | } 45 | }, 5); 46 | }); 47 | } 48 | describe("Test Websocket and helper functions", () => { 49 | let example_payload = { 50 | "payload": { 51 | "orderId": "b1e1a549-7187-443e-a9b6-cdc8f16679a9", 52 | "userId": "a0297e97-202a-44aa-b69e-40b6310c9c5f", 53 | "kitchenId": "e68a75fa-4861-4635-96f8-4e3ae2d14da2", 54 | "dish": "10 Alaska Rolls", 55 | "totalPrice": "30", 56 | "requestId": "da016387-bc4a-4631-93b9-7f3a16f8e815" 57 | } 58 | } 59 | 60 | let example_response = { 61 | status: 200, 62 | data: { 63 | status: "message_sent", 64 | message_sent: "A new order of " + example_payload.payload.dish + " has been placed." 65 | } 66 | } 67 | 68 | let example_request_body = { 69 | message: "A new order of " + example_payload.payload.dish + " has been placed.", 70 | kitchenId: example_payload.payload.kitchenId, 71 | payload: example_payload 72 | } 73 | 74 | let server; 75 | let client; 76 | 77 | test("Successfully process valid message from server", () => { 78 | let processedMessage = ClientHelper.processMessage(example_response); 79 | expect(processedMessage).toEqual({ 80 | status: true, 81 | message: example_response.data.message_sent 82 | }); 83 | }); 84 | 85 | test("should fail to process unknown message from server", () => { 86 | let unknown_message = { foo: "bar" }; 87 | let processedMessage = ClientHelper.processMessage(unknown_message) 88 | expect(processedMessage).toEqual({ 89 | status: false, 90 | reason: 'Unable to process message received from server.', 91 | message: unknown_message 92 | }); 93 | }); 94 | 95 | test("showMessageDOM should show notification in DOM", () => { 96 | document.body.innerHTML = ` 97 |
98 |
99 | `; 100 | 101 | const notificationContainer = document.getElementById('notificationArea'); 102 | let example_notification = "A new order of " + example_payload.payload.dish + " has been placed." 103 | ClientHelper.showMessageDOM(notificationContainer, example_notification); 104 | let divNotification = document.createElement('div'); 105 | divNotification.className = "notificationText" 106 | divNotification.innerHTML = example_notification; 107 | 108 | expect(notificationContainer.children[0]).toEqual(divNotification); 109 | }) 110 | }) -------------------------------------------------------------------------------- /microservices/realtimedata/KafkaWrapper.js: -------------------------------------------------------------------------------- 1 | const Kafka = require('node-rdkafka'); 2 | 3 | const { Kafka: KafkaJS } = require('kafkajs') 4 | 5 | class KafkaWrapper { 6 | constructor(brokers, protocol, mechanism, username, password) { 7 | // ibm cloud service credentials 8 | // let jsonCredentials = JSON.parse(ibmcloud_credentials) 9 | // let brokers = jsonCredentials.kafka_brokers_sasl 10 | // let apiKey = jsonCredentials.api_key 11 | // producer 12 | // let driver_options = { 13 | // //'debug': 'all', 14 | // 'metadata.broker.list': brokers, 15 | // 'security.protocol': 'SASL_SSL', 16 | // 'sasl.mechanisms': 'PLAIN', 17 | // 'sasl.username': 'token', 18 | // 'sasl.password': apiKey, 19 | // 'log.connection.close' : false, 20 | // 'enable.auto.commit': false, 21 | // 'statistics.interval.ms': 1000 22 | // }; 23 | let driver_options = { 24 | //'debug': 'all', 25 | 'metadata.broker.list': brokers, 26 | 'security.protocol': protocol, 27 | 'sasl.mechanisms': mechanism, 28 | 'sasl.username': username, 29 | 'sasl.password': password, 30 | 'log.connection.close' : false, 31 | 'enable.auto.commit': false, 32 | 'statistics.interval.ms': 1000 33 | }; 34 | let consumerConfig = { 35 | 'client.id': 'realtimedata-consumer', 36 | 'group.id': 'realtimedata-consumer-group', 37 | } 38 | 39 | for (var key in driver_options) { 40 | consumerConfig[key] = driver_options[key] 41 | } 42 | 43 | // create kafka consumer 44 | let topicConfig = { 45 | 'auto.offset.reset': 'earliest' 46 | } 47 | let consumer = new Kafka.KafkaConsumer(consumerConfig, topicConfig) 48 | 49 | // Register error listener 50 | consumer.on('event.error', function(err) { 51 | console.error('Error from consumer:' + JSON.stringify(err)); 52 | }); 53 | let prevCommitted = 0 54 | // Register stats listener 55 | // consumer.on('event.stats', function(log) { 56 | // console.log('Log from consumer:'); 57 | // console.log(JSON.parse(log.message)) 58 | 59 | // let stats = JSON.parse(log.message) 60 | // // console.log(stats) 61 | // if (stats.topics['orders']) { 62 | // let partitionStats = stats.topics.orders.partitions['0'] 63 | // // console.log(stats.topics.orders.partitions['0']) 64 | // let commitPerSecond = 0 65 | // if (prevCommitted) { 66 | // commitPerSecond = partitionStats.committed_offset - prevCommitted 67 | // } 68 | // if (partitionStats.consumer_lag) { 69 | // // console.log('consumer lag: ' + partitionStats.consumer_lag) 70 | // } 71 | // // console.log(commitPerSecond) 72 | // prevCommitted = partitionStats.committed_offset 73 | // } 74 | // }); 75 | 76 | this.consumer = consumer 77 | 78 | // KafkaJS admin client 79 | let adminKafka = new KafkaJS({ 80 | clientId: 'admin', 81 | brokers: brokers.split(','), 82 | ssl: true, 83 | sasl: { 84 | mechanism, 85 | username, 86 | password 87 | } 88 | }).admin() 89 | this.admin = adminKafka 90 | } 91 | 92 | on(event, callback) { 93 | this.consumer.on(event, callback) 94 | } 95 | } 96 | // const kafkaWrapper = new KafkaWrapper(process.env.KAFKA_CREDENTIALS) 97 | const kafkaWrapper = new KafkaWrapper(process.env.BOOTSTRAP_SERVERS, 98 | process.env.SECURITY_PROTOCOL, 99 | process.env.SASL_MECHANISMS, 100 | process.env.SASL_USERNAME, 101 | process.env.SASL_PASSWORD) 102 | Object.freeze(kafkaWrapper) 103 | 104 | module.exports = kafkaWrapper -------------------------------------------------------------------------------- /microservices/eem/exampleNotificationReceiver/example-pos/style/style.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --loyalty-background-color: white; 3 | --loyalty-app-color: white; 4 | --loyalty-text-color: #494A4B; 5 | --loyalty-button-color: #FF7E00; 6 | --loyalty-highlight-color: #FF002E; 7 | --loyalty-phone-color: #569BC6; 8 | --loyalty-sensor-color: #FFFFFF; 9 | --loyalty-divide-color: aliceblue; 10 | --loyalty-highlight-color: #fff2e5; 11 | } 12 | 13 | body { 14 | padding: 0; 15 | margin: 0; 16 | color: var(--lyrics-analysis-ink-color); 17 | font-family: 'Roboto', sans-serif; 18 | background: #2C3A42; 19 | /* font-family: 'IBM Plex Sans', sans-serif; */ 20 | } 21 | 22 | .panel { 23 | background: var(--farrow-panel-background); 24 | } 25 | 26 | .appname { 27 | /* font-weight:bold; */ 28 | color: white; 29 | } 30 | 31 | .info { 32 | /* font-weight:bold; */ 33 | color: white; 34 | } 35 | 36 | .container { 37 | padding: 0; 38 | margin: 0; 39 | /* #F5F8FA; #33729b; #9f805e; */ 40 | /* font-family: 'Open Sans', sans-serif;*/ 41 | /* font-family: 'IBM Plex Sans', sans-serif; */ 42 | /* font-family: 'Questrial', sans-serif; */ 43 | -webkit-font-smoothing: antialiased; 44 | display: flex; 45 | flex-direction: column; 46 | justify-content: center; 47 | align-items: center; 48 | background: var(--farrow-background-color); 49 | /* padding-bottom: 100px; */ 50 | } 51 | 52 | .grid-container { 53 | display: grid; 54 | grid-template-columns: 262px 600px; 55 | grid-template-rows: 80px 482px 325px; 56 | gap: 0px 0px; 57 | /* height:1000px; */ 58 | grid-template-areas: 59 | "Banner Banner" 60 | "Phone ." 61 | "Phone ."; 62 | } 63 | 64 | .Banner { 65 | grid-area: Banner; 66 | height: 0px; 67 | } 68 | 69 | .Phone { 70 | grid-area: Phone; 71 | /* padding: 20px;; */ 72 | } 73 | 74 | 75 | .bar { 76 | display: flex; 77 | flex-direction: row; 78 | justify-content: space-between; 79 | align-items: center; 80 | width: 100%; 81 | height: 80px; 82 | } 83 | 84 | .appname { 85 | /* margin: 20px; */ 86 | } 87 | 88 | .info { 89 | margin: 10px; 90 | } 91 | 92 | .architecture { 93 | /* margin:20px; */ 94 | /* border-radius:5px; 95 | border:1px solid var(--loyalty-phone-color); 96 | box-shadow: 0 5px 5px -3px rgb(0 0 0 / 20%), 0 8px 10px 1px rgb(0 0 0 / 14%), 0 3px 14px 2px rgb(0 0 0 / 12%); */ 97 | } 98 | 99 | .management { 100 | display: flex; 101 | flex-direction: row; 102 | } 103 | 104 | .connector { 105 | width: 90px; 106 | } 107 | 108 | .controls { 109 | border: 1px solid #ACC9D8; 110 | width: 238px; 111 | height: 280px; 112 | margin: 20px; 113 | margin-left: 0; 114 | } 115 | 116 | .analysis { 117 | border: 1px solid #ACC9D8; 118 | width: 238px; 119 | height: 280px; 120 | margin: 20px; 121 | margin-right: 0; 122 | margin-left: 0; 123 | display: flex; 124 | flex-direction: column; 125 | justify-content: center; 126 | align-items: center; 127 | } 128 | 129 | .linksarea { 130 | background: #f3f3f3; 131 | margin-top: 40px; 132 | height: 280px; 133 | border-radius: 4px; 134 | } 135 | 136 | .linkstitle { 137 | font-size: 12px; 138 | padding: 10px; 139 | padding-top: 12px; 140 | font-weight: bold; 141 | } 142 | 143 | .scalinglink { 144 | font-size: 12px; 145 | padding: 10px; 146 | } 147 | 148 | .controlbuttons { 149 | display: flex; 150 | flex-direction: row; 151 | justify-content: center; 152 | align-items: center; 153 | border-top: 1px dotted #ACC9D8; 154 | margin-top: 10px; 155 | height: 36px; 156 | } 157 | 158 | .controlbutton { 159 | background: #ACC9D8; 160 | border: none; 161 | font-size: 10px; 162 | margin: 10px; 163 | padding: 6px; 164 | border-radius: 4px; 165 | outline: none; 166 | cursor: pointer; 167 | } 168 | 169 | #notificationArea { 170 | display: flex; 171 | flex-direction: row; 172 | flex-wrap: wrap; 173 | width: 862px; 174 | } 175 | 176 | .notificationText { 177 | color: #FFF; 178 | background-color: #455b68; 179 | max-width: 250px; 180 | margin: 10px; 181 | overflow: scroll; 182 | -ms-overflow-style: none; 183 | scrollbar-width: none; 184 | padding: 10px; 185 | border-radius: 20px 10px; 186 | } 187 | 188 | .notificationText::-webkit-scrollbar { 189 | display: none; 190 | } 191 | -------------------------------------------------------------------------------- /microservices/eem/eemSubscriber/KafkaWrapper.js: -------------------------------------------------------------------------------- 1 | const Kafka = require('node-rdkafka'); 2 | 3 | class KafkaWrapper { 4 | constructor(brokers, protocol, mechanism, username, password, clientId, certificateLocation) { 5 | let driver_options = { 6 | //'debug': 'all', 7 | 'metadata.broker.list': brokers, 8 | 'security.protocol': protocol, 9 | 'sasl.mechanisms': mechanism, 10 | 'sasl.username': username, 11 | 'sasl.password': password, 12 | 'log.connection.close' : false, 13 | 'enable.auto.commit': false 14 | }; 15 | 16 | if (certificateLocation) { 17 | driver_options['ssl.ca.location'] = certificateLocation; 18 | } 19 | 20 | let consumerConfig = { 21 | 'client.id': '2c04144b-01c5-454f-9eba-45bac93dcfc2', 22 | 'group.id': 'eem-subscriber-consumer-group', 23 | } 24 | 25 | if (clientId) { 26 | driver_options['client.id'] = clientId; 27 | } 28 | 29 | for (var key in driver_options) { 30 | consumerConfig[key] = driver_options[key] 31 | } 32 | 33 | // create kafka consumer 34 | let topicConfig = { 35 | 'auto.offset.reset': 'earliest' 36 | } 37 | let consumer = new Kafka.KafkaConsumer(consumerConfig, topicConfig) 38 | 39 | // Register error listener 40 | consumer.on('event.error', function(err) { 41 | console.error('Error from consumer:' + JSON.stringify(err)); 42 | }); 43 | 44 | this.consumer = consumer 45 | 46 | // producer 47 | let producerConfig = { 48 | 'client.id': 'example-eem-subscriber', 49 | 'dr_msg_cb': true // Enable delivery reports with message payload 50 | } 51 | 52 | for (var key in driver_options) { 53 | producerConfig[key] = driver_options[key] 54 | } 55 | 56 | // create kafka producer 57 | let producerTopicConfig = { 58 | 'request.required.acks': -1 59 | // 'produce.offset.report': true 60 | } 61 | let producer = new Kafka.Producer(producerConfig, producerTopicConfig) 62 | producer.setPollInterval(100) 63 | 64 | // debug 65 | // producer.on('event.log', function(log) { 66 | // console.log(log); 67 | // }); 68 | 69 | // Register error listener 70 | producer.on('event.error', function(err) { 71 | console.error('Error from producer:' + JSON.stringify(err)); 72 | }); 73 | 74 | // Register delivery report listener 75 | producer.on('delivery-report', function(err, dr) { 76 | if (err) { 77 | console.error('Delivery report: Failed sending message ' + dr.value); 78 | console.error(err); 79 | // We could retry sending the message 80 | } else { 81 | console.log('Message produced, partition: ' + dr.partition + ' offset: ' + dr.offset); 82 | } 83 | }); 84 | producer.connect() 85 | 86 | this.producer = producer 87 | } 88 | 89 | on(event, callback) { 90 | this.consumer.on(event, callback) 91 | } 92 | 93 | createEvent(order, event, simulatorConfig, callback) { 94 | let topicName = 'orders' 95 | let eventType = event 96 | let message = Buffer.from(JSON.stringify({eventType, payload: order, simulatorConfig})) 97 | try { 98 | this.producer.produce( 99 | topicName, 100 | null, 101 | message, 102 | order.orderId 103 | ) 104 | callback(null) 105 | } catch (err) { 106 | console.log('caught') 107 | callback(err) 108 | } 109 | } 110 | } 111 | 112 | // const kafkaWrapper = new KafkaWrapper(process.env.KAFKA_CREDENTIALS) 113 | const kafkaWrapper = new KafkaWrapper(process.env.BOOTSTRAP_SERVERS, 114 | process.env.SECURITY_PROTOCOL, 115 | process.env.SASL_MECHANISMS, 116 | process.env.SASL_USERNAME, 117 | process.env.SASL_PASSWORD, 118 | process.env.CLIENT_ID, 119 | process.env.CA_LOCATION) 120 | Object.freeze(kafkaWrapper) 121 | 122 | module.exports = kafkaWrapper 123 | -------------------------------------------------------------------------------- /microservices/frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Scaling Applications With Kafka 5 | 6 | 9 | 10 | 11 | 12 |
13 |
14 | 20 |
21 |
22 | 23 | 32 |
33 |
34 |
Scaling OpenShift Apps With Kafka & Mongo
35 | 36 | 37 | 38 | 39 | 40 |
41 |
42 |
43 | 44 |
45 | 46 |
47 | 48 | 49 | 50 | 51 |
52 | 53 | 54 |
55 |
56 |
57 | 58 |
59 |
60 |
61 |
62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /microservices/frontend/components/receipt.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 162 | 163 |
164 |
165 | 166 | 167 | 168 | 169 |
170 |
171 |
172 |
173 |
174 |
175 | -------------------------------------------------------------------------------- /microservices/eem/exampleNotificationReceiver/__tests__/server-api-tests.js: -------------------------------------------------------------------------------- 1 | const request = require("supertest"); 2 | const {app, redis} = require("../app"); 3 | 4 | jest.mock('ioredis', () => { 5 | const Redis = require('ioredis-mock'); 6 | 7 | if (typeof Redis === 'object') { 8 | // the first mock is an ioredis shim because ioredis-mock depends on it 9 | // https://github.com/stipsan/ioredis-mock/blob/master/src/index.js#L101-L111 10 | return { 11 | Command: { _transformer: { argument: {}, reply: {} } }, 12 | }; 13 | } 14 | 15 | let instance = null; 16 | 17 | // second mock for our code 18 | return function (...args) { 19 | if (instance) { 20 | return instance.createConnectedClient(); 21 | } 22 | 23 | instance = new Redis(args); 24 | 25 | return instance; 26 | }; 27 | }); 28 | 29 | describe("Test REST APIs", () => { 30 | let example_payload = { 31 | "payload": { 32 | "orderId": "b1e1a549-7187-443e-a9b6-cdc8f16679a9", 33 | "userId": "a0297e97-202a-44aa-b69e-40b6310c9c5f", 34 | "kitchenId": "e68a75fa-4861-4635-96f8-4e3ae2d14da2", 35 | "dish": "10 Alaska Rolls", 36 | "totalPrice": "30", 37 | "requestId": "da016387-bc4a-4631-93b9-7f3a16f8e815" 38 | } 39 | } 40 | 41 | let example_response = { 42 | status: 200, 43 | data: { 44 | status: "message_sent", 45 | message_sent: "A new order of " + example_payload.payload.dish + " has been placed." 46 | } 47 | } 48 | 49 | let example_request_body = { 50 | message: "A new order of " + example_payload.payload.dish + " has been placed.", 51 | kitchenId: example_payload.payload.kitchenId, 52 | payload: example_payload 53 | } 54 | 55 | test("POST '/callback' with VALID request body should return 200 example_response (orderCreated)", async () => { 56 | let response = await request(app) 57 | .post("/callback") 58 | .send(example_request_body) 59 | .set('Content-type', 'application/json'); 60 | 61 | expect(response.statusCode).toBe(200); 62 | expect(response.body).toEqual(example_response); 63 | }); 64 | 65 | 66 | test("POST '/callback' with VALID request body should return 200 example_response (delivered)", async () => { 67 | 68 | example_request_body.message = "Delivered! " + example_payload.payload.dish + " has been delivered."; 69 | example_response.data.message_sent = example_request_body.message; 70 | let response = await request(app) 71 | .post("/callback") 72 | .send(example_request_body) 73 | .set('Content-type', 'application/json'); 74 | 75 | expect(response.statusCode).toBe(200); 76 | expect(response.body).toEqual(example_response); 77 | }); 78 | 79 | test("POST '/callback' with INVALID request body should return 404", async () => { 80 | let response = await request(app) 81 | .post("/callback") 82 | .send({foo: "bar"}) 83 | .set('Content-type', 'application/json'); 84 | 85 | expect(response.statusCode).toBe(404); 86 | expect(response.text).toBe("Invalid Request body."); 87 | }); 88 | }); 89 | 90 | describe("Test API and Redis subscribe", () => { 91 | let example_payload = { 92 | "payload": { 93 | "orderId": "b1e1a549-7187-443e-a9b6-cdc8f16679a9", 94 | "userId": "a0297e97-202a-44aa-b69e-40b6310c9c5f", 95 | "kitchenId": "e68a75fa-4861-4635-96f8-4e3ae2d14da2", 96 | "dish": "10 Alaska Rolls", 97 | "totalPrice": "30", 98 | "requestId": "da016387-bc4a-4631-93b9-7f3a16f8e815" 99 | } 100 | } 101 | 102 | let example_response = { 103 | status: 200, 104 | data: { 105 | status: "message_sent", 106 | message_sent: "A new order of " + example_payload.payload.dish + " has been placed." 107 | } 108 | } 109 | 110 | let example_request_body = { 111 | message: "A new order of " + example_payload.payload.dish + " has been placed.", 112 | kitchenId: example_payload.payload.kitchenId, 113 | payload: example_payload 114 | } 115 | test("Redis subscription should receive message when POST '/callback' succeeds", async () => { 116 | let subscriber = redis.createConnectedClient(); 117 | subscriber.on('message', (channel, message) => { 118 | expect(JSON.parse(message)).toEqual(example_response); 119 | }); 120 | subscriber.subscribe("notification-pubsub"); 121 | 122 | let response = await request(app) 123 | .post("/callback") 124 | .send(example_request_body) 125 | .set('Content-type', 'application/json'); 126 | 127 | 128 | expect(response.statusCode).toBe(200); 129 | expect(response.body).toEqual(example_response); 130 | }); 131 | }) -------------------------------------------------------------------------------- /microservices/statusService/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "orderconsumer", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "bindings": { 8 | "version": "1.5.0", 9 | "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", 10 | "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", 11 | "requires": { 12 | "file-uri-to-path": "1.0.0" 13 | } 14 | }, 15 | "cluster-key-slot": { 16 | "version": "1.1.0", 17 | "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz", 18 | "integrity": "sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw==" 19 | }, 20 | "denque": { 21 | "version": "1.5.0", 22 | "resolved": "https://registry.npmjs.org/denque/-/denque-1.5.0.tgz", 23 | "integrity": "sha512-CYiCSgIF1p6EUByQPlGkKnP1M9g0ZV3qMIrqMqZqdwazygIA/YP2vrbcyl1h/WppKJTdl1F85cXIle+394iDAQ==" 24 | }, 25 | "file-uri-to-path": { 26 | "version": "1.0.0", 27 | "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", 28 | "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" 29 | }, 30 | "ioredis": { 31 | "version": "4.22.0", 32 | "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-4.22.0.tgz", 33 | "integrity": "sha512-mtC+jNFMPRxReWx0HodDbcwj34Gj5pK/P4+aE6Nh0pdqgtZKvxUh4z2lVtLjqnRIvMhKaBnIgMYFR8qH/xtttA==", 34 | "requires": { 35 | "cluster-key-slot": "^1.1.0", 36 | "debug": "^4.1.1", 37 | "denque": "^1.1.0", 38 | "lodash.defaults": "^4.2.0", 39 | "lodash.flatten": "^4.4.0", 40 | "p-map": "^2.1.0", 41 | "redis-commands": "1.7.0", 42 | "redis-errors": "^1.2.0", 43 | "redis-parser": "^3.0.0", 44 | "standard-as-callback": "^2.0.1" 45 | }, 46 | "dependencies": { 47 | "debug": { 48 | "version": "4.3.1", 49 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", 50 | "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", 51 | "requires": { 52 | "ms": "2.1.2" 53 | } 54 | } 55 | } 56 | }, 57 | "lodash.defaults": { 58 | "version": "4.2.0", 59 | "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", 60 | "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=" 61 | }, 62 | "lodash.flatten": { 63 | "version": "4.4.0", 64 | "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", 65 | "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=" 66 | }, 67 | "ms": { 68 | "version": "2.1.2", 69 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 70 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 71 | }, 72 | "nan": { 73 | "version": "2.14.2", 74 | "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz", 75 | "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==" 76 | }, 77 | "node-rdkafka": { 78 | "version": "2.10.1", 79 | "resolved": "https://registry.npmjs.org/node-rdkafka/-/node-rdkafka-2.10.1.tgz", 80 | "integrity": "sha512-yRb9Y90ipef4X+S/UbvQedUNtKZONa9RR6hCpAaGD83NqUga/uxTofdRQG8bm7SEh/DNuaifIJjRzLcoG9nUSQ==", 81 | "requires": { 82 | "bindings": "^1.3.1", 83 | "nan": "^2.14.0" 84 | } 85 | }, 86 | "p-map": { 87 | "version": "2.1.0", 88 | "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", 89 | "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==" 90 | }, 91 | "redis-commands": { 92 | "version": "1.7.0", 93 | "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.7.0.tgz", 94 | "integrity": "sha512-nJWqw3bTFy21hX/CPKHth6sfhZbdiHP6bTawSgQBlKOVRG7EZkfHbbHwQJnrE4vsQf0CMNE+3gJ4Fmm16vdVlQ==" 95 | }, 96 | "redis-errors": { 97 | "version": "1.2.0", 98 | "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", 99 | "integrity": "sha1-62LSrbFeTq9GEMBK/hUpOEJQq60=" 100 | }, 101 | "redis-parser": { 102 | "version": "3.0.0", 103 | "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", 104 | "integrity": "sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ=", 105 | "requires": { 106 | "redis-errors": "^1.0.0" 107 | } 108 | }, 109 | "standard-as-callback": { 110 | "version": "2.0.1", 111 | "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.0.1.tgz", 112 | "integrity": "sha512-NQOxSeB8gOI5WjSaxjBgog2QFw55FV8TkS6Y07BiB3VJ8xNTvUYm0wl0s8ObgQ5NhdpnNfigMIKjgPESzgr4tg==" 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /microservices/consumers/courierConsumer/KafkaWrapper.js: -------------------------------------------------------------------------------- 1 | const Kafka = require('node-rdkafka'); 2 | 3 | class KafkaWrapper { 4 | constructor(brokers, protocol, mechanism, username, password) { 5 | // ibm cloud service credentials 6 | // let jsonCredentials = JSON.parse(ibmcloud_credentials) 7 | // let brokers = jsonCredentials.kafka_brokers_sasl 8 | // let apiKey = jsonCredentials.api_key 9 | // producer 10 | // let driver_options = { 11 | // //'debug': 'all', 12 | // 'metadata.broker.list': brokers, 13 | // 'security.protocol': 'SASL_SSL', 14 | // 'sasl.mechanisms': 'PLAIN', 15 | // 'sasl.username': 'token', 16 | // 'sasl.password': apiKey, 17 | // 'log.connection.close' : false, 18 | // 'enable.auto.commit': false 19 | // }; 20 | let driver_options = { 21 | //'debug': 'all', 22 | 'metadata.broker.list': brokers, 23 | 'security.protocol': protocol, 24 | 'sasl.mechanisms': mechanism, 25 | 'sasl.username': username, 26 | 'sasl.password': password, 27 | 'log.connection.close' : false, 28 | 'enable.auto.commit': false 29 | }; 30 | let consumerConfig = { 31 | 'client.id': 'courier-consumer', 32 | 'group.id': 'courier-consumer-group', 33 | } 34 | 35 | for (var key in driver_options) { 36 | consumerConfig[key] = driver_options[key] 37 | } 38 | 39 | // create kafka consumer 40 | let topicConfig = { 41 | 'auto.offset.reset': 'earliest' 42 | } 43 | let consumer = new Kafka.KafkaConsumer(consumerConfig, topicConfig) 44 | 45 | // Register error listener 46 | consumer.on('event.error', function(err) { 47 | console.error('Error from consumer:' + JSON.stringify(err)); 48 | }); 49 | 50 | this.consumer = consumer 51 | 52 | // producer 53 | let producerConfig = { 54 | 'client.id': 'example-orders-producer', 55 | 'dr_msg_cb': true // Enable delivery reports with message payload 56 | } 57 | 58 | for (var key in driver_options) { 59 | producerConfig[key] = driver_options[key] 60 | } 61 | 62 | // create kafka producer 63 | let producerTopicConfig = { 64 | 'request.required.acks': -1 65 | // 'produce.offset.report': true 66 | } 67 | let producer = new Kafka.Producer(producerConfig, producerTopicConfig) 68 | producer.setPollInterval(100) 69 | 70 | // debug 71 | // producer.on('event.log', function(log) { 72 | // console.log(log); 73 | // }); 74 | 75 | // Register error listener 76 | producer.on('event.error', function(err) { 77 | console.error('Error from producer:' + JSON.stringify(err)); 78 | }); 79 | 80 | // Register delivery report listener 81 | producer.on('delivery-report', function(err, dr) { 82 | if (err) { 83 | console.error('Delivery report: Failed sending message ' + dr.value); 84 | console.error(err); 85 | // We could retry sending the message 86 | } else { 87 | console.log('Message produced, partition: ' + dr.partition + ' offset: ' + dr.offset); 88 | } 89 | }); 90 | producer.connect() 91 | 92 | this.producer = producer 93 | } 94 | 95 | on(event, callback) { 96 | this.consumer.on(event, callback) 97 | } 98 | 99 | createEvent(order, event, simulatorConfig, callback) { 100 | let topicName = 'orders' 101 | let eventType = event 102 | let message = Buffer.from(JSON.stringify({eventType, payload: order, simulatorConfig})) 103 | try { 104 | this.producer.produce( 105 | topicName, 106 | null, 107 | message, 108 | order.orderId 109 | ) 110 | callback(null) 111 | } catch (err) { 112 | console.log('caught') 113 | callback(err) 114 | } 115 | } 116 | 117 | courierMatchedEvent(order, simulatorConfig, callback) { 118 | this.createEvent(order, 'courierMatched', simulatorConfig, callback) 119 | } 120 | 121 | courierPickedUpEvent(order, simulatorConfig, callback) { 122 | this.createEvent(order, 'courierPickedUp', simulatorConfig, callback) 123 | } 124 | 125 | deliveredEvent(order, simulatorConfig, callback) { 126 | this.createEvent(order, 'delivered', simulatorConfig, callback) 127 | } 128 | } 129 | 130 | // const kafkaWrapper = new KafkaWrapper(process.env.KAFKA_CREDENTIALS) 131 | const kafkaWrapper = new KafkaWrapper(process.env.BOOTSTRAP_SERVERS, 132 | process.env.SECURITY_PROTOCOL, 133 | process.env.SASL_MECHANISMS, 134 | process.env.SASL_USERNAME, 135 | process.env.SASL_PASSWORD) 136 | Object.freeze(kafkaWrapper) 137 | 138 | module.exports = kafkaWrapper -------------------------------------------------------------------------------- /microservices/consumers/orderConsumer/KafkaWrapper.js: -------------------------------------------------------------------------------- 1 | const Kafka = require('node-rdkafka'); 2 | 3 | class KafkaWrapper { 4 | constructor(brokers, protocol, mechanism, username, password) { 5 | // ibm cloud service credentials 6 | // let jsonCredentials = JSON.parse(ibmcloud_credentials) 7 | // let brokers = jsonCredentials.kafka_brokers_sasl 8 | // let apiKey = jsonCredentials.api_key 9 | // producer 10 | // let driver_options = { 11 | // //'debug': 'all', 12 | // 'metadata.broker.list': brokers, 13 | // 'security.protocol': 'SASL_SSL', 14 | // 'sasl.mechanisms': 'PLAIN', 15 | // 'sasl.username': 'token', 16 | // 'sasl.password': apiKey, 17 | // 'log.connection.close' : false, 18 | // 'enable.auto.commit': false 19 | // }; 20 | let driver_options = { 21 | //'debug': 'all', 22 | 'metadata.broker.list': brokers, 23 | 'security.protocol': protocol, 24 | 'sasl.mechanisms': mechanism, 25 | 'sasl.username': username, 26 | 'sasl.password': password, 27 | 'log.connection.close' : false, 28 | 'enable.auto.commit': false 29 | }; 30 | let consumerConfig = { 31 | 'client.id': 'orders-consumer', 32 | 'group.id': 'orders-consumer-group', 33 | } 34 | 35 | for (var key in driver_options) { 36 | consumerConfig[key] = driver_options[key] 37 | } 38 | 39 | // create kafka consumer 40 | let topicConfig = { 41 | 'auto.offset.reset': 'earliest' 42 | } 43 | let consumer = new Kafka.KafkaConsumer(consumerConfig, topicConfig) 44 | 45 | // Register error listener 46 | consumer.on('event.error', function(err) { 47 | console.error('Error from consumer:' + JSON.stringify(err)); 48 | }); 49 | 50 | this.consumer = consumer 51 | 52 | // producer 53 | let producerConfig = { 54 | 'client.id': 'example-orders-producer', 55 | 'dr_msg_cb': true // Enable delivery reports with message payload 56 | } 57 | 58 | for (var key in driver_options) { 59 | producerConfig[key] = driver_options[key] 60 | } 61 | 62 | // create kafka producer 63 | let producerTopicConfig = { 64 | 'request.required.acks': -1 65 | // 'produce.offset.report': true 66 | } 67 | let producer = new Kafka.Producer(producerConfig, producerTopicConfig) 68 | producer.setPollInterval(100) 69 | 70 | // debug 71 | // producer.on('event.log', function(log) { 72 | // console.log(log); 73 | // }); 74 | 75 | // Register error listener 76 | producer.on('event.error', function(err) { 77 | console.error('Error from producer:' + JSON.stringify(err)); 78 | }); 79 | 80 | // Register delivery report listener 81 | producer.on('delivery-report', function(err, dr) { 82 | if (err) { 83 | console.error('Delivery report: Failed sending message ' + dr.value); 84 | console.error(err); 85 | // We could retry sending the message 86 | } else { 87 | console.log('Message produced, partition: ' + dr.partition + ' offset: ' + dr.offset); 88 | } 89 | }); 90 | producer.connect() 91 | 92 | this.producer = producer 93 | } 94 | 95 | on(event, callback) { 96 | this.consumer.on(event, callback) 97 | } 98 | 99 | createEvent(order, event, simulatorConfig, callback) { 100 | let topicName = 'orders' 101 | let eventType = event 102 | let message = Buffer.from(JSON.stringify({eventType, payload: order, simulatorConfig})) 103 | try { 104 | this.producer.produce( 105 | topicName, 106 | null, 107 | message, 108 | order.orderId 109 | ) 110 | callback(null) 111 | } catch (err) { 112 | console.log('caught') 113 | callback(err) 114 | } 115 | } 116 | 117 | createdOrderEvent(order, simulatorConfig, callback) { 118 | this.createEvent(order, 'orderCreated', simulatorConfig, callback) 119 | } 120 | 121 | validatedEvent(order, simulatorConfig, callback) { 122 | this.createEvent(order, 'orderValidated', simulatorConfig, callback) 123 | } 124 | 125 | updateHttpResponse(payload, simulatorConfig, callback) { 126 | this.createEvent(payload, 'updateHttpResponse', simulatorConfig, callback) 127 | } 128 | } 129 | 130 | // const kafkaWrapper = new KafkaWrapper(process.env.KAFKA_CREDENTIALS) 131 | const kafkaWrapper = new KafkaWrapper(process.env.BOOTSTRAP_SERVERS, 132 | process.env.SECURITY_PROTOCOL, 133 | process.env.SASL_MECHANISMS, 134 | process.env.SASL_USERNAME, 135 | process.env.SASL_PASSWORD) 136 | Object.freeze(kafkaWrapper) 137 | 138 | module.exports = kafkaWrapper -------------------------------------------------------------------------------- /microservices/ksqlcontroller/app.js: -------------------------------------------------------------------------------- 1 | const { response } = require('express'); 2 | const express = require('express'); 3 | const app = express(); 4 | const cors = require('cors'); 5 | // const needle = require('needle'); 6 | const http2 = require('http2'); 7 | const WebSocket = require('ws'); 8 | const url = require('url'); 9 | const wss = new WebSocket.Server({ noServer: true }); 10 | 11 | const PORT = process.env.PORT || 8080 12 | 13 | const KSQL_ENDPOINT = process.env.KSQL_ENDPOINT 14 | const KSQL_API_KEY = process.env.KSQL_API_KEY 15 | const KSQL_API_SECRET = process.env.KSQL_API_SECRET 16 | const KSQL_STATEMENT = process.env.KSQL_STATEMENT || "select ROWTIME, kitchenId, count from favoriteRestaurants EMIT CHANGES;" 17 | 18 | app.use(express.json()); 19 | app.use(cors()) 20 | 21 | let favoritesMap = new Map() 22 | let mostRecentDataTimesamp; 23 | let lastDataSendTimestamp; 24 | 25 | wss.on('connection', (ws, req) => { 26 | ws.PATHNAME = req.url 27 | // send current map on connect 28 | ws.send(JSON.stringify({favorites: Object.fromEntries(favoritesMap), timestamp: mostRecentDataTimesamp})) 29 | }) 30 | 31 | function queryStream(sql_statement) { 32 | // change sql to use param 33 | // add LIMIT to sql statement for scalability? 34 | let params = { 35 | "sql": KSQL_STATEMENT, 36 | "streamsProperties": {} 37 | } 38 | 39 | var auth = 'Basic ' + Buffer.from(KSQL_API_KEY + ':' + KSQL_API_SECRET).toString('base64'); 40 | let buffer = Buffer.from(JSON.stringify(params)) 41 | const connection = http2.connect(KSQL_ENDPOINT + '/query-stream') 42 | const stream = connection.request({ 43 | ':method': 'POST', 44 | ':path': '/query-stream', 45 | 'authorization': auth, 46 | 'Content-Length': buffer.length 47 | }, { endStream: false }) 48 | 49 | stream.on('response', (headers) => { 50 | console.log('RESPONSE', headers) 51 | }) 52 | 53 | stream.on('data', (data) => { 54 | console.log('DATA', data.toString()) 55 | try { 56 | let dataJSON = JSON.parse(data.toString()) 57 | 58 | // check if header 59 | if (dataJSON.queryId != null) { 60 | console.log("found ksql query header") 61 | } 62 | 63 | // check for data 64 | if (Array.isArray(dataJSON)) { 65 | console.log("found row") 66 | // column dish is index 1 67 | let kitchenId = dataJSON[1] 68 | let count = dataJSON[2] 69 | favoritesMap.set(kitchenId, count) 70 | console.log(favoritesMap) 71 | mostRecentDataTimesamp = Date.now() 72 | } 73 | } catch (err) { 74 | console.error(err) 75 | } 76 | }) 77 | 78 | stream.on('end', () => { 79 | console.log('END HTTP/2 STREAM') 80 | console.log('Starting another http/2 stream') 81 | queryStream("") 82 | }) 83 | 84 | stream.end(JSON.stringify(params)); 85 | } 86 | 87 | app.get("/favorites/sync", (req, res) => { 88 | res.status('200').send({favorites: Object.fromEntries(favoritesMap), timestamp: mostRecentDataTimesamp}) 89 | }) 90 | 91 | let server = app.listen(PORT, () => { 92 | console.log(`listening on port ${PORT}`); 93 | }); 94 | 95 | let intervalPublishWebsocket = setInterval(() => { 96 | if (Object.keys(Object.fromEntries(favoritesMap)).length == 0) { 97 | return 98 | } 99 | // send only new data 100 | if (lastDataSendTimestamp < mostRecentDataTimesamp || (lastDataSendTimestamp == null || mostRecentDataTimesamp == null)) { 101 | lastDataSendTimestamp = mostRecentDataTimesamp 102 | } else { 103 | return 104 | } 105 | let message = JSON.stringify({favorites: Object.fromEntries(favoritesMap), timestamp: lastDataSendTimestamp}) 106 | wss.clients.forEach(client => { 107 | if (client.readyState === WebSocket.OPEN && client.PATHNAME === '/favorites') { 108 | client.send(message); 109 | } 110 | }) 111 | }, 1000) 112 | 113 | let intervalSendDataEveryMinute = setInterval(() => { 114 | if (Object.keys(Object.fromEntries(favoritesMap)).length == 0) { 115 | return 116 | } 117 | // send data every minute 118 | let message = JSON.stringify({favorites: Object.fromEntries(favoritesMap), timestamp: mostRecentDataTimesamp}) 119 | wss.clients.forEach(client => { 120 | if (client.readyState === WebSocket.OPEN && client.PATHNAME === '/favorites') { 121 | client.send(message); 122 | } 123 | }) 124 | }, 60000) 125 | 126 | // accept websocket only when data is available 127 | server.on('upgrade', function upgrade(request, socket, head) { 128 | const pathname = url.parse(request.url).pathname; 129 | console.log(pathname) 130 | 131 | // if (Object.keys(Object.fromEntries(favoritesMap)).length == 0) { 132 | // socket.destroy() 133 | // return; 134 | // } 135 | if (pathname === '/favorites') { 136 | wss.handleUpgrade(request, socket, head, function done(ws) { 137 | wss.emit('connection', ws, request); 138 | }); 139 | } else { 140 | socket.destroy(); 141 | } 142 | }); 143 | 144 | queryStream("") -------------------------------------------------------------------------------- /microservices/apiservice/app.js: -------------------------------------------------------------------------------- 1 | const { response } = require('express'); 2 | const express = require('express'); 3 | const app = express(); 4 | const cors = require('cors'); 5 | const KafkaProducer = require('./KafkaProducer.js') 6 | const { v4: uuidv4 } = require('uuid'); 7 | const { createProxyMiddleware } = require('http-proxy-middleware') 8 | const needle = require('needle'); 9 | 10 | const PORT = process.env.PORT || 8080 11 | 12 | const STATUS_SERVICE = process.env.STATUS_SERVICE || 'http://status:8080' 13 | 14 | app.use('/status', createProxyMiddleware({ target: STATUS_SERVICE, changeOrigin: true })) 15 | 16 | function setStatus(key, value) { 17 | let data = { value } 18 | return needle('post', STATUS_SERVICE + '/status/' + key, data, { json: true }) 19 | } 20 | 21 | app.use(express.json()); 22 | app.use(cors()) 23 | 24 | // { 25 | // "orderId":"", 26 | // "userId":"", 27 | // "kitchenId":"", 28 | // "food":[{ 29 | // "name":"", 30 | // "quantity":"", 31 | // "price":"" 32 | // }], 33 | // "deliveryLocation":{ 34 | // "customerName":"" 35 | // "long":"", 36 | // "lat":"", 37 | // "address":"" 38 | // } 39 | // "totalPrice":"" 40 | // "deliveredBy":{ 41 | // "id":"", 42 | // "name":"" 43 | // } 44 | // "status":"" 45 | // } 46 | app.post("/createOrder", (req, res) => { 47 | let orderId = uuidv4() 48 | let userId = req.body.userId || uuidv4() // assign unique user id for simulation 49 | let requestId = uuidv4() 50 | let kitchenId = req.body.kitchenId || uuidv4() 51 | let dish = req.body.dish || "testDish" 52 | let totalPrice = req.body.totalPrice || (Math.random() * 100).toFixed(2) 53 | let order = { 54 | orderId, 55 | userId, 56 | kitchenId, 57 | dish, 58 | totalPrice 59 | } 60 | // simulatorConfig 61 | let kitchenSpeed = req.body.kitchenSpeed || 5000 62 | let courierSpeed = req.body.courierSpeed || 5000 63 | 64 | KafkaProducer.publishOrder({...order, requestId}, {kitchenSpeed, courierSpeed}, (err) => { 65 | if (err) { 66 | console.log(err) 67 | res.status('404').send({requestId, error: "Error sending message"}) 68 | } else { 69 | setStatus(requestId, JSON.stringify({status: "processing"})) 70 | .then(response => { 71 | res.status('202').send({requestId, payloadSent: order}) 72 | }) 73 | .catch(err => { 74 | res.status('404').send({requestId, error: "Error sending message", err}) 75 | }) 76 | } 77 | }) 78 | }) 79 | 80 | // create restaurants 81 | app.post("/restaurants", (req, res) => { 82 | let requestId = uuidv4() 83 | let restaurants = req.body 84 | KafkaProducer.createRestaurants(requestId, restaurants, (err) => { 85 | if (err) { 86 | console.log(err) 87 | res.status('404').send({requestId, error: "Error sending message"}) 88 | } else { 89 | setStatus(requestId, JSON.stringify({status: "processing"})) 90 | .then(response => { 91 | res.status('202').send({requestId, payloadSent: {restaurants}}) 92 | }) 93 | .catch(err => { 94 | console.log(err) 95 | res.status('404').send({requestId, error: "Error sending message", err}) 96 | }) 97 | } 98 | }) 99 | }) 100 | 101 | // get restaurants 102 | app.get("/restaurants", (req, res) => { 103 | let requestId = uuidv4() 104 | KafkaProducer.getRestaurants(requestId, (err) => { 105 | if (err) { 106 | console.log(err) 107 | res.status('404').send({requestId, error: "Error sending message"}) 108 | } else { 109 | setStatus(requestId, JSON.stringify({status: "processing"})) 110 | .then(response => { 111 | res.status('202').send({requestId, status: "processing"}) 112 | }) 113 | .catch(err => { 114 | res.status('404').send({requestId, error: "Error sending message", err}) 115 | }) 116 | } 117 | }) 118 | }) 119 | 120 | // get status 121 | app.get("/user/:userId/orders", (req, res) => { 122 | let requestId = uuidv4() 123 | let userId = req.params.userId 124 | KafkaProducer.getOrdersOfUser({requestId, userId}, (err) => { 125 | if (err) { 126 | console.log(err) 127 | res.status('404').send({requestId, error: "Error sending message"}) 128 | } else { 129 | setStatus(requestId, JSON.stringify({status: "processing"})) 130 | .then(response => { 131 | res.status('202').send({requestId, status: "processing"}) 132 | }) 133 | .catch(err => { 134 | res.status('404').send({requestId, error: "Error sending message", err}) 135 | }) 136 | } 137 | }) 138 | }) 139 | 140 | KafkaProducer.on('ready', function() { 141 | console.log('The producer has connected.') 142 | 143 | app.listen(PORT, () => { 144 | console.log(`listening on port ${PORT}`); 145 | }); 146 | }) -------------------------------------------------------------------------------- /microservices/consumers/kitchenConsumer/KafkaWrapper.js: -------------------------------------------------------------------------------- 1 | const Kafka = require('node-rdkafka'); 2 | 3 | class KafkaWrapper { 4 | constructor(brokers, protocol, mechanism, username, password) { 5 | // ibm cloud service credentials 6 | // let jsonCredentials = JSON.parse(ibmcloud_credentials) 7 | // let brokers = jsonCredentials.kafka_brokers_sasl 8 | // let apiKey = jsonCredentials.api_key 9 | // producer 10 | // let driver_options = { 11 | // //'debug': 'all', 12 | // 'metadata.broker.list': brokers, 13 | // 'security.protocol': 'SASL_SSL', 14 | // 'sasl.mechanisms': 'PLAIN', 15 | // 'sasl.username': 'token', 16 | // 'sasl.password': apiKey, 17 | // 'log.connection.close' : false, 18 | // 'enable.auto.commit': false 19 | // }; 20 | let driver_options = { 21 | //'debug': 'all', 22 | 'metadata.broker.list': brokers, 23 | 'security.protocol': protocol, 24 | 'sasl.mechanisms': mechanism, 25 | 'sasl.username': username, 26 | 'sasl.password': password, 27 | 'log.connection.close' : false, 28 | 'enable.auto.commit': false 29 | }; 30 | let consumerConfig = { 31 | 'client.id': 'kitchen-consumer', 32 | 'group.id': 'kitchen-consumer-group', 33 | } 34 | 35 | for (var key in driver_options) { 36 | consumerConfig[key] = driver_options[key] 37 | } 38 | 39 | // create kafka consumer 40 | let topicConfig = { 41 | 'auto.offset.reset': 'earliest' 42 | } 43 | let consumer = new Kafka.KafkaConsumer(consumerConfig, topicConfig) 44 | 45 | // Register error listener 46 | consumer.on('event.error', function(err) { 47 | console.error('Error from consumer:' + JSON.stringify(err)); 48 | }); 49 | 50 | this.consumer = consumer 51 | 52 | // producer 53 | let producerConfig = { 54 | 'client.id': 'example-orders-producer', 55 | 'dr_msg_cb': true // Enable delivery reports with message payload 56 | } 57 | 58 | for (var key in driver_options) { 59 | producerConfig[key] = driver_options[key] 60 | } 61 | 62 | // create kafka producer 63 | let producerTopicConfig = { 64 | 'request.required.acks': -1 65 | // 'produce.offset.report': true 66 | } 67 | let producer = new Kafka.Producer(producerConfig, producerTopicConfig) 68 | producer.setPollInterval(100) 69 | 70 | // debug 71 | // producer.on('event.log', function(log) { 72 | // console.log(log); 73 | // }); 74 | 75 | // Register error listener 76 | producer.on('event.error', function(err) { 77 | console.error('Error from producer:' + JSON.stringify(err)); 78 | }); 79 | 80 | // Register delivery report listener 81 | producer.on('delivery-report', function(err, dr) { 82 | if (err) { 83 | console.error('Delivery report: Failed sending message ' + dr.value); 84 | console.error(err); 85 | // We could retry sending the message 86 | } else { 87 | console.log('Message produced, partition: ' + dr.partition + ' offset: ' + dr.offset); 88 | } 89 | }); 90 | producer.connect() 91 | 92 | this.producer = producer 93 | } 94 | 95 | on(event, callback) { 96 | this.consumer.on(event, callback) 97 | } 98 | 99 | createEvent(order, event, simulatorConfig, callback) { 100 | let topicName = 'orders' 101 | let eventType = event 102 | let message = Buffer.from(JSON.stringify({eventType, payload: order, simulatorConfig})) 103 | try { 104 | this.producer.produce( 105 | topicName, 106 | null, 107 | message, 108 | order.orderId 109 | ) 110 | callback(null) 111 | } catch (err) { 112 | console.log('caught') 113 | callback(err) 114 | } 115 | } 116 | 117 | preparingFoodEvent(order, simulatorConfig, callback) { 118 | this.createEvent(order, 'kitchenPreparingFood', simulatorConfig, callback) 119 | } 120 | 121 | foodReadyEvent(order, simulatorConfig, callback) { 122 | this.createEvent(order, 'kitchenFoodReady', simulatorConfig, callback) 123 | } 124 | 125 | updateHttpResponse(payload, simulatorConfig, callback) { 126 | console.log(payload) 127 | this.createEvent(payload, 'updateHttpResponse', simulatorConfig, callback) 128 | } 129 | } 130 | 131 | // const kafkaWrapper = new KafkaWrapper(process.env.KAFKA_CREDENTIALS) 132 | const kafkaWrapper = new KafkaWrapper(process.env.BOOTSTRAP_SERVERS, 133 | process.env.SECURITY_PROTOCOL, 134 | process.env.SASL_MECHANISMS, 135 | process.env.SASL_USERNAME, 136 | process.env.SASL_PASSWORD) 137 | Object.freeze(kafkaWrapper) 138 | 139 | module.exports = kafkaWrapper --------------------------------------------------------------------------------