diff --git a/src/controller/expenses.controller.js b/src/controller/expenses.controller.js new file mode 100644 index 000000000..1a4da278a --- /dev/null +++ b/src/controller/expenses.controller.js @@ -0,0 +1,120 @@ +const expensesServices = require('../services/expenses.services'); +const usersService = require('../services/users.services'); + +async function getExpenses(req, res) { + const { userId, categories, from, to } = req.query; + + const expenses = await expensesServices.getExpenses({ + userId: +userId, + categories, + from, + to, + }); + + res.send(expenses.map(expensesServices.normalize)); +} + +async function getExpenseById(req, res) { + const { id } = req.params; + + if (!id || isNaN(+id)) { + return res.status(400).json({ message: 'Bad Request' }); + } + + const expense = await expensesServices.getExpenseById(+id); + + if (!expense) { + return res.status(404).json({ message: 'Not found' }); + } + res.send(expensesServices.normalize(expense)); +} + +async function createExpense(req, res) { + const { userId, spentAt, title, amount, category, note } = req.body; + + if ( + userId == null || + !spentAt || + !title || + amount == null || + isNaN(+amount) + ) { + return res.status(400).json({ message: 'Bad Request' }); + } + + const userExist = await usersService.getUserById(+userId); + + if (!userExist) { + return res.status(400).json({ message: 'Bad Request' }); + } + + const newExpense = await expensesServices.createExpense({ + userId: +userId, + spentAt, + title, + amount, + category: category || '', + note: note || '', + }); + + res.status(201).send(expensesServices.normalize(newExpense)); +} + +async function deleteExpense(req, res) { + const { id } = req.params; + + if (!id || isNaN(+id)) { + return res.status(400).json({ message: 'Bad Request' }); + } + + const wasDeleted = await expensesServices.deleteExpense(+id); + + if (!wasDeleted) { + return res.status(404).json({ message: 'Not found' }); + } + res.status(204).send(); +} + +async function updateExpense(req, res) { + const { id } = req.params; + + if (!id || isNaN(+id)) { + return res.status(400).json({ message: 'Bad Request' }); + } + + const expenseExist = await expensesServices.getExpenseById(+id); + + if (!expenseExist) { + return res.status(404).json({ message: 'Not found' }); + } + + const { spentAt, title, amount, category, note } = req.body; + + if ( + spentAt === undefined && + title === undefined && + amount === undefined && + category === undefined && + note === undefined + ) { + return res.status(400).json({ message: 'Bad Request' }); + } + + const updatedExpense = await expensesServices.updateExpense({ + id: +id, + ...req.body, + }); + + if (!updatedExpense) { + return res.status(404).json({ message: 'Not found' }); + } + res.send(expensesServices.normalize(updatedExpense)); +} + +module.exports = { + getExpenses, + getExpenseById, + createExpense, + deleteExpense, + updateExpense, +}; diff --git a/src/controller/users.controller.js b/src/controller/users.controller.js new file mode 100644 index 000000000..83d62665f --- /dev/null +++ b/src/controller/users.controller.js @@ -0,0 +1,73 @@ +const usersService = require('../services/users.services'); + +async function getUsers(req, res) { + const users = await usersService.getUsers(); + + res.send(users.map(usersService.normalize)); +} + +async function getUserById(req, res) { + const { id } = req.params; + + if (!id || isNaN(+id)) { + return res.status(400).json({ message: 'Bad Request' }); + } + + const user = await usersService.getUserById(+id); + + if (!user) { + return res.status(404).json({ message: 'Not found' }); + } + res.send(usersService.normalize(user)); +} + +async function createUser(req, res) { + const { name } = req.body; + + if (!name) { + return res.status(400).json({ message: 'Bad Request' }); + } + + const newUser = await usersService.createUser(name); + + res.status(201).send(usersService.normalize(newUser)); +} + +async function deleteUser(req, res) { + const { id } = req.params; + + if (!id || isNaN(+id)) { + return res.status(400).json({ message: 'Bad Request' }); + } + + const wasDeleted = await usersService.deleteUser(+id); + + if (!wasDeleted) { + return res.status(404).json({ message: 'Not found' }); + } + res.status(204).send(); +} + +async function updateUser(req, res) { + const { id } = req.params; + const { name } = req.body; + + if (!name || !id || isNaN(+id)) { + return res.status(400).json({ message: 'Bad Request' }); + } + + const updatedUser = await usersService.updateUser({ id: +id, name }); + + if (!updatedUser) { + return res.status(404).json({ message: 'Not found' }); + } + res.send(usersService.normalize(updatedUser)); +} + +module.exports = { + getUsers, + getUserById, + createUser, + deleteUser, + updateUser, +}; diff --git a/src/createServer.js b/src/createServer.js index 1ea5542d6..038f98c51 100644 --- a/src/createServer.js +++ b/src/createServer.js @@ -1,7 +1,35 @@ 'use strict'; +const express = require('express'); +const { usersRouter } = require('./routers/users.router'); +const cors = require('cors'); +const { expensesRouter } = require('./routers/expenses.router'); +const { User } = require('./models/User.model'); +const { Expense } = require('./models/Expense.model'); +const { sequelize } = require('./db.js'); const createServer = () => { - // your code goes here + const syncDb = async () => { + try { + await sequelize.authenticate(); + await User.sync(); + await Expense.sync(); + } catch (error) { + // eslint-disable-next-line no-console + console.error('DB sync failed:', error); + } + }; + + syncDb(); + + const app = express(); + + app.use(cors()); + app.use(express.json()); + + app.use('/users', usersRouter); + app.use('/expenses', expensesRouter); + + return app; }; module.exports = { diff --git a/src/db.js b/src/db.js index 1ba3046cc..6861ccaac 100644 --- a/src/db.js +++ b/src/db.js @@ -21,12 +21,12 @@ const { */ const sequelize = new Sequelize({ - database: POSTGRES_DB || 'postgres', + database: POSTGRES_DB || 'test', username: POSTGRES_USER || 'postgres', host: POSTGRES_HOST || 'localhost', dialect: 'postgres', port: POSTGRES_PORT || 5432, - password: POSTGRES_PASSWORD || '123', + password: POSTGRES_PASSWORD || 'password', }); module.exports = { diff --git a/src/models/Expense.model.js b/src/models/Expense.model.js index 567e1c3e7..c39943c18 100644 --- a/src/models/Expense.model.js +++ b/src/models/Expense.model.js @@ -1,9 +1,38 @@ 'use strict'; +const { DataTypes } = require('sequelize'); const { sequelize } = require('../db.js'); const Expense = sequelize.define( - // your code goes here + 'Expense', + { + userId: { + type: DataTypes.INTEGER, + allowNull: false, + }, + spentAt: { + type: DataTypes.DATE, + allowNull: false, + }, + title: { + type: DataTypes.STRING, + allowNull: false, + }, + amount: { + type: DataTypes.INTEGER, + allowNull: false, + }, + category: { + type: DataTypes.STRING, + allowNull: false, + }, + note: { + type: DataTypes.STRING, + }, + }, + { + tableName: 'expenses', + }, ); module.exports = { diff --git a/src/models/User.model.js b/src/models/User.model.js index 61861c9e4..9bb805acb 100644 --- a/src/models/User.model.js +++ b/src/models/User.model.js @@ -1,9 +1,19 @@ 'use strict'; +const { DataTypes } = require('sequelize'); const { sequelize } = require('../db.js'); const User = sequelize.define( - // your code goes here + 'User', + { + name: { + type: DataTypes.STRING, + allowNull: false, + }, + }, + { + tableName: 'users', + }, ); module.exports = { diff --git a/src/routers/expenses.router.js b/src/routers/expenses.router.js new file mode 100644 index 000000000..da62b48c7 --- /dev/null +++ b/src/routers/expenses.router.js @@ -0,0 +1,15 @@ +const expensesController = require('../controller/expenses.controller'); + +const Router = require('express').Router; + +const expensesRouter = Router(); + +expensesRouter.get('/', expensesController.getExpenses); +expensesRouter.get('/:id', expensesController.getExpenseById); +expensesRouter.post('/', expensesController.createExpense); +expensesRouter.delete('/:id', expensesController.deleteExpense); +expensesRouter.patch('/:id', expensesController.updateExpense); + +module.exports = { + expensesRouter, +}; diff --git a/src/routers/users.router.js b/src/routers/users.router.js new file mode 100644 index 000000000..6b5664d0c --- /dev/null +++ b/src/routers/users.router.js @@ -0,0 +1,15 @@ +const usersController = require('../controller/users.controller'); + +const Router = require('express').Router; + +const usersRouter = Router(); + +usersRouter.get('/', usersController.getUsers); +usersRouter.get('/:id', usersController.getUserById); +usersRouter.post('/', usersController.createUser); +usersRouter.delete('/:id', usersController.deleteUser); +usersRouter.patch('/:id', usersController.updateUser); + +module.exports = { + usersRouter, +}; diff --git a/src/services/expenses.services.js b/src/services/expenses.services.js new file mode 100644 index 000000000..3ff82f8b0 --- /dev/null +++ b/src/services/expenses.services.js @@ -0,0 +1,92 @@ +const { Expense } = require('../models/Expense.model'); +const { Op } = require('sequelize'); + +function normalize({ id, userId, spentAt, title, amount, category, note }) { + return { + id, + userId, + spentAt, + title, + amount, + category, + note, + }; +} + +function getExpenses(data) { + const { userId, categories, from, to } = data; + + const whereConditions = {}; + + if (userId) { + whereConditions.userId = userId; + } + + if (categories) { + const categoriesArray = Array.isArray(categories) + ? categories + : [categories]; + + whereConditions.category = { + [Op.in]: categoriesArray, + }; + } + + if (from || to) { + whereConditions.spentAt = {}; + + if (from) { + whereConditions.spentAt[Op.gte] = new Date(from); + } + + if (to) { + whereConditions.spentAt[Op.lte] = new Date(to); + } + } + + return Expense.findAll({ + where: whereConditions, + }); +} + +function getExpenseById(id) { + return Expense.findByPk(id); +} + +function createExpense({ userId, spentAt, title, amount, category, note }) { + return Expense.create({ + userId, + spentAt, + title, + amount, + category, + note: note || '', + }); +} + +function deleteExpense(id) { + return Expense.destroy({ + where: { + id, + }, + }); +} + +async function updateExpense(data) { + await Expense.update(data, { + where: { + id: data.id, + }, + }); + + return Expense.findByPk(data.id); +} + +module.exports = { + getExpenses, + getExpenseById, + createExpense, + deleteExpense, + updateExpense, + normalize, +}; diff --git a/src/services/users.services.js b/src/services/users.services.js new file mode 100644 index 000000000..823794620 --- /dev/null +++ b/src/services/users.services.js @@ -0,0 +1,39 @@ +const { User } = require('../models/User.model'); + +function normalize({ id, name }) { + return { + id, + name, + }; +} + +function getUsers() { + return User.findAll(); +} + +function getUserById(id) { + return User.findByPk(id); +} + +function createUser(name) { + return User.create({ name }); +} + +function deleteUser(id) { + return User.destroy({ where: { id } }); +} + +async function updateUser({ id, name }) { + await User.update({ name }, { where: { id } }); + + return User.findByPk(id); +} + +module.exports = { + getUsers, + createUser, + deleteUser, + getUserById, + updateUser, + normalize, +};