From 51d1edc5932787e5987592853c40c7ad305e46b9 Mon Sep 17 00:00:00 2001 From: Volodymyr Kolosov Date: Wed, 22 Apr 2026 16:00:01 +0100 Subject: [PATCH 1/6] app-with-db --- package-lock.json | 9 +- package.json | 2 +- src/controlers/expenses.controler.js | 196 +++++++++++++++++++++++++++ src/controlers/user.controler.js | 88 ++++++++++++ src/createServer.js | 15 +- src/db.js | 8 +- src/models/Expense.model.js | 40 +++++- src/models/User.model.js | 19 ++- src/routes/expenses.rotes.js | 16 +++ src/routes/user.routes.js | 16 +++ src/services/expenses.services.js | 53 ++++++++ src/services/user.services.js | 38 ++++++ 12 files changed, 485 insertions(+), 15 deletions(-) create mode 100644 src/controlers/expenses.controler.js create mode 100644 src/controlers/user.controler.js create mode 100644 src/routes/expenses.rotes.js create mode 100644 src/routes/user.routes.js create mode 100644 src/services/expenses.services.js create mode 100644 src/services/user.services.js diff --git a/package-lock.json b/package-lock.json index cd25728ae..03a0a7224 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,7 @@ }, "devDependencies": { "@mate-academy/eslint-config": "latest", - "@mate-academy/scripts": "^1.8.6", + "@mate-academy/scripts": "^2.1.3", "axios": "^1.7.2", "eslint": "^8.57.0", "eslint-plugin-jest": "^28.6.0", @@ -1474,10 +1474,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 bdf079250..e7103013c 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ }, "devDependencies": { "@mate-academy/eslint-config": "latest", - "@mate-academy/scripts": "^1.8.6", + "@mate-academy/scripts": "^2.1.3", "axios": "^1.7.2", "eslint": "^8.57.0", "eslint-plugin-jest": "^28.6.0", diff --git a/src/controlers/expenses.controler.js b/src/controlers/expenses.controler.js new file mode 100644 index 000000000..b6dba7f52 --- /dev/null +++ b/src/controlers/expenses.controler.js @@ -0,0 +1,196 @@ +const expenseServices = require('../services/expenses.services'); +const userServices = require('../services/user.services'); + +const getAll = async (req, res) => { + let { userId, categories, from, to } = req.query; + + if (userId) { + userId = Number(userId); + + if (isNaN(userId)) { + return res.status(400).send('Bad request'); + } + } else { + userId = null; + } + + if (categories) { + categories = categories.split(',').map((c) => c.trim()); + } else { + categories = null; + } + + if (from) { + const parsed = Date.parse(from); + + if (isNaN(parsed)) { + return res.status(400).send('Bad request'); + } + + from = new Date(parsed); + } else { + from = null; + } + + if (to) { + const parsed = Date.parse(to); + + if (isNaN(parsed)) { + return res.status(400).send('Bad request'); + } + + to = new Date(parsed); + } else { + to = null; + } + + const data = await expenseServices.get({ + userId, + categories, + from, + to, + }); + + res.status(200).send(data); +}; + +const getOne = async (req, res) => { + const { id } = req.params; + const normalaizedId = Number(id); + + if (isNaN(normalaizedId)) { + return res.status(400).send('Bad request'); + } + + const expense = await expenseServices.getOne(id); + + if (!expense) { + return res.status(404).send('Not fond'); + } + + res.status(200).send(expense); +}; + +const add = async (req, res) => { + const { userId, spentAt, title, amount, category, note } = req.body; + const normalaizedId = Number(userId); + const normalaizedAmount = Number(amount); + const normalaizedTitle = title ? title.trim() : null; + const normalaizedSpentAt = spentAt ? Date.parse(spentAt) : Date.now(); + + const checker = + isNaN(normalaizedId) || + isNaN(normalaizedAmount) || + !normalaizedTitle || + isNaN(normalaizedSpentAt); + + if (checker) { + return res.status(400).send('Bad request'); + } + + const user = await userServices.getOne(normalaizedId); + + if (!user) { + return res.status(400).send('User not found'); + } + + const newUser = await expenseServices.add({ + userId: normalaizedId, + title: normalaizedTitle, + amount: normalaizedAmount, + spentAt: normalaizedSpentAt, + category: category ? category.trim() : null, + note: note ? note.trim() : null, + }); + + res.status(201).send(newUser); +}; + +const remove = async (req, res) => { + const { id } = req.params; + const narmalaizedId = Number(id); + + if (isNaN(narmalaizedId)) { + return res.status(400).send('Bad request'); + } + + const expense = await expenseServices.getOne(id); + + if (!expense) { + return res.status(404).send('Not found'); + } + + await expenseServices.remove(id); + res.sendStatus(204); +}; + +const update = async (req, res) => { + const { id } = req.params; + const { spentAt, title, amount, category, note } = req.body; + + const normalaizedId = Number(id); + + if (isNaN(normalaizedId)) { + return res.status(400).send('Bad request'); + } + + const expense = await expenseServices.getOne(normalaizedId); + + if (!expense) { + return res.status(404).send('Not found'); + } + + const data = {}; + + if (title !== undefined) { + const normalaizedTitle = title.trim(); + + if (!normalaizedTitle) { + return res.status(400).send('Bad request'); + } + + data.title = normalaizedTitle; + } + + if (amount !== undefined) { + const normalaizedAmount = Number(amount); + + if (isNaN(normalaizedAmount)) { + return res.status(400).send('Bad request'); + } + + data.amount = normalaizedAmount; + } + + if (spentAt !== undefined) { + const normalaizedSpentAt = Date.parse(spentAt); + + if (isNaN(normalaizedSpentAt)) { + return res.status(400).send('Bad request'); + } + + data.spentAt = new Date(normalaizedSpentAt); + } + + if (category !== undefined) { + data.category = category.trim() || null; + } + + if (note !== undefined) { + data.note = note.trim() || null; + } + + await expenseServices.update({ id: normalaizedId, ...data }); + + const updatedExpense = await expenseServices.getOne(id); + + res.status(200).send(updatedExpense); +}; + +module.exports = { + getAll, + getOne, + add, + remove, + update, +}; diff --git a/src/controlers/user.controler.js b/src/controlers/user.controler.js new file mode 100644 index 000000000..f24b106c6 --- /dev/null +++ b/src/controlers/user.controler.js @@ -0,0 +1,88 @@ +const userServices = require('../services/user.services'); + +const getAll = async (req, res) => { + const users = await userServices.getAll(); + + res.status(200).send(users); +}; + +const getOne = async (req, res) => { + const { id } = req.params; + const normalaizedId = Number(id); + + if (isNaN(normalaizedId)) { + return res.status(400).send('Bad request'); + } + + const user = await userServices.getOne(normalaizedId); + + if (!user) { + return res.status(404).send('Not found'); + } + + res.status(200).send(user); +}; + +const add = async (req, res) => { + const { name } = req.body; + + if (!name) { + return res.status(400).send('Bad request'); + } + + const normalaizedName = name.trim(); + + const newUser = await userServices.add(normalaizedName); + + res.status(201).send(newUser); +}; + +const remove = async (req, res) => { + const { id } = req.params; + const normalaizedId = Number(id); + + if (isNaN(normalaizedId)) { + return res.status(400).send('Bad request'); + } + + const userForRemove = await userServices.getOne(id); + + if (!userForRemove) { + return res.status(404).send('Not found'); + } + + await userServices.remove(normalaizedId); + res.sendStatus(204); +}; + +const update = async (req, res) => { + const { id } = req.params; + const { name } = req.body; + const normalaizedId = Number(id); + + if (isNaN(normalaizedId) || !name) { + return res.status(400).send('Bad request'); + } + + const normalaizedName = name.trim(); + + const userForUpdate = await userServices.getOne(normalaizedId); + + if (!userForUpdate) { + return res.status(404).send('Not Found'); + } + + await userServices.update({ id: normalaizedId, name: normalaizedName }); + + const updatedUser = await userServices.getOne(normalaizedId); + + res.status(200).send(updatedUser); +}; + +module.exports = { + getAll, + getOne, + update, + remove, + add, +}; diff --git a/src/createServer.js b/src/createServer.js index 1ea5542d6..bd8695587 100644 --- a/src/createServer.js +++ b/src/createServer.js @@ -1,7 +1,20 @@ 'use strict'; +const express = require('express'); +const cors = require('cors'); +const userRoter = require('./routes/user.routes'); +const expensesRouter = require('./routes/expenses.rotes'); + const createServer = () => { - // your code goes here + const app = express(); + + app.use(cors()); + + app.use('/users', express.json(), userRoter); + + app.use('/expenses', express.json(), expensesRouter); + + return app; }; module.exports = { diff --git a/src/db.js b/src/db.js index 1ba3046cc..b9ad3928e 100644 --- a/src/db.js +++ b/src/db.js @@ -3,7 +3,6 @@ const { Sequelize } = require('sequelize'); const utils = require('util'); -// Needed for testing purposes, do not remove require('dotenv').config(); global.TextEncoder = utils.TextEncoder; @@ -15,18 +14,13 @@ const { POSTGRES_DB, } = process.env; -/* - All credentials setted to default values (exsept password - it is exapmle) - replace if needed with your own -*/ - const sequelize = new Sequelize({ database: POSTGRES_DB || 'postgres', username: POSTGRES_USER || 'postgres', host: POSTGRES_HOST || 'localhost', dialect: 'postgres', port: POSTGRES_PORT || 5432, - password: POSTGRES_PASSWORD || '123', + password: POSTGRES_PASSWORD || 'Mocliamg1', }); module.exports = { diff --git a/src/models/Expense.model.js b/src/models/Expense.model.js index 567e1c3e7..c98ea5675 100644 --- a/src/models/Expense.model.js +++ b/src/models/Expense.model.js @@ -1,9 +1,47 @@ 'use strict'; +const { DataTypes } = require('sequelize'); const { sequelize } = require('../db.js'); const Expense = sequelize.define( - // your code goes here + 'Expense', + { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + }, + userId: { + type: DataTypes.INTEGER, + allowNull: false, + }, + spentAt: { + type: DataTypes.DATE, + allowNull: false, + defaultValue: DataTypes.NOW, + }, + title: { + type: DataTypes.TEXT, + allowNull: false, + }, + amount: { + type: DataTypes.FLOAT, + allowNull: false, + }, + category: { + type: DataTypes.STRING, + allowNull: true, + }, + note: { + type: DataTypes.TEXT, + allowNull: true, + }, + }, + { + tableName: 'expenses', + createdAt: false, + updatedAt: false, + }, ); module.exports = { diff --git a/src/models/User.model.js b/src/models/User.model.js index 61861c9e4..2ac1ad1ea 100644 --- a/src/models/User.model.js +++ b/src/models/User.model.js @@ -1,9 +1,26 @@ 'use strict'; +const { DataTypes } = require('sequelize'); const { sequelize } = require('../db.js'); const User = sequelize.define( - // your code goes here + 'User', + { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + }, + name: { + type: DataTypes.TEXT, + allowNull: false, + }, + }, + { + tableName: 'users', + createdAt: false, + updatedAt: false, + }, ); module.exports = { diff --git a/src/routes/expenses.rotes.js b/src/routes/expenses.rotes.js new file mode 100644 index 000000000..748cbceb4 --- /dev/null +++ b/src/routes/expenses.rotes.js @@ -0,0 +1,16 @@ +const express = require('express'); +const expenseControler = require('../controlers/expenses.controler'); + +const router = express.Router(); + +router.get('/', expenseControler.getAll); + +router.get('/:id', expenseControler.getOne); + +router.post('/', expenseControler.add); + +router.delete('/:id', expenseControler.remove); + +router.patch('/:id', expenseControler.update); + +module.exports = router; diff --git a/src/routes/user.routes.js b/src/routes/user.routes.js new file mode 100644 index 000000000..d1cf0acb1 --- /dev/null +++ b/src/routes/user.routes.js @@ -0,0 +1,16 @@ +const express = require('express'); +const usersControler = require('../controlers/user.controler'); + +const router = express.Router(); + +router.get('/', usersControler.getAll); + +router.get('/:id', usersControler.getOne); + +router.post('/', usersControler.add); + +router.delete('/:id', usersControler.remove); + +router.patch('/:id', usersControler.update); + +module.exports = router; diff --git a/src/services/expenses.services.js b/src/services/expenses.services.js new file mode 100644 index 000000000..8d0eb3dae --- /dev/null +++ b/src/services/expenses.services.js @@ -0,0 +1,53 @@ +const { Op } = require('sequelize'); +const { models } = require('../models/models'); +const Expenses = models.Expense; + +const get = async ({ userId, categories, from, to }) => { + const where = {}; + + if (userId) { + where.userId = userId; + } + + if (categories) { + where.category = { [Op.in]: categories }; + } + + if (from || to) { + where.spentAt = {}; + + if (from) { + where.spentAt[Op.gte] = from; + } + + if (to) { + where.spentAt[Op.lte] = to; + } + } + + return Expenses.findAll({ where: where }); +}; + +const getOne = async (id) => Expenses.findByPk(id); + +const add = async (data) => { + const newExpense = await Expenses.create(data); + + return newExpense; +}; + +const remove = async (id) => { + await Expenses.destroy({ where: { id } }); +}; + +const update = async ({ id, ...data }) => { + await Expenses.update({ ...data }, { where: { id } }); +}; + +module.exports = { + get, + getOne, + add, + remove, + update, +}; diff --git a/src/services/user.services.js b/src/services/user.services.js new file mode 100644 index 000000000..f40c0ecb6 --- /dev/null +++ b/src/services/user.services.js @@ -0,0 +1,38 @@ +const { models } = require('../models/models'); +const User = models.User; + +const getAll = async () => { + const data = await User.findAll(); + + return data; +}; + +const getOne = async (id) => { + return User.findByPk(id); +}; + +const add = async (name) => { + const newTodo = await User.create({ name }); + + return newTodo; +}; + +const remove = async (id) => { + await User.destroy({ + where: { + id, + }, + }); +}; + +const update = async ({ id, name }) => { + await User.update({ name }, { where: { id } }); +}; + +module.exports = { + getAll, + getOne, + add, + remove, + update, +}; From 11ead55fd0a0d2222216061245824f8767b8a7f6 Mon Sep 17 00:00:00 2001 From: Volodymyr Kolosov Date: Fri, 24 Apr 2026 12:43:54 +0100 Subject: [PATCH 2/6] add category --- src/controlers/categories.controler.js | 84 ++++++++++++++++++++++++++ src/controlers/expenses.controler.js | 63 +++++++++++++------ src/createServer.js | 3 + src/models/Category.model.js | 25 ++++++++ src/models/Expense.model.js | 12 +++- src/models/models.js | 2 + src/routes/categories.routes.js | 12 ++++ src/services/categories.services.js | 29 +++++++++ src/services/expenses.services.js | 6 +- 9 files changed, 213 insertions(+), 23 deletions(-) create mode 100644 src/controlers/categories.controler.js create mode 100644 src/models/Category.model.js create mode 100644 src/routes/categories.routes.js create mode 100644 src/services/categories.services.js diff --git a/src/controlers/categories.controler.js b/src/controlers/categories.controler.js new file mode 100644 index 000000000..5b026c623 --- /dev/null +++ b/src/controlers/categories.controler.js @@ -0,0 +1,84 @@ +const categoryServices = require('../services/categories.services'); + +const getAll = async (req, res) => { + const categories = await categoryServices.get(); + + res.status(200).send(categories); +}; + +const getOne = async (req, res) => { + const { id } = req.params; + const normalaizedId = Number(id); + + if (isNaN(normalaizedId)) { + return res.status(400).send('Bad request'); + } + + const category = await categoryServices.getOne(normalaizedId); + + if (!category) { + return res.status(404).send('Not found'); + } + + res.status(200).send(category); +}; + +const add = async (req, res) => { + const { title } = req.body; + + if (!title || typeof title !== 'string') { + return res.status(400).send('Bad request'); + } + + const newCategory = await categoryServices.add(title.trim()); + + res.status(201).send(newCategory); +}; + +const remove = async (req, res) => { + const { id } = req.params; + const normalaizedId = Number(id); + + if (isNaN(normalaizedId)) { + return res.status(400).status('Bad request'); + } + + const category = await categoryServices.getOne(normalaizedId); + + if (!category) { + return res.status(404).send('Not found'); + } + + await categoryServices.remove(normalaizedId); + res.sendStatus(204); +}; + +const update = async (req, res) => { + const { id } = req.params; + const { title } = req.body; + const normalaizedId = Number(id); + + if (isNaN(normalaizedId) || !title || typeof title !== 'string') { + return res.status(400).send('Bad request'); + } + + const category = await categoryServices.getOne(normalaizedId); + + if (!category) { + return res.status(404).send('Not found'); + } + + await categoryServices.update({ id: normalaizedId, title: title.trim() }); + + const updatedCategory = await categoryServices.getOne(normalaizedId); + + res.status(200).send(updatedCategory); +}; + +module.exports = { + getAll, + getOne, + add, + remove, + update, +}; diff --git a/src/controlers/expenses.controler.js b/src/controlers/expenses.controler.js index b6dba7f52..aa5c70923 100644 --- a/src/controlers/expenses.controler.js +++ b/src/controlers/expenses.controler.js @@ -1,8 +1,9 @@ const expenseServices = require('../services/expenses.services'); const userServices = require('../services/user.services'); +const categoryServices = require('../services/categories.services'); const getAll = async (req, res) => { - let { userId, categories, from, to } = req.query; + let { userId, categoriesId, from, to } = req.query; if (userId) { userId = Number(userId); @@ -14,10 +15,13 @@ const getAll = async (req, res) => { userId = null; } - if (categories) { - categories = categories.split(',').map((c) => c.trim()); + if (categoriesId && categoriesId.length > 0) { + categoriesId = categoriesId + .split(',') + .map((category) => Number(category)) + .filter((category) => !isNaN(category)); } else { - categories = null; + categoriesId = null; } if (from) { @@ -46,7 +50,7 @@ const getAll = async (req, res) => { const data = await expenseServices.get({ userId, - categories, + categoriesId, from, to, }); @@ -62,7 +66,7 @@ const getOne = async (req, res) => { return res.status(400).send('Bad request'); } - const expense = await expenseServices.getOne(id); + const expense = await expenseServices.getOne(normalaizedId); if (!expense) { return res.status(404).send('Not fond'); @@ -72,17 +76,20 @@ const getOne = async (req, res) => { }; const add = async (req, res) => { - const { userId, spentAt, title, amount, category, note } = req.body; + const { userId, spentAt, title, amount, categoryId, note } = req.body; + const normalaizedId = Number(userId); const normalaizedAmount = Number(amount); const normalaizedTitle = title ? title.trim() : null; const normalaizedSpentAt = spentAt ? Date.parse(spentAt) : Date.now(); + const normalaizedCategoryId = categoryId ? Number(categoryId) : null; const checker = isNaN(normalaizedId) || isNaN(normalaizedAmount) || !normalaizedTitle || - isNaN(normalaizedSpentAt); + isNaN(normalaizedSpentAt) || + (categoryId && isNaN(normalaizedCategoryId)); if (checker) { return res.status(400).send('Bad request'); @@ -91,19 +98,27 @@ const add = async (req, res) => { const user = await userServices.getOne(normalaizedId); if (!user) { - return res.status(400).send('User not found'); + return res.status(400).send('Not found'); } - const newUser = await expenseServices.add({ + if (categoryId) { + const category = await categoryServices.getOne(normalaizedCategoryId); + + if (!category) { + return res.status(404).send('Not found'); + } + } + + const newExpense = await expenseServices.add({ userId: normalaizedId, title: normalaizedTitle, amount: normalaizedAmount, spentAt: normalaizedSpentAt, - category: category ? category.trim() : null, + categoryId: normalaizedCategoryId, note: note ? note.trim() : null, }); - res.status(201).send(newUser); + res.status(201).send(newExpense); }; const remove = async (req, res) => { @@ -114,19 +129,19 @@ const remove = async (req, res) => { return res.status(400).send('Bad request'); } - const expense = await expenseServices.getOne(id); + const expense = await expenseServices.getOne(narmalaizedId); if (!expense) { return res.status(404).send('Not found'); } - await expenseServices.remove(id); + await expenseServices.remove(narmalaizedId); res.sendStatus(204); }; const update = async (req, res) => { const { id } = req.params; - const { spentAt, title, amount, category, note } = req.body; + const { spentAt, title, amount, categoryId, note } = req.body; const normalaizedId = Number(id); @@ -172,8 +187,20 @@ const update = async (req, res) => { data.spentAt = new Date(normalaizedSpentAt); } - if (category !== undefined) { - data.category = category.trim() || null; + if (categoryId !== undefined) { + const normalaizedCategoryId = Number(categoryId); + + if (isNaN(normalaizedCategoryId)) { + return res.status(400).send('Bad request'); + } + + const category = await categoryServices.getOne(normalaizedCategoryId); + + if (!category) { + return res.status(404).send('Category not found'); + } + + data.categoryId = normalaizedCategoryId; } if (note !== undefined) { @@ -182,7 +209,7 @@ const update = async (req, res) => { await expenseServices.update({ id: normalaizedId, ...data }); - const updatedExpense = await expenseServices.getOne(id); + const updatedExpense = await expenseServices.getOne(normalaizedId); res.status(200).send(updatedExpense); }; diff --git a/src/createServer.js b/src/createServer.js index bd8695587..20a362e19 100644 --- a/src/createServer.js +++ b/src/createServer.js @@ -4,6 +4,7 @@ const express = require('express'); const cors = require('cors'); const userRoter = require('./routes/user.routes'); const expensesRouter = require('./routes/expenses.rotes'); +const categoriesRouter = require('./routes/categories.routes'); const createServer = () => { const app = express(); @@ -14,6 +15,8 @@ const createServer = () => { app.use('/expenses', express.json(), expensesRouter); + app.use('/category', express.json(), categoriesRouter); + return app; }; diff --git a/src/models/Category.model.js b/src/models/Category.model.js new file mode 100644 index 000000000..c9504ec92 --- /dev/null +++ b/src/models/Category.model.js @@ -0,0 +1,25 @@ +const { DataTypes } = require('sequelize'); +const { sequelize } = require('../db.js'); + +const Category = sequelize.define( + 'Category', + { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + }, + title: { + type: DataTypes.TEXT, + allowNull: false, + unique: true, + }, + }, + { + tableName: 'categories', + updatedAt: false, + createdAt: false, + }, +); + +module.exports = { Category }; diff --git a/src/models/Expense.model.js b/src/models/Expense.model.js index c98ea5675..66c843944 100644 --- a/src/models/Expense.model.js +++ b/src/models/Expense.model.js @@ -14,6 +14,10 @@ const Expense = sequelize.define( userId: { type: DataTypes.INTEGER, allowNull: false, + references: { + model: 'users', + key: 'id', + }, }, spentAt: { type: DataTypes.DATE, @@ -28,9 +32,13 @@ const Expense = sequelize.define( type: DataTypes.FLOAT, allowNull: false, }, - category: { - type: DataTypes.STRING, + categoryId: { + type: DataTypes.INTEGER, allowNull: true, + references: { + model: 'categories', + key: 'id', + }, }, note: { type: DataTypes.TEXT, diff --git a/src/models/models.js b/src/models/models.js index b43b55752..544e0a26a 100644 --- a/src/models/models.js +++ b/src/models/models.js @@ -2,10 +2,12 @@ const { User } = require('./User.model'); const { Expense } = require('./Expense.model'); +const { Category } = require('./Category.model'); module.exports = { models: { User, Expense, + Category, }, }; diff --git a/src/routes/categories.routes.js b/src/routes/categories.routes.js new file mode 100644 index 000000000..6b8881464 --- /dev/null +++ b/src/routes/categories.routes.js @@ -0,0 +1,12 @@ +const express = require('express'); +const categoriesControler = require('../controlers/categories.controler'); + +const router = express.Router(); + +router.get('/', categoriesControler.getAll); +router.get('/:id', categoriesControler.getOne); +router.post('/', categoriesControler.add); +router.delete('/:id', categoriesControler.remove); +router.patch('/:id', categoriesControler.update); + +module.exports = router; diff --git a/src/services/categories.services.js b/src/services/categories.services.js new file mode 100644 index 000000000..7220bd718 --- /dev/null +++ b/src/services/categories.services.js @@ -0,0 +1,29 @@ +const { + models: { Category }, +} = require('../models/models'); + +const get = async () => Category.findAll(); + +const getOne = async (id) => Category.findByPk(id); + +const add = async (title) => { + const newCategory = await Category.create({ title }); + + return newCategory; +}; + +const remove = async (id) => { + await Category.destroy({ where: { id } }); +}; + +const update = async ({ id, title }) => { + await Category.update({ title }, { where: { id } }); +}; + +module.exports = { + get, + getOne, + add, + remove, + update, +}; diff --git a/src/services/expenses.services.js b/src/services/expenses.services.js index 8d0eb3dae..2c2e12c8d 100644 --- a/src/services/expenses.services.js +++ b/src/services/expenses.services.js @@ -2,15 +2,15 @@ const { Op } = require('sequelize'); const { models } = require('../models/models'); const Expenses = models.Expense; -const get = async ({ userId, categories, from, to }) => { +const get = async ({ userId, categoriesId, from, to }) => { const where = {}; if (userId) { where.userId = userId; } - if (categories) { - where.category = { [Op.in]: categories }; + if (categoriesId) { + where.categoryId = { [Op.in]: categoriesId }; } if (from || to) { From 0f6aa4dc7222b875eff824af0778f2d953be92dc Mon Sep 17 00:00:00 2001 From: Volodymyr Kolosov Date: Fri, 24 Apr 2026 12:56:38 +0100 Subject: [PATCH 3/6] bag fix_1 --- src/controlers/categories.controler.js | 2 +- src/controlers/user.controler.js | 2 +- src/createServer.js | 8 ++++---- src/routes/{expenses.rotes.js => expenses.routes.js} | 0 4 files changed, 6 insertions(+), 6 deletions(-) rename src/routes/{expenses.rotes.js => expenses.routes.js} (100%) diff --git a/src/controlers/categories.controler.js b/src/controlers/categories.controler.js index 5b026c623..861058d07 100644 --- a/src/controlers/categories.controler.js +++ b/src/controlers/categories.controler.js @@ -40,7 +40,7 @@ const remove = async (req, res) => { const normalaizedId = Number(id); if (isNaN(normalaizedId)) { - return res.status(400).status('Bad request'); + return res.status(400).send('Bad request'); } const category = await categoryServices.getOne(normalaizedId); diff --git a/src/controlers/user.controler.js b/src/controlers/user.controler.js index f24b106c6..8cdae8c8d 100644 --- a/src/controlers/user.controler.js +++ b/src/controlers/user.controler.js @@ -45,7 +45,7 @@ const remove = async (req, res) => { return res.status(400).send('Bad request'); } - const userForRemove = await userServices.getOne(id); + const userForRemove = await userServices.getOne(normalaizedId); if (!userForRemove) { return res.status(404).send('Not found'); diff --git a/src/createServer.js b/src/createServer.js index 20a362e19..949294d45 100644 --- a/src/createServer.js +++ b/src/createServer.js @@ -2,8 +2,8 @@ const express = require('express'); const cors = require('cors'); -const userRoter = require('./routes/user.routes'); -const expensesRouter = require('./routes/expenses.rotes'); +const userRouter = require('./routes/user.routes'); +const expensesRouter = require('./routes/expenses.routes'); const categoriesRouter = require('./routes/categories.routes'); const createServer = () => { @@ -11,11 +11,11 @@ const createServer = () => { app.use(cors()); - app.use('/users', express.json(), userRoter); + app.use('/users', express.json(), userRouter); app.use('/expenses', express.json(), expensesRouter); - app.use('/category', express.json(), categoriesRouter); + app.use('/categories', express.json(), categoriesRouter); return app; }; diff --git a/src/routes/expenses.rotes.js b/src/routes/expenses.routes.js similarity index 100% rename from src/routes/expenses.rotes.js rename to src/routes/expenses.routes.js From aa510cf7f7c9c2303f3f6079e341598abb16e708 Mon Sep 17 00:00:00 2001 From: Volodymyr Kolosov Date: Fri, 24 Apr 2026 14:13:00 +0100 Subject: [PATCH 4/6] bag_fix_2 --- src/controlers/expenses.controler.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/controlers/expenses.controler.js b/src/controlers/expenses.controler.js index aa5c70923..e8a137428 100644 --- a/src/controlers/expenses.controler.js +++ b/src/controlers/expenses.controler.js @@ -69,7 +69,7 @@ const getOne = async (req, res) => { const expense = await expenseServices.getOne(normalaizedId); if (!expense) { - return res.status(404).send('Not fond'); + return res.status(404).send('Not found'); } res.status(200).send(expense); @@ -123,19 +123,19 @@ const add = async (req, res) => { const remove = async (req, res) => { const { id } = req.params; - const narmalaizedId = Number(id); + const normalizedId = Number(id); - if (isNaN(narmalaizedId)) { + if (isNaN(normalizedId)) { return res.status(400).send('Bad request'); } - const expense = await expenseServices.getOne(narmalaizedId); + const expense = await expenseServices.getOne(normalizedId); if (!expense) { return res.status(404).send('Not found'); } - await expenseServices.remove(narmalaizedId); + await expenseServices.remove(normalizedId); res.sendStatus(204); }; From f6f5b7d0b622d32da73a9d006884b8d25cd7bb8e Mon Sep 17 00:00:00 2001 From: Volodymyr Kolosov Date: Mon, 27 Apr 2026 12:09:06 +0100 Subject: [PATCH 5/6] bag fix 3 --- src/controlers/expenses.controler.js | 8 ++++---- src/createServer.js | 8 +++++--- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/controlers/expenses.controler.js b/src/controlers/expenses.controler.js index e8a137428..518ef692f 100644 --- a/src/controlers/expenses.controler.js +++ b/src/controlers/expenses.controler.js @@ -123,19 +123,19 @@ const add = async (req, res) => { const remove = async (req, res) => { const { id } = req.params; - const normalizedId = Number(id); + const narmalaizedId = Number(id); - if (isNaN(normalizedId)) { + if (isNaN(narmalaizedId)) { return res.status(400).send('Bad request'); } - const expense = await expenseServices.getOne(normalizedId); + const expense = await expenseServices.getOne(narmalaizedId); if (!expense) { return res.status(404).send('Not found'); } - await expenseServices.remove(normalizedId); + await expenseServices.remove(narmalaizedId); res.sendStatus(204); }; diff --git a/src/createServer.js b/src/createServer.js index 949294d45..2c132af61 100644 --- a/src/createServer.js +++ b/src/createServer.js @@ -11,11 +11,13 @@ const createServer = () => { app.use(cors()); - app.use('/users', express.json(), userRouter); + app.use(express.json({ limit: '1mb' })); - app.use('/expenses', express.json(), expensesRouter); + app.use('/users', userRouter); - app.use('/categories', express.json(), categoriesRouter); + app.use('/expenses', expensesRouter); + + app.use('/categories', categoriesRouter); return app; }; From 50fd8fa5a0e998bb851aead068431daf2d032e12 Mon Sep 17 00:00:00 2001 From: Volodymyr Kolosov Date: Wed, 6 May 2026 13:59:12 +0100 Subject: [PATCH 6/6] fix category --- src/controlers/expenses.controler.js | 49 ++++++++-------------------- src/controlers/user.controler.js | 4 +-- src/models/Expense.model.js | 17 ++-------- src/models/User.model.js | 7 +--- src/services/expenses.services.js | 6 ++-- 5 files changed, 21 insertions(+), 62 deletions(-) diff --git a/src/controlers/expenses.controler.js b/src/controlers/expenses.controler.js index 518ef692f..e72064072 100644 --- a/src/controlers/expenses.controler.js +++ b/src/controlers/expenses.controler.js @@ -1,9 +1,8 @@ const expenseServices = require('../services/expenses.services'); const userServices = require('../services/user.services'); -const categoryServices = require('../services/categories.services'); const getAll = async (req, res) => { - let { userId, categoriesId, from, to } = req.query; + let { userId, categories, from, to } = req.query; if (userId) { userId = Number(userId); @@ -15,13 +14,13 @@ const getAll = async (req, res) => { userId = null; } - if (categoriesId && categoriesId.length > 0) { - categoriesId = categoriesId + if (categories && categories.length > 0) { + categories = categories .split(',') - .map((category) => Number(category)) - .filter((category) => !isNaN(category)); + .map((c) => c.trim()) + .filter(Boolean); } else { - categoriesId = null; + categories = null; } if (from) { @@ -50,7 +49,7 @@ const getAll = async (req, res) => { const data = await expenseServices.get({ userId, - categoriesId, + categories, from, to, }); @@ -76,20 +75,18 @@ const getOne = async (req, res) => { }; const add = async (req, res) => { - const { userId, spentAt, title, amount, categoryId, note } = req.body; + const { userId, spentAt, title, amount, category, note } = req.body; const normalaizedId = Number(userId); const normalaizedAmount = Number(amount); const normalaizedTitle = title ? title.trim() : null; const normalaizedSpentAt = spentAt ? Date.parse(spentAt) : Date.now(); - const normalaizedCategoryId = categoryId ? Number(categoryId) : null; const checker = isNaN(normalaizedId) || isNaN(normalaizedAmount) || !normalaizedTitle || - isNaN(normalaizedSpentAt) || - (categoryId && isNaN(normalaizedCategoryId)); + isNaN(normalaizedSpentAt); if (checker) { return res.status(400).send('Bad request'); @@ -101,20 +98,12 @@ const add = async (req, res) => { return res.status(400).send('Not found'); } - if (categoryId) { - const category = await categoryServices.getOne(normalaizedCategoryId); - - if (!category) { - return res.status(404).send('Not found'); - } - } - const newExpense = await expenseServices.add({ userId: normalaizedId, title: normalaizedTitle, amount: normalaizedAmount, spentAt: normalaizedSpentAt, - categoryId: normalaizedCategoryId, + category, note: note ? note.trim() : null, }); @@ -141,7 +130,7 @@ const remove = async (req, res) => { const update = async (req, res) => { const { id } = req.params; - const { spentAt, title, amount, categoryId, note } = req.body; + const { spentAt, title, amount, category, note } = req.body; const normalaizedId = Number(id); @@ -187,20 +176,8 @@ const update = async (req, res) => { data.spentAt = new Date(normalaizedSpentAt); } - if (categoryId !== undefined) { - const normalaizedCategoryId = Number(categoryId); - - if (isNaN(normalaizedCategoryId)) { - return res.status(400).send('Bad request'); - } - - const category = await categoryServices.getOne(normalaizedCategoryId); - - if (!category) { - return res.status(404).send('Category not found'); - } - - data.categoryId = normalaizedCategoryId; + if (category !== undefined) { + data.category = category || null; } if (note !== undefined) { diff --git a/src/controlers/user.controler.js b/src/controlers/user.controler.js index 8cdae8c8d..ae4f01bec 100644 --- a/src/controlers/user.controler.js +++ b/src/controlers/user.controler.js @@ -26,7 +26,7 @@ const getOne = async (req, res) => { const add = async (req, res) => { const { name } = req.body; - if (!name) { + if (!name || !name.trim()) { return res.status(400).send('Bad request'); } @@ -60,7 +60,7 @@ const update = async (req, res) => { const { name } = req.body; const normalaizedId = Number(id); - if (isNaN(normalaizedId) || !name) { + if (isNaN(normalaizedId) || !name || !name.trim()) { return res.status(400).send('Bad request'); } diff --git a/src/models/Expense.model.js b/src/models/Expense.model.js index 66c843944..47c4df67e 100644 --- a/src/models/Expense.model.js +++ b/src/models/Expense.model.js @@ -6,18 +6,9 @@ const { sequelize } = require('../db.js'); const Expense = sequelize.define( 'Expense', { - id: { - type: DataTypes.INTEGER, - primaryKey: true, - autoIncrement: true, - }, userId: { type: DataTypes.INTEGER, allowNull: false, - references: { - model: 'users', - key: 'id', - }, }, spentAt: { type: DataTypes.DATE, @@ -32,13 +23,9 @@ const Expense = sequelize.define( type: DataTypes.FLOAT, allowNull: false, }, - categoryId: { - type: DataTypes.INTEGER, + category: { + type: DataTypes.TEXT, allowNull: true, - references: { - model: 'categories', - key: 'id', - }, }, note: { type: DataTypes.TEXT, diff --git a/src/models/User.model.js b/src/models/User.model.js index 2ac1ad1ea..b1edee536 100644 --- a/src/models/User.model.js +++ b/src/models/User.model.js @@ -6,13 +6,8 @@ const { sequelize } = require('../db.js'); const User = sequelize.define( 'User', { - id: { - type: DataTypes.INTEGER, - primaryKey: true, - autoIncrement: true, - }, name: { - type: DataTypes.TEXT, + type: DataTypes.STRING, allowNull: false, }, }, diff --git a/src/services/expenses.services.js b/src/services/expenses.services.js index 2c2e12c8d..8d0eb3dae 100644 --- a/src/services/expenses.services.js +++ b/src/services/expenses.services.js @@ -2,15 +2,15 @@ const { Op } = require('sequelize'); const { models } = require('../models/models'); const Expenses = models.Expense; -const get = async ({ userId, categoriesId, from, to }) => { +const get = async ({ userId, categories, from, to }) => { const where = {}; if (userId) { where.userId = userId; } - if (categoriesId) { - where.categoryId = { [Op.in]: categoriesId }; + if (categories) { + where.category = { [Op.in]: categories }; } if (from || to) {