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