Repository: IBM/innovate-digital-bank Branch: master Commit: a92ee010e94f Files: 196 Total size: 410.2 KB Directory structure: gitextract_gbz4ssqw/ ├── .bluemix/ │ ├── deploy.json │ ├── locales.yml │ ├── nls/ │ │ └── messages.yml │ ├── pipeline.yml │ └── toolchain.yml ├── .gitignore ├── .ibm-project ├── .travis.yml ├── CONTRIBUTING.md ├── DOCS.md ├── Dockerfile ├── Dockerfile-tools ├── LICENSE.md ├── MAINTAINERS.md ├── README.md ├── accounts/ │ ├── .cfignore │ ├── .dockerignore │ ├── .gitignore │ ├── .ibm-project │ ├── Dockerfile │ ├── Dockerfile-tools │ ├── app.js │ ├── chart/ │ │ └── innovate-accounts/ │ │ ├── Chart.yaml │ │ ├── templates/ │ │ │ ├── deployment.yaml │ │ │ ├── hpa.yaml │ │ │ └── service.yaml │ │ └── values.yaml │ ├── cli-config.yml │ ├── idt.js │ ├── manifest.yml │ ├── mongoose/ │ │ └── account.js │ ├── package.json │ └── server.js ├── authentication/ │ ├── .cfignore │ ├── .dockerignore │ ├── .gitignore │ ├── .ibm-project │ ├── Dockerfile │ ├── Dockerfile-tools │ ├── app.js │ ├── chart/ │ │ └── innovate-authentication/ │ │ ├── Chart.yaml │ │ ├── templates/ │ │ │ ├── deployment.yaml │ │ │ ├── hpa.yaml │ │ │ └── service.yaml │ │ └── values.yaml │ ├── cli-config.yml │ ├── idt.js │ ├── manifest.yml │ ├── mongoose/ │ │ └── user.js │ ├── package.json │ └── server.js ├── bills/ │ ├── .cfignore │ ├── .dockerignore │ ├── .gitignore │ ├── .ibm-project │ ├── Dockerfile │ ├── Dockerfile-tools │ ├── app.js │ ├── chart/ │ │ └── innovate-bills/ │ │ ├── Chart.yaml │ │ ├── templates/ │ │ │ ├── deployment.yaml │ │ │ ├── hpa.yaml │ │ │ └── service.yaml │ │ └── values.yaml │ ├── cli-config.yml │ ├── idt.js │ ├── manifest.yml │ ├── mongoose/ │ │ └── bill.js │ ├── package.json │ └── server.js ├── chart/ │ └── innovate-bank/ │ ├── Chart.yaml │ ├── charts/ │ │ └── mongodb/ │ │ ├── .helmignore │ │ ├── Chart.yaml │ │ ├── templates/ │ │ │ ├── _helpers.tpl │ │ │ ├── deployment.yaml │ │ │ ├── pv.yaml │ │ │ ├── pvc.yaml │ │ │ ├── secrets.yaml │ │ │ └── service.yaml │ │ └── values.yaml │ ├── templates/ │ │ ├── _helpers.tpl │ │ ├── configmap.yaml │ │ ├── deployment.yaml │ │ ├── hpa.yaml │ │ ├── ingress.yaml │ │ └── service.yaml │ └── values.yaml ├── cli-config.yml ├── creating-pv.md ├── creating-pvc.md ├── idt.js ├── mongo-db.md ├── package.json ├── portal/ │ ├── .cfignore │ ├── .dockerignore │ ├── .gitignore │ ├── .ibm-project │ ├── Dockerfile │ ├── Dockerfile-tools │ ├── chart/ │ │ └── innovate-portal/ │ │ ├── Chart.yaml │ │ ├── templates/ │ │ │ ├── deployment.yaml │ │ │ ├── hpa.yaml │ │ │ └── service.yaml │ │ └── values.yaml │ ├── cli-config.yml │ ├── config.js │ ├── idt.js │ ├── manifest.yml │ ├── package.json │ ├── public/ │ │ ├── accounts.html │ │ ├── bills.html │ │ ├── chat.html │ │ ├── index.html │ │ ├── login.html │ │ ├── overview.html │ │ ├── res/ │ │ │ ├── css/ │ │ │ │ ├── chat.css │ │ │ │ ├── queries.css │ │ │ │ └── styles.css │ │ │ └── js/ │ │ │ ├── accounts.js │ │ │ ├── api.js │ │ │ ├── bills.js │ │ │ ├── common.js │ │ │ ├── conversation.js │ │ │ ├── global.js │ │ │ ├── navbar.js │ │ │ ├── payload.js │ │ │ ├── spending.js │ │ │ └── transactions.js │ │ ├── signup.html │ │ ├── transactions.html │ │ └── vendors/ │ │ └── css/ │ │ ├── animate.css │ │ ├── grid.css │ │ └── normalize.css │ ├── routes/ │ │ ├── accounts.js │ │ ├── auth.js │ │ ├── bills.js │ │ ├── support.js │ │ ├── transactions.js │ │ └── user.js │ └── server.js ├── scripts/ │ └── install_bx.sh ├── support/ │ ├── .cfignore │ ├── .dockerignore │ ├── .gitignore │ ├── .ibm-project │ ├── Dockerfile │ ├── Dockerfile-tools │ ├── app.js │ ├── chart/ │ │ └── innovate-support/ │ │ ├── Chart.yaml │ │ ├── templates/ │ │ │ ├── deployment.yaml │ │ │ ├── hpa.yaml │ │ │ └── service.yaml │ │ └── values.yaml │ ├── cli-config.yml │ ├── conversation-workspace.json │ ├── idt.js │ ├── lib/ │ │ └── watson-conversation-setup.js │ ├── manifest.yml │ ├── package.json │ └── server.js ├── transactions/ │ ├── .cfignore │ ├── .dockerignore │ ├── .gitignore │ ├── .ibm-project │ ├── Dockerfile │ ├── Dockerfile-tools │ ├── app.js │ ├── chart/ │ │ └── innovate-transactions/ │ │ ├── Chart.yaml │ │ ├── templates/ │ │ │ ├── deployment.yaml │ │ │ ├── hpa.yaml │ │ │ └── service.yaml │ │ └── values.yaml │ ├── cli-config.yml │ ├── idt.js │ ├── manifest.yml │ ├── mongoose/ │ │ └── transaction.js │ ├── package.json │ └── server.js └── userbase/ ├── .cfignore ├── .dockerignore ├── .gitignore ├── .ibm-project ├── Dockerfile ├── Dockerfile-tools ├── app.js ├── chart/ │ └── innovate-userbase/ │ ├── Chart.yaml │ ├── templates/ │ │ ├── deployment.yaml │ │ ├── hpa.yaml │ │ └── service.yaml │ └── values.yaml ├── cli-config.yml ├── config.js ├── idt.js ├── manifest.yml ├── package.json ├── populate.js └── server.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .bluemix/deploy.json ================================================ { "$schema": "http://json-schema.org/draft-04/schema#", "messages": { "$i18n": "locales.yml" }, "title": { "$ref": "#/messages/deploy.title" }, "description": { "$ref": "#/messages/deploy.description" }, "longDescription": { "$ref": "#/messages/deploy.longDescription" }, "type": "object", "properties": { "api-key": { "description": "The IBM Cloud API key is used to access the IBM Container Service API and interact with the cluster. You can obtain your API key with 'bx iam api-key-create' or via the console at https://console.ng.bluemix.net/iam/#/apikeys by clicking **Create API key** (Each API key only can be viewed once).", "type": "string", "pattern": "\\S" }, "registry-region": { "description": "The IBM Cloud region for image registry", "type": "string", "disabled": false }, "registry-namespace": { "description": "The namespace in the container image registry. You can set it up with {bx cr namespace-add]. Learn more at https://console.bluemix.net/docs/services/Registry/index.html .", "type": "string", "pattern": "^[a-z0-9][a-z0-9_]{3,29}$" }, "prod-region": { "description": "The IBM Cloud region for prod deployment", "type": "string", "disabled": false }, "prod-cluster-name": { "description": "The name of prod cluster. Retrieve it with [bx cs clusters] or via the console at https://console.ng.bluemix.net/containers-kubernetes/home/clusters .", "type": "string", "pattern": "\\S" }, "prod-cluster-namespace": { "description": "Prod namespace", "type": "string", "pattern": "\\S" }, "app-name": { "description": { "$ref": "#/messages/deploy.appDescription" }, "type": "string", "pattern": "\\S" } }, "required": ["app-name", "api-key", "registry-region", "registry-namespace", "prod-region", "prod-cluster-name", "prod-cluster-namespace"], "form": [ { "type": "validator", "url": "/devops/setup/bm-helper/custom_helper.html" },{ "type": "text", "readonly": false, "title": { "$ref": "#/messages/deploy.appName" }, "key": "app-name" },{ "type": "password", "readonly": false, "title": "IBM Cloud API key", "key": "api-key" }, { "type": "table", "columnCount": 3, "widths": ["50%", "3%", "47%"], "items": [ { "type": "label", "title": "Container registry region" },{ "type": "label", "title": "" },{ "type": "label", "title": "Container registry namespace" },{ "type": "select", "title": "Container image registry region", "key": "registry-region" },{ "type": "label", "title": "" },{ "type": "select", "readonly": false, "title": "Container image registry namespace", "key": "registry-namespace" } ] },{ "type": "table", "columnCount": 5, "widths": ["32%","2%","32%", "2%", "32%"], "items": [ { "type": "label", "title": { "$ref": "#/messages/region" } },{ "type": "label", "title": "" }, { "type": "label", "title": "Cluster name" },{ "type": "label", "title": "" }, { "type": "label", "title": "Cluster namespace" },{ "type": "select", "key": "prod-region" },{ "type": "label", "title": "" }, { "type": "select", "key": "prod-cluster-name", "readonly": false },{ "type": "label", "title": "" }, { "type": "text", "key": "prod-cluster-namespace", "readonly": false } ] },{ "type": "table", "columnCount": 1, "widths": ["100%"], "items": [ { "type": "label", "title": "Retrieve the Kubernetes cluster name with the CLI command 'bx cs clusters' or via the [console](https://console.bluemix.net/containers-kubernetes/home/clusters)." } ] },{ "type": "table", "columnCount": 1, "widths": ["100%"], "items": [ { "type": "label", "title": "If the cluster namespace doesn't exist already, it will be automatically created and configured." } ] } ] } ================================================ FILE: .bluemix/locales.yml ================================================ --- root: $ref: ./nls/messages.yml # de: # $ref: ./nls/messages_de.yml # en-AA: # $ref: ./nls/messages_en_AA.yml # en-RR: # $ref: ./nls/messages_en_RR.yml # en-ZZ: # $ref: ./nls/messages_en_ZZ.yml # es: # $ref: ./nls/messages_es.yml # fr: # $ref: ./nls/messages_fr.yml # it: # $ref: ./nls/messages_it.yml # ja: # $ref: ./nls/messages_ja.yml # ko: # $ref: ./nls/messages_ko.yml # pt-BR: # $ref: ./nls/messages_pt_BR.yml # zh: # $ref: ./nls/messages_zh.yml # zh-HK: # $ref: ./nls/messages_zh_HK.yml # zh-TW: # $ref: ./nls/messages_zh_TW.yml ================================================ FILE: .bluemix/nls/messages.yml ================================================ --- #The format for creating a link with a URL and text is [link text](link url) with no spaces or characters between the two sets of brackets- []() template.name: "Develop a Kubernetes app with Helm" template.description: "With this toolchain, you can develop a Docker application and its Helm chart together in source control and have it built and deployed automatically to a Kubernetes cluster. The toolchain performs sanity checks prior to building or deploying and ensures privacy by using a private container registry and namespaces for the container registry and the Kubernetes cluster. This toolchain is also leveraging [Vulnerability Advisor](https://www.ibm.com/blogs/bluemix/2016/06/docker-container-security-with-vulnerability-advisor/), to ensure only secure images get deployed.\n\nBy default, the toolchain uses a sample Node.js \"Hello World\" app, but you can link to your own Git repository instead as long as it has a Dockerfile and a Helm chart.\n\n You can manage your IBM Cloud Container clusters in the [console](https://console.ng.bluemix.net/docs/containers/cs_cluster.html#cs_cluster).\n\n This toolchain uses tools that are part of the Continuous Delivery service. If an instance of that service isn't already in your organization, when you click **Create**, it is automatically added at no cost to you. For more information and terms, see the [IBM Cloud catalog](/catalog/services/continuous-delivery/).\n\nTo get started, click **Create**.\n\nFor step-by-step instructions, follow the [tutorial](https://www.ibm.com/cloud/garage/tutorials/tc-simple-kube-helm)." template.gettingStarted: " **Your toolchain is ready!**\n**Quick start:** Commit a change to the Git repo to trigger a new Docker image build, which will be scanned for vulnerabilites and deployed with Helm to your Kubernetes cluster. For step-by-step instructions, see the [tutorial](https://www.ibm.com/cloud/garage/tutorials/tc-simple-kube-helm?task=2) for this toolchain." deploy.title: "Innovate Deploy Stage" deploy.description: "Innovate simple toolchain" deploy.longDescription: "The Delivery Pipeline automates continuous build, test and deploy of the Docker application." deploy.appDescription: "The name of your app" deploy.appName: "App name" region: "Cluster region" organization: "Organization" space: "Space" prodStage: "Production environment" ================================================ FILE: .bluemix/pipeline.yml ================================================ --- stages: - name: BUILD inputs: - type: git branch: master service: ${GIT_REPO} triggers: - type: commit jobs: - name: Pre-build check type: builder build_type: cr artifact_dir: '' target: region_id: ${REGISTRY_REGION_ID} api_key: ${API_KEY} namespace: ${REGISTRY_NAMESPACE} image_name: ${CF_APP_NAME} script: |- #******************************************** #!/bin/bash # uncomment to debug the script #set -x # copy the script below into your app code repo (e.g. ./scripts/check_prebuild.sh) and 'source' it from your pipeline job # source ./scripts/check_prebuild.sh # alternatively, you can source it from online script: # source <(curl -sSL "https://raw.githubusercontent.com/open-toolchain/commons/master/scripts/check_prebuild.sh") # ------------------ # source: https://raw.githubusercontent.com/open-toolchain/commons/master/scripts/check_prebuild.sh export TOOLCHAIN_FLAG=active echo "Build environment variables:" echo "REGISTRY_URL=${REGISTRY_URL}" echo "TOOLCHAIN_FLAG=${TOOLCHAIN_FLAG}" echo "REGISTRY_NAMESPACE=${REGISTRY_NAMESPACE}" echo "IMAGE_NAME=${IMAGE_NAME}" echo "BUILD_NUMBER=${BUILD_NUMBER}" echo "ARCHIVE_DIR=${ARCHIVE_DIR}" # also run 'env' command to find all available env variables # or learn more about the available environment variables at: # https://console.bluemix.net/docs/services/ContinuousDelivery/pipeline_deploy_var.html#deliverypipeline_environment echo "==========================================================" echo "CHECKING DOCKERFILE" echo "Checking Dockerfile at the repository root" if [ -f Dockerfile ]; then echo "Dockerfile found" else echo "Dockerfile not found" exit 1 fi echo "Linting Dockerfile" npm install -g dockerlint dockerlint -f Dockerfile echo "==========================================================" echo "CHECKING HELM CHART" echo "Looking for chart under /chart/" if [ -d ./chart ]; then CHART_NAME=$(find chart/. -maxdepth 2 -type d -name '[^.]?*' -printf %f -quit) fi if [ -z "${CHART_NAME}" ]; then echo -e "No Helm chart found for Kubernetes deployment under /chart/." exit 1 else echo -e "Helm chart found for Kubernetes deployment : /chart/${CHART_NAME}" fi echo "Linting Helm Chart" helm lint ./chart/${CHART_NAME} echo "==========================================================" echo "CHECKING REGISTRY current plan and quota" ibmcloud cr plan ibmcloud cr quota echo "If needed, discard older images using: ibmcloud cr image-rm" echo "Current content of image registry" ibmcloud cr images echo "Checking registry namespace: ${REGISTRY_NAMESPACE}" NS=$( ibmcloud cr namespaces | grep ${REGISTRY_NAMESPACE} ||: ) if [ -z "${NS}" ]; then echo "Registry namespace ${REGISTRY_NAMESPACE} not found, creating it." ibmcloud cr namespace-add ${REGISTRY_NAMESPACE} echo "Registry namespace ${REGISTRY_NAMESPACE} created." else echo "Registry namespace ${REGISTRY_NAMESPACE} found." fi - name: Build Docker image type: builder build_type: cr artifact_dir: output target: region_id: ${REGISTRY_REGION_ID} api_key: ${API_KEY} namespace: ${REGISTRY_NAMESPACE} image_name: ${CF_APP_NAME} script: | #!/bin/bash # uncomment to debug the script #set -x # copy the script below into your app code repo (e.g. ./scripts/build_image.sh) and 'source' it from your pipeline job # source ./scripts/build_image.sh # alternatively, you can source it from online script: # source <(curl -sSL "https://raw.githubusercontent.com/open-toolchain/commons/master/scripts/build_image.sh") # ------------------ # source: https://raw.githubusercontent.com/open-toolchain/commons/master/scripts/build_image.sh echo "Build environment variables:" export TOOLCHAIN_FLAG=active echo "TOOLCHAIN_FLAG=${TOOLCHAIN_FLAG}" echo "REGISTRY_URL=${REGISTRY_URL}" echo "REGISTRY_NAMESPACE=${REGISTRY_NAMESPACE}" echo "IMAGE_NAME=${IMAGE_NAME}" echo "BUILD_NUMBER=${BUILD_NUMBER}" echo "ARCHIVE_DIR=${ARCHIVE_DIR}" # also run 'env' command to find all available env variables # or learn more about the available environment variables at: # https://console.bluemix.net/docs/services/ContinuousDelivery/pipeline_deploy_var.html#deliverypipeline_environment # To review or change build options use: # ibmcloud cr build --help echo -e "Existing images in registry" ibmcloud cr images echo "==========================================================" echo -e "BUILDING CONTAINER IMAGE: ${IMAGE_NAME}:${BUILD_NUMBER}" set -x ibmcloud cr build -t ${REGISTRY_URL}/${REGISTRY_NAMESPACE}/${IMAGE_NAME}:${BUILD_NUMBER} . set +x ibmcloud cr image-inspect ${REGISTRY_URL}/${REGISTRY_NAMESPACE}/${IMAGE_NAME}:${BUILD_NUMBER} # When 'ibmcloud' commands are in the pipeline job config directly, the image URL will automatically be passed # along with the build result as env variable PIPELINE_IMAGE_URL to any subsequent job consuming this build result. # When the job is sourc'ing an external shell script, or to pass a different image URL than the one inferred by the pipeline, # please uncomment and modify the environment variable the following line. export PIPELINE_IMAGE_URL="$REGISTRY_URL/$REGISTRY_NAMESPACE/$IMAGE_NAME:$BUILD_NUMBER" echo "TODO - remove once no longer needed to unlock VA job ^^^^" ibmcloud cr images echo "==========================================================" echo "COPYING ARTIFACTS needed for deployment and testing (in particular build.properties)" echo "Checking archive dir presence" mkdir -p $ARCHIVE_DIR # Persist env variables into a properties file (build.properties) so that all pipeline stages consuming this # build as input and configured with an environment properties file valued 'build.properties' # will be able to reuse the env variables in their job shell scripts. # CHART information from build.properties is used in Helm Chart deployment to set the release name CHART_NAME=$(find chart/. -maxdepth 2 -type d -name '[^.]?*' -printf %f -quit) echo "CHART_NAME=${CHART_NAME}" >> $ARCHIVE_DIR/build.properties # IMAGE information from build.properties is used in Helm Chart deployment to set the release name echo "IMAGE_NAME=${IMAGE_NAME}" >> $ARCHIVE_DIR/build.properties echo "BUILD_NUMBER=${BUILD_NUMBER}" >> $ARCHIVE_DIR/build.properties # REGISTRY information from build.properties is used in Helm Chart deployment to generate cluster secret echo "REGISTRY_URL=${REGISTRY_URL}" >> $ARCHIVE_DIR/build.properties echo "REGISTRY_NAMESPACE=${REGISTRY_NAMESPACE}" >> $ARCHIVE_DIR/build.properties echo "File 'build.properties' created for passing env variables to subsequent pipeline jobs:" cat $ARCHIVE_DIR/build.properties echo "Copy pipeline scripts along with the build" # Copy scripts (incl. deploy scripts) if [ -d ./scripts/ ]; then if [ ! -d $ARCHIVE_DIR/scripts/ ]; then # no need to copy if working in ./ already cp -r ./scripts/ $ARCHIVE_DIR/ fi fi echo "Copy Helm chart along with the build" if [ ! -d $ARCHIVE_DIR/chart/ ]; then # no need to copy if working in ./ already cp -r ./chart/ $ARCHIVE_DIR/ fi - name: VALIDATE inputs: - type: job stage: BUILD job: Build Docker image triggers: - type: stage properties: - name: buildprops value: build.properties type: file jobs: - name: Vulnerability Advisor type: tester test_type: vulnerabilityadvisor use_image_from_build_input: true fail_stage: false target: region_id: ${REGISTRY_REGION_ID} api_key: ${API_KEY} script: | #!/bin/bash # uncomment to debug the script # set -x # copy the script below into your app code repo (e.g. ./scripts/check_vulnerabilities.sh) and 'source' it from your pipeline job # source ./scripts/check_vulnerabilities.sh # alternatively, you can source it from online script: # source <(curl -sSL "https://raw.githubusercontent.com/open-toolchain/commons/master/scripts/check_vulnerabilities.sh") # ------------------ # source: https://raw.githubusercontent.com/open-toolchain/commons/master/scripts/check_vulnerabilities.sh # Input env variables (can be received via a pipeline environment properties.file. echo "CHART_NAME=${CHART_NAME}" echo "IMAGE_NAME=${IMAGE_NAME}" echo "BUILD_NUMBER=${BUILD_NUMBER}" echo "REGISTRY_URL=${REGISTRY_URL}" echo "REGISTRY_NAMESPACE=${REGISTRY_NAMESPACE}" #View build properties # cat build.properties # also run 'env' command to find all available env variables # or learn more about the available environment variables at: # https://console.bluemix.net/docs/services/ContinuousDelivery/pipeline_deploy_var.html#deliverypipeline_environment ibmcloud cr images PIPELINE_IMAGE_URL=$REGISTRY_URL/$REGISTRY_NAMESPACE/$IMAGE_NAME:$BUILD_NUMBER echo -e "Checking vulnerabilities in image: ${PIPELINE_IMAGE_URL}" for ITER in {1..30} do set +e STATUS=$( ibmcloud cr va -e -o json ${PIPELINE_IMAGE_URL} | jq '.[0].status' ) set -e if [[ ${STATUS} == *OK* ]]; then break fi echo -e "${ITER} STATUS ${STATUS} : A vulnerability report was not found for the specified image." echo "Either the image doesn't exist or the scan hasn't completed yet. " echo "Waiting for scan to complete.." sleep 10 done set +e ibmcloud cr va ${PIPELINE_IMAGE_URL} set -e [[ $(ibmcloud cr va -e -o json ${PIPELINE_IMAGE_URL} | jq '.[0].status') == *OK* ]] || { echo "ERROR: The vulnerability scan was not successful, check the OUTPUT of the command and try again."; exit 1; } - name: PROD inputs: - type: job stage: BUILD job: Build Docker image triggers: - type: stage properties: - name: buildprops value: build.properties type: file - name: CLUSTER_NAMESPACE value: ${PROD_CLUSTER_NAMESPACE} type: text jobs: - name: Pre-deploy check type: deployer target: region_id: ${PROD_REGION_ID} api_key: ${API_KEY} kubernetes_cluster: ${PROD_CLUSTER_NAME} script: | #!/bin/bash # uncomment to debug the script #set -x # copy the script below into your app code repo (e.g. ./scripts/check_predeploy.sh) and 'source' it from your pipeline job # source ./scripts/check_predeploy.sh # alternatively, you can source it from online script: # source <(curl -sSL "https://raw.githubusercontent.com/open-toolchain/commons/master/scripts/check_predeploy.sh") # ------------------ # source: https://raw.githubusercontent.com/open-toolchain/commons/master/scripts/check_predeploy.sh # Input env variables (can be received via a pipeline environment properties.file. echo "CHART_NAME=${CHART_NAME}" echo "IMAGE_NAME=${IMAGE_NAME}" echo "BUILD_NUMBER=${BUILD_NUMBER}" echo "REGISTRY_URL=${REGISTRY_URL}" echo "REGISTRY_NAMESPACE=${REGISTRY_NAMESPACE}" #View build properties # cat build.properties # also run 'env' command to find all available env variables # or learn more about the available environment variables at: # https://console.bluemix.net/docs/services/ContinuousDelivery/pipeline_deploy_var.html#deliverypipeline_environment # Input env variables from pipeline job echo "PIPELINE_KUBERNETES_CLUSTER_NAME=${PIPELINE_KUBERNETES_CLUSTER_NAME}" echo "CLUSTER_NAMESPACE=${CLUSTER_NAMESPACE}" #Check cluster availability echo "==========================================================" echo "CHECKING CLUSTER readiness and namespace existence" IP_ADDR=$(ibmcloud cs workers ${PIPELINE_KUBERNETES_CLUSTER_NAME} | grep normal | awk '{ print $2 }') if [ -z "${IP_ADDR}" ]; then echo -e "${PIPELINE_KUBERNETES_CLUSTER_NAME} not created or workers not ready" exit 1 fi echo "Configuring cluster namespace" if kubectl get namespace ${CLUSTER_NAMESPACE}; then echo -e "Namespace ${CLUSTER_NAMESPACE} found." else kubectl create namespace ${CLUSTER_NAMESPACE} echo -e "Namespace ${CLUSTER_NAMESPACE} created." fi echo "==========================================================" echo "Checking if conversation service exists" echo "1" | echo "\n" | echo "1" | ibmcloud target --cf export CONV_SERVICE_CHECK=$(ibmcloud service show innovate-conversation-service | grep "FAILED") echo "Check: $CONV_SERVICE_CHECK" if [ ${CONV_SERVICE_CHECK} == "FAILED" ]; then echo "Conversation service does not exist, creating" ibmcloud service create conversation free innovate-conversation-service fi # Grant access to private image registry from namespace $CLUSTER_NAMESPACE # reference https://console.bluemix.net/docs/containers/cs_cluster.html#ibmcloud_registry_other echo "==========================================================" echo -e "CONFIGURING ACCESS to private image registry from namespace ${CLUSTER_NAMESPACE}" IMAGE_PULL_SECRET_NAME="ibmcloud-toolchain-${PIPELINE_TOOLCHAIN_ID}-${REGISTRY_URL}" echo -e "Checking for presence of ${IMAGE_PULL_SECRET_NAME} imagePullSecret for this toolchain" if ! kubectl get secret ${IMAGE_PULL_SECRET_NAME} --namespace ${CLUSTER_NAMESPACE}; then echo -e "${IMAGE_PULL_SECRET_NAME} not found in ${CLUSTER_NAMESPACE}, creating it" # for Container Registry, docker username is 'token' and email does not matter kubectl --namespace ${CLUSTER_NAMESPACE} create secret docker-registry ${IMAGE_PULL_SECRET_NAME} --docker-server=${REGISTRY_URL} --docker-password=${PIPELINE_BLUEMIX_API_KEY} --docker-username=iamapikey --docker-email=a@b.com else echo -e "Namespace ${CLUSTER_NAMESPACE} already has an imagePullSecret for this toolchain." fi echo "Checking ability to pass pull secret via Helm chart" CHART_PULL_SECRET=$( grep 'pullSecret' ./chart/${CHART_NAME}/values.yaml || : ) if [ -z "$CHART_PULL_SECRET" ]; then echo "WARNING: Chart is not expecting an explicit private registry imagePullSecret. Will patch the cluster default serviceAccount to pass it implicitly for now." echo "Going forward, you should edit the chart to add in:" echo -e "[./chart/${CHART_NAME}/templates/deployment.yaml] (under kind:Deployment)" echo " ..." echo " spec:" echo " imagePullSecrets: #<<<<<<<<<<<<<<<<<<<<<<<<" echo " - name: {{ .Values.image.pullSecret }} #<<<<<<<<<<<<<<<<<<<<<<<<" echo " containers:" echo " - name: {{ .Chart.Name }}" echo " image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" echo " ..." echo -e "[./chart/${CHART_NAME}/values.yaml]" echo "or check out this chart example: https://github.com/open-toolchain/hello-helm/tree/master/chart/hello" echo "or refer to: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/#create-a-pod-that-uses-your-secret" echo " ..." echo " image:" echo "repository: webapp" echo " tag: 1" echo " pullSecret: regsecret #<<<<<<<<<<<<<<<<<<<<<<<<"" echo " pullPolicy: IfNotPresent" echo " ..." echo "Enabling default serviceaccount to use the pull secret" kubectl patch -n ${CLUSTER_NAMESPACE} serviceaccount/default -p '{"imagePullSecrets":[{"name":"'"${IMAGE_PULL_SECRET_NAME}"'"}]}' echo "default serviceAccount:" kubectl get serviceAccount default -o yaml echo -e "Namespace ${CLUSTER_NAMESPACE} authorizing with private image registry using patched default serviceAccount" else echo -e "Namespace ${CLUSTER_NAMESPACE} authorizing with private image registry using Helm chart imagePullSecret" fi echo "==========================================================" echo "CONFIGURING TILLER enabled (Helm server-side component)" helm init --upgrade kubectl rollout status -w deployment/tiller-deploy --namespace=kube-system helm version echo "==========================================================" echo -e "CHECKING HELM releases in this namespace: ${CLUSTER_NAMESPACE}" helm list --namespace ${CLUSTER_NAMESPACE} - name: Deploy Helm chart type: deployer target: region_id: ${PROD_REGION_ID} api_key: ${API_KEY} kubernetes_cluster: ${PROD_CLUSTER_NAME} script: | #!/bin/bash # uncomment to debug the script #set -x # copy the script below into your app code repo (e.g. ./scripts/deploy_helm.sh) and 'source' it from your pipeline job # source ./scripts/deploy_helm.sh # alternatively, you can source it from online script: # source <(curl -sSL "https://raw.githubusercontent.com/open-toolchain/commons/master/scripts/deploy_helm.sh") # ------------------ # source: https://raw.githubusercontent.com/open-toolchain/commons/master/scripts/deploy_helm.sh # Input env variables (can be received via a pipeline environment properties.file. echo "CHART_NAME=${CHART_NAME}" echo "IMAGE_NAME=${IMAGE_NAME}" echo "BUILD_NUMBER=${BUILD_NUMBER}" echo "REGISTRY_URL=${REGISTRY_URL}" echo "REGISTRY_NAMESPACE=${REGISTRY_NAMESPACE}" #View build properties # cat build.properties # also run 'env' command to find all available env variables # or learn more about the available environment variables at: # https://console.bluemix.net/docs/services/ContinuousDelivery/pipeline_deploy_var.html#deliverypipeline_environment # Input env variables from pipeline job echo "PIPELINE_KUBERNETES_CLUSTER_NAME=${PIPELINE_KUBERNETES_CLUSTER_NAME}" echo "CLUSTER_NAMESPACE=${CLUSTER_NAMESPACE}" echo "==========================================================" echo "DEFINE RELEASE by prefixing image (app) name with namespace if not 'default' as Helm needs unique release names across namespaces" if [[ "${CLUSTER_NAMESPACE}" != "default" ]]; then RELEASE_NAME="${CLUSTER_NAMESPACE}-${IMAGE_NAME}" else RELEASE_NAME=${IMAGE_NAME} fi echo -e "Release name: ${RELEASE_NAME}" echo "==========================================================" echo "Binding conversation service to cluster" export CONV_BINDING_CHECK=$(ibmcloud cs cluster-services ${PIPELINE_KUBERNETES_CLUSTER_NAME} --namespace ${CLUSTER_NAMESPACE} | grep "innovate-conversation-service") echo "Check: $CONV_BINDING_CHECK" if [[ $CONV_BINDING_CHECK == *"innovate-conversation-service"* ]]; then echo "Binding already exists, moving on" else echo "Binding does not exist, binding" ibmcloud cs cluster-service-bind ${PIPELINE_KUBERNETES_CLUSTER_NAME} ${CLUSTER_NAMESPACE} innovate-conversation-service fi echo "==========================================================" echo "DEPLOYING HELM chart" IP_ADDR=$(ibmcloud cs workers ${PIPELINE_KUBERNETES_CLUSTER_NAME} | grep normal | awk '{ print $2 }') IMAGE_REPOSITORY=${REGISTRY_URL}/${REGISTRY_NAMESPACE}/${IMAGE_NAME} IMAGE_PULL_SECRET_NAME="ibmcloud-toolchain-${PIPELINE_TOOLCHAIN_ID}-${REGISTRY_URL}" # Using 'upgrade --install" for rolling updates. Note that subsequent updates will occur in the same namespace the release is currently deployed in, ignoring the explicit--namespace argument". echo -e "Dry run into: ${PIPELINE_KUBERNETES_CLUSTER_NAME}/${CLUSTER_NAMESPACE}." helm upgrade --install --debug --dry-run ${RELEASE_NAME} ./chart/${CHART_NAME} --set image.repository=${IMAGE_REPOSITORY},image.tag=${BUILD_NUMBER},image.pullSecret=${IMAGE_PULL_SECRET_NAME},config.mongoUrl=mongodb://mongo:***REMOVED***@${IP_ADDR}:30443/***REMOVED***,config.basePath=${IP_ADDR} --namespace ${CLUSTER_NAMESPACE} echo -e "Deploying into: ${PIPELINE_KUBERNETES_CLUSTER_NAME}/${CLUSTER_NAMESPACE}." helm upgrade --install ${RELEASE_NAME} ./chart/${CHART_NAME} --set image.repository=${IMAGE_REPOSITORY},image.tag=${BUILD_NUMBER},image.pullSecret=${IMAGE_PULL_SECRET_NAME},config.mongoUrl=mongodb://mongo:***REMOVED***@${IP_ADDR}:30443/***REMOVED***,config.basePath=${IP_ADDR} --namespace ${CLUSTER_NAMESPACE} echo "==========================================================" echo -e "CHECKING deployment status of release ${RELEASE_NAME} with image tag: ${BUILD_NUMBER}" echo "" for ITERATION in {1..30} do DATA=$( kubectl get pods --namespace ${CLUSTER_NAMESPACE} -a -l release=${RELEASE_NAME} -o json ) NOT_READY=$( echo $DATA | jq '.items[].status.containerStatuses[] | select(.image=="'"${IMAGE_REPOSITORY}:${BUILD_NUMBER}"'") | select(.ready==false) ' ) if [[ -z "$NOT_READY" ]]; then echo -e "All pods are ready:" echo $DATA | jq '.items[].status.containerStatuses[] | select(.image=="'"${IMAGE_REPOSITORY}:${BUILD_NUMBER}"'") | select(.ready==true) ' break # deployment succeeded fi REASON=$(echo $DATA | jq '.items[].status.containerStatuses[] | select(.image=="'"${IMAGE_REPOSITORY}:${BUILD_NUMBER}"'") | .state.waiting.reason') echo -e "${ITERATION} : Deployment still pending..." echo -e "NOT_READY:${NOT_READY}" echo -e "REASON: ${REASON}" if [[ ${REASON} == *ErrImagePull* ]] || [[ ${REASON} == *ImagePullBackOff* ]]; then echo "Detected ErrImagePull or ImagePullBackOff failure. " echo "Please check proper authenticating to from cluster to image registry (e.g. image pull secret)" break; # no need to wait longer, error is fatal elif [[ ${REASON} == *CrashLoopBackOff* ]]; then echo "Detected CrashLoopBackOff failure. " echo "Application is unable to start, check the application startup logs" break; # no need to wait longer, error is fatal fi sleep 5 done if [[ ! -z "$NOT_READY" ]]; then echo "" echo "==========================================================" echo "DEPLOYMENT FAILED" echo "Deployed Services:" kubectl describe services ${RELEASE_NAME}-${CHART_NAME} --namespace ${CLUSTER_NAMESPACE} echo "" echo "Deployed Pods:" kubectl describe pods --selector app=${CHART_NAME} --namespace ${CLUSTER_NAMESPACE} echo "" echo "Application Logs" kubectl logs --selector app=${CHART_NAME} --namespace ${CLUSTER_NAMESPACE} echo "==========================================================" PREVIOUS_RELEASE=$( helm history ${RELEASE_NAME} | grep SUPERSEDED | sort -r -n | awk '{print $1}' | head -n 1 ) echo -e "Could rollback to previous release: ${PREVIOUS_RELEASE} using command:" echo -e "helm rollback ${RELEASE_NAME} ${PREVIOUS_RELEASE}" # helm rollback ${RELEASE_NAME} ${PREVIOUS_RELEASE} # echo -e "History for release:${RELEASE_NAME}" # helm history ${RELEASE_NAME} # echo "Deployed Services:" # kubectl describe services ${RELEASE_NAME}-${CHART_NAME} --namespace ${CLUSTER_NAMESPACE} # echo "" # echo "Deployed Pods:" # kubectl describe pods --selector app=${CHART_NAME} --namespace ${CLUSTER_NAMESPACE} exit 1 fi echo "" echo "==========================================================" echo "DEPLOYMENT SUCCEEDED" echo "" echo -e "Status for release:${RELEASE_NAME}" helm status ${RELEASE_NAME} echo "" echo -e "History for release:${RELEASE_NAME}" helm history ${RELEASE_NAME} # echo "" # echo "Deployed Services:" # kubectl describe services ${RELEASE_NAME}-${CHART_NAME} --namespace ${CLUSTER_NAMESPACE} # echo "" # echo "Deployed Pods:" # kubectl describe pods --selector app=${CHART_NAME} --namespace ${CLUSTER_NAMESPACE} echo "==========================================================" IP_ADDR=$(ibmcloud cs workers ${PIPELINE_KUBERNETES_CLUSTER_NAME} | grep normal | head -n 1 | awk '{ print $2 }') PORT=30200 export MONGO_URL=mongodb://mongo:***REMOVED***@${IP_ADDR}:30443/***REMOVED*** echo "Mongo connection string: ${MONGO_URL}" echo -e "View the application at: http://${IP_ADDR}:${PORT}" ================================================ FILE: .bluemix/toolchain.yml ================================================ version: '2' messages: $i18n: locales.yml template: name: $ref: "#/messages/template.name" description: $ref: "#/messages/template.description" header: '![](toolchain.svg?localize)' icon: secure-lock-helm.svg required: - build - repo info: git url: >- [https://github.com/open-toolchain/simple-helm-toolchain](https://github.com/open-toolchain/simple-helm-toolchain) git branch: >- [master](https://github.com/open-toolchain/simple-helm-toolchain/tree/master) toolchain: name: 'innovate-toolchain' template: getting_started: $ref: "#/messages/template.gettingStarted" services: repo: service_id: hostedgit parameters: repo_name: 'innovate-bank' repo_url: 'https://github.com/IBM/innovate-digital-bank' type: clone has_issues: true enable_traceability: true build: service_id: pipeline parameters: services: - repo name: 'innovate-bank' ui-pipeline: true configuration: content: $text: pipeline.yml env: GIT_REPO: repo CF_APP_NAME: '{{form.pipeline.parameters.app-name}}' REGISTRY_REGION_ID: '{{form.pipeline.parameters.registry-region}}' REGISTRY_NAMESPACE: '{{form.pipeline.parameters.registry-namespace}}' API_KEY: '{{form.pipeline.parameters.api-key}}' PROD_REGION_ID: '{{form.pipeline.parameters.prod-region}}' PROD_CLUSTER_NAME: '{{form.pipeline.parameters.prod-cluster-name}}' PROD_CLUSTER_NAMESPACE: '{{form.pipeline.parameters.prod-cluster-namespace}}' execute: true webide: service_id: orion form: pipeline: parameters: app-name: '{{services.repo.parameters.repo_name}}' prod-cluster-namespace: prod schema: $ref: deploy.json ================================================ FILE: .gitignore ================================================ # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* # Runtime data pids *.pid *.seed *.pid.lock # Directory for instrumented libs generated by jscoverage/JSCover lib-cov # Coverage directory used by tools like istanbul coverage # nyc test coverage .nyc_output # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) .grunt # Bower dependency directory (https://bower.io/) bower_components # node-waf configuration .lock-wscript # Compiled binary addons (http://nodejs.org/api/addons.html) build/Release # Dependency directories node_modules/ node_modules_linux/ jspm_packages/ # Typescript v1 declaration files typings/ # Optional npm cache directory .npm # Optional eslint cache .eslintcache # Optional REPL history .node_repl_history # Output of 'npm pack' *.tgz # Yarn Integrity file .yarn-integrity # dotenv environment variables file .env # other .DS_Store license_accepted buildContext.tar package-lock.json ================================================ FILE: .ibm-project ================================================ {"build-release-ready":false,"build-debug-ready":true,"deploy-image":"","chart-path":"","ibm-cluster":"tokyo-k8s"} ================================================ FILE: .travis.yml ================================================ language: node_js node_js: 8 sudo: required group: stable dist: trusty os: linux services: - docker addons: apt: sources: - debian-sid install: - "npm -g install npm" - "./scripts/install_bx.sh" before-script: - "sudo apt-get install cabal-install" - "cabal update" - "cabal install shellcheck" script: "npm test" ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing This is an open source project, and we appreciate your help! We use the GitHub issue tracker to discuss new features and non-trivial bugs. In addition to the issue tracker, [#journeys on Slack](https://dwopen.slack.com) is the best way to get into contact with the project's maintainers. To contribute code, documentation, or tests, please submit a pull request to the GitHub repository. Generally, we expect two maintainers to review your pull request before it is approved for merging. For more details, see the [MAINTAINERS](MAINTAINERS.md) page. ================================================ FILE: DOCS.md ================================================ # Docs ## Microservices ### Portal [3100:30200] Loads the UI and takes care of user sessions. Communicates with all other microservices. ### Authentication [3200:30100] Handles user profile creation, as well as login & logout. #### Endpoints: ##### /api/user/create Description: Creates a new user account Method: POST Example input: ``` { uuid: String, name: String, email: String, phone: String, gender: String, dob: String, eid: String, password: String } ``` ##### /api/user/authenticate Description: Authenticates a user Method: POST Example input: ``` { email: String, password: String } ``` ##### /api/user/get Description: Returns a list of all users Method: GET ### Accounts [3400:30120] Handles creation, management, and retrieval of a user's banking accounts. #### Endpoints: ##### /api/accounts/create Description: Creates a new user account Method: POST Example input: ``` { uuid: String, type: String, currency: String, } ``` Notes: The parameter uuid links the account to a user's unique identifier. Type has to be one of the following: current, savings, credit, prepaid ##### /api/accounts/get Description: Retrieves a user's accounts Method: POST Example input: ``` { uuid: String } ``` ##### /api/accounts/deposit Description: Deposits an amount to a user's account Method: POST Example input: ``` { number: String, amount: Number } ``` Notes: The parameter number references an account ##### /api/accounts/withdraw Description: Withdraws an amount from a user's account Method: POST Example input: ``` { number: String, amount: Number } ``` ##### /api/accounts/drop Description: Drops the accounts collection Method: GET ### Transactions [3600:30140] Handles creation and retrieval of transactions #### Endpoints: ##### /api/transactions/create Description: Creates a new transaction Method: POST Example input: ``` { uuid: String, amount: String, currency: String, description: String, date: String, category: String } ``` > Category has to be one of the following: > - groceries > - eating_out > - transport > - bills > - expenses > - cash > - holidays ##### /api/transactions/get Description: Retrieves a user's transactions Method: POST Example input: ``` { uuid: String } ``` ##### /api/transactions/drop Description: Drops the transactions collection Method: GET ### Bills [3800:30160] Handles creation, payment, and retrieval of bills #### Endpoints: ##### /api/bills/create Description: Creates a new bill Method: POST Example input: ``` { uuid: String, category: String, entity: String, account_no: String, amount: String, date: String } ``` > Category has to be one of the following: > - utilities > - home_entertainment > - mobile_phone > - credit_card ##### /api/bills/get Description: Retrieves a user's bills Method: POST Example input: ``` { uuid: String } ``` ##### /api/bills/drop Description: Drops the bills collection Method: GET ### Support [4000:30180] Handles communication with Watson Assistant on IBM Cloud to enable a dummy support chat feature. ### Userbase [4100:30050] Simulates a fake userbase for the app. Periodically loops through all user accounts and adds randomized bills and transactions for them. ================================================ FILE: Dockerfile ================================================ FROM ibmcom/ibmnode:latest LABEL maintainer="***REMOVED***" # Upgrade npm to latest version RUN npm install -g yarnpkg RUN yarn global add npm RUN npm -v RUN npm config set unsafe-perm=true # Copy app & set working directory WORKDIR /app COPY . /app RUN npm install; npm prune --production ENV NODE_ENV production ENV SESSION_SECRET ***REMOVED*** ENV MONGO_USERNAME mongo ENV MONGO_PASSWORD ***REMOVED*** ENV MONGO_DB ***REMOVED*** EXPOSE 3100 3200 3400 3600 3800 4000 4100 CMD [ "npm","start" ] ================================================ FILE: Dockerfile-tools ================================================ FROM ibmcom/ibmnode ENV PORT 3000 WORKDIR "/app" # Bundle app source COPY . /app EXPOSE 3000 CMD ["/bin/bash"] ARG bx_dev_user=root ARG bx_dev_userid=1000 RUN BX_DEV_USER=$bx_dev_user RUN BX_DEV_USERID=$bx_dev_userid RUN if [ $bx_dev_user != "root" ]; then useradd -ms /bin/bash -u $bx_dev_userid $bx_dev_user; fi ================================================ FILE: LICENSE.md ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [2018] [Amal Amine] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: MAINTAINERS.md ================================================ # Maintainers Guide This guide is intended for maintainers - anybody with commit access to one or more Code Pattern repositories. ## Methodology This repository does not have a traditional release management cycle, but should instead be maintained as a useful, working, and polished reference at all times. While all work can therefore be focused on the master branch, the quality of this branch should never be compromised. The remainder of this document details how to merge pull requests to the repositories. ## Merge approval The project maintainers use LGTM (Looks Good To Me) in comments on the pull request to indicate acceptance prior to merging. A change requires LGTMs from two project maintainers. If the code is written by a maintainer, the change only requires one additional LGTM. ## Reviewing Pull Requests We recommend reviewing pull requests directly within GitHub. This allows a public commentary on changes, providing transparency for all users. When providing feedback be civil, courteous, and kind. Disagreement is fine, so long as the discourse is carried out politely. If we see a record of uncivil or abusive comments, we will revoke your commit privileges and invite you to leave the project. During your review, consider the following points: ### Does the change have positive impact? Some proposed changes may not represent a positive impact to the project. Ask whether or not the change will make understanding the code easier, or if it could simply be a personal preference on the part of the author (see [bikeshedding](https://en.wiktionary.org/wiki/bikeshedding)). Pull requests that do not have a clear positive impact should be closed without merging. ### Do the changes make sense? If you do not understand what the changes are or what they accomplish, ask the author for clarification. Ask the author to add comments and/or clarify test case names to make the intentions clear. At times, such clarification will reveal that the author may not be using the code correctly, or is unaware of features that accommodate their needs. If you feel this is the case, work up a code sample that would address the pull request for them, and feel free to close the pull request once they confirm. ### Does the change introduce a new feature? For any given pull request, ask yourself "is this a new feature?" If so, does the pull request (or associated issue) contain narrative indicating the need for the feature? If not, ask them to provide that information. Are new unit tests in place that test all new behaviors introduced? If not, do not merge the feature until they are! Is documentation in place for the new feature? (See the documentation guidelines). If not do not merge the feature until it is! Is the feature necessary for general use cases? Try and keep the scope of any given component narrow. If a proposed feature does not fit that scope, recommend to the user that they maintain the feature on their own, and close the request. You may also recommend that they see if the feature gains traction among other users, and suggest they re-submit when they can show such support. ================================================ FILE: README.md ================================================ [![Build Status](https://travis-ci.org/IBM/innovate-digital-bank.svg?branch=master)](https://travis-ci.org/IBM/innovate-digital-bank) # Building a Digital Bank with Node.js, Express, MongoDB, & Kubernetes In this code pattern, we will build a dummy digital bank composed of a set of microservices that communicate with each other. We'll be using Node.js, Express, MongoDB, and the IBM Cloud Container Service. Development of [cloud native apps](https://www.cncf.io/blog/2017/05/15/developing-cloud-native-applications/) that are broken down into a set of [microservices](http://microservices.io/) has been praised and commended as best-practice in software development methodologies. Software stacks like [Kubernetes](https://kubernetes.io/), which enable cloud native computing, have therefore picked up quite a bit of popularity. It’s a little _(a lot)_ more fun, however, to build a so-called cloud native app, than to talk about one. So here's our attempt: We’ll take a use-case that has a bit of real-world familiarity to it — A digital bank. Naturally inspired by [Monzo](http://monzo.com/). Let’s call it Innovate. [A live version deployed on a Kubernetes cluster in IBM Cloud is available for you to try here](http://ibm.biz/digibank). To test it out, sign up for an account. A process runs periodically to dump randomized transactions and bills for user accounts, so give it a couple of minutes and refresh to see your populated profile. ![Screens](doc/source/images/screens-1.png) ![Screens](doc/source/images/screens-2.png) ## Learning objectives When you've completed this Code Pattern, you will understand how to: * Break an application down to a set of microservices * Create and manage a Kubernetes cluster on IBM Cloud * Deploy to a Kubernetes cluster on IBM Cloud * Deploy to IBM Cloud Private ## Flow When thinking of business capabilities, our imaginary bank will need the following set of microservices: 1. *Portal:* Loads the UI and takes care of user sessions and relies on all other microservices for core functionality. 2. *Authentication:* Handles user profile creation, as well as login & logout. 3. *Accounts:* Handles creation, management, and retrieval of a user’s banking accounts. 4. *Transactions:* Handles creation and retrieval of transactions made against users' bank accounts. 5. *Bills:* Handles creation, payment, and retrieval of bills. 6. *Support:* Handles communication with Watson Assistant to enable a support chat feature. ![Demo architecture](doc/source/images/architecture.png) ## Included components * [IBM Cloud Kubernetes Service](https://console.bluemix.net/docs/containers/): IBM Cloud Kubernetes Service manages highly available apps inside Docker containers and Kubernetes clusters on the IBM Cloud. * [Microservice Builder](https://www.ibm.com/us-en/marketplace/microclimate): Learn, build, run, and manage applications in a microservices framework. * [Watson Assistant](https://www.ibm.com/cloud/watson-assistant/): Create a chatbot with a program that conducts a conversation via auditory or textual methods. ## Featured technologies * [Microservices](https://developer.ibm.com/technologies/microservices/): Collection of fine-grained, loosely coupled services using a lightweight protocol to provide building blocks in modern application composition in the cloud. * [Node.js](https://nodejs.org/): An open-source JavaScript run-time environment for executing server-side JavaScript code. * [Containers](https://developer.ibm.com/technologies/containers/): Virtual software objects that include all the elements that an app needs to run. * [Databases](https://developer.ibm.com/technologies/databases/): Repository for storing and managing collections of data. * [Hybrid Cloud](https://developer.ibm.com/depmodels/hybrid/): Enabling customers to draw on the capabilities of public cloud service providers while using private cloud deployment for sensitive applications and data. ## Watch the Video [![](https://img.youtube.com/vi/1F1EnnMrsZ8/0.jpg)](https://www.youtube.com/watch?v=1F1EnnMrsZ8) ## Setup You have multiple options to setup your own instance: * [Run it locally](#run-locally) * [Deploy to IBM Cloud the hard way (manual, multi-stage)](#deploy-to-ibm-cloud-the-hard-way) * [Deploy to IBM Cloud Private](#deploy-to-ibm-cloud-private) ## Run Locally ### 1. Clone the repo Clone the `innovate-digital-bank` repository locally. In a terminal, run: ```bash $ git clone https://github.com/IBM/innovate-digital-bank.git ``` ### 2. Create an Instance of MongoDB This code pattern depends on MongoDB as a session and data store. From the [IBM Cloud catalog](https://cloud.ibm.com/catalog), find **Compose for MongoDB** and click create. Give it a name, choose a region, pick the standard pricing plan and click create. **Get your mongo connection string. Almost all your microservices need it; keep it safe!** ![kubectl config](doc/source/images/11.png) ### 3. Configure your environment variables Each of the 7 microservices must have a _**.env**_ file that stores all credentials. An example is already provided within each folder. From the directory of each microservice, copy the example file, rename it to _**.env**_, and fill it with the appropriate values. For example, from within the **/innovate** folder, navigate into the accounts folder ```bash $ cd accounts ``` Next, copy and rename the _**.env.example**_ folder ```bash $ cp .env.example .env ``` Finally, edit your **.env** folder and add your Mongodb connection string ***Repeat these steps for all microservices. In addition to your mongo URL, most will need the public IP address of your Kubernetes cluster, _You can find that under the overview of your cluster on IBM Cloud_.*** ### 4. Configure your environment mode When running the app locally without Kubernetes, the microservices do not run on the NodePorts specified in our helm chart, so we need to point our portal and userbase microservices to the correct ports. If you're running on macOS or any linux-based system, run the following in a terminal from the git repo's directory ```bash $ export NODE_ENV=development ``` if you're running on Windows, edit the NODE_ENV attribute in your .env file from within the **/portal** folder and the **/userbase** folder to the following: ```bash NODE_ENV=development ``` ### 5. Run Finally, navigate to each microservice folder, and start it. Make sure you run the 7 microservice in 7 separate terminals. ```bash $ npm start ``` You can now visit `localhost:3100` to access the portal ## Deploy to IBM Cloud the Hard Way > NOTE: This guide requires a paid/upgraded account on IBM Cloud. You **cannot** complete the steps with a free or lite account. 1. [Get the tools](#1-get-the-tools) 2. [Clone the repo](#2-clone-the-repo) 3. [Login to IBM Cloud](#3-login-to-ibm-cloud) 4. [Create a cluster](#4-create-a-cluster-) 5. [Create an instance of MongoDB](#5-create-an-instance-of-mongodb) 6. [Configure your deploy target](#6-configure-your-deploy-target) 7. [Configure your environment variables](#7-configure-your-environment-variables) 8. [Configure kubectl](#8-configure-kubectl) 9. [Initialize helm](#9-initialize-helm) 10. [Deploy](#10-deploy) ### 1. Get the tools You'll need each of the following pre-requisites: * The [Kubernetes CLI](https://kubernetes.io/docs/tasks/tools/install-kubectl/) * The [IBM Cloud Developer Tools CLI](https://cloud.ibm.com/docs/cli/index.html#overview) * Thee IBM Cloud plugins: `container-service`, `container-registry`, and `dev` ```bash $ ibmcloud plugin install container-service $ ibmcloud plugin install container-registry $ ibmcloud plugin install dev ``` ### 2. Clone the repo Clone the `innovate-digital-bank` repository locally. In a terminal, run: ```bash $ git clone https://github.com/IBM/innovate-digital-bank.git ``` ### 3. Log into IBM Cloud We'll need to log into IBM Cloud through both the [console](https://cloud.ibm.com/) and the terminal. > NOTE: If you need to specify the region you want to deploy in, you can do so by adding the `-a` flag followed by the region URL. ```bash $ ibmcloud login ``` ### 4. Create a cluster From the catalog, find **Containers in Kubernetes Clusters** and click create. Choose a region and a cluster type, and create your cluster. Allow it some time to deploy. ![kubectl config](doc/source/images/9.png) ### 5. Create an instance of MongoDB This demo depends on MongoDB as a session and data store. From the [catalog](https://cloud.ibm.com/catalog/), find **Compose for MongoDB** and click create. Give it a name, choose a region, pick the standard pricing plan and click create. **Get your mongo connection string. Almost all your microservices need it; keep it safe!** ![kubectl config](doc/source/images/11.png) ### 6. Configure your deploy target Each of the 7 docker images needs to be pushed to your docker image registry on IBM Cloud. You need to set the correct _**deploy target**_. Depending on the region you've created your cluster in, your URL will be in the following format ``` registry..bluemix.net// ``` For example, to deploy the accounts microservice to my docker image registry in the US-South region, my **deploy_target** will be: ``` registry.ng.bluemix.net/amalamine/innovate-accounts ``` If you need to get your namespace, run: ```bash $ ibmcloud cr namespace-list ``` You can also add a new namespace by running: ```bash $ ibmcloud cr namespace-add ``` From the directory of each microservice, replace the deploy target in ***cli-config.yml*** and in ***/chart/innovate-/values.yaml*** with the correct one For example, from within the **/innovate** folder, navigate into the accounts folder ```bash $ cd accounts ``` Next, edit line 58 of [cli-config.yaml](https://github.com/IBM/innovate-digital-bank/blob/master/accounts/cli-config.yml) file. Replace the ***deploy-image-target*** with the correct value. ``` deploy-image-target: "registry.ng.bluemix.net/amalamine/innovate-accounts" ``` ![kubectl config](doc/source/images/12.png) Edit line 6 of the [values.yaml](https://github.com/IBM/innovate-digital-bank/blob/master/accounts/chart/innovate-accounts/values.yaml) file. Replace the ***repository*** with the correct value. ``` repository: registry.ng.bluemix.net/amalamine/innovate-accounts ``` ![kubectl config](doc/source/images/13.png) **Repeat these steps for all 7 microservices.** ### 7. Configure your environment variables Each of the 7 microservices must have a _**.env**_ file that stores all credentials. An example is already provided within each folder. From the directory of each microservice, copy the example file, rename it to _**.env**_, and fill it with the appropriate values. For example, from within the **/innovate** folder, navigate into the accounts folder ```bash $ cd accounts ``` Next, copy and rename the _**.env.example**_ folder ```bash $ cp .env.example .env ``` Finally, edit your .env folder and add your Mongodb connection string ***Repeat these steps for all microservices. In addition to your mongo URL, most will need the public IP address of your Kubernetes cluster, _You can find that under the overview of your cluster on IBM Cloud_.*** ### 8. Configure kubectl Run the following command: ```bash $ ibmcloud cs cluster-config ``` Then copy the output and paste it in your terminal ### 9. Initialize helm If you dont have helm installed, see the [Helm Documentation](https://docs.helm.sh/using_helm/#install-helm). ```bash $ helm init ``` ### 10. Deploy Finally, navigate to each microservice folder, and run the following command ```bash $ ibmcloud dev deploy ``` Once done, you'll be able to access the portal on port _30060_ of your cluster's _public IP address_, which you can find under the overview of your cluster on IBM Cloud. Or if you are logged in `ibmcloud` cli, you can find your public ip of your worker node by ```bash $ ibmcloud cs workers ``` # Deploy to IBM Cloud Private If you have an instance of IBM Cloud Private running, you can follow the steps below to deploy the app. If you'd like to deploy your own instance of ICP, [you can follow this great writeup](https://github.com/IBM/deploy-ibm-cloud-private). 1. [Create a persistent volume](#1-create-a-persistent-volume) 2. [Create a persistent volume claim](#2-create-a-persistent-volume-claim) 3. [Create an instance of MongoDB](#3-create-an-instance-of-mongodb) 4. [Configure your environment variables](#4-configure-your-environment-variables) 5. [Add your ICP's address to your hosts file](#5-add-your-icps-address-to-your-hosts-file) 6. [Login to docker](#6-login-to-docker) 7. [Configure kubectl](#7-configure-kubectl) 8. [Configure cloudctl](#8-configure-cloudctl) 9. [Install Helm](#9-install-helm) 10. [Deploy](#10-deploy) ### 1. Create a persistent volume This code pattern depends on MongoDB as a session and data store. From ICP's menu, click on Storage > Create persistent volume. Give it a name and a capacity, choose storage type _**Hostpath**_, and add a _**path parameter**_ [More details here](./creating-pv.md) ### 2. Create a persistent volume claim From ICP's menu, click on **_Storage_** > **_Create Persistent Volume Claim_**. Give it a name and a storage request value. [More details here](./creating-pvc.md) ### 3. Create an instance of MongoDB From the catalog, choose MongoDB. Give it a **_Name_**, specify the **_Existing Volume Claim Name_**, and give it a **_password_**. [More details here](./mongo-db.md) ***Get your mongo connection string; Almost all your microservices need it; keep it safe!*** Your connection string will be in the following format: ``` mongodb://:@:/ ``` ### 4. Configure your environment variables Each of the 7 microservices must have a _**.env**_ file. An example is already provided within each folder. From the directory of each microservice, copy the example file, rename it to _**.env**_, and fill it with the appropriate values. For example, from within the **/innovate** folder, navigate into the accounts folder ```bash $ cd accounts ``` Next, copy and rename the _**.env.example**_ folder ```bash $ cp .env.example .env ``` Finally, edit your **.env** folder and add your Mongodb connection string ***Repeat those steps for all microservices. In addition to your mongo url, you may the IP address of your ICP.*** ### 5. Add your ICP's address to your hosts file Add an entry to your /etc/hosts file as follows ```ini mycluster.icp ``` ### 6. Login to docker For Mac users, if you are running ICP locally in VM you would also have to add the cluster info to the insecure registry. To do so, go to the Docker icon in the system tray, select **_Preferences_** > **_Daemon_**, and choose to enable experimental features and add cluster to insecure registries list. ![Docker 1](doc/source/images/docker1.png) ![Docker 2](doc/source/images/docker2.png) ```bash $ docker login mycluster.icp:8500 ``` ### 7. Configure `kubectl` From your ICP's dashboard, copy the `kubectl` commands under `***REMOVED***` > `configure client` ![kubectl config](doc/source/images/5.png) ### 8. Configure `cloudctl` IBM Cloud Private provides a custom CLI, similar to `kubectl`, called `cloudctl`. To find the CLI for your ICP version, navigate to **_Menu_** > **_Command Line Tools_** > **_Cloud Private CLI_** and follow instruction to get `cloudctl` installed. ![cloudctl config](doc/source/images/cloudctl1.png) ![cloudctl config](doc/source/images/cloudctl2.png) Once you have `cloudctl` cli installed, log into **ICP** ```bash $ cloudctl login -a https://mycluster.icp:8443 -u ***REMOVED*** --skip-ssl-validation ``` > NOTE: The default password for the local ICP VM is _***REMOVED***_. ### 9. Install Helm If you dont have helm installed, see the [Helm Documentation](https://docs.helm.sh/using_helm/#install-helm). ```bash $ helm init ``` ### 10. Deploy Finally, navigate to each microservice, and run the following command ```bash $ ibmcloud dev deploy ``` Once the deployment is successfully completed, you can access the portal on port _30060_ of your _ICP's IP address_. ## (Optional) Adding Support with Watson Assistant The *Support* microservice connects to an instance of Watson Assistant on IBM Cloud to simulate a chat with a virtual support agent. *This is an optional step.* You only need to continue if you'd like to enable the *Support* feature in the app. 1. [Create an instance of Watson Assistant](#1-create-an-instance-of-watson-assistant) 2. [Get your credentials](#2-get-your-credentials) 3. [Configure your environment variables](#3-configure-your-environment-variables) 4. [Deploy](#4-deploy) ### 1. Create an instance of Watson Assistant From the [IBM Cloud Catalog](https://cloud.ibm.com/login), choose Watson Assistant, and click *Create*. ### 2. Get your credentials Navigate to the *Credentials* tab and copy your credentials. ![Watson Conversation](doc/source/images/8.png) ### 3. Configure your environment variables From within the support folder, edit your `.env` to include your newly acquired credentials. ### 4. Deploy Re-deploy the *Support* microservice, the support feature should now be accessible through the portal. ```bash $ ibmcloud dev deploy ``` ## Troubleshooting 1. Trouble with IBM Cloud CLI? See their [Troubleshooting Documentation](https://console.bluemix.net/docs/cli/ts_createapps.html#troubleshoot). 2. Trouble with IBM Cloud Private? See their [Troubleshooting Documentation](https://www.ibm.com/support/knowledgecenter/en/SS8G7U_18.2.0/com.ibm.app.mgmt.doc/content/trouble_common_deployment_errors.htm) 3. IBM Cloud Private on local VM not working? Check the ICP version, Vagrant version and VirtualBox version carefully. Update them if necessary. If all else fails, `vagrant destroy` and `vagrant up` again to reset the cluster. ## Learn more * **Containers Code Patterns**: Enjoyed this Code Pattern? Check out our other [Container Code Patterns](https://developer.ibm.com/technologies/containers/). * **IBM Cloud Private Code Patterns**: Enjoyed this Code Pattern? Check out our other [IBM Cloud Private Code Patterns](https://developer.ibm.com/components/cloud-private/) * **Kubernetes on IBM Cloud**: Deliver your apps with the combined the power of [Kubernetes and Docker on IBM Cloud](https://www.ibm.com/cloud/container-service) ## Docs Additional documentation of all the backend endpoints is available in [DOCS.md](DOCS.md). ## License This code pattern is licensed under the Apache Software License, Version 2. Separate third party code objects invoked within this code pattern are licensed by their respective providers pursuant to their own separate licenses. Contributions are subject to the [Developer Certificate of Origin, Version 1.1 (DCO)](https://developercertificate.org/) and the [Apache Software License, Version 2](http://www.apache.org/licenses/LICENSE-2.0.txt). [Apache Software License (ASL) FAQ](http://www.apache.org/foundation/license-faq.html#WhatDoesItMEAN) ================================================ FILE: accounts/.cfignore ================================================ .git/ node_modules/ test/ vcap-local.js ================================================ FILE: accounts/.dockerignore ================================================ node_modules/ test/ .bluemix/ ================================================ FILE: accounts/.gitignore ================================================ # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* # Runtime data pids *.pid *.seed *.pid.lock # Directory for instrumented libs generated by jscoverage/JSCover lib-cov # Coverage directory used by tools like istanbul coverage # nyc test coverage .nyc_output # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) .grunt # Bower dependency directory (https://bower.io/) bower_components # node-waf configuration .lock-wscript # Compiled binary addons (http://nodejs.org/api/addons.html) build/Release # Dependency directories node_modules/ jspm_packages/ # Typescript v1 declaration files typings/ # Optional npm cache directory .npm # Optional eslint cache .eslintcache # Optional REPL history .node_repl_history # Output of 'npm pack' *.tgz # Yarn Integrity file .yarn-integrity # dotenv environment variables file .env # other .DS_Store license_accepted ================================================ FILE: accounts/.ibm-project ================================================ {"build-release-ready":true,"build-debug-ready":false,"deploy-image":"","chart-path":"","ibm-cluster":""} ================================================ FILE: accounts/Dockerfile ================================================ FROM ibmcom/ibmnode:latest LABEL maintainer="***REMOVED***" #RUN apt-get install -y nodejs npm WORKDIR /app # Install app dependencies COPY . /app RUN cd /app; npm install; npm prune --production ENV NODE_ENV production ENV PORT 3400 EXPOSE 3400 CMD [ "npm","start" ] ================================================ FILE: accounts/Dockerfile-tools ================================================ FROM ibmcom/ibmnode ENV PORT 3400 WORKDIR "/app" # Bundle app source COPY . /app EXPOSE 3400 CMD ["/bin/bash"] ARG bx_dev_user=root ARG bx_dev_userid=1000 RUN BX_DEV_USER=$bx_dev_user RUN BX_DEV_USERID=$bx_dev_userid RUN if [ $bx_dev_user != "root" ]; then useradd -ms /bin/bash -u $bx_dev_userid $bx_dev_user; fi ================================================ FILE: accounts/app.js ================================================ 'use strict'; const express = require('express'); const bodyParser = require('body-parser'); const accounts = require('./mongoose/account'); var app = express(); app.use(bodyParser.json()); app.all('*', function (req, res, next) { res.header("Access-Control-Allow-Origin", "*"); res.header("Access-Control-Allow-Headers", "*"); next(); }); app.post('/api/accounts/create', function (req, res) { let number = Math.floor(Math.random() * 900000); let balance = 0; if (req.body.type === 'current') { balance = 5000; } if (req.body.type === 'credit') { balance = 40000; } var newAccount = { uuid: req.body.uuid, type: req.body.type, currency: req.body.currency, balance: balance, number: number }; accounts.create(newAccount, function (err) { if (err) { console.log(err); res.status(500).send(err); return; } console.log("Account created"); res.status(200).send({'message': 'Done!'}); }); }); app.post('/api/accounts/get', function (req, res) { accounts.find({'uuid': req.body.uuid}, function (err, results) { if (err) { console.log(err); res.status(500).send({'err': err}); return; } res.status(200).send(results); }); }); app.post('/api/accounts/deposit', function (req, res) { accounts.find({'number': req.body.number}, function (err, results) { if (err) { console.log(err); res.status(500).send({'err': err}); return; } if (results.length === 0) { console.log('account not found'); res.status(500).send({'err': 'account not found'}); return; } let amount = Number(results[0].balance) + Number(req.body.amount); accounts.findOneAndUpdate({'number': req.body.number}, {'balance': amount}, function (err, results) { if (err) { console.log(err); res.status(500).send({'err': err}); return; } res.status(200).send(results); }); }); }); app.post('/api/accounts/withdraw', function (req, res) { accounts.find({'number': req.body.number}, function (err, results) { if (err) { console.log(err); res.status(500).send({'err': err}); return; } if (results.length === 0) { console.log('account not found'); res.status(500).send({'err': 'account not found'}); return; } let amount = Number(results[0].balance) - Number(req.body.amount); accounts.findOneAndUpdate({'number': req.body.number}, {'balance': amount}, function (err, results) { if (err) { console.log(err); res.status(500).send({'err': err}); return; } res.status(200).send(results); }); }); }); app.get('/api/accounts/drop', function (req, res) { accounts.collection.drop(); res.status(200).send({'message': 'Done!'}); }); module.exports = app; ================================================ FILE: accounts/chart/innovate-accounts/Chart.yaml ================================================ apiVersion: v1 description: A Helm chart for innovate bank portal name: innovate-accounts version: 1.0.0 ================================================ FILE: accounts/chart/innovate-accounts/templates/deployment.yaml ================================================ apiVersion: extensions/v1beta1 kind: Deployment metadata: name: "{{ .Chart.Name }}-deployment" labels: chart: '{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}' spec: replicas: {{ .Values.replicaCount }} strategy: type: RollingUpdate rollingUpdate: maxUnavailable: 0 maxSurge: 1 revisionHistoryLimit: {{ .Values.revisionHistoryLimit }} template: metadata: labels: app: "{{ .Chart.Name }}-selector" spec: containers: - name: "{{ .Chart.Name }}" image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" imagePullPolicy: {{ .Values.image.pullPolicy }} livenessProbe: httpGet: path: /health port: {{ .Values.service.servicePort }} initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds}} periodSeconds: {{ .Values.livenessProbe.periodSeconds}} resources: requests: cpu: "{{ .Values.image.resources.requests.cpu }}" memory: "{{ .Values.image.resources.requests.memory }}" env: - name: PORT value : "{{ .Values.service.servicePort }}" ================================================ FILE: accounts/chart/innovate-accounts/templates/hpa.yaml ================================================ {{ if .Values.hpa.enabled }} apiVersion: autoscaling/v2alpha1 kind: HorizontalPodAutoscaler metadata: name: "{{ .Chart.Name }}-hpa-policy" namespace: spec: scaleTargetRef: apiVersion: apps/v2alpha1 kind: Deployment name: "{{ .Chart.Name }}-deployment" minReplicas: {{ .Values.hpa.minReplicas }} maxReplicas: {{ .Values.hpa.maxReplicas }} metrics: - type: Resource resource: name: cpu targetAverageUtilization: {{ .Values.hpa.metrics.cpu.targetAverageUtilization }} - type: Resource resource: name: memory targetAverageUtilization: {{ .Values.hpa.metrics.memory.targetAverageUtilization }} {{ end }} ================================================ FILE: accounts/chart/innovate-accounts/templates/service.yaml ================================================ apiVersion: v1 kind: Service metadata: annotations: prometheus.io/scrape: 'true' name: "{{ .Chart.Name }}" labels: chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}" spec: type: {{ .Values.service.type }} ports: - port: {{ .Values.service.servicePort }} nodePort: {{ .Values.service.serviceNodePort }} selector: app: "{{ .Chart.Name }}-selector" ================================================ FILE: accounts/chart/innovate-accounts/values.yaml ================================================ # This is a YAML-formatted file. # Declare variables to be passed into your templates. replicaCount: 1 revisionHistoryLimit: 3 image: repository: mycluster.icp:8500/default/innovate-accounts tag: v1.0.0 pullPolicy: Always resources: requests: cpu: 500m memory: 512Mi livenessProbe: initialDelaySeconds: 3000 periodSeconds: 1000 service: name: Node type: NodePort servicePort: 3400 serviceNodePort: 30120 hpa: enabled: false minReplicas: 2 maxReplicas: 3 metrics: cpu: targetAverageUtilization: 80 memory: targetAverageUtilization: 80 services: ================================================ FILE: accounts/cli-config.yml ================================================ # The IBM version of this configuration version : 0.0.3 # The container name used for the run container container-name-run : "innovate-accounts-run" # The container name used for the tools container container-name-tools : "innovate-accounts-tools" # The project root on the host for the run container to mount to container-path-run host-path-run : . # The project root on the host for the tools container to mount to container-path-tools host-path-tools : . # The project root in the run container to mount to host-path-run container-path-run : "/app" # The project root in the tools container that will be mounted to host-path-tools container-path-tools : "/app" # The list of additional mounts between the host and the run container in the form [host_path:container_path] container-mounts-run: - "./node_modules_linux": "/app/node_modules" # The list of additional mounts between the host and the tools container in the form [host_path:container_path] container-mounts-tools: - "./node_modules_linux": "/app/node_modules" # The port mappings between the host and the container in the form [host:container] container-port-map : "3400:3400" # The port mappings between the host and the container for the debug port in the form [host:container] container-port-map-debug : "5862:5862" # The name for the dockerfile for the run container dockerfile-run : "Dockerfile" # The name for the dockerfile for the tools container dockerfile-tools : "Dockerfile-tools" # The name of image to create from dockerfile-run image-name-run : "innovate-accounts-run" # The name of image to create from dockerfile-tools image-name-tools : "innovate-accounts-tools" # The command to build the code and docker image for RUN build-cmd-run : "npm install" # The command to execute tests for the code in the tools container test-cmd : "npm run test" # The command to build the code and docker image for DEBUG build-cmd-debug : "npm install" # The command to run the code in the run container run-cmd : "" # The command to execute debug of the code in the tools container debug-cmd : "npm run debug" # The command to stop the code stop-cmd : "npm stop" # The relative path to the helm chart used for Kubernetes deployment chart-path: "chart/innovate-accounts" deploy-target: "container" deploy-image-target: "mycluster.icp:8500/default/innovate-accounts" ================================================ FILE: accounts/idt.js ================================================ 'use strict' /* * Wrapper for the idt (IBM Developer Tools) command. * Run with the same arguments as `idt`, e.g. * `node idt.js build` -> `idt build`. * If `idt` isn't installed, this will prompt you to install. Or you can run * `node idt.js install` to automatically install idt and any other * required dependencies (e.g. docker, git, kubernetes, helm). * */ const fs = require('fs'); const process = require('process'); const cp = require('child_process'); const request = require('request'); const path = require('path'); const chalk = require('chalk'); const node = process.execPath; // Array of args passed to idt.js. const args = process.argv.slice(2); let win = (process.platform === 'win32'); // Either install idt or run idt + args. if (args.includes('install')) { downloadInstaller(); } else { // TODO(gib): Check for idt once this works in scripts: // const checkCmd = win ? 'where idt' : 'which idt'; const checkCmd = 'bx plugin show dev'; let hasIDT = false; try { console.log(chalk.blue('Checking for idt')); cp.execSync(checkCmd); // Don't inherit stdio, we don't want to print the output. hasIDT = true; // If we didn't have idt, the previous command would have thrown. } catch (e) { const prompt = require('prompt-confirm'); new prompt({ name: 'install', message: 'IDT not found, do you want to install it? y/N', default: false }).ask((answer) => { if (answer) { downloadInstaller(() => runIDT(args)); } else { console.error(chalk.red(`Not installing idt, so not running: idt ${args.join(' ')}`)); } }); } if (hasIDT) runIDT(args); } // Run IDT with whatever args we were given. function runIDT(args) { const cmd = 'bx dev ' + args.join(' '); console.log(chalk.blue('Running:'), cmd); cp.execSync(cmd, {stdio: 'inherit'}); } // Download the IDT installer script and trigger runInstaller(). function downloadInstaller(cb) { const url = win ? 'https://ibm.biz/yeoman-idt-win-install' : 'http://ibm.biz/yeoman-idt-install'; const fileName = url.split('/').pop() console.log(chalk.blue('Downloading installer from:'), url); const file = fs.createWriteStream(fileName); request .get({url, followAllRedirects: true}) .on('error', (err) => { console.error(err); }) .pipe(file) .on('finish', () => runInstaller(fileName, cb)); } // Run the installer script and trigger optional callback (cb). function runInstaller(fileName, cb) { const shell = win ? 'powershell.exe' : 'bash'; const filePath = path.resolve(__dirname, fileName); console.log(`Now running: ${shell} ${filePath}`); cp.spawnSync(shell, [filePath], {stdio: 'inherit'}); typeof cb === 'function' && cb(); } ================================================ FILE: accounts/manifest.yml ================================================ --- applications: - instances: 1 timeout: 180 name: innovate-accounts buildpack: sdk-for-nodejs command: npm start memory: 512M ================================================ FILE: accounts/mongoose/account.js ================================================ const mongoose = require('mongoose'); var Schema = mongoose.Schema; var Account = new Schema({ uuid: String, type: String, currency: String, balance: Number, number: Number }); module.exports = mongoose.model('Account', Account, "accounts"); ================================================ FILE: accounts/package.json ================================================ { "name": "innovate-accounts", "version": "1.0.0", "description": "Innovate: Digital Bank", "private": true, "engines": { "node": "^6.9.0" }, "scripts": { "start": "node server.js", "start:cluster": "sl-run server/server.js", "debug": "node --debug server/server.js", "build": "npm run build:idt", "idt:build": "node idt.js build", "idt:test": "node idt.js test", "idt:debug": "node idt.js debug", "idt:run": "node idt.js run", "idt:deploy": "node idt.js deploy", "idt:install": "node idt.js install" }, "dependencies": { "body-parser": "^1.17.2", "dotenv": "^2.0.0", "express": "^4.15.3", "jslint": "^0.12.0", "mongoose": "^5.0.0-rc1", "request": "^2.83.0", "strong-supervisor": "^6.2.0", "uuid": "^3.1.0", "watson-developer-cloud": "^2.40.0" }, "devDependencies": { "chai": "^4.0.0", "chalk": "^1.1.3", "mocha": "^3.4.2", "nyc": "^10.3.2", "prompt-confirm": "^1.2.0", "proxyquire": "^1.8.0" } } ================================================ FILE: accounts/server.js ================================================ 'use strict'; const mongoose = require('mongoose'); require('dotenv').config({silent: true, path: `${__dirname}/.env`}); var server = require('./app'); var port = 3400; console.log(`Running on ${process.env.BASE_PATH}:${port}, connecting to ${process.env.MONGO_URL}`) mongoose.connect(process.env.MONGO_URL, function (ignore, connection) { connection.onOpen(); server.listen(port, function () { console.log('Server running on port: %d', port); }); }); ================================================ FILE: authentication/.cfignore ================================================ .git/ node_modules/ test/ vcap-local.js ================================================ FILE: authentication/.dockerignore ================================================ node_modules/ test/ .bluemix/ ================================================ FILE: authentication/.gitignore ================================================ # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* # Runtime data pids *.pid *.seed *.pid.lock # Directory for instrumented libs generated by jscoverage/JSCover lib-cov # Coverage directory used by tools like istanbul coverage # nyc test coverage .nyc_output # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) .grunt # Bower dependency directory (https://bower.io/) bower_components # node-waf configuration .lock-wscript # Compiled binary addons (http://nodejs.org/api/addons.html) build/Release # Dependency directories node_modules/ jspm_packages/ # Typescript v1 declaration files typings/ # Optional npm cache directory .npm # Optional eslint cache .eslintcache # Optional REPL history .node_repl_history # Output of 'npm pack' *.tgz # Yarn Integrity file .yarn-integrity # dotenv environment variables file .env # other .DS_Store license_accepted ================================================ FILE: authentication/.ibm-project ================================================ {"build-release-ready":true,"build-debug-ready":false,"deploy-image":"","chart-path":"","ibm-cluster":""} ================================================ FILE: authentication/Dockerfile ================================================ FROM ibmcom/ibmnode:latest LABEL maintainer="***REMOVED***" #RUN apt-get install -y nodejs npm WORKDIR /app # Install app dependencies COPY . /app RUN cd /app; npm install; npm prune --production ENV NODE_ENV production ENV PORT 3200 EXPOSE 3200 CMD [ "npm","start" ] ================================================ FILE: authentication/Dockerfile-tools ================================================ FROM ibmcom/ibmnode ENV PORT 3200 WORKDIR "/app" # Bundle app source COPY . /app EXPOSE 3200 CMD ["/bin/bash"] ARG bx_dev_user=root ARG bx_dev_userid=1000 RUN BX_DEV_USER=$bx_dev_user RUN BX_DEV_USERID=$bx_dev_userid RUN if [ $bx_dev_user != "root" ]; then useradd -ms /bin/bash -u $bx_dev_userid $bx_dev_user; fi ================================================ FILE: authentication/app.js ================================================ 'use strict'; const express = require('express'); const bodyParser = require('body-parser'); const users = require('./mongoose/user'); const uuidv4 = require('uuid/v4'); var app = express(); app.use(bodyParser.json()); app.all('/', function (req, res, next) { res.header("Access-Control-Allow-Origin", "*"); res.header("Access-Control-Allow-Headers", "*"); next(); }); app.post('/api/user/create', function (req, res) { findUser({'email': req.body.email}, function (err, user) { if (err) { console.log(err); res.status(500).send('Something went wrong. Try again in a few seconds, or contact support.'); return; } if (user) { res.status(500).send({'err': 'Seems like you already have an account with us. Please log in instead, or contact support to recover regain to your account.'}); return; } var uuid = uuidv4(); var newUser = { uuid: uuid, name: req.body.name, email: req.body.email, phone: req.body.phone, gender: req.body.gender, dob: req.body.dob, eid: req.body.eid, password: req.body.password }; users.create(newUser, function (err) { if (err) { console.log(err); res.status(500).send('Something went wrong. Try again in a few seconds, or contact support.'); return; } console.log("User created"); res.status(200).send(newUser); }); }); }); app.post('/api/user/authenticate', function (req, res) { findUser({'email': req.body.email, 'password': req.body.password}, function (err, user) { if (err) { console.log(err); res.status(500).send({'err': 'Something went wrong. Try again in a few seconds, or contact support.'}); return; } if (!user) { res.status(500).send({'err': `Your username & password are incorrect. Try again, or contact support to recover lost login details. `}); return; } res.status(200).send(user); }); }); app.get('/api/user/get', function(req, res) { findUser({}, function (err, users) { if (err) { console.log(err); res.status(500).send({'err': 'Something went wrong. Try again in a few seconds, or contact support.'}); return; } if (!users) { res.status(500).send({'err': 'No Users Found'}); return; } res.status(200).send(users); }); }); function findUser (criteria, callback){ users.find(criteria, function (err, results) { if (err) { console.log(err); callback(err); } if (Object.keys(criteria).length > 0) { results = results[0]; } callback(null, results); }); }; module.exports = app; ================================================ FILE: authentication/chart/innovate-authentication/Chart.yaml ================================================ apiVersion: v1 description: A Helm chart for innovate bank portal name: innovate-authentication version: 1.0.0 ================================================ FILE: authentication/chart/innovate-authentication/templates/deployment.yaml ================================================ apiVersion: extensions/v1beta1 kind: Deployment metadata: name: "{{ .Chart.Name }}-deployment" labels: chart: '{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}' spec: replicas: {{ .Values.replicaCount }} strategy: type: RollingUpdate rollingUpdate: maxUnavailable: 0 maxSurge: 1 revisionHistoryLimit: {{ .Values.revisionHistoryLimit }} template: metadata: labels: app: "{{ .Chart.Name }}-selector" spec: containers: - name: "{{ .Chart.Name }}" image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" imagePullPolicy: {{ .Values.image.pullPolicy }} livenessProbe: httpGet: path: /health port: {{ .Values.service.servicePort }} initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds}} periodSeconds: {{ .Values.livenessProbe.periodSeconds}} resources: requests: cpu: "{{ .Values.image.resources.requests.cpu }}" memory: "{{ .Values.image.resources.requests.memory }}" env: - name: PORT value : "{{ .Values.service.servicePort }}" ================================================ FILE: authentication/chart/innovate-authentication/templates/hpa.yaml ================================================ {{ if .Values.hpa.enabled }} apiVersion: autoscaling/v2alpha1 kind: HorizontalPodAutoscaler metadata: name: "{{ .Chart.Name }}-hpa-policy" namespace: spec: scaleTargetRef: apiVersion: apps/v2alpha1 kind: Deployment name: "{{ .Chart.Name }}-deployment" minReplicas: {{ .Values.hpa.minReplicas }} maxReplicas: {{ .Values.hpa.maxReplicas }} metrics: - type: Resource resource: name: cpu targetAverageUtilization: {{ .Values.hpa.metrics.cpu.targetAverageUtilization }} - type: Resource resource: name: memory targetAverageUtilization: {{ .Values.hpa.metrics.memory.targetAverageUtilization }} {{ end }} ================================================ FILE: authentication/chart/innovate-authentication/templates/service.yaml ================================================ apiVersion: v1 kind: Service metadata: annotations: prometheus.io/scrape: 'true' name: "{{ .Chart.Name }}" labels: chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}" spec: type: {{ .Values.service.type }} ports: - port: {{ .Values.service.servicePort }} nodePort: {{ .Values.service.serviceNodePort }} selector: app: "{{ .Chart.Name }}-selector" ================================================ FILE: authentication/chart/innovate-authentication/values.yaml ================================================ # This is a YAML-formatted file. # Declare variables to be passed into your templates. replicaCount: 1 revisionHistoryLimit: 3 image: repository: mycluster.icp:8500/default/innovate-authentication tag: v1.0.0 pullPolicy: Always resources: requests: cpu: 500m memory: 512Mi livenessProbe: initialDelaySeconds: 3000 periodSeconds: 1000 service: name: Node type: NodePort servicePort: 3200 serviceNodePort: 30100 hpa: enabled: false minReplicas: 2 maxReplicas: 3 metrics: cpu: targetAverageUtilization: 80 memory: targetAverageUtilization: 80 services: ================================================ FILE: authentication/cli-config.yml ================================================ # The IBM version of this configuration version : 0.0.3 # The container name used for the run container container-name-run : "innovate-authentication-run" # The container name used for the tools container container-name-tools : "innovate-authentication-tools" # The project root on the host for the run container to mount to container-path-run host-path-run : . # The project root on the host for the tools container to mount to container-path-tools host-path-tools : . # The project root in the run container to mount to host-path-run container-path-run : "/app" # The project root in the tools container that will be mounted to host-path-tools container-path-tools : "/app" # The list of additional mounts between the host and the run container in the form [host_path:container_path] container-mounts-run: - "./node_modules_linux": "/app/node_modules" # The list of additional mounts between the host and the tools container in the form [host_path:container_path] container-mounts-tools: - "./node_modules_linux": "/app/node_modules" # The port mappings between the host and the container in the form [host:container] container-port-map : "3200:3200" # The port mappings between the host and the container for the debug port in the form [host:container] container-port-map-debug : "5860:5860" # The name for the dockerfile for the run container dockerfile-run : "Dockerfile" # The name for the dockerfile for the tools container dockerfile-tools : "Dockerfile-tools" # The name of image to create from dockerfile-run image-name-run : "innovate-authentication-run" # The name of image to create from dockerfile-tools image-name-tools : "innovate-authentication-tools" # The command to build the code and docker image for RUN build-cmd-run : "npm install" # The command to execute tests for the code in the tools container test-cmd : "npm run test" # The command to build the code and docker image for DEBUG build-cmd-debug : "npm install" # The command to run the code in the run container run-cmd : "" # The command to execute debug of the code in the tools container debug-cmd : "npm run debug" # The command to stop the code stop-cmd : "npm stop" # The relative path to the helm chart used for Kubernetes deployment chart-path: "chart/innovate-authentication" deploy-target: "container" deploy-image-target: "mycluster.icp:8500/default/innovate-authentication" ================================================ FILE: authentication/idt.js ================================================ 'use strict' /* * Wrapper for the idt (IBM Developer Tools) command. * Run with the same arguments as `idt`, e.g. * `node idt.js build` -> `idt build`. * If `idt` isn't installed, this will prompt you to install. Or you can run * `node idt.js install` to automatically install idt and any other * required dependencies (e.g. docker, git, kubernetes, helm). * */ const fs = require('fs'); const process = require('process'); const cp = require('child_process'); const request = require('request'); const path = require('path'); const chalk = require('chalk'); const node = process.execPath; // Array of args passed to idt.js. const args = process.argv.slice(2); let win = (process.platform === 'win32'); // Either install idt or run idt + args. if (args.includes('install')) { downloadInstaller(); } else { // TODO(gib): Check for idt once this works in scripts: // const checkCmd = win ? 'where idt' : 'which idt'; const checkCmd = 'bx plugin show dev'; let hasIDT = false; try { console.log(chalk.blue('Checking for idt')); cp.execSync(checkCmd); // Don't inherit stdio, we don't want to print the output. hasIDT = true; // If we didn't have idt, the previous command would have thrown. } catch (e) { const prompt = require('prompt-confirm'); new prompt({ name: 'install', message: 'IDT not found, do you want to install it? y/N', default: false }).ask((answer) => { if (answer) { downloadInstaller(() => runIDT(args)); } else { console.error(chalk.red(`Not installing idt, so not running: idt ${args.join(' ')}`)); } }); } if (hasIDT) runIDT(args); } // Run IDT with whatever args we were given. function runIDT(args) { const cmd = 'bx dev ' + args.join(' '); console.log(chalk.blue('Running:'), cmd); cp.execSync(cmd, {stdio: 'inherit'}); } // Download the IDT installer script and trigger runInstaller(). function downloadInstaller(cb) { const url = win ? 'https://ibm.biz/yeoman-idt-win-install' : 'http://ibm.biz/yeoman-idt-install'; const fileName = url.split('/').pop() console.log(chalk.blue('Downloading installer from:'), url); const file = fs.createWriteStream(fileName); request .get({url, followAllRedirects: true}) .on('error', (err) => { console.error(err); }) .pipe(file) .on('finish', () => runInstaller(fileName, cb)); } // Run the installer script and trigger optional callback (cb). function runInstaller(fileName, cb) { const shell = win ? 'powershell.exe' : 'bash'; const filePath = path.resolve(__dirname, fileName); console.log(`Now running: ${shell} ${filePath}`); cp.spawnSync(shell, [filePath], {stdio: 'inherit'}); typeof cb === 'function' && cb(); } ================================================ FILE: authentication/manifest.yml ================================================ --- applications: - instances: 1 timeout: 180 name: innovate-authentication buildpack: sdk-for-nodejs command: npm start memory: 512M ================================================ FILE: authentication/mongoose/user.js ================================================ const mongoose = require('mongoose'); var Schema = mongoose.Schema; var User = new Schema({ uuid: String, name: String, email: String, phone: String, gender: String, dob: String, eid: String, password: String }); module.exports = mongoose.model('User', User, "users"); ================================================ FILE: authentication/package.json ================================================ { "name": "innovate-authentication", "version": "1.0.0", "description": "Innovate: Digital Bank", "private": true, "engines": { "node": "^6.9.0" }, "scripts": { "start": "node server.js", "start:cluster": "sl-run server/server.js", "debug": "node --debug server/server.js", "build": "npm run build:idt", "idt:build": "node idt.js build", "idt:test": "node idt.js test", "idt:debug": "node idt.js debug", "idt:run": "node idt.js run", "idt:deploy": "node idt.js deploy", "idt:install": "node idt.js install" }, "dependencies": { "body-parser": "^1.17.2", "dotenv": "^2.0.0", "express": "^4.15.3", "mongoose": "^5.0.0-rc1", "request": "^2.83.0", "strong-supervisor": "^6.2.0", "uuid": "^3.1.0", "watson-developer-cloud": "^2.40.0" }, "devDependencies": { "chai": "^4.0.0", "chalk": "^1.1.3", "mocha": "^3.4.2", "nyc": "^10.3.2", "prompt-confirm": "^1.2.0", "proxyquire": "^1.8.0" } } ================================================ FILE: authentication/server.js ================================================ 'use strict'; const mongoose = require('mongoose'); require('dotenv').config({silent: true, path: `${__dirname}/.env`}); var server = require('./app'); var port = 3200; console.log(`Running on ${process.env.BASE_PATH}:${port}, connecting to ${process.env.MONGO_URL}`) mongoose.connect(process.env.MONGO_URL, function (ignore, connection) { connection.onOpen(); server.listen(port, function () { console.log('Server running on port: %d', port); }); }); ================================================ FILE: bills/.cfignore ================================================ .git/ node_modules/ test/ vcap-local.js ================================================ FILE: bills/.dockerignore ================================================ node_modules/ test/ .bluemix/ ================================================ FILE: bills/.gitignore ================================================ # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* # Runtime data pids *.pid *.seed *.pid.lock # Directory for instrumented libs generated by jscoverage/JSCover lib-cov # Coverage directory used by tools like istanbul coverage # nyc test coverage .nyc_output # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) .grunt # Bower dependency directory (https://bower.io/) bower_components # node-waf configuration .lock-wscript # Compiled binary addons (http://nodejs.org/api/addons.html) build/Release # Dependency directories node_modules/ jspm_packages/ # Typescript v1 declaration files typings/ # Optional npm cache directory .npm # Optional eslint cache .eslintcache # Optional REPL history .node_repl_history # Output of 'npm pack' *.tgz # Yarn Integrity file .yarn-integrity # dotenv environment variables file .env # other .DS_Store license_accepted ================================================ FILE: bills/.ibm-project ================================================ {"build-release-ready":true,"build-debug-ready":false,"deploy-image":"","chart-path":"","ibm-cluster":""} ================================================ FILE: bills/Dockerfile ================================================ FROM ibmcom/ibmnode:latest LABEL maintainer="***REMOVED***" #RUN apt-get install -y nodejs npm WORKDIR /app # Install app dependencies COPY . /app RUN cd /app; npm install; npm prune --production ENV NODE_ENV production ENV PORT 3800 EXPOSE 3800 CMD [ "npm","start" ] ================================================ FILE: bills/Dockerfile-tools ================================================ FROM ibmcom/ibmnode ENV PORT 3800 WORKDIR "/app" # Bundle app source COPY . /app EXPOSE 3800 CMD ["/bin/bash"] ARG bx_dev_user=root ARG bx_dev_userid=1000 RUN BX_DEV_USER=$bx_dev_user RUN BX_DEV_USERID=$bx_dev_userid RUN if [ $bx_dev_user != "root" ]; then useradd -ms /bin/bash -u $bx_dev_userid $bx_dev_user; fi ================================================ FILE: bills/app.js ================================================ 'use strict'; const express = require('express'); const bodyParser = require('body-parser'); const bills = require('./mongoose/bill'); var app = express(); app.use(bodyParser.json()); app.all('*', function (req, res, next) { res.header("Access-Control-Allow-Origin", "*"); res.header("Access-Control-Allow-Headers", "*"); next(); }); app.post('/api/bills/create', function (req, res) { var newBill = { uuid: req.body.uuid, category: req.body.category, entity: req.body.entity, account_no: req.body.account_no, amount: req.body.amount, date: req.body.date }; bills.update({'category': req.body.category}, newBill, {upsert: true}, function (err) { if (err) { console.log(err); res.status(500).send(err); return; } console.log("Bill created"); res.status(200).send({'message': 'Done!'}); }); }); app.post('/api/bills/get', function (req, res) { bills.find({'uuid': req.body.uuid}, function (err, results) { if (err) { console.log(err); res.status(500).send({'err': err}); return; } res.status(200).send(results); }); }); app.get('/api/bills/drop', function (req, res) { bills.collection.drop(); res.status(200).send({'message': 'Done!'}); }); module.exports = app; ================================================ FILE: bills/chart/innovate-bills/Chart.yaml ================================================ apiVersion: v1 description: A Helm chart for innovate bank portal name: innovate-bills version: 1.0.0 ================================================ FILE: bills/chart/innovate-bills/templates/deployment.yaml ================================================ apiVersion: extensions/v1beta1 kind: Deployment metadata: name: "{{ .Chart.Name }}-deployment" labels: chart: '{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}' spec: replicas: {{ .Values.replicaCount }} strategy: type: RollingUpdate rollingUpdate: maxUnavailable: 0 maxSurge: 1 revisionHistoryLimit: {{ .Values.revisionHistoryLimit }} template: metadata: labels: app: "{{ .Chart.Name }}-selector" spec: containers: - name: "{{ .Chart.Name }}" image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" imagePullPolicy: {{ .Values.image.pullPolicy }} livenessProbe: httpGet: path: /health port: {{ .Values.service.servicePort }} initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds}} periodSeconds: {{ .Values.livenessProbe.periodSeconds}} resources: requests: cpu: "{{ .Values.image.resources.requests.cpu }}" memory: "{{ .Values.image.resources.requests.memory }}" env: - name: PORT value : "{{ .Values.service.servicePort }}" ================================================ FILE: bills/chart/innovate-bills/templates/hpa.yaml ================================================ {{ if .Values.hpa.enabled }} apiVersion: autoscaling/v2alpha1 kind: HorizontalPodAutoscaler metadata: name: "{{ .Chart.Name }}-hpa-policy" namespace: spec: scaleTargetRef: apiVersion: apps/v2alpha1 kind: Deployment name: "{{ .Chart.Name }}-deployment" minReplicas: {{ .Values.hpa.minReplicas }} maxReplicas: {{ .Values.hpa.maxReplicas }} metrics: - type: Resource resource: name: cpu targetAverageUtilization: {{ .Values.hpa.metrics.cpu.targetAverageUtilization }} - type: Resource resource: name: memory targetAverageUtilization: {{ .Values.hpa.metrics.memory.targetAverageUtilization }} {{ end }} ================================================ FILE: bills/chart/innovate-bills/templates/service.yaml ================================================ apiVersion: v1 kind: Service metadata: annotations: prometheus.io/scrape: 'true' name: "{{ .Chart.Name }}" labels: chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}" spec: type: {{ .Values.service.type }} ports: - port: {{ .Values.service.servicePort }} nodePort: {{ .Values.service.serviceNodePort }} selector: app: "{{ .Chart.Name }}-selector" ================================================ FILE: bills/chart/innovate-bills/values.yaml ================================================ # This is a YAML-formatted file. # Declare variables to be passed into your templates. replicaCount: 1 revisionHistoryLimit: 3 image: repository: mycluster.icp:8500/default/innovate-bills tag: v1.0.0 pullPolicy: Always resources: requests: cpu: 500m memory: 512Mi livenessProbe: initialDelaySeconds: 3000 periodSeconds: 1000 service: name: Node type: NodePort servicePort: 3800 serviceNodePort: 30160 hpa: enabled: false minReplicas: 2 maxReplicas: 3 metrics: cpu: targetAverageUtilization: 80 memory: targetAverageUtilization: 80 services: ================================================ FILE: bills/cli-config.yml ================================================ # The IBM version of this configuration version : 0.0.3 # The container name used for the run container container-name-run : "innovate-bills-run" # The container name used for the tools container container-name-tools : "innovate-bills-tools" # The project root on the host for the run container to mount to container-path-run host-path-run : . # The project root on the host for the tools container to mount to container-path-tools host-path-tools : . # The project root in the run container to mount to host-path-run container-path-run : "/app" # The project root in the tools container that will be mounted to host-path-tools container-path-tools : "/app" # The list of additional mounts between the host and the run container in the form [host_path:container_path] container-mounts-run: - "./node_modules_linux": "/app/node_modules" # The list of additional mounts between the host and the tools container in the form [host_path:container_path] container-mounts-tools: - "./node_modules_linux": "/app/node_modules" # The port mappings between the host and the container in the form [host:container] container-port-map : "3800:3800" # The port mappings between the host and the container for the debug port in the form [host:container] container-port-map-debug : "5866:5866" # The name for the dockerfile for the run container dockerfile-run : "Dockerfile" # The name for the dockerfile for the tools container dockerfile-tools : "Dockerfile-tools" # The name of image to create from dockerfile-run image-name-run : "innovate-bills-run" # The name of image to create from dockerfile-tools image-name-tools : "innovate-bills-tools" # The command to build the code and docker image for RUN build-cmd-run : "npm install" # The command to execute tests for the code in the tools container test-cmd : "npm run test" # The command to build the code and docker image for DEBUG build-cmd-debug : "npm install" # The command to run the code in the run container run-cmd : "" # The command to execute debug of the code in the tools container debug-cmd : "npm run debug" # The command to stop the code stop-cmd : "npm stop" # The relative path to the helm chart used for Kubernetes deployment chart-path: "chart/innovate-bills" deploy-target: "container" deploy-image-target: "mycluster.icp:8500/default/innovate-bills" ================================================ FILE: bills/idt.js ================================================ 'use strict' /* * Wrapper for the idt (IBM Developer Tools) command. * Run with the same arguments as `idt`, e.g. * `node idt.js build` -> `idt build`. * If `idt` isn't installed, this will prompt you to install. Or you can run * `node idt.js install` to automatically install idt and any other * required dependencies (e.g. docker, git, kubernetes, helm). * */ const fs = require('fs'); const process = require('process'); const cp = require('child_process'); const request = require('request'); const path = require('path'); const chalk = require('chalk'); const node = process.execPath; // Array of args passed to idt.js. const args = process.argv.slice(2); let win = (process.platform === 'win32'); // Either install idt or run idt + args. if (args.includes('install')) { downloadInstaller(); } else { // TODO(gib): Check for idt once this works in scripts: // const checkCmd = win ? 'where idt' : 'which idt'; const checkCmd = 'bx plugin show dev'; let hasIDT = false; try { console.log(chalk.blue('Checking for idt')); cp.execSync(checkCmd); // Don't inherit stdio, we don't want to print the output. hasIDT = true; // If we didn't have idt, the previous command would have thrown. } catch (e) { const prompt = require('prompt-confirm'); new prompt({ name: 'install', message: 'IDT not found, do you want to install it? y/N', default: false }).ask((answer) => { if (answer) { downloadInstaller(() => runIDT(args)); } else { console.error(chalk.red(`Not installing idt, so not running: idt ${args.join(' ')}`)); } }); } if (hasIDT) runIDT(args); } // Run IDT with whatever args we were given. function runIDT(args) { const cmd = 'bx dev ' + args.join(' '); console.log(chalk.blue('Running:'), cmd); cp.execSync(cmd, {stdio: 'inherit'}); } // Download the IDT installer script and trigger runInstaller(). function downloadInstaller(cb) { const url = win ? 'https://ibm.biz/yeoman-idt-win-install' : 'http://ibm.biz/yeoman-idt-install'; const fileName = url.split('/').pop() console.log(chalk.blue('Downloading installer from:'), url); const file = fs.createWriteStream(fileName); request .get({url, followAllRedirects: true}) .on('error', (err) => { console.error(err); }) .pipe(file) .on('finish', () => runInstaller(fileName, cb)); } // Run the installer script and trigger optional callback (cb). function runInstaller(fileName, cb) { const shell = win ? 'powershell.exe' : 'bash'; const filePath = path.resolve(__dirname, fileName); console.log(`Now running: ${shell} ${filePath}`); cp.spawnSync(shell, [filePath], {stdio: 'inherit'}); typeof cb === 'function' && cb(); } ================================================ FILE: bills/manifest.yml ================================================ --- applications: - instances: 1 timeout: 180 name: innovate-bills buildpack: sdk-for-nodejs command: npm start memory: 512M ================================================ FILE: bills/mongoose/bill.js ================================================ const mongoose = require('mongoose'); var Schema = mongoose.Schema; var Bill = new Schema({ uuid: String, category: String, entity: String, account_no: String, amount: Number, currency: String, date: String }); module.exports = mongoose.model('Bill', Bill, "bills"); ================================================ FILE: bills/package.json ================================================ { "name": "innovate-bills", "version": "1.0.0", "description": "Innovate: Digital Bank", "private": true, "engines": { "node": "^6.9.0" }, "scripts": { "start": "node server.js", "start:cluster": "sl-run server/server.js", "debug": "node --debug server/server.js", "build": "npm run build:idt", "idt:build": "node idt.js build", "idt:test": "node idt.js test", "idt:debug": "node idt.js debug", "idt:run": "node idt.js run", "idt:deploy": "node idt.js deploy", "idt:install": "node idt.js install" }, "dependencies": { "body-parser": "^1.17.2", "dotenv": "^2.0.0", "express": "^4.15.3", "mongoose": "^5.0.0-rc1", "request": "^2.83.0", "strong-supervisor": "^6.2.0", "uuid": "^3.1.0", "watson-developer-cloud": "^2.40.0" }, "devDependencies": { "chai": "^4.0.0", "chalk": "^1.1.3", "mocha": "^3.4.2", "nyc": "^10.3.2", "prompt-confirm": "^1.2.0", "proxyquire": "^1.8.0" } } ================================================ FILE: bills/server.js ================================================ 'use strict'; const mongoose = require('mongoose'); require('dotenv').config({silent: true, path: `${__dirname}/.env`}); var server = require('./app'); var port = 3800; console.log(`Running on ${process.env.BASE_PATH}:${port}, connecting to ${process.env.MONGO_URL}`) mongoose.connect(process.env.MONGO_URL, function (ignore, connection) { connection.onOpen(); server.listen(port, function () { console.log('Server running on port: %d', port); }); }); ================================================ FILE: chart/innovate-bank/Chart.yaml ================================================ apiVersion: v1 description: A Helm chart for innovate bank name: innovate-bank version: 2.0.0 ================================================ FILE: chart/innovate-bank/charts/mongodb/.helmignore ================================================ # Patterns to ignore when building packages. # This supports shell glob matching, relative path matching, and # negation (prefixed with !). Only one pattern per line. .DS_Store # Common VCS dirs .git/ .gitignore .bzr/ .bzrignore .hg/ .hgignore .svn/ # Common backup files *.swp *.bak *.tmp *~ # Various IDEs .project .idea/ *.tmproj ================================================ FILE: chart/innovate-bank/charts/mongodb/Chart.yaml ================================================ name: mongodb version: 1.0.0 appVersion: 3.7.3 description: NoSQL document-oriented database that stores JSON-like documents with dynamic schemas, simplifying the integration of data in content-driven applications. keywords: - mongodb - database - nosql home: https://mongodb.org icon: https://bitnami.com/assets/stacks/mongodb/img/mongodb-stack-220x234.png sources: - https://github.com/bitnami/bitnami-docker-mongodb maintainers: - name: Bitnami email: containers@bitnami.com engine: gotpl ================================================ FILE: chart/innovate-bank/charts/mongodb/templates/_helpers.tpl ================================================ {{/* vim: set filetype=mustache: */}} {{/* Expand the name of the chart. */}} {{- define "mongodb.name" -}} {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} {{- end -}} {{/* Create a default fully qualified app name. We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). */}} {{- define "mongodb.fullname" -}} {{- $name := default .Chart.Name .Values.nameOverride -}} {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} {{- end -}} ================================================ FILE: chart/innovate-bank/charts/mongodb/templates/deployment.yaml ================================================ apiVersion: extensions/v1beta1 kind: Deployment metadata: name: {{ template "mongodb.fullname" . }} labels: app: {{ template "mongodb.fullname" . }} chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" release: "{{ .Release.Name }}" heritage: "{{ .Release.Service }}" spec: template: metadata: labels: app: {{ template "mongodb.fullname" . }} spec: containers: - name: {{ template "mongodb.fullname" . }} image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" imagePullPolicy: {{ .Values.image.pullPolicy }} env: - name: MONGODB_ROOT_PASSWORD valueFrom: secretKeyRef: name: {{ template "mongodb.fullname" . }} key: mongodb-root-password - name: MONGODB_USERNAME value: {{ default "" .Values.mongodbUsername | quote }} - name: MONGODB_PASSWORD valueFrom: secretKeyRef: name: {{ template "mongodb.fullname" . }} key: mongodb-password - name: MONGODB_DATABASE value: {{ default "" .Values.mongodbDatabase | quote }} ports: - name: mongodb containerPort: 27017 livenessProbe: exec: command: - mongo - --eval - "db.***REMOVED***Command('ping')" initialDelaySeconds: 30 timeoutSeconds: 5 readinessProbe: exec: command: - mongo - --eval - "db.***REMOVED***Command('ping')" initialDelaySeconds: 5 timeoutSeconds: 1 volumeMounts: - name: data mountPath: /data/mongo resources: {{ toYaml .Values.resources | indent 10 }} volumes: - name: data {{- if .Values.persistence.enabled }} persistentVolumeClaim: claimName: {{ template "mongodb.fullname" . }} {{- else }} emptyDir: {} {{- end -}} ================================================ FILE: chart/innovate-bank/charts/mongodb/templates/pv.yaml ================================================ apiVersion: v1 kind: PersistentVolume metadata: name: {{ template "mongodb.fullname" . }} spec: capacity: storage: {{ .Values.persistence.size }} accessModes: - {{ .Values.persistence.accessMode }} hostPath: path: {{ .Values.persistence.path }} ================================================ FILE: chart/innovate-bank/charts/mongodb/templates/pvc.yaml ================================================ kind: PersistentVolumeClaim apiVersion: v1 metadata: name: {{ template "mongodb.fullname" . }} labels: billingType: {{ .Values.persistence.billingType }} spec: accessModes: - {{ .Values.persistence.accessMode }} resources: requests: storage: {{ .Values.persistence.claimSize }} ================================================ FILE: chart/innovate-bank/charts/mongodb/templates/secrets.yaml ================================================ apiVersion: v1 kind: Secret metadata: name: {{ template "mongodb.fullname" . }} labels: app: {{ template "mongodb.fullname" . }} chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" release: "{{ .Release.Name }}" heritage: "{{ .Release.Service }}" type: Opaque data: {{ if .Values.mongodbRootPassword }} mongodb-root-password: {{ .Values.mongodbRootPassword | b64enc | quote }} {{ else }} mongodb-root-password: {{ randAlphaNum 10 | b64enc | quote }} {{ end }} {{ if .Values.mongodbPassword }} mongodb-password: {{ .Values.mongodbPassword | b64enc | quote }} {{ else }} mongodb-password: {{ randAlphaNum 10 | b64enc | quote }} {{ end }} ================================================ FILE: chart/innovate-bank/charts/mongodb/templates/service.yaml ================================================ apiVersion: v1 kind: Service metadata: name: {{ template "mongodb.fullname" . }} labels: app: {{ template "mongodb.fullname" . }} chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" release: "{{ .Release.Name }}" heritage: "{{ .Release.Service }}" spec: type: {{ .Values.serviceType }} ports: - name: mongodb port: 27017 targetPort: mongodb nodePort: 30443 selector: app: {{ template "mongodb.fullname" . }} ================================================ FILE: chart/innovate-bank/charts/mongodb/values.yaml ================================================ image: registry: docker.io repository: bitnami/mongodb tag: 3.7.3 pullPolicy: IfNotPresent mongodbRootPassword: mongo mongodbUsername: mongo mongodbPassword: ***REMOVED*** mongodbDatabase: ***REMOVED*** serviceType: NodePort persistence: enabled: true accessMode: ReadWriteMany size: 5Gi claimSize: 1Gi storageClass: ibmc-file-bronze billingType: hourly path: /innovate resources: requests: memory: 512Mi cpu: 200m ================================================ FILE: chart/innovate-bank/templates/_helpers.tpl ================================================ {{/* vim: set filetype=mustache: */}} {{/* Expand the name of the chart. */}} {{- define "name" -}} {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} {{- end -}} {{/* Create a default fully qualified app name. We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). */}} {{- define "fullname" -}} {{- $name := default .Chart.Name .Values.nameOverride -}} {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} {{- end -}} ================================================ FILE: chart/innovate-bank/templates/configmap.yaml ================================================ apiVersion: v1 kind: ConfigMap metadata: name: "{{ .Chart.Name }}-configmap" labels: chart: '{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}' data: mongoUrl: "{{ .Values.config.mongoUrl }}" basePath: "{{ .Values.config.basePath }}" sessionSecret: "{{ .Values.config.sessionSecret }}" nodeEnv: "{{ .Values.config.nodeEnv }}" signupEndpoint: "/api/user/create" loginEndpoint: "/api/user/authenticate" getUsersEndpoint: "/api/user/get" getBillsEndpoint: "/api/bills/get" upsertBillEndpoint: "/api/bills/create" dropBillsEndpoint: "/api/bills/drop" createAccountEndpoint: "/api/accounts/create" getAccountsEndpoint: "/api/accounts/get" accountWithdrawEndpoint: "/api/accounts/withdraw" accountDepositEndpoint: "/api/accounts/deposit" dropAccountsEndpoint: "/api/accounts/drop" createTransactionEndpoint: "/api/transactions/create" getTransactionsEndpoint: "/api/transactions/get" dropTransactionsEndpoint: "/api/transactions/drop" chatEndpoint: "/api/message" ================================================ FILE: chart/innovate-bank/templates/deployment.yaml ================================================ apiVersion: extensions/v1beta1 kind: Deployment metadata: name: "{{ .Chart.Name }}-deployment" labels: chart: '{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}' spec: replicas: {{ .Values.replicaCount }} strategy: type: RollingUpdate rollingUpdate: maxUnavailable: 0 maxSurge: 1 revisionHistoryLimit: {{ .Values.revisionHistoryLimit }} template: metadata: labels: app: "{{ .Chart.Name }}-selector" spec: imagePullSecrets: - name: {{ .Values.image.pullSecret }} containers: - name: "{{ .Chart.Name }}" image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" imagePullPolicy: {{ .Values.image.pullPolicy }} livenessProbe: httpGet: path: /health port: {{ .Values.service.servicePort }} initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds}} periodSeconds: {{ .Values.livenessProbe.periodSeconds}} resources: requests: cpu: "{{ .Values.image.resources.requests.cpu }}" memory: "{{ .Values.image.resources.requests.memory }}" env: - name: MONGO_URL valueFrom: configMapKeyRef: name: "{{ .Chart.Name }}-configmap" key: mongoUrl - name: BASE_PATH valueFrom: configMapKeyRef: name: "{{ .Chart.Name }}-configmap" key: basePath - name: PORT value : "{{ .Values.service.servicePort }}" - name: SESSION_SECRET valueFrom: configMapKeyRef: name: "{{ .Chart.Name }}-configmap" key: sessionSecret - name: NODE_ENV valueFrom: configMapKeyRef: name: "{{ .Chart.Name }}-configmap" key: nodeEnv - name: SIGNUP_ENDPOINT valueFrom: configMapKeyRef: name: "{{ .Chart.Name }}-configmap" key: signupEndpoint - name: LOGIN_ENDPOINT valueFrom: configMapKeyRef: name: "{{ .Chart.Name }}-configmap" key: loginEndpoint - name: GET_USERS_ENDPOINT valueFrom: configMapKeyRef: name: "{{ .Chart.Name }}-configmap" key: getUsersEndpoint - name: GET_BILLS_ENDPOINT valueFrom: configMapKeyRef: name: "{{ .Chart.Name }}-configmap" key: getBillsEndpoint - name: UPSERT_BILL_ENDPOINT valueFrom: configMapKeyRef: name: "{{ .Chart.Name }}-configmap" key: upsertBillEndpoint - name: DROP_BILLS_ENDPOINT valueFrom: configMapKeyRef: name: "{{ .Chart.Name }}-configmap" key: dropBillsEndpoint - name: CREATE_ACCOUNT_ENDPOINT valueFrom: configMapKeyRef: name: "{{ .Chart.Name }}-configmap" key: createAccountEndpoint - name: GET_ACCOUNTS_ENDPOINT valueFrom: configMapKeyRef: name: "{{ .Chart.Name }}-configmap" key: getAccountsEndpoint - name: ACCOUNT_WITHDRAW_ENDPOINT valueFrom: configMapKeyRef: name: "{{ .Chart.Name }}-configmap" key: accountWithdrawEndpoint - name: ACCOUNT_DEPOSIT_ENDPOINT valueFrom: configMapKeyRef: name: "{{ .Chart.Name }}-configmap" key: accountDepositEndpoint - name: DROP_ACCOUNTS_ENDPOINT valueFrom: configMapKeyRef: name: "{{ .Chart.Name }}-configmap" key: dropAccountsEndpoint - name: CREATE_TRANSACTION_ENDPOINT valueFrom: configMapKeyRef: name: "{{ .Chart.Name }}-configmap" key: createTransactionEndpoint - name: GET_TRANSACTIONS_ENDPOINT valueFrom: configMapKeyRef: name: "{{ .Chart.Name }}-configmap" key: getTransactionsEndpoint - name: DROP_TRANSACTIONS_ENDPOINT valueFrom: configMapKeyRef: name: "{{ .Chart.Name }}-configmap" key: dropTransactionsEndpoint - name: CHAT_ENDPOINT valueFrom: configMapKeyRef: name: "{{ .Chart.Name }}-configmap" key: chatEndpoint - name: CONVERSATION_BINDING valueFrom: secretKeyRef: name: "binding-innovate-conversation-service" key: binding ================================================ FILE: chart/innovate-bank/templates/hpa.yaml ================================================ {{ if .Values.hpa.enabled }} apiVersion: autoscaling/v2alpha1 kind: HorizontalPodAutoscaler metadata: name: "{{ .Chart.Name }}-hpa-policy" namespace: spec: scaleTargetRef: apiVersion: apps/v2alpha1 kind: Deployment name: "{{ .Chart.Name }}-deployment" minReplicas: {{ .Values.hpa.minReplicas }} maxReplicas: {{ .Values.hpa.maxReplicas }} metrics: - type: Resource resource: name: cpu targetAverageUtilization: {{ .Values.hpa.metrics.cpu.targetAverageUtilization }} - type: Resource resource: name: memory targetAverageUtilization: {{ .Values.hpa.metrics.memory.targetAverageUtilization }} {{ end }} ================================================ FILE: chart/innovate-bank/templates/ingress.yaml ================================================ {{- if .Values.ingress.enabled -}} {{- $serviceName := include "fullname" . -}} {{- $servicePort := .Values.service.externalPort -}} apiVersion: extensions/v1beta1 kind: Ingress metadata: name: {{ template "fullname" . }} labels: app: {{ template "name" . }} chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} release: {{ .Release.Name }} heritage: {{ .Release.Service }} annotations: {{- range $key, $value := .Values.ingress.annotations }} {{ $key }}: {{ $value | quote }} {{- end }} spec: rules: {{- range $host := .Values.ingress.hosts }} - host: {{ $host }} http: paths: - path: / backend: serviceName: {{ $serviceName }} servicePort: {{ $servicePort }} {{- end -}} {{- if .Values.ingress.tls }} tls: {{ toYaml .Values.ingress.tls | indent 4 }} {{- end -}} {{- end -}} ================================================ FILE: chart/innovate-bank/templates/service.yaml ================================================ apiVersion: v1 kind: Service metadata: annotations: prometheus.io/scrape: 'true' name: "{{ .Chart.Name }}" labels: chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}" spec: type: {{ .Values.service.type }} ports: - name: portal port: {{ .Values.service.servicePort }} nodePort: {{ .Values.service.serviceNodePort }} - name: authentication port: {{ .Values.authentication.servicePort }} nodePort: {{ .Values.authentication.serviceNodePort }} - name: accounts port: {{ .Values.accounts.servicePort }} nodePort: {{ .Values.accounts.serviceNodePort }} - name: transactions port: {{ .Values.transactions.servicePort }} nodePort: {{ .Values.transactions.serviceNodePort }} - name: bills port: {{ .Values.bills.servicePort }} nodePort: {{ .Values.bills.serviceNodePort }} - name: support port: {{ .Values.support.servicePort }} nodePort: {{ .Values.support.serviceNodePort }} - name: userbase port: {{ .Values.userbase.servicePort }} nodePort: {{ .Values.userbase.serviceNodePort }} selector: app: "{{ .Chart.Name }}-selector" ================================================ FILE: chart/innovate-bank/values.yaml ================================================ # This is a YAML-formatted file. # Declare variables to be passed into your templates. replicaCount: 1 revisionHistoryLimit: 3 image: repository: innovate-bank tag: 1 pullSecret: regsecret pullPolicy: IfNotPresent resources: requests: cpu: 300m memory: 512Mi livenessProbe: initialDelaySeconds: 3000 periodSeconds: 1000 service: name: Node type: NodePort servicePort: 3100 serviceNodePort: 30200 authentication: servicePort: 3200 serviceNodePort: 30100 accounts: servicePort: 3400 serviceNodePort: 30120 transactions: servicePort: 3600 serviceNodePort: 30140 bills: servicePort: 3800 serviceNodePort: 30160 support: servicePort: 4000 serviceNodePort: 30180 userbase: servicePort: 4100 serviceNodePort: 30050 ingress: enabled: false # Used to create an Ingress record. hosts: - chart-example.local annotations: # kubernetes.io/ingress.class: nginx # kubernetes.io/tls-acme: "true" tls: # Secrets must be manually created in the namespace. # - secretName: chart-example-tls # hosts: # - chart-example.local hpa: enabled: false minReplicas: 2 maxReplicas: 3 metrics: cpu: targetAverageUtilization: 80 memory: targetAverageUtilization: 80 config: mongoUrl: placeholder basePath: placeholder sessionSecret: "***REMOVED***" nodeEnv: production services: ================================================ FILE: cli-config.yml ================================================ # The IBM version of this configuration version : 0.0.3 # The container name used for the run container container-name-run : "innovate-bank-run" # The container name used for the tools container container-name-tools : "innovate-bank-tools" # The project root on the host for the run container to mount to container-path-run host-path-run : . # The project root on the host for the tools container to mount to container-path-tools host-path-tools : . # The project root in the run container to mount to host-path-run container-path-run : "/app" # The project root in the tools container that will be mounted to host-path-tools container-path-tools : "/app" # The list of additional mounts between the host and the run container in the form [host_path:container_path] container-mounts-run: - "./node_modules_linux": "/app/node_modules" # The list of additional mounts between the host and the tools container in the form [host_path:container_path] container-mounts-tools: - "./node_modules_linux": "/app/node_modules" # The port mappings between the host and the container in the form [host:container] container-port-map: "3100:3100,3200:3200" # The port mappings between the host and the container for the debug port in the form [host:container] container-port-map-debug: "5858:5858,5860:5860" # The name for the dockerfile for the run container dockerfile-run : "Dockerfile" # The name for the dockerfile for the tools container dockerfile-tools : "Dockerfile-tools" # The name of image to create from dockerfile-run image-name-run : "innovate-bank-run" # The name of image to create from dockerfile-tools image-name-tools : "innovate-bank-tools" # The command to build the code and docker image for RUN build-cmd-run : "npm install" # The command to execute tests for the code in the tools container test-cmd : "npm run test" # The command to build the code and docker image for DEBUG build-cmd-debug : "npm install" # The command to run the code in the run container run-cmd : "" # The command to execute debug of the code in the tools container debug-cmd : "npm run debug" # The command to stop the code stop-cmd : "npm stop" # The relative path to the helm chart used for Kubernetes deployment chart-path: "chart/innovate-bank" deploy-target: "container" deploy-image-target: "registry.eu-de.bluemix.net/amalamine/innovate-bank" ================================================ FILE: creating-pv.md ================================================ # Creating Persistent Volume in ICP We will be using a persisten volume to deploy mongo db for our application. To create a Persisten Volume 1. Navigate to Menu > Platform > Storage ![console](doc/source/images/pv1.png) ![menu](doc/source/images/pv2.png) 2. Select `Create Persistent Volume` ![Persistent Volume](doc/source/images/pv3.png) 3. In General set name, capacity, access type, storage type. For our use case we will create a persisten volume with name `mongo-pv`, `5 gb` capacity with `Read write many` access mode and storage type `Host Path` ![setting 1](doc/source/images/pv4.png) ![setting 2](doc/source/images/pv5.png) 4. In Parameter set `key` to `/renovate` ![setting 3](doc/source/images/pv6.png) 5. Click create. ================================================ FILE: creating-pvc.md ================================================ # Create Persistent Volume Claim in ICP To create persistent volume claim in IBM Cloud Private 1. After you have created a persistent volume, click on the `Persistent Volume Claim` tab. ![pvc](doc/source/images/pvc1.png) 2. Click on `Create PersistentVolumeClaim` button ![click](doc/source/images/pvc2.png) 3. Select a name, storage request and access mode. For our case we are going with `mongo-pvc`, `1 Gb` and `Read write many` ![setting 1](doc/source/images/pvc3.png) ![setting 2](doc/source/images/pvc4.png) 4. Click Create. ================================================ FILE: idt.js ================================================ 'use strict' /* * Wrapper for the idt (IBM Developer Tools) command. * Run with the same arguments as `idt`, e.g. * `node idt.js build` -> `idt build`. * If `idt` isn't installed, this will prompt you to install. Or you can run * `node idt.js install` to automatically install idt and any other * required dependencies (e.g. docker, git, kubernetes, helm). * */ const fs = require('fs'); const process = require('process'); const cp = require('child_process'); const request = require('request'); const path = require('path'); const chalk = require('chalk'); const node = process.execPath; // Array of args passed to idt.js. const args = process.argv.slice(2); let win = (process.platform === 'win32'); // Either install idt or run idt + args. if (args.includes('install')) { downloadInstaller(); } else { // TODO(gib): Check for idt once this works in scripts: // const checkCmd = win ? 'where idt' : 'which idt'; const checkCmd = 'bx plugin show dev'; let hasIDT = false; try { console.log(chalk.blue('Checking for idt')); cp.execSync(checkCmd); // Don't inherit stdio, we don't want to print the output. hasIDT = true; // If we didn't have idt, the previous command would have thrown. } catch (e) { const prompt = require('prompt-confirm'); new prompt({ name: 'install', message: 'IDT not found, do you want to install it? y/N', default: false }).ask((answer) => { if (answer) { downloadInstaller(() => runIDT(args)); } else { console.error(chalk.red(`Not installing idt, so not running: idt ${args.join(' ')}`)); } }); } if (hasIDT) runIDT(args); } // Run IDT with whatever args we were given. function runIDT(args) { const cmd = 'bx dev ' + args.join(' '); console.log(chalk.blue('Running:'), cmd); cp.execSync(cmd, {stdio: 'inherit'}); } // Download the IDT installer script and trigger runInstaller(). function downloadInstaller(cb) { const url = win ? 'https://ibm.biz/yeoman-idt-win-install' : 'http://ibm.biz/yeoman-idt-install'; const fileName = url.split('/').pop() console.log(chalk.blue('Downloading installer from:'), url); const file = fs.createWriteStream(fileName); request .get({url, followAllRedirects: true}) .on('error', (err) => { console.error(err); }) .pipe(file) .on('finish', () => runInstaller(fileName, cb)); } // Run the installer script and trigger optional callback (cb). function runInstaller(fileName, cb) { const shell = win ? 'powershell.exe' : 'bash'; const filePath = path.resolve(__dirname, fileName); console.log(`Now running: ${shell} ${filePath}`); cp.spawnSync(shell, [filePath], {stdio: 'inherit'}); typeof cb === 'function' && cb(); } ================================================ FILE: mongo-db.md ================================================ # Create Mongo-DB deployment in ICP To create a mongo db deployment in Ibm Cloud Private 1. Navigate to catalog in the top right corner. 2. Search for `mongo` in the catalog. Select mongodb ![mongo](doc/source/images/mongo1.png) 3. Move to configuration tab and give the release a name. ![mongo](doc/source/images/mongo2.png) 4. Expand `All Parameters` 5. Check `Enable Persistance for this deployment`. In **Data Volume Configuration** put the name of the **Persistent Volume Claim** created in the last section. In our case its called `mongo-pvc`. Set the size of the claim to `5 Gb` ![mongo](doc/source/images/mongo3.png) 6. In **MongoDB Configuration** set a username, password, database name and mongo connect command. ![mongo](doc/source/images/mongo4.png) 7. Click Install. ================================================ FILE: package.json ================================================ { "name": "innovate", "version": "1.0.0", "description": "Innovate: Digital Bank", "private": true, "engines": { "node": "^6.9.0" }, "scripts": { "start": "node support/server.js & node accounts/server.js & node authentication/server.js & node bills/server.js & node transactions/server.js & node userbase/server.js & node portal/server.js", "start:cluster": "sl-run server/server.js", "debug": "node --debug server/server.js", "build": "sudo ibmcloud dev build --debug", "test": "sudo ibmcloud dev build --debug", "idt:build": "node idt.js build", "idt:test": "node idt.js test", "idt:debug": "node idt.js debug", "idt:run": "node idt.js run", "idt:deploy": "node idt.js deploy", "idt:install": "node idt.js install" }, "dependencies": { "body-parser": "^1.17.2", "connect-mongo": "^2.0.1", "cookie-parser": "^1.4.3", "date_format": "^0.1.1", "dateformat": "^3.0.2", "dotenv": "^2.0.0", "express": "^4.15.3", "express-session": "^1.15.6", "ip": "^1.1.5", "mongoose": "^5.0.0-rc1", "request": "^2.83.0", "request-promise": "^4.2.2", "strong-supervisor": "^6.2.0", "uuid": "^3.1.0", "watson-developer-cloud": "^2.40.0" }, "devDependencies": { "chai": "^4.0.0", "chalk": "^1.1.3", "mocha": "^3.4.2", "nyc": "^10.3.2", "prompt-confirm": "^1.2.0", "proxyquire": "^1.8.0" } } ================================================ FILE: portal/.cfignore ================================================ .git/ node_modules/ test/ vcap-local.js ================================================ FILE: portal/.dockerignore ================================================ node_modules/ test/ .bluemix/ ================================================ FILE: portal/.gitignore ================================================ # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* # Runtime data pids *.pid *.seed *.pid.lock # Directory for instrumented libs generated by jscoverage/JSCover lib-cov # Coverage directory used by tools like istanbul coverage # nyc test coverage .nyc_output # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) .grunt # Bower dependency directory (https://bower.io/) bower_components # node-waf configuration .lock-wscript # Compiled binary addons (http://nodejs.org/api/addons.html) build/Release # Dependency directories node_modules/ node_modules_linux/ jspm_packages/ # Typescript v1 declaration files typings/ # Optional npm cache directory .npm # Optional eslint cache .eslintcache # Optional REPL history .node_repl_history # Output of 'npm pack' *.tgz # Yarn Integrity file .yarn-integrity # dotenv environment variables file .env # other .DS_Store license_accepted buildContext.tar ================================================ FILE: portal/.ibm-project ================================================ {"build-release-ready":false,"build-debug-ready":true,"deploy-image":"","chart-path":"","ibm-cluster":""} ================================================ FILE: portal/Dockerfile ================================================ FROM ibmcom/ibmnode:latest LABEL maintainer="***REMOVED***" #RUN apt-get install -y nodejs npm WORKDIR /app # Install app dependencies COPY . /app RUN cd /app; npm install; npm prune --production ENV NODE_ENV production ENV PORT 3000 EXPOSE 3000 CMD [ "npm","start" ] ================================================ FILE: portal/Dockerfile-tools ================================================ FROM ibmcom/ibmnode ENV PORT 3000 WORKDIR "/app" # Bundle app source COPY . /app EXPOSE 3000 CMD ["/bin/bash"] ARG bx_dev_user=root ARG bx_dev_userid=1000 RUN BX_DEV_USER=$bx_dev_user RUN BX_DEV_USERID=$bx_dev_userid RUN if [ $bx_dev_user != "root" ]; then useradd -ms /bin/bash -u $bx_dev_userid $bx_dev_user; fi ================================================ FILE: portal/chart/innovate-portal/Chart.yaml ================================================ apiVersion: v1 description: A Helm chart for innovate bank portal name: innovate-portal version: 1.0.0 ================================================ FILE: portal/chart/innovate-portal/templates/deployment.yaml ================================================ apiVersion: extensions/v1beta1 kind: Deployment metadata: name: "{{ .Chart.Name }}-deployment" labels: chart: '{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}' spec: replicas: {{ .Values.replicaCount }} strategy: type: RollingUpdate rollingUpdate: maxUnavailable: 0 maxSurge: 1 revisionHistoryLimit: {{ .Values.revisionHistoryLimit }} template: metadata: labels: app: "{{ .Chart.Name }}-selector" spec: containers: - name: "{{ .Chart.Name }}" image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" imagePullPolicy: {{ .Values.image.pullPolicy }} livenessProbe: httpGet: path: /health port: {{ .Values.service.servicePort }} initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds}} periodSeconds: {{ .Values.livenessProbe.periodSeconds}} resources: requests: cpu: "{{ .Values.image.resources.requests.cpu }}" memory: "{{ .Values.image.resources.requests.memory }}" env: - name: PORT value : "{{ .Values.service.servicePort }}" ================================================ FILE: portal/chart/innovate-portal/templates/hpa.yaml ================================================ {{ if .Values.hpa.enabled }} apiVersion: autoscaling/v2alpha1 kind: HorizontalPodAutoscaler metadata: name: "{{ .Chart.Name }}-hpa-policy" namespace: spec: scaleTargetRef: apiVersion: apps/v2alpha1 kind: Deployment name: "{{ .Chart.Name }}-deployment" minReplicas: {{ .Values.hpa.minReplicas }} maxReplicas: {{ .Values.hpa.maxReplicas }} metrics: - type: Resource resource: name: cpu targetAverageUtilization: {{ .Values.hpa.metrics.cpu.targetAverageUtilization }} - type: Resource resource: name: memory targetAverageUtilization: {{ .Values.hpa.metrics.memory.targetAverageUtilization }} {{ end }} ================================================ FILE: portal/chart/innovate-portal/templates/service.yaml ================================================ apiVersion: v1 kind: Service metadata: annotations: prometheus.io/scrape: 'true' name: "{{ .Chart.Name }}" labels: chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}" spec: type: {{ .Values.service.type }} ports: - port: {{ .Values.service.servicePort }} nodePort: {{ .Values.service.serviceNodePort }} selector: app: "{{ .Chart.Name }}-selector" ================================================ FILE: portal/chart/innovate-portal/values.yaml ================================================ # This is a YAML-formatted file. # Declare variables to be passed into your templates. replicaCount: 1 revisionHistoryLimit: 3 image: repository: mycluster.icp:8500/default/innovate-portal tag: v1.0.0 pullPolicy: Always resources: requests: cpu: 500m memory: 512Mi livenessProbe: initialDelaySeconds: 3000 periodSeconds: 1000 service: name: Node type: NodePort servicePort: 3100 serviceNodePort: 30060 hpa: enabled: false minReplicas: 2 maxReplicas: 3 metrics: cpu: targetAverageUtilization: 80 memory: targetAverageUtilization: 80 services: ================================================ FILE: portal/cli-config.yml ================================================ # The IBM version of this configuration version : 0.0.3 # The container name used for the run container container-name-run : "innovate-portal-run" # The container name used for the tools container container-name-tools : "innovate-portal-tools" # The project root on the host for the run container to mount to container-path-run host-path-run : . # The project root on the host for the tools container to mount to container-path-tools host-path-tools : . # The project root in the run container to mount to host-path-run container-path-run : "/app" # The project root in the tools container that will be mounted to host-path-tools container-path-tools : "/app" # The list of additional mounts between the host and the run container in the form [host_path:container_path] container-mounts-run: - "./node_modules_linux": "/app/node_modules" # The list of additional mounts between the host and the tools container in the form [host_path:container_path] container-mounts-tools: - "./node_modules_linux": "/app/node_modules" # The port mappings between the host and the container in the form [host:container] container-port-map : "3000:3000" # The port mappings between the host and the container for the debug port in the form [host:container] container-port-map-debug : "5858:5858" # The name for the dockerfile for the run container dockerfile-run : "Dockerfile" # The name for the dockerfile for the tools container dockerfile-tools : "Dockerfile-tools" # The name of image to create from dockerfile-run image-name-run : "innovate-portal-run" # The name of image to create from dockerfile-tools image-name-tools : "innovate-portal-tools" # The command to build the code and docker image for RUN build-cmd-run : "npm install" # The command to execute tests for the code in the tools container test-cmd : "npm run test" # The command to build the code and docker image for DEBUG build-cmd-debug : "npm install" # The command to run the code in the run container run-cmd : "" # The command to execute debug of the code in the tools container debug-cmd : "npm run debug" # The command to stop the code stop-cmd : "npm stop" # The relative path to the helm chart used for Kubernetes deployment chart-path: "chart/innovate-portal" deploy-target: "container" deploy-image-target: "mycluster.icp:8500/default/innovate-portal" ================================================ FILE: portal/config.js ================================================ module.exports = { development: { ports: { portal: 3100, authentication: 3200, accounts: 3400, transactions: 3600, bills: 3800, support: 4000, userbase: 4100 } }, production: { ports: { portal: 30200, authentication: 30100, accounts: 30120, transactions: 30140, bills: 30160, support: 30180, userbase: 30050 } } }; ================================================ FILE: portal/idt.js ================================================ 'use strict' /* * Wrapper for the idt (IBM Developer Tools) command. * Run with the same arguments as `idt`, e.g. * `node idt.js build` -> `idt build`. * If `idt` isn't installed, this will prompt you to install. Or you can run * `node idt.js install` to automatically install idt and any other * required dependencies (e.g. docker, git, kubernetes, helm). * */ const fs = require('fs'); const process = require('process'); const cp = require('child_process'); const request = require('request'); const path = require('path'); const chalk = require('chalk'); const node = process.execPath; // Array of args passed to idt.js. const args = process.argv.slice(2); let win = (process.platform === 'win32'); // Either install idt or run idt + args. if (args.includes('install')) { downloadInstaller(); } else { // TODO(gib): Check for idt once this works in scripts: // const checkCmd = win ? 'where idt' : 'which idt'; const checkCmd = 'bx plugin show dev'; let hasIDT = false; try { console.log(chalk.blue('Checking for idt')); cp.execSync(checkCmd); // Don't inherit stdio, we don't want to print the output. hasIDT = true; // If we didn't have idt, the previous command would have thrown. } catch (e) { const prompt = require('prompt-confirm'); new prompt({ name: 'install', message: 'IDT not found, do you want to install it? y/N', default: false }).ask((answer) => { if (answer) { downloadInstaller(() => runIDT(args)); } else { console.error(chalk.red(`Not installing idt, so not running: idt ${args.join(' ')}`)); } }); } if (hasIDT) runIDT(args); } // Run IDT with whatever args we were given. function runIDT(args) { const cmd = 'bx dev ' + args.join(' '); console.log(chalk.blue('Running:'), cmd); cp.execSync(cmd, {stdio: 'inherit'}); } // Download the IDT installer script and trigger runInstaller(). function downloadInstaller(cb) { const url = win ? 'https://ibm.biz/yeoman-idt-win-install' : 'http://ibm.biz/yeoman-idt-install'; const fileName = url.split('/').pop() console.log(chalk.blue('Downloading installer from:'), url); const file = fs.createWriteStream(fileName); request .get({url, followAllRedirects: true}) .on('error', (err) => { console.error(err); }) .pipe(file) .on('finish', () => runInstaller(fileName, cb)); } // Run the installer script and trigger optional callback (cb). function runInstaller(fileName, cb) { const shell = win ? 'powershell.exe' : 'bash'; const filePath = path.resolve(__dirname, fileName); console.log(`Now running: ${shell} ${filePath}`); cp.spawnSync(shell, [filePath], {stdio: 'inherit'}); typeof cb === 'function' && cb(); } ================================================ FILE: portal/manifest.yml ================================================ --- applications: - instances: 1 timeout: 180 name: innovate-portal buildpack: sdk-for-nodejs command: npm start memory: 512M ================================================ FILE: portal/package.json ================================================ { "name": "innovate", "version": "1.0.0", "description": "Innovate: Digital Bank", "private": true, "engines": { "node": "^6.9.0" }, "scripts": { "start": "node server.js", "start:cluster": "sl-run server/server.js", "debug": "node --debug server/server.js", "build": "npm run build:idt", "test": "npm run test:idt", "idt:build": "node idt.js build", "idt:test": "node idt.js test", "idt:debug": "node idt.js debug", "idt:run": "node idt.js run", "idt:deploy": "node idt.js deploy", "idt:install": "node idt.js install" }, "dependencies": { "body-parser": "^1.17.2", "connect-mongo": "^2.0.1", "cookie-parser": "^1.4.3", "date_format": "^0.1.1", "dateformat": "^3.0.2", "dotenv": "^2.0.0", "express": "^4.15.3", "express-session": "^1.15.6", "mongoose": "^5.0.0-rc1", "request": "^2.83.0", "request-promise": "^4.2.2", "strong-supervisor": "^6.2.0" }, "devDependencies": { "chai": "^4.0.0", "chalk": "^1.1.3", "mocha": "^3.4.2", "nyc": "^10.3.2", "prompt-confirm": "^1.2.0", "proxyquire": "^1.8.0" } } ================================================ FILE: portal/public/accounts.html ================================================ Innovate: Digital Bank

Accounts

AED

Total balance

Current

Savings

Credit

Prepaid

================================================ FILE: portal/public/bills.html ================================================ Innovate: Digital Bank

Bills

Due this month

Water and Electricity

Pay

Home Entertainment

Pay

Mobile Phone

Pay

Credit Card

Pay
================================================ FILE: portal/public/chat.html ================================================ Innovate: Digital Bank
Type something to see the output
================================================ FILE: portal/public/index.html ================================================ Innovate: Digital Bank

This is banking like never before. We’re building the best digital bank on the planet and we want you on board.

Start banking Show me more

The bank of the future

Built for your smartphone, this is banking like never before. One that updates your balance instantly, sends intelligent notifications, and is actually easy to use.

Instant notifications

Innovate sends you a notification whenever you make a payment.

Fast, friendly support

Innovate provides world-class support through in-app chat, with an average response time of under ten minutes.

Budget breakdowns

We automatically categorise your transactions to help you keep track of your spending.

Fee-free travel abroad

Purchases with your Innovate card are free, both in country and abroad.

Help us build the kind of bank you want to use

================================================ FILE: portal/public/login.html ================================================ Innovate: Digital Bank
================================================ FILE: portal/public/overview.html ================================================ Innovate: Digital Bank

Spending

AED

Spent this month

================================================ FILE: portal/public/res/css/chat.css ================================================ /* ----------------------------------------------------- */ /* CHAT */ /* ----------------------------------------------------- */ #contentParent { height: 80%; margin-bottom: 20px; } .responsive-columns-wrapper { display: -ms-flexbox; display: -webkit-flex; display: flex; flex-direction: row; -ms-display: flex; -ms-flex-direction: row; } .responsive-column { -webkit-flex: 1; -ms-flex: 1; flex: 1; overflow: auto; } #chat-column-holder { text-align: center; } .chat-column { height: 100%; padding: 0.9375rem 0 0.625rem 0; margin: auto; text-align: left; max-width: 50rem; min-width: 9.375rem; } #scrollingChat { margin: 0.75rem; overflow-y: auto; overflow-x: hidden; height: calc(100% - 4rem); } .message-inner { opacity: 0; margin-top: 0.9375rem; -webkit-transition-property: opacity, margin-top; -webkit-transition-duration: 0.75s; -webkit-transition-timing-function: ease-in; -moz-transition-property: opacity, margin-top; -moz-transition-duration: 0.75s; -moz-transition-timing-function: ease-in; -o-transition-property: opacity, margin-top; -o-transition-duration: 0.75s; -o-transition-timing-function: ease-in; -ms-transition-property: opacity, margin-top; -ms-transition-duration: 0.75s; -ms-transition-timing-function: ease-in; transition-property: opacity, margin-top; transition-duration: 0.75s; transition-timing-function: ease-in; } .load .message-inner { opacity: 1; margin-top: 0.3125rem; } .from-user { text-align: right; } .from-user .message-inner { position: relative; font-size: 1rem; color: #fff; letter-spacing: 0.015rem; line-height: 1.3125rem; background: #00B4A0; border-radius: 1.25rem; border-bottom-right-radius: 0; text-align: left; display: inline-block; margin-left: 2.5rem; min-width: 2.5rem; } .from-user .message-inner p { margin: 0.3125rem; padding: 0 0.9375rem; } .from-user .message-inner:before, .from-user .message-inner:after { content: ""; position: absolute; } .from-user .message-inner:before { z-index: -2; bottom: -0.375rem; right: 0; height: 0.375rem; width: 0.5rem; background: #1cb3a0; } .from-user .message-inner:after { z-index: -1; bottom: -0.5rem; right: 0; height: 0.5rem; width: 0.5rem; background: #fff; border-top-right-radius: 1.25rem; } .from-watson .message-inner { position: relative; border-radius: 1.5625rem; font-size: 1rem; color: #B5B5B5; letter-spacing: 0.015rem; line-height: 1.3125rem; } .from-watson.latest .message-inner { color: #323232; } .from-watson p { margin: 0.3125rem; padding: 0 1.25rem; } .from-watson.latest.top p:before { content: "."; color: #d24142; background-image: url("images/marker.png"); background-size: 0.3125rem 1.3125rem; position: absolute; z-index: 2; left: 0.4375rem; width: 0.3125rem; height: 1.3125rem; line-height: 1.3125rem; } #textInput { border: none; outline: none; background: transparent; font-size: 1rem; color: #323232; letter-spacing: 0.015rem; line-height: 1.3125rem; height: 2.5rem; max-width: 100%; padding-left: 0.125rem; margin-bottom: -0.125rem; font-family: Helvetica Neue for IBM, Helvetica Neue, Helvetica, Arial, sans-serif; } #textInput.underline { border-bottom: 2px solid #00B4A0; } ::-webkit-input-placeholder { color: #B5B5B5; } ::-moz-placeholder { color: #B5B5B5; opacity: 1; } input:-moz-placeholder { color: #B5B5B5; opacity: 1; } :-ms-input-placeholder { color: #B5B5B5; } ::-ms-clear { display: none; } .inputOutline { display: block; border-bottom: 0.0625rem solid #aeaeae; margin-left: 0.5rem; margin-right: 0.5rem; } #textInputDummy { position:absolute; white-space:pre; top: 0; left: -1000%; opacity: 0; } #payload-column { display: none; font-family: Monaco, monospace; font-size: 0.75rem; letter-spacing: 0; line-height: 1.125rem; background-color: #23292A; color: #fff; overflow-x: auto; width: 45%; max-width: 32.0625rem; min-width: 29.6875rem; } #payload-column.full { width: 100%; max-width: none; min-width: initial; } #payload-column .header-text, #payload-column #payload-initial-message { font-family: Helvetica Neue for IBM, Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 1.125rem; color: #9E9E9E; letter-spacing: 0.01875rem; padding: 0.5rem; padding-left: 2.5rem; background: #383D3E; } .payload .line-numbers, .payload .payload-text { padding: 0.5rem; } .line-numbers { width: 2rem; color: #898989; text-align: right; } pre { margin: 0; word-wrap: normal; } .string { color: #54EED0; } .boolean, .null, .number { color: #CE8EFF; } .key { color: #66B7FF; } ================================================ FILE: portal/public/res/css/queries.css ================================================ /* LARGE TABLETS TO DESKTOP BROWSERS */ @media only screen and (max-width: 1200px) { .hero-text-box { width: 100%; padding: 0 3%; } .row { padding: 0 3%; } } /* TABLETS & PORTRAIT MODE */ @media only screen and (max-width: 1023px) { body { font-size: 80%; } section { padding 60px 0; } .long-copy { width: 80%; margin-left: 10% } } /* LARGE PHONES TO SMALL TABLETS */ @media only screen and (max-width: 767px) { .steps-box { margin-top: 10px; } .steps-box:first-child { margin-top: 10px; margin-left: -60px; } .steps-box:last-child { margin-top: 10px; } .steps-box { margin-top: 10px; } .works-step { margin-bottom: 20px; } .works-step:last-of-type { margin-bottom: 30px; } .app-screen { width: 40%; } .works-step div { height: 35px; width: 35px; font-size: 135%; margin-right: 15px;} .btn-app img { height: 30px; } .plan-box ul li .icon-small { width: 2%} .plan-price { font-size: 200%} .plan-box .btn { padding: 10%;} .section-cities .icon-small { width: 2%} .contact-form { width: 90%; } .mobile-nav-icon { display: inline-block; } .main-nav { float: left; margin-top: 2px; display: none; } .main-nav li { display: block; } .main-nav li a:link, .main-nav li a:visited { display: block; border: 0; } .sticky-nav .main-nav { margin-top: 0px; } .sticky-nav .mobile-nav-icon i { color: #555; } } /* SMALL PHONES */ @media only screen and (max-width: 480px) { .logo { height: 80px; } .hero-text-box .btn { margin-bottom: 10px; } .section-features h3, .section-features p { text-align: center; } .app-screen { width: 30%; float: left; margin: 0 0 20px 50%; } .works-step div { height: 20px; width: 20px; font-size: 80%; padding: 0.5%; font-weight: 500;} .btn-app img { height: 35px; padding-left: 3%} .plan-box { font-size: 120% } .plan-box ul li .icon-small { width: 5%} .plan-price { font-size: 300%} .plan-box .btn { padding: 5%;} .contact-form .row { padding: 5px; } footer { padding-left: 15px; } .footer-nav { float: none; padding-bottom: 10px; width: 120%; } .social-links { padding-bottom: 10px; float: none; } .section-spending { padding: 12px; } .section-spending h2 { margin-bottom: 15px } .section-spending h3 { font-size: 200%; } .section-spending .small { font-size: 50%; } .section-spending p { font-weight: 200; } .section-category-list { padding-top: 0px; } .section-category-list .row { margin-bottom: 5px; } .section-category-list .icon-big { padding: 0px; margin: 0px; } .section-category-list h3 { text-align: center; font-size: 120%; padding-bottom: 10px;} .section-category-list p { text-align: center; font-size: 90%; padding-bottom: 5px; } .section-bill-list { padding-top: 0px; } .section-bill-list h3 { font-size: 120%; text-align: center; } .section-bill-list p { font-size: 80%; text-align: center; padding-bottom: 5px;} .service-provider-logo { width: 40px; height: auto; margin-left: 45%; } .section-bill-list .btn {margin-left: calc(47.5% - 20px); padding: 3px 20px; margin-bottom: 10px; margin-top: 5px;} .section-transaction-list .category { margin-bottom: 10px; margin-top: 10px; } .section-transaction-list .row { border-bottom: 0.2px solid #ccc;} .section-transaction-list .transaction {display: inline-flex; width: 100%; padding-bottom: 10px;} } ================================================ FILE: portal/public/res/css/styles.css ================================================ /* ----------------------------------------------------- */ /* BASIC SETUP */ /* ----------------------------------------------------- */ * { margin: 0; padding: 0; -webkit-box-sizing: border-box; box-sizing: border-box; } html { height: 100%; background-color: #fff; color: #555; font-family: 'Lato', 'Arial', 'sans-serif'; font-size: 2opx; font-weight: 300; text-rendering: optimizeLegibility; } body { height: 100%; } .clearfix {zoom: 1} .clearfix:after { content: '.'; clear: both; display: block; height: 0; visibility: hidden; } /* ----------------------------------------------------- */ /* HEADER */ /* ----------------------------------------------------- */ header { background-image: -webkit-gradient(linear, left top, left bottom, from(rgba(0, 0, 0, 0.7)), to(rgba(0, 0, 0, 0.7))),url(images/hero.jpg); background-image: linear-gradient(rgba(0, 0, 0, 0.7), rgba(0, 0, 0, 0.7)),url(images/hero.jpg); background-size: cover; background-position: center; height: 100vh; background-attachment: fixed; } .bar { height: 20%; } .hero-text-box { position: absolute; width: 1140px; top: 50%; left: 50%; -webkit-transform: translate(-50%, -50%); transform: translate(-50%, -50%); } .logo { height: 100px; width: auto; float: left; margin-top: 20px; } .logo-black { display: none; height: 50px; width: auto; float: left; margin: 5px 0; } /* MAIN NAV */ .main-nav { float: right; list-style: none; margin-top: 50px; } .main-nav li { display: inline-block; margin-left: 20px; } .main-nav li a:link, .main-nav li a:visited { padding: 8px 0; color: #fff; text-decoration: none; text-transform: uppercase; font-size: 90%; border-bottom: 2px solid transparent; -webkit-transition: border-bottom 0.2s; transition: border-bottom 0.2s; } .main-nav li a:hover, .main-nav li a:active { border-bottom: 2px solid #d24142; } /* MOBILE NAV */ .mobile-nav-icon { float: right; margin-top: 20px; cursor: pointer; display: none; } .mobile-nav-icon i { font-size: 200%; color: #fff; } /* STICKY NAV */ .sticky-nav { position: fixed; top: 0; left: 0; width: 100%; background-color: rgba(255, 255, 255, 0.98); box-shadow: 0 2px 2px #efefef; z-index: 9999; } .sticky-nav .main-nav { margin-top: 18px; } .sticky-nav .main-nav li a:link, .sticky-nav .main-nav li a:visited { padding: 16px 0; color: #555; } .sticky-nav .logo-black { display: block; } .sticky-nav .logo { display: none; } /* ----------------------------------------------------- */ /* REUSABLE COMPONENTS */ /* ----------------------------------------------------- */ .row { max-width: 1140px; margin: 0 auto; } .left-align { text-align: left; } .center { margin: auto; } section { padding: 80px 0; } .box { padding: 1%; } /* ------- HEADINGS ------- */ h1, h2, h3 { font-weight: 300; text-transform: uppercase; } h1 { margin-top: 0; margin-bottom: 20px; color: #fff; font-size: 240%; word-spacing: 4px; letter-spacing: 1px; } h2 { font-size: 180%; word-spacing: 2px; text-align: center; margin-bottom: 30px; letter-spacing: 1px; } h2:after { display: block; height: 2px; background-color: #d24142; content: " "; width: 100px; margin: 0 auto; margin-top: 30px; } h3 { font-size: 110%; margin-bottom: 15px; } /* ------- PARAGRAPHS ------- */ .long-copy { line-height: 145%; width: 80%; margin-left: 10%; font-size: 130%; } .box p { font-size: 90%; line-height: 145%; } /* ------- ICONS ------- */ .icon-big { font-size: 350%; display: block; color: #d24142; margin-bottom: 10px; margin-left: -25px; text-align: center; } .icon-small { display: inline-block; width: 30px; text-align: center; color: #d24142; font-size: 120%; line-height: 120%; vertical-align: middle; margin-top: -5px; margin-right: 10px; } /* ------- ICONS ------- */ a:link, a:visited { color: #d24142; text-decoration: none; padding-bottom: 1px; border-bottom: 1px solid #d24142; -webkit-transition: border-botton 0.2s, color 0.2s; transition: border-botton 0.2s, color 0.2s; } a:hover, a:active { color: #555; border-bottom: 1px solid transparent; } /* ------- BUTTONS ------- */ .btn:link, .btn:visited, input[type=submit] { display: inline-block; padding: 10px 30px; font-weight: 300; text-decoration: none; border-radius: 200px; -webkit-transition: background-color 0.2s, border 0.2s, color 0.2s; transition: background-color 0.2s, border 0.2s, color 0.2s; } .btn:hover, .btn:active, input[type=submit] { background-color: #d24142; } .btn-full:link, .btn-full:visited, input[type=submit] { background-color: #d24142; border: 1px solid #d24142; color: #fff; margin-right: 15px; } .btn-full:hover, .btn-full:active, input[type=submit] { background-color: #d24142; border: 1px solid #d24142; } .btn-ghost:link, .btn-ghost:visited { border: 1px solid #d24142; color: #d24142; } .btn-ghost:hover, .btn-ghost:active { border: 1px solid #d24142; color: #fff; } .btn-disabled:link, .btn-disabled:visited { background-color: #ccc; border: 1px solid #ccc; color: #fff; margin-right: 15px; cursor: not-allowed; } .btn-disabled:hover, .btn-disabled:active { background-color: #ccc; border: 1px solid #ccc; cursor: not-allowed; } /* ------- POPUPS ------- */ .overlay { position: fixed; top: 0; bottom: 0; left: 0; right: 0; background: rgba(0, 0, 0, 0.6); transition: opacity 0.5s; visibility: hidden; opacity: 0; } .overlay:target { visibility: visible; opacity: 1; } .popup { margin: calc(20% + 20px) auto; padding: 30px 15px;; background: #fff; border-radius: 5px; max-width: 600px; width: 60%; min-height: 30%; height: auto; position: relative; } .popup h2 { margin-top: 0; color: #000; } .popup .close { position: absolute; top: 20px; right: 20px; font-size: 180%; font-weight: 200; text-decoration: none; color: #000; border-bottom: none; } .popup .close:hover { color: #d24142; font-weight: 300; } .popup .content { overflow: auto; text-align: center; max-height: 50%; margin-bottom: 5px; } .popup .btn { margin-top: 15px; margin-bottom: 10px; margin-left: calc(43% - 20px); padding: 10px 20px; } .popup .icon-big { margin: auto; } /* ----------------------------------------------------- */ /* FEATURES */ /* ----------------------------------------------------- */ .section-features .long-copy { margin-bottom: 30px; } /* ----------------------------------------------------- */ /* CONTACT FORM */ /* ----------------------------------------------------- */ .section-form { background-color: #f4f4f4; } .contact-form { width: 60%; margin: 0 auto; } .contact-form input[type=text], .contact-form input[type=email], .contact-form select, .contact-form textarea { width: 100%; padding: 7px; border-radius: 3px; border: 1px solid #ccc; } .contact-form textarea { height: 100px; } .contact-form input[type=checkbox] { margin: 10px 5px 10px 0; } *:focus { outline: none; } /* ----------------------------------------------------- */ /* FOOTER */ /* ----------------------------------------------------- */ footer { background-color: #333; padding: 40px; } footer p:last-child{ padding-top: 10px; color: #d24142; } .footer-nav { list-style: none; float: left; } .social-links { list-style: none; float: right; } .footer-nav li, .social-links li { display: inline-block; margin-right: 20px; } .footer-nav li:last-child, .social-links li:last-child { margin-right: 0; } .footer-nav li a:link, .footer-nav li a:visited, .social-links li a:link, .social-links li a:visited { text-decoration: none; border: 0; color: #888; } .footer-nav li a:hover, .footer-nav li a:active { color: #ddd; cursor: pointer; } .social-links li a:link, .social-links li a:visited { font-size: 150%; } .ion-social-facebook, .ion-social-twitter, .ion-social-googleplus, .ion-social-instagram { -webkit-transition: color 0.2s; transition: color 0.2s; } .ion-social-facebook:hover { color: #3b5998; } .ion-social-twitter:hover { color: #00aced; } .ion-social-googleplus:hover { color: #dd4b39; } .ion-social-instagram:hover { color: #8a3ab9; } .footer p { color: #888; text-align: center; font-size: 90%; margin-top: 20px; } /* ----------------------------------------------------- */ /* SIGN UP & LOGIN FORMS */ /* ----------------------------------------------------- */ .cutout { padding: 0px 30px 30px 30px; } .sign-up-box { margin: 0; padding: 0; height: 100vh; background-color: #f4f4f4; } .sign-up-form { width: 85%; margin: 0 auto; } .sign-up-form input, .sign-up-form select { width: 100%; padding: 5px; margin-bottom: 15px; border-radius: 3px; border: 1px solid #ccc; } .sign-up-form input[type=submit] { border-radius: 50px; margin-bottom: 5px; } .sign-up-form input[type=checkbox] { width: 15px; } .sign-up-form .center { text-align: center; } /* ----------------------------------------------------- */ /* SPENDING */ /* ----------------------------------------------------- */ /* ------- OVERVIEW ------- */ .section-spending { background-color: #f4f4f4; text-align: center; padding: 30px; border-bottom: 0.2px solid #ccc; box-shadow: 0 2px 2px #efefef; } .section-spending h3 { font-size: 250%; font-weight: 300; margin-bottom: 0; } .section-spending .small { font-size: 65%; } .section-spending p { font-weight: 200; } /* ------- CATEGORY LIST ------- */ .section-category-list { padding-top: 15px; } .section-category-list .row { padding-top: 10px; border-bottom: 0.2px solid #ccc; } .section-category-list .row:hover, .section-category-list .row:active { color: #d24142; cursor: pointer; font-size: 102%; box-shadow: 0px 3.5px 3.5px #efefef; } .section-category-list .icon-big { text-align: center; padding-left: 70%; cursor: pointer; } .section-category-list h3 { text-align: left; font-size: 140%; padding-left: 20; margin-bottom: 3px; } .section-category-list p { text-align: left; font-size: 110%; color: #888; } .greyed-out { font-weight: 100; color: #bfbfbf; } .greyed-out .icon-big { color: #e0e5e8; } .section-category-list .row.greyed-out:hover, .section-category-list .row.greyed-out:active { color: #bfbfbf; cursor: default; font-size: 100%; box-shadow: 0 0px 0px; } /* ----------------------------------------------------- */ /* BILLS */ /* ----------------------------------------------------- */ .section-bill-list { padding-top: 15px; } .section-bill-list .row { padding-top: 10px; border-bottom: 0.2px solid #ccc; } .section-bill-list h3 { font-size: 130%; margin-bottom: 5px; } .section-bill-list p { font-size: 90%; } .service-provider-logo { width: 50px; height: auto; margin-left: 50%; margin-bottom: 10px; } /* ----------------------------------------------------- */ /* TRANSACTIONS */ /* ----------------------------------------------------- */ .section-transaction-list { padding-top: 0px; } .section-transaction-list .category { font-size: 130%; margin-bottom: 0px; color: #888888; margin-top: 15px; } .section-transaction-list h2 { font-size: 120%; margin-bottom: 0px; text-align: left; } .section-transaction-list h2:after { background-color: #d24142; content: " "; width: 100%; margin-top: 15px; } .section-transaction-list .row { border-bottom: 0.2px solid #ccc; } .section-transaction-list h3 { font-size: 130%; margin-bottom: 5px; margin-left: 20px; } .section-transaction-list h3:last-child { margin-top: 3%; } .section-transaction-list p { font-size: 80%; margin-left: 20px; } ================================================ FILE: portal/public/res/js/accounts.js ================================================ $(document).ready(function() { var http = new XMLHttpRequest(); var getUrl = window.location; var baseUrl = getUrl.protocol + "//" + getUrl.host; var AccountsEndpoint = baseUrl + '/endpoints/accounts/get'; console.log('Accounts >>>> ', AccountsEndpoint) var uuid = 0; var selectors = { current: { label: '#current_label', number: '#current_number', balance: '#current_balance', button: '#current_button' }, savings: { label: '#savings_label', number: '#savings_number', balance: '#savings_balance', button: '#savings_button' }, credit: { label: '#credit_label', number: '#credit_number', balance: '#credit_balance', button: '#credit_button' }, prepaid: { label: '#prepaid_label', number: '#prepaid_number', balance: '#prepaid_balance', button: '#prepaid_button' } } //Ensure authenticated and get uuid http.open('GET', '/endpoints/auth', true); http.setRequestHeader('Content-type', 'application/json'); http.onreadystatechange = function() { if (http.readyState === 4 && http.status === 500) { window.location.replace('/index.html') } else if (http.readyState === 4 && http.status === 200 && http.responseText) { var response = JSON.parse(http.responseText) uuid = response.uuid http.open('POST', AccountsEndpoint, true); http.setRequestHeader('Content-type', 'application/json'); http.onreadystatechange = function() { if (http.readyState === 4 && http.status === 200 && http.responseText) { var accounts = JSON.parse(http.responseText); var total = 0; var innerHTML = '' for (var type in selectors) { var account = accounts.find(item => item.type==type) if (account) { $(selectors[type].label).html('Account No. '); $(selectors[type].number).html(account.number); $(selectors[type].balance).html('AED ' + account.balance); $(selectors[type].button).html('Transfer'); if (account.balance <= 0) $(selectors[type].button).removeClass('btn-ghost').addClass('btn-disabled') $(selectors[type].button).attr("href", "#confirm-popup-transfer"); total += account.balance innerHTML += `` } else { $(selectors[type].button).html('Activate'); $(selectors[type].button).attr("href", "#confirm-popup-" + type); $(selectors[type].button).removeClass('btn-disabled').addClass('btn-ghost') } } $('#from').html(innerHTML) $('#to').html(innerHTML) $('#js-balance').html(total) } }; http.send(JSON.stringify({'uuid': uuid})); } }; http.send(); }) ================================================ FILE: portal/public/res/js/api.js ================================================ // The Api module is designed to handle all interactions with the server var Api = (function() { var requestPayload; var responsePayload; var getUrl = window.location; var baseUrl = getUrl.protocol + "//" + getUrl.host; var messageEndpoint = baseUrl + '/endpoints/support/chat'; console.log('Support >>>> ', messageEndpoint) // Publicly accessible methods defined return { sendRequest: sendRequest, // The request/response getters/setters are defined here to prevent internal methods // from calling the methods without any of the callbacks that are added elsewhere. getRequestPayload: function() { return requestPayload; }, setRequestPayload: function(newPayloadStr) { requestPayload = JSON.parse(newPayloadStr); }, getResponsePayload: function() { return responsePayload; }, setResponsePayload: function(newPayloadStr) { responsePayload = JSON.parse(newPayloadStr); } }; // Send a message request to the server function sendRequest(text, context) { // Build request payload var payloadToWatson = {}; if (text) { payloadToWatson.input = { text: text }; } if (context) { payloadToWatson.context = context; } // Built http request var http = new XMLHttpRequest(); http.open('POST', messageEndpoint, true); http.setRequestHeader('Content-type', 'application/json'); http.onreadystatechange = function() { if (http.readyState === 4 && http.status === 200 && http.responseText) { Api.setResponsePayload(http.responseText); } }; var params = JSON.stringify(payloadToWatson); // Stored in variable (publicly visible through Api.getRequestPayload) // to be used throughout the application if (Object.getOwnPropertyNames(payloadToWatson).length !== 0) { Api.setRequestPayload(params); } // Send request http.send(params); } }()); ================================================ FILE: portal/public/res/js/bills.js ================================================ $(document).ready(function() { var http = new XMLHttpRequest(); var getUrl = window.location; var baseUrl = getUrl.protocol + "//" + getUrl.host; var billsEndpoint = baseUrl + '/endpoints/bills/get'; console.log('Bills >>>> ', billsEndpoint) var uuid = 0; var selectors = { utilities: { entity: '#utilities_entity', account: '#utilities_account', amount: '#utilities_amount', popup_entity: '#utilities_bill_entity', popup_account: '#utilities_bill_account', popup_amount: '#utilities_bill_amount', button: '#utilities_pay_button', confirm_button: '#utilities_confirm_button' }, home_entertainment: { entity: '#home_entertainment_entity', account: '#home_entertainment_account', amount: '#home_entertainment_amount', popup_entity: '#home_entertainment_bill_entity', popup_account: '#home_entertainment_bill_account', popup_amount: '#home_entertainment_bill_amount', button: '#home_entertainment_pay_button', confirm_button: '#home_entertainment_confirm_button' }, mobile_phone: { entity: '#mobile_phone_entity', account: '#mobile_phone_account', amount: '#mobile_phone_amount', popup_entity: '#mobile_phone_bill_entity', popup_account: '#mobile_phone_bill_account', popup_amount: '#mobile_phone_bill_amount', button: '#mobile_phone_pay_button', confirm_button: '#mobile_phone_confirm_button' }, credit_card: { entity: '#credit_card_entity', account: '#credit_card_account', amount: '#credit_card_amount', popup_entity: '#credit_card_bill_entity', popup_account: '#credit_card_bill_account', popup_amount: '#credit_card_bill_amount', button: '#credit_card_pay_button', confirm_button: '#credit_card_confirm_button' } } //Ensure authenticated and get uuid http.open('GET', '/endpoints/auth', true); http.setRequestHeader('Content-type', 'application/json'); http.onreadystatechange = function() { if (http.readyState === 4 && http.status === 500) { window.location.replace('/index.html') } else if (http.readyState === 4 && http.status === 200 && http.responseText) { var response = JSON.parse(http.responseText) console.log('response >>>> ', response) uuid = response.uuid http.open('POST', billsEndpoint, true); http.setRequestHeader('Content-type', 'application/json'); http.onreadystatechange = function() { if (http.readyState === 4 && http.status === 200 && http.responseText) { var bills = JSON.parse(http.responseText); var total = 0; for (var category in selectors) { var bill = bills.find(item => item.category==category) if (bill) { $(selectors[category].entity).html(bill.entity); $(selectors[category].popup_entity).html(bill.entity); $(selectors[category].account).html(' Account No. ' + bill.account_no); $(selectors[category].popup_account).html(' Account No. ' + bill.account_no); $(selectors[category].amount).html('AED ' + bill.amount); $(selectors[category].popup_amount).html('AED ' + bill.amount); $(selectors[category].confirm_button).attr("href", "/endpoints/bills/pay?amount="+bill.amount+"&entity="+bill.entity+"&category="+category+"&account="+bill.account_no); if (bill.amount <= 0) $(selectors[category].button).removeClass('btn-ghost').addClass('btn-disabled') total += bill.amount } else { $(selectors[category].button).removeClass('btn-ghost').addClass('btn-disabled') } } $('#js-total').html(`AED ` + total) } }; http.send(JSON.stringify({'uuid': uuid})); } }; http.send(); }) ================================================ FILE: portal/public/res/js/common.js ================================================ // The Common module is designed as an auxiliary module // to hold functions that are used in multiple other modules /* eslint no-unused-vars: "off" */ var Common = (function() { // Publicly accessible methods defined return { buildDomElement: buildDomElementFromJson, fireEvent: fireEvent, listForEach: listForEach }; // Take in JSON object and build a DOM element out of it // (Limited in scope, cannot necessarily create arbitrary DOM elements) // JSON Example: // { // "tagName": "div", // "text": "Hello World!", // "className": ["aClass", "bClass"], // "attributes": [{ // "name": "onclick", // "value": "alert("Hi there!")" // }], // "children: [{other similarly structured JSON objects...}, {...}] // } function buildDomElementFromJson(domJson) { // Create a DOM element with the given tag name var element = document.createElement(domJson.tagName); // Fill the "content" of the element if (domJson.text) { element.innerHTML = domJson.text; } else if (domJson.html) { element.insertAdjacentHTML('beforeend', domJson.html); } // Add classes to the element if (domJson.classNames) { for (var i = 0; i < domJson.classNames.length; i++) { element.classList.add(domJson.classNames[i]); } } // Add attributes to the element if (domJson.attributes) { for (var j = 0; j < domJson.attributes.length; j++) { var currentAttribute = domJson.attributes[j]; element.setAttribute(currentAttribute.name, currentAttribute.value); } } // Add children elements to the element if (domJson.children) { for (var k = 0; k < domJson.children.length; k++) { var currentChild = domJson.children[k]; element.appendChild(buildDomElementFromJson(currentChild)); } } return element; } // Trigger an event to fire function fireEvent(element, event) { var evt; if (document.createEventObject) { // dispatch for IE evt = document.createEventObject(); return element.fireEvent('on' + event, evt); } // otherwise, dispatch for Firefox, Chrome + others evt = document.createEvent('HTMLEvents'); evt.initEvent(event, true, true); // event type,bubbling,cancelable return !element.dispatchEvent(evt); } // A function that runs a for each loop on a List, running the callback function for each one function listForEach(list, callback) { for (var i = 0; i < list.length; i++) { callback.call(null, list[i]); } } }()); ================================================ FILE: portal/public/res/js/conversation.js ================================================ // The ConversationPanel module is designed to handle // all display and behaviors of the conversation column of the app. /* eslint no-unused-vars: "off" */ /* global Api: true, Common: true*/ var ConversationPanel = (function() { var settings = { selectors: { chatBox: '#scrollingChat', fromUser: '.from-user', fromWatson: '.from-watson', latest: '.latest' }, authorTypes: { user: 'user', watson: 'watson' } }; // Publicly accessible methods defined return { init: init, inputKeyDown: inputKeyDown }; // Initialize the module function init() { chatUpdateSetup(); Api.sendRequest( '', null ); setupInputBox(); } // Set up callbacks on payload setters in Api module // This causes the displayMessage function to be called when messages are sent / received function chatUpdateSetup() { var currentRequestPayloadSetter = Api.setRequestPayload; Api.setRequestPayload = function(newPayloadStr) { currentRequestPayloadSetter.call(Api, newPayloadStr); displayMessage(JSON.parse(newPayloadStr), settings.authorTypes.user); }; var currentResponsePayloadSetter = Api.setResponsePayload; Api.setResponsePayload = function(newPayloadStr) { currentResponsePayloadSetter.call(Api, newPayloadStr); displayMessage(JSON.parse(newPayloadStr), settings.authorTypes.watson); }; } // Set up the input box to underline text as it is typed // This is done by creating a hidden dummy version of the input box that // is used to determine what the width of the input text should be. // This value is then used to set the new width of the visible input box. function setupInputBox() { var input = document.getElementById('textInput'); var dummy = document.getElementById('textInputDummy'); var minFontSize = 14; var maxFontSize = 16; var minPadding = 4; var maxPadding = 6; // If no dummy input box exists, create one if (dummy === null) { var dummyJson = { 'tagName': 'div', 'attributes': [{ 'name': 'id', 'value': 'textInputDummy' }] }; dummy = Common.buildDomElement(dummyJson); document.body.appendChild(dummy); } function adjustInput() { if (input.value === '') { // If the input box is empty, remove the underline input.classList.remove('underline'); input.setAttribute('style', 'width:' + '100%'); input.style.width = '100%'; } else { // otherwise, adjust the dummy text to match, and then set the width of // the visible input box to match it (thus extending the underline) input.classList.add('underline'); var txtNode = document.createTextNode(input.value); ['font-size', 'font-style', 'font-weight', 'font-family', 'line-height', 'text-transform', 'letter-spacing'].forEach(function(index) { dummy.style[index] = window.getComputedStyle(input, null).getPropertyValue(index); }); dummy.textContent = txtNode.textContent; var padding = 0; var htmlElem = document.getElementsByTagName('html')[0]; var currentFontSize = parseInt(window.getComputedStyle(htmlElem, null).getPropertyValue('font-size'), 10); if (currentFontSize) { padding = Math.floor((currentFontSize - minFontSize) / (maxFontSize - minFontSize) * (maxPadding - minPadding) + minPadding); } else { padding = maxPadding; } var widthValue = ( dummy.offsetWidth + padding) + 'px'; input.setAttribute('style', 'width:' + widthValue); input.style.width = widthValue; } } // Any time the input changes, or the window resizes, adjust the size of the input box input.addEventListener('input', adjustInput); window.addEventListener('resize', adjustInput); // Trigger the input event once to set up the input box and dummy element Common.fireEvent(input, 'input'); } // Display a user or Watson message that has just been sent/received function displayMessage(newPayload, typeValue) { var isUser = isUserMessage(typeValue); var textExists = (newPayload.input && newPayload.input.text) || (newPayload.output && newPayload.output.text); if (isUser !== null && textExists) { // Create new message DOM element var messageDivs = buildMessageDomElements(newPayload, isUser); var chatBoxElement = document.querySelector(settings.selectors.chatBox); var previousLatest = chatBoxElement.querySelectorAll((isUser ? settings.selectors.fromUser : settings.selectors.fromWatson) + settings.selectors.latest); // Previous "latest" message is no longer the most recent if (previousLatest) { Common.listForEach(previousLatest, function(element) { element.classList.remove('latest'); }); } messageDivs.forEach(function(currentDiv) { chatBoxElement.appendChild(currentDiv); // Class to start fade in animation currentDiv.classList.add('load'); }); // Move chat to the most recent messages when new messages are added scrollToChatBottom(); } } // Checks if the given typeValue matches with the user "name", the Watson "name", or neither // Returns true if user, false if Watson, and null if neither // Used to keep track of whether a message was from the user or Watson function isUserMessage(typeValue) { if (typeValue === settings.authorTypes.user) { return true; } else if (typeValue === settings.authorTypes.watson) { return false; } return null; } // Constructs new DOM element from a message payload function buildMessageDomElements(newPayload, isUser) { var textArray = isUser ? newPayload.input.text : newPayload.output.text; if (Object.prototype.toString.call( textArray ) !== '[object Array]') { textArray = [textArray]; } var messageArray = []; textArray.forEach(function(currentText) { if (currentText) { var messageJson = { //
'tagName': 'div', 'classNames': ['segments'], 'children': [{ //
'tagName': 'div', 'classNames': [(isUser ? 'from-user' : 'from-watson'), 'latest', ((messageArray.length === 0) ? 'top' : 'sub')], 'children': [{ //
'tagName': 'div', 'classNames': ['message-inner'], 'children': [{ //

{messageText}

'tagName': 'p', 'text': currentText }] }] }] }; messageArray.push(Common.buildDomElement(messageJson)); } }); return messageArray; } // Scroll to the bottom of the chat window (to the most recent messages) // Note: this method will bring the most recent user message into view, // even if the most recent message is from Watson. // This is done so that the "context" of the conversation is maintained in the view, // even if the Watson message is long. function scrollToChatBottom() { var scrollingChat = document.querySelector('#scrollingChat'); // Scroll to the latest message sent by the user var scrollEl = scrollingChat.querySelector(settings.selectors.fromUser + settings.selectors.latest); if (scrollEl) { scrollingChat.scrollTop = scrollEl.offsetTop; } } // Handles the submission of input function inputKeyDown(event, inputBox) { // Submit on enter key, dis-allowing blank messages if (event.keyCode === 13 && inputBox.value) { // Retrieve the context from the previous server response var context; var latestResponse = Api.getResponsePayload(); if (latestResponse) { context = latestResponse.context; } // Send the user message Api.sendRequest(inputBox.value, context); // Clear input box for further messages inputBox.value = ''; Common.fireEvent(inputBox, 'input'); } } }()); ================================================ FILE: portal/public/res/js/global.js ================================================ /* global ConversationPanel: true, PayloadPanel: true*/ /* eslint no-unused-vars: "off" */ // Other JS files required to be loaded first: apis.js, conversation.js, payload.js (function() { // Initialize all modules ConversationPanel.init(); PayloadPanel.init(); })(); ================================================ FILE: portal/public/res/js/navbar.js ================================================ $(document).ready(function() { //Sticky navigation if (document.getElementById('js-section-features')) { var waypoint = new Waypoint({ element: document.getElementById('js-section-features'), handler: function(direction) { if (direction == "down") $('nav').addClass('sticky-nav') else $('nav').removeClass('sticky-nav') } }) } //Mobile navigation $('.mobile-nav-icon').click(function() { var icon = $('.js-mobile-nav-icon') $('.main-nav').slideToggle(200) if (icon.hasClass('ion-navicon-round')) { icon.removeClass('ion-navicon-round'); icon.addClass('ion-close-round') } else if (icon.hasClass('ion-close-round')) { icon.removeClass('ion-close-round'); icon.addClass('ion-navicon-round') } }) }) ================================================ FILE: portal/public/res/js/payload.js ================================================ // The PayloadPanel module is designed to handle // all display and behaviors of the conversation column of the app. /* eslint no-unused-vars: "off" */ /* global Api: true, Common: true, PayloadPanel: true*/ var PayloadPanel = (function() { var settings = { selectors: { payloadColumn: '#payload-column', payloadInitial: '#payload-initial-message', payloadRequest: '#payload-request', payloadResponse: '#payload-response' }, payloadTypes: { request: 'request', response: 'response' } }; // Publicly accessible methods defined return { init: init, togglePanel: togglePanel }; // Initialize the module function init() { payloadUpdateSetup(); } // Toggle panel between being: // reduced width (default for large resolution apps) // hidden (default for small/mobile resolution apps) // full width (regardless of screen size) function togglePanel(event, element) { var payloadColumn = document.querySelector(settings.selectors.payloadColumn); if (element.classList.contains('full')) { element.classList.remove('full'); payloadColumn.classList.remove('full'); } else { element.classList.add('full'); payloadColumn.classList.add('full'); } } // Set up callbacks on payload setters in Api module // This causes the displayPayload function to be called when messages are sent / received function payloadUpdateSetup() { var currentRequestPayloadSetter = Api.setRequestPayload; Api.setRequestPayload = function(newPayloadStr) { currentRequestPayloadSetter.call(Api, newPayloadStr); displayPayload(settings.payloadTypes.request); }; var currentResponsePayloadSetter = Api.setResponsePayload; Api.setResponsePayload = function(newPayload) { currentResponsePayloadSetter.call(Api, newPayload); displayPayload(settings.payloadTypes.response); }; } // Display a request or response payload that has just been sent/received function displayPayload(typeValue) { var isRequest = checkRequestType(typeValue); if (isRequest !== null) { // Create new payload DOM element var payloadDiv = buildPayloadDomElement(isRequest); var payloadElement = document.querySelector(isRequest ? settings.selectors.payloadRequest : settings.selectors.payloadResponse); // Clear out payload holder element while (payloadElement.lastChild) { payloadElement.removeChild(payloadElement.lastChild); } // Add new payload element payloadElement.appendChild(payloadDiv); // Set the horizontal rule to show (if request and response payloads both exist) // or to hide (otherwise) var payloadInitial = document.querySelector(settings.selectors.payloadInitial); if (Api.getRequestPayload() || Api.getResponsePayload()) { payloadInitial.classList.add('hide'); } } } // Checks if the given typeValue matches with the request "name", the response "name", or neither // Returns true if request, false if response, and null if neither // Used to keep track of what type of payload we're currently working with function checkRequestType(typeValue) { if (typeValue === settings.payloadTypes.request) { return true; } else if (typeValue === settings.payloadTypes.response) { return false; } return null; } // Constructs new DOM element to use in displaying the payload function buildPayloadDomElement(isRequest) { var payloadPrettyString = jsonPrettyPrint(isRequest ? Api.getRequestPayload() : Api.getResponsePayload()); var payloadJson = { 'tagName': 'div', 'children': [{ //
'tagName': 'div', 'text': isRequest ? 'User input' : 'Watson understands', 'classNames': ['header-text'] }, { //
'tagName': 'div', 'classNames': ['code-line', 'responsive-columns-wrapper'], 'children': [{ //
'tagName': 'pre', 'text': createLineNumberString((payloadPrettyString.match(/\n/g) || []).length + 1), 'classNames': ['line-numbers'] }, { //
'tagName': 'pre', 'classNames': ['payload-text', 'responsive-column'], 'html': payloadPrettyString }] }] }; return Common.buildDomElement(payloadJson); } // Format (payload) JSON to make it more readable function jsonPrettyPrint(json) { if (json === null) { return ''; } var convert = JSON.stringify(json, null, 2); convert = convert.replace(/&/g, '&').replace(//g, '>'); convert = convert .replace( /("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function(match) { var cls = 'number'; if (/^"/.test(match)) { if (/:$/.test(match)) { cls = 'key'; } else { cls = 'string'; } } else if (/true|false/.test(match)) { cls = 'boolean'; } else if (/null/.test(match)) { cls = 'null'; } return '' + match + ''; }); return convert; } // Used to generate a string of consecutive numbers separated by new lines // - used as line numbers for displayed JSON function createLineNumberString(numberOfLines) { var lineString = ''; var prefix = ''; for (var i = 1; i <= numberOfLines; i++) { lineString += prefix; lineString += i; prefix = '\n'; } return lineString; } }()); ================================================ FILE: portal/public/res/js/spending.js ================================================ $(document).ready(function() { var http = new XMLHttpRequest(); var getUrl = window.location; var baseUrl = getUrl.protocol + "//" + getUrl.host; var transactionsEndpoint = baseUrl + '/endpoints/transactions/get'; console.log('Transactions >>>> ', transactionsEndpoint) var uuid, total = 0; var selectors = { groceries: { count: '#groceries_transaction_count', total: '#groceries_transaction_total', row: '#groceries_row', link: '#groceries_link' }, eating_out: { count: '#eating_out_transaction_count', total: '#eating_out_transaction_total', row: '#eating_out_row', link: '#eating_out_link' }, transport: { count: '#transport_transaction_count', total: '#transport_transaction_total', row: '#transport_row', link: '#transport_link' }, bills: { count: '#bills_transaction_count', total: '#bills_transaction_total', row: '#bills_row', link: '#bills_link' }, expenses : { count: '#expenses_transaction_count', total: '#expenses_transaction_total', row: '#expenses_row', link: '#expenses_link' }, cash : { count: '#cash_transaction_count', total: '#cash_transaction_total', row: '#cash_row', link: '#cash_link' }, holidays : { count: '#holidays_transaction_count', total: '#holidays_transaction_total', row: '#holidays_row', link: '#holidays_link' }, } //Ensure authenticated and get uuid http.open('GET', '/endpoints/auth', true); http.setRequestHeader('Content-type', 'application/json'); http.onreadystatechange = function() { if (http.readyState === 4 && http.status === 500) { window.location.replace('/index.html') } else if (http.readyState === 4 && http.status === 200 && http.responseText) { var response = JSON.parse(http.responseText) console.log('response >>>> ', response) uuid = response.uuid http.open('POST', transactionsEndpoint, true); http.setRequestHeader('Content-type', 'application/json'); http.onreadystatechange = function() { if (http.readyState === 4 && http.status === 200 && http.responseText) { console.log(http.responseText) var transactions = JSON.parse(http.responseText).group(item => item.category); console.log(transactions) for (var selector in selectors) { var transaction = transactions.find(item => item.key==selector) if (transaction && transaction.total > 0) { $(selectors[transaction.key].count).html(transaction.data.length + ' transactions'); $(selectors[transaction.key].total).html('AED ' + Math.round(transaction.total*100)/100); total += transaction.total; } else { $(selectors[selector].row).addClass('greyed-out') $(selectors[selector].link).removeAttr('href') } } $('#js-total').html(total) } }; http.send(JSON.stringify({'uuid': uuid})); } }; http.send(); }) Object.defineProperty(Array.prototype, 'group', { enumerable: false, value: function (key) { let map = {}; this.map(e => ({k: key(e), d: e})).forEach(e => { map[e.k] = map[e.k] || []; if (e.d.amount) map[e.k].push(e.d); }); return Object.keys(map).map(k => ({key: k, data: map[k], total: map[k].reduce(((a, b) => a + b.amount), 0)})); } }); ================================================ FILE: portal/public/res/js/transactions.js ================================================ $(document).ready(function() { var http = new XMLHttpRequest(); var getUrl = window.location; var baseUrl = getUrl.protocol + "//" + getUrl.host; var transactionsEndpoint = baseUrl + '/endpoints/transactions/get'; console.log('Transactions >>>> ', transactionsEndpoint) var uuid; var innerHTML = ''; //Ensure authenticated and get uuid http.open('GET', '/endpoints/auth', true); http.setRequestHeader('Content-type', 'application/json'); http.onreadystatechange = function() { if (http.readyState === 4 && http.status === 500) { window.location.replace('/index.html') } else if (http.readyState === 4 && http.status === 200 && http.responseText) { var response = JSON.parse(http.responseText) console.log('response >>>> ', response) uuid = response.uuid http.open('POST', transactionsEndpoint, true); http.setRequestHeader('Content-type', 'application/json'); http.onreadystatechange = function() { if (http.readyState === 4 && http.status === 200 && http.responseText) { var transactions = JSON.parse(http.responseText).group(item => item.category); console.log(transactions) transactions.forEach(function(category){ if (category.total > 0) { innerHTML += `
` + `

`+category.key.toTitleCase()+`

` + `
` category.data.forEach(function(transaction){ innerHTML += `
` + `
` + `

`+transaction.description+`

` + `

`+transaction.date+`

` + `
` + `
` + `

AED `+transaction.amount+`

` + `
` + `
` }) } }) $('#js-transaction-list').html(innerHTML) console.log(innerHTML) } }; http.send(JSON.stringify({'uuid': uuid})); } }; http.send(); }) String.prototype.toTitleCase = function () { return this.replace('_', ' ').replace(/\w\S*/g, function(txt){return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase()}); }; Object.defineProperty(Array.prototype, 'group', { enumerable: false, value: function (key) { let map = {}; this.map(e => ({k: key(e), d: e})).forEach(e => { map[e.k] = map[e.k] || []; if (e.d.amount) map[e.k].push(e.d); }); return Object.keys(map).map(k => ({key: k, data: map[k], total: map[k].reduce(((a, b) => a + b.amount), 0)})); } }); ================================================ FILE: portal/public/signup.html ================================================ Innovate: Digital Bank
================================================ FILE: portal/public/transactions.html ================================================ Innovate: Digital Bank

Transactions

================================================ FILE: portal/public/vendors/css/animate.css ================================================ @charset "UTF-8"; /*! Animate.css - http://daneden.me/animate Licensed under the MIT license - http://opensource.org/licenses/MIT Copyright (c) 2015 Daniel Eden */ .animated { -webkit-animation-duration: 1s; animation-duration: 1s; -webkit-animation-fill-mode: both; animation-fill-mode: both; } .animated.infinite { -webkit-animation-iteration-count: infinite; animation-iteration-count: infinite; } .animated.hinge { -webkit-animation-duration: 2s; animation-duration: 2s; } .animated.bounceIn, .animated.bounceOut { -webkit-animation-duration: .75s; animation-duration: .75s; } .animated.flipOutX, .animated.flipOutY { -webkit-animation-duration: .75s; animation-duration: .75s; } @-webkit-keyframes bounce { 0%, 20%, 53%, 80%, 100% { -webkit-transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); -webkit-transform: translate3d(0,0,0); transform: translate3d(0,0,0); } 40%, 43% { -webkit-transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060); transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060); -webkit-transform: translate3d(0, -30px, 0); transform: translate3d(0, -30px, 0); } 70% { -webkit-transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060); transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060); -webkit-transform: translate3d(0, -15px, 0); transform: translate3d(0, -15px, 0); } 90% { -webkit-transform: translate3d(0,-4px,0); transform: translate3d(0,-4px,0); } } @keyframes bounce { 0%, 20%, 53%, 80%, 100% { -webkit-transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); -webkit-transform: translate3d(0,0,0); transform: translate3d(0,0,0); } 40%, 43% { -webkit-transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060); transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060); -webkit-transform: translate3d(0, -30px, 0); transform: translate3d(0, -30px, 0); } 70% { -webkit-transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060); transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060); -webkit-transform: translate3d(0, -15px, 0); transform: translate3d(0, -15px, 0); } 90% { -webkit-transform: translate3d(0,-4px,0); transform: translate3d(0,-4px,0); } } .bounce { -webkit-animation-name: bounce; animation-name: bounce; -webkit-transform-origin: center bottom; transform-origin: center bottom; } @-webkit-keyframes flash { 0%, 50%, 100% { opacity: 1; } 25%, 75% { opacity: 0; } } @keyframes flash { 0%, 50%, 100% { opacity: 1; } 25%, 75% { opacity: 0; } } .flash { -webkit-animation-name: flash; animation-name: flash; } /* originally authored by Nick Pettit - https://github.com/nickpettit/glide */ @-webkit-keyframes pulse { 0% { -webkit-transform: scale3d(1, 1, 1); transform: scale3d(1, 1, 1); } 50% { -webkit-transform: scale3d(1.05, 1.05, 1.05); transform: scale3d(1.05, 1.05, 1.05); } 100% { -webkit-transform: scale3d(1, 1, 1); transform: scale3d(1, 1, 1); } } @keyframes pulse { 0% { -webkit-transform: scale3d(1, 1, 1); transform: scale3d(1, 1, 1); } 50% { -webkit-transform: scale3d(1.05, 1.05, 1.05); transform: scale3d(1.05, 1.05, 1.05); } 100% { -webkit-transform: scale3d(1, 1, 1); transform: scale3d(1, 1, 1); } } .pulse { -webkit-animation-name: pulse; animation-name: pulse; } @-webkit-keyframes rubberBand { 0% { -webkit-transform: scale3d(1, 1, 1); transform: scale3d(1, 1, 1); } 30% { -webkit-transform: scale3d(1.25, 0.75, 1); transform: scale3d(1.25, 0.75, 1); } 40% { -webkit-transform: scale3d(0.75, 1.25, 1); transform: scale3d(0.75, 1.25, 1); } 50% { -webkit-transform: scale3d(1.15, 0.85, 1); transform: scale3d(1.15, 0.85, 1); } 65% { -webkit-transform: scale3d(.95, 1.05, 1); transform: scale3d(.95, 1.05, 1); } 75% { -webkit-transform: scale3d(1.05, .95, 1); transform: scale3d(1.05, .95, 1); } 100% { -webkit-transform: scale3d(1, 1, 1); transform: scale3d(1, 1, 1); } } @keyframes rubberBand { 0% { -webkit-transform: scale3d(1, 1, 1); transform: scale3d(1, 1, 1); } 30% { -webkit-transform: scale3d(1.25, 0.75, 1); transform: scale3d(1.25, 0.75, 1); } 40% { -webkit-transform: scale3d(0.75, 1.25, 1); transform: scale3d(0.75, 1.25, 1); } 50% { -webkit-transform: scale3d(1.15, 0.85, 1); transform: scale3d(1.15, 0.85, 1); } 65% { -webkit-transform: scale3d(.95, 1.05, 1); transform: scale3d(.95, 1.05, 1); } 75% { -webkit-transform: scale3d(1.05, .95, 1); transform: scale3d(1.05, .95, 1); } 100% { -webkit-transform: scale3d(1, 1, 1); transform: scale3d(1, 1, 1); } } .rubberBand { -webkit-animation-name: rubberBand; animation-name: rubberBand; } @-webkit-keyframes shake { 0%, 100% { -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); } 10%, 30%, 50%, 70%, 90% { -webkit-transform: translate3d(-10px, 0, 0); transform: translate3d(-10px, 0, 0); } 20%, 40%, 60%, 80% { -webkit-transform: translate3d(10px, 0, 0); transform: translate3d(10px, 0, 0); } } @keyframes shake { 0%, 100% { -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); } 10%, 30%, 50%, 70%, 90% { -webkit-transform: translate3d(-10px, 0, 0); transform: translate3d(-10px, 0, 0); } 20%, 40%, 60%, 80% { -webkit-transform: translate3d(10px, 0, 0); transform: translate3d(10px, 0, 0); } } .shake { -webkit-animation-name: shake; animation-name: shake; } @-webkit-keyframes swing { 20% { -webkit-transform: rotate3d(0, 0, 1, 15deg); transform: rotate3d(0, 0, 1, 15deg); } 40% { -webkit-transform: rotate3d(0, 0, 1, -10deg); transform: rotate3d(0, 0, 1, -10deg); } 60% { -webkit-transform: rotate3d(0, 0, 1, 5deg); transform: rotate3d(0, 0, 1, 5deg); } 80% { -webkit-transform: rotate3d(0, 0, 1, -5deg); transform: rotate3d(0, 0, 1, -5deg); } 100% { -webkit-transform: rotate3d(0, 0, 1, 0deg); transform: rotate3d(0, 0, 1, 0deg); } } @keyframes swing { 20% { -webkit-transform: rotate3d(0, 0, 1, 15deg); transform: rotate3d(0, 0, 1, 15deg); } 40% { -webkit-transform: rotate3d(0, 0, 1, -10deg); transform: rotate3d(0, 0, 1, -10deg); } 60% { -webkit-transform: rotate3d(0, 0, 1, 5deg); transform: rotate3d(0, 0, 1, 5deg); } 80% { -webkit-transform: rotate3d(0, 0, 1, -5deg); transform: rotate3d(0, 0, 1, -5deg); } 100% { -webkit-transform: rotate3d(0, 0, 1, 0deg); transform: rotate3d(0, 0, 1, 0deg); } } .swing { -webkit-transform-origin: top center; transform-origin: top center; -webkit-animation-name: swing; animation-name: swing; } @-webkit-keyframes tada { 0% { -webkit-transform: scale3d(1, 1, 1); transform: scale3d(1, 1, 1); } 10%, 20% { -webkit-transform: scale3d(.9, .9, .9) rotate3d(0, 0, 1, -3deg); transform: scale3d(.9, .9, .9) rotate3d(0, 0, 1, -3deg); } 30%, 50%, 70%, 90% { -webkit-transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg); transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg); } 40%, 60%, 80% { -webkit-transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg); transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg); } 100% { -webkit-transform: scale3d(1, 1, 1); transform: scale3d(1, 1, 1); } } @keyframes tada { 0% { -webkit-transform: scale3d(1, 1, 1); transform: scale3d(1, 1, 1); } 10%, 20% { -webkit-transform: scale3d(.9, .9, .9) rotate3d(0, 0, 1, -3deg); transform: scale3d(.9, .9, .9) rotate3d(0, 0, 1, -3deg); } 30%, 50%, 70%, 90% { -webkit-transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg); transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg); } 40%, 60%, 80% { -webkit-transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg); transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg); } 100% { -webkit-transform: scale3d(1, 1, 1); transform: scale3d(1, 1, 1); } } .tada { -webkit-animation-name: tada; animation-name: tada; } /* originally authored by Nick Pettit - https://github.com/nickpettit/glide */ @-webkit-keyframes wobble { 0% { -webkit-transform: none; transform: none; } 15% { -webkit-transform: translate3d(-25%, 0, 0) rotate3d(0, 0, 1, -5deg); transform: translate3d(-25%, 0, 0) rotate3d(0, 0, 1, -5deg); } 30% { -webkit-transform: translate3d(20%, 0, 0) rotate3d(0, 0, 1, 3deg); transform: translate3d(20%, 0, 0) rotate3d(0, 0, 1, 3deg); } 45% { -webkit-transform: translate3d(-15%, 0, 0) rotate3d(0, 0, 1, -3deg); transform: translate3d(-15%, 0, 0) rotate3d(0, 0, 1, -3deg); } 60% { -webkit-transform: translate3d(10%, 0, 0) rotate3d(0, 0, 1, 2deg); transform: translate3d(10%, 0, 0) rotate3d(0, 0, 1, 2deg); } 75% { -webkit-transform: translate3d(-5%, 0, 0) rotate3d(0, 0, 1, -1deg); transform: translate3d(-5%, 0, 0) rotate3d(0, 0, 1, -1deg); } 100% { -webkit-transform: none; transform: none; } } @keyframes wobble { 0% { -webkit-transform: none; transform: none; } 15% { -webkit-transform: translate3d(-25%, 0, 0) rotate3d(0, 0, 1, -5deg); transform: translate3d(-25%, 0, 0) rotate3d(0, 0, 1, -5deg); } 30% { -webkit-transform: translate3d(20%, 0, 0) rotate3d(0, 0, 1, 3deg); transform: translate3d(20%, 0, 0) rotate3d(0, 0, 1, 3deg); } 45% { -webkit-transform: translate3d(-15%, 0, 0) rotate3d(0, 0, 1, -3deg); transform: translate3d(-15%, 0, 0) rotate3d(0, 0, 1, -3deg); } 60% { -webkit-transform: translate3d(10%, 0, 0) rotate3d(0, 0, 1, 2deg); transform: translate3d(10%, 0, 0) rotate3d(0, 0, 1, 2deg); } 75% { -webkit-transform: translate3d(-5%, 0, 0) rotate3d(0, 0, 1, -1deg); transform: translate3d(-5%, 0, 0) rotate3d(0, 0, 1, -1deg); } 100% { -webkit-transform: none; transform: none; } } .wobble { -webkit-animation-name: wobble; animation-name: wobble; } @-webkit-keyframes bounceIn { 0%, 20%, 40%, 60%, 80%, 100% { -webkit-transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); } 0% { opacity: 0; -webkit-transform: scale3d(.3, .3, .3); transform: scale3d(.3, .3, .3); } 20% { -webkit-transform: scale3d(1.1, 1.1, 1.1); transform: scale3d(1.1, 1.1, 1.1); } 40% { -webkit-transform: scale3d(.9, .9, .9); transform: scale3d(.9, .9, .9); } 60% { opacity: 1; -webkit-transform: scale3d(1.03, 1.03, 1.03); transform: scale3d(1.03, 1.03, 1.03); } 80% { -webkit-transform: scale3d(.97, .97, .97); transform: scale3d(.97, .97, .97); } 100% { opacity: 1; -webkit-transform: scale3d(1, 1, 1); transform: scale3d(1, 1, 1); } } @keyframes bounceIn { 0%, 20%, 40%, 60%, 80%, 100% { -webkit-transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); } 0% { opacity: 0; -webkit-transform: scale3d(.3, .3, .3); transform: scale3d(.3, .3, .3); } 20% { -webkit-transform: scale3d(1.1, 1.1, 1.1); transform: scale3d(1.1, 1.1, 1.1); } 40% { -webkit-transform: scale3d(.9, .9, .9); transform: scale3d(.9, .9, .9); } 60% { opacity: 1; -webkit-transform: scale3d(1.03, 1.03, 1.03); transform: scale3d(1.03, 1.03, 1.03); } 80% { -webkit-transform: scale3d(.97, .97, .97); transform: scale3d(.97, .97, .97); } 100% { opacity: 1; -webkit-transform: scale3d(1, 1, 1); transform: scale3d(1, 1, 1); } } .bounceIn { -webkit-animation-name: bounceIn; animation-name: bounceIn; } @-webkit-keyframes bounceInDown { 0%, 60%, 75%, 90%, 100% { -webkit-transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); } 0% { opacity: 0; -webkit-transform: translate3d(0, -3000px, 0); transform: translate3d(0, -3000px, 0); } 60% { opacity: 1; -webkit-transform: translate3d(0, 25px, 0); transform: translate3d(0, 25px, 0); } 75% { -webkit-transform: translate3d(0, -10px, 0); transform: translate3d(0, -10px, 0); } 90% { -webkit-transform: translate3d(0, 5px, 0); transform: translate3d(0, 5px, 0); } 100% { -webkit-transform: none; transform: none; } } @keyframes bounceInDown { 0%, 60%, 75%, 90%, 100% { -webkit-transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); } 0% { opacity: 0; -webkit-transform: translate3d(0, -3000px, 0); transform: translate3d(0, -3000px, 0); } 60% { opacity: 1; -webkit-transform: translate3d(0, 25px, 0); transform: translate3d(0, 25px, 0); } 75% { -webkit-transform: translate3d(0, -10px, 0); transform: translate3d(0, -10px, 0); } 90% { -webkit-transform: translate3d(0, 5px, 0); transform: translate3d(0, 5px, 0); } 100% { -webkit-transform: none; transform: none; } } .bounceInDown { -webkit-animation-name: bounceInDown; animation-name: bounceInDown; } @-webkit-keyframes bounceInLeft { 0%, 60%, 75%, 90%, 100% { -webkit-transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); } 0% { opacity: 0; -webkit-transform: translate3d(-3000px, 0, 0); transform: translate3d(-3000px, 0, 0); } 60% { opacity: 1; -webkit-transform: translate3d(25px, 0, 0); transform: translate3d(25px, 0, 0); } 75% { -webkit-transform: translate3d(-10px, 0, 0); transform: translate3d(-10px, 0, 0); } 90% { -webkit-transform: translate3d(5px, 0, 0); transform: translate3d(5px, 0, 0); } 100% { -webkit-transform: none; transform: none; } } @keyframes bounceInLeft { 0%, 60%, 75%, 90%, 100% { -webkit-transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); } 0% { opacity: 0; -webkit-transform: translate3d(-3000px, 0, 0); transform: translate3d(-3000px, 0, 0); } 60% { opacity: 1; -webkit-transform: translate3d(25px, 0, 0); transform: translate3d(25px, 0, 0); } 75% { -webkit-transform: translate3d(-10px, 0, 0); transform: translate3d(-10px, 0, 0); } 90% { -webkit-transform: translate3d(5px, 0, 0); transform: translate3d(5px, 0, 0); } 100% { -webkit-transform: none; transform: none; } } .bounceInLeft { -webkit-animation-name: bounceInLeft; animation-name: bounceInLeft; } @-webkit-keyframes bounceInRight { 0%, 60%, 75%, 90%, 100% { -webkit-transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); } 0% { opacity: 0; -webkit-transform: translate3d(3000px, 0, 0); transform: translate3d(3000px, 0, 0); } 60% { opacity: 1; -webkit-transform: translate3d(-25px, 0, 0); transform: translate3d(-25px, 0, 0); } 75% { -webkit-transform: translate3d(10px, 0, 0); transform: translate3d(10px, 0, 0); } 90% { -webkit-transform: translate3d(-5px, 0, 0); transform: translate3d(-5px, 0, 0); } 100% { -webkit-transform: none; transform: none; } } @keyframes bounceInRight { 0%, 60%, 75%, 90%, 100% { -webkit-transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); } 0% { opacity: 0; -webkit-transform: translate3d(3000px, 0, 0); transform: translate3d(3000px, 0, 0); } 60% { opacity: 1; -webkit-transform: translate3d(-25px, 0, 0); transform: translate3d(-25px, 0, 0); } 75% { -webkit-transform: translate3d(10px, 0, 0); transform: translate3d(10px, 0, 0); } 90% { -webkit-transform: translate3d(-5px, 0, 0); transform: translate3d(-5px, 0, 0); } 100% { -webkit-transform: none; transform: none; } } .bounceInRight { -webkit-animation-name: bounceInRight; animation-name: bounceInRight; } @-webkit-keyframes bounceInUp { 0%, 60%, 75%, 90%, 100% { -webkit-transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); } 0% { opacity: 0; -webkit-transform: translate3d(0, 3000px, 0); transform: translate3d(0, 3000px, 0); } 60% { opacity: 1; -webkit-transform: translate3d(0, -20px, 0); transform: translate3d(0, -20px, 0); } 75% { -webkit-transform: translate3d(0, 10px, 0); transform: translate3d(0, 10px, 0); } 90% { -webkit-transform: translate3d(0, -5px, 0); transform: translate3d(0, -5px, 0); } 100% { -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); } } @keyframes bounceInUp { 0%, 60%, 75%, 90%, 100% { -webkit-transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); } 0% { opacity: 0; -webkit-transform: translate3d(0, 3000px, 0); transform: translate3d(0, 3000px, 0); } 60% { opacity: 1; -webkit-transform: translate3d(0, -20px, 0); transform: translate3d(0, -20px, 0); } 75% { -webkit-transform: translate3d(0, 10px, 0); transform: translate3d(0, 10px, 0); } 90% { -webkit-transform: translate3d(0, -5px, 0); transform: translate3d(0, -5px, 0); } 100% { -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); } } .bounceInUp { -webkit-animation-name: bounceInUp; animation-name: bounceInUp; } @-webkit-keyframes bounceOut { 20% { -webkit-transform: scale3d(.9, .9, .9); transform: scale3d(.9, .9, .9); } 50%, 55% { opacity: 1; -webkit-transform: scale3d(1.1, 1.1, 1.1); transform: scale3d(1.1, 1.1, 1.1); } 100% { opacity: 0; -webkit-transform: scale3d(.3, .3, .3); transform: scale3d(.3, .3, .3); } } @keyframes bounceOut { 20% { -webkit-transform: scale3d(.9, .9, .9); transform: scale3d(.9, .9, .9); } 50%, 55% { opacity: 1; -webkit-transform: scale3d(1.1, 1.1, 1.1); transform: scale3d(1.1, 1.1, 1.1); } 100% { opacity: 0; -webkit-transform: scale3d(.3, .3, .3); transform: scale3d(.3, .3, .3); } } .bounceOut { -webkit-animation-name: bounceOut; animation-name: bounceOut; } @-webkit-keyframes bounceOutDown { 20% { -webkit-transform: translate3d(0, 10px, 0); transform: translate3d(0, 10px, 0); } 40%, 45% { opacity: 1; -webkit-transform: translate3d(0, -20px, 0); transform: translate3d(0, -20px, 0); } 100% { opacity: 0; -webkit-transform: translate3d(0, 2000px, 0); transform: translate3d(0, 2000px, 0); } } @keyframes bounceOutDown { 20% { -webkit-transform: translate3d(0, 10px, 0); transform: translate3d(0, 10px, 0); } 40%, 45% { opacity: 1; -webkit-transform: translate3d(0, -20px, 0); transform: translate3d(0, -20px, 0); } 100% { opacity: 0; -webkit-transform: translate3d(0, 2000px, 0); transform: translate3d(0, 2000px, 0); } } .bounceOutDown { -webkit-animation-name: bounceOutDown; animation-name: bounceOutDown; } @-webkit-keyframes bounceOutLeft { 20% { opacity: 1; -webkit-transform: translate3d(20px, 0, 0); transform: translate3d(20px, 0, 0); } 100% { opacity: 0; -webkit-transform: translate3d(-2000px, 0, 0); transform: translate3d(-2000px, 0, 0); } } @keyframes bounceOutLeft { 20% { opacity: 1; -webkit-transform: translate3d(20px, 0, 0); transform: translate3d(20px, 0, 0); } 100% { opacity: 0; -webkit-transform: translate3d(-2000px, 0, 0); transform: translate3d(-2000px, 0, 0); } } .bounceOutLeft { -webkit-animation-name: bounceOutLeft; animation-name: bounceOutLeft; } @-webkit-keyframes bounceOutRight { 20% { opacity: 1; -webkit-transform: translate3d(-20px, 0, 0); transform: translate3d(-20px, 0, 0); } 100% { opacity: 0; -webkit-transform: translate3d(2000px, 0, 0); transform: translate3d(2000px, 0, 0); } } @keyframes bounceOutRight { 20% { opacity: 1; -webkit-transform: translate3d(-20px, 0, 0); transform: translate3d(-20px, 0, 0); } 100% { opacity: 0; -webkit-transform: translate3d(2000px, 0, 0); transform: translate3d(2000px, 0, 0); } } .bounceOutRight { -webkit-animation-name: bounceOutRight; animation-name: bounceOutRight; } @-webkit-keyframes bounceOutUp { 20% { -webkit-transform: translate3d(0, -10px, 0); transform: translate3d(0, -10px, 0); } 40%, 45% { opacity: 1; -webkit-transform: translate3d(0, 20px, 0); transform: translate3d(0, 20px, 0); } 100% { opacity: 0; -webkit-transform: translate3d(0, -2000px, 0); transform: translate3d(0, -2000px, 0); } } @keyframes bounceOutUp { 20% { -webkit-transform: translate3d(0, -10px, 0); transform: translate3d(0, -10px, 0); } 40%, 45% { opacity: 1; -webkit-transform: translate3d(0, 20px, 0); transform: translate3d(0, 20px, 0); } 100% { opacity: 0; -webkit-transform: translate3d(0, -2000px, 0); transform: translate3d(0, -2000px, 0); } } .bounceOutUp { -webkit-animation-name: bounceOutUp; animation-name: bounceOutUp; } @-webkit-keyframes fadeIn { 0% { opacity: 0; } 100% { opacity: 1; } } @keyframes fadeIn { 0% { opacity: 0; } 100% { opacity: 1; } } .fadeIn { -webkit-animation-name: fadeIn; animation-name: fadeIn; } @-webkit-keyframes fadeInDown { 0% { opacity: 0; -webkit-transform: translate3d(0, -100%, 0); transform: translate3d(0, -100%, 0); } 100% { opacity: 1; -webkit-transform: none; transform: none; } } @keyframes fadeInDown { 0% { opacity: 0; -webkit-transform: translate3d(0, -100%, 0); transform: translate3d(0, -100%, 0); } 100% { opacity: 1; -webkit-transform: none; transform: none; } } .fadeInDown { -webkit-animation-name: fadeInDown; animation-name: fadeInDown; } @-webkit-keyframes fadeInDownBig { 0% { opacity: 0; -webkit-transform: translate3d(0, -2000px, 0); transform: translate3d(0, -2000px, 0); } 100% { opacity: 1; -webkit-transform: none; transform: none; } } @keyframes fadeInDownBig { 0% { opacity: 0; -webkit-transform: translate3d(0, -2000px, 0); transform: translate3d(0, -2000px, 0); } 100% { opacity: 1; -webkit-transform: none; transform: none; } } .fadeInDownBig { -webkit-animation-name: fadeInDownBig; animation-name: fadeInDownBig; } @-webkit-keyframes fadeInLeft { 0% { opacity: 0; -webkit-transform: translate3d(-100%, 0, 0); transform: translate3d(-100%, 0, 0); } 100% { opacity: 1; -webkit-transform: none; transform: none; } } @keyframes fadeInLeft { 0% { opacity: 0; -webkit-transform: translate3d(-100%, 0, 0); transform: translate3d(-100%, 0, 0); } 100% { opacity: 1; -webkit-transform: none; transform: none; } } .fadeInLeft { -webkit-animation-name: fadeInLeft; animation-name: fadeInLeft; } @-webkit-keyframes fadeInLeftBig { 0% { opacity: 0; -webkit-transform: translate3d(-2000px, 0, 0); transform: translate3d(-2000px, 0, 0); } 100% { opacity: 1; -webkit-transform: none; transform: none; } } @keyframes fadeInLeftBig { 0% { opacity: 0; -webkit-transform: translate3d(-2000px, 0, 0); transform: translate3d(-2000px, 0, 0); } 100% { opacity: 1; -webkit-transform: none; transform: none; } } .fadeInLeftBig { -webkit-animation-name: fadeInLeftBig; animation-name: fadeInLeftBig; } @-webkit-keyframes fadeInRight { 0% { opacity: 0; -webkit-transform: translate3d(100%, 0, 0); transform: translate3d(100%, 0, 0); } 100% { opacity: 1; -webkit-transform: none; transform: none; } } @keyframes fadeInRight { 0% { opacity: 0; -webkit-transform: translate3d(100%, 0, 0); transform: translate3d(100%, 0, 0); } 100% { opacity: 1; -webkit-transform: none; transform: none; } } .fadeInRight { -webkit-animation-name: fadeInRight; animation-name: fadeInRight; } @-webkit-keyframes fadeInRightBig { 0% { opacity: 0; -webkit-transform: translate3d(2000px, 0, 0); transform: translate3d(2000px, 0, 0); } 100% { opacity: 1; -webkit-transform: none; transform: none; } } @keyframes fadeInRightBig { 0% { opacity: 0; -webkit-transform: translate3d(2000px, 0, 0); transform: translate3d(2000px, 0, 0); } 100% { opacity: 1; -webkit-transform: none; transform: none; } } .fadeInRightBig { -webkit-animation-name: fadeInRightBig; animation-name: fadeInRightBig; } @-webkit-keyframes fadeInUp { 0% { opacity: 0; -webkit-transform: translate3d(0, 100%, 0); transform: translate3d(0, 100%, 0); } 100% { opacity: 1; -webkit-transform: none; transform: none; } } @keyframes fadeInUp { 0% { opacity: 0; -webkit-transform: translate3d(0, 100%, 0); transform: translate3d(0, 100%, 0); } 100% { opacity: 1; -webkit-transform: none; transform: none; } } .fadeInUp { -webkit-animation-name: fadeInUp; animation-name: fadeInUp; } @-webkit-keyframes fadeInUpBig { 0% { opacity: 0; -webkit-transform: translate3d(0, 2000px, 0); transform: translate3d(0, 2000px, 0); } 100% { opacity: 1; -webkit-transform: none; transform: none; } } @keyframes fadeInUpBig { 0% { opacity: 0; -webkit-transform: translate3d(0, 2000px, 0); transform: translate3d(0, 2000px, 0); } 100% { opacity: 1; -webkit-transform: none; transform: none; } } .fadeInUpBig { -webkit-animation-name: fadeInUpBig; animation-name: fadeInUpBig; } @-webkit-keyframes fadeOut { 0% { opacity: 1; } 100% { opacity: 0; } } @keyframes fadeOut { 0% { opacity: 1; } 100% { opacity: 0; } } .fadeOut { -webkit-animation-name: fadeOut; animation-name: fadeOut; } @-webkit-keyframes fadeOutDown { 0% { opacity: 1; } 100% { opacity: 0; -webkit-transform: translate3d(0, 100%, 0); transform: translate3d(0, 100%, 0); } } @keyframes fadeOutDown { 0% { opacity: 1; } 100% { opacity: 0; -webkit-transform: translate3d(0, 100%, 0); transform: translate3d(0, 100%, 0); } } .fadeOutDown { -webkit-animation-name: fadeOutDown; animation-name: fadeOutDown; } @-webkit-keyframes fadeOutDownBig { 0% { opacity: 1; } 100% { opacity: 0; -webkit-transform: translate3d(0, 2000px, 0); transform: translate3d(0, 2000px, 0); } } @keyframes fadeOutDownBig { 0% { opacity: 1; } 100% { opacity: 0; -webkit-transform: translate3d(0, 2000px, 0); transform: translate3d(0, 2000px, 0); } } .fadeOutDownBig { -webkit-animation-name: fadeOutDownBig; animation-name: fadeOutDownBig; } @-webkit-keyframes fadeOutLeft { 0% { opacity: 1; } 100% { opacity: 0; -webkit-transform: translate3d(-100%, 0, 0); transform: translate3d(-100%, 0, 0); } } @keyframes fadeOutLeft { 0% { opacity: 1; } 100% { opacity: 0; -webkit-transform: translate3d(-100%, 0, 0); transform: translate3d(-100%, 0, 0); } } .fadeOutLeft { -webkit-animation-name: fadeOutLeft; animation-name: fadeOutLeft; } @-webkit-keyframes fadeOutLeftBig { 0% { opacity: 1; } 100% { opacity: 0; -webkit-transform: translate3d(-2000px, 0, 0); transform: translate3d(-2000px, 0, 0); } } @keyframes fadeOutLeftBig { 0% { opacity: 1; } 100% { opacity: 0; -webkit-transform: translate3d(-2000px, 0, 0); transform: translate3d(-2000px, 0, 0); } } .fadeOutLeftBig { -webkit-animation-name: fadeOutLeftBig; animation-name: fadeOutLeftBig; } @-webkit-keyframes fadeOutRight { 0% { opacity: 1; } 100% { opacity: 0; -webkit-transform: translate3d(100%, 0, 0); transform: translate3d(100%, 0, 0); } } @keyframes fadeOutRight { 0% { opacity: 1; } 100% { opacity: 0; -webkit-transform: translate3d(100%, 0, 0); transform: translate3d(100%, 0, 0); } } .fadeOutRight { -webkit-animation-name: fadeOutRight; animation-name: fadeOutRight; } @-webkit-keyframes fadeOutRightBig { 0% { opacity: 1; } 100% { opacity: 0; -webkit-transform: translate3d(2000px, 0, 0); transform: translate3d(2000px, 0, 0); } } @keyframes fadeOutRightBig { 0% { opacity: 1; } 100% { opacity: 0; -webkit-transform: translate3d(2000px, 0, 0); transform: translate3d(2000px, 0, 0); } } .fadeOutRightBig { -webkit-animation-name: fadeOutRightBig; animation-name: fadeOutRightBig; } @-webkit-keyframes fadeOutUp { 0% { opacity: 1; } 100% { opacity: 0; -webkit-transform: translate3d(0, -100%, 0); transform: translate3d(0, -100%, 0); } } @keyframes fadeOutUp { 0% { opacity: 1; } 100% { opacity: 0; -webkit-transform: translate3d(0, -100%, 0); transform: translate3d(0, -100%, 0); } } .fadeOutUp { -webkit-animation-name: fadeOutUp; animation-name: fadeOutUp; } @-webkit-keyframes fadeOutUpBig { 0% { opacity: 1; } 100% { opacity: 0; -webkit-transform: translate3d(0, -2000px, 0); transform: translate3d(0, -2000px, 0); } } @keyframes fadeOutUpBig { 0% { opacity: 1; } 100% { opacity: 0; -webkit-transform: translate3d(0, -2000px, 0); transform: translate3d(0, -2000px, 0); } } .fadeOutUpBig { -webkit-animation-name: fadeOutUpBig; animation-name: fadeOutUpBig; } @-webkit-keyframes flip { 0% { -webkit-transform: perspective(400px) rotate3d(0, 1, 0, -360deg); transform: perspective(400px) rotate3d(0, 1, 0, -360deg); -webkit-animation-timing-function: ease-out; animation-timing-function: ease-out; } 40% { -webkit-transform: perspective(400px) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -190deg); transform: perspective(400px) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -190deg); -webkit-animation-timing-function: ease-out; animation-timing-function: ease-out; } 50% { -webkit-transform: perspective(400px) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -170deg); transform: perspective(400px) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -170deg); -webkit-animation-timing-function: ease-in; animation-timing-function: ease-in; } 80% { -webkit-transform: perspective(400px) scale3d(.95, .95, .95); transform: perspective(400px) scale3d(.95, .95, .95); -webkit-animation-timing-function: ease-in; animation-timing-function: ease-in; } 100% { -webkit-transform: perspective(400px); transform: perspective(400px); -webkit-animation-timing-function: ease-in; animation-timing-function: ease-in; } } @keyframes flip { 0% { -webkit-transform: perspective(400px) rotate3d(0, 1, 0, -360deg); transform: perspective(400px) rotate3d(0, 1, 0, -360deg); -webkit-animation-timing-function: ease-out; animation-timing-function: ease-out; } 40% { -webkit-transform: perspective(400px) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -190deg); transform: perspective(400px) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -190deg); -webkit-animation-timing-function: ease-out; animation-timing-function: ease-out; } 50% { -webkit-transform: perspective(400px) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -170deg); transform: perspective(400px) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -170deg); -webkit-animation-timing-function: ease-in; animation-timing-function: ease-in; } 80% { -webkit-transform: perspective(400px) scale3d(.95, .95, .95); transform: perspective(400px) scale3d(.95, .95, .95); -webkit-animation-timing-function: ease-in; animation-timing-function: ease-in; } 100% { -webkit-transform: perspective(400px); transform: perspective(400px); -webkit-animation-timing-function: ease-in; animation-timing-function: ease-in; } } .animated.flip { -webkit-backface-visibility: visible; backface-visibility: visible; -webkit-animation-name: flip; animation-name: flip; } @-webkit-keyframes flipInX { 0% { -webkit-transform: perspective(400px) rotate3d(1, 0, 0, 90deg); transform: perspective(400px) rotate3d(1, 0, 0, 90deg); -webkit-transition-timing-function: ease-in; transition-timing-function: ease-in; opacity: 0; } 40% { -webkit-transform: perspective(400px) rotate3d(1, 0, 0, -20deg); transform: perspective(400px) rotate3d(1, 0, 0, -20deg); -webkit-transition-timing-function: ease-in; transition-timing-function: ease-in; } 60% { -webkit-transform: perspective(400px) rotate3d(1, 0, 0, 10deg); transform: perspective(400px) rotate3d(1, 0, 0, 10deg); opacity: 1; } 80% { -webkit-transform: perspective(400px) rotate3d(1, 0, 0, -5deg); transform: perspective(400px) rotate3d(1, 0, 0, -5deg); } 100% { -webkit-transform: perspective(400px); transform: perspective(400px); } } @keyframes flipInX { 0% { -webkit-transform: perspective(400px) rotate3d(1, 0, 0, 90deg); transform: perspective(400px) rotate3d(1, 0, 0, 90deg); -webkit-transition-timing-function: ease-in; transition-timing-function: ease-in; opacity: 0; } 40% { -webkit-transform: perspective(400px) rotate3d(1, 0, 0, -20deg); transform: perspective(400px) rotate3d(1, 0, 0, -20deg); -webkit-transition-timing-function: ease-in; transition-timing-function: ease-in; } 60% { -webkit-transform: perspective(400px) rotate3d(1, 0, 0, 10deg); transform: perspective(400px) rotate3d(1, 0, 0, 10deg); opacity: 1; } 80% { -webkit-transform: perspective(400px) rotate3d(1, 0, 0, -5deg); transform: perspective(400px) rotate3d(1, 0, 0, -5deg); } 100% { -webkit-transform: perspective(400px); transform: perspective(400px); } } .flipInX { -webkit-backface-visibility: visible !important; backface-visibility: visible !important; -webkit-animation-name: flipInX; animation-name: flipInX; } @-webkit-keyframes flipInY { 0% { -webkit-transform: perspective(400px) rotate3d(0, 1, 0, 90deg); transform: perspective(400px) rotate3d(0, 1, 0, 90deg); -webkit-transition-timing-function: ease-in; transition-timing-function: ease-in; opacity: 0; } 40% { -webkit-transform: perspective(400px) rotate3d(0, 1, 0, -20deg); transform: perspective(400px) rotate3d(0, 1, 0, -20deg); -webkit-transition-timing-function: ease-in; transition-timing-function: ease-in; } 60% { -webkit-transform: perspective(400px) rotate3d(0, 1, 0, 10deg); transform: perspective(400px) rotate3d(0, 1, 0, 10deg); opacity: 1; } 80% { -webkit-transform: perspective(400px) rotate3d(0, 1, 0, -5deg); transform: perspective(400px) rotate3d(0, 1, 0, -5deg); } 100% { -webkit-transform: perspective(400px); transform: perspective(400px); } } @keyframes flipInY { 0% { -webkit-transform: perspective(400px) rotate3d(0, 1, 0, 90deg); transform: perspective(400px) rotate3d(0, 1, 0, 90deg); -webkit-transition-timing-function: ease-in; transition-timing-function: ease-in; opacity: 0; } 40% { -webkit-transform: perspective(400px) rotate3d(0, 1, 0, -20deg); transform: perspective(400px) rotate3d(0, 1, 0, -20deg); -webkit-transition-timing-function: ease-in; transition-timing-function: ease-in; } 60% { -webkit-transform: perspective(400px) rotate3d(0, 1, 0, 10deg); transform: perspective(400px) rotate3d(0, 1, 0, 10deg); opacity: 1; } 80% { -webkit-transform: perspective(400px) rotate3d(0, 1, 0, -5deg); transform: perspective(400px) rotate3d(0, 1, 0, -5deg); } 100% { -webkit-transform: perspective(400px); transform: perspective(400px); } } .flipInY { -webkit-backface-visibility: visible !important; backface-visibility: visible !important; -webkit-animation-name: flipInY; animation-name: flipInY; } @-webkit-keyframes flipOutX { 0% { -webkit-transform: perspective(400px); transform: perspective(400px); } 30% { -webkit-transform: perspective(400px) rotate3d(1, 0, 0, -20deg); transform: perspective(400px) rotate3d(1, 0, 0, -20deg); opacity: 1; } 100% { -webkit-transform: perspective(400px) rotate3d(1, 0, 0, 90deg); transform: perspective(400px) rotate3d(1, 0, 0, 90deg); opacity: 0; } } @keyframes flipOutX { 0% { -webkit-transform: perspective(400px); transform: perspective(400px); } 30% { -webkit-transform: perspective(400px) rotate3d(1, 0, 0, -20deg); transform: perspective(400px) rotate3d(1, 0, 0, -20deg); opacity: 1; } 100% { -webkit-transform: perspective(400px) rotate3d(1, 0, 0, 90deg); transform: perspective(400px) rotate3d(1, 0, 0, 90deg); opacity: 0; } } .flipOutX { -webkit-animation-name: flipOutX; animation-name: flipOutX; -webkit-backface-visibility: visible !important; backface-visibility: visible !important; } @-webkit-keyframes flipOutY { 0% { -webkit-transform: perspective(400px); transform: perspective(400px); } 30% { -webkit-transform: perspective(400px) rotate3d(0, 1, 0, -15deg); transform: perspective(400px) rotate3d(0, 1, 0, -15deg); opacity: 1; } 100% { -webkit-transform: perspective(400px) rotate3d(0, 1, 0, 90deg); transform: perspective(400px) rotate3d(0, 1, 0, 90deg); opacity: 0; } } @keyframes flipOutY { 0% { -webkit-transform: perspective(400px); transform: perspective(400px); } 30% { -webkit-transform: perspective(400px) rotate3d(0, 1, 0, -15deg); transform: perspective(400px) rotate3d(0, 1, 0, -15deg); opacity: 1; } 100% { -webkit-transform: perspective(400px) rotate3d(0, 1, 0, 90deg); transform: perspective(400px) rotate3d(0, 1, 0, 90deg); opacity: 0; } } .flipOutY { -webkit-backface-visibility: visible !important; backface-visibility: visible !important; -webkit-animation-name: flipOutY; animation-name: flipOutY; } @-webkit-keyframes lightSpeedIn { 0% { -webkit-transform: translate3d(100%, 0, 0) skewX(-30deg); transform: translate3d(100%, 0, 0) skewX(-30deg); opacity: 0; } 60% { -webkit-transform: skewX(20deg); transform: skewX(20deg); opacity: 1; } 80% { -webkit-transform: skewX(-5deg); transform: skewX(-5deg); opacity: 1; } 100% { -webkit-transform: none; transform: none; opacity: 1; } } @keyframes lightSpeedIn { 0% { -webkit-transform: translate3d(100%, 0, 0) skewX(-30deg); transform: translate3d(100%, 0, 0) skewX(-30deg); opacity: 0; } 60% { -webkit-transform: skewX(20deg); transform: skewX(20deg); opacity: 1; } 80% { -webkit-transform: skewX(-5deg); transform: skewX(-5deg); opacity: 1; } 100% { -webkit-transform: none; transform: none; opacity: 1; } } .lightSpeedIn { -webkit-animation-name: lightSpeedIn; animation-name: lightSpeedIn; -webkit-animation-timing-function: ease-out; animation-timing-function: ease-out; } @-webkit-keyframes lightSpeedOut { 0% { opacity: 1; } 100% { -webkit-transform: translate3d(100%, 0, 0) skewX(30deg); transform: translate3d(100%, 0, 0) skewX(30deg); opacity: 0; } } @keyframes lightSpeedOut { 0% { opacity: 1; } 100% { -webkit-transform: translate3d(100%, 0, 0) skewX(30deg); transform: translate3d(100%, 0, 0) skewX(30deg); opacity: 0; } } .lightSpeedOut { -webkit-animation-name: lightSpeedOut; animation-name: lightSpeedOut; -webkit-animation-timing-function: ease-in; animation-timing-function: ease-in; } @-webkit-keyframes rotateIn { 0% { -webkit-transform-origin: center; transform-origin: center; -webkit-transform: rotate3d(0, 0, 1, -200deg); transform: rotate3d(0, 0, 1, -200deg); opacity: 0; } 100% { -webkit-transform-origin: center; transform-origin: center; -webkit-transform: none; transform: none; opacity: 1; } } @keyframes rotateIn { 0% { -webkit-transform-origin: center; transform-origin: center; -webkit-transform: rotate3d(0, 0, 1, -200deg); transform: rotate3d(0, 0, 1, -200deg); opacity: 0; } 100% { -webkit-transform-origin: center; transform-origin: center; -webkit-transform: none; transform: none; opacity: 1; } } .rotateIn { -webkit-animation-name: rotateIn; animation-name: rotateIn; } @-webkit-keyframes rotateInDownLeft { 0% { -webkit-transform-origin: left bottom; transform-origin: left bottom; -webkit-transform: rotate3d(0, 0, 1, -45deg); transform: rotate3d(0, 0, 1, -45deg); opacity: 0; } 100% { -webkit-transform-origin: left bottom; transform-origin: left bottom; -webkit-transform: none; transform: none; opacity: 1; } } @keyframes rotateInDownLeft { 0% { -webkit-transform-origin: left bottom; transform-origin: left bottom; -webkit-transform: rotate3d(0, 0, 1, -45deg); transform: rotate3d(0, 0, 1, -45deg); opacity: 0; } 100% { -webkit-transform-origin: left bottom; transform-origin: left bottom; -webkit-transform: none; transform: none; opacity: 1; } } .rotateInDownLeft { -webkit-animation-name: rotateInDownLeft; animation-name: rotateInDownLeft; } @-webkit-keyframes rotateInDownRight { 0% { -webkit-transform-origin: right bottom; transform-origin: right bottom; -webkit-transform: rotate3d(0, 0, 1, 45deg); transform: rotate3d(0, 0, 1, 45deg); opacity: 0; } 100% { -webkit-transform-origin: right bottom; transform-origin: right bottom; -webkit-transform: none; transform: none; opacity: 1; } } @keyframes rotateInDownRight { 0% { -webkit-transform-origin: right bottom; transform-origin: right bottom; -webkit-transform: rotate3d(0, 0, 1, 45deg); transform: rotate3d(0, 0, 1, 45deg); opacity: 0; } 100% { -webkit-transform-origin: right bottom; transform-origin: right bottom; -webkit-transform: none; transform: none; opacity: 1; } } .rotateInDownRight { -webkit-animation-name: rotateInDownRight; animation-name: rotateInDownRight; } @-webkit-keyframes rotateInUpLeft { 0% { -webkit-transform-origin: left bottom; transform-origin: left bottom; -webkit-transform: rotate3d(0, 0, 1, 45deg); transform: rotate3d(0, 0, 1, 45deg); opacity: 0; } 100% { -webkit-transform-origin: left bottom; transform-origin: left bottom; -webkit-transform: none; transform: none; opacity: 1; } } @keyframes rotateInUpLeft { 0% { -webkit-transform-origin: left bottom; transform-origin: left bottom; -webkit-transform: rotate3d(0, 0, 1, 45deg); transform: rotate3d(0, 0, 1, 45deg); opacity: 0; } 100% { -webkit-transform-origin: left bottom; transform-origin: left bottom; -webkit-transform: none; transform: none; opacity: 1; } } .rotateInUpLeft { -webkit-animation-name: rotateInUpLeft; animation-name: rotateInUpLeft; } @-webkit-keyframes rotateInUpRight { 0% { -webkit-transform-origin: right bottom; transform-origin: right bottom; -webkit-transform: rotate3d(0, 0, 1, -90deg); transform: rotate3d(0, 0, 1, -90deg); opacity: 0; } 100% { -webkit-transform-origin: right bottom; transform-origin: right bottom; -webkit-transform: none; transform: none; opacity: 1; } } @keyframes rotateInUpRight { 0% { -webkit-transform-origin: right bottom; transform-origin: right bottom; -webkit-transform: rotate3d(0, 0, 1, -90deg); transform: rotate3d(0, 0, 1, -90deg); opacity: 0; } 100% { -webkit-transform-origin: right bottom; transform-origin: right bottom; -webkit-transform: none; transform: none; opacity: 1; } } .rotateInUpRight { -webkit-animation-name: rotateInUpRight; animation-name: rotateInUpRight; } @-webkit-keyframes rotateOut { 0% { -webkit-transform-origin: center; transform-origin: center; opacity: 1; } 100% { -webkit-transform-origin: center; transform-origin: center; -webkit-transform: rotate3d(0, 0, 1, 200deg); transform: rotate3d(0, 0, 1, 200deg); opacity: 0; } } @keyframes rotateOut { 0% { -webkit-transform-origin: center; transform-origin: center; opacity: 1; } 100% { -webkit-transform-origin: center; transform-origin: center; -webkit-transform: rotate3d(0, 0, 1, 200deg); transform: rotate3d(0, 0, 1, 200deg); opacity: 0; } } .rotateOut { -webkit-animation-name: rotateOut; animation-name: rotateOut; } @-webkit-keyframes rotateOutDownLeft { 0% { -webkit-transform-origin: left bottom; transform-origin: left bottom; opacity: 1; } 100% { -webkit-transform-origin: left bottom; transform-origin: left bottom; -webkit-transform: rotate3d(0, 0, 1, 45deg); transform: rotate3d(0, 0, 1, 45deg); opacity: 0; } } @keyframes rotateOutDownLeft { 0% { -webkit-transform-origin: left bottom; transform-origin: left bottom; opacity: 1; } 100% { -webkit-transform-origin: left bottom; transform-origin: left bottom; -webkit-transform: rotate3d(0, 0, 1, 45deg); transform: rotate3d(0, 0, 1, 45deg); opacity: 0; } } .rotateOutDownLeft { -webkit-animation-name: rotateOutDownLeft; animation-name: rotateOutDownLeft; } @-webkit-keyframes rotateOutDownRight { 0% { -webkit-transform-origin: right bottom; transform-origin: right bottom; opacity: 1; } 100% { -webkit-transform-origin: right bottom; transform-origin: right bottom; -webkit-transform: rotate3d(0, 0, 1, -45deg); transform: rotate3d(0, 0, 1, -45deg); opacity: 0; } } @keyframes rotateOutDownRight { 0% { -webkit-transform-origin: right bottom; transform-origin: right bottom; opacity: 1; } 100% { -webkit-transform-origin: right bottom; transform-origin: right bottom; -webkit-transform: rotate3d(0, 0, 1, -45deg); transform: rotate3d(0, 0, 1, -45deg); opacity: 0; } } .rotateOutDownRight { -webkit-animation-name: rotateOutDownRight; animation-name: rotateOutDownRight; } @-webkit-keyframes rotateOutUpLeft { 0% { -webkit-transform-origin: left bottom; transform-origin: left bottom; opacity: 1; } 100% { -webkit-transform-origin: left bottom; transform-origin: left bottom; -webkit-transform: rotate3d(0, 0, 1, -45deg); transform: rotate3d(0, 0, 1, -45deg); opacity: 0; } } @keyframes rotateOutUpLeft { 0% { -webkit-transform-origin: left bottom; transform-origin: left bottom; opacity: 1; } 100% { -webkit-transform-origin: left bottom; transform-origin: left bottom; -webkit-transform: rotate3d(0, 0, 1, -45deg); transform: rotate3d(0, 0, 1, -45deg); opacity: 0; } } .rotateOutUpLeft { -webkit-animation-name: rotateOutUpLeft; animation-name: rotateOutUpLeft; } @-webkit-keyframes rotateOutUpRight { 0% { -webkit-transform-origin: right bottom; transform-origin: right bottom; opacity: 1; } 100% { -webkit-transform-origin: right bottom; transform-origin: right bottom; -webkit-transform: rotate3d(0, 0, 1, 90deg); transform: rotate3d(0, 0, 1, 90deg); opacity: 0; } } @keyframes rotateOutUpRight { 0% { -webkit-transform-origin: right bottom; transform-origin: right bottom; opacity: 1; } 100% { -webkit-transform-origin: right bottom; transform-origin: right bottom; -webkit-transform: rotate3d(0, 0, 1, 90deg); transform: rotate3d(0, 0, 1, 90deg); opacity: 0; } } .rotateOutUpRight { -webkit-animation-name: rotateOutUpRight; animation-name: rotateOutUpRight; } @-webkit-keyframes hinge { 0% { -webkit-transform-origin: top left; transform-origin: top left; -webkit-animation-timing-function: ease-in-out; animation-timing-function: ease-in-out; } 20%, 60% { -webkit-transform: rotate3d(0, 0, 1, 80deg); transform: rotate3d(0, 0, 1, 80deg); -webkit-transform-origin: top left; transform-origin: top left; -webkit-animation-timing-function: ease-in-out; animation-timing-function: ease-in-out; } 40%, 80% { -webkit-transform: rotate3d(0, 0, 1, 60deg); transform: rotate3d(0, 0, 1, 60deg); -webkit-transform-origin: top left; transform-origin: top left; -webkit-animation-timing-function: ease-in-out; animation-timing-function: ease-in-out; opacity: 1; } 100% { -webkit-transform: translate3d(0, 700px, 0); transform: translate3d(0, 700px, 0); opacity: 0; } } @keyframes hinge { 0% { -webkit-transform-origin: top left; transform-origin: top left; -webkit-animation-timing-function: ease-in-out; animation-timing-function: ease-in-out; } 20%, 60% { -webkit-transform: rotate3d(0, 0, 1, 80deg); transform: rotate3d(0, 0, 1, 80deg); -webkit-transform-origin: top left; transform-origin: top left; -webkit-animation-timing-function: ease-in-out; animation-timing-function: ease-in-out; } 40%, 80% { -webkit-transform: rotate3d(0, 0, 1, 60deg); transform: rotate3d(0, 0, 1, 60deg); -webkit-transform-origin: top left; transform-origin: top left; -webkit-animation-timing-function: ease-in-out; animation-timing-function: ease-in-out; opacity: 1; } 100% { -webkit-transform: translate3d(0, 700px, 0); transform: translate3d(0, 700px, 0); opacity: 0; } } .hinge { -webkit-animation-name: hinge; animation-name: hinge; } /* originally authored by Nick Pettit - https://github.com/nickpettit/glide */ @-webkit-keyframes rollIn { 0% { opacity: 0; -webkit-transform: translate3d(-100%, 0, 0) rotate3d(0, 0, 1, -120deg); transform: translate3d(-100%, 0, 0) rotate3d(0, 0, 1, -120deg); } 100% { opacity: 1; -webkit-transform: none; transform: none; } } @keyframes rollIn { 0% { opacity: 0; -webkit-transform: translate3d(-100%, 0, 0) rotate3d(0, 0, 1, -120deg); transform: translate3d(-100%, 0, 0) rotate3d(0, 0, 1, -120deg); } 100% { opacity: 1; -webkit-transform: none; transform: none; } } .rollIn { -webkit-animation-name: rollIn; animation-name: rollIn; } /* originally authored by Nick Pettit - https://github.com/nickpettit/glide */ @-webkit-keyframes rollOut { 0% { opacity: 1; } 100% { opacity: 0; -webkit-transform: translate3d(100%, 0, 0) rotate3d(0, 0, 1, 120deg); transform: translate3d(100%, 0, 0) rotate3d(0, 0, 1, 120deg); } } @keyframes rollOut { 0% { opacity: 1; } 100% { opacity: 0; -webkit-transform: translate3d(100%, 0, 0) rotate3d(0, 0, 1, 120deg); transform: translate3d(100%, 0, 0) rotate3d(0, 0, 1, 120deg); } } .rollOut { -webkit-animation-name: rollOut; animation-name: rollOut; } @-webkit-keyframes zoomIn { 0% { opacity: 0; -webkit-transform: scale3d(.3, .3, .3); transform: scale3d(.3, .3, .3); } 50% { opacity: 1; } } @keyframes zoomIn { 0% { opacity: 0; -webkit-transform: scale3d(.3, .3, .3); transform: scale3d(.3, .3, .3); } 50% { opacity: 1; } } .zoomIn { -webkit-animation-name: zoomIn; animation-name: zoomIn; } @-webkit-keyframes zoomInDown { 0% { opacity: 0; -webkit-transform: scale3d(.1, .1, .1) translate3d(0, -1000px, 0); transform: scale3d(.1, .1, .1) translate3d(0, -1000px, 0); -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); } 60% { opacity: 1; -webkit-transform: scale3d(.475, .475, .475) translate3d(0, 60px, 0); transform: scale3d(.475, .475, .475) translate3d(0, 60px, 0); -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); } } @keyframes zoomInDown { 0% { opacity: 0; -webkit-transform: scale3d(.1, .1, .1) translate3d(0, -1000px, 0); transform: scale3d(.1, .1, .1) translate3d(0, -1000px, 0); -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); } 60% { opacity: 1; -webkit-transform: scale3d(.475, .475, .475) translate3d(0, 60px, 0); transform: scale3d(.475, .475, .475) translate3d(0, 60px, 0); -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); } } .zoomInDown { -webkit-animation-name: zoomInDown; animation-name: zoomInDown; } @-webkit-keyframes zoomInLeft { 0% { opacity: 0; -webkit-transform: scale3d(.1, .1, .1) translate3d(-1000px, 0, 0); transform: scale3d(.1, .1, .1) translate3d(-1000px, 0, 0); -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); } 60% { opacity: 1; -webkit-transform: scale3d(.475, .475, .475) translate3d(10px, 0, 0); transform: scale3d(.475, .475, .475) translate3d(10px, 0, 0); -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); } } @keyframes zoomInLeft { 0% { opacity: 0; -webkit-transform: scale3d(.1, .1, .1) translate3d(-1000px, 0, 0); transform: scale3d(.1, .1, .1) translate3d(-1000px, 0, 0); -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); } 60% { opacity: 1; -webkit-transform: scale3d(.475, .475, .475) translate3d(10px, 0, 0); transform: scale3d(.475, .475, .475) translate3d(10px, 0, 0); -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); } } .zoomInLeft { -webkit-animation-name: zoomInLeft; animation-name: zoomInLeft; } @-webkit-keyframes zoomInRight { 0% { opacity: 0; -webkit-transform: scale3d(.1, .1, .1) translate3d(1000px, 0, 0); transform: scale3d(.1, .1, .1) translate3d(1000px, 0, 0); -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); } 60% { opacity: 1; -webkit-transform: scale3d(.475, .475, .475) translate3d(-10px, 0, 0); transform: scale3d(.475, .475, .475) translate3d(-10px, 0, 0); -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); } } @keyframes zoomInRight { 0% { opacity: 0; -webkit-transform: scale3d(.1, .1, .1) translate3d(1000px, 0, 0); transform: scale3d(.1, .1, .1) translate3d(1000px, 0, 0); -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); } 60% { opacity: 1; -webkit-transform: scale3d(.475, .475, .475) translate3d(-10px, 0, 0); transform: scale3d(.475, .475, .475) translate3d(-10px, 0, 0); -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); } } .zoomInRight { -webkit-animation-name: zoomInRight; animation-name: zoomInRight; } @-webkit-keyframes zoomInUp { 0% { opacity: 0; -webkit-transform: scale3d(.1, .1, .1) translate3d(0, 1000px, 0); transform: scale3d(.1, .1, .1) translate3d(0, 1000px, 0); -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); } 60% { opacity: 1; -webkit-transform: scale3d(.475, .475, .475) translate3d(0, -60px, 0); transform: scale3d(.475, .475, .475) translate3d(0, -60px, 0); -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); } } @keyframes zoomInUp { 0% { opacity: 0; -webkit-transform: scale3d(.1, .1, .1) translate3d(0, 1000px, 0); transform: scale3d(.1, .1, .1) translate3d(0, 1000px, 0); -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); } 60% { opacity: 1; -webkit-transform: scale3d(.475, .475, .475) translate3d(0, -60px, 0); transform: scale3d(.475, .475, .475) translate3d(0, -60px, 0); -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); } } .zoomInUp { -webkit-animation-name: zoomInUp; animation-name: zoomInUp; } @-webkit-keyframes zoomOut { 0% { opacity: 1; } 50% { opacity: 0; -webkit-transform: scale3d(.3, .3, .3); transform: scale3d(.3, .3, .3); } 100% { opacity: 0; } } @keyframes zoomOut { 0% { opacity: 1; } 50% { opacity: 0; -webkit-transform: scale3d(.3, .3, .3); transform: scale3d(.3, .3, .3); } 100% { opacity: 0; } } .zoomOut { -webkit-animation-name: zoomOut; animation-name: zoomOut; } @-webkit-keyframes zoomOutDown { 40% { opacity: 1; -webkit-transform: scale3d(.475, .475, .475) translate3d(0, -60px, 0); transform: scale3d(.475, .475, .475) translate3d(0, -60px, 0); -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); } 100% { opacity: 0; -webkit-transform: scale3d(.1, .1, .1) translate3d(0, 2000px, 0); transform: scale3d(.1, .1, .1) translate3d(0, 2000px, 0); -webkit-transform-origin: center bottom; transform-origin: center bottom; -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); } } @keyframes zoomOutDown { 40% { opacity: 1; -webkit-transform: scale3d(.475, .475, .475) translate3d(0, -60px, 0); transform: scale3d(.475, .475, .475) translate3d(0, -60px, 0); -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); } 100% { opacity: 0; -webkit-transform: scale3d(.1, .1, .1) translate3d(0, 2000px, 0); transform: scale3d(.1, .1, .1) translate3d(0, 2000px, 0); -webkit-transform-origin: center bottom; transform-origin: center bottom; -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); } } .zoomOutDown { -webkit-animation-name: zoomOutDown; animation-name: zoomOutDown; } @-webkit-keyframes zoomOutLeft { 40% { opacity: 1; -webkit-transform: scale3d(.475, .475, .475) translate3d(42px, 0, 0); transform: scale3d(.475, .475, .475) translate3d(42px, 0, 0); } 100% { opacity: 0; -webkit-transform: scale(.1) translate3d(-2000px, 0, 0); transform: scale(.1) translate3d(-2000px, 0, 0); -webkit-transform-origin: left center; transform-origin: left center; } } @keyframes zoomOutLeft { 40% { opacity: 1; -webkit-transform: scale3d(.475, .475, .475) translate3d(42px, 0, 0); transform: scale3d(.475, .475, .475) translate3d(42px, 0, 0); } 100% { opacity: 0; -webkit-transform: scale(.1) translate3d(-2000px, 0, 0); transform: scale(.1) translate3d(-2000px, 0, 0); -webkit-transform-origin: left center; transform-origin: left center; } } .zoomOutLeft { -webkit-animation-name: zoomOutLeft; animation-name: zoomOutLeft; } @-webkit-keyframes zoomOutRight { 40% { opacity: 1; -webkit-transform: scale3d(.475, .475, .475) translate3d(-42px, 0, 0); transform: scale3d(.475, .475, .475) translate3d(-42px, 0, 0); } 100% { opacity: 0; -webkit-transform: scale(.1) translate3d(2000px, 0, 0); transform: scale(.1) translate3d(2000px, 0, 0); -webkit-transform-origin: right center; transform-origin: right center; } } @keyframes zoomOutRight { 40% { opacity: 1; -webkit-transform: scale3d(.475, .475, .475) translate3d(-42px, 0, 0); transform: scale3d(.475, .475, .475) translate3d(-42px, 0, 0); } 100% { opacity: 0; -webkit-transform: scale(.1) translate3d(2000px, 0, 0); transform: scale(.1) translate3d(2000px, 0, 0); -webkit-transform-origin: right center; transform-origin: right center; } } .zoomOutRight { -webkit-animation-name: zoomOutRight; animation-name: zoomOutRight; } @-webkit-keyframes zoomOutUp { 40% { opacity: 1; -webkit-transform: scale3d(.475, .475, .475) translate3d(0, 60px, 0); transform: scale3d(.475, .475, .475) translate3d(0, 60px, 0); -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); } 100% { opacity: 0; -webkit-transform: scale3d(.1, .1, .1) translate3d(0, -2000px, 0); transform: scale3d(.1, .1, .1) translate3d(0, -2000px, 0); -webkit-transform-origin: center bottom; transform-origin: center bottom; -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); } } @keyframes zoomOutUp { 40% { opacity: 1; -webkit-transform: scale3d(.475, .475, .475) translate3d(0, 60px, 0); transform: scale3d(.475, .475, .475) translate3d(0, 60px, 0); -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); } 100% { opacity: 0; -webkit-transform: scale3d(.1, .1, .1) translate3d(0, -2000px, 0); transform: scale3d(.1, .1, .1) translate3d(0, -2000px, 0); -webkit-transform-origin: center bottom; transform-origin: center bottom; -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); } } .zoomOutUp { -webkit-animation-name: zoomOutUp; animation-name: zoomOutUp; } @-webkit-keyframes slideInDown { 0% { -webkit-transform: translateY(-100%); transform: translateY(-100%); visibility: visible; } 100% { -webkit-transform: translateY(0); transform: translateY(0); } } @keyframes slideInDown { 0% { -webkit-transform: translateY(-100%); transform: translateY(-100%); visibility: visible; } 100% { -webkit-transform: translateY(0); transform: translateY(0); } } .slideInDown { -webkit-animation-name: slideInDown; animation-name: slideInDown; } @-webkit-keyframes slideInLeft { 0% { -webkit-transform: translateX(-100%); transform: translateX(-100%); visibility: visible; } 100% { -webkit-transform: translateX(0); transform: translateX(0); } } @keyframes slideInLeft { 0% { -webkit-transform: translateX(-100%); transform: translateX(-100%); visibility: visible; } 100% { -webkit-transform: translateX(0); transform: translateX(0); } } .slideInLeft { -webkit-animation-name: slideInLeft; animation-name: slideInLeft; } @-webkit-keyframes slideInRight { 0% { -webkit-transform: translateX(100%); transform: translateX(100%); visibility: visible; } 100% { -webkit-transform: translateX(0); transform: translateX(0); } } @keyframes slideInRight { 0% { -webkit-transform: translateX(100%); transform: translateX(100%); visibility: visible; } 100% { -webkit-transform: translateX(0); transform: translateX(0); } } .slideInRight { -webkit-animation-name: slideInRight; animation-name: slideInRight; } @-webkit-keyframes slideInUp { 0% { -webkit-transform: translateY(100%); transform: translateY(100%); visibility: visible; } 100% { -webkit-transform: translateY(0); transform: translateY(0); } } @keyframes slideInUp { 0% { -webkit-transform: translateY(100%); transform: translateY(100%); visibility: visible; } 100% { -webkit-transform: translateY(0); transform: translateY(0); } } .slideInUp { -webkit-animation-name: slideInUp; animation-name: slideInUp; } @-webkit-keyframes slideOutDown { 0% { -webkit-transform: translateY(0); transform: translateY(0); } 100% { visibility: hidden; -webkit-transform: translateY(100%); transform: translateY(100%); } } @keyframes slideOutDown { 0% { -webkit-transform: translateY(0); transform: translateY(0); } 100% { visibility: hidden; -webkit-transform: translateY(100%); transform: translateY(100%); } } .slideOutDown { -webkit-animation-name: slideOutDown; animation-name: slideOutDown; } @-webkit-keyframes slideOutLeft { 0% { -webkit-transform: translateX(0); transform: translateX(0); } 100% { visibility: hidden; -webkit-transform: translateX(-100%); transform: translateX(-100%); } } @keyframes slideOutLeft { 0% { -webkit-transform: translateX(0); transform: translateX(0); } 100% { visibility: hidden; -webkit-transform: translateX(-100%); transform: translateX(-100%); } } .slideOutLeft { -webkit-animation-name: slideOutLeft; animation-name: slideOutLeft; } @-webkit-keyframes slideOutRight { 0% { -webkit-transform: translateX(0); transform: translateX(0); } 100% { visibility: hidden; -webkit-transform: translateX(100%); transform: translateX(100%); } } @keyframes slideOutRight { 0% { -webkit-transform: translateX(0); transform: translateX(0); } 100% { visibility: hidden; -webkit-transform: translateX(100%); transform: translateX(100%); } } .slideOutRight { -webkit-animation-name: slideOutRight; animation-name: slideOutRight; } @-webkit-keyframes slideOutUp { 0% { -webkit-transform: translateY(0); transform: translateY(0); } 100% { visibility: hidden; -webkit-transform: translateY(-100%); transform: translateY(-100%); } } @keyframes slideOutUp { 0% { -webkit-transform: translateY(0); transform: translateY(0); } 100% { visibility: hidden; -webkit-transform: translateY(-100%); transform: translateY(-100%); } } .slideOutUp { -webkit-animation-name: slideOutUp; animation-name: slideOutUp; } ================================================ FILE: portal/public/vendors/css/grid.css ================================================ /* SECTIONS ============================================================================= */ .section { clear: both; padding: 0px; margin: 0px; } /* GROUPING ============================================================================= */ .row { zoom: 1; /* For IE 6/7 (trigger hasLayout) */ } .row:before, .row:after { content:""; display:table; } .row:after { clear:both; } /* GRID COLUMN SETUP ==================================================================== */ .col { display: block; float:left; margin: 1% 0 1% 1.6%; } .col:first-child { margin-left: 0; } /* all browsers except IE6 and lower */ /* REMOVE MARGINS AS ALL GO FULL WIDTH AT 480 PIXELS */ @media only screen and (max-width: 480px) { .col { /*margin: 1% 0 1% 0%;*/ margin: 0; } } /* GRID OF TWO ============================================================================= */ .span-2-of-2 { width: 100%; } .span-1-of-2 { width: 49.2%; } /* GO FULL WIDTH AT LESS THAN 480 PIXELS */ @media only screen and (max-width: 480px) { .span-2-of-2 { width: 100%; } .span-1-of-2 { width: 100%; } } /* GRID OF THREE ============================================================================= */ .span-3-of-3 { width: 100%; } .span-2-of-3 { width: 66.13%; } .span-1-of-3 { width: 32.26%; } /* GO FULL WIDTH AT LESS THAN 480 PIXELS */ @media only screen and (max-width: 480px) { .span-3-of-3 { width: 100%; } .span-2-of-3 { width: 100%; } .span-1-of-3 { width: 100%; } } /* GRID OF FOUR ============================================================================= */ .span-4-of-4 { width: 100%; } .span-3-of-4 { width: 74.6%; } .span-2-of-4 { width: 49.2%; } .span-1-of-4 { width: 23.8%; } /* GO FULL WIDTH AT LESS THAN 480 PIXELS */ @media only screen and (max-width: 480px) { .span-4-of-4 { width: 100%; } .span-3-of-4 { width: 100%; } .span-2-of-4 { width: 100%; } .span-1-of-4 { width: 100%; } } /* GRID OF FIVE ============================================================================= */ .span-5-of-5 { width: 100%; } .span-4-of-5 { width: 79.68%; } .span-3-of-5 { width: 59.36%; } .span-2-of-5 { width: 39.04%; } .span-1-of-5 { width: 18.72%; } /* GO FULL WIDTH AT LESS THAN 480 PIXELS */ @media only screen and (max-width: 480px) { .span-5-of-5 { width: 100%; } .span-4-of-5 { width: 100%; } .span-3-of-5 { width: 100%; } .span-2-of-5 { width: 100%; } .span-1-of-5 { width: 100%; } } /* GRID OF SIX ============================================================================= */ .span-6-of-6 { width: 100%; } .span-5-of-6 { width: 83.06%; } .span-4-of-6 { width: 66.13%; } .span-3-of-6 { width: 49.2%; } .span-2-of-6 { width: 32.26%; } .span-1-of-6 { width: 15.33%; } /* GO FULL WIDTH AT LESS THAN 480 PIXELS */ @media only screen and (max-width: 480px) { .span-6-of-6 { width: 100%; } .span-5-of-6 { width: 100%; } .span-4-of-6 { width: 100%; } .span-3-of-6 { width: 100%; } .span-2-of-6 { width: 100%; } .span-1-of-6 { width: 100%; } } /* GRID OF SEVEN ============================================================================= */ .span-7-of-7 { width: 100%; } .span-6-of-7 { width: 85.48%; } .span-5-of-7 { width: 70.97%; } .span-4-of-7 { width: 56.45%; } .span-3-of-7 { width: 41.94%; } .span-2-of-7 { width: 27.42%; } .span-1-of-7 { width: 12.91%; } /* GO FULL WIDTH AT LESS THAN 480 PIXELS */ @media only screen and (max-width: 480px) { .span-7-of-7 { width: 100%; } .span-6-of-7 { width: 100%; } .span-5-of-7 { width: 100%; } .span-4-of-7 { width: 100%; } .span-3-of-7 { width: 100%; } .span-2-of-7 { width: 100%; } .span-1-of-7 { width: 100%; } } /* GRID OF EIGHT ============================================================================= */ .span-8-of-8 { width: 100%; } .span-7-of-8 { width: 87.3%; } .span-6-of-8 { width: 74.6%; } .span-5-of-8 { width: 61.9%; } .span-4-of-8 { width: 49.2%; } .span-3-of-8 { width: 36.5%; } .span-2-of-8 { width: 23.8%; } .span-1-of-8 { width: 11.1%; } /* GO FULL WIDTH AT LESS THAN 480 PIXELS */ @media only screen and (max-width: 480px) { .span-8-of-8 { width: 100%; } .span-7-of-8 { width: 100%; } .span-6-of-8 { width: 100%; } .span-5-of-8 { width: 100%; } .span-4-of-8 { width: 100%; } .span-3-of-8 { width: 100%; } .span-2-of-8 { width: 100%; } .span-1-of-8 { width: 100%; } } /* GRID OF NINE ============================================================================= */ .span-9-of-9 { width: 100%; } .span-8-of-9 { width: 88.71%; } .span-7-of-9 { width: 77.42%; } .span-6-of-9 { width: 66.13%; } .span-5-of-9 { width: 54.84%; } .span-4-of-9 { width: 43.55%; } .span-3-of-9 { width: 32.26%; } .span-2-of-9 { width: 20.97%; } .span-1-of-9 { width: 9.68%; } /* GO FULL WIDTH AT LESS THAN 480 PIXELS */ @media only screen and (max-width: 480px) { .span-9-of-9 { width: 100%; } .span-8-of-9 { width: 100%; } .span-7-of-9 { width: 100%; } .span-6-of-9 { width: 100%; } .span-5-of-9 { width: 100%; } .span-4-of-9 { width: 100%; } .span-3-of-9 { width: 100%; } .span-2-of-9 { width: 100%; } .span-1-of-9 { width: 100%; } } /* GRID OF TEN ============================================================================= */ .span-10-of-10 { width: 100%; } .span-9-of-10 { width: 89.84%; } .span-8-of-10 { width: 79.68%; } .span-7-of-10 { width: 69.52%; } .span-6-of-10 { width: 59.36%; } .span-5-of-10 { width: 49.2%; } .span-4-of-10 { width: 39.04%; } .span-3-of-10 { width: 28.88%; } .span-2-of-10 { width: 18.72%; } .span-1-of-10 { width: 8.56%; } /* GO FULL WIDTH AT LESS THAN 480 PIXELS */ @media only screen and (max-width: 480px) { .span-10-of-10 { width: 100%; } .span-9-of-10 { width: 100%; } .span-8-of-10 { width: 100%; } .span-7-of-10 { width: 100%; } .span-6-of-10 { width: 100%; } .span-5-of-10 { width: 100%; } .span-4-of-10 { width: 100%; } .span-3-of-10 { width: 100%; } .span-2-of-10 { width: 100%; } .span-1-of-10 { width: 100%; } } /* GRID OF ELEVEN ============================================================================= */ .span-11-of-11 { width: 100%; } .span-10-of-11 { width: 90.76%; } .span-9-of-11 { width: 81.52%; } .span-8-of-11 { width: 72.29%; } .span-7-of-11 { width: 63.05%; } .span-6-of-11 { width: 53.81%; } .span-5-of-11 { width: 44.58%; } .span-4-of-11 { width: 35.34%; } .span-3-of-11 { width: 26.1%; } .span-2-of-11 { width: 16.87%; } .span-1-of-11 { width: 7.63%; } /* GO FULL WIDTH AT LESS THAN 480 PIXELS */ @media only screen and (max-width: 480px) { .span-11-of-11 { width: 100%; } .span-10-of-11 { width: 100%; } .span-9-of-11 { width: 100%; } .span-8-of-11 { width: 100%; } .span-7-of-11 { width: 100%; } .span-6-of-11 { width: 100%; } .span-5-of-11 { width: 100%; } .span-4-of-11 { width: 100%; } .span-3-of-11 { width: 100%; } .span-2-of-11 { width: 100%; } .span-1-of-11 { width: 100%; } } /* GRID OF TWELVE ============================================================================= */ .span-12-of-12 { width: 100%; } .span-11-of-12 { width: 91.53%; } .span-10-of-12 { width: 83.06%; } .span-9-of-12 { width: 74.6%; } .span-8-of-12 { width: 66.13%; } .span-7-of-12 { width: 57.66%; } .span-6-of-12 { width: 49.2%; } .span-5-of-12 { width: 40.73%; } .span-4-of-12 { width: 32.26%; } .span-3-of-12 { width: 23.8%; } .span-2-of-12 { width: 15.33%; } .span-1-of-12 { width: 6.86%; } /* GO FULL WIDTH AT LESS THAN 480 PIXELS */ @media only screen and (max-width: 480px) { .span-12-of-12 { width: 100%; } .span-11-of-12 { width: 100%; } .span-10-of-12 { width: 100%; } .span-9-of-12 { width: 100%; } .span-8-of-12 { width: 100%; } .span-7-of-12 { width: 100%; } .span-6-of-12 { width: 100%; } .span-5-of-12 { width: 100%; } .span-4-of-12 { width: 100%; } .span-3-of-12 { width: 100%; } .span-2-of-12 { width: 100%; } .span-1-of-12 { width: 100%; } } ================================================ FILE: portal/public/vendors/css/normalize.css ================================================ /*! normalize.css v3.0.2 | MIT License | git.io/normalize */ /** * 1. Set default font family to sans-serif. * 2. Prevent iOS text size adjust after orientation change, without disabling * user zoom. */ html { font-family: sans-serif; /* 1 */ -ms-text-size-adjust: 100%; /* 2 */ -webkit-text-size-adjust: 100%; /* 2 */ } /** * Remove default margin. */ body { margin: 0; } /* HTML5 display definitions ========================================================================== */ /** * Correct `block` display not defined for any HTML5 element in IE 8/9. * Correct `block` display not defined for `details` or `summary` in IE 10/11 * and Firefox. * Correct `block` display not defined for `main` in IE 11. */ article, aside, details, figcaption, figure, footer, header, hgroup, main, menu, nav, section, summary { display: block; } /** * 1. Correct `inline-block` display not defined in IE 8/9. * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. */ audio, canvas, progress, video { display: inline-block; /* 1 */ vertical-align: baseline; /* 2 */ } /** * Prevent modern browsers from displaying `audio` without controls. * Remove excess height in iOS 5 devices. */ audio:not([controls]) { display: none; height: 0; } /** * Address `[hidden]` styling not present in IE 8/9/10. * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22. */ [hidden], template { display: none; } /* Links ========================================================================== */ /** * Remove the gray background color from active links in IE 10. */ a { background-color: transparent; } /** * Improve readability when focused and also mouse hovered in all browsers. */ a:active, a:hover { outline: 0; } /* Text-level semantics ========================================================================== */ /** * Address styling not present in IE 8/9/10/11, Safari, and Chrome. */ abbr[title] { border-bottom: 1px dotted; } /** * Address style set to `bolder` in Firefox 4+, Safari, and Chrome. */ b, strong { font-weight: bold; } /** * Address styling not present in Safari and Chrome. */ dfn { font-style: italic; } /** * Address variable `h1` font-size and margin within `section` and `article` * contexts in Firefox 4+, Safari, and Chrome. */ h1 { font-size: 2em; margin: 0.67em 0; } /** * Address styling not present in IE 8/9. */ mark { background: #ff0; color: #000; } /** * Address inconsistent and variable font size in all browsers. */ small { font-size: 80%; } /** * Prevent `sub` and `sup` affecting `line-height` in all browsers. */ sub, sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; } sup { top: -0.5em; } sub { bottom: -0.25em; } /* Embedded content ========================================================================== */ /** * Remove border when inside `a` element in IE 8/9/10. */ img { border: 0; } /** * Correct overflow not hidden in IE 9/10/11. */ svg:not(:root) { overflow: hidden; } /* Grouping content ========================================================================== */ /** * Address margin not present in IE 8/9 and Safari. */ figure { margin: 1em 40px; } /** * Address differences between Firefox and other browsers. */ hr { -moz-box-sizing: content-box; box-sizing: content-box; height: 0; } /** * Contain overflow in all browsers. */ pre { overflow: auto; } /** * Address odd `em`-unit font size rendering in all browsers. */ code, kbd, pre, samp { font-family: monospace, monospace; font-size: 1em; } /* Forms ========================================================================== */ /** * Known limitation: by default, Chrome and Safari on OS X allow very limited * styling of `select`, unless a `border` property is set. */ /** * 1. Correct color not being inherited. * Known issue: affects color of disabled elements. * 2. Correct font properties not being inherited. * 3. Address margins set differently in Firefox 4+, Safari, and Chrome. */ button, input, optgroup, select, textarea { color: inherit; /* 1 */ font: inherit; /* 2 */ margin: 0; /* 3 */ } /** * Address `overflow` set to `hidden` in IE 8/9/10/11. */ button { overflow: visible; } /** * Address inconsistent `text-transform` inheritance for `button` and `select`. * All other form control elements do not inherit `text-transform` values. * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. * Correct `select` style inheritance in Firefox. */ button, select { text-transform: none; } /** * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` * and `video` controls. * 2. Correct inability to style clickable `input` types in iOS. * 3. Improve usability and consistency of cursor style between image-type * `input` and others. */ button, html input[type="button"], /* 1 */ input[type="reset"], input[type="submit"] { -webkit-appearance: button; /* 2 */ cursor: pointer; /* 3 */ } /** * Re-set default cursor for disabled elements. */ button[disabled], html input[disabled] { cursor: default; } /** * Remove inner padding and border in Firefox 4+. */ button::-moz-focus-inner, input::-moz-focus-inner { border: 0; padding: 0; } /** * Address Firefox 4+ setting `line-height` on `input` using `!important` in * the UA stylesheet. */ input { line-height: normal; } /** * It's recommended that you don't attempt to style these elements. * Firefox's implementation doesn't respect box-sizing, padding, or width. * * 1. Address box sizing set to `content-box` in IE 8/9/10. * 2. Remove excess padding in IE 8/9/10. */ input[type="checkbox"], input[type="radio"] { box-sizing: border-box; /* 1 */ padding: 0; /* 2 */ } /** * Fix the cursor style for Chrome's increment/decrement buttons. For certain * `font-size` values of the `input`, it causes the cursor style of the * decrement button to change from `default` to `text`. */ input[type="number"]::-webkit-inner-spin-button, input[type="number"]::-webkit-outer-spin-button { height: auto; } /** * 1. Address `appearance` set to `searchfield` in Safari and Chrome. * 2. Address `box-sizing` set to `border-box` in Safari and Chrome * (include `-moz` to future-proof). */ input[type="search"] { -webkit-appearance: textfield; /* 1 */ -moz-box-sizing: content-box; -webkit-box-sizing: content-box; /* 2 */ box-sizing: content-box; } /** * Remove inner padding and search cancel button in Safari and Chrome on OS X. * Safari (but not Chrome) clips the cancel button when the search input has * padding (and `textfield` appearance). */ input[type="search"]::-webkit-search-cancel-button, input[type="search"]::-webkit-search-decoration { -webkit-appearance: none; } /** * Define consistent border, margin, and padding. */ fieldset { border: 1px solid #c0c0c0; margin: 0 2px; padding: 0.35em 0.625em 0.75em; } /** * 1. Correct `color` not being inherited in IE 8/9/10/11. * 2. Remove padding so people aren't caught out if they zero out fieldsets. */ legend { border: 0; /* 1 */ padding: 0; /* 2 */ } /** * Remove default vertical scrollbar in IE 8/9/10/11. */ textarea { overflow: auto; } /** * Don't inherit the `font-weight` (applied by a rule above). * NOTE: the default cannot safely be changed in Chrome and Safari on OS X. */ optgroup { font-weight: bold; } /* Tables ========================================================================== */ /** * Remove most spacing between table cells. */ table { border-collapse: collapse; border-spacing: 0; } td, th { padding: 0; } ================================================ FILE: portal/routes/accounts.js ================================================ const dateFormat = require('dateformat'); module.exports = function (app, request, ports) { app.post('/endpoints/accounts/transfer', function (req, res) { console.log('Transfer initiated ', req.body); var to = req.body.to.match(/\d+/); var from = req.body.from.match(/\d+/); var params = { uuid: req.session.user.uuid, amount: req.body.amount, currency: "AED", description: "Transfer", date: dateFormat(new Date(), "mm, dd, yyyy"), category: "cash" }; var options = { method: 'POST', uri: `${req.protocol}://${req.hostname}:${ports.transactions}${process.env.CREATE_TRANSACTION_ENDPOINT}`, body: params, json: true }; request.post(options, function (error, response, body) { if (response.statusCode === 500) { res.redirect('/accounts.html#failure'); return; } var options = { method: 'POST', uri: `${req.protocol}://${req.hostname}:${ports.accounts}${process.env.ACCOUNT_WITHDRAW_ENDPOINT}`, body: { uuid: req.session.user.uuid, amount: req.body.amount, number: from }, json: true }; request.post(options, function (error, response, body) { if (response.statusCode==500){ res.redirect('/accounts.html#failure') return; } var options = { method: 'POST', uri: `${req.protocol}://${req.hostname}:${ports.accounts}${process.env.ACCOUNT_DEPOSIT_ENDPOINT}`, body: { uuid: req.session.user.uuid, amount: req.body.amount, number: to }, json: true }; request.post(options, function (error, response, body) { if (response.statusCode==500){ res.redirect('/accounts.html#failure') return; } res.redirect('/accounts.html#success') return; }) }) }); }); app.get('/endpoints/accounts/activate', function (req, res) { var body = { uuid: req.session.user.uuid, type: req.query.type, currency: 'AED' } var options = { method: 'POST', uri: `${req.protocol}://${req.hostname}:${ports.accounts}${process.env.CREATE_ACCOUNT_ENDPOINT}`, body: body, json: true }; request.post(options, function (error, response, body) { if (response.statusCode==500){ res.redirect('/accounts.html#failure') return; } res.redirect('/accounts.html#activated') return; }); }); app.post('/endpoints/accounts/get', function (req, res) { var options = { method: 'POST', uri: `${req.protocol}://${req.hostname}:${ports.accounts}${process.env.GET_ACCOUNTS_ENDPOINT}`, body: { uuid: req.session.user.uuid }, json: true }; request.post(options, function (error, response, body) { console.log('accounts for ', req.body,': ', body) res.send(body) }); }); }; ================================================ FILE: portal/routes/auth.js ================================================ module.exports = function (app) { app.get('/endpoints/auth', function (req, res) { if (req.session.user) { res.send({'uuid': req.session.user.uuid}); } else { res.status(500).send({'err': 'unauthorized'}); } }); }; ================================================ FILE: portal/routes/bills.js ================================================ const dateFormat = require('dateformat'); module.exports = function (app, request, ports) { app.get('/endpoints/bills/pay', function (req, res) { var params = { "uuid": req.session.user.uuid, "amount": req.query.amount, "currency": "AED", "description": req.query.entity, "date": dateFormat(new Date(), "mm, dd, yyyy"), "category": "bills" }; var options = { method: 'POST', uri: `${req.protocol}://${req.hostname}:${ports.transactions}${process.env.CREATE_TRANSACTION_ENDPOINT}`, body: params, json: true }; request.post(options, function (error, response, body) { if (response.statusCode === 500) { res.redirect('/bills.html#failure'); return; } var params = { uuid: req.session.user.uuid, category: req.query.category, entity: req.query.entity, account_no: req.query.account, amount: 0.00, date: dateFormat(new Date(), "mm, dd, yyyy") }; var options = { method: 'POST', uri: `${req.protocol}://${req.hostname}:${ports.bills}${process.env.UPSERT_BILL_ENDPOINT}`, body: params, json: true }; request.post(options, function (error, response, body) { if (response.statusCode === 500) { res.redirect('/bills.html#failure'); return; } res.redirect('/bills.html#success'); }); }); }); app.post('/endpoints/bills/get', function (req, res) { var options = { method: 'POST', uri: `${req.protocol}://${req.hostname}:${ports.bills}${process.env.GET_BILLS_ENDPOINT}`, body: req.body, json: true }; request.post(options, function (error, response, body) { console.log('bills for ', req.body, ': ', body); res.send(body); }); }); }; ================================================ FILE: portal/routes/support.js ================================================ module.exports = function (app, request, ports) { app.post('/endpoints/support/chat', function (req, res) { var options = { method: 'POST', uri: `${req.protocol}://${req.hostname}:${ports.support}${process.env.CHAT_ENDPOINT}`, body: req.body, json: true }; request.post(options, function (error, response, body) { console.log('support for ', req.body, ': ', body); res.send(body); }); }); }; ================================================ FILE: portal/routes/transactions.js ================================================ module.exports = function (app, request, ports) { app.post('/endpoints/transactions/get', function (req, res) { var options = { method: 'POST', uri: `${req.protocol}://${req.hostname}:${ports.transactions}${process.env.GET_TRANSACTIONS_ENDPOINT}`, body: req.body, json: true }; request.post(options, function (error, response, body) { console.log('transactions for ', req.body, ': ', body); res.send(body); }); }); }; ================================================ FILE: portal/routes/user.js ================================================ module.exports = function (app, request, ports) { app.post('/endpoints/login', function (req, res) { var options = { method: 'POST', uri: `${req.protocol}://${req.hostname}:${ports.authentication}${process.env.LOGIN_ENDPOINT}`, body: req.body, json: true }; request.post(options, function (error, response, body) { if (response.statusCode === 500) { res.redirect('/login.html#unauthorized'); return; } req.session.user = body; res.redirect('/overview.html'); return; }); }); app.post('/endpoints/signup', function (req, res) { console.log(req); var options = { method: 'POST', uri: `${req.protocol}://${req.hostname}:${ports.authentication}${process.env.SIGNUP_ENDPOINT}`, body: req.body, json: true }; request.post(options, function (error, response, body) { console.log(error, response, body); if (response.statusCode === 500) { res.redirect('/signup.html#failed'); return; } req.session.user = body; res.redirect('/overview.html'); return; }); }); app.get('/endpoints/logout', function (req, res) { res.cookie("connect.sid", "", {expires: new Date()}); req.session.destroy(function (err) { console.log("session destroyed"); res.redirect('/index.html'); }); }); }; ================================================ FILE: portal/server.js ================================================ 'use strict'; require('dotenv').config({silent: true, path: `${__dirname}/.env`}); const express = require('express'); const bodyParser = require('body-parser'); const cookieParser = require('cookie-parser'); const session = require('express-session'); const mongoose = require('mongoose'); const mongostore = require('connect-mongo')(session); const request = require('request'); const config = require(`${__dirname}/config`)[process.env.NODE_ENV]; var app = express(); app.use(express.static(`${__dirname}/public`)); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({extended: false})); app.use(cookieParser()); app.use(session({ secret: process.env.SESSION_SECRET, store: new mongostore({url: process.env.MONGO_URL}), resave: true, saveUninitialized: true, cookie: { cookieName: 'connect.sid', secret: process.env.SESSION_SECRET, httpOnly: false, secure: false, ephemeral: true } })); require('./routes/auth')(app); require('./routes/user')(app, request, config.ports); require('./routes/bills')(app, request, config.ports); require('./routes/accounts')(app, request, config.ports); require('./routes/transactions')(app, request, config.ports); require('./routes/support')(app, request, config.ports); var port = 3100; console.log(`Running on ${process.env.BASE_PATH}:${port}, connecting to ${process.env.MONGO_URL}`) mongoose.connect(process.env.MONGO_URL, function (ignore, connection) { connection.onOpen(); app.listen(port, function () { console.log('Innovate portal running on port: %d', port); }); }); ================================================ FILE: scripts/install_bx.sh ================================================ #!/bin/bash -e if [[ "$TRAVIS_PULL_REQUEST" != "false" ]]; then echo -e "\033[0;33mPull Request detected; not installing extra software.\033[0m" exit 0 fi echo "Installing IBM Cloud CLI" curl -L https://clis.ng.bluemix.net/download/bluemix-cli/latest/linux64 > IBM_Cloud_CLI.tar.gz tar -xvf IBM_Cloud_CLI.tar.gz sudo ./Bluemix_CLI/install_bluemix_cli echo "IBM Cloud Developer Tools" curl -sL https://ibm.biz/idt-installer | bash echo "Running IBM Cloud help" ibmcloud dev help echo "Configuring ibmcloud to disable version check" ibmcloud config --check-version=false echo "Checking ibmcloud version" ibmcloud --version ================================================ FILE: support/.cfignore ================================================ .git/ node_modules/ test/ vcap-local.js ================================================ FILE: support/.dockerignore ================================================ node_modules/ test/ .bluemix/ ================================================ FILE: support/.gitignore ================================================ # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* # Runtime data pids *.pid *.seed *.pid.lock # Directory for instrumented libs generated by jscoverage/JSCover lib-cov # Coverage directory used by tools like istanbul coverage # nyc test coverage .nyc_output # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) .grunt # Bower dependency directory (https://bower.io/) bower_components # node-waf configuration .lock-wscript # Compiled binary addons (http://nodejs.org/api/addons.html) build/Release # Dependency directories node_modules/ jspm_packages/ # Typescript v1 declaration files typings/ # Optional npm cache directory .npm # Optional eslint cache .eslintcache # Optional REPL history .node_repl_history # Output of 'npm pack' *.tgz # Yarn Integrity file .yarn-integrity # dotenv environment variables file .env # other .DS_Store license_accepted ================================================ FILE: support/.ibm-project ================================================ {"build-release-ready":true,"build-debug-ready":false,"deploy-image":"","chart-path":"","ibm-cluster":""} ================================================ FILE: support/Dockerfile ================================================ FROM ibmcom/ibmnode:latest LABEL maintainer="***REMOVED***" #RUN apt-get install -y nodejs npm WORKDIR /app # Install app dependencies COPY . /app RUN cd /app; npm install; npm prune --production ENV NODE_ENV production ENV PORT 4000 EXPOSE 4000 CMD [ "npm","start" ] ================================================ FILE: support/Dockerfile-tools ================================================ FROM ibmcom/ibmnode ENV PORT 4000 WORKDIR "/app" # Bundle app source COPY . /app EXPOSE 4000 CMD ["/bin/bash"] ARG bx_dev_user=root ARG bx_dev_userid=1000 RUN BX_DEV_USER=$bx_dev_user RUN BX_DEV_USERID=$bx_dev_userid RUN if [ $bx_dev_user != "root" ]; then useradd -ms /bin/bash -u $bx_dev_userid $bx_dev_user; fi ================================================ FILE: support/app.js ================================================ 'use strict'; const express = require('express'); const bodyParser = require('body-parser'); const fs = require('fs'); const Conversation = require('watson-developer-cloud/conversation/v1'); const WatsonConversationSetup = require('./lib/watson-conversation-setup'); var app = express(); app.use(bodyParser.json()); app.all('/api/message', function (req, res, next) { res.header("Access-Control-Allow-Origin", "*"); res.header("Access-Control-Allow-Headers", "*"); next(); }); let conversationUsername; let conversationPassword; let conversationUrl; if (process.env.CONVERSATION_BINDING) { console.log(process.env.CONVERSATION_BINDING); let conversationBinding = process.env.CONVERSATION_BINDING; if (typeof conversationBinding === "string") conversationBinding = JSON.parse(conversationBinding); conversationUsername = conversationBinding.username; conversationPassword = conversationBinding.password; conversationUrl = conversationBinding.url; } else { conversationUsername = process.env.CONVERSATION_USERNAME; conversationPassword = process.env.CONVERSATION_PASSWORD; conversationUrl = process.env.CONVERSATION_URL; } var conversation = new Conversation({ version_date: Conversation.VERSION_DATE_2017_04_21, username: conversationUsername, password: conversationPassword, url: conversationUrl }); let workspaceID; // workspaceID will be set when the workspace is created or validated. const conversationSetup = new WatsonConversationSetup(conversation); const workspaceJson = JSON.parse(fs.readFileSync(`${__dirname}/conversation-workspace.json`)); const conversationSetupParams = { default_name: 'innovate-support', workspace_json: workspaceJson }; conversationSetup.setupConversationWorkspace(conversationSetupParams, (err, data) => { if (err) { console.log(err); } else { console.log('Watson Assistant is ready!'); workspaceID = data; } }); app.post('/api/message', function (req, res) { res.header("Content-Type", "application/json"); if (!workspaceID) { return res.json({ 'output': { 'text': 'The app has not been configured with a WORKSPACE_ID environment variable. Please refer to the ' + 'README documentation on how to set this variable.
' + 'Once a workspace has been defined the intents may be imported from ' + 'here in order to get a working application.' } }); } var payload = { workspace_id: workspaceID, context: req.body.context || {}, input: req.body.input || {} }; conversation.message(payload, function (err, data) { if (err) { return res.status(err.code || 500).json(err); } return res.json(updateMessage(payload, data)); }); }); /** * Updates the response text using the intent confidence * @param {Object} input The request to the Conversation service * @param {Object} response The response from the Conversation service * @return {Object} The response with the updated message **/ function updateMessage(input, response) { var responseText = null; if (!response.output) { response.output = {}; } else { return response; } if (response.intents && response.intents[0]) { var intent = response.intents[0]; if (intent.confidence >= 0.75) { responseText = 'I understood your intent was ' + intent.intent; } else if (intent.confidence >= 0.5) { responseText = 'I think your intent was ' + intent.intent; } else { responseText = 'I did not understand your intent'; } } response.output.text = responseText; return response; } module.exports = app; ================================================ FILE: support/chart/innovate-support/Chart.yaml ================================================ apiVersion: v1 description: A Helm chart for innovate bank portal name: innovate-support version: 1.0.0 ================================================ FILE: support/chart/innovate-support/templates/deployment.yaml ================================================ apiVersion: extensions/v1beta1 kind: Deployment metadata: name: "{{ .Chart.Name }}-deployment" labels: chart: '{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}' spec: replicas: {{ .Values.replicaCount }} strategy: type: RollingUpdate rollingUpdate: maxUnavailable: 0 maxSurge: 1 revisionHistoryLimit: {{ .Values.revisionHistoryLimit }} template: metadata: labels: app: "{{ .Chart.Name }}-selector" spec: containers: - name: "{{ .Chart.Name }}" image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" imagePullPolicy: {{ .Values.image.pullPolicy }} livenessProbe: httpGet: path: /health port: {{ .Values.service.servicePort }} initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds}} periodSeconds: {{ .Values.livenessProbe.periodSeconds}} resources: requests: cpu: "{{ .Values.image.resources.requests.cpu }}" memory: "{{ .Values.image.resources.requests.memory }}" env: - name: PORT value : "{{ .Values.service.servicePort }}" ================================================ FILE: support/chart/innovate-support/templates/hpa.yaml ================================================ {{ if .Values.hpa.enabled }} apiVersion: autoscaling/v2alpha1 kind: HorizontalPodAutoscaler metadata: name: "{{ .Chart.Name }}-hpa-policy" namespace: spec: scaleTargetRef: apiVersion: apps/v2alpha1 kind: Deployment name: "{{ .Chart.Name }}-deployment" minReplicas: {{ .Values.hpa.minReplicas }} maxReplicas: {{ .Values.hpa.maxReplicas }} metrics: - type: Resource resource: name: cpu targetAverageUtilization: {{ .Values.hpa.metrics.cpu.targetAverageUtilization }} - type: Resource resource: name: memory targetAverageUtilization: {{ .Values.hpa.metrics.memory.targetAverageUtilization }} {{ end }} ================================================ FILE: support/chart/innovate-support/templates/service.yaml ================================================ apiVersion: v1 kind: Service metadata: annotations: prometheus.io/scrape: 'true' name: "{{ .Chart.Name }}" labels: chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}" spec: type: {{ .Values.service.type }} ports: - port: {{ .Values.service.servicePort }} nodePort: {{ .Values.service.serviceNodePort }} selector: app: "{{ .Chart.Name }}-selector" ================================================ FILE: support/chart/innovate-support/values.yaml ================================================ # This is a YAML-formatted file. # Declare variables to be passed into your templates. replicaCount: 1 revisionHistoryLimit: 3 image: repository: mycluster.icp:8500/default/innovate-support tag: v1.0.0 pullPolicy: Always resources: requests: cpu: 500m memory: 512Mi livenessProbe: initialDelaySeconds: 3000 periodSeconds: 1000 service: name: Node type: NodePort servicePort: 4000 serviceNodePort: 30180 hpa: enabled: false minReplicas: 2 maxReplicas: 3 metrics: cpu: targetAverageUtilization: 80 memory: targetAverageUtilization: 80 services: ================================================ FILE: support/cli-config.yml ================================================ # The IBM version of this configuration version : 0.0.3 # The container name used for the run container container-name-run : "innovate-support-run" # The container name used for the tools container container-name-tools : "innovate-support-tools" # The project root on the host for the run container to mount to container-path-run host-path-run : . # The project root on the host for the tools container to mount to container-path-tools host-path-tools : . # The project root in the run container to mount to host-path-run container-path-run : "/app" # The project root in the tools container that will be mounted to host-path-tools container-path-tools : "/app" # The list of additional mounts between the host and the run container in the form [host_path:container_path] container-mounts-run: - "./node_modules_linux": "/app/node_modules" # The list of additional mounts between the host and the tools container in the form [host_path:container_path] container-mounts-tools: - "./node_modules_linux": "/app/node_modules" # The port mappings between the host and the container in the form [host:container] container-port-map : "4000:4000" # The port mappings between the host and the container for the debug port in the form [host:container] container-port-map-debug : "5868:5868" # The name for the dockerfile for the run container dockerfile-run : "Dockerfile" # The name for the dockerfile for the tools container dockerfile-tools : "Dockerfile-tools" # The name of image to create from dockerfile-run image-name-run : "innovate-support-run" # The name of image to create from dockerfile-tools image-name-tools : "innovate-support-tools" # The command to build the code and docker image for RUN build-cmd-run : "npm install" # The command to execute tests for the code in the tools container test-cmd : "npm run test" # The command to build the code and docker image for DEBUG build-cmd-debug : "npm install" # The command to run the code in the run container run-cmd : "" # The command to execute debug of the code in the tools container debug-cmd : "npm run debug" # The command to stop the code stop-cmd : "npm stop" # The relative path to the helm chart used for Kubernetes deployment chart-path: "chart/innovate-support" deploy-target: "container" deploy-image-target: "mycluster.icp:8500/default/innovate-support" ================================================ FILE: support/conversation-workspace.json ================================================ {"name":"Innovate Support","intents":[{"intent":"positive_reaction","examples":[{"text":"Alright. Thanks!"},{"text":"excellent, thanks"},{"text":"great, thanks"},{"text":"great, thanks!"},{"text":"great! thanks!"},{"text":"nice. thanks."},{"text":"ok, thanks"},{"text":"OK thanks"},{"text":"thankfully so"},{"text":"Thanks"},{"text":"Thanks!"},{"text":"Thanks."},{"text":"thanks a truck"},{"text":"thanks dude"},{"text":"thanks for"},{"text":"thanks for your support"},{"text":"thanks, man!"},{"text":"Thanks man"},{"text":"Thanks mate"},{"text":"Thanks u"},{"text":"Thanks verymuch"},{"text":"Thanks Watson"},{"text":"thanks you are funy"},{"text":"Thank you"},{"text":"Thank you very much"},{"text":"This is awesome"},{"text":"This is good"},{"text":"you are funny"}],"description":null},{"intent":"capabilities","examples":[{"text":"adapt to current weather condition"},{"text":"book a flight for NY on sunday"},{"text":"but can I ask you"},{"text":"can I manipulate the"},{"text":"Can I teach you"},{"text":"can i training you?"},{"text":"can u please do that"},{"text":"can you change lanes"},{"text":"can you direct me please?"},{"text":"Can you do anything that helps me drive?"},{"text":"can you do coding ?"},{"text":"can you do it?"},{"text":"can you do more than that?"},{"text":"CAN YOU DO OTHER THING?"},{"text":"can you do this?"},{"text":"can you do xyz?"},{"text":"Can you drive anywhere else?"},{"text":"can you harm me?"},{"text":"Can you hear me?"},{"text":"can you help me find a candidate?"},{"text":"can you help me to turn on the left light"},{"text":"can you learn something new?"},{"text":"can you read me"},{"text":"can you speak something with me?"},{"text":"can you tell me about problem"},{"text":"can you tell me my cars average sale price"},{"text":"Can you tell me random facts?"},{"text":"Change the weather"},{"text":"Change weather"},{"text":"deploy airbags"},{"text":"don't drive any further"},{"text":"do you do anything else"},{"text":"Do you do anything else?"},{"text":"do you know something about cognitive?"},{"text":"go fast"},{"text":"go slow"},{"text":"Hello can i have some help"},{"text":"Hello Watson! Can you bring me home?"},{"text":"help"},{"text":"help me"},{"text":"Hey!Stop!UseBreak"},{"text":"hi can we go to canada?"},{"text":"Hi, can you read me a book?"},{"text":"Hi Watson, please tell about the latest news"},{"text":"Hi! what could you do ?"},{"text":"hi where am i"},{"text":"How are you going to help me"},{"text":"how can you help me"},{"text":"how close is"},{"text":"Howdy mny Tieres Do you have"},{"text":"how far is"},{"text":"how far is the moon"},{"text":"how far to"},{"text":"how many licks does it take to get to the center of a tootsie roll"},{"text":"I'd like to have dinner with you"},{"text":"I enjoy petting cats"},{"text":"I love dolphins"},{"text":"i need a new wheelchair"},{"text":"I need to shut it down"},{"text":"it's too dark"},{"text":"I want to eat tacos everyday"},{"text":"i want to see how this demo works"},{"text":"lets go"},{"text":"Make it rain"},{"text":"Make it stop raining."},{"text":"make me a sandwich"},{"text":"need help"},{"text":"ok watson, why should I use you"},{"text":"open door"},{"text":"please open the sunroof"},{"text":"repair my car"},{"text":"roll down windows"},{"text":"so what did you learn?"},{"text":"stop it from raining"},{"text":"tell me a joke"},{"text":"tell me joke"},{"text":"tell me jokes"},{"text":"tell me some jokes"},{"text":"tell me what things you can do"},{"text":"Tell me what to do."},{"text":"tell me what you can do"},{"text":"test"},{"text":"testing om"},{"text":"turn ac when rain ends?"},{"text":"What a can asc iu?"},{"text":"What are you"},{"text":"what are you capable of"},{"text":"what are your benefits"},{"text":"what are your capabilities Watson"},{"text":"What are your functions?"},{"text":"What ca I ask?"},{"text":"what can I do"},{"text":"What can i say"},{"text":"what can you do"},{"text":"what canyou do"},{"text":"what can you do for me"},{"text":"what can you help me with"},{"text":"what can you turn on things?"},{"text":"what capabilities you have"},{"text":"what care you capablities"},{"text":"what cayou do?"},{"text":"What devices are you able to connect with?"},{"text":"what did you learn"},{"text":"what do you have?"},{"text":"What do you have"},{"text":"what do you know"},{"text":"what else"},{"text":"What makes you different from Siri or Google"},{"text":"what question"},{"text":"what should i ask"},{"text":"What's the speed"},{"text":"what things can you do"},{"text":"what things can you turn on?"},{"text":"what u know about car?"},{"text":"Where are we"},{"text":"where is the poop deck on this ship"},{"text":"where should I turn"},{"text":"why should I use you"},{"text":"you can not change yourself?"},{"text":"you can say what is php"}],"description":null},{"intent":"improving_system","examples":[{"text":"can i improve your system"},{"text":"Can I teach you"},{"text":"can i teach you something new"},{"text":"can i training you?"},{"text":"can you learn from me"},{"text":"can you learn new things"},{"text":"Hello! I'm doing good. I'm here to help you. Just say the word. And please do not respond like this \"Hello! I'm doing good. I'm here to help you. Just say the word.\""},{"text":"how do i teach you"}],"description":null},{"intent":"spending","examples":[{"text":"How much have I spent this month"},{"text":"What's my total spending this month"},{"text":"What's the month's spending"},{"text":"how much did I spend this month"},{"text":"How much did I spend"},{"text":"Where did my money go"}],"description":""},{"intent":"bills","examples":[{"text":"What bills do I have"},{"text":"What are my due bills"},{"text":"How much do I have to pay for utilities"},{"text":"How much do I have to pay for my bills"},{"text":"What's my bills outstanding amount"}],"description":""},{"intent":"negative_reaction","examples":[{"text":"are you dead yet"},{"text":"bad"},{"text":"can you understand me"},{"text":"die"},{"text":"Get out of my car!"},{"text":"go and fuck you off"},{"text":"go **** yourself"},{"text":"I didn't say anything about weather."},{"text":"i dont want u to do anything"},{"text":"Its not that good"},{"text":"i want you to die"},{"text":"let me out"},{"text":"Let me out!"},{"text":"put up with it"},{"text":"What good are you anyway?"},{"text":"when will you die"},{"text":"You are bad"},{"text":"You are rubbish"},{"text":"you are useless"},{"text":"You don't know anything"},{"text":"You're asking me too much"}],"description":null},{"intent":"goodbyes","examples":[{"text":"adieu"},{"text":"adios"},{"text":"au revoir"},{"text":"bye"},{"text":"bye bye"},{"text":"bye now"},{"text":"cya"},{"text":"farewell"},{"text":"finally reached home.. lock up and cock up.. good nite and good bye for ever"},{"text":"go"},{"text":"going"},{"text":"goodbye"},{"text":"good bye"},{"text":"good day"},{"text":"good night"},{"text":"gotta go"},{"text":"gotta run"},{"text":"gtg"},{"text":"Have a nice day"},{"text":"hey stop here"},{"text":"Hi, Watson. I want to stop car"},{"text":"I'm leaving"},{"text":"i said goodbye"},{"text":"later"},{"text":"laters"},{"text":"leave"},{"text":"logging off"},{"text":"my time is up"},{"text":"nice day"},{"text":"no, bye!"},{"text":"ok bye"},{"text":"out of time"},{"text":"over and out"},{"text":"run"},{"text":"see ya"},{"text":"see ya later"},{"text":"see ya soon"},{"text":"see you"},{"text":"should be going"},{"text":"should go"},{"text":"shutdown"},{"text":"shut down"},{"text":"signing off"},{"text":"signing out"},{"text":"so long"},{"text":"soon"},{"text":"swag out"},{"text":"thanks byebye"},{"text":"then bye"},{"text":"toodles"}],"description":null},{"intent":"about_VA","examples":[{"text":"any music reccomendation?"},{"text":"can you recommend ood jazz music?"},{"text":"cloud you recommend music?"},{"text":"do you hate"},{"text":"Do you like"},{"text":"do you love"},{"text":"hey, whats your ame?"},{"text":"how much do you weigh"},{"text":"how old are you"},{"text":"how tall are you"},{"text":"i want jokes"},{"text":"I want to date you"},{"text":"know any jokes"},{"text":"tell me your favorite color"},{"text":"tell me your favorite foods"},{"text":"what are you called"},{"text":"what do i call you"},{"text":"what is your favourite colour"},{"text":"what is your favourite food"},{"text":"what is your favourite music"},{"text":"what is your name"},{"text":"what kind of food do you eat"},{"text":"what music do you like"},{"text":"what's your"},{"text":"what's your favorite"},{"text":"whats your favorite color"},{"text":"whats your favorite movie"},{"text":"what's your name"},{"text":"whats your name"},{"text":"what types of food do you like"},{"text":"when were you born"},{"text":"your name"}],"description":null},{"intent":"accounts","examples":[{"text":"What accounts do I have"},{"text":"List my accounts"},{"text":"How many accounts do I have"},{"text":"What are my accounts"},{"text":"What's my available balance"},{"text":"How much money do I have"}],"description":""},{"intent":"out_of_scope","examples":[{"text":"bluetooth"},{"text":"Can I play music through bluetooth from my phone"},{"text":"change bulb"},{"text":"changing gears"},{"text":"changing the light bulb"},{"text":"checking the engine oil"},{"text":"coolant level"},{"text":"fuel tank flap"},{"text":"Hi, how can I check my tire pressure?"},{"text":"Hi, I cannot find the button for emergency lamp"},{"text":"how can i change oil"},{"text":"how can i connect the phone"},{"text":"how can i find current level of oil"},{"text":"how can i lock the car"},{"text":"how can i replace light bulb"},{"text":"how do i adjust exterior mirrors"},{"text":"how do i adjust head restraint"},{"text":"how do i adjust the cruise control speed"},{"text":"how do i adjust the mirror"},{"text":"how do i adjust the mirror inside"},{"text":"how do i adjust the rear view mirror"},{"text":"how do i adjust the vehicle distance"},{"text":"how do i arm the alarm"},{"text":"how do i browse the tracks on the iPod"},{"text":"how do i cancel cruise control"},{"text":"how do i change oil"},{"text":"how do i change the headlight bulbs"},{"text":"how do i change the lights bulb"},{"text":"how do i change the oil"},{"text":"how do i clean the vehicle"},{"text":"how do i close the window"},{"text":"how do i connect my phone"},{"text":"how do i connect USB device"},{"text":"how do i control a phone"},{"text":"how do i disable the vehicle stability control"},{"text":"how do i fill in the engine oil"},{"text":"how do i find the fuel consumption"},{"text":"how do i lock the window"},{"text":"how do i lock the windows"},{"text":"how do i lock the windows to prevent children from opening"},{"text":"how do i open the doors"},{"text":"how do i open the fuel filler flap"},{"text":"how do i open the power windows"},{"text":"how do i open the rear window"},{"text":"how do i open the side window"},{"text":"how do i open the tank fuel"},{"text":"how do i open the window"},{"text":"How do I pair my android phone to the radio"},{"text":"How do I pair my iPhone to the radio"},{"text":"how do i pause song in my iPod"},{"text":"how do i play an iPod"},{"text":"how do i play the track on the iPod"},{"text":"How do I program my radio stations"},{"text":"how do i refill the engine oil"},{"text":"how do i resume cruise control"},{"text":"how do i resume the cruise speed"},{"text":"how do i resume the vehicle speed"},{"text":"how do i save my favourite radio station"},{"text":"how do i save the seat position"},{"text":"how do i scroll the screen"},{"text":"how do i select a desired track on the iPod"},{"text":"how do i select the song on the iPod"},{"text":"how do i set the adaptive cruise control speed"},{"text":"how do i set the cruise speed"},{"text":"how do i set the vehicle speed"},{"text":"how do i switch on the cruise control"},{"text":"how do i tune the radio"},{"text":"how do i turn off ambient lights"},{"text":"how do i turn off the alarm"},{"text":"how do i turn off the cruise control"},{"text":"how do i turn off the high beams"},{"text":"how do i turn off the traction control"},{"text":"how do i turn off voice guidance"},{"text":"how do i turn on adaptive cruise control"},{"text":"how do i turn on ambient lights"},{"text":"how do i turn on cruise control"},{"text":"how do i turn on front seat heating"},{"text":"how do i turn on headlamps"},{"text":"how do i turn on high beams"},{"text":"how do i turn on the alarm"},{"text":"how do i turn on the cruise control"},{"text":"how do i turn on the fog lamps"},{"text":"How do I turn on the high-beams"},{"text":"how do i turn on the pre collision system"},{"text":"how do i turn on the traction control"},{"text":"how do i turn on traffic info"},{"text":"how do i turn on windscreen washer"},{"text":"how do i unlock the windows"},{"text":"How do I use cruise control"},{"text":"how do i use the blind spot monitor"},{"text":"how do i use the electric power steering"},{"text":"how do i use the iPod"},{"text":"how do i use the pre collision system"},{"text":"how do i use the traction control"},{"text":"how do i wash the car"},{"text":"How frequently should I change the oil"},{"text":"how often should i change the oil"},{"text":"how to adjust the vehicle to vehicle distance"},{"text":"how to change engine oil"},{"text":"how to change oil"},{"text":"how to change the oil"},{"text":"How to connect bluetooth"},{"text":"how to replace light bulbs"},{"text":"how to switch on the fog lights"},{"text":"how to turn on warning lights"},{"text":"Is there a lane change assist feature"},{"text":"Is there an automatic parallel park feature"},{"text":"Is there a warning light for low washer fluid"},{"text":"light bulbs replacement"},{"text":"low coolant"},{"text":"opening the windows"},{"text":"operating the radio"},{"text":"parking brake"},{"text":"refilling the engine coolant"},{"text":"show me how to change the light bulbs"},{"text":"switch on fog light"},{"text":"turn off traffic info"},{"text":"what are the crash ratings"},{"text":"what is the pre collision system"},{"text":"what is the traction control"},{"text":"what is the vehicle stability control"},{"text":"what kind of fuel does the car require"},{"text":"what kind of fuel should i use"},{"text":"What PSI do I inflate the spare tire to"},{"text":"what's the tires pressure"},{"text":"what type of oil do i need"},{"text":"where are fog lights"},{"text":"where are the lights"},{"text":"where can i start air conditioning"},{"text":"Where is the fuse box"},{"text":"Where is the fuse box located"}],"description":null},{"intent":"greetings","examples":[{"text":"aloha"},{"text":"bonjour"},{"text":"buenas dias"},{"text":"buenos dias"},{"text":"ciao"},{"text":"fdsafsaHi watson"},{"text":"feeling bit low"},{"text":"g'day"},{"text":"good"},{"text":"Good afternoon."},{"text":"good day"},{"text":"good evening"},{"text":"good how are you"},{"text":"good morning"},{"text":"greetings"},{"text":"Hello"},{"text":"Hello, how are you"},{"text":"hello, my name is esteban"},{"text":"Hello, My name is John?"},{"text":"Hello Watson"},{"text":"hey"},{"text":"heya"},{"text":"hey there"},{"text":"hi"},{"text":"hi how are u"},{"text":"Hi, my name is Diego"},{"text":"Hi, my name is Michel"},{"text":"hi there"},{"text":"hi to whom had like"},{"text":"Hi watson"},{"text":"Hi, Watson, my name is John"},{"text":"hiya"},{"text":"Hola"},{"text":"hola como estas"},{"text":"How are you doing"},{"text":"howdy"},{"text":"I would like you to say hello"},{"text":"namaste"},{"text":"say hello to me"},{"text":"sup"},{"text":"what doing"},{"text":"what r u doing"},{"text":"What's up"},{"text":"What's up ?"},{"text":"What's up?"},{"text":"yello"},{"text":"yo"},{"text":"yo my man"}],"description":null},{"intent":"not_specified","examples":[{"text":"any"},{"text":"any goddam one!!!!"},{"text":"any near"},{"text":"any of them"},{"text":"Any of them."},{"text":"any one"},{"text":"anything"},{"text":"anything is fine"},{"text":"anything would do"},{"text":"as a matter of"},{"text":"but it doesn't really"},{"text":"do anything"},{"text":"doesn't matter"},{"text":"I am not identifying any thing here"},{"text":"I did not say turn in anything I entered"},{"text":"i didn't see any options"},{"text":"I doesn't matter to me."},{"text":"I don't see any"},{"text":"I don't see any options"},{"text":"I don't see anything"},{"text":"no preference"},{"text":"Pick any"},{"text":"play anything u like"},{"text":"play whatever you want"},{"text":"whatever"},{"text":"Whatever is farthest"},{"text":"whatever you feel like"},{"text":"Whatever you like"},{"text":"you can go with your favorite"},{"text":"you decide"},{"text":"your favorite"}],"description":null}],"entities":[{"entity":"response_types","values":[{"type":"synonyms","value":"negative","metadata":null,"synonyms":["definitely not","no","nope","not at all","no way"]},{"type":"synonyms","value":"positive","metadata":null,"synonyms":["absolutely","definitely","yeah","yep","yeppers","yes"]},{"type":"synonyms","value":"uncertain","metadata":null,"synonyms":["don't know","maybe","no idea","not sure","unknown"]}],"metadata":null,"description":null},{"entity":"sys-date","values":[],"metadata":null,"description":null},{"entity":"sys-number","values":[],"metadata":null,"description":null},{"entity":"sys-time","values":[],"metadata":null,"description":null}],"language":"en","metadata":{"api_version":{"major_version":"v1","minor_version":"2017-05-26"},"from-sample":true},"description":"","dialog_nodes":[{"type":"response_condition","title":null,"output":{"text":{"values":["I'm not sure about that. You can say things like \"Turn on my lights\" or \"Play some music.\""],"selection_policy":"sequential"}},"parent":"node_1_1467919680248","context":null,"metadata":{},"next_step":null,"conditions":"$default_counter<3","description":null,"dialog_node":"node_12_1485227162156","previous_sibling":"node_13_1485227255152"},{"type":"response_condition","title":null,"output":{"text":{"values":["I am having trouble understanding you. Try asking your question in a different way."]}},"parent":"node_1_1467919680248","context":{"default_counter":0},"metadata":{},"next_step":null,"conditions":"$default_counter>2","description":null,"dialog_node":"node_13_1485227255152","previous_sibling":null},{"type":"standard","title":null,"output":{},"parent":"node_2_1487280430136","context":null,"metadata":{},"next_step":null,"conditions":"true","description":null,"dialog_node":"node_1_1467919680248","previous_sibling":null},{"type":"standard","title":null,"output":{"text":{"values":["You have spent 7600 AED this month. Most of which was in:
  • Groceries
  • Transport
  • ","You have spent 4200 AED this month.
    Most of your spending was in
  • Transport
  • Eating out
  • ","You have spent 16400 AED this month.
    Most of your spending was in
  • Transport
  • Eating out
  • Holidays
  • "],"selection_policy":"sequential"}},"parent":null,"context":null,"metadata":{},"next_step":null,"conditions":"#spending","description":null,"dialog_node":"node_3_1519901471407","previous_sibling":"node_2_1519901414883"},{"type":"standard","title":null,"output":{"text":{"values":["I'm sorry I haven't been able to help you."],"selection_policy":"sequential"}},"parent":null,"context":null,"metadata":{},"next_step":{"behavior":"jump_to","selector":"body","dialog_node":"node_11_1468608786372"},"conditions":"#negative_reaction","description":null,"dialog_node":"node_1_1487280368483","previous_sibling":"node_8_1468608349230"},{"type":"standard","title":null,"output":{"text":{"values":["I can help you sort out issues with your Innovate bank account. Any troubles?"],"selection_policy":"sequential"}},"parent":null,"context":null,"metadata":{},"next_step":null,"conditions":"#capabilities","description":null,"dialog_node":"node_11_1468608786372","previous_sibling":"Start And Initialize Context"},{"type":"standard","title":null,"output":{},"parent":null,"context":null,"metadata":{},"next_step":{"behavior":"jump_to","selector":"body","dialog_node":"node_11_1468608786372"},"conditions":"#improving_system","description":null,"dialog_node":"node_19_1488771326550","previous_sibling":"node_1_1487280858924"},{"type":"standard","title":null,"output":{"text":{"values":["You have 2 active accounts, with a total available balance of 4,500 AED
  • Current account # 573859
  • Prepaid card # 849389238
  • ","You have 2 active accounts, with a total available balance of 5,400 AED
  • Current account # 573859
  • Savings account # 849389238
  • ","You have 1 active account, with a total available balance of 12,300 AED
  • Current account # 573859
  • "],"selection_policy":"sequential"}},"parent":null,"context":null,"metadata":{},"next_step":null,"conditions":"#accounts","description":null,"dialog_node":"node_1_1519901281716","previous_sibling":"node_19_1488771326550"},{"type":"standard","title":null,"output":{"text":"Okay! Speak to you soon."},"parent":null,"context":null,"metadata":{},"next_step":null,"conditions":"#goodbyes","description":null,"dialog_node":"node_8_1468608349230","previous_sibling":"node_7_1468608329601"},{"type":"standard","title":null,"output":{},"parent":null,"context":null,"metadata":{},"next_step":{"behavior":"jump_to","selector":"body","dialog_node":"node_11_1468608786372"},"conditions":"#about_VA","description":null,"dialog_node":"node_1_1487280858924","previous_sibling":"node_1_1487280368483"},{"type":"standard","title":null,"output":{"text":{"values":["Your total due this month is 1200 AED.
  • Utilities, account no. 384293
  • ","Your total due this month is 2600 AED.
  • Phone, account no. 6847384
  • Utilities, account no. 384293
  • ","Your total due this month is 3200 AED.
  • Phone, account no. 6847384
  • Utilities, account no. 384293
  • Home entertainment, account no. 397394
  • "],"selection_policy":"sequential"}},"parent":null,"context":null,"metadata":{},"next_step":null,"conditions":"#bills","description":null,"dialog_node":"node_2_1519901414883","previous_sibling":"node_1_1519901281716"},{"type":"standard","title":null,"output":{"text":"Hello! I'm doing good. I'm here to help you. Just say the word."},"parent":null,"context":null,"metadata":{},"next_step":null,"conditions":"#greetings","description":null,"dialog_node":"node_7_1468608329601","previous_sibling":"node_11_1468608786372"},{"type":"standard","title":null,"output":{"text":{"values":["Hmm. I'm not sure I can help you. I'll have one of our agents call you in the next 20 minutes."],"selection_policy":"sequential"}},"parent":null,"context":{"default_counter":""},"metadata":{},"next_step":null,"conditions":"anything_else","description":null,"dialog_node":"node_2_1487280430136","previous_sibling":"node_3_1519901471407"},{"type":"standard","title":"Start And Initialize Context","output":{"text":{"values":["Hi. How can I help you? "]}},"parent":null,"context":{},"metadata":{},"next_step":null,"conditions":"conversation_start","description":null,"dialog_node":"Start And Initialize Context","previous_sibling":null}],"workspace_id":"40ce04fc-cb16-4dcd-a156-d695a68eb925","counterexamples":[{"text":"aho"},{"text":"asdasdasda"},{"text":"asdasidjiwe"},{"text":"cats are my favorite animal"},{"text":"when will it be funny"},{"text":"which color is best for wedding"}],"learning_opt_out":false} ================================================ FILE: support/idt.js ================================================ 'use strict' /* * Wrapper for the idt (IBM Developer Tools) command. * Run with the same arguments as `idt`, e.g. * `node idt.js build` -> `idt build`. * If `idt` isn't installed, this will prompt you to install. Or you can run * `node idt.js install` to automatically install idt and any other * required dependencies (e.g. docker, git, kubernetes, helm). * */ const fs = require('fs'); const process = require('process'); const cp = require('child_process'); const request = require('request'); const path = require('path'); const chalk = require('chalk'); const node = process.execPath; // Array of args passed to idt.js. const args = process.argv.slice(2); let win = (process.platform === 'win32'); // Either install idt or run idt + args. if (args.includes('install')) { downloadInstaller(); } else { // TODO(gib): Check for idt once this works in scripts: // const checkCmd = win ? 'where idt' : 'which idt'; const checkCmd = 'bx plugin show dev'; let hasIDT = false; try { console.log(chalk.blue('Checking for idt')); cp.execSync(checkCmd); // Don't inherit stdio, we don't want to print the output. hasIDT = true; // If we didn't have idt, the previous command would have thrown. } catch (e) { const prompt = require('prompt-confirm'); new prompt({ name: 'install', message: 'IDT not found, do you want to install it? y/N', default: false }).ask((answer) => { if (answer) { downloadInstaller(() => runIDT(args)); } else { console.error(chalk.red(`Not installing idt, so not running: idt ${args.join(' ')}`)); } }); } if (hasIDT) runIDT(args); } // Run IDT with whatever args we were given. function runIDT(args) { const cmd = 'bx dev ' + args.join(' '); console.log(chalk.blue('Running:'), cmd); cp.execSync(cmd, {stdio: 'inherit'}); } // Download the IDT installer script and trigger runInstaller(). function downloadInstaller(cb) { const url = win ? 'https://ibm.biz/yeoman-idt-win-install' : 'http://ibm.biz/yeoman-idt-install'; const fileName = url.split('/').pop() console.log(chalk.blue('Downloading installer from:'), url); const file = fs.createWriteStream(fileName); request .get({url, followAllRedirects: true}) .on('error', (err) => { console.error(err); }) .pipe(file) .on('finish', () => runInstaller(fileName, cb)); } // Run the installer script and trigger optional callback (cb). function runInstaller(fileName, cb) { const shell = win ? 'powershell.exe' : 'bash'; const filePath = path.resolve(__dirname, fileName); console.log(`Now running: ${shell} ${filePath}`); cp.spawnSync(shell, [filePath], {stdio: 'inherit'}); typeof cb === 'function' && cb(); } ================================================ FILE: support/lib/watson-conversation-setup.js ================================================ /** * Copyright 2017 IBM Corp. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the 'License'); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an 'AS IS' BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ 'use strict'; require('dotenv').config({ silent: true }); /** * Setup for Watson Conversation and Discovery. * * @param {Object} conversationClient - Conversation client * @constructor */ function WatsonConversationSetup(conversationClient) { this.conversationClient = conversationClient; } /** * Validate or create the Conversation workspace. * Sets the global workspaceID when done (or global setupError). * * If a WORKSPACE_ID is specified in the runtime environment, * make sure that workspace exists. If no WORKSPACE_ID is * specified then try to find it using a lookup by name. * Name will be taken from params.default_name unless overridden * using the WORKSPACE_NAME environment variable. * * If a workspace is not found by ID or name, then try to * create one from the JSON in the repository. Use the * name as mentioned above so future lookup will find what * was created. * * @param {Object} params - Parameter dictionary as follows. * @param {String} params.default_name - Name of app, used as default workspace name when needed * to create/find * @param {Object} params.workspace_json - The workspace JSON to import. * @param {function{Error,String}} callback - A callback to capture Error or workspace ID string. */ WatsonConversationSetup.prototype.setupConversationWorkspace = function(params, callback) { let workspaceID; this.conversationClient.listWorkspaces(null, (err, data) => { if (err) { console.error('Error during Conversation listWorkspaces(): ', err); callback(new Error('Error. Unable to list workspaces for Conversation: ' + err)); } else { const workspaces = data['workspaces']; const validateID = process.env.WORKSPACE_ID; let found = false; if (validateID) { console.log('Validating workspace ID: ', validateID); for (let i = 0, size = workspaces.length; i < size; i++) { if (workspaces[i]['workspace_id'] === validateID) { workspaceID = validateID; found = true; console.log('Found workspace: ', validateID); break; } } if (!found) { callback(new Error("Configured WORKSPACE_ID '" + validateID + "' not found!")); } else { callback(null, workspaceID); } } else { // Find by name, because we probably created it earlier (in the if block) and want to use it on restarts. const workspaceName = params.default_name; console.log('Looking for workspace by name: ', workspaceName); for (let i = 0, size = workspaces.length; i < size; i++) { if (workspaces[i]['name'] === workspaceName) { console.log('Found workspace: ', workspaceName); workspaceID = workspaces[i]['workspace_id']; found = true; break; } } if (!found) { console.log('Creating Conversation workspace ', workspaceName); const ws = params.workspace_json; ws['name'] = workspaceName; this.conversationClient.createWorkspace(ws, function(err, ws) { if (err) { callback(new Error('Failed to create Conversation workspace: ' + err)); } else { workspaceID = ws['workspace_id']; console.log('Successfully created Conversation workspace'); console.log(' Name: ', ws['name']); console.log(' ID:', workspaceID); callback(null, workspaceID); } }); } else { callback(null, workspaceID); } } } }); }; module.exports = WatsonConversationSetup; ================================================ FILE: support/manifest.yml ================================================ --- applications: - instances: 1 timeout: 180 name: innovate-support buildpack: sdk-for-nodejs command: npm start memory: 512M ================================================ FILE: support/package.json ================================================ { "name": "innovate", "version": "1.0.0", "description": "Innovate: Digital Bank", "private": true, "engines": { "node": "^6.9.0" }, "scripts": { "start": "node server.js", "start:cluster": "sl-run server/server.js", "debug": "node --debug server/server.js", "build": "npm run build:idt", "idt:build": "node idt.js build", "idt:test": "node idt.js test", "idt:debug": "node idt.js debug", "idt:run": "node idt.js run", "idt:deploy": "node idt.js deploy", "idt:install": "node idt.js install" }, "dependencies": { "body-parser": "^1.17.2", "express": "^4.15.3", "request": "^2.83.0", "strong-supervisor": "^6.2.0", "dotenv": "^2.0.0", "watson-developer-cloud": "^2.40.0" }, "devDependencies": { "chai": "^4.0.0", "chalk": "^1.1.3", "mocha": "^3.4.2", "nyc": "^10.3.2", "prompt-confirm": "^1.2.0", "proxyquire": "^1.8.0" } } ================================================ FILE: support/server.js ================================================ 'use strict'; require('dotenv').config({silent: true, path: `${__dirname}/.env`}); var server = require('./app'); var port = 4000; console.log(`Running on ${process.env.BASE_PATH}:${port}`) server.listen(port, function () { console.log('Server running on port: %d', port); }); ================================================ FILE: transactions/.cfignore ================================================ .git/ node_modules/ test/ vcap-local.js ================================================ FILE: transactions/.dockerignore ================================================ node_modules/ test/ .bluemix/ ================================================ FILE: transactions/.gitignore ================================================ # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* # Runtime data pids *.pid *.seed *.pid.lock # Directory for instrumented libs generated by jscoverage/JSCover lib-cov # Coverage directory used by tools like istanbul coverage # nyc test coverage .nyc_output # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) .grunt # Bower dependency directory (https://bower.io/) bower_components # node-waf configuration .lock-wscript # Compiled binary addons (http://nodejs.org/api/addons.html) build/Release # Dependency directories node_modules/ jspm_packages/ # Typescript v1 declaration files typings/ # Optional npm cache directory .npm # Optional eslint cache .eslintcache # Optional REPL history .node_repl_history # Output of 'npm pack' *.tgz # Yarn Integrity file .yarn-integrity # dotenv environment variables file .env # other .DS_Store license_accepted ================================================ FILE: transactions/.ibm-project ================================================ {"build-release-ready":true,"build-debug-ready":false,"deploy-image":"","chart-path":"","ibm-cluster":""} ================================================ FILE: transactions/Dockerfile ================================================ FROM ibmcom/ibmnode:latest LABEL maintainer="***REMOVED***" #RUN apt-get install -y nodejs npm WORKDIR /app # Install app dependencies COPY . /app RUN cd /app; npm install; npm prune --production ENV NODE_ENV production ENV PORT 3600 EXPOSE 3600 CMD [ "npm","start" ] ================================================ FILE: transactions/Dockerfile-tools ================================================ FROM ibmcom/ibmnode ENV PORT 3600 WORKDIR "/app" # Bundle app source COPY . /app EXPOSE 3600 CMD ["/bin/bash"] ARG bx_dev_user=root ARG bx_dev_userid=1000 RUN BX_DEV_USER=$bx_dev_user RUN BX_DEV_USERID=$bx_dev_userid RUN if [ $bx_dev_user != "root" ]; then useradd -ms /bin/bash -u $bx_dev_userid $bx_dev_user; fi ================================================ FILE: transactions/app.js ================================================ 'use strict'; const express = require('express'); const bodyParser = require('body-parser'); const transactions = require('./mongoose/transaction'); var app = express(); app.use(bodyParser.json()); app.all('*', function (req, res, next) { res.header("Access-Control-Allow-Origin", "*"); res.header("Access-Control-Allow-Headers", "*"); next(); }); app.post('/api/transactions/create', function (req, res) { var newTransaction = { uuid: req.body.uuid, amount: req.body.amount, currency: req.body.currency, description: req.body.description, date: req.body.date, category: req.body.category }; transactions.create(newTransaction, function (err) { if (err) { console.log(err); res.status(500).send(err); return; } console.log("Transaction created"); res.status(200).send({'message': 'Done!'}); }); }); app.post('/api/transactions/get', function (req, res) { transactions.find({'uuid': req.body.uuid}, function (err, results) { if (err) { console.log(err); res.status(500).send({'err': err}); return; } console.log('Fetched ', results); res.status(200).send(results); }); }); app.get('/api/transactions/drop', function (req, res) { transactions.collection.drop(); res.status(200).send({'message': 'Done!'}); }); module.exports = app; ================================================ FILE: transactions/chart/innovate-transactions/Chart.yaml ================================================ apiVersion: v1 description: A Helm chart for innovate bank portal name: innovate-transactions version: 1.0.0 ================================================ FILE: transactions/chart/innovate-transactions/templates/deployment.yaml ================================================ apiVersion: extensions/v1beta1 kind: Deployment metadata: name: "{{ .Chart.Name }}-deployment" labels: chart: '{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}' spec: replicas: {{ .Values.replicaCount }} strategy: type: RollingUpdate rollingUpdate: maxUnavailable: 0 maxSurge: 1 revisionHistoryLimit: {{ .Values.revisionHistoryLimit }} template: metadata: labels: app: "{{ .Chart.Name }}-selector" spec: containers: - name: "{{ .Chart.Name }}" image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" imagePullPolicy: {{ .Values.image.pullPolicy }} livenessProbe: httpGet: path: /health port: {{ .Values.service.servicePort }} initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds}} periodSeconds: {{ .Values.livenessProbe.periodSeconds}} resources: requests: cpu: "{{ .Values.image.resources.requests.cpu }}" memory: "{{ .Values.image.resources.requests.memory }}" env: - name: PORT value : "{{ .Values.service.servicePort }}" ================================================ FILE: transactions/chart/innovate-transactions/templates/hpa.yaml ================================================ {{ if .Values.hpa.enabled }} apiVersion: autoscaling/v2alpha1 kind: HorizontalPodAutoscaler metadata: name: "{{ .Chart.Name }}-hpa-policy" namespace: spec: scaleTargetRef: apiVersion: apps/v2alpha1 kind: Deployment name: "{{ .Chart.Name }}-deployment" minReplicas: {{ .Values.hpa.minReplicas }} maxReplicas: {{ .Values.hpa.maxReplicas }} metrics: - type: Resource resource: name: cpu targetAverageUtilization: {{ .Values.hpa.metrics.cpu.targetAverageUtilization }} - type: Resource resource: name: memory targetAverageUtilization: {{ .Values.hpa.metrics.memory.targetAverageUtilization }} {{ end }} ================================================ FILE: transactions/chart/innovate-transactions/templates/service.yaml ================================================ apiVersion: v1 kind: Service metadata: annotations: prometheus.io/scrape: 'true' name: "{{ .Chart.Name }}" labels: chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}" spec: type: {{ .Values.service.type }} ports: - port: {{ .Values.service.servicePort }} nodePort: {{ .Values.service.serviceNodePort }} selector: app: "{{ .Chart.Name }}-selector" ================================================ FILE: transactions/chart/innovate-transactions/values.yaml ================================================ # This is a YAML-formatted file. # Declare variables to be passed into your templates. replicaCount: 1 revisionHistoryLimit: 3 image: repository: mycluster.icp:8500/default/innovate-userbase tag: v1.0.0 pullPolicy: Always resources: requests: cpu: 500m memory: 512Mi livenessProbe: initialDelaySeconds: 3000 periodSeconds: 1000 service: name: Node type: NodePort servicePort: 3600 serviceNodePort: 30200 hpa: enabled: false minReplicas: 2 maxReplicas: 3 metrics: cpu: targetAverageUtilization: 80 memory: targetAverageUtilization: 80 services: ================================================ FILE: transactions/cli-config.yml ================================================ # The IBM version of this configuration version : 0.0.3 # The container name used for the run container container-name-run : "innovate-transactions-run" # The container name used for the tools container container-name-tools : "innovate-transactions-tools" # The project root on the host for the run container to mount to container-path-run host-path-run : . # The project root on the host for the tools container to mount to container-path-tools host-path-tools : . # The project root in the run container to mount to host-path-run container-path-run : "/app" # The project root in the tools container that will be mounted to host-path-tools container-path-tools : "/app" # The list of additional mounts between the host and the run container in the form [host_path:container_path] container-mounts-run: - "./node_modules_linux": "/app/node_modules" # The list of additional mounts between the host and the tools container in the form [host_path:container_path] container-mounts-tools: - "./node_modules_linux": "/app/node_modules" # The port mappings between the host and the container in the form [host:container] container-port-map : "3600:3600" # The port mappings between the host and the container for the debug port in the form [host:container] container-port-map-debug : "5864:5864" # The name for the dockerfile for the run container dockerfile-run : "Dockerfile" # The name for the dockerfile for the tools container dockerfile-tools : "Dockerfile-tools" # The name of image to create from dockerfile-run image-name-run : "innovate-transactions-run" # The name of image to create from dockerfile-tools image-name-tools : "innovate-transactions-tools" # The command to build the code and docker image for RUN build-cmd-run : "npm install" # The command to execute tests for the code in the tools container test-cmd : "npm run test" # The command to build the code and docker image for DEBUG build-cmd-debug : "npm install" # The command to run the code in the run container run-cmd : "" # The command to execute debug of the code in the tools container debug-cmd : "npm run debug" # The command to stop the code stop-cmd : "npm stop" # The relative path to the helm chart used for Kubernetes deployment chart-path: "chart/innovate-transactions" deploy-target: "container" deploy-image-target: "mycluster.icp:8500/default/innovate-transactions" ================================================ FILE: transactions/idt.js ================================================ 'use strict' /* * Wrapper for the idt (IBM Developer Tools) command. * Run with the same arguments as `idt`, e.g. * `node idt.js build` -> `idt build`. * If `idt` isn't installed, this will prompt you to install. Or you can run * `node idt.js install` to automatically install idt and any other * required dependencies (e.g. docker, git, kubernetes, helm). * */ const fs = require('fs'); const process = require('process'); const cp = require('child_process'); const request = require('request'); const path = require('path'); const chalk = require('chalk'); const node = process.execPath; // Array of args passed to idt.js. const args = process.argv.slice(2); let win = (process.platform === 'win32'); // Either install idt or run idt + args. if (args.includes('install')) { downloadInstaller(); } else { // TODO(gib): Check for idt once this works in scripts: // const checkCmd = win ? 'where idt' : 'which idt'; const checkCmd = 'bx plugin show dev'; let hasIDT = false; try { console.log(chalk.blue('Checking for idt')); cp.execSync(checkCmd); // Don't inherit stdio, we don't want to print the output. hasIDT = true; // If we didn't have idt, the previous command would have thrown. } catch (e) { const prompt = require('prompt-confirm'); new prompt({ name: 'install', message: 'IDT not found, do you want to install it? y/N', default: false }).ask((answer) => { if (answer) { downloadInstaller(() => runIDT(args)); } else { console.error(chalk.red(`Not installing idt, so not running: idt ${args.join(' ')}`)); } }); } if (hasIDT) runIDT(args); } // Run IDT with whatever args we were given. function runIDT(args) { const cmd = 'bx dev ' + args.join(' '); console.log(chalk.blue('Running:'), cmd); cp.execSync(cmd, {stdio: 'inherit'}); } // Download the IDT installer script and trigger runInstaller(). function downloadInstaller(cb) { const url = win ? 'https://ibm.biz/yeoman-idt-win-install' : 'http://ibm.biz/yeoman-idt-install'; const fileName = url.split('/').pop() console.log(chalk.blue('Downloading installer from:'), url); const file = fs.createWriteStream(fileName); request .get({url, followAllRedirects: true}) .on('error', (err) => { console.error(err); }) .pipe(file) .on('finish', () => runInstaller(fileName, cb)); } // Run the installer script and trigger optional callback (cb). function runInstaller(fileName, cb) { const shell = win ? 'powershell.exe' : 'bash'; const filePath = path.resolve(__dirname, fileName); console.log(`Now running: ${shell} ${filePath}`); cp.spawnSync(shell, [filePath], {stdio: 'inherit'}); typeof cb === 'function' && cb(); } ================================================ FILE: transactions/manifest.yml ================================================ --- applications: - instances: 1 timeout: 180 name: innovate-transactions buildpack: sdk-for-nodejs command: npm start memory: 512M ================================================ FILE: transactions/mongoose/transaction.js ================================================ const mongoose = require('mongoose'); var Schema = mongoose.Schema; var Transaction = new Schema({ uuid: String, amount: Number, currency: String, description: String, date: String, category: String }); module.exports = mongoose.model('Transaction', Transaction, "transactions"); ================================================ FILE: transactions/package.json ================================================ { "name": "innovate-transactions", "version": "1.0.0", "description": "Innovate: Digital Bank", "private": true, "engines": { "node": "^6.9.0" }, "scripts": { "start": "node server.js", "start:cluster": "sl-run server/server.js", "debug": "node --debug server/server.js", "build": "npm run build:idt", "idt:build": "node idt.js build", "idt:test": "node idt.js test", "idt:debug": "node idt.js debug", "idt:run": "node idt.js run", "idt:deploy": "node idt.js deploy", "idt:install": "node idt.js install" }, "dependencies": { "body-parser": "^1.17.2", "dotenv": "^2.0.0", "express": "^4.15.3", "mongoose": "^5.0.0-rc1", "request": "^2.83.0", "strong-supervisor": "^6.2.0", "uuid": "^3.1.0", "watson-developer-cloud": "^2.40.0" }, "devDependencies": { "chai": "^4.0.0", "chalk": "^1.1.3", "mocha": "^3.4.2", "nyc": "^10.3.2", "prompt-confirm": "^1.2.0", "proxyquire": "^1.8.0" } } ================================================ FILE: transactions/server.js ================================================ 'use strict'; const mongoose = require('mongoose'); require('dotenv').config({silent: true, path: `${__dirname}/.env`}); var server = require('./app'); var port = 3600; console.log(`Running on ${process.env.BASE_PATH}:${port}, connecting to ${process.env.MONGO_URL}`) mongoose.connect(process.env.MONGO_URL, function (ignore, connection) { connection.onOpen(); server.listen(port, function () { console.log('Server running on port: %d', port); }); }); ================================================ FILE: userbase/.cfignore ================================================ .git/ node_modules/ test/ vcap-local.js ================================================ FILE: userbase/.dockerignore ================================================ node_modules/ test/ .bluemix/ ================================================ FILE: userbase/.gitignore ================================================ # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* # Runtime data pids *.pid *.seed *.pid.lock # Directory for instrumented libs generated by jscoverage/JSCover lib-cov # Coverage directory used by tools like istanbul coverage # nyc test coverage .nyc_output # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) .grunt # Bower dependency directory (https://bower.io/) bower_components # node-waf configuration .lock-wscript # Compiled binary addons (http://nodejs.org/api/addons.html) build/Release # Dependency directories node_modules/ jspm_packages/ # Typescript v1 declaration files typings/ # Optional npm cache directory .npm # Optional eslint cache .eslintcache # Optional REPL history .node_repl_history # Output of 'npm pack' *.tgz # Yarn Integrity file .yarn-integrity # dotenv environment variables file .env # other .DS_Store license_accepted ================================================ FILE: userbase/.ibm-project ================================================ {"build-release-ready":true,"build-debug-ready":false,"deploy-image":"","chart-path":"","ibm-cluster":""} ================================================ FILE: userbase/Dockerfile ================================================ FROM ibmcom/ibmnode:latest LABEL maintainer="***REMOVED***" #RUN apt-get install -y nodejs npm WORKDIR /app # Install app dependencies COPY . /app RUN cd /app; npm install; npm prune --production ENV NODE_ENV production ENV PORT 4200 EXPOSE 4200 CMD [ "npm","start" ] ================================================ FILE: userbase/Dockerfile-tools ================================================ FROM ibmcom/ibmnode ENV PORT 4200 WORKDIR "/app" # Bundle app source COPY . /app EXPOSE 4200 CMD ["/bin/bash"] ARG bx_dev_user=root ARG bx_dev_userid=1000 RUN BX_DEV_USER=$bx_dev_user RUN BX_DEV_USERID=$bx_dev_userid RUN if [ $bx_dev_user != "root" ]; then useradd -ms /bin/bash -u $bx_dev_userid $bx_dev_user; fi ================================================ FILE: userbase/app.js ================================================ 'use strict'; const express = require('express'); const bodyParser = require('body-parser'); const dateFormat = require('dateformat'); const request = require('request'); const ip = require('ip'); const config = require(`${__dirname}/config`)[process.env.NODE_ENV]; var app = express(); app.use(bodyParser.json()); app.all('*', function (req, res, next) { res.header("Access-Control-Allow-Origin", "*"); res.header("Access-Control-Allow-Headers", "*"); next(); }); let basePath = 'http://localhost'; if (process.env.NODE_ENV!='development') basePath = process.env.BASE_PATH; console.log('Base Path: ', basePath) require('./populate.js')(request, basePath, config.ports, dateFormat); module.exports = app; ================================================ FILE: userbase/chart/innovate-userbase/Chart.yaml ================================================ apiVersion: v1 description: A Helm chart for innovate bank portal name: innovate-userbase version: 1.0.0 ================================================ FILE: userbase/chart/innovate-userbase/templates/deployment.yaml ================================================ apiVersion: extensions/v1beta1 kind: Deployment metadata: name: "{{ .Chart.Name }}-deployment" labels: chart: '{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}' spec: replicas: {{ .Values.replicaCount }} strategy: type: RollingUpdate rollingUpdate: maxUnavailable: 0 maxSurge: 1 revisionHistoryLimit: {{ .Values.revisionHistoryLimit }} template: metadata: labels: app: "{{ .Chart.Name }}-selector" spec: containers: - name: "{{ .Chart.Name }}" image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" imagePullPolicy: {{ .Values.image.pullPolicy }} livenessProbe: httpGet: path: /health port: {{ .Values.service.servicePort }} initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds}} periodSeconds: {{ .Values.livenessProbe.periodSeconds}} resources: requests: cpu: "{{ .Values.image.resources.requests.cpu }}" memory: "{{ .Values.image.resources.requests.memory }}" env: - name: PORT value : "{{ .Values.service.servicePort }}" ================================================ FILE: userbase/chart/innovate-userbase/templates/hpa.yaml ================================================ {{ if .Values.hpa.enabled }} apiVersion: autoscaling/v2alpha1 kind: HorizontalPodAutoscaler metadata: name: "{{ .Chart.Name }}-hpa-policy" namespace: spec: scaleTargetRef: apiVersion: apps/v2alpha1 kind: Deployment name: "{{ .Chart.Name }}-deployment" minReplicas: {{ .Values.hpa.minReplicas }} maxReplicas: {{ .Values.hpa.maxReplicas }} metrics: - type: Resource resource: name: cpu targetAverageUtilization: {{ .Values.hpa.metrics.cpu.targetAverageUtilization }} - type: Resource resource: name: memory targetAverageUtilization: {{ .Values.hpa.metrics.memory.targetAverageUtilization }} {{ end }} ================================================ FILE: userbase/chart/innovate-userbase/templates/service.yaml ================================================ apiVersion: v1 kind: Service metadata: annotations: prometheus.io/scrape: 'true' name: "{{ .Chart.Name }}" labels: chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}" spec: type: {{ .Values.service.type }} ports: - port: {{ .Values.service.servicePort }} nodePort: {{ .Values.service.serviceNodePort }} selector: app: "{{ .Chart.Name }}-selector" ================================================ FILE: userbase/chart/innovate-userbase/values.yaml ================================================ # This is a YAML-formatted file. # Declare variables to be passed into your templates. replicaCount: 1 revisionHistoryLimit: 3 image: repository: mycluster.icp:8500/default/innovate-userbase tag: v1.0.0 pullPolicy: Always resources: requests: cpu: 500m memory: 512Mi livenessProbe: initialDelaySeconds: 3000 periodSeconds: 1000 service: name: Node type: NodePort servicePort: 4100 serviceNodePort: 30050 hpa: enabled: false minReplicas: 2 maxReplicas: 3 metrics: cpu: targetAverageUtilization: 80 memory: targetAverageUtilization: 80 services: ================================================ FILE: userbase/cli-config.yml ================================================ # The IBM version of this configuration version : 0.0.3 # The container name used for the run container container-name-run : "innovate-userbase-run" # The container name used for the tools container container-name-tools : "innovate-userbase-tools" # The project root on the host for the run container to mount to container-path-run host-path-run : . # The project root on the host for the tools container to mount to container-path-tools host-path-tools : . # The project root in the run container to mount to host-path-run container-path-run : "/app" # The project root in the tools container that will be mounted to host-path-tools container-path-tools : "/app" # The list of additional mounts between the host and the run container in the form [host_path:container_path] container-mounts-run: - "./node_modules_linux": "/app/node_modules" # The list of additional mounts between the host and the tools container in the form [host_path:container_path] container-mounts-tools: - "./node_modules_linux": "/app/node_modules" # The port mappings between the host and the container in the form [host:container] container-port-map : "4200:4200" # The port mappings between the host and the container for the debug port in the form [host:container] container-port-map-debug : "5870:5870" # The name for the dockerfile for the run container dockerfile-run : "Dockerfile" # The name for the dockerfile for the tools container dockerfile-tools : "Dockerfile-tools" # The name of image to create from dockerfile-run image-name-run : "innovate-userbase-run" # The name of image to create from dockerfile-tools image-name-tools : "innovate-userbase-tools" # The command to build the code and docker image for RUN build-cmd-run : "npm install" # The command to execute tests for the code in the tools container test-cmd : "npm run test" # The command to build the code and docker image for DEBUG build-cmd-debug : "npm install" # The command to run the code in the run container run-cmd : "" # The command to execute debug of the code in the tools container debug-cmd : "npm run debug" # The command to stop the code stop-cmd : "npm stop" # The relative path to the helm chart used for Kubernetes deployment chart-path: "chart/innovate-userbase" deploy-target: "container" deploy-image-target: "mycluster.icp:8500/default/innovate-userbase" ================================================ FILE: userbase/config.js ================================================ module.exports = { development: { ports: { portal: 3100, authentication: 3200, accounts: 3400, transactions: 3600, bills: 3800, support: 4000, userbase: 4100 } }, production: { ports: { portal: 30200, authentication: 30100, accounts: 30120, transactions: 30140, bills: 30160, support: 30180, userbase: 30050 } } }; ================================================ FILE: userbase/idt.js ================================================ 'use strict' /* * Wrapper for the idt (IBM Developer Tools) command. * Run with the same arguments as `idt`, e.g. * `node idt.js build` -> `idt build`. * If `idt` isn't installed, this will prompt you to install. Or you can run * `node idt.js install` to automatically install idt and any other * required dependencies (e.g. docker, git, kubernetes, helm). * */ const fs = require('fs'); const process = require('process'); const cp = require('child_process'); const request = require('request'); const path = require('path'); const chalk = require('chalk'); const node = process.execPath; // Array of args passed to idt.js. const args = process.argv.slice(2); let win = (process.platform === 'win32'); // Either install idt or run idt + args. if (args.includes('install')) { downloadInstaller(); } else { // TODO(gib): Check for idt once this works in scripts: // const checkCmd = win ? 'where idt' : 'which idt'; const checkCmd = 'bx plugin show dev'; let hasIDT = false; try { console.log(chalk.blue('Checking for idt')); cp.execSync(checkCmd); // Don't inherit stdio, we don't want to print the output. hasIDT = true; // If we didn't have idt, the previous command would have thrown. } catch (e) { const prompt = require('prompt-confirm'); new prompt({ name: 'install', message: 'IDT not found, do you want to install it? y/N', default: false }).ask((answer) => { if (answer) { downloadInstaller(() => runIDT(args)); } else { console.error(chalk.red(`Not installing idt, so not running: idt ${args.join(' ')}`)); } }); } if (hasIDT) runIDT(args); } // Run IDT with whatever args we were given. function runIDT(args) { const cmd = 'bx dev ' + args.join(' '); console.log(chalk.blue('Running:'), cmd); cp.execSync(cmd, {stdio: 'inherit'}); } // Download the IDT installer script and trigger runInstaller(). function downloadInstaller(cb) { const url = win ? 'https://ibm.biz/yeoman-idt-win-install' : 'http://ibm.biz/yeoman-idt-install'; const fileName = url.split('/').pop() console.log(chalk.blue('Downloading installer from:'), url); const file = fs.createWriteStream(fileName); request .get({url, followAllRedirects: true}) .on('error', (err) => { console.error(err); }) .pipe(file) .on('finish', () => runInstaller(fileName, cb)); } // Run the installer script and trigger optional callback (cb). function runInstaller(fileName, cb) { const shell = win ? 'powershell.exe' : 'bash'; const filePath = path.resolve(__dirname, fileName); console.log(`Now running: ${shell} ${filePath}`); cp.spawnSync(shell, [filePath], {stdio: 'inherit'}); typeof cb === 'function' && cb(); } ================================================ FILE: userbase/manifest.yml ================================================ --- applications: - instances: 1 timeout: 180 name: innovate-userbase buildpack: sdk-for-nodejs command: npm start memory: 512M ================================================ FILE: userbase/package.json ================================================ { "name": "innovate-userbase", "version": "1.0.0", "description": "Innovate: Digital Bank", "private": true, "engines": { "node": "^6.9.0" }, "scripts": { "start": "node server.js", "start:cluster": "sl-run server/server.js", "debug": "node --debug server/server.js", "build": "npm run build:idt", "idt:build": "node idt.js build", "idt:test": "node idt.js test", "idt:debug": "node idt.js debug", "idt:run": "node idt.js run", "idt:deploy": "node idt.js deploy", "idt:install": "node idt.js install" }, "dependencies": { "body-parser": "^1.17.2", "dateformat": "^3.0.3", "dotenv": "^2.0.0", "express": "^4.15.3", "ip": "^1.1.5", "mongoose": "^5.0.0-rc1", "request": "^2.83.0", "strong-supervisor": "^6.2.0", "uuid": "^3.1.0", "watson-developer-cloud": "^2.40.0" }, "devDependencies": { "chai": "^4.0.0", "chalk": "^1.1.3", "mocha": "^3.4.2", "nyc": "^10.3.2", "prompt-confirm": "^1.2.0", "proxyquire": "^1.8.0" } } ================================================ FILE: userbase/populate.js ================================================ module.exports = function (request, basePath, ports, dateFormat) { var randomEntries = { bills: [ { category: 'utilities', entity: 'DEWA' }, { category: 'home_entertainment', entity: 'Etisalat' }, { category: 'mobile_phone', entity: 'Du' }, { category: 'credit_card', entity: 'Innovate' } ], transactions: [ { category: 'groceries', description: [ 'Carrefour', 'Lulu Hypermarket', 'Geant', 'Waitrose', 'Spinneys' ] }, { category: 'eating_out', description: [ 'Krush Burgers', 'Clinton Baking St', 'Mamaeesh', 'Starbucks', 'Costa Coffee', 'Blaze Burgers', 'Cheesecake Factory' ] }, { category: 'transport', description: [ 'Uber', 'Careem' ] }, { category: 'bills', description: [ 'Utilities', 'Home entertainment', 'Phone', 'Credit card' ] }, { category: 'expenses', description: [ 'Municipality fee', 'VAT', 'Shopping', 'Electronics', 'Shoes' ] }, { category: 'cash', description: [ 'Withdrawal', 'Transfer' ] }, { category: 'holidays', description: [ 'Flight ticket', 'Hotel booking' ] } ] }; function createTransaction(uuid) { let amount = Math.floor(Math.random() * Math.floor(1200)) + ''; let date = dateFormat(new Date(), "mm, dd, yyyy"); let randomCategoryIndex = Math.floor(Math.random() * Math.floor(randomEntries.transactions.length)); let randomDescriptionIndex = Math.floor(Math.random() * Math.floor(randomEntries.transactions[randomCategoryIndex].description.length)); var body = { uuid: uuid, amount: amount, currency: 'AED', category: randomEntries.transactions[randomCategoryIndex].category, description: randomEntries.transactions[randomCategoryIndex].description[randomDescriptionIndex], date: date }; console.log('Adding transaction: '); console.log(body); var options = { method: 'POST', uri: `http://${basePath}:${ports.transactions}${process.env.CREATE_TRANSACTION_ENDPOINT}`, body: body, json: true }; request.post(options, function (err, response, body) { return; }); } function createBill(uuid) { let randomIndex = Math.floor(Math.random() * Math.floor(randomEntries.bills.length)); let amount = Math.floor(Math.random() * Math.floor(1200)) + ''; let date = dateFormat(new Date(), "mm, dd, yyyy"); let account_no = Math.floor(Math.random() * 90000000) + ''; var body = { uuid: uuid, category: randomEntries.bills[randomIndex].category, entity: randomEntries.bills[randomIndex].entity, amount: amount, date: date, account_no: account_no }; console.log('Adding bill: '); console.log(body); var options = { method: 'POST', uri: `http://${basePath}:${ports.bills}${process.env.UPSERT_BILL_ENDPOINT}`, body: body, json: true }; request.post(options, function (err, response, body) { return; }); } function dropBills() { var options = { method: 'GET', uri: `http://${basePath}:${ports.bills}${process.env.DROP_BILLS_ENDPOINT}`, json: true }; request.get(options, function (err, response, body) { return; }); } function dropTransactions() { var options = { method: 'GET', uri: `http://${basePath}:${ports.transactions}${process.env.DROP_TRANSACTIONS_ENDPOINT}`, json: true }; request.get(options, function (err, response, body) { return; }); } function dropAccounts() { var options = { method: 'GET', uri: `http://${basePath}:${ports.accounts}${process.env.DROP_ACCOUNTS_ENDPOINT}`, json: true }; request.get(options, function (err, response, body) { return; }); } function populate() { console.log('Populating'); return new Promise(function (resolve, reject) { var options = { method: 'GET', uri: `http://${basePath}:${ports.authentication}${process.env.GET_USERS_ENDPOINT}`, json: true }; request.get(options, function (err, response, body) { if (err) { reject(err); } resolve(body); }); }) .then(function (users) { console.log(users); for (index in users) { createBill(users[index].uuid); createTransaction(users[index].uuid); } return; }) .catch(function (err) { console.log('Failed to retrieve users; ', err); return; }); } function reset() { console.log('Resetting'); dropAccounts(); dropTransactions(); dropBills(); return; } function init() { setInterval(reset, 3600000); setInterval(populate, 180000); } reset(); populate(); init(); }; ================================================ FILE: userbase/server.js ================================================ 'use strict'; const mongoose = require('mongoose'); require('dotenv').config({silent: true, path: `${__dirname}/.env`}); var server = require('./app'); var port = 4100; console.log(`Running on ${process.env.BASE_PATH}:${port}, connecting to ${process.env.MONGO_URL}`) mongoose.connect(process.env.MONGO_URL, function (ignore, connection) { connection.onOpen(); server.listen(port, function () { console.log('Server running on port: %d', port); }); });