From 8c8ec1534e43fc1fb13bfaada56ce55df4682347 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Sun, 19 Apr 2020 18:26:22 +0200 Subject: [PATCH] feat: Currencies CRUD. --- server/src/database/factories/index.js | 7 + .../20200419171451_create_currencies_table.js | 12 ++ server/src/http/controllers/Currencies.js | 117 +++++++++-- server/src/models/Currency.js | 10 + server/tests/routes/currencies.test.js | 188 ++++++++++++++++++ 5 files changed, 321 insertions(+), 13 deletions(-) create mode 100644 server/src/database/migrations/20200419171451_create_currencies_table.js create mode 100644 server/src/models/Currency.js create mode 100644 server/tests/routes/currencies.test.js diff --git a/server/src/database/factories/index.js b/server/src/database/factories/index.js index 6981b664f..2f0cc8998 100644 --- a/server/src/database/factories/index.js +++ b/server/src/database/factories/index.js @@ -243,6 +243,13 @@ factory.define('option', 'options', async () => { }; }); +factory.define('currency', 'currencies', async () => { + return { + currency_name: faker.lorem.slug(), + currency_code: 'USD', + }; +}); + factory.define('budget', 'budgets', async () => { return { name: faker.lorem.slug(), diff --git a/server/src/database/migrations/20200419171451_create_currencies_table.js b/server/src/database/migrations/20200419171451_create_currencies_table.js new file mode 100644 index 000000000..43aa21da2 --- /dev/null +++ b/server/src/database/migrations/20200419171451_create_currencies_table.js @@ -0,0 +1,12 @@ + +exports.up = function(knex) { + return knex.schema.createTable('currencies', table => { + table.increments(); + table.string('currency_name'); + table.string('currency_code', 4); + }) +}; + +exports.down = function(knex) { + return knex.schema.dropTableIfExists('currencies'); +}; diff --git a/server/src/http/controllers/Currencies.js b/server/src/http/controllers/Currencies.js index 630ee8254..190919304 100644 --- a/server/src/http/controllers/Currencies.js +++ b/server/src/http/controllers/Currencies.js @@ -1,45 +1,136 @@ import express from 'express'; -import { check, validationResult } from 'express-validator'; +import { check, param, validationResult } from 'express-validator'; import asyncMiddleware from '@/http/middleware/asyncMiddleware'; +import Currency from '@/models/Currency'; +import jwtAuth from '@/http/middleware/jwtAuth'; export default { router() { const router = express.Router(); + router.use(jwtAuth); - router.get('/all', + router.get('/', this.all.validation, asyncMiddleware(this.all.handler)); - router.get('/registered', - this.registered.validation, - asyncMiddleware(this.registered.handler)); + router.post('/', + this.newCurrency.validation, + asyncMiddleware(this.newCurrency.handler)); + + router.post('/:id', + this.editCurrency.validation, + asyncMiddleware(this.editCurrency.handler)); + + router.delete('/:currency_code', + this.deleteCurrecy.validation, + asyncMiddleware(this.deleteCurrecy.handler)); return router; }, + /** + * Retrieve all registered currency details. + */ all: { validation: [], async handler(req, res) { + const currencies = await Currency.query(); return res.status(200).send({ currencies: [ - { currency_code: 'USD', currency_sign: '$' }, - { currency_code: 'LYD', currency_sign: '' }, + ...currencies, ], }); }, }, - registered: { - validation: [], + newCurrency: { + validation: [ + check('currency_name').exists().trim().escape(), + check('currency_code').exists().trim().escape(), + ], async handler(req, res) { + const validationErrors = validationResult(req); + + if (!validationErrors.isEmpty()) { + return res.boom.badData(null, { + code: 'validation_error', ...validationErrors, + }); + } + const form = { ...req.body }; + + const foundCurrency = await Currency.query() + .where('currency_code', form.currency_code); + + if (foundCurrency.length > 0) { + return res.status(400).send({ + errors: [{ type: 'CURRENCY.CODE.ALREADY.EXISTS', code: 100 }], + }); + } + await Currency.query() + .insert({ ...form }); return res.status(200).send({ - currencies: [ - { currency_code: 'USD', currency_sign: '$' }, - { currency_code: 'LYD', currency_sign: '' }, - ], + currency: { ...form }, + }); + }, + }, + + deleteCurrecy: { + validation: [ + param('currency_code').exists().trim().escape(), + ], + async handler(req, res) { + const validationErrors = validationResult(req); + + if (!validationErrors.isEmpty()) { + return res.boom.badData(null, { + code: 'validation_error', ...validationErrors, + }); + } + const { currency_code: currencyCode } = req.params; + + await Currency.query() + .where('currency_code', currencyCode) + .delete(); + + return res.status(200).send({ currency_code: currencyCode }); + }, + }, + + editCurrency: { + validation: [ + param('id').exists().isNumeric().toInt(), + check('currency_name').exists().trim().escape(), + check('currency_code').exists().trim().escape(), + ], + async handler(req, res) { + const validationErrors = validationResult(req); + + if (!validationErrors.isEmpty()) { + return res.boom.badData(null, { + code: 'validation_error', ...validationErrors, + }); + } + const form = { ...req.body }; + const { id } = req.params; + + const foundCurrency = await Currency.query() + .where('currency_code', form.currency_code) + .whereNot('id', id); + + if (foundCurrency.length > 0) { + return res.status(400).send({ + errors: [{ type: 'CURRENCY.CODE.ALREADY.EXISTS', code: 100 }], + }); + } + await Currency.query() + .where('id', id) + .update({ ...form }); + + return res.status(200).send({ + currency: { ...form }, }); }, }, diff --git a/server/src/models/Currency.js b/server/src/models/Currency.js new file mode 100644 index 000000000..7f8b37daa --- /dev/null +++ b/server/src/models/Currency.js @@ -0,0 +1,10 @@ +import BaseModel from '@/models/Model'; + +export default class Currency extends BaseModel { + /** + * Table name + */ + static get tableName() { + return 'currencies'; + } +} diff --git a/server/tests/routes/currencies.test.js b/server/tests/routes/currencies.test.js new file mode 100644 index 000000000..8931cd9b4 --- /dev/null +++ b/server/tests/routes/currencies.test.js @@ -0,0 +1,188 @@ +import { + request, + create, + expect, + login, +} from '~/testInit'; +import Currency from '@/models/Currency'; + +let loginRes; + +describe('route: /currencies/', () => { + beforeEach(async () => { + loginRes = await login(); + }); + afterEach(() => { + loginRes = null; + }); + + describe('POST: `/api/currencies`', () => { + + it('Should response unauthorized in case user was not logged in.', async () => { + const res = await request() + .post('/api/currencies') + .send(); + + expect(res.status).equals(401); + expect(res.body.message).equals('unauthorized'); + }); + + it('Should `currency_name` be required.', async () => { + const res = await request() + .post('/api/currencies') + .set('x-access-token', loginRes.body.token) + .send(); + + expect(res.status).equals(422); + expect(res.body.code).equals('validation_error'); + expect(res.body.errors).include.something.deep.equals({ + msg: 'Invalid value', param: 'currency_name', location: 'body', + }); + }); + + it('Should `currency_code` be required.', async () => { + const res = await request() + .post('/api/currencies') + .set('x-access-token', loginRes.body.token) + .send(); + + expect(res.status).equals(422); + expect(res.body.code).equals('validation_error'); + expect(res.body.errors).include.something.deep.equals({ + msg: 'Invalid value', param: 'currency_code', location: 'body', + }); + }); + + it('Should response currency code is duplicated.', async () => { + create('currency', { currency_code: 'USD' }); + + const res = await request() + .post('/api/currencies') + .set('x-access-token', loginRes.body.token) + .send({ + currency_code: 'USD', + currency_name: 'Dollar', + }); + + expect(res.status).equals(400); + expect(res.body.errors).include.something.deep.equals({ + type: 'CURRENCY.CODE.ALREADY.EXISTS', code: 100, + }); + }); + + it('Should insert currency details to the storage.', async () => { + const res = await request() + .post('/api/currencies') + .set('x-access-token', loginRes.body.token) + .send({ + currency_code: 'USD', + currency_name: 'Dollar', + }); + + const foundCurrency = await Currency.query().where('currency_code', 'USD'); + + expect(foundCurrency.length).equals(1); + expect(foundCurrency[0].currencyCode).equals('USD'); + expect(foundCurrency[0].currencyName).equals('Dollar'); + }); + + it('Should response success with correct data.', async () => { + const res = await request() + .post('/api/currencies') + .set('x-access-token', loginRes.body.token) + .send({ + currency_code: 'USD', + currency_name: 'Dollar', + }); + + expect(res.status).equals(200); + }); + }); + + describe('DELETE: `/api/currencies/:currency_code`', () => { + + it('Should delete the given currency code from the storage.', async () => { + const currency = await create('currency'); + const res = await request() + .delete(`/api/currencies/${currency.currencyCode}`) + .set('x-access-token', loginRes.body.token) + .send(); + + expect(res.status).equals(200); + + const foundCurrency = await Currency.query().where('currency_code', 'USD'); + expect(foundCurrency.length).equals(0); + }); + }); + + describe('POST: `/api/currencies/:id`', () => { + it('Should `currency_name` be required.', async () => { + const currency = await create('currency'); + const res = await request() + .post(`/api/currencies/${currency.code}`) + .set('x-access-token', loginRes.body.token) + .send(); + + expect(res.status).equals(422); + expect(res.body.code).equals('validation_error'); + expect(res.body.errors).include.something.deep.equals({ + msg: 'Invalid value', param: 'currency_name', location: 'body', + }); + }); + + it('Should `currency_code` be required.', async () => { + const currency = await create('currency'); + const res = await request() + .post(`/api/currencies/${currency.code}`) + .set('x-access-token', loginRes.body.token) + .send(); + + expect(res.status).equals(422); + expect(res.body.code).equals('validation_error'); + expect(res.body.errors).include.something.deep.equals({ + msg: 'Invalid value', param: 'currency_code', location: 'body', + }); + }); + + it('Should response currency code is duplicated.', async () => { + const currency1 = await create('currency'); + const currency2 = await create('currency'); + + const res = await request() + .post(`/api/currencies/${currency2.id}`) + .set('x-access-token', loginRes.body.token) + .send({ + currency_code: currency1.currencyCode, + currency_name: 'Dollar', + }); + + expect(res.status).equals(400); + expect(res.body.errors).include.something.deep.equals({ + type: 'CURRENCY.CODE.ALREADY.EXISTS', code: 100, + }); + }); + + it('Should update currency details of the given currency on the storage.', async () => { + const currency1 = await create('currency'); + const currency2 = await create('currency'); + + const res = await request() + .post(`/api/currencies/${currency2.id}`) + .set('x-access-token', loginRes.body.token) + .send({ + currency_code: 'ABC', + currency_name: 'Name', + }); + + const foundCurrency = await Currency.query().where('currency_code', 'ABC'); + + expect(foundCurrency.length).equals(1); + expect(foundCurrency[0].currencyCode).equals('ABC'); + expect(foundCurrency[0].currencyName).equals('Name'); + }); + + it('Should response success with correct data.', () => { + + }); + }) +});