manhtien465 2 years ago
commit c609113d8a
  1. 19
      .babelrc
  2. 52
      .dockerignore
  3. 13
      .editorconfig
  4. 22
      .env.example
  5. 1
      .eslintignore
  6. 43
      .eslintrc
  7. 58
      .gitignore
  8. 97
      .gitlab-ci.yml
  9. 8
      Dockerfile
  10. 82
      README.md
  11. 6
      docker-compose.dev-event-dispatcher.yml
  12. 6
      docker-compose.dev-worker.yml
  13. 6
      docker-compose.dev.yml
  14. 26
      docker-compose.yml
  15. 109
      package.json
  16. 19
      process copy.yml
  17. 19
      process.yml
  18. BIN
      public/2/about.jpg
  19. 32
      src/api/controllers/v1/auth.controller.js
  20. 95
      src/api/controllers/v1/image.controller.js
  21. 111
      src/api/controllers/v1/path.controller.js
  22. 177
      src/api/controllers/v1/staff.controller.js
  23. 56
      src/api/controllers/v1/stock-take-excel.controller.js
  24. 32
      src/api/controllers/v1/user-excel.controller.js
  25. 134
      src/api/controllers/v1/user.controller.js
  26. 48
      src/api/middlewares/ApiError.js
  27. 331
      src/api/middlewares/auth.middleware.js
  28. 109
      src/api/middlewares/authen.middleware.js
  29. 72
      src/api/middlewares/error.js
  30. 67
      src/api/middlewares/errors.js
  31. 17
      src/api/middlewares/image.middleware.js
  32. 113
      src/api/middlewares/staff.middleware.js
  33. 88
      src/api/middlewares/user.middleware.js
  34. 38
      src/api/routes/v1/auth.route.js
  35. 38
      src/api/routes/v1/image.route.js
  36. 26
      src/api/routes/v1/index.js
  37. 32
      src/api/routes/v1/path.route.js
  38. 63
      src/api/routes/v1/staff.route.js
  39. 46
      src/api/routes/v1/user.route.js
  40. 33
      src/api/validations/v1/auth.validation.js
  41. 14
      src/api/validations/v1/excel.validation.js
  42. 39
      src/api/validations/v1/image.validation.js
  43. 69
      src/api/validations/v1/staff.validation.js
  44. 96
      src/api/validations/v1/user.validation.js
  45. 408
      src/common/models/config.model.js
  46. 276
      src/common/models/file.model.js
  47. 292
      src/common/models/image.model.js
  48. 1005
      src/common/models/user.model.js
  49. 101
      src/common/services/adapters/sharp-adapter.js
  50. 109
      src/common/services/adapters/upload-adapter.js
  51. 9
      src/common/services/event-bus.js
  52. 0
      src/common/services/managers/image-manager.js
  53. 53
      src/common/services/managers/upload-manager.js
  54. 28
      src/common/services/service-client.js
  55. 51
      src/common/utils/APIException.js
  56. 120
      src/common/utils/Permissions.js
  57. 7
      src/common/utils/utilities.js
  58. 54
      src/config/express.js
  59. 8
      src/config/i18n.js
  60. 21
      src/config/locales/en.json
  61. 4
      src/config/locales/vi.json
  62. 21
      src/config/messages.js
  63. 38
      src/config/mongoose.js
  64. 117
      src/config/persistent.js
  65. 40
      src/config/postgres.js
  66. 29
      src/config/rabbitmq.js
  67. 71
      src/config/redis.js
  68. 35
      src/config/vars.js
  69. 5
      src/event-adapter/index.js
  70. 27
      src/index-event-dispatcher.js
  71. 27
      src/index-worker.js
  72. 46
      src/index.js
  73. 8
      src/providers/index.js
  74. 8
      src/providers/upload-event.js
  75. 5
      src/worker/index.js
  76. 7225
      yarn.lock

@ -0,0 +1,19 @@
{
"presets": [
[
"env",
{
"targets": {
"node": "current"
}
}
]
],
"env": {
"test": {
"plugins": [
"istanbul"
]
}
}
}

@ -0,0 +1,52 @@
# Environment variables
# Logs
logs
*.log
npm-debug.log*
# Documentation
docs
# 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
# node-waf configuration
.lock-wscript
# Dependency directories
node_modules
jspm_packages
# Optional npm cache directory
.npm
# Optional REPL history
.node_repl_history
# Dist folder
dist
# Project file
*.sublime-project
*.sublime-workspace
# Git file
.git
# Docker file
*Dockerfile*
*docker-compose*
# Deploy folder and file
deploy
.gitlab-ci.yml
# Documentation file
*.md

@ -0,0 +1,13 @@
# editorconfig.org
root = true
[*]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false

@ -0,0 +1,22 @@
# App config
NODE_ENV=development
PORT=3004
# Db config
POSTGRES_URI=postgres://admin:admin@127.0.0.1:5432/game
POSTGRES_URI_TEST=postgres://admin:admin@127.0.0.1:5432/game-test
MONGO_URI=mongodb://127.0.0.1:27017/game-event?authSource=admin
MONGO_URI_TEST=mongodb://127.0.0.1:27017/game-event?authSource=admin
RABBITMQ_URI=amqp://127.0.0.1:5672
REDIS_URI=redis://127.0.0.1:6379
# Cdn config
CDN_URI=http://103.162.31.170
DEV_CDN_URI=http://103.162.31.170
STORAGE_URI=/home/storages
DEV_STORAGE_URI=/home/storages
# Other service
MANAGER_SERVICE_URL=http://sv-backend-manager

@ -0,0 +1 @@
dist/*

@ -0,0 +1,43 @@
{
"rules": {
"no-console": 0,
"no-underscore-dangle": 0,
"no-unused-vars": [
"error",
{
"argsIgnorePattern": "next"
}
],
"no-use-before-define": [
"error",
{
"variables": false
}
],
"no-multi-str": 0,
"indent": [
"warn",
4,
{
"SwitchCase": 1
}
],
"object-shorthand": "off",
"prefer-destructuring": "off",
"comma-dangle": "off",
"object-curly-newline": "off",
"arrow-parens": "off",
"max-len": "off",
"function-paren-newline": "off"
},
"env": {
"node": true,
"browser": true
},
"parserOptions": {
"ecmaVersion": 8
},
"extends": [
"airbnb-base"
]
}

58
.gitignore vendored

@ -0,0 +1,58 @@
# Use yarn.lock instead
package-lock.json
yarn.json
# Environment variables
.env
.env-live.example
# Logs
logs
*.log
npm-debug.log*
# Documentation
docs
# Runtime data
pids
*.pid
*.seed
# 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
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules
jspm_packages
# Optional npm cache directory
.npm
# Optional REPL history
.node_repl_history
# Dist folder
dist
-p
# Project file
*.sublime-project
*.sublime-workspace
.vs

@ -0,0 +1,97 @@
image: docker:latest
services:
- docker:dind
stages:
- build
- deploy
variables:
IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME
KUBECONFIG: /etc/deploy/config
NAMESPACE: sv-production
APP_NAME: sv-backend-file
build:
stage: build
script:
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN registry.gitlab.com
- docker pull $IMAGE_TAG-builder || echo "Building builder from scratch"
- docker pull $IMAGE_TAG || echo "Building runtime from scratch"
- >
docker build
--target=builder
--cache-from $IMAGE_TAG-builder
-t $IMAGE_TAG-builder .
- >
docker build
--cache-from $IMAGE_TAG
--cache-from $IMAGE_TAG-builder
--build-arg GIT_COMMIT_TAG="$CI_COMMIT_SHA $(TZ=':Asia/Ho_Chi_Minh' date)"
-t $IMAGE_TAG .
- docker push $IMAGE_TAG-builder
- docker push $IMAGE_TAG
only:
- dev
# - master
deploy_dev:
stage: deploy
image: thanhnguyenit/docker-helm
environment:
name: development
before_script:
- mkdir -p /etc/deploy
- eval ${DEV_GET_KUBECONFIG} > ${KUBECONFIG}
- helm init --client-only
# Escape URI
- export DEV_MONGO_URI=$(echo ${DEV_MONGO_URI} | sed -e "s/\,/\\\,/g" - )
- export DEV_MONGO_URI_TESTS=$(echo ${DEV_MONGO_URI_TESTS} | sed -e "s/\,/\\\,/g" - )
- export DEV_RABBITMQ_URI=$(echo ${DEV_RABBITMQ_URI} | sed -e "s/\,/\\\,/g" - )
- export DEV_REDIS_URI=$(echo ${DEV_REDIS_URI} | sed -e "s/\,/\\\,/g" - )
script:
- >
helm upgrade --install
--namespace=${NAMESPACE}
--set "mongo.uri=${DEV_MONGO_URI}"
--set "mongo.uriTest=${DEV_MONGO_URI_TESTS}"
--set "rabbitmq.uri=${DEV_RABBITMQ_URI}"
--set "redis.uri=${DEV_REDIS_URI}"
--set "commitSha=${CI_COMMIT_SHA}"
--set "image.repository=${CI_REGISTRY_IMAGE}"
--set "image.tag=${CI_COMMIT_REF_NAME}"
--set "replicaCount=1"
--set "worker.replicaCount=1"
--set "eventDispatcher.replicaCount=1"
${APP_NAME} ./deploy/helm
only:
- dev
# deploy_prod:
# stage: deploy
# image: thanhnguyenit/docker-helm
# environment:
# name: production
# before_script:
# - mkdir -p /etc/deploy
# - echo ${KUBE_CONFIG} | base64 -d > ${KUBECONFIG}
# - helm init --client-only
# # Escape URI
# - export MONGO_URI=$(echo ${MONGO_URI} | sed -e "s/\,/\\\,/g" - )
# - export MONGO_URI_TESTS=$(echo ${MONGO_URI_TESTS} | sed -e "s/\,/\\\,/g" - )
# - export RABBITMQ_URI=$(echo ${RABBITMQ_URI} | sed -e "s/\,/\\\,/g" - )
# - export REDIS_URI=$(echo ${REDIS_URI} | sed -e "s/\,/\\\,/g" - )
# script:
# - >
# helm upgrade --install
# --namespace=${NAMESPACE}
# --set "mongo.uri=${MONGO_URI}"
# --set "mongo.uriTest=${MONGO_URI_TESTS}"
# --set "rabbitmq.uri=${RABBITMQ_URI}"
# --set "redis.uri=${REDIS_URI}"
# --set "commitSha=${CI_COMMIT_SHA}"
# --set "image.repository=${CI_REGISTRY_IMAGE}"
# --set "image.tag=${CI_COMMIT_REF_NAME}"
# ${APP_NAME} ./deploy/helm
# only:
# - master

@ -0,0 +1,8 @@
FROM node:alpine
WORKDIR /
RUN apk add --no-cache git openssh
COPY . .
RUN yarn
RUN yarn build
CMD ["yarn", "docker:start"]

@ -0,0 +1,82 @@
# B2C Backend File
[![pipeline status](https://gitlab.com/csell-team/b2c/sv-backend-file/badges/master/pipeline.svg)](https://gitlab.com/csell-team/b2c/sv-backend-file/commits/master)
Microservice handles all file logics
## Requirements
- [Node v8+](https://nodejs.org/en/download/current/) or [Docker](https://www.docker.com/)
- [Yarn](https://yarnpkg.com/en/docs/install)
## Getting Started
Install dependencies:
```bash
yarn
```
Set environment variables:
```bash
cp .env.example .env
```
## Running Locally
```bash
yarn dev
```
## Running in Production
```bash
yarn start
```
## Lint
```bash
# lint code with ESLint
yarn lint
# try to fix ESLint errors
yarn lint:fix
# lint and watch for changes
yarn lint:watch
```
## Test
```bash
# run all tests with Mocha
yarn test
# run unit tests
yarn test:unit
# run integration tests
yarn test:integration
# run all tests and watch for changes
yarn test:watch
# open nyc test coverage reports
yarn coverage
```
## Validate
```bash
# run lint and tests
yarn validate
```
## Logs
```bash
# show logs in production
pm2 logs
```

@ -0,0 +1,6 @@
version: "2"
services:
sv-backend-product:
environment:
- NODE_ENV=development
command: yarn dev-event-dispatcher -- -L

@ -0,0 +1,6 @@
version: "2"
services:
sv-backend-product:
environment:
- NODE_ENV=development
command: yarn dev-worker -- -L

@ -0,0 +1,6 @@
version: "2"
services:
sv-backend-product:
environment:
- NODE_ENV=development
command: yarn dev -- -L

@ -0,0 +1,26 @@
version: "2"
services:
sv-backend-product:
container_name: sv_backend_product
build: .
environment:
- PORT=6000
- POSTGRES_URI=postgres://admin:admin@postgresql:5432/b2c
- POSTGRES_URI_TEST=postgres://admin:admin@postgresql:5432/b2c-test
- MONGO_URI=mongodb://mongo:27017/b2c-event?authSource=admin
- MONGO_URI_TEST=mongodb://mongo:27017/b2c-event-test?authSource=admin
- RABBITMQ_URI=amqp://rabbitmq:5672
- REDIS_URI=redis://redis:6379
- MANAGER_SERVICE_URL=https://sv_backend_manager
user: "1000:1000"
volumes:
- .:/app
ports:
- "6001:6000"
external_links:
- mongodb
networks:
- docker_sv_backend_common
networks:
docker_sv_backend_common:
external: true

@ -0,0 +1,109 @@
{
"name": "backend-image",
"version": "1.0.0",
"description": "Service backend image",
"author": "Mạnh Tiến",
"main": "/src/index.js",
"private": true,
"license": "MIT",
"engines": {
"node": ">=8",
"yarn": "*"
},
"nyc": {
"require": [
"babel-register"
],
"sourceMap": false,
"instrument": false
},
"scripts": {
"precommit": "yarn lint",
"clean": "./node_modules/.bin/rimraf dist -p",
"build": "yarn run clean && mkdir -p dist && ./node_modules/.bin/babel src -s -D -d dist",
"start": "cross-env NODE_ENV=production pm2 start ./dist/index.js",
"start-worker": "cross-env NODE_ENV=production pm2 start docker-process.yml --only worker",
"start-event-dispatcher": "cross-env NODE_ENV=production pm2 start docker-process.yml --only event-dispatcher",
"dev": "nodemon src/index.js --exec ./node_modules/.bin/babel-node",
"dev-worker": "nodemon src/index-worker.js --exec ./node_modules/.bin/babel-node",
"dev-event-dispatcher": "nodemon src/index-event-dispatcher.js --exec ./node_modules/.bin/babel-node",
"lint": "eslint **/*.js --ignore-path .gitignore --ignore-pattern internals/scripts",
"lint:fix": "yarn lint -- --fix",
"lint:watch": "yarn lint -- --watch",
"test": "cross-env NODE_ENV=test nyc --reporter=html --reporter=text mocha --timeout 20000 --recursive src/api/tests",
"test:unit": "cross-env NODE_ENV=test mocha dist/api/tests/unit",
"test:integration": "cross-env NODE_ENV=test mocha --timeout 20000 dist/api/tests/integration",
"test:watch": "cross-env NODE_ENV=test mocha --watch dist/api/tests/unit",
"coverage": "nyc report --reporter=text-lcov | coveralls",
"validate": "yarn lint && yarn test",
"postpublish": "git push --tags",
"docker:start": "node ./dist/index.js --exec ./node_modules/.bin/babel-node",
"docker:dev": "docker-compose -f docker-compose.yml -f docker-compose.dev.yml up",
"docker:test": "docker-compose -f docker-compose.yml -f docker-compose.test.yml up --abort-on-container-exit"
},
"repository": {
"type": "git",
"url": "git@gitlab.com:csell-team/b2c/sv-backend-file.git"
},
"dependencies": {
"amqplib": "^0.5.2",
"auth-adapter": "1.1.0",
"axios": "^0.18.0",
"bcryptjs": "^2.4.3",
"bluebird": "^3.5.2",
"body-parser": "^1.17.0",
"bull": "^3.4.1",
"busboy": "^1.6.0",
"compression": "^1.6.2",
"cors": "^2.8.3",
"cross-env": "^5.0.1",
"dotenv-safe": "^5.0.1",
"exceljs": "^4.3.0",
"express": "^4.15.2",
"express-validation": "^1.0.2",
"fs-extra": "^10.1.0",
"helmet": "^3.5.0",
"http-status": "^1.0.1",
"i18n": "^0.8.3",
"image-downloader": "^4.3.0",
"ioredis": "^4.14.0",
"joi": "^10.4.1",
"jsonwebtoken": "^8.5.1",
"lodash": "^4.17.4",
"method-override": "^2.3.8",
"moment-timezone": "^0.5.13",
"mongoose": "^5.7.0",
"morgan": "^1.8.1",
"multer": "^1.4.2",
"nanoid": "^2.0.3",
"pg": "^8.5.1",
"pg-hstore": "^2.3.3",
"pm2": "^2.4.6",
"query-string": "^7.0.0",
"rabbit-event-source": "1.0.0",
"request": "^2.88.2",
"sequelize": "^6.3.5",
"sharp": "^0.30.6",
"uuid": "^9.0.0",
"xlsx": "^0.16.9"
},
"devDependencies": {
"babel-cli": "^6.26.0",
"babel-plugin-istanbul": "^4.1.6",
"babel-preset-env": "^1.6.1",
"chai": "^4.1.0",
"chai-as-promised": "^7.1.1",
"coveralls": "^3.0.0",
"eslint": "^4.2.0",
"eslint-config-airbnb-base": "^12.0.1",
"eslint-plugin-import": "^2.2.0",
"husky": "^0.14.3",
"mocha": "^3.3.0",
"nodemon": "^1.11.0",
"nyc": "^11.0.3",
"rimraf": "^2.6.2",
"sinon": "^6.1.0",
"sinon-chai": "^3.0.0",
"supertest": "^3.0.0"
}
}

@ -0,0 +1,19 @@
apps:
- script : './dist/index.js'
name : 'file-api'
min_uptime: 10000
max_restarts: 15
restart_delay: 2000
kill_timeout: 3000
- script : './dist/index-event-dispatcher.js'
name : 'file-event-dispatcher'
min_uptime: 10000
max_restarts: 15
restart_delay: 2000
kill_timeout: 3000
- script : './dist/index-worker.js'
name : 'file-worker'
min_uptime: 10000
max_restarts: 15
restart_delay: 2000
kill_timeout: 3000

@ -0,0 +1,19 @@
apps:
- script : './dist/index.js'
name : 'file-api'
min_uptime: 10000
max_restarts: 15
restart_delay: 2000
kill_timeout: 3000
- script : './dist/index-event-dispatcher.js'
name : 'file-event-dispatcher'
min_uptime: 10000
max_restarts: 15
restart_delay: 2000
kill_timeout: 3000
- script : './dist/index-worker.js'
name : 'file-worker'
min_uptime: 10000
max_restarts: 15
restart_delay: 2000
kill_timeout: 3000

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 KiB

@ -0,0 +1,32 @@
import messages from '../../../config/messages';
import { handler as ErrorHandler } from '../../middlewares/error';
import User from '../../../common/models/user.model';
exports.register = async (req, res, next) => {
const user = new User(
req.body
);
user.save()
.then(data => {
res.json({
code: 0,
message: messages.CREATE_SUCCESS,
data: User.transform(data)
});
}).catch(ex => {
ErrorHandler(ex, req, res, next);
});
};
exports.login = async (req, res, next) => {
res.json({
code: 0,
message: null,
data: { token: req.locals.token, user: req.locals.user }
});
};
exports.loginToken = async (req, res, next) => res.json({
code: 0,
message: messages.CREATE_SUCCESS,
data: { token: req.locals.token, data: req.locals.user }
});

@ -0,0 +1,95 @@
/* eslint-disable camelcase */
import path from 'path';
import Busboy from 'busboy';
import fs from 'fs-extra';
import multer from 'multer';
import httpStatus from 'http-status';
import moment from 'moment-timezone';
import { handler as ErrorHandel } from '../../middlewares/errors';
import ApiException from '../../../common/utils/APIException';
import eventBus from '../../../common/services/event-bus';
import Image from '../../../common/models/image.model';
import {
cdn as cdnConfig,
storage as storageConfig
} from '../../../config/vars';
/** storage will create folder when new date */
const date = new Date();
const year = moment(date).format('YYYY');
const month = moment(date).format('MM');
const filePath = `${storageConfig.uri}/${year}/${month}/upload`;
const replaceBaseUrl = (location) =>
location.replace(storageConfig.uri, cdnConfig.uri);
exports.uploadSingle = (req, res, next) => {
try {
if (!req.file) {
throw new ApiException({
status: httpStatus.BAD_REQUEST,
message: 'Invalid file!'
});
}
/** resize image uploaded */
// eventBus.emit(Image.Events.IMAGE_CREATED, req.file);
return res.json({ url: replaceBaseUrl(req.file.path) });
} catch (ex) {
return ErrorHandel(ex, req, res, next);
}
};
/**
* Upload multiple
*
* @param {Formdata} file
*/
exports.uploadMultiple = (req, res, next) => {
try {
if (!req.files) {
throw new ApiException({
status: httpStatus.BAD_REQUEST,
message: 'Invalid file!'
});
}
const urls = [];
for (let index = 0; index < req.files.length; index += 1) {
urls.push(replaceBaseUrl(req.files[index].path));
/** resize image uploaded */
eventBus.emit(Image.Events.IMAGE_CREATED, req.files[index]);
}
return res.json({ urls: urls });
} catch (ex) {
return ErrorHandel(ex, req, res, next);
}
};
exports.uploadFile = (req, res, next) => {
try {
let filename = null;
const cfg = { highWaterMark: 2097152 };
cfg.headers = req.headers;
req.busboy = Busboy(cfg);
multer({ dest: `${filePath}` });
req.pipe(req.busboy); // Pipe it trough busboy
return req.busboy.on('file', (name, file, info) => {
filename = info.filename;
console.log('start', filename);
// Create a write stream of the new file
const fstream = fs.createWriteStream(path.join(filePath, filename));
// Pipe it trough
file.pipe(fstream);
// On finish of the upload
fstream.on('close', () => {
console.log(`Upload of '${filename}' finished`);
return res.json({ url: replaceBaseUrl(`${filePath}/${filename}`) });
});
});
} catch (ex) {
return ErrorHandel(ex, req, res, next);
}
};

@ -0,0 +1,111 @@
// import httpStatus from 'http-status';
import fs from 'fs';
import { handler as ErrorHandel } from '../../middlewares/errors';
// import ApiException from '../../../common/utils/APIException';
import {
cdn as cdnConfig,
storage as storageConfig
} from '../../../config/vars';
import uploadAdapter from '../../../common/services/adapters/upload-adapter';
/**
* get file and folder
*
* @param {Formdata} file
*/
exports.get = (req, res, next) => {
try {
const user = req.user;
let path = `${storageConfig.uri}/${user.id}`;
// let path = storageConfig.uri;
if (req.body.path) {
path += req.body.path;
}
const listFile = [];
fs.readdir(path, (err, files) => {
if (files && files.length > 0) {
files.forEach((item) => {
listFile.push({
name: item,
path: `${cdnConfig.uri}/${user.id}${req.body.path}/${item}`,
isFolder: fs.lstatSync(`${storageConfig.uri}/${user.id}/${req.body.path}/${item}`).isDirectory()
});
});
}
return res.json({
code: 0,
data: listFile
});
});
return null;
/** resize image uploaded */
} catch (ex) {
return ErrorHandel(ex, req, res, next);
}
};
/**
* get file and folder
*
* @param {Formdata} file
*/
exports.create = (req, res, next) => {
const user = req.user;
let dir = `${user.id}`;
console.log(dir);
if (req.body.path) {
dir += req.body.path;
}
if (req.body.name) {
dir += `/${req.body.name}`;
}
if (!fs.existsSync(dir)) {
uploadAdapter.createFolder({ path: dir });
}
return res.json({ code: 0, message: 'success' });
};
/**
* get file and folder
*
* @param {Formdata} file
*/
exports.update = (req, res, next) => {
try {
console.log(cdnConfig.uri, storageConfig.uri);
const oldPath = req.body.oldPath.replace(cdnConfig.uri, storageConfig.uri);
const newPath = req.body.newPath.replace(cdnConfig.uri, storageConfig.uri);
console.log('aaa', oldPath, newPath);
fs.rename(oldPath, newPath, (err) => {
if (err) {
console.log(err);
return res.status(400).json({ code: 400, message: 'lỗi' });
}
return res.json({ code: 0, message: 'success' });
});
return null;
} catch (ex) {
return ErrorHandel(ex, req, res, next);
}
};
exports.delete = (req, res, next) => {
try {
const path = req.body.path.replace(cdnConfig.uri, storageConfig.uri);
fs.rm(path, { recursive: true }, err => {
if (err) {
return res.status(400).json({ code: 400, message: 'lỗi', detail: err });
}
return res.json({ code: 0, message: 'success' });
});
return null;
} catch (ex) {
return ErrorHandel(ex, req, res, next);
}
};

@ -0,0 +1,177 @@
import { pick } from 'lodash';
// import httpStatus from 'http-status';
import messages from '../../../config/messages';
import { handler as ErrorHandler } from '../../middlewares/error';
import User from '../../../common/models/user.model';
/**
* Create
*
* @public
* @param {StorySchema} body
* @returns {Promise<StorySchema>, APIException>}
*/
exports.create = async (req, res, next) => {
// transform data
req.body.created_by = pick(req.user, ['id', 'name']);
// save data
await User.create(req.body)
.then(data => {
res.json({
code: 0,
message: messages.CREATE_SUCCESS,
data: User.transform(data)
});
}).catch(ex => {
ErrorHandler(ex, req, res, next);
});
};
/**
* List
*
* @public
* @param {StorySchema} query
* @returns {Promise<StorySchema[]>, APIException>}
*/
exports.list = async (req, res, next) => {
// req.query.types = User.Types.STAFF;
User.list(
req.query
).then(result => {
res.json({
code: 0,
count: req.totalRecords,
data: result.map(
x => User.transform(x)
)
});
}).catch(ex => {
ErrorHandler(ex, req, res, next);
});
};
/**
* Detail
*
* @public
* @param {params} userId,
* @returns {Promise<StorySchema>, APIException>}
*/
exports.get = async (req, res, next) => res.json({ data: User.transform(req.locals.user, true) });
/**
* Update
*
* @public
* @param {params} userId
* @returns {Promise<any>, APIException>}
*/
exports.update = async (req, res, next) => {
const { user } = req.locals;
return User.update(
req.body,
{
where: {
id: user.id
}
}
).then(() => {
res.json({
code: 0,
message: messages.UPDATE_SUCCESS
});
}).catch(ex => {
ErrorHandler(ex, req, res, next);
});
};
/**
* delete
*
* @public
* @param {params} userId
* @returns {Promise<any>, APIException>}
*/
exports.delete = async (req, res, next) => {
const { user } = req.locals;
return User.update(
{
is_active: false,
updated_at: new Date(),
updated_by: pick(req.user, ['id', 'name'])
},
{
where: {
id: user.id
}
}
).then(() => {
res.json({
code: 0,
message: messages.REMOVE_SUCCESS
});
}).catch(ex => {
ErrorHandler(ex, req, res, next);
});
};
exports.getStaffPermission = async (req, res, next) => {
const { story } = req.locals;
return res.json({
code: 0,
data: story
});
};
exports.block = async (req, res, next) => {
const { user } = req.locals;
return User.update(
{
status: User.Statuses.INACTIVE,
status_name: User.NameStatuses.INACTIVE,
updated_at: new Date(),
updated_by: pick(req.user, ['id', 'name'])
},
{
where: {
id: user.id
}
}
).then(() => {
res.json({
code: 0,
message: messages.UPDATE_SUCCESS
});
}).catch(ex => {
ErrorHandler(ex, req, res, next);
});
};
exports.active = async (req, res, next) => {
const { user } = req.locals;
return User.update(
{
status: User.Statuses.ACTIVE,
status_name: User.NameStatuses.INACTIVE,
updated_at: new Date(),
updated_by: pick(req.user, ['id', 'name'])
},
{
where: {
id: user.id
}
}
).then(() => {
res.json({
code: 0,
message: messages.UPDATE_SUCCESS
});
}).catch(ex => {
ErrorHandler(ex, req, res, next);
});
};

@ -0,0 +1,56 @@
import httpStatus from 'http-status';
import { handler as ErrorHandel } from '../../middlewares/errors';
import ApiException from '../../../common/utils/APIException';
import { cdn as cdnConfig, storage as storageConfig } from '../../../config/vars';
const replaceBaseUrl = (location) =>
location.replace(storageConfig.uri, `${cdnConfig.uri}`);
/**
* Generate file
*
* @param {Formdata} file
*/
exports.generate = (req, res, next) => {
try {
if (!req.file) {
throw new ApiException({
status: httpStatus.BAD_REQUEST,
message: 'Invalid file!'
});
}
return res.json({
code: 0,
message: 'Tải dữ liệu thành công !',
data: req.locals ? req.locals.data : null,
url: replaceBaseUrl(req.file.path)
});
} catch (ex) {
return ErrorHandel(ex, req, res, next);
}
};
/**
* Get item
*
* @param {Formdata} file
*/
exports.getItem = (req, res, next) => {
try {
if (!req.file) {
throw new ApiException({
status: httpStatus.BAD_REQUEST,
message: 'Invalid file!'
});
}
return res.json({
code: 0,
data: req.locals.data,
url: replaceBaseUrl(req.file.path)
});
} catch (ex) {
return ErrorHandel(ex, req, res, next);
}
};

@ -0,0 +1,32 @@
import httpStatus from 'http-status';
import { handler as ErrorHandel } from '../../middlewares/errors';
import ApiException from '../../../common/utils/APIException';
import { cdn as cdnConfig, storage as storageConfig } from '../../../config/vars';
const replaceBaseUrl = (location) =>
location.replace(storageConfig.uri, `${cdnConfig.uri}`);
/**
* Generate file
*
* @param {Formdata} file
*/
exports.generate = (req, res, next) => {
try {
if (!req.file) {
throw new ApiException({
status: httpStatus.BAD_REQUEST,
message: 'Invalid file!'
});
}
return res.json({
code: 0,
message: 'Tải dữ liệu thành công !',
data: req.locals ? req.locals.data : null,
url: replaceBaseUrl(req.file.path)
});
} catch (ex) {
return ErrorHandel(ex, req, res, next);
}
};

@ -0,0 +1,134 @@
import { pick } from 'lodash';
// import httpStatus from 'http-status';
import messages from '../../../config/messages';
import { handler as ErrorHandler } from '../../middlewares/error';
import User from '../../../common/models/user.model';
import uploadAdapter from '../../../common/services/adapters/upload-adapter';
/**
* Create
*
* @public
* @param {StorySchema} body
* @returns {Promise<StorySchema>, APIException>}
*/
exports.create = async (req, res, next) => {
// transform data
req.body.created_by = pick(req.user, ['id', 'name']);
const params = req.body;
params.type = User.Types.INDIVIDUAL;
params.service = User.Services.INDIVIDUAL;
// save data
await User.create(req.body)
.then(data => {
uploadAdapter.createDefaultFolder({ id: data.id });
res.json({
code: 0,
message: messages.CREATE_SUCCESS,
data: User.transform(data)
});
}).catch(ex => {
ErrorHandler(ex, req, res, next);
});
};
/**
* List
*
* @public
* @param {StorySchema} query
* @returns {Promise<StorySchema[]>, APIException>}
*/
exports.list = async (req, res, next) => {
User.list(
req.query
).then(result => {
res.json({
code: 0,
count: req.totalRecords,
data: result.map(
x => User.transform(x)
)
});
}).catch(ex => {
ErrorHandler(ex, req, res, next);
});
};
/**
* Detail
*
* @public
* @param {params} userId,
* @returns {Promise<StorySchema>, APIException>}
*/
exports.get = async (req, res, next) => res.json({ data: User.transform(req.locals.user) });
/**
* Update
*
* @public
* @param {params} userId
* @returns {Promise<any>, APIException>}
*/
exports.update = async (req, res, next) => {
const { user } = req.locals;
const dataChanged = user.getChangedProperties(req.body);
const updateUser = Object.assign(
user,
pick(req.body, dataChanged)
);
return User.update(
updateUser,
{
where: {
id: user.id
}
}
).then(() => {
res.json({
code: 0,
message: messages.UPDATE_SUCCESS
});
}).catch(ex => {
ErrorHandler(ex, req, res, next);
});
};
/**
* delete
*
* @public
* @param {params} userId
* @returns {Promise<any>, APIException>}
*/
exports.delete = async (req, res, next) => {
const { user } = req.locals;
const updateUser = Object.assign(
user,
{ is_active: true }
);
return User.update(
updateUser,
{
where: {
id: user.id
}
}
).then(() => {
res.json({
code: 0,
message: messages.REMOVE_SUCCESS
});
}).catch(ex => {
ErrorHandler(ex, req, res, next);
});
};
exports.getStaffPermission = async (req, res, next) => {
const { story } = req.locals;
return res.json({
code: 0,
data: story
});
};

@ -0,0 +1,48 @@
const httpStatus = require('http-status');
/**
* @extends Error
*/
class ExtendableError extends Error {
constructor({ message, errors, status, isPublic, stack }) {
super(message);
this.name = this.constructor.name;
this.message = message;
this.errors = errors;
this.status = status;
this.isPublic = isPublic;
this.isOperational = true; // This is required since bluebird 4 doesn't append it anymore.
this.stack = stack;
// Error.captureStackTrace(this, this.constructor.name);
}
}
/**
* Class representing an API error.
* @extends ExtendableError
*/
class APIError extends ExtendableError {
/**
* Creates an API error.
* @param {string} message - Error message.
* @param {number} status - HTTP status code of error.
* @param {boolean} isPublic - Whether the message should be visible to user or not.
*/
constructor({
message,
errors,
stack,
status = httpStatus.INTERNAL_SERVER_ERROR,
isPublic = false
}) {
super({
message,
errors,
status,
isPublic,
stack
});
}
}
module.exports = APIError;

@ -0,0 +1,331 @@
const httpStatus = require('http-status');
const jsonwentoken = require('jsonwebtoken');
const APIError = require('./ApiError');
const ConsumerGroups = {
/** Service group with all permissions */
SERVICE: 'service',
/** Staff group with RBAC permissions */
STAFF: 'staff',
/** User group with all permissions if granted */
USER: 'user',
/** Guest group */
GUEST: 'guest'
};
/**
* Configuration for authentication module
* @type {Object}
*/
const Configs = {
/** Default permission for user */
PERMISSION_USER: 'user',
/** Default permission for staff */
PERMISSION_LOGGED_IN: 'staff',
/** Default permission for all provider */
PERMISSION_ADMINISTRATOR: 'administrator',
/** Custom header name */
HEADER_NAME: 'Authorization',
/** Include scheme in header */
HEADER_INCLUDE_SCHEME: true,
getStaffPermissions: (staffId) => {
console.log(staffId);
return [];
}
};
const HEADER_REGEX = /(\S+)\s+(\S+)/;
/**
* Get authentication infomation from auth header
*
* @param {*} headerValue
* @returns {Object}
*/
function parseAuthHeader(headerValue) {
if (typeof headerValue !== 'string') {
return null;
}
if (Configs.HEADER_INCLUDE_SCHEME) {
const matches = headerValue.match(HEADER_REGEX);
return (
matches && { scheme: matches[1].trim(), value: matches[2].trim() }
);
}
return {
scheme: 'Bearer',
value: headerValue.trim()
};
}
/**
* Get User info from jwt payload
*
* @param {Object} jwtPayload
* @returns {Object}
*/
function getUserFromJwtPayload(jwtPayload) {
const user = {
id: jwtPayload.id,
name: jwtPayload.name,
phone: jwtPayload.phone,
email: jwtPayload.email,
avatar: jwtPayload.avatar,
service: jwtPayload.service
};
return user;
}
/**
* Check if staff have all requested permission in jwt payload
*
* @param {Request} req
* @param {Array} requestedPermissions
*
* @returns {Boolean}
*/
function checkStaffPermission(req, requestedPermissions) {
// not allow when permissions null
if (requestedPermissions.length === 0) {
return false;
}
// check special requested permissions (LoggedIn)
if (requestedPermissions.includes(Configs.PERMISSION_LOGGED_IN)) {
return true;
}
// check service permissions
const { permissions } = req.user;
if (!Array.isArray(permissions) || permissions.length === 0) {
return false;
}
// check special permission (administrator))
if (permissions.includes(Configs.PERMISSION_ADMINISTRATOR)) {
return true;
}
const deniedPermissions = requestedPermissions.filter(
(permission) => !permissions.includes(permission)
);
return deniedPermissions.length === 0;
}
/**
* Get JWT payload from authorization header
*
* @param {Request} req
*/
const getTokenInfo = (req) => {
let jwt = req.get(Configs.HEADER_NAME);
if (!jwt) {
return null;
}
jwt = parseAuthHeader(jwt);
if (jwt === null) {
return null;
}
jwt.payload = jsonwentoken.decode(jwt.value, { json: true });
return jwt;
};
/**
* Get authentication info from service
*
* @param {Request} req
*/
const getAuthInfo = (req) => {
const { user } = req;
const isAnonymous = (req.get('X-Anonymous-Consumer') || 'false') === 'true';
let consumerGroups = [];
// console.log(user);
if (user) {
consumerGroups = [user.service];
} else {
consumerGroups = [req.get('X-Consumer-Groups')];
}
// let consumerGroups = user ? user.service : req.get('X-Consumer-Groups') || '';
// consumerGroups = consumerGroups.split(',').filter((item) => item.length > 0);
// console.log(consumerGroups);
// Check accessLevel
let accessLevel = ConsumerGroups.GUEST;
const allAccessLevels = Object.values(ConsumerGroups);
for (let index = 0; index < allAccessLevels.length; index += 1) {
if (consumerGroups.includes(allAccessLevels[index])) {
accessLevel = allAccessLevels[index];
break;
}
}
return {
client: req.get('X-Consumer-Username') || null,
clientId: req.get('X-Consumer-Custom-Id') || null,
consumerId: req.get('X-Consumer-ID') || null,
isAnonymous,
accessLevel
};
};
/**
* Load auth info to request
*
* @param {Request} req
*/
const loadInfo = async (req) => {
let user = null;
let tokenInfo = getTokenInfo(req);
// console.log('before', tokenInfo);
if (tokenInfo === null || !tokenInfo.payload) {
tokenInfo = null;
} else {
user = getUserFromJwtPayload(tokenInfo.payload);
}
req.user = user;
req.tokenInfo = tokenInfo;
req.authInfo = getAuthInfo(req);
// load permission for staff
if (req.authInfo.accessLevel === ConsumerGroups.STAFF && user !== null) {
req.user.permissions = await Configs.getStaffPermissions(
user.id
);
}
};
/**
* Check user has required permission
*
* @param {Request} req
* @param {Array} permissions
* @param {Function} additionalCheck
*/
const checkPermission = async (req, permissions, additionalCheck) => {
const apiError = new APIError({
message: 'Unauthorized',
status: httpStatus.UNAUTHORIZED,
stack: undefined
});
/** service permission required */
const permissionsToCheck = Array.isArray(permissions)
? permissions.slice(0)
: [];
// allow if require no permission
if (permissionsToCheck.length === 0) {
return null;
}
// get user permission userPermissionIndex in permission array
const userPermissionIndex = permissionsToCheck.indexOf(
Configs.PERMISSION_USER
);
switch (req.authInfo.accessLevel) {
case ConsumerGroups.SERVICE:
// allow all access with service level
return null;
case ConsumerGroups.STAFF:
// remove user permission
if (userPermissionIndex !== -1) {
permissionsToCheck.splice(userPermissionIndex, 1);
}
if (!checkStaffPermission(req, permissionsToCheck)) {
apiError.status = httpStatus.FORBIDDEN;
apiError.message = 'Forbidden';
return apiError;
}
break;
case ConsumerGroups.USER:
if (permissionsToCheck.indexOf(Configs.PERMISSION_USER) === -1) {
apiError.status = httpStatus.FORBIDDEN;
apiError.message = 'Forbidden';
return apiError;
}
break;
default:
// reject guest access
return apiError;
}
// check permission by additionalCheck (only user and staff)
if (additionalCheck && !(await additionalCheck(req))) {
apiError.status = httpStatus.FORBIDDEN;
apiError.message = 'Forbidden';
return apiError;
}
return null;
};
/**
* Handle JWT token
*
* @param {Request} req
* @param {Response} res
* @param {Function} next
* @param {Array} permissions user-config permission
* @param {Function} additionalCheck additional checking function
*/
const handleJWT = async (
req,
res,
next,
permissions,
additionalCheck = null,
includeCheckPermission = true
) => {
// Load auth info to request
await loadInfo(req);
if (!includeCheckPermission) {
return next();
}
// check user permission
const permissionCheckResult = await checkPermission(
req,
permissions,
additionalCheck
);
if (permissionCheckResult) {
// Throw permission error
return next(permissionCheckResult);
}
return next();
};
/**
* Authenticate middleware with express
*
* @param {Array} permissions
* @param {Function} additionalCheck
* @param {Boolean} includeCheckPermission
*/
const authorize = (permissions, additionalCheck, includeCheckPermission) => (
req,
res,
next
) =>
handleJWT(
req,
res,
next,
permissions,
additionalCheck,
includeCheckPermission
);
module.exports = {
ConsumerGroups,
Configs,
getAuthInfo,
getTokenInfo,
authorize,
checkStaffPermission
};

@ -0,0 +1,109 @@
import { pick } from 'lodash';
import Moment from 'moment-timezone';
import JWT from 'jsonwebtoken';
import { handler as ErrorHandler } from './error';
import User from '../..//common/models/user.model';
/**
* Load item by id add to req locals.
*/
exports.checkEmail = async (req, res, next) => {
try {
const user = await User.getUserByPhoneOrEmailRegister({ email: req.body.email });
if (!user) {
return res.status(400).json({ message: 'Email have aldready exist' });
}
return next();
} catch (ex) {
return ErrorHandler(ex, req, res, next);
}
};
exports.loadUser = async (req, res, next) => {
try {
const user = await User.getUserByPhoneOrEmail({ email: req.body.email || req.body.username });
if (!user) {
return res.status(400).json({ message: 'email or password incorrect' });
}
req.locals = {
user
};
return next();
} catch (ex) {
return ErrorHandler(ex, req, res, next);
}
};
exports.checkPassword = async (req, res, next) => {
const { user } = req.locals;
const isCheck = await User.passwordMatches(user, req.body.password);
if (!isCheck) {
return res.status(400).json({ message: 'Email or password incorrect' });
}
return next();
};
exports.genarateToken = async (req, res, next) => {
let user = null;
user = pick(req.locals.user, ['name', 'avatar', 'email', 'phone', 'service']);
user.id = req.locals.user.id;
// req.locals = {
// user
// };
const inforToken = {};
inforToken.access_token = JWT.sign(user, process.env.NODE_ENV || 'development', { expiresIn: 60 * 60 });
inforToken.refresh_token = JWT.sign(user, process.env.PORT || '3002', { expiresIn: 60 * 60 * 100 });
inforToken.access_expired_at = Moment.tz(new Date(), 'Asia/Ho_Chi_Minh').unix() + (60 * 60 * 24);
inforToken.expRefreshTime = Moment.tz(new Date(), 'Asia/Ho_Chi_Minh').unix();
req.locals.token = inforToken;
return next();
};
exports.loginGoogle = async (req, res, next) => {
if (req.body.type === 'google') {
const user = {};
const decode = await JWT.decode(req.body.token, { json: true });
user.id = decode.sub;
user.name = `${decode.family_name} ${decode.given_name}`;
user.email = decode.email;
user.avatar = decode.picture;
req.locals = { user };
}
return next();
};
exports.loadUserViaThirdParty = async (req, res, next) => {
try {
let find = {};
let user = {};
if (req.body.type === 'google') {
user = req.locals.user;
}
if (req.body.type === 'facebook') {
user = req.body.user;
}
const newuser = user;
switch (req.body.type) {
case 'google':
find = { 'google.id': user.id, isThirdParty: true };
newuser.google = user;
break;
case 'facebook':
find = { 'facebook.id': user.id, isThirdParty: true };
newuser.facebook = user;
break;
default:
find = { 'google.id': user.id, isThirdParty: true };
break;
}
let userExist = await User.findOne(find);
if (!userExist) {
newuser.isThirdParty = true;
const newUser1 = new User(newuser);
userExist = await newUser1.save();
}
req.locals = req.locals ? req.locals : {};
req.locals.user = userExist;
return next();
} catch (ex) {
return ErrorHandler(ex, req, res, next);
}
};

@ -0,0 +1,72 @@
const httpStatus = require('http-status');
const expressValidation = require('express-validation');
const APIError = require('../../common/utils/APIException');
const { env } = require('../../config/vars');
/**
* Error handler. Send stacktrace only during development
* @public
*/
const handler = (err, req, res, next) => {
const { status = 500 } = err;
const response = {
code: status,
message: err.message || httpStatus[status],
errors: err.errors,
stack: err.stack
};
response.message = !err.isTranslated
? res.__(response.message)
: response.message;
if (env !== 'development') {
delete response.stack;
}
// res.status(status);
res.json(response);
res.end();
};
exports.handler = handler;
/**
* If error is not an instanceOf APIError, convert it.
* @public
*/
exports.converter = (err, req, res, next) => {
let convertedError = err;
if (err instanceof expressValidation.ValidationError) {
convertedError = new APIError({
message: res.__('Validation Error!'),
errors: err.errors,
status: err.status,
stack: err.stack,
isTranslated: true
});
} else if (!(err instanceof APIError)) {
convertedError = new APIError({
message: res.__(err.message),
status: err.status,
stack: err.stack,
isTranslated: true
});
}
return handler(convertedError, req, res);
};
/**
* Catch 404 and forward to error handler
* @public
*/
exports.notFound = (req, res, next) => {
const err = new APIError({
message: res.__('Not found!'),
status: httpStatus.NOT_FOUND,
isTranslated: true
});
return handler(err, req, res);
};

@ -0,0 +1,67 @@
const httpStatus = require('http-status');
const expressValidation = require('express-validation');
const APIException = require('../../common/utils/APIException');
/**
* Error handler. Send stacktrace only during development
* @public
*/
const handler = (ex, req, res, next) => {
const { status = 500 } = ex;
const response = {
code: status,
message: ex.message || httpStatus[status],
errors: ex.errors
};
if (ex.stack && process.env.NODE_ENV !== 'development') response.stack = ex.stack;
/* Step:: translate message */
response.message = !ex.isTranslated
? res.__(response.message)
: response.message;
// res.status(status);
res.json(response);
res.end();
};
exports.handler = handler;
/**
* If error is not an instanceOf ApiResult, convert it.
* @public
*/
exports.converter = (err, req, res, next) => {
let convertedError = err;
if (err instanceof expressValidation.ValidationError) {
convertedError = new APIException({
message: res.__('VALIDATION_ERROR'),
errors: err.errors,
status: err.status,
stack: err.stack,
isTranslated: true
});
} else if (!(err instanceof APIException)) {
convertedError = new APIException({
message: res.__(err.message),
status: err.status,
stack: err.stack,
isTranslated: true
});
}
return handler(convertedError, req, res);
};
/**
* Catch 404 and forward to error handler
* @public
*/
exports.notFound = (req, res, next) => {
const err = new APIException({
message: res.__('NOT_FOUND!'),
status: httpStatus.NOT_FOUND,
isTranslated: true
});
return handler(err, req, res);
};

@ -0,0 +1,17 @@
import { handler as ErrorHandler } from './errors';
import Image from '../../common/models/image.model';
/**
* Load image and append to req.
* @public
*/
exports.load = async (req, res, next) => {
try {
const image = await Image.getImageById(req.params.id);
req.locals = req.locals ? req.locals : {};
req.locals.image = image;
return next();
} catch (error) {
return ErrorHandler(error, req, res);
}
};

@ -0,0 +1,113 @@
/* eslint-disable no-param-reassign */
import { cloneDeep, pick } from 'lodash';
import { hash } from 'bcryptjs';
import { handler as ErrorHandler } from './error';
import User from '../../common/models/user.model';
/**
* Converter
* @param {*} str
*/
function convertToEn(str) {
str = str.replace(/à|á|ạ|ả|ã|â|ầ|ấ|ậ|ẩ|ẫ|ă|ằ|ắ|ặ|ẳ|ẵ/g, 'a');
str = str.replace(/è|é|ẹ|ẻ|ẽ|ê|ề|ế|ệ|ể|ễ/g, 'e');
str = str.replace(/ì|í|ị|ỉ|ĩ/g, 'i');
str = str.replace(/ò|ó|ọ|ỏ|õ|ô|ồ|ố|ộ|ổ|ỗ|ơ|ờ|ớ|ợ|ở|ỡ/g, 'o');
str = str.replace(/ù|ú|ụ|ủ|ũ|ư|ừ|ứ|ự|ử|ữ/g, 'u');
str = str.replace(/ỳ|ý|ỵ|ỷ|ỹ/g, 'y');
str = str.replace(/đ/g, 'd');
str = str.replace(/À|Á|Ạ|Ả|Ã|Â|Ầ|Ấ|Ậ|Ẩ|Ẫ|Ă|Ằ|Ắ|Ặ|Ẳ|Ẵ/g, 'A');
str = str.replace(/È|É|Ẹ|Ẻ|Ẽ|Ê|Ề|Ế|Ệ|Ể|Ễ/g, 'E');
str = str.replace(/Ì|Í|Ị|Ỉ|Ĩ/g, 'I');
str = str.replace(/Ò|Ó|Ọ|Ỏ|Õ|Ô|Ồ|Ố|Ộ|Ổ|Ỗ|Ơ|Ờ|Ớ|Ợ|Ở|Ỡ/g, 'O');
str = str.replace(/Ù|Ú|Ụ|Ủ|Ũ|Ư|Ừ|Ứ|Ự|Ử|Ữ/g, 'U');
str = str.replace(/Ỳ|Ý|Ỵ|Ỷ|Ỹ/g, 'Y');
str = str.replace(/Đ/g, 'D');
str = str.toLowerCase();
return str;
}
/**
* Load item by id add to req locals.
*/
exports.load = async (req, res, next) => {
try {
const user = await User.get(req.params.id);
req.locals = req.locals ? req.locals : {};
req.locals.user = user;
return next();
} catch (ex) {
return ErrorHandler(ex, req, res, next);
}
};
/**
* Load count for filter.
*/
exports.count = async (req, res, next) => {
try {
req.query.types = User.Types.STAFF;
req.totalRecords = await User.totalRecords(
req.query
);
return next();
} catch (ex) {
return ErrorHandler(ex, req, res, next);
}
};
/**
* Load item by id add to req locals.
*/
exports.checkEmail = async (req, res, next) => {
try {
const user = await User.findOne({ email: req.body.email });
if (user) {
return res.status(400).json({ message: 'email have aldready exist' });
}
return next();
} catch (ex) {
return ErrorHandler(ex, req, res, next);
}
};
exports.loadUser = async (req, res, next) => {
try {
const user = await User.findOne({ email: req.body.email });
if (!user) {
return res.status(400).json({ message: 'email or password incorrect' });
}
req.locals = {
user
};
return next();
} catch (ex) {
return ErrorHandler(ex, req, res, next);
}
};
exports.prepareParams = async (req, res, next) => {
const params = cloneDeep(req.body);
params.type = User.Types.STAFF;
params.service = User.Services.STAFF;
if (params.name) {
params.normalize_name = convertToEn(`${params.name}`);
}
req.body = params;
next();
};
exports.prepareParamsUpdated = async (req, res, next) => {
// const params = cloneDeep(req.body);
const { user: oldModel } = req.locals;
const params = User.filterParams(req.body);
const dataChanged = User.getChangedProperties({ oldModel, newModel: params });
const paramChanged = pick(params, dataChanged);
if (paramChanged.password) {
const rounds = 10;
paramChanged.password = await hash(paramChanged.password, rounds);
}
if (paramChanged.name) {
paramChanged.normalize_name = convertToEn(`${params.name}`);
}
paramChanged.updated_by = pick(req.user, ['id', 'name']);
paramChanged.updated_at = new Date();
req.body = paramChanged;
next();
};

@ -0,0 +1,88 @@
import { pick } from 'lodash';
import Moment from 'moment-timezone';
import JWT from 'jsonwebtoken';
import { handler as ErrorHandler } from './error';
import User from '../../common/models/user.model';
/**
* Load item by id add to req locals.
*/
exports.load = async (req, res, next) => {
try {
const user = await User.get(req.params.id);
req.locals = req.locals ? req.locals : {};
req.locals.user = user;
return next();
} catch (ex) {
return ErrorHandler(ex, req, res, next);
}
};
/**
* Load count for filter.
*/
exports.count = async (req, res, next) => {
try {
req.totalRecords = await User.totalRecords(
req.query
);
return next();
} catch (ex) {
return ErrorHandler(ex, req, res, next);
}
};
/**
* Load item by id add to req locals.
*/
exports.checkEmail = async (req, res, next) => {
try {
const user = await User.findOne({ where: { email: req.body.email } });
if (user) {
return res.status(400).json({ message: 'email have aldready exist' });
}
return next();
} catch (ex) {
return ErrorHandler(ex, req, res, next);
}
};
exports.loadUser = async (req, res, next) => {
try {
const user = await User.findOne({ where: { email: req.body.email } });
if (!user) {
return res.status(400).json({ message: 'email or password incorrect' });
}
req.locals = {
user
};
return next();
} catch (ex) {
return ErrorHandler(ex, req, res, next);
}
};
exports.checkPassword = async (req, res, next) => {
const { user } = req.locals;
const isCheck = await user.passwordMatch(req.body.password);
if (!isCheck) {
return res.status(400).json({ message: 'email or password incorrect' });
}
return next();
};
exports.genarateToken = async (req, res, next) => {
let user = null;
user = pick(req.locals.user, ['name', 'avatar', 'email', 'phone', 'service']);
user.id = req.locals.user._id;
req.locals = {
user
};
const inforToken = {};
inforToken.token = JWT.sign(user, process.env.NODE_ENV || 'development', { expiresIn: 60 * 60 });
inforToken.refresToken = JWT.sign(user, process.env.PORT || '3002', { expiresIn: 60 * 60 * 1000 });
inforToken.access_expired_at = Moment.tz(new Date(), 'Asia/Ho_Chi_Minh').unix() + (60 * 60 * 1000);
inforToken.expRefreshTime = Moment.tz(new Date(), 'Asia/Ho_Chi_Minh').unix();
req.locals.token = inforToken;
return next();
};
// exports.loginFacebook= async (req,res,next)=>{
// }

@ -0,0 +1,38 @@
import express from 'express';
// import { authorize } from 'auth-adapter';
import validate from 'express-validation';
import middleware from '../../middlewares/authen.middleware';
import controller from '../../controllers/v1/auth.controller';
// import permissions from '../../../common/utils/Permissions';
import {
loginToken,
registerValidation,
loginValidation,
} from '../../validations/v1/auth.validation';
const router = express.Router();
router.route('/register')
.post(
validate(registerValidation),
middleware.checkEmail,
// middleware.genarateToken,
controller.register
);
router.route('/login-password')
.post(
validate(loginValidation),
middleware.loadUser,
middleware.checkPassword,
middleware.genarateToken,
controller.login
);
router.route('/login-token')
.post(
validate(loginToken),
middleware.loginGoogle,
middleware.loadUserViaThirdParty,
middleware.genarateToken,
controller.loginToken
);
export default router;

@ -0,0 +1,38 @@
import express from 'express';
import validate from 'express-validation';
// import { authorize } from '../../middlewares/auth.middleware';
// import Permissions from '../../../common/utils/Permissions';
import { uploader } from '../../../common/services/adapters/upload-adapter';
import controller from '../../controllers/v1/image.controller';
import {
uploadValidation
} from '../../validations/v1/image.validation';
const router = express.Router();
router
.route('/upload-single')
.post(
// authorize([Permissions.IMAGE_UPLOAD]),
// validate(uploadValidation),
uploader.single('file'),
controller.uploadSingle
);
router
.route('/upload-multiple')
.post(
// authorize([Permissions.IMAGE_UPLOAD]),
validate(uploadValidation),
uploader.array('file', 10),
controller.uploadMultiple
);
router
.route('/upload-file')
.post(
// authorize([Permissions.IMAGE_UPLOAD]),
controller.uploadFile
);
export default router;

@ -0,0 +1,26 @@
import { Router } from 'express';
import iamgeRoutes from './image.route';
import pathRoutes from './path.route';
import authRoutes from './auth.route';
import staffRoutes from './staff.route';
import userRoutes from './user.route';
const router = Router();
/**
* GET v1/status
*/
router.get('/status', (req, res) => res.send('OK'));
router.get('/version/:service', (req, res) => res.send(process.env.GIT_COMMIT_TAG || 'Not available'));
/**
* v1/images
*/
router.use('/images', iamgeRoutes);
router.use('/paths', pathRoutes);
router.use('/auth', authRoutes);
router.use('/staffs', staffRoutes);
router.use('/users', userRoutes);
export default router;

@ -0,0 +1,32 @@
import express from 'express';
import { authorize } from '../../middlewares/auth.middleware';
import controller from '../../controllers/v1/path.controller';
import Permissions from '../../../common/utils/Permissions';
const router = express.Router();
router
.route('/')
.post(
authorize([Permissions.USER]),
controller.get
);
router
.route('/create')
.post(
authorize([Permissions.USER]),
controller.create
);
router
.route('/update')
.put(
authorize([Permissions.USER]),
controller.update
);
router
.route('/delete')
.patch(
authorize([Permissions.USER]),
controller.delete
);
export default router;

@ -0,0 +1,63 @@
import express from 'express';
import validate from 'express-validation';
import middleware from '../../middlewares/staff.middleware';
import controller from '../../controllers/v1/staff.controller';
import permissions from '../../../common/utils/Permissions';
import { authorize } from '../../middlewares/auth.middleware';
import {
// listValidation,
updateValidation,
createValidation
} from '../../validations/v1/staff.validation';
const router = express.Router();
router
.route('/')
.get(
authorize([permissions.LOGGED_IN]),
middleware.count,
controller.list
)
.post(
validate(createValidation),
// authorize([permissions.LOGGED_IN]),
middleware.prepareParams,
controller.create
);
router
.route('/:id')
.get(
middleware.load,
controller.get
)
.put(
validate(updateValidation),
authorize([permissions.LOGGED_IN]),
middleware.load,
middleware.prepareParamsUpdated,
controller.update
)
.delete(
authorize([permissions.LOGGED_IN]),
middleware.load,
controller.delete
);
router
.route('/:id/block')
.post(
authorize([permissions.LOGGED_IN]),
middleware.load,
controller.block
);
router
.route('/:id/active')
.post(
authorize([permissions.LOGGED_IN]),
middleware.load,
controller.active
);
export default router;

@ -0,0 +1,46 @@
import express from 'express';
import validate from 'express-validation';
import { authorize } from '../../middlewares/auth.middleware';
import middleware from '../../middlewares/user.middleware';
import controller from '../../controllers/v1/user.controller';
import permissions from '../../../common/utils/Permissions';
import {
listValidation,
updateValidation,
createValidation,
} from '../../validations/v1/user.validation';
const router = express.Router();
router
.route('/')
.get(
validate(listValidation),
middleware.count,
controller.list
)
.post(
validate(createValidation),
// authorize([permissions.USER_CREATE]),
middleware.checkEmail,
controller.create
);
router
.route('/:id')
.get(
middleware.load,
controller.get
)
.put(
validate(updateValidation),
authorize([permissions.USER_UPDATE]),
middleware.load,
controller.update
)
.delete(
authorize([permissions.USER_DELETE]),
middleware.load,
controller.delete
);
export default router;

@ -0,0 +1,33 @@
import Joi from 'joi';
module.exports = {
registerValidation: {
body: {
name: Joi.string()
.max(255)
.required(),
email: Joi.string()
.required(),
phone: Joi.string()
.required(),
password: Joi.string()
.required()
}
},
loginValidation: {
body: {
email: Joi.string(),
username: Joi.string(),
password: Joi.string()
.required()
}
},
loginToken: {
body: {
type: Joi.string().only(['google', 'facebook']),
token: Joi.string(),
user: Joi.object()
}
}
};

@ -0,0 +1,14 @@
import Joi from 'joi';
import { values } from 'lodash';
import Image from '../../../common/models/image.model';
module.exports = {
// POST v1/excels/read-file
readFile: {
query: {
group: Joi.string()
.only(values(Image.Groups))
.required()
}
}
};

@ -0,0 +1,39 @@
import Joi from 'joi';
import { values } from 'lodash';
import Image from '../../../common/models/image.model';
module.exports = {
// POST v1/images
uploadValidation: {
query: {
group: Joi.string()
.only(values(Image.Groups))
.required()
}
},
// POST v1/images
createValidation: {
body: {
image: Joi.string()
.required(),
alt: Joi.string()
.allow(''),
title: Joi.string()
.allow('')
}
},
// PUT v1/images
updateValidation: {
body: {
image: Joi.string()
.required(),
alt: Joi.string()
.allow(''),
title: Joi.string()
.allow('')
}
}
};

@ -0,0 +1,69 @@
import Joi from 'joi';
module.exports = {
listValidation: {
query: {
skip: Joi.number()
.default(0)
.allow(null, ''),
limit: Joi.number()
.default(20)
.allow(null, ''),
sortBy: Joi.string()
.only([
'createdAt',
'updatedAt',
])
.allow(null, ''),
orderBy: Joi.string()
.only(['desc', 'asc']),
keyword: Joi.string()
.trim()
.allow(null, ''),
vips: Joi.string()
.allow(null, ''),
levels: Joi.string()
.allow(null, ''),
genders: Joi.string()
.allow(null, ''),
isActive: Joi.bool()
.allow(null, '')
}
},
createValidation: {
body: {
name: Joi.string()
.max(255)
.required(),
phone: Joi.string()
.max(155),
// .required(),
email: Joi.string()
.allow(null, '')
.required(),
gender: Joi.string()
.allow(null, ''),
type: Joi.string()
.max(255),
password: Joi.string().min(6)
}
},
updateValidation: {
body: {
name: Joi.string()
.max(255)
.required(),
phone: Joi.string()
.max(155)
.required(),
email: Joi.string()
.allow(null, ''),
gender: Joi.string()
.allow(null, ''),
type: Joi.string()
.max(255),
password: Joi.string().min(6)
}
}
};

@ -0,0 +1,96 @@
import Joi from 'joi';
module.exports = {
listValidation: {
query: {
skip: Joi.number()
.default(0)
.allow(null, ''),
limit: Joi.number()
.default(20)
.allow(null, ''),
sortBy: Joi.string()
.only([
// count
'vip',
'level',
// date
'createdAt',
'updatedAt',
])
.allow(null, ''),
orderBy: Joi.string()
.only(['desc', 'asc']),
keyword: Joi.string()
.trim()
.allow(null, ''),
vips: Joi.string()
.allow(null, ''),
levels: Joi.string()
.allow(null, ''),
genders: Joi.string()
.allow(null, ''),
isActive: Joi.bool()
.allow(null, '')
}
},
createValidation: {
body: {
name: Joi.string()
.max(255)
.required(),
email: Joi.string()
.required()
.allow(null, ''),
gender: Joi.string()
.allow(null, ''),
type: Joi.string()
.max(255)
}
},
updateValidation: {
body: {
// attribute
slug: Joi.string()
.max(255)
.allow(null, ''),
title: Joi.string()
.max(155)
.allow(null, ''),
content: Joi.string()
.allow(null, ''),
cover: Joi.string()
.max(255)
.allow(null, ''),
thumbnail: Joi.string()
.max(255)
.allow(null, ''),
chapLabel: Joi.string()
.allow(null, ''),
categories: Joi.array()
.items({
id: Joi.string().required(),
name: Joi.string().required(),
slug: Joi.string().allow(null, ''),
logo: Joi.string().allow(null, '')
})
.allow(null, ''),
genders: Joi.array()
.items(Joi.string())
.allow(null, ''),
// config
isTopHot: Joi.bool()
.allow(null, ''),
isHotTrend: Joi.bool()
.allow(null, ''),
isRecommend: Joi.bool()
.allow(null, ''),
isWidespread: Joi.bool()
.allow(null, ''),
isActive: Joi.bool()
.allow(null, '')
}
}
};

@ -0,0 +1,408 @@
/* eslint-disable camelcase */
import httpStatus from 'http-status';
import { Model, DataTypes, Op } from 'sequelize';
import { isEqual, isNil, isUndefined, omitBy, pick } from 'lodash';
import moment from 'moment-timezone';
import { serviceName } from '../../config/vars';
import postgres from '../../config/postgres';
import APIError from '../utils/APIException';
/**
* Create connection
*/
const { sequelize } = postgres;
class FileConfig extends Model { }
const PUBLIC_FIELDS = [
'name',
'type',
'group',
'config'
];
FileConfig.Groups = {
PRODUCT: 'product',
PRODUCT_OPTION: 'product-option',
PRODUCT_PRICE: 'product-price',
ORDER: 'order',
ORDER_NESTED: 'order-nested',
INVOICE: 'invoice',
INVOICE_NESTED: 'invoice-nested',
RETURN: 'return',
RETURN_NESTED: 'return-nested',
DELIVERY: 'delivery',
IMPORT: 'import',
IMPORT_NESTED: 'import-nested',
STOCK_TAKE: 'stock-take',
STOCK_TAKE_NESTED: 'stock-take-nested',
TRANSFER: 'transfer',
TRANSFER_NESTED: 'transfer-nested',
EXPORT: 'export',
EXPORT_NESTED: 'export-nested',
PAYMENT: 'payment',
CUSTOMER: 'customer',
SUPPLIER: 'supplier',
DELIVERY_PAYMENT: 'delivery-payment',
DELIVERY_PAYMENT_NESTED: 'delivery-payment-nested',
/** Sale Report */
SALE_REPORT_TIME: 'sale-report-time',
SALE_REPORT_TIME_NESTED: 'sale-report-time-nested',
SALE_REPORT_TIME_INVOICE: 'sale-report-time-invoice',
SALE_REPORT_PROFIT: 'sale-report-profit',
SALE_REPORT_PROFIT_NESTED: 'sale-report-profit-nested',
SALE_REPORT_PROFIT_INVOICE: 'sale-report-profit-invoice',
SALE_REPORT_PROFIT_PRODUCT: 'sale-report-profit-product',
SALE_REPORT_DISCOUNT: 'sale-report-discount',
SALE_REPORT_DISCOUNT_NESTED: 'sale-report-discount-nested',
SALE_REPORT_DISCOUNT_INVOICE: 'sale-report-discount-invoice',
SALE_REPORT_RETURN: 'sale-report-return',
SALE_REPORT_RETURN_NESTED: 'sale-report-return-nested',
SALE_REPORT_RETURN_INVOICE: 'sale-report-return-invoice',
SALE_REPORT_STAFF: 'sale-report-staff',
SALE_REPORT_STAFF_NESTED: 'sale-report-staff-nested',
SALE_REPORT_STAFF_TIME: 'sale-report-staff-time',
SALE_REPORT_STAFF_TIME_NESTED: 'sale-report-staff-time-nested',
SALE_REPORT_STAFF_INVOICE: 'sale-report-staff-invoice',
SALE_REPORT_STORE: 'sale-report-store',
SALE_REPORT_STORE_NESTED: 'sale-report-store-nested',
SALE_REPORT_STORE_TIME: 'sale-report-store-time',
SALE_REPORT_STORE_TIME_NESTED: 'sale-report-store-time-nested',
SALE_REPORT_STORE_INVOICE: 'sale-report-store-invoice',
/** product report */
PRODUCT_REPORT_SALE: 'product-report-sale',
PRODUCT_REPORT_SALE_SPECIFIC: 'product-report-sale-specific',
PRODUCT_REPORT_SALE_GROUP_CATEGORIES: 'product-report-sale-group-categories',
PRODUCT_REPORT_SALE_DETAIL: 'product-report-sale-detail',
PRODUCT_REPORT_PROFIT: 'product-report-profit',
PRODUCT_REPORT_PROFIT_GROUP_CATEGORIES: 'product-report-profit-group-categories',
PRODUCT_REPORT_STOCK_VALUE: 'product-report-stock-value',
PRODUCT_REPORT_STOCK_VALUE_GROUP_CATEGORIES: 'product-report-stock-value-group-categories',
PRODUCT_REPORT_STOCK_VALUE_DETAIL: 'product-report-stock-value-detail',
PRODUCT_REPORT_STOCK_VALUE_STORE: 'product-report-stock-value-store',
PRODUCT_REPORT_STOCK_VALUE_GENERAL: 'product-report-stock-value-general',
PRODUCT_REPORT_STOCK: 'product-report-stock',
PRODUCT_REPORT_STOCK_STORE: 'product-report-stock-store',
PRODUCT_REPORT_STOCK_ONE_STORE: 'product-report-stock-one-store',
PRODUCT_REPORT_STOCK_GROUP_CATEGORIES: 'product-report-stock-group-categories',
PRODUCT_REPORT_STOCK_MORE_STORE: 'product-report-stock-more-store',
PRODUCT_REPORT_STOCK_DETAIL: 'product-report-stock-detail',
PRODUCT_REPORT_STOCK_DETAIL_STORE: 'product-report-stock-detail-store',
PRODUCT_REPORT_STOCK_DETAIL_DETAIL: 'product-report-stock-detail-detail',
PRODUCT_REPORT_STOCK_DETAIL_GROUP_CATEGORIES: 'product-report-stock-detail-group-categories',
PRODUCT_REPORT_STOCK_DETAIL_GENERAL: 'product-report-stock-detail-general',
PRODUCT_REPORT_STAFF: 'product-report-staff',
PRODUCT_REPORT_STAFF_SPECIFIC: 'product-report-staff-specific',
PRODUCT_REPORT_STAFF_DETAIL: 'product-report-staff-detail',
PRODUCT_REPORT_STAFF_GROUP_CATEGORIES: 'product-report-staff-group-categories',
PRODUCT_REPORT_EXPORT: 'product-report-export',
PRODUCT_REPORT_EXPORT_SPECIFIC: 'product-report-export-specific',
PRODUCT_REPORT_EXPORT_DETAIL: 'product-report-export-detail',
PRODUCT_REPORT_EXPORT_GROUP_CATEGORIES: 'product-report-export-group-categories',
PRODUCT_REPORT_CUSTOMER: 'product-report-customer',
PRODUCT_REPORT_CUSTOMER_SPECIFIC: 'product-report-customer-specific',
PRODUCT_REPORT_CUSTOMER_DETAIL: 'product-report-customer-detail',
PRODUCT_REPORT_CUSTOMER_GROUP_CATEGORIES: 'product-report-customer-group-categories',
PRODUCT_REPORT_SUPPLIER: 'product-report-supplier',
PRODUCT_REPORT_SUPPLIER_DETAIL: 'product-report-supplier-detail',
PRODUCT_REPORT_SUPPLIER_SPECIFIC: 'product-report-supplier-specific',
PRODUCT_REPORT_SUPPLIER_GROUP_CATEGORIES: 'product-report-supplier-group-categories',
PRODUCT_REPORT_STOCK_SPECIFIC: 'product-report-stock-specific',
PRODUCT_REPORT_STOCK_DETAIL_SPECIFIC: 'product-report-stock-detail-specific',
PRODUCT_REPORT_CUSTOMER_INVOICE: 'product-report-customer-invoice',
PRODUCT_REPORT_STAFF_INVOICE: 'product-report-staff-invoice',
/** start end of day report */
END_OF_DAY_REPORT_SALE: 'end-of-day-report-sale',
END_OF_DAY_REPORT_SALE_DETAIL: 'end-of-day-report-sale-detail',
END_OF_DAY_REPORT_SALE_DETAIL_TIME: 'end-of-day-report-sale-detail-time',
END_OF_DAY_REPORT_PAYMENT: 'end-of-day-report-payment',
END_OF_DAY_REPORT_PRODUCT: 'end-of-day-report-product',
END_OF_DAY_REPORT_PRODUCT_DETAIL: 'end-of-day-report-product-detail',
};
FileConfig.Types = {
IMPORT: 'import',
EXPORT: 'export'
};
FileConfig.Configs = [
'name', // file name,
'path', // file path,
'content', // file data
];
/**
* FileConfig Schema
* @public
*/
FileConfig.init(
{
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true
},
name: {
type: DataTypes.STRING(255),
defaultValue: null
},
type: {
type: DataTypes.STRING(50),
allowNull: false
},
group: {
type: DataTypes.STRING(100),
defaultValue: null
},
path: {
type: DataTypes.STRING(155),
defaultValue: null
},
config: {
type: DataTypes.JSONB,
defaultValue: null
},
// manager
is_active: {
type: DataTypes.BOOLEAN,
defaultValue: true
},
created_at: {
type: DataTypes.DATE,
defaultValue: DataTypes.NOW
},
updated_at: {
type: DataTypes.DATE,
defaultValue: DataTypes.NOW
},
created_by: {
type: DataTypes.JSONB,
defaultValue: null // id | name
}
},
{
timestamps: false,
schema: serviceName,
sequelize: sequelize,
modelName: 'file_config',
tableName: 'tbl_file_configs'
}
);
/**
* Register event emiter
*/
FileConfig.Events = {
FILE_CONFIG_CREATED: `${serviceName}.file-config.created`,
FILE_CONFIG_UPDATED: `${serviceName}.file-config.updated`,
FILE_CONFIG_DELETED: `${serviceName}.file-config.deleted`,
};
FileConfig.EVENT_SOURCE = `${serviceName}.file-config`;
/**
* Add your
* - pre-save hooks
* - validations
* - virtuals
*/
FileConfig.addHook('afterCreate', () => { });
FileConfig.addHook('afterUpdate', () => { });
FileConfig.addHook('afterDestroy', () => { });
/**
* Load query
* @param {*} params
*/
function filterConditions(params) {
const options = omitBy(params, isNil);
options.is_active = true;
// TODO: load condition
if (options.name) {
options.name = {
[Op.iLike]: `%${options.name}%`
};
}
return options;
}
/**
* Load sort query
* @param {*} sort_by
* @param {*} order_by
*/
function sortConditions({ sort_by, order_by }) {
let sort = null;
switch (sort_by) {
case 'created_at':
sort = ['created_at', order_by];
break;
case 'updated_at':
sort = ['updated_at', order_by];
break;
default: sort = ['created_at', 'DESC'];
break;
}
return sort;
}
/**
* Transform postgres model to expose object
*/
FileConfig.transform = (params) => {
const transformed = {};
const fields = [
'id',
'name',
'type',
'group',
'config',
'created_by'
];
fields.forEach((field) => {
transformed[field] = params[field];
});
// pipe date
const dateFields = [
'created_at',
'updated_at'
];
dateFields.forEach((field) => {
if (params[field]) {
transformed[field] = moment(params[field]).unix();
} else {
transformed[field] = null;
}
});
return transformed;
};
/**
* Get all changed properties
*/
FileConfig.getChangedProperties = ({ newModel, oldModel }) => {
const changedProperties = [];
const allChangableProperties = [
'name',
'type',
'group',
'config',
'status',
'status_name'
];
if (!oldModel) {
return allChangableProperties;
}
allChangableProperties.forEach((field) => {
if (
!isUndefined(newModel[field]) &&
!isEqual(newModel[field], oldModel[field])
) {
changedProperties.push(field);
}
});
return changedProperties;
};
/**
* Detail
*
* @public
* @param {string} group
*/
FileConfig.get = async (operation) => {
try {
const data = await FileConfig.findOne({
where: {
type: operation.type,
group: operation.group,
is_active: true
}
});
if (!data) {
throw new APIError({
status: httpStatus.NOT_FOUND,
message: 'Không tìm thấy file!'
});
}
return data;
} catch (ex) {
throw ex;
}
};
/**
* List users in descending order of 'createdAt' timestamp.
*
* @param {number} skip - Number of users to be skipped.
* @param {number} limit - Limit number of users to be returned.
* @returns {Promise<Supplider[]>}
*/
FileConfig.list = async ({
name,
// sort
sort_by,
order_by,
skip = 0,
limit = 20,
}) => {
const options = filterConditions({
name
});
const sort = sortConditions({ sort_by, order_by });
return FileConfig.findAll({
where: options,
order: [sort],
offset: skip,
limit: limit
});
};
/**
* Total records.
*
* @param {number} skip - Number of users to be skipped.
* @param {number} limit - Limit number of users to be returned.
* @returns {Promise<Number>}
*/
FileConfig.totalRecords = ({
name
}) => {
const options = filterConditions({
name
});
return FileConfig.count({ where: options });
};
/**
* Filter only allowed fields from Province
*
* @param {Object} params
*/
FileConfig.filterParams = (params) => pick(params, PUBLIC_FIELDS);
/**
* @typedef Province
*/
export default FileConfig;

@ -0,0 +1,276 @@
/* eslint-disable camelcase */
import httpStatus from 'http-status';
import { Model, DataTypes, Op } from 'sequelize';
import { isEqual, isNil, isUndefined, omitBy, pick } from 'lodash';
import moment from 'moment-timezone';
import { serviceName } from '../../config/vars';
import postgres from '../../config/postgres';
import APIError from '../utils/APIException';
/**
* Create connection
*/
const { sequelize } = postgres;
class File extends Model { }
const PUBLIC_FIELDS = [
'name',
'title',
'payload'
];
/**
* File Schema
* @public
*/
File.init(
{
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true
},
url: {
type: DataTypes.STRING(255),
allowNull: false
},
name: {
type: DataTypes.STRING(255),
defaultValue: null
},
title: {
type: DataTypes.STRING(255),
defaultValue: null
},
payload: {
type: DataTypes.JSONB,
defaultValue: null // id | code | name
},
// manager
is_active: {
type: DataTypes.BOOLEAN,
defaultValue: true
},
created_at: {
type: DataTypes.DATE,
defaultValue: DataTypes.NOW
},
updated_at: {
type: DataTypes.DATE,
defaultValue: DataTypes.NOW
},
created_by: {
type: DataTypes.JSONB,
defaultValue: null // id | name
}
},
{
timestamps: false,
sequelize: sequelize,
schema: serviceName,
modelName: 'file',
tableName: 'tbl_files'
}
);
/**
* Register event emiter
*/
File.Events = {
File_CREATED: `${serviceName}.file.created`,
File_UPDATED: `${serviceName}.file.updated`,
File_DELETED: `${serviceName}.file.deleted`,
};
File.EVENT_SOURCE = `${serviceName}.file`;
/**
* Add your
* - pre-save hooks
* - validations
* - virtuals
*/
File.addHook('afterCreate', () => { });
File.addHook('afterUpdate', () => { });
File.addHook('afterDestroy', () => { });
/**
* Load query
* @param {*} params
*/
function filterConditions(params) {
const options = omitBy(params, isNil);
options.is_active = true;
// TODO: load condition
if (options.name) {
options.name = {
[Op.iLike]: `%${options.name}%`
};
}
return options;
}
/**
* Load sort query
* @param {*} sort_by
* @param {*} order_by
*/
function sortConditions({ sort_by, order_by }) {
let sort = null;
switch (sort_by) {
case 'created_at':
sort = ['created_at', order_by];
break;
case 'updated_at':
sort = ['updated_at', order_by];
break;
default: sort = ['created_at', 'DESC'];
break;
}
return sort;
}
/**
* Transform postgres model to expose object
*/
File.transform = (params) => {
const transformed = {};
const fields = [
'id',
'name',
'payload',
'created_by'
];
fields.forEach((field) => {
transformed[field] = params[field];
});
// pipe date
const dateFields = [
'created_at',
'updated_at'
];
dateFields.forEach((field) => {
if (params[field]) {
transformed[field] = moment(params[field]).unix();
} else {
transformed[field] = null;
}
});
return transformed;
};
/**
* Get all changed properties
*/
File.getChangedProperties = ({ newModel, oldModel }) => {
const changedProperties = [];
const allChangableProperties = [
'id',
'name',
'payload',
];
if (!oldModel) {
return allChangableProperties;
}
allChangableProperties.forEach((field) => {
if (
!isUndefined(newModel[field]) &&
!isEqual(newModel[field], oldModel[field])
) {
changedProperties.push(field);
}
});
return changedProperties;
};
/**
* Detail
*
* @public
* @param {string} id
*/
File.get = async (id) => {
try {
const data = await File.findOne({
where: {
id,
is_active: true
}
});
if (!data) {
throw new APIError({
status: httpStatus.NOT_FOUND,
message: 'Không tìm thấy địa chỉ tỉnh/thành!'
});
}
return data;
} catch (ex) {
throw ex;
}
};
/**
* List users in descending order of 'createdAt' timestamp.
*
* @param {number} skip - Number of users to be skipped.
* @param {number} limit - Limit number of users to be returned.
* @returns {Promise<Supplider[]>}
*/
File.list = async ({
name,
// sort
sort_by,
order_by,
skip = 0,
limit = 20,
}) => {
const options = filterConditions({
name
});
const sort = sortConditions({ sort_by, order_by });
return File.findAll({
where: options,
order: [sort],
offset: skip,
limit: limit
});
};
/**
* Total records.
*
* @param {number} skip - Number of users to be skipped.
* @param {number} limit - Limit number of users to be returned.
* @returns {Promise<Number>}
*/
File.totalRecords = ({
name
}) => {
const options = filterConditions({
name
});
return File.count({ where: options });
};
/**
* Filter only allowed fields from File
*
* @param {Object} params
*/
File.filterParams = (params) => pick(params, PUBLIC_FIELDS);
/**
* @typedef File
*/
export default File;

@ -0,0 +1,292 @@
/* eslint-disable camelcase */
import httpStatus from 'http-status';
import { Model, DataTypes, Op } from 'sequelize';
import { isEqual, isNil, isUndefined, omitBy, pick } from 'lodash';
import moment from 'moment-timezone';
import { serviceName } from '../../config/vars';
import postgres from '../../config/postgres';
import APIError from '../utils/APIException';
/**
* Create connection
*/
const { sequelize } = postgres;
class Image extends Model { }
const PUBLIC_FIELDS = [
'name',
'title',
'payload'
];
Image.Groups = {
USER: 'users',
STORE: 'stores',
VOUCHER: 'vouchers',
STORIES: 'stories',
CHAPTERS: 'chapters',
GAMES: 'games',
CUSTOMER: 'customers',
PROMOTION: 'promotions',
PRODUCT: 'products',
// configration
BANNER: 'banners',
CATEGORY: 'categories',
DEFAULT: 'defaults'
};
/**
* Image Schema
* @public
*/
Image.init(
{
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true
},
url: {
type: DataTypes.STRING(255),
allowNull: false
},
name: {
type: DataTypes.STRING(255),
defaultValue: null
},
title: {
type: DataTypes.STRING(255),
defaultValue: null
},
payload: {
type: DataTypes.JSONB,
defaultValue: null // id | code | name
},
// manager
is_active: {
type: DataTypes.BOOLEAN,
defaultValue: true
},
created_at: {
type: DataTypes.DATE,
defaultValue: DataTypes.NOW
},
updated_at: {
type: DataTypes.DATE,
defaultValue: DataTypes.NOW
},
created_by: {
type: DataTypes.JSONB,
defaultValue: null // id | name
}
},
{
timestamps: false,
sequelize: sequelize,
schema: serviceName,
modelName: 'image',
tableName: 'tbl_images'
}
);
/**
* Register event emiter
*/
Image.Events = {
IMAGE_CREATED: `${serviceName}.image.created`,
IMAGE_UPDATED: `${serviceName}.image.updated`,
IMAGE_DELETED: `${serviceName}.image.deleted`,
};
Image.EVENT_SOURCE = `${serviceName}.image`;
/**
* Add your
* - pre-save hooks
* - validations
* - virtuals
*/
Image.addHook('afterCreate', () => { });
Image.addHook('afterUpdate', () => { });
Image.addHook('afterDestroy', () => { });
/**
* Load query
* @param {*} params
*/
function filterConditions(params) {
const options = omitBy(params, isNil);
options.is_active = true;
// TODO: load condition
if (options.name) {
options.name = {
[Op.iLike]: `%${options.name}%`
};
}
return options;
}
/**
* Load sort query
* @param {*} sort_by
* @param {*} order_by
*/
function sortConditions({ sort_by, order_by }) {
let sort = null;
switch (sort_by) {
case 'created_at':
sort = ['created_at', order_by];
break;
case 'updated_at':
sort = ['updated_at', order_by];
break;
default: sort = ['created_at', 'DESC'];
break;
}
return sort;
}
/**
* Transform postgres model to expose object
*/
Image.transform = (params) => {
const transformed = {};
const fields = [
'id',
'name',
'payload',
'created_by'
];
fields.forEach((field) => {
transformed[field] = params[field];
});
// pipe date
const dateFields = [
'created_at',
'updated_at'
];
dateFields.forEach((field) => {
if (params[field]) {
transformed[field] = moment(params[field]).unix();
} else {
transformed[field] = null;
}
});
return transformed;
};
/**
* Get all changed properties
*/
Image.getChangedProperties = ({ newModel, oldModel }) => {
const changedProperties = [];
const allChangableProperties = [
'id',
'name',
'payload',
];
if (!oldModel) {
return allChangableProperties;
}
allChangableProperties.forEach((field) => {
if (
!isUndefined(newModel[field]) &&
!isEqual(newModel[field], oldModel[field])
) {
changedProperties.push(field);
}
});
return changedProperties;
};
/**
* Detail
*
* @public
* @param {string} id
*/
Image.get = async (id) => {
try {
const data = await Image.findOne({
where: {
id,
is_active: true
}
});
if (!data) {
throw new APIError({
status: httpStatus.NOT_FOUND,
message: 'Không tìm thấy địa chỉ tỉnh/thành!'
});
}
return data;
} catch (ex) {
throw ex;
}
};
/**
* List users in descending order of 'createdAt' timestamp.
*
* @param {number} skip - Number of users to be skipped.
* @param {number} limit - Limit number of users to be returned.
* @returns {Promise<Supplider[]>}
*/
Image.list = async ({
name,
// sort
sort_by,
order_by,
skip = 0,
limit = 20,
}) => {
const options = filterConditions({
name
});
const sort = sortConditions({ sort_by, order_by });
return Image.findAll({
where: options,
order: [sort],
offset: skip,
limit: limit
});
};
/**
* Total records.
*
* @param {number} skip - Number of users to be skipped.
* @param {number} limit - Limit number of users to be returned.
* @returns {Promise<Number>}
*/
Image.totalRecords = ({
name
}) => {
const options = filterConditions({
name
});
return Image.count({ where: options });
};
/**
* Filter only allowed fields from Province
*
* @param {Object} params
*/
Image.filterParams = (params) => pick(params, PUBLIC_FIELDS);
/**
* @typedef Province
*/
export default Image;

File diff suppressed because it is too large Load Diff

@ -0,0 +1,101 @@
/* eslint-disable no-param-reassign */
import sharp from 'sharp';
/** image size supported */
const Sizes = [50, 100, 200, 300, 400, 500, 600, 700, 800];
function transformer(options, transformOptions) {
let imageStream = sharp();
if (transformOptions.resize) {
const keys = Object.keys(options);
for (let index = 0; index < keys.length; index += 1) {
const value = options[keys[index]];
if (value) {
imageStream = resolveImageStream(
keys[index],
value,
transformOptions.resize,
imageStream
);
}
}
}
return imageStream;
}
const objectHasOwnProperty = (source, prop) =>
Object.prototype.hasOwnProperty.call(source, prop);
const hasProp = (value) =>
typeof value === 'object' && objectHasOwnProperty(value, 'type');
const validateFormat = (value) => {
if (hasProp(value)) {
return value.type;
}
return value;
};
const validateValue = (value) => {
if (typeof value === 'boolean') {
return null;
}
return value;
};
const resolveImageStream = (key, value, size, imageStream) => {
if (key === 'resize') {
imageStream = imageStream.resize(
size.width,
size.height,
Object.assign(value, size.options)
);
} else if (key === 'crop') {
imageStream = imageStream[key](value);
} else if (key === 'toFormat') {
imageStream = imageStream.toFormat(
validateFormat(value),
value.options
);
} else {
const valid = validateValue(value);
imageStream = imageStream[key](valid);
}
return imageStream;
};
const getSharpOptions = (options) => ({
resize: options.resize,
background: options.background,
crop: options.crop,
embed: options.embed,
max: options.max,
min: options.min,
toFormat: options.toFormat,
extract: options.extract,
trim: options.trim,
flatten: options.flatten,
extend: options.extend,
negate: options.negate,
rotate: options.rotate,
flip: options.flip,
flop: options.flop,
blur: options.blur,
sharpen: options.sharpen,
gamma: options.gamma,
grayscale: options.grayscale,
greyscale: options.greyscale,
normalize: options.normalize,
normalise: options.normalise,
convolve: options.convolve,
threshold: options.threshold,
toColourspace: options.toColourspace,
toColorspace: options.toColorspace,
ignoreAspectRatio: options.ignoreAspectRatio,
withMetadata: options.withMetadata,
withoutEnlargement: options.withoutEnlargement
});
module.exports = { Sizes, transformer, getSharpOptions };

@ -0,0 +1,109 @@
import multer from 'multer';
// import moment from 'moment-timezone';
import { storage as storageConfig } from '../../../config/vars';
/** storage will create folder when new date */
// const date = new Date();
// const year = moment(date).format('YYYY');
// const month = moment(date).format('MM');
const filePath = `${storageConfig.uri}`;
const createDefaultFolder = ({ id }) => {
try {
if (id) {
multer({ dest: `${filePath}/${id}` });
} else {
multer({ dest: `${filePath}` });
}
// multer({ dest: `${filePath}/images/games` });
// multer({ dest: `${filePath}/images/stories` });
// multer({ dest: `${filePath}/images/chapters` });
// multer({ dest: `${filePath}/images/vouchers` });
// multer({ dest: `${filePath}/images/products` });
// multer({ dest: `${filePath}/images/customers` });
// multer({ dest: `${filePath}/images/promotions` });
// // configuration
// multer({ dest: `${filePath}/images/banners` });
// multer({ dest: `${filePath}/images/categories` });
// multer({ dest: `${filePath}/images/upload/default` });
return true;
} catch (ex) {
return false;
}
};
const createFolder = ({ path }) => {
try {
console.log(`${filePath}/${path}`);
multer({ dest: `${filePath}/${path}` });
// multer({ dest: `${filePath}/images/games` });
// multer({ dest: `${filePath}/images/stories` });
// multer({ dest: `${filePath}/images/chapters` });
// multer({ dest: `${filePath}/images/vouchers` });
// multer({ dest: `${filePath}/images/products` });
// multer({ dest: `${filePath}/images/customers` });
// multer({ dest: `${filePath}/images/promotions` });
// // configuration
// multer({ dest: `${filePath}/images/banners` });
// multer({ dest: `${filePath}/images/categories` });
// multer({ dest: `${filePath}/images/upload/default` });
return true;
} catch (ex) {
return false;
}
};
/** add image to storage follow group */
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, filePath);
},
filename: (req, file, cb) => {
/**
* setup folder follow date
*/
createDefaultFolder({});
/**
* save image follow type
*/
const path = req.query.path;
console.log('path', path);
// const fileName = file.originalname.includes('.')
// ? file.originalname.slice(0, file.originalname.lastIndexOf('.'))
// : file.originalname;
cb(
null,
`/${path}/${file.originalname}`
);
}
});
const fileFilter = (req, file, cb) => {
// if (
// file.mimetype === 'image/jpeg' ||
// file.mimetype === 'image/webp' ||
// file.mimetype === 'image/png' ||
// file.mimetype === 'image/gif' ||
// ) {
// cb(null, true);
// } else {
// cb(null, false);
// }
cb(null, true);
};
const uploader = multer({
storage,
limits: {
fileSize: 1024 * 1024 * 2 // 5MB
},
fileFilter
});
module.exports = {
createDefaultFolder,
createFolder,
uploader,
fileFilter
};

@ -0,0 +1,9 @@
import { EventEmitter } from 'events';
const emitter = new EventEmitter();
emitter.on('uncaughtException', (err) => {
console.error(err);
});
export default emitter;

@ -0,0 +1,53 @@
import fs from 'fs';
import sharp from 'sharp';
import eventBus from '../event-bus';
import Image from '../../models/image.model';
import { getSharpOptions, transformer, Sizes } from '../adapters/sharp-adapter';
function registerUploadEvent() {
/**
* When token created service will send code verify to user by phone
*
* @param {TokenVerify} token
*/
eventBus.on(Image.Events.IMAGE_CREATED, async (file) => {
console.log(file);
Sizes.forEach(size => {
// setup sharp transform option
const transformOptions = getSharpOptions({
resize: {
width: size,
height: size
}
});
// setup sharp option
const sharpOptions = {
resize: {
fit: sharp.fit.inside,
withoutEnlargement: true,
watermark: false
}
};
// create image stream
const fileStream = fs.createReadStream(file.path);
const imageStream = transformer(sharpOptions, transformOptions);
// replace name resize
const imageSplit = file.path.split('.');
const imageType = imageSplit[imageSplit.length - 1];
const imageFullName = `${imageSplit[imageSplit.length - 2]}-${size}x${size}.${imageType}`.split(' ').join('-');
// save new image
fileStream.pipe(imageStream)
.webp({ quality: 80, lossless: true })
.toFormat(imageType).toFile(`${imageFullName}`)
.catch(() => console.log('resize image failed!'));
});
});
}
module.exports = {
registerUploadEvent
};

@ -0,0 +1,28 @@
import axios from 'axios';
import { cloneDeep } from 'lodash';
const DEFAULT_OPTIONS = {
headers: {
'X-Consumer-Groups': 'service',
'X-Anonymous-User': 'true'
}
};
module.exports = {
get: (url, options = {}) => {
const defaultOptions = cloneDeep(DEFAULT_OPTIONS);
return axios.get(url, Object.assign(defaultOptions, options));
},
post: (url, data, options = {}) => {
const defaultOptions = cloneDeep(DEFAULT_OPTIONS);
return axios.post(url, data, Object.assign(defaultOptions, options));
},
put: (url, data, options = {}) => {
const defaultOptions = cloneDeep(DEFAULT_OPTIONS);
return axios.put(url, data, Object.assign(defaultOptions, options));
},
delete: (url, options = {}) => {
const defaultOptions = cloneDeep(DEFAULT_OPTIONS);
return axios.delete(url, Object.assign(defaultOptions, options));
}
};

@ -0,0 +1,51 @@
const httpStatus = require('http-status');
/**
* @extends Error
*/
class ExtendableError extends Error {
constructor({ message, errors, status, isPublic, stack, isTranslated }) {
super(message);
this.name = this.constructor.name;
this.message = message;
this.errors = errors;
this.status = status;
this.isPublic = isPublic;
this.isOperational = true; // This is required since bluebird 4 doesn't append it anymore.
this.stack = stack;
this.isTranslated = isTranslated;
// Error.captureStackTrace(this, this.constructor.name);
}
}
/**
* Class representing an API error.
* @extends ExtendableError
*/
class APIException extends ExtendableError {
/**
* Create an API error.
* @param {string} message - Error message.
* @param {number} status - HTTP status code of error.
* @param {boolean} isPublic - Whether the message should be visible to user or not.
*/
constructor({
message,
errors,
stack,
status = httpStatus.INTERNAL_SERVER_ERROR,
isPublic = false,
isTranslated = false
}) {
super({
message,
errors,
status,
isPublic,
stack,
isTranslated
});
}
}
module.exports = APIException;

@ -0,0 +1,120 @@
import { serviceName } from '../../config/vars';
export default {
// Service Permission
USER: 'user',
LOGGED_IN: 'staff',
// For Product Route
PRODUCT_VIEW: `${serviceName}_product_view`,
PRODUCT_CREATE: `${serviceName}_product_create`,
PRODUCT_UPDATE: `${serviceName}_product_update`,
PRODUCT_DELETE: `${serviceName}_product_delete`,
// For Category Route
CATEGORY_VIEW: `${serviceName}_category_view`,
CATEGORY_CREATE: `${serviceName}_category_create`,
CATEGORY_UPDATE: `${serviceName}_category_update`,
CATEGORY_DELETE: `${serviceName}_category_delete`,
// For Order Route
ORDER_VIEW: `${serviceName}_order_view`,
ORDER_CREATE: `${serviceName}_order_create`,
ORDER_UPDATE: `${serviceName}_order_update`,
// For Import Route
IMPORT_VIEW: `${serviceName}_import_view`,
IMPORT_CREATE: `${serviceName}_import_create`,
IMPORT_UPDATE: `${serviceName}_import_update`,
IMPORT_DELETE: `${serviceName}_import_delete`,
// For Export Route
EXPORT_VIEW: `${serviceName}_export_view`,
EXPORT_CREATE: `${serviceName}_export_create`,
EXPORT_UPDATE: `${serviceName}_export_update`,
EXPORT_DELETE: `${serviceName}_export_delete`,
// For Stock Take Route
STOCK_TAKE_VIEW: `${serviceName}_stock_take_view`,
STOCK_TAKE_CREATE: `${serviceName}_stock_take_create`,
STOCK_TAKE_UPDATE: `${serviceName}_stock_take_update`,
STOCK_TAKE_DELETE: `${serviceName}_stock_take_delete`,
// For Province Route
PROVINCE_VIEW: `${serviceName}_province_view`,
PROVINCE_CREATE: `${serviceName}_province_create`,
PROVINCE_UPDATE: `${serviceName}_province_update`,
PROVINCE_DELETE: `${serviceName}_province_delete`,
// For District Route
DISTRICT_VIEW: `${serviceName}_district_view`,
DISTRICT_CREATE: `${serviceName}_district_create`,
DISTRICT_UPDATE: `${serviceName}_district_update`,
DISTRICT_DELETE: `${serviceName}_district_delete`,
// For Ward Route
WARD_VIEW: `${serviceName}_ward_view`,
WARD_CREATE: `${serviceName}_ward_create`,
WARD_UPDATE: `${serviceName}_ward_update`,
WARD_DELETE: `${serviceName}_ward_delete`,
// For Banner Route
BANNER_VIEW: `${serviceName}_banner_view`,
BANNER_CREATE: `${serviceName}_banner_create`,
BANNER_UPDATE: `${serviceName}_banner_update`,
BANNER_DELETE: `${serviceName}_banner_delete`,
// For Banner Route
VOUCHER_VIEW: `${serviceName}_voucher_view`,
VOUCHER_CREATE: `${serviceName}_voucher_create`,
VOUCHER_UPDATE: `${serviceName}_voucher_update`,
VOUCHER_DELETE: `${serviceName}_voucher_delete`,
// For Payment Route
PAYMENT_VIEW: `${serviceName}_payment_view`,
PAYMENT_CREATE: `${serviceName}_payment_create`,
PAYMENT_UPDATE: `${serviceName}_payment_update`,
PAYMENT_DELETE: `${serviceName}_payment_delete`,
// For price book Route
PRICE_VIEW: `${serviceName}_price_view`,
PRICE_CREATE: `${serviceName}_price_create`,
PRICE_UPDATE: `${serviceName}_price_update`,
PRICE_DELETE: `${serviceName}_price_delete`,
// For Delivery Route
DELIVERY_VIEW: `${serviceName}_delivery_view`,
DELIVERY_CREATE: `${serviceName}_delivery_create`,
DELIVERY_UPDATE: `${serviceName}_delivery_update`,
DELIVERY_DELETE: `${serviceName}_delivery_delete`,
// For Delivery Route
DELIVERY_PAYMENT_VIEW: `${serviceName}_delivery_payment_view`,
DELIVERY_PAYMENT_CREATE: `${serviceName}_delivery_payment_create`,
DELIVERY_PAYMENT_UPDATE: `${serviceName}_delivery_payment_update`,
DELIVERY_PAYMENT_DELETE: `${serviceName}_delivery_payment_delete`,
// For Research Route
RESEARCH_VIEW: `${serviceName}_research_view`,
RESEARCH_CREATE: `${serviceName}_research_create`,
RESEARCH_UPDATE: `${serviceName}_research_update`,
RESEARCH_DELETE: `${serviceName}_research_delete`,
// For Production Route
PRODUCTION_VIEW: `${serviceName}_production_view`,
PRODUCTION_CREATE: `${serviceName}_production_create`,
PRODUCTION_UPDATE: `${serviceName}_production_update`,
PRODUCTION_DELETE: `${serviceName}_production_delete`,
// For Promotion Route
PROMOTION_VIEW: `${serviceName}_promotion_view`,
PROMOTION_CREATE: `${serviceName}_promotion_create`,
PROMOTION_UPDATE: `${serviceName}_promotion_update`,
PATH_VIEW: `${serviceName}_path_view`,
PATH_CREATE: `${serviceName}_path_create`,
PATH_UPDATE: `${serviceName}_path_update`,
PATH_DELETE: `${serviceName}_path_delete`,
IMAGE_UPLOAD: `${serviceName}_image_upload`
};

@ -0,0 +1,7 @@
/* eslint-disable no-useless-escape */
/**
* Replace string to - character
* @public
*/
exports.replaceText = (text) => text.replace(/\s/g, '-').replace(/[`~!@#$%^&*()_|+\-=÷¿?;:'",.<>\{\}\[\]\\\/]/gi, '-');

@ -0,0 +1,54 @@
import express from 'express';
import morgan from 'morgan';
import { json, urlencoded } from 'body-parser';
import compress from 'compression';
import methodOverride from 'method-override';
import cors from 'cors';
import helmet from 'helmet';
import i18n from './i18n';
import routes from '../api/routes/v1';
import { logs } from './vars';
import { converter, handler } from '../api/middlewares/errors';
// import { converter, notFound, handler } from '../api/middlewares/errors';
/**
* Express instance
* @public
*/
const app = express();
app.use(express.static('public'));
// request logging. dev: console | production: file
app.use(morgan(`${logs}`));
// parse body params and attache them to req.body
app.use(json());
app.use(urlencoded({ extended: true }));
// gzip compression
app.use(compress());
// lets you use HTTP verbs such as PUT or DELETE
// in places where the client doesn't support it
app.use(methodOverride());
// secure apps by setting various HTTP headers
app.use(helmet());
// enable CORS - Cross Origin Resource Sharing
app.use(cors());
// init i18n
app.use(i18n.init);
// mount api v1 routes
app.use('/v1', routes);
// if error is not an instanceOf APIError, convert it.
app.use(converter);
// catch 404 and forward to error handler
// app.use(notFound);
// error handler, send stacktrace only during development
app.use(handler);
export default app;

@ -0,0 +1,8 @@
import i18n, { configure } from 'i18n';
import { join } from 'path';
configure({
directory: join(__dirname, '/locales')
});
export default i18n;

@ -0,0 +1,21 @@
{
"Invalid file!": "Invalid file!",
"Unauthorized": "Unauthorized",
"NOT_FOUND!": "NOT_FOUND!",
"VALIDATION_ERROR": "VALIDATION_ERROR",
"Cannot set headers after they are sent to the client": "Cannot set headers after they are sent to the client",
"The \"path\" argument must be of type string. Received an instance of Object": "The \"path\" argument must be of type string. Received an instance of Object",
"Cannot read properties of undefined (reading 'replace')": "Cannot read properties of undefined (reading 'replace')",
"Callback must be a function. Received undefined": "Callback must be a function. Received undefined",
"Cannot read properties of undefined (reading 'on')": "Cannot read properties of undefined (reading 'on')",
"_busboy2.default is not a constructor": "_busboy2.default is not a constructor",
"ENOENT: no such file or directory, open 'public1/undefined/50ba9c5e20a3fcfda5b2.jpeg'": "ENOENT: no such file or directory, open 'public1/undefined/50ba9c5e20a3fcfda5b2.jpeg'",
"ENOENT: no such file or directory, open 'public1/1235/12/50ba9c5e20a3fcfda5b2.jpeg'": "ENOENT: no such file or directory, open 'public1/1235/12/50ba9c5e20a3fcfda5b2.jpeg'",
"ENOENT: no such file or directory, open 'public1/public11235/12/50ba9c5e20a3fcfda5b2.jpeg'": "ENOENT: no such file or directory, open 'public1/public11235/12/50ba9c5e20a3fcfda5b2.jpeg'",
"ENOENT: no such file or directory, open 'public1/images/12/50ba9c5e20a3fcfda5b2.jpeg'": "ENOENT: no such file or directory, open 'public1/images/12/50ba9c5e20a3fcfda5b2.jpeg'",
"Không tìm thấy tài khoản này!": "Không tìm thấy tài khoản này!",
"ENOENT: no such file or directory, open 'public/12/about.jpg'": "ENOENT: no such file or directory, open 'public/12/about.jpg'",
"Cannot read properties of undefined (reading 'id')": "Cannot read properties of undefined (reading 'id')",
"Forbidden": "Forbidden",
"ENOENT: no such file or directory, open 'public/5/50ba9c5e20a3fcfda5b2.jpeg'": "ENOENT: no such file or directory, open 'public/5/50ba9c5e20a3fcfda5b2.jpeg'"
}

@ -0,0 +1,4 @@
{
"NOT_FOUND!": "NOT_FOUND!",
"Không tìm thấy tài khoản này!": "Không tìm thấy tài khoản này!"
}

@ -0,0 +1,21 @@
module.exports = {
/** for http status */
BAD_REQUEST: 'Tham số không hợp lệ.!',
NOT_FOUND: 'Không tìm thấy dữ liệu.!',
NOT_PERMISSION: 'Bạn không có quyền truy cập hoặc thực hiện thao tác này.!',
SERVER_ERROR: 'Hệ thống đã xảy ra sự cố. Xin vui lòng thử lại sau ít phút.!',
/** for events */
CREATE_FAILED: 'Thêm mới không thành công.!',
CREATE_SUCCESS: 'Thêm mới thành công.!',
UPDATE_FAILED: 'Cập nhật dữ liệu không thành công.!',
UPDATE_SUCCESS: 'Cập nhật dữ liệu thành công.!',
REMOVE_FAILED: 'Xóa dữ liệu không thành công.!',
REMOVE_SUCCESS: 'Xóa dữ liệu thành công.!',
/** for upload image */
UPLOAD_FAILED: 'Tải lên thất bại',
UPLOAD_SUCCESS: 'Tải lên thành công'
};

@ -0,0 +1,38 @@
const mongoose = require('mongoose');
const { mongo, env } = require('./vars');
const bluebird = require('bluebird');
// set mongoose Promise to Bluebird
mongoose.Promise = bluebird;
// print mongoose logs in dev env
if (env === 'development') {
mongoose.set('debug', true);
}
const defaultErrorHandler = (err) => {
console.log(`Connection to Mongo error: ${err}`);
};
/**
* Connect to mongo db
*
* @returns {object} Mongoose connection
* @public
*/
const connect = (errorHandler = defaultErrorHandler) => {
mongoose.connection.on('error', errorHandler);
/** connect database */
mongoose.connect(mongo.uri, {
useNewUrlParser: true,
keepAlive: 1
});
mongoose.set('useCreateIndex', true);
mongoose.set('useFindAndModify', false);
return mongoose.connection;
};
const disconnect = mongoose.disconnect.bind(mongoose);
module.exports = { connect, disconnect };

@ -0,0 +1,117 @@
import { basename } from 'path';
import postgres from './postgres';
import mongoose from './mongoose';
import rabbitMQ from './rabbitmq';
import redis from './redis';
const isCriticalProcess = basename(process.mainModule.filename) === 'index.js';
const DELAY_TIME = 60000; // time to wait before kill process
let timeout;
/**
* Error handle
*/
const errorHandler = (err, isCriticalConnection = false) => {
if (err) {
if (isCriticalProcess && !isCriticalConnection) {
if (timeout) {
clearTimeout(timeout);
}
timeout = setTimeout(() => {
console.log(process.pid);
process.kill(process.pid, 'SIGUSR1');
}, DELAY_TIME);
return null;
}
process.kill(process.pid, 'SIGUSR1');
}
return err;
};
/**
* Disconnect to database
*/
const disconnect = async () => {
// try {
// await postgres.disconnect();
// } catch (error) {
// console.log(`postgres disconnect: ${error}`);
// }
try {
await mongoose.disconnect();
} catch (error) {
console.log(`mongoose disconnect: ${error}`);
}
try {
await rabbitMQ.disconnect();
} catch (error) {
console.log(`rabbitMQ disconnect: ${error}`);
}
try {
await redis.disconnect();
} catch (error) {
console.log(`redis disconnect: ${error}`);
}
await new Promise((s) => setTimeout(s, 100));
};
/**
* Create connection to database
*/
const connect = async () => {
try {
await postgres.connect((err) => {
console.log(`Connection to Postgres error: ${err}`);
errorHandler(err);
});
} catch (error) {
if (errorHandler(error)) {
throw error;
}
}
// try {
// // Exit application on error
// await mongoose.connect((err) => {
// console.error(`MongoDB connection error: ${err}`);
// errorHandler(err, true);
// });
// } catch (error) {
// if (errorHandler(error, true)) {
// throw error;
// }
// }
// try {
// await rabbitMQ.connect(
// (err) => {
// console.log(`Connection to RabbitMQ error: ${err}`);
// errorHandler(err);
// },
// (err) => {
// console.log(`Connection to RabbitMQ closed: ${err}`);
// if (err) {
// errorHandler(err);
// }
// }
// );
// } catch (error) {
// if (errorHandler(error)) {
// throw error;
// }
// }
// try {
// await redis.connect((err) => {
// console.log(`Connection to Redis error: ${err}`);
// errorHandler(err);
// });
// } catch (error) {
// if (errorHandler(error)) {
// throw error;
// }
// }
};
module.exports = {
connect,
disconnect
};

@ -0,0 +1,40 @@
import Sequelize from 'sequelize';
import bluebird from 'bluebird';
import { postgres, env } from './vars';
Sequelize.Promise = bluebird;
const defaultErrorHandler = (err) => {
console.log(`Connection to Postgres error: ${err}`);
};
const app = {
sequelize: new Sequelize(
postgres.uri,
{
dialect: 'postgres'
}
),
connect(errorHandler = defaultErrorHandler) {
this.sequelize.authenticate()
.then(() => {
console.log('Postgres connection established!');
if (env === '1') {
this.sequelize.sync({
alter: true,
logging: true
});
}
}).catch((error) => {
errorHandler(error);
});
return this.sequelize;
},
disconnect() {
// close connection
console.log('Closing postgres connection!');
this.sequelize.close();
}
};
export default app;

@ -0,0 +1,29 @@
import rabbitEventSource from 'rabbit-event-source';
import { rabbit, env } from './vars';
const defaultErrorHandler = (err) => {
console.log(`Connection to Rabbit error: ${err}`);
};
const defaultCloseHandler = (err) => {
console.log(`Connection to Rabbit closed: ${err}`);
};
const connect = async (
errorHandler = defaultErrorHandler,
closeHandler = defaultCloseHandler
) => {
await rabbitEventSource.init(rabbit.uri, env);
rabbitEventSource.rabbit.connection.on('error', errorHandler);
rabbitEventSource.rabbit.connection.on('close', closeHandler);
};
const disconnect = rabbitEventSource.rabbit.close.bind(
rabbitEventSource.rabbit
);
module.exports = {
connect,
disconnect
};

@ -0,0 +1,71 @@
import Redis from 'ioredis';
import bluebird from 'bluebird';
import { redis as redisConfig, serviceName } from './vars';
Redis.Promise = bluebird;
bluebird.promisifyAll(Redis);
bluebird.promisifyAll(Redis.prototype);
const defaultErrorHandler = (err) => {
console.log(`Connection to Redis error: ${err}`);
};
const app = {
client: null,
subscriber: null,
allClients: [],
connect(errorHandler = defaultErrorHandler, overrideClient = true) {
const client = new Redis(redisConfig.uri, {
maxRetriesPerRequest: 3
});
client.on('ready', () => {
console.log('Redis connection established!');
});
client.on('end', () => {
console.log('Redis connection closed!');
});
client.on('error', errorHandler);
if (overrideClient) {
this.client = client;
}
this.allClients.push(client);
return client;
},
disconnect() {
this.allClients.forEach((client) => {
if (client) {
console.log('Closing redis connection!');
client.quitAsync().catch(console.log);
}
});
return null;
},
getJobOptions() {
if (!this.subscriber) {
this.subscriber = this.connect(undefined, false);
}
return {
prefix: `jobs:${serviceName}`,
defaultJobOptions: {
removeOnComplete: 100,
removeOnFail: 100
},
createClient: (type) => {
switch (type) {
case 'client':
return this.client;
case 'subscriber':
return this.subscriber;
default:
return this.connect(undefined, false);
}
}
};
}
};
module.exports = app;

@ -0,0 +1,35 @@
const path = require('path');
// import .env variables
require('dotenv-safe').load({
path: path.join(__dirname, '../../.env'),
sample: path.join(__dirname, '../../.env.example')
});
module.exports = {
serviceName: 'file_service',
env: process.env.NODE_ENV,
port: process.env.PORT,
logs: process.env.NODE_ENV === 'production' ? 'combined' : 'development',
cdn: {
uri: process.env.NODE_ENV === 'production' ? process.env.CDN_URI : process.env.DEV_CDN_URI
},
postgres: {
uri: process.env.NODE_ENV === 'production' ? process.env.POSTGRES_URI : process.env.POSTGRES_URI_TEST
},
storage: {
uri: process.env.NODE_ENV === 'production' ? process.env.STORAGE_URI : process.env.DEV_STORAGE_URI
},
mongo: {
uri: process.env.NODE_ENV === 'production' ? process.env.MONGO_URI : process.env.MONGO_URI_TEST
},
rabbit: {
uri: process.env.RABBITMQ_URI
},
redis: {
uri: process.env.REDIS_URI
},
otherServices: {
manager: process.env.MANAGER_SERVICE_URL
}
};

@ -0,0 +1,5 @@
async function register() {
/** register here */
}
export default { register };

@ -0,0 +1,27 @@
import eventDispatcher from './event-adapter';
import provider from './providers';
import persistent from './config/persistent';
persistent
.connect()
.then(() => {
provider.register();
eventDispatcher.register();
})
.catch((err) => {
console.log(err);
process.exit(-1);
});
function terminate(exitCode) {
return () => {
persistent.disconnect().then(() => {
process.exit(exitCode);
});
};
}
// handle close
process.on('SIGINT', terminate(0));
process.on('SIGTERM', terminate(0));
process.on('SIGUSR1', terminate(-1));

@ -0,0 +1,27 @@
import worker from './worker';
import provider from './providers';
import persistent from './config/persistent';
persistent
.connect()
.then(() => {
provider.register();
worker.register();
})
.catch((err) => {
console.log(err);
process.exit(-1);
});
function terminate(exitCode) {
return () => {
persistent.disconnect().then(() => {
process.exit(exitCode);
});
};
}
// handle close
process.on('SIGINT', terminate(0));
process.on('SIGTERM', terminate(0));
process.on('SIGUSR1', terminate(-1));

@ -0,0 +1,46 @@
import app from './config/express';
import { port, env } from './config/vars';
import provider from './providers/index';
import persistent from './config/persistent';
/**
* open persistent connection
* service will shutdown when any database disconnect or error.
*/
persistent
.connect()
.then(() => {
// register provider
provider.register();
// listen to requests
const server = app.listen(port, () =>
console.info(`Server started on port ${port} (${env})`)
);
function terminate(exitCode) {
return () => {
console.log('Terminating');
server.close(() => {
persistent.disconnect().then(() => {
process.exit(exitCode);
});
});
};
}
// handle close
process.on('SIGINT', terminate(0));
process.on('SIGTERM', terminate(0));
process.on('SIGUSR1', terminate(-1));
})
.catch((err) => {
console.log(err);
process.exit(-1);
});
/**
* Exports express
* @public
*/
export default app;

@ -0,0 +1,8 @@
import uploadEvent from './upload-event';
export default {
register: () => {
// register any event emitter || event rabbitmq here
uploadEvent.register();
}
};

@ -0,0 +1,8 @@
import uploadEvent from '../common/services/managers/upload-manager';
export default {
register: () => {
// register user event
uploadEvent.registerUploadEvent();
}
};

@ -0,0 +1,5 @@
function register() {
/** register worker here */
}
export default { register };

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save