From 67d73802408de7c7df771e21c3855ecd897a8761 Mon Sep 17 00:00:00 2001 From: Sergey Peshkov Date: Sat, 18 May 2019 23:41:51 +0300 Subject: [PATCH] feat(discounts): added categories. Fixes MEMB-529 --- lib/categories.js | 75 ++++++++++++++++++++ lib/server.js | 10 +++ migrations/20190518200032-create-category.js | 27 +++++++ models/Category.js | 41 +++++++++++ models/index.js | 4 +- package-lock.json | 72 ++++++++++++++++--- package.json | 1 + 7 files changed, 218 insertions(+), 12 deletions(-) create mode 100644 lib/categories.js create mode 100644 migrations/20190518200032-create-category.js create mode 100644 models/Category.js diff --git a/lib/categories.js b/lib/categories.js new file mode 100644 index 00000000..1cf92dfd --- /dev/null +++ b/lib/categories.js @@ -0,0 +1,75 @@ +const { Category } = require('../models'); +const errors = require('./errors'); +const helpers = require('./helpers'); + +exports.createCategory = async (req, res) => { + if (!req.permissions.manage_discounts) { + return errors.makeForbiddenError(res, 'You are not allowed to create integrations.'); + } + + const category = await Category.create(req.body); + + return res.json({ + success: true, + data: category + }); +}; + +exports.listAllCategories = async (req, res) => { + const categories = await Category.findAll({}); + + return res.json({ + success: true, + data: categories + }); +}; + +exports.findCategory = async (req, res, next) => { + const isNumber = helpers.isNumber(req.params.category_id); + + if (!isNumber) { + return errors.makeBadRequestError('The category ID is not a number.') + } + + const caregory = await caregory.findOne({ where: { id: Number(req.params.category_id) } }); + + if (!caregory) { + return errors.makeNotFoundError(res, 'The caregory is not found.'); + } + + req.caregory = caregory; + return next(); +}; + +exports.getCategory = async (req, res) => { + return res.json({ + success: true, + data: req.caregory + }); +}; + +exports.updateCategory = async (req, res) => { + if (!req.permissions.manage_discounts) { + return errors.makeForbiddenError(res, 'You are not allowed to update category.'); + } + + await req.category.update(req.body); + + return res.json({ + success: true, + data: req.category + }); +}; + +exports.deleteCategory = async (req, res) => { + if (!req.permissions.manage_discounts) { + return errors.makeForbiddenError(res, 'You are not allowed to update caregory.'); + } + + await req.caregory.destroy(); + + return res.json({ + success: true, + data: req.caregory + }); +}; diff --git a/lib/server.js b/lib/server.js index de55d4f1..2e2085fe 100644 --- a/lib/server.js +++ b/lib/server.js @@ -8,6 +8,7 @@ const bugsnag = require('./bugsnag'); const morgan = require('./morgan'); const middlewares = require('./middlewares'); const integrations = require('./integrations'); +const categories = require('./categories'); const db = require('./sequelize'); const server = express(); @@ -26,6 +27,8 @@ process.on('unhandledRejection', (err) => { }); GeneralRouter.use(middlewares.authenticateUser); + +// integrations and codes GeneralRouter.get('/integrations', integrations.listAllIntegrations); GeneralRouter.post('/integrations', integrations.createIntegration); GeneralRouter.post('/integrations/:integration_id/codes', integrations.findIntegration, integrations.addCodesToIntegration); @@ -36,6 +39,13 @@ GeneralRouter.delete('/integrations/:integration_id', integrations.findIntegrati GeneralRouter.get('/codes/mine', integrations.getMyCodes); +// categories and discounts (for listing to members) +GeneralRouter.get('/categories', categories.listAllCategories); +GeneralRouter.post('/categories', categories.createCategory); +GeneralRouter.get('/categories/:category_id', categories.findCategory, categories.getCategory); +GeneralRouter.put('/categories/:category_id', categories.findCategory, categories.updateCategory); +GeneralRouter.delete('/categories/:category_id', categories.findCategory, categories.deleteCategory); + server.use('/', GeneralRouter); server.use(middlewares.notFound); server.use(middlewares.errorHandler); diff --git a/migrations/20190518200032-create-category.js b/migrations/20190518200032-create-category.js new file mode 100644 index 00000000..af4e4655 --- /dev/null +++ b/migrations/20190518200032-create-category.js @@ -0,0 +1,27 @@ +module.exports = { + up: (queryInterface, Sequelize) => queryInterface.createTable('categories', { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: Sequelize.INTEGER + }, + name: { + allowNull: false, + type: Sequelize.STRING + }, + discounts: { + allowNull: false, + type: Sequelize.JSONB + }, + created_at: { + allowNull: false, + type: Sequelize.DATE + }, + updated_at: { + allowNull: false, + type: Sequelize.DATE + } + }), + down: queryInterface => queryInterface.dropTable('categories') +}; diff --git a/models/Category.js b/models/Category.js new file mode 100644 index 00000000..ce0e7df6 --- /dev/null +++ b/models/Category.js @@ -0,0 +1,41 @@ +const Joi = require('joi'); +const { Sequelize, sequelize } = require('../lib/sequelize'); + +const categoriesSchema = Joi.array().min(1).items(Joi.object().keys({ + icon: Joi.string().trim().required(), + name: Joi.string().trim().required(), + shortDescription: Joi.string().trim().required(), + longDescription: Joi.string().trim().required(), +})); + +const Code = sequelize.define('code', { + name: { + type: Sequelize.STRING, + allowNull: false, + defaultValue: '', + validate: { + notEmpty: { msg: 'Name should be set.' }, + }, + }, + discounts: { + type: Sequelize.JSONB, + allowNull: false, + defaultValue: '', + validate: { + isValid(categoriesValue) { + const { error, value } = Joi.validate(categoriesValue, categoriesSchema); + if (error) { + throw error; + } + + // eslint-disable-next-line no-param-reassign + categoriesValue = value; + } + }, + }, + claimed_by: { + type: Sequelize.INTEGER + } +}, { underscored: true, tableName: 'codes' }); + +module.exports = Code; diff --git a/models/index.js b/models/index.js index 64e7caa7..ec2fd3a1 100644 --- a/models/index.js +++ b/models/index.js @@ -1,10 +1,12 @@ const Integration = require('./Integration'); const Code = require('./Code'); +const Category = require('./Category'); Integration.hasMany(Code); Code.belongsTo(Integration); module.exports = { Integration, - Code + Code, + Category }; diff --git a/package-lock.json b/package-lock.json index a16e02b7..a6540ecc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3176,7 +3176,8 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -3197,12 +3198,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -3217,17 +3220,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -3344,7 +3350,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -3356,6 +3363,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -3370,6 +3378,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -3377,12 +3386,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -3401,6 +3412,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -3481,7 +3493,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -3493,6 +3506,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -3578,7 +3592,8 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -3614,6 +3629,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -3633,6 +3649,7 @@ "version": "3.0.1", "bundled": true, "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -3676,12 +3693,14 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true } } }, @@ -3901,6 +3920,11 @@ "is-stream": "^1.0.1" } }, + "hoek": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-6.1.3.tgz", + "integrity": "sha512-YXXAAhmF9zpQbC7LEcREFtXfGq5K1fmd+4PHkBq8NUqmzW3G+Dq10bI/i0KucLRwss3YYFQ0fSfoxBZYiGUqtQ==" + }, "hosted-git-info": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", @@ -4492,6 +4516,14 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, + "isemail": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/isemail/-/isemail-3.2.0.tgz", + "integrity": "sha512-zKqkK+O+dGqevc93KNsbZ/TqTUFd46MwWjYOoMrjIMZ51eU7DtQG3Wmd9SQQT7i7RVnuTPEiYEWHU3MSbxC1Tg==", + "requires": { + "punycode": "2.x.x" + } + }, "iserror": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/iserror/-/iserror-0.0.2.tgz", @@ -5070,6 +5102,16 @@ } } }, + "joi": { + "version": "14.3.1", + "resolved": "https://registry.npmjs.org/joi/-/joi-14.3.1.tgz", + "integrity": "sha512-LQDdM+pkOrpAn4Lp+neNIFV3axv1Vna3j38bisbQhETPMANYRbFJFUyOZcOClYvM/hppMhGWuKSFEK9vjrB+bQ==", + "requires": { + "hoek": "6.x.x", + "isemail": "3.x.x", + "topo": "3.x.x" + } + }, "js-beautify": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.9.1.tgz", @@ -8094,6 +8136,14 @@ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" }, + "topo": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/topo/-/topo-3.0.3.tgz", + "integrity": "sha512-IgpPtvD4kjrJ7CRA3ov2FhWQADwv+Tdqbsf1ZnPUSAtCJ9e1Z44MmoSGDXGk4IppoZA7jd/QRkNddlLJWlUZsQ==", + "requires": { + "hoek": "6.x.x" + } + }, "toposort-class": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toposort-class/-/toposort-class-1.0.1.tgz", diff --git a/package.json b/package.json index e388c6ed..bdd8c757 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,7 @@ "deep-assign": "^3.0.0", "express": "^4.16.4", "express-promise-router": "^3.0.3", + "joi": "^14.3.1", "moment": "^2.24.0", "morgan": "^1.9.1", "pg": "^7.10.0",