commit
c609113d8a
@ -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" |
||||||
|
] |
||||||
|
} |
@ -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 |
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 }; |
Loading…
Reference in new issue