diff --git a/.github/workflows/test.yml-template b/.github/workflows/test.yml-template new file mode 100644 index 00000000..bb13dfc4 --- /dev/null +++ b/.github/workflows/test.yml-template @@ -0,0 +1,23 @@ +name: Test + +on: + pull_request: + branches: [ master ] + +jobs: + build: + + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [20.x] + + steps: + - uses: actions/checkout@v2 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - run: npm install + - run: npm test diff --git a/package-lock.json b/package-lock.json index c86872bf..5543485c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,7 @@ }, "devDependencies": { "@mate-academy/eslint-config": "latest", - "@mate-academy/scripts": "^1.8.6", + "@mate-academy/scripts": "^2.1.3", "eslint": "^8.57.0", "eslint-plugin-jest": "^28.6.0", "eslint-plugin-node": "^11.1.0", @@ -1472,10 +1472,11 @@ } }, "node_modules/@mate-academy/scripts": { - "version": "1.8.6", - "resolved": "https://registry.npmjs.org/@mate-academy/scripts/-/scripts-1.8.6.tgz", - "integrity": "sha512-b4om/whj4G9emyi84ORE3FRZzCRwRIesr8tJHXa8EvJdOaAPDpzcJ8A0sFfMsWH9NUOVmOwkBtOXDu5eZZ00Ig==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@mate-academy/scripts/-/scripts-2.1.3.tgz", + "integrity": "sha512-a07wHTj/1QUK2Aac5zHad+sGw4rIvcNl5lJmJpAD7OxeSbnCdyI6RXUHwXhjF5MaVo9YHrJ0xVahyERS2IIyBQ==", "dev": true, + "license": "MIT", "dependencies": { "@octokit/rest": "^17.11.2", "@types/get-port": "^4.2.0", diff --git a/package.json b/package.json index 9b8e432d..b5470cee 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ }, "devDependencies": { "@mate-academy/eslint-config": "latest", - "@mate-academy/scripts": "^1.8.6", + "@mate-academy/scripts": "^2.1.3", "eslint": "^8.57.0", "eslint-plugin-jest": "^28.6.0", "eslint-plugin-node": "^11.1.0", diff --git a/src/controllers/expensesCtrl.js b/src/controllers/expensesCtrl.js new file mode 100644 index 00000000..721d704e --- /dev/null +++ b/src/controllers/expensesCtrl.js @@ -0,0 +1,111 @@ +'use strict'; + +const expensesService = require('../services/expensesSvc'); +const usersService = require('../services/usersSvc'); + +const expensesController = { + create(req, res) { + const { userId, spentAt, title, amount, category, note } = req.body; + + // Check if required fields are provided + if ( + userId === undefined || + !spentAt || + !title || + amount === undefined || + !category || + !note + ) { + res.status(400).json({ error: 'missing required fields' }); + + return; + } + + // Check if user exists + const user = usersService.getById(userId); + + if (!user) { + res.status(404).json({ error: 'user not found' }); + + return; + } + + const expense = expensesService.create({ + userId, + spentAt, + title, + amount, + category, + note, + }); + + res.status(201).json(expense); + }, + + getAll(req, res) { + const { userId, from, to, categories } = req.query; + + const filters = {}; + + if (userId) { + filters.userId = Number(userId); + } + + if (from) { + filters.from = from; + } + + if (to) { + filters.to = to; + } + + if (categories) { + filters.categories = categories.split(',').map((c) => c.trim()); + } + + const expenses = expensesService.getAll(filters); + + res.status(200).json(expenses); + }, + + getById(req, res) { + const { id } = req.params; + const expense = expensesService.getById(Number(id)); + + if (!expense) { + res.status(404).json({ error: 'expense not found' }); + + return; + } + + res.status(200).json(expense); + }, + + update(req, res) { + const { id } = req.params; + const expense = expensesService.update(Number(id), req.body); + + if (!expense) { + res.status(404).json({ error: 'expense not found' }); + + return; + } + + res.status(200).json(expense); + }, + + delete(req, res) { + const { id } = req.params; + const deleted = expensesService.delete(Number(id)); + + if (!deleted) { + res.status(404).json({ error: 'expense not found' }); + + return; + } + + res.status(204).send(); + }, +}; + +module.exports = expensesController; diff --git a/src/controllers/usersCtrl.js b/src/controllers/usersCtrl.js new file mode 100644 index 00000000..7a5415e6 --- /dev/null +++ b/src/controllers/usersCtrl.js @@ -0,0 +1,66 @@ +'use strict'; + +const usersService = require('../services/usersSvc'); + +const usersController = { + create(req, res) { + const { name } = req.body; + + if (!name) { + res.status(400).json({ error: 'name is required' }); + + return; + } + + const user = usersService.create(name); + + res.status(201).json(user); + }, + + getAll(req, res) { + const users = usersService.getAll(); + + res.status(200).json(users); + }, + + getById(req, res) { + const { id } = req.params; + const user = usersService.getById(Number(id)); + + if (!user) { + res.status(404).json({ error: 'user not found' }); + + return; + } + + res.status(200).json(user); + }, + + update(req, res) { + const { id } = req.params; + const user = usersService.update(Number(id), req.body); + + if (!user) { + res.status(404).json({ error: 'user not found' }); + + return; + } + + res.status(200).json(user); + }, + + delete(req, res) { + const { id } = req.params; + const deleted = usersService.delete(Number(id)); + + if (!deleted) { + res.status(404).json({ error: 'user not found' }); + + return; + } + + res.status(204).send(); + }, +}; + +module.exports = usersController; diff --git a/src/createServer.js b/src/createServer.js index 5b405372..3daf27ff 100644 --- a/src/createServer.js +++ b/src/createServer.js @@ -1,11 +1,23 @@ 'use strict'; -// const express = require('express'); +const express = require('express'); +const usersService = require('./services/usersSvc'); +const expensesService = require('./services/expensesSvc'); +const usersRouter = require('./routes/usersRt'); +const expensesRouter = require('./routes/expensesRt'); function createServer() { - // Use express to create a server - // Add a routes to the server - // Return the server (express app) + usersService.clear(); + expensesService.clear(); + + const app = express(); + + app.use(express.json()); + + app.use('/users', usersRouter); + app.use('/expenses', expensesRouter); + + return app; } module.exports = { diff --git a/src/routes/expensesRt.js b/src/routes/expensesRt.js new file mode 100644 index 00000000..7a4887a4 --- /dev/null +++ b/src/routes/expensesRt.js @@ -0,0 +1,14 @@ +'use strict'; + +const express = require('express'); +const expensesController = require('../controllers/expensesCtrl'); + +const router = express.Router(); + +router.post('/', (req, res) => expensesController.create(req, res)); +router.get('/', (req, res) => expensesController.getAll(req, res)); +router.get('/:id', (req, res) => expensesController.getById(req, res)); +router.patch('/:id', (req, res) => expensesController.update(req, res)); +router.delete('/:id', (req, res) => expensesController.delete(req, res)); + +module.exports = router; diff --git a/src/routes/usersRt.js b/src/routes/usersRt.js new file mode 100644 index 00000000..915a5b72 --- /dev/null +++ b/src/routes/usersRt.js @@ -0,0 +1,14 @@ +'use strict'; + +const express = require('express'); +const usersController = require('../controllers/usersCtrl'); + +const router = express.Router(); + +router.post('/', (req, res) => usersController.create(req, res)); +router.get('/', (req, res) => usersController.getAll(req, res)); +router.get('/:id', (req, res) => usersController.getById(req, res)); +router.patch('/:id', (req, res) => usersController.update(req, res)); +router.delete('/:id', (req, res) => usersController.delete(req, res)); + +module.exports = router; diff --git a/src/services/expensesSvc.js b/src/services/expensesSvc.js new file mode 100644 index 00000000..59afad6c --- /dev/null +++ b/src/services/expensesSvc.js @@ -0,0 +1,104 @@ +'use strict'; + +let expenses = []; +let expenseId = 1; + +const expensesService = { + clear() { + expenses = []; + expenseId = 1; + }, + + create(expenseData) { + const expense = { + id: expenseId, + ...expenseData, + }; + + expenses.push(expense); + expenseId += 1; + + return expense; + }, + + getAll(filters = {}) { + let result = expenses; + + if (filters.userId) { + result = result.filter((e) => e.userId === filters.userId); + } + + if (filters.from) { + result = result.filter( + (e) => new Date(e.spentAt) >= new Date(filters.from), + ); + } + + if (filters.to) { + result = result.filter( + (e) => new Date(e.spentAt) <= new Date(filters.to), + ); + } + + if (filters.categories) { + const categoryList = Array.isArray(filters.categories) + ? filters.categories + : [filters.categories]; + + result = result.filter((e) => categoryList.includes(e.category)); + } + + return result; + }, + + getById(id) { + return expenses.find((e) => e.id === id); + }, + + update(id, data) { + const expense = this.getById(id); + + if (!expense) { + return null; + } + + if (data.userId !== undefined) { + expense.userId = data.userId; + } + + if (data.spentAt !== undefined) { + expense.spentAt = data.spentAt; + } + + if (data.title !== undefined) { + expense.title = data.title; + } + + if (data.amount !== undefined) { + expense.amount = data.amount; + } + + if (data.category !== undefined) { + expense.category = data.category; + } + + if (data.note !== undefined) { + expense.note = data.note; + } + + return expense; + }, + + delete(id) { + const index = expenses.findIndex((e) => e.id === id); + + if (index === -1) { + return false; + } + expenses.splice(index, 1); + + return true; + }, +}; + +module.exports = expensesService; diff --git a/src/services/usersSvc.js b/src/services/usersSvc.js new file mode 100644 index 00000000..453b168c --- /dev/null +++ b/src/services/usersSvc.js @@ -0,0 +1,58 @@ +'use strict'; + +let users = []; +let userId = 1; + +const usersService = { + clear() { + users = []; + userId = 1; + }, + + create(name) { + const user = { + id: userId, + name, + }; + + users.push(user); + userId += 1; + + return user; + }, + + getAll() { + return users; + }, + + getById(id) { + return users.find((u) => u.id === id); + }, + + update(id, data) { + const user = this.getById(id); + + if (!user) { + return null; + } + + if (data.name !== undefined) { + user.name = data.name; + } + + return user; + }, + + delete(id) { + const index = users.findIndex((u) => u.id === id); + + if (index === -1) { + return false; + } + users.splice(index, 1); + + return true; + }, +}; + +module.exports = usersService; diff --git a/tests/expense.test.js b/tests/expense.test.js index b5bcbf1d..e7b3b847 100644 --- a/tests/expense.test.js +++ b/tests/expense.test.js @@ -47,7 +47,7 @@ describe('Expense', () => { await api.post('/expenses').send({}).expect(400); }); - it('should return 400 if user not found', async () => { + it('should return 404 if user not found', async () => { const expenseData = { userId: 1, spentAt: '2022-10-19T11:01:43.462Z', @@ -57,7 +57,7 @@ describe('Expense', () => { note: 'I need a new laptop', }; - await api.post('/expenses').send(expenseData).expect(400); + await api.post('/expenses').send(expenseData).expect(404); }); });