Contact App
Gcalls 3.1 contactApp is an web application that allow callcenter agent manages contacts. contactApp also helps agents grow up their customer support service quality.
I. Functionality
- Allow callcenter mange their contacts
- Enhance customer support service quality
- Free sign up
- Call box
- Manage contacts and contact group
- Manage call logs and activities
- Manage agents
- Manage SIP providers
- Manage roles
- Statistics
- CRM intergration
II. Packages
1. Dependencies
- Abbrev - A data abbreviation library for JavaScript Abbrev
- Abortcontroller-polyfill - A polyfill for AbortController Abortcontroller-polyfill
- Aws-sdk - AWS SDK for JavaScript Aws-sdk
- Axios - Promise based HTTP client for the browser and node.js Axios
- Babel-core - Babel compiler core. Babel-core
- Babel-loader - This package allows transpiling JavaScript files using Babel and webpack. Babel-loader
- Babel-plugin-transform-decorators-legacy - A plugin for Babel 6 that (mostly) replicates the old decorator behavior from Babel 5. Babel-plugin-transform-decorators-legacy
- Babel-polyfill - Provides polyfills necessary for a full ES2015+ environment Babel-polyfill
- Babel-preset-es2015 - Babel preset for all es2015 plugins. Babel-preset-es2015
- Babel-preset-react - Babel preset for all React plugins. Babel-preset-react
- Babel-preset-stage-0 - Babel preset for stage 0 plugins. Babel-preset-stage-0
- Babel-register - babel require hook Babel-register
- Bluebird - Full featured Promises/A+ implementation with exceptionally good performance Bluebird
- Body-parser - Node.js body parsing middleware Body-parser
- Comma-separated-values - A simple library to encode and decode CSV file. Comma-separated-values
- Connect-multiparty - Middleware for handling
multipart/form-data. Connect-multiparty - Connect-redis - Redis session store for Connect. Connect-redis
- Cors - Node.js CORS middleware Cors
- Cron - Cron is a tool that allows you to execute something on a schedule. Cron
- Delay - Delay a promise a specified amount of time Delay
- Dotenv-webpack - A secure webpack plugin that supports dotenv and other environment variables and only exposes what you choose and use. Dotenv-webpack
- Dotenv - Loads environment variables from .env file. Dotenv
- Ejs - Embedded JavaScript templates Ejs
- Express-ip - IP address utilities for Express Express-ip
- Express-session - Simple session middleware for Express Express-session
- Express - Fast, unopinionated, minimalist web framework for node. Express
- Form-data - A library to create readable "multipart/form-data" streams. Can be used to submit forms and file uploads to other web applications. Form-data
- Fs - Node.js File System module. Fs
- Googleapis - Node.js client library for using Google APIs. Support for authorization and authentication with OAuth 2.0, API Keys and JWT (Service Tokens) is included. Googleapis
- Ignore-by-default - A node-ignore powered ignore manager. Ignore-by-default
- Jsonwebtoken - JSON Web Token implementation (symmetric and asymmetric) Jsonwebtoken
- Keycloak-connect-multirealm - Keycloak Connect Middleware for Express Keycloak-connect-multirealm
- Keycloak-connect - Keycloak Connect Middleware for Express Keycloak-connect
- Keycloak-js - JavaScript adapter for Keycloak Keycloak-js
- Lodash - Lodash modular utilities. Lodash
- Mime - Mime types for JavaScript Mime
- Morgan - HTTP request logger middleware for node.js Morgan
- Node-schedule - A cron-like and not-cron-like job scheduler for Node. Node-schedule
- Nodemailer - Send e-mails from Node.js Nodemailer
- Nodemon - Simple monitor script for use during development of a node.js app. Nodemon
- Nopt - Option parsing for Node, supporting types, shorthands, etc. Used by npm. Nopt
- Path - Node.JS path module. Path
- Pouchdb-find - A query language for PouchDB and CouchDB. Pouchdb-find
- Pouchdb - PouchDB is a pocket-sized database. Pouchdb
- Pstree.remy - A node module to list all processes running on the system in a tree structure. Pstree.remy
- React-audio-player - A React component to play an audio file. React-audio-player
- React-bootstrap-table - It's a react table for bootstrap. React-bootstrap-table
- React-dom - React package for working with the DOM. React-dom
- React-draggable - React draggable component. React-draggable
- React-helmet - A document head manager for React. React-helmet
- React-multilingual - A simple react component to translate text in multiple languages. React-multilingual
- React-redux - Official React bindings for Redux. React-redux
- React-router - Declarative routing for React. React-router
- React-scroll - Component for animating vertical scrolling. React-scroll
- React - React is a JavaScript library for building user interfaces. React
- Redis - Redis client library. Redis
- Redux-thunk - Thunk middleware for Redux. Redux-thunk
- Redux - Predictable state container for JavaScript apps. Redux
- Rotating-file-stream - Creates a stream.Writable to a file which is rotated. Rotating-file-stream
- Simple-update-notifier - A simple update notifier for your CLI app. Simple-update-notifier
- Slugify - Slugifies a string. Slugify
- Socket.io - Node.js realtime framework server. Socket.io
- Sweetalert - A beautiful replacement for JavaScript's "alert" Sweetalert
- Sweetalert2 - A beautiful, responsive, customizable and accessible (WAI-ARIA) replacement for JavaScript's popup boxes. Zero dependencies. Sweetalert2
- Touch - A Node.js module for interfacing with the Apple Mac OS X Touch Bar. Touch
- Undefsafe - A utility to access deep properties in JavaScript (Node.js and Browser). Undefsafe
- Uuid - RFC4122 (v1, v4, and v5) UUIDs. Uuid
- Webpack-dev-middleware - Offers a dev middleware for webpack, which arguments a live bundle to a directory. Webpack-dev-middleware
- Webpack-hot-middleware - Webpack hot reloading you can attach to your own server. Webpack-hot-middleware
- Webpack - Packs CommonJs/AMD modules for the browser. Allows to split your codebase into multiple bundles, which can be loaded on demand. Support loaders to preprocess files, i.e. json, jsx, es7, css, less, ... and your custom stuff. Webpack
- Winston - A multi-transport async logging library for Node.js. Winston
2. Dev dependencies
- Clean-webpack-plugin - A library that helps you to keep your webpack's output directory clean. Clean-webpack-plugin
- Ignore-by-default - A node module to ignore files by default. Ignore-by-default
- Nodemon - A utility that will monitor for any changes in your source and automatically restart your server. Nodemon
- Nopt - Node.js option parsing library. Nopt
- Pstree.remy - A node module to list all processes running on the system in a tree structure. Pstree.remy
- Simple-update-notifier - A simple update notifier for your CLI app. Simple-update-notifier
- Touch - A Node.js module for interfacing with the Apple Mac OS X Touch Bar. Touch
- Undefsafe - A utility to access deep properties in JavaScript (Node.js and Browser). Undefsafe
- Webpack-cli - Webpack's Command Line Interface. Webpack-cli
- Webpack-merge - Merge designed for Webpack (MIT). Webpack-merge
III. Source tree
1. Server
.
├── .babelrc
├── Dockerfile
├── .dockerignore
├── .env
├── .gitignore
├── index.js
├── jsonwebtoken
│ └── index.js
├── keycloak.json
├── package.json
├── package-lock.json
├── server
│ ├── config
│ │ ├── access.js
│ │ ├── auth-google.js
│ │ ├── aws.js
│ │ ├── backendVersion.js
│ │ ├── feature.js
│ │ ├── host.js
│ │ ├── integration.js
│ │ ├── jwt.js
│ │ ├── log-service.js
│ │ ├── mailer.js
│ │ ├── mapper.js
│ │ └── redis.js
│ ├── lib
│ │ ├── access.js
│ │ ├── cdn.js
│ │ ├── email.js
│ │ ├── exportContact.js
│ │ ├── feature.js
│ │ ├── integration.js
│ │ ├── jwt.js
│ │ ├── logger.js
│ │ ├── password.js
│ │ ├── phonenumber.js
│ │ ├── pubsub.js
│ │ └── sheet.js
│ ├── middleware
│ │ └── authenticate.js
│ ├── route
│ │ ├── authenticated
│ │ │ ├── Activity.js
│ │ │ ├── Agent.js
│ │ │ ├── Api.js
│ │ │ ├── Button.js
│ │ │ ├── CallLog.js
│ │ │ ├── ContactField.js
│ │ │ ├── ContactGroup.js
│ │ │ ├── Contact.js
│ │ │ ├── ExportContact.js
│ │ │ ├── GroupField.js
│ │ │ ├── Integration.js
│ │ │ ├── Integrations.js
│ │ │ ├── Invite.js
│ │ │ ├── Log.js
│ │ │ ├── MailService.js
│ │ │ ├── Record.js
│ │ │ ├── SipAccount.js
│ │ │ ├── Team.js
│ │ │ ├── Template.js
│ │ │ └── User.js
│ │ ├── index.js
│ │ └── unauthenticated
│ │ ├── ForgetCallcenterName.js
│ │ ├── ForgetPassword.js
│ │ ├── Invite.js
│ │ ├── MailService.js
│ │ ├── Notify.js
│ │ ├── PrivacyPolicy.js
│ │ └── User.js
│ └── views
│ ├── admin.html
│ ├── agent.html
│ ├── ejs
│ │ ├── admin.ejs
│ │ ├── agent.ejs
│ │ └── non-user.ejs
│ └── non-user.html
├── server.js
├── sources
│ ├── api.yaml
│ └── images
│ └── callbox.png
├── webpack.common.js
├── webpack.config.js
├── webpack.dev.js
└── webpack.prod.js
2. Client
├── client
│ ├── Admin
│ │ ├── actions
│ │ ├── components
│ │ ├── constants
│ │ ├── locales
│ │ ├── reducers
│ │ └── socket.io
│ ├── Agent
│ │ ├── actions
│ │ ├── components
│ │ ├── constants
│ │ ├── locales
│ │ ├── reducers
│ │ └── socket.io
│ ├── Anonymous
│ │ ├── components
│ │ ├── locales
│ │ └── redux
│ ├── config
│ └── library
├── jsonwebtoken
├── logs
├── public
│ ├── aehlke-tag-it
│ │ ├── css
│ │ ├── js
│ │ └── _../../../../../static
│ ├── css
│ ├── fonts
│ ├── img
│ │ └── survey2022
│ ├── js
│ └── sounds
│ └── dialpad
├── server
│ ├── config
│ ├── lib
│ ├── middleware
│ ├── route
│ │ ├── authenticated
│ │ └── unauthenticated
│ └── views
│ └── ejs
└── sources
└── images
IV. Installation
Install Redis before running the project
- Guide install Redis: https://redis.io/topics/quickstart
- I strongly recommend using Docker to install Redis
Clone project:
git clone https://gitlab.com/gcalls-opensource/gcallsfront.git
- Install yarn
npm install -g yarn
- Install utility modules:
yarn install
Start server:
run local
npm run localrun in development
yarn global add pm2
npm run devrun in production
yarn global add pm2
npm run prod
V. API
Method GET: ${webServiceUrl}/api/activities
-> contactApp
-> GET webService("/activities")
-> GET accessService("/callcenter/:idCallcenter/activities")
-> GET filterService("/callcenter/:idCallcenter/activities")
${webServiceUrl}/api/activities/chart
Method GET: contactApp -> GET webService("/activities/chart") -> accessService("/callcenter/:idCallcenter/activities/chart") -> filterService("/callcenter/:idCallcenter/activities/chart")
${webServiceUrl}/api/activities/count
Method GET: contactApp -> GET webService("/activities/count") -> accessService("/callcenter/:idCallcenter/activities/count") -> filterService("/callcenter/:idCallcenter/activities/count")
${webServiceUrl}/api/activities/download
Method GET: contactApp -> GET webService("/activities/download") -> IF(idGroup) {GET accessService("/callcenter/:idCallcenter/contactgroup/:id/contacts") -> filterService("/callcenter/:idCallcenter/contactgroup/:idContact/contacts")} -> IF(creator) {POST accessService("/callcenter/:idCallcenter/activities") -> POST filterService("/callcenter/:idCallcenter/activities)} -> ELSE(!creator) {POST accessService("/callcenter/:idCallcenter/activities") -> POST filterService("/callcenter/:idCallcenter/activities)}
${webServiceUrl}/api/activities/sum
Method GET: contactApp -> GET webService("/activities/sum") -> GET accessService("/callcenter/:idCallcenter/activities/sum") -> GET filterService("callcenter/:idCallcenter/activities/sum")
${webServiceUrl}/api/activity
Method GET: contactApp -> GET webService("/activity") -> POST accessService("/callcenter/:idCallcenter/activity") -> POST CommandService("/callcenter/:idCallcenter/activity") -> GET filterService("callcenter/:idCallcenter/contact/:idContact/activity/:idActivity) -> GET FilterService("/callcenter/:idCallcenter/contact/:idContact") -> Send Mail -> IF(SMSStatus) Redis
${webServiceUrl}/api/contact/:idContact/activity/:id
Method GET: contactApp -> GET webService("/contact/:idContact/activity/:id") -> GET accessService("/callcenter/:idCallcenter/contact/:idContact/activity/idActivity/:id") -> filterService("/callcenter/:idCallcenter/contact/:idContact/activity/:idActivity")
Method PUT: contactApp -> PUT webService("/contact/:idContact/activity/:id") -> PUT accessService("/callcenter/:idCallcenter/contact/:idContact/activity/idActivity/:id") -> PUT CommandService("/callcenter/:callcenter_id/contact/:contact_id/activity/:activity_id") -> IF(recordUrl) Redis
Method DELETE: contactApp -> DELETE webService("/contact/:idContact/activity/:id") -> PUT accessService("/callcenter/:idCallcenter/contact/:idContact/activity/:id") -> IF(recordURL) {Redis -> Return} -> PUT CommandService("/callcenter/:callcenter_id/contact/:contact_id/activity/:activity_id") -> IF(recordURL) {Redis -> Return}
${webServiceUrl}/api/activity/:idCalllog/tags
Method POST: contactApp -> POST webService("/activity/:id/tags") -> POST accessService("/callcenter/:idCallcenter/activity/:id/tags") -> POST CommandService("/callcenter/:callcenter_id/activity/:activity_id/tags")
Method DELETE: contactApp -> webService("/callcenter/:idCallcenter/activity/:id/tags") -> DELETE accessService("/callcenter/:idCallcenter/activity/:id/tags") -> DELETE CommandService("/callcenter/:callcenter_id/activity/:activity_id/tags")
${webServiceUrl}/api/activity/:id
Method GET: contactApp -> GET webService("/activity/:id") -> GET accessService("/callcenter/:idCallcenter/activity/:id") -> GET filterService("/callcenter/:idCallcenter/activity/:idActivity")
Method PUT: contactApp -> PUT webService("/activity/:id") -> PUT accessService("/callcenter/:idCallcenter/activity/:id") -> if(recordUrl) {Redis -> Return} -> PUT CommandService("/callcenter/:callcenter_id/activity/:activity_id") -> IF(recordUrl){Redis} -> Return
Method DELETE: contactApp -> DELETE webService("/activity/:id") -> PUT accessService("/callcenter/:idCallcenter/activity/:id") -> if(recordUrl) {Redis -> Return} -> PUT CommandService("/callcenter/:callcenter_id/activity/:activity_id") -> IF(recordUrl){Redis} -> Return
${webServiceUrl}/api/agent/:idAgent
Method GET: contactApp -> GET webService("/agent/:idAgent") -> GET accessService("/callcenter/:idCallcenter/agent/:idUser") -> GET filterService("/callcenter/:idCallcenter/agent/:idAgent") -> Query userModel -> return
---
Method PUT: contactApp -> PUT webService("/agent/:idAgent") -> IF(status){Redis} -> ELSE(!status){ PUT accessService("/callcenter/:idCallcenter/agent/:idUser") -> PUT CommandService("/callcenter/:callcenter_id/agent/:agent_id") -> Query AgentModel -> IF(field === 'deleted'){PUT CommandService("/callcenter/:callcenter_id/agent/:agent_id")}} -> Redis.publish("agentUpdated")
${webServiceUrl}/api/agent/${idAgent}/contactgroups
Method GET:
contactApp
-> webService("/agent/:idAgent/contactgroups")
-> GET accessService("/callcenter/:idCallcenter/agent/:idAgent/contactgroups")
-> GET filterService("/callcenter/:idCallcenter/agentgroup/:idAgent/contactgroups")
-> Query customerModel
-> Query SipAccountModel
-> Update Agent
-> return to accessService
-> return to webService
-> Return to contactApp
Method PUT: ${webServiceUrl}/api/agent/actions/user/:id
-> contactApp
-> PUT webService("/api/agent/actions/user/:id")
-> Get user's actions: PUT accessService("/callcenter/:idCallcenter/user/:idUser/actions")
-> Get agent: PUT CommandService("/callcenter/:callcenter_id/agent/:agent_id")
-> Get agent: AgentModel(db, callcenter_id)
-> Get SipAccount: SipAccountModel(db, callcenter_id)
-> Update agent: Agent.update(agent_id, data, versionSchema)
-> Get SipAccount and update to data: SipAccount.read(result.idSip)
-> Return data to AccessService
-> Publish event: Redis publisher.publish("agentUpdated", JSON.stringify(message))
Method GET: ${webServiceUrl}/api/agent/actions/user/:id/:role
-> ContactApp
-> GET webService(/api/agent/actions/user/:id/:role)
-> Get list actions from GET getActionCDN(callcenter.level, roleLevel)
-> GET accessService("/callcenter/:idCallcenter/user/:idUser/actions")
-> Get callcenter from Query Callcenter({_id: idCallcenter,_v: versionSchema,}).getById();
-> Get user from Query User({ _id: idUser })
-> GET filterService("/callcenter/:idCallcenter/agent/:idAgent")
-> Get agent from Query User.getOne(`agent_${idCallcenter}`).getById()
-> Get sipAccount from Query User.getOne(`sipAccount_${idCallcenter}`)
-> Get team from Query User.getOne(`team_${idCallcenter}`)
-> Get role from Query User.getOne(`role_${idCallcenter}`)
-> Combine data and return to accessService
-> Get action from getAction(callcenter.level, user.role)
-> Return data action to webService
-> Return data to contactApp
Method POST: ${webServiceUrl}/api/agent/admin/:id
-> ContactApp
-> POST webService("/agent/admin/:id")
-> POST accessService("/callcenter/:idCallcenter/transfer-admin/:idUser")
-> Get user: Query User({_id: idUser, _v: versionSchema }).getById()
-> Get agent: GET FilterService("/callcenter/:idCallcenter/agent/:idAgent")
-> Get agent: Query userModel.getOne(`agent_${idCallcenter}`, { _id: idAgent }, versionSchema)
-> Get sip: Query userModel.getOne(`sipAccount_${idCallcenter}`,{ _id: agent.idSip },versionSchema)
-> Get team: userModel.getOne(`team_${idCallcenter}`,{ _id: agent.idTeam },versionSchema)
-> Get role: userModel.getOne(`role_${idCallcenter}`,{ _id: agent.role },versionSchema)
-> Combine all data and return data to accessService
-> Get roles: GET FilterService("callcenter/:idCallcenter/roles/all")
-> Get roles: User.queryByFields(`role_${idCallcenter}`, { deleted: false }, {}, versionSchema)
-> Return roles to AccessService
-> Get agents: GET filterService("/callcenter/:idCallcenter/agents/all?filter=...")
-> Query agents: userModel.queryByFields(`agent_${idCallcenter}`,filter_options,{},versionSchema)
-> For each agent:
-> Fetch SIP: userModel.getOne(`sipAccount_${idCallcenter}`, { _id: agent.idSip }, versionSchema)
-> Fetch role: userModel.getOne(`role_${idCallcenter}`, { _id: agent.role }, versionSchema)
-> Fetch team: userModel.getOne(`team_${idCallcenter}`, { _id: agent.idTeam }, versionSchema)
-> Combine all data and return data to accessService
-> Update first agent: PUT CommandService("/callcenter/:callcenter_id/agent/:agent_id")
-> Load AgentModel(db, callcenter_id)
-> Load SipAccountModel(db, callcenter_id)
-> Update agent info: Agent.update(...)
-> Fetch SipAccount and update to data
-> Return data to accessService
-> Update Admin: PUT CommandService("/callcenter/:callcenter_id/agent/:agent_id")
-> Load AgentModel(db, callcenter_id)
-> Load SipAccountModel(db, callcenter_id)
-> Update agent info: Agent.update(...)
-> Fetch SipAccount and update to data
-> Return data to accessService
-> Publish event: Redis publisher.publish("agentUpdated", JSON.stringify(message))
-> Return data to Webservice
-> Return data to ContactApp
Method POST: ${webServiceUrl}/api/agent/revoke/${id}
-> ContactApp
-> webService("/agent/:idAgent/contactgroups")
-> Get users: Redis publisher.getAsync("agent-callcenter-${user.idCallcenter}")
-> Delete user: POST AccessService("/api/revoke")
-> Get user: Query User({ _id: idUser, _v: versionSchema }).getById()
-> Get callcenter: Query Callcenter({_id: idCallcenter}).getById()
-> PUT CommandService("/callcenter/:callcenter_id/agent/:agent_id").data:{ field: "deleted", value: true, deleted: true }
-> Get agent: AgentModel(db, callcenter_id)
-> Get SipAccount: SipAccountModel(db, callcenter_id)
-> Update agent: Agent.update(agent_id, data, versionSchema)
-> Get SipAccount and update to data: SipAccount.read(result.idSip)
-> PUT CommandService("/callcenter/:callcenter_id/contactgroups/agents").data({
all: true,
ids: [],
list: [idUser],
})
-> Get AgentGroup: AgentGroupModel(db, callcenter_id)
-> Get ContactGroup: ContactGroupModel(db, callcenter_id)
-> Get Groups: ContactGroup.queryByFields(all ? {} : { _id: { $in: groupList } })
-> Combime data to agentgroupList and create agentGroups: AgentGroup.createMany(agentgroupList, versionSchema)
-> Return result of create agentGroups to AccessService
-> Delete User in keycloak
-> Get Token: POST Keycloak("${kcHost}/auth/realms/${kcRealm}/protocol/openid-connect/token").data:{data}
-> Handle response from Keycloak token API
-> Cache result to Redis: Redis redisClient.setAsync('keycloak', JSON.stringify(result))
-> Get user: GET Keycloak("${kcHost}/auth/admin/realms/${realm}/users/?email=${email}")
-> Delete user: DELETE Keycloak("${kcHost}/auth/admin/realms/${realm}/users/${user.id}")
-> Update user: UPDATE user.update({ deleted: true })
-> Return status to Webservice
-> Update redis: publisher.setAsync(`agent-callcenter-${user.idCallcenter}`,JSON.stringify(data))
-> Return data to contactApp
Method PUT: ${webServiceUrl}/api/agent/role/:idTenant/:id
-> ContactApp
-> Update role of callcenter: PUT webService("/agent/role/:idTenant/:id")
-> PUT accessService("/callcenter/:callcenter_id/role/:role_id")
-> PUT CommandService("/callcenter/:callcenter_id/role/:role_id")
-> Get Role: RoleModel(db, req.params.callcenter_id, logger)
-> Update role: Role.update(req.params.role_id, body, versionSchema)
-> Return data to AccessService
-> Return data to Webservice
-> Return data to contactApp
Method GET: ${webServiceUrl}/api/agent/status/count
-> ContactApp
-> Get webService("/agent/status/count")
-> Get AccessService("/callcenter/:idCallcenter/agent/status/count")
-> Get filterService("/callcenter/:idCallcenter/agents/status/count")
-> Get agents of role: userModel.queryByFields(`agent_${idCallcenter}`, { role: idRole }, {}, versionSchema)
-> Get agents of team: userModel.queryByFields(`agent_${idCallcenter}`, { idTeam }, {}, versionSchema)
-> Calculate the total time each agent spent in each status (excluding 'offline') within a specific time range:
-> userModel.agentLogs_aggregateStatusTimeCount(idCallcenter, filter_options, versionSchema)
-> Combine data and return to AccessService
-> Return data to Webservice
-> Return data to contactApp
${webServiceUrl}/api/agent/status/logs
-> ContactApp
-> GET Webservice("/agent/status/logs")
-> GET AccessService("/callcenter/:idCallcenter/agents/status/logs")
-> GET FilterService("/callcenter/:idCallcenter/agents/status/logs")
-> Get status of agents: userModel.getMany(collectionName,{createdAt:{$gte:+date,$lt:+date+24*60*60*1000,},},10000,0,undefined,versionSchema)
-> Return status of agents to AccessService
VI. Deployment
1. Deploy với CF page
- Deploy với CF page có thể xem tại Deploy lên cloudflare
2. Blue/Green deployment
Sử dụng để deploy chức năng mới trên Blue(/b) trước khi chạy chính thức trên Green(/g), được phân biệt với URL của ứng dụng như sau: https://app.gcalls.co/g/ và https://app.gcalls.co/b/
Đối với FE Contact App có thể kiểm tra ENV và Config để lấy giá trị của cờ (flag) Blue/Green sử dụng cho những mục đích khác nhau:

- Get giá trị rootApp trong file config

3. Nginx Proxy & Load balancer
Đối với FE và BE (webservice) của Contact App, bạn có thể triển khai Blue/Green deployment với nginx proxy hoặc load balancer dự vào đường dẫn URL của ứng dụng:
- NGINX Reverse Proxy
location /g {
proxy_pass http://10.20.1.5:4004;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
location /b {
proxy_pass http://10.20.1.5:4005;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}- CF Load balancer sử dụng Custom Rules. Ví dụ với URL có chứa /g sẽ chuyển request sang Bizfly, còn lại sẽ dùng gcallsfront.pages.dev.

VII. Author
- Duong Cat Hung Vuong vuong.duong@gcalls.co- Fullstack engineer at Gcall Vietnam Pte Ltd
- Dang Hoang Thien thien.dang@gcalls.co- Frontend developer at Gcall Vietnam Pte Ltd
- Vo Ngoc Mai Linh linh.vo@gcalls.co - Fullstack engineer at Gcall Vietnam Pte Ltd
VIII. Acknowlegde
- This project is based on __react@15.6.2__, react-router@3. There are many changes in react-router@4 and react^@16, it's not able to used in this project. Be careful if you want to upgrade version of these packages.