-
Notifications
You must be signed in to change notification settings - Fork 663
task #518
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
task #518
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,19 @@ | ||
| 'use strict'; | ||
|
|
||
| const express = require('express'); | ||
| const usersRouter = require('./routes/users'); | ||
| const expensesRouter = require('./routes/expenses'); | ||
| const categoriesRouter = require('./routes/categories'); | ||
|
|
||
| const createServer = () => { | ||
| // your code goes here | ||
| const app = express(); | ||
|
|
||
| app.use(express.json()); | ||
| app.use('/users', usersRouter); | ||
| app.use('/expenses', expensesRouter); | ||
|
Comment on lines
+12
to
+13
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The task requires a CRUD page to manage categories. There is no categories router mounted here. Add and mount a categories router (for example |
||
| app.use('/categories', categoriesRouter); | ||
|
|
||
| return app; | ||
| }; | ||
|
|
||
| module.exports = { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,12 +21,12 @@ const { | |
| */ | ||
|
|
||
| const sequelize = new Sequelize({ | ||
| database: POSTGRES_DB || 'postgres', | ||
| username: POSTGRES_USER || 'postgres', | ||
| host: POSTGRES_HOST || 'localhost', | ||
| database: POSTGRES_DB || 'mydb', | ||
| username: POSTGRES_USER || 'mykola', | ||
| host: POSTGRES_HOST || '20.160.160.166', | ||
| dialect: 'postgres', | ||
| port: POSTGRES_PORT || 5432, | ||
| password: POSTGRES_PASSWORD || '123', | ||
| password: POSTGRES_PASSWORD || 'strongpassword', | ||
|
Comment on lines
+26
to
+29
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The model currently defines |
||
| }); | ||
|
|
||
| module.exports = { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| 'use strict'; | ||
|
|
||
| const { sequelize } = require('../db.js'); | ||
| const { DataTypes } = require('sequelize'); | ||
|
|
||
| const Category = sequelize.define( | ||
| 'Category', | ||
| { | ||
| name: { | ||
| type: DataTypes.STRING, | ||
| allowNull: false, | ||
| }, | ||
| }, | ||
| { | ||
| tableName: 'categories', | ||
| timestamps: false, | ||
| }, | ||
| ); | ||
|
|
||
| module.exports = { | ||
| Category, | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,8 +2,40 @@ | |
|
|
||
| const { sequelize } = require('../db.js'); | ||
|
|
||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In createServer.js you import users and expenses routers but there is no import for a categories router. The task requires implementing a CRUD page to manage categories — add a categories router (e.g. |
||
| const { DataTypes } = require('sequelize'); | ||
|
|
||
| const Expense = sequelize.define( | ||
| // your code goes here | ||
| 'Expense', | ||
| { | ||
| userId: { | ||
| type: DataTypes.INTEGER, | ||
| allowNull: false, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You register |
||
| }, | ||
| spentAt: { | ||
| type: DataTypes.DATE, | ||
| allowNull: false, | ||
| }, | ||
| title: { | ||
| type: DataTypes.STRING, | ||
| allowNull: false, | ||
| }, | ||
| amount: { | ||
| type: DataTypes.FLOAT, | ||
| allowNull: false, | ||
| }, | ||
| category: { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The Expense model stores |
||
| type: DataTypes.STRING, | ||
| allowNull: true, | ||
| }, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The Expense model currently stores |
||
| note: { | ||
| type: DataTypes.STRING, | ||
| allowNull: true, | ||
| }, | ||
| }, | ||
| { | ||
| tableName: 'expenses', | ||
| timestamps: false, | ||
| }, | ||
| ); | ||
|
|
||
| module.exports = { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,8 +2,20 @@ | |
|
|
||
| const { sequelize } = require('../db.js'); | ||
|
|
||
| const { DataTypes } = require('sequelize'); | ||
|
|
||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| const User = sequelize.define( | ||
| // your code goes here | ||
| 'User', | ||
| { | ||
| name: { | ||
| type: DataTypes.STRING, | ||
| allowNull: false, | ||
| }, | ||
| }, | ||
| { | ||
| tableName: 'users', | ||
| timestamps: false, | ||
| }, | ||
| ); | ||
|
|
||
| module.exports = { | ||
|
Comment on lines
+11
to
21
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,10 +2,15 @@ | |
|
|
||
| const { User } = require('./User.model'); | ||
| const { Expense } = require('./Expense.model'); | ||
| const { Category } = require('./Category.model'); | ||
|
|
||
| User.hasMany(Expense, { foreignKey: 'userId', constraints: false }); | ||
| Expense.belongsTo(User, { foreignKey: 'userId', constraints: false }); | ||
|
Comment on lines
+5
to
+8
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You import |
||
|
|
||
| module.exports = { | ||
| models: { | ||
| User, | ||
| Expense, | ||
| Category, | ||
| }, | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| 'use strict'; | ||
|
|
||
| const express = require('express'); | ||
| const { | ||
| models: { Category }, | ||
| } = require('../models/models'); | ||
|
Comment on lines
+5
to
+6
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You import only |
||
|
|
||
| const router = express.Router(); | ||
|
|
||
| router.post('/', async (req, res) => { | ||
| const { name } = req.body; | ||
|
|
||
| if (!name) { | ||
| return res.status(400).json({ error: 'Missing required parameter: name' }); | ||
| } | ||
|
|
||
| try { | ||
| const category = await Category.create({ name }); | ||
|
|
||
| res.status(201).json(category); | ||
| } catch (error) { | ||
| res.status(500).json({ error: error.message }); | ||
|
Comment on lines
+11
to
+22
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This |
||
| } | ||
| }); | ||
|
|
||
| router.get('/', async (req, res) => { | ||
| const categories = await Category.findAll(); | ||
|
|
||
|
Comment on lines
+26
to
+28
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The categories filter is mapped to numbers and NaNs are removed, so non-numeric category names (strings) will be ignored. If you intend to support filtering by category name (allowed by the task as an option), map provided names to Category ids before building the |
||
| res.status(200).json(categories); | ||
| }); | ||
|
|
||
| router.get('/:id', async (req, res) => { | ||
| const { id } = req.params; | ||
| const category = await Category.findByPk(id); | ||
|
|
||
| if (!category) { | ||
| return res.status(404).json({ error: 'Category not found' }); | ||
| } | ||
| res.status(200).json(category); | ||
| }); | ||
|
|
||
| router.delete('/:id', async (req, res) => { | ||
| const { id } = req.params; | ||
| const category = await Category.findByPk(id); | ||
|
|
||
| if (!category) { | ||
| return res.status(404).json({ error: 'Category not found' }); | ||
|
Comment on lines
+46
to
+47
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. GET /expenses currently filters with |
||
| } | ||
| await category.destroy(); | ||
| res.status(204).send(); | ||
| }); | ||
|
|
||
| const updateHandler = async (req, res) => { | ||
|
Comment on lines
+50
to
+53
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider returning expenses with their Category included ( |
||
| const { id } = req.params; | ||
| const { name } = req.body; | ||
| const category = await Category.findByPk(id); | ||
|
|
||
| if (!category) { | ||
| return res.status(404).json({ error: 'Category not found' }); | ||
| } | ||
|
|
||
| if (name !== undefined) { | ||
| category.name = name; | ||
| } | ||
|
|
||
| await category.save(); | ||
| res.status(200).json(category); | ||
| }; | ||
|
|
||
|
Comment on lines
+68
to
+69
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. POST handler destructures and accepts |
||
| router.patch('/:id', updateHandler); | ||
| router.put('/:id', updateHandler); | ||
|
|
||
| module.exports = router; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,158 @@ | ||
| 'use strict'; | ||
|
|
||
| const express = require('express'); | ||
| const { Op } = require('sequelize'); | ||
| const { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You imported |
||
| models: { Expense, User }, | ||
| } = require('../models/models'); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is no Category model or any association between Expense and a Category. The task requires CRUD management for categories — add a |
||
|
|
||
|
Comment on lines
+7
to
+8
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. User<->Expense associations are present; do the same for Category<->Expense here and use |
||
| const router = express.Router(); | ||
|
|
||
| function parseCategories(categories) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| if (!categories) { | ||
| return []; | ||
|
Comment on lines
+10
to
+13
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Keep |
||
| } | ||
|
|
||
| const values = Array.isArray(categories) ? categories : [categories]; | ||
|
|
||
| return values | ||
| .flatMap((value) => String(value).split(',')) | ||
| .map((value) => value.trim()) | ||
| .filter(Boolean); | ||
| } | ||
|
|
||
| router.get('/', async (req, res) => { | ||
| const { userId, from, to, categories } = req.query; | ||
| const categoryList = parseCategories(categories); | ||
|
|
||
| const where = {}; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The model defines |
||
|
|
||
| if (userId !== undefined) { | ||
| where.userId = userId; | ||
| } | ||
|
|
||
| if (from || to) { | ||
| where.spentAt = {}; | ||
|
|
||
| if (from) { | ||
| where.spentAt[Op.gte] = new Date(from); | ||
| } | ||
|
|
||
| if (to) { | ||
| where.spentAt[Op.lte] = new Date(to); | ||
| } | ||
| } | ||
|
|
||
| if (categoryList.length > 0) { | ||
| where.category = { [Op.in]: categoryList }; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Filtering uses There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Filtering currently uses |
||
| } | ||
|
|
||
| const expenses = await Expense.findAll({ | ||
| where, | ||
| order: [['id', 'ASC']], | ||
| }); | ||
|
|
||
| res.status(200).json(expenses); | ||
| }); | ||
|
|
||
| router.get('/:id', async (req, res) => { | ||
| const { id } = req.params; | ||
| const expense = await Expense.findByPk(id); | ||
|
|
||
| if (!expense) { | ||
| return res.status(404).json({ error: 'Expense not found' }); | ||
| } | ||
| res.status(200).json(expense); | ||
| }); | ||
|
|
||
| router.post('/', async (req, res) => { | ||
| const { userId, spentAt, title, amount, category, note } = req.body; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Request handlers destructure |
||
|
|
||
| if ( | ||
| userId === undefined || | ||
| spentAt === undefined || | ||
| title === undefined || | ||
| amount === undefined | ||
| ) { | ||
| return res.status(400).json({ error: 'Missing required parameters' }); | ||
| } | ||
|
|
||
| const user = await User.findByPk(userId); | ||
|
|
||
| if (!user) { | ||
| return res.status(400).json({ error: 'User not found' }); | ||
| } | ||
|
|
||
| try { | ||
| const expense = await Expense.create({ | ||
| userId, | ||
| spentAt, | ||
| title, | ||
| amount, | ||
| category, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When creating an expense you accept |
||
| note, | ||
|
Comment on lines
+104
to
+111
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When creating an expense you pass the |
||
| }); | ||
|
|
||
| res.status(201).json(expense); | ||
| } catch (error) { | ||
| res.status(500).json({ error: error.message }); | ||
| } | ||
| }); | ||
|
|
||
| const updateHandler = async (req, res) => { | ||
| const { id } = req.params; | ||
| const { userId, spentAt, title, amount, category, note } = req.body; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Update handler still accepts |
||
| const expense = await Expense.findByPk(id); | ||
|
|
||
| if (!expense) { | ||
| return res.status(404).json({ error: 'Expense not found' }); | ||
| } | ||
|
|
||
| if (userId !== undefined) { | ||
| const user = await User.findByPk(userId); | ||
|
|
||
| if (!user) { | ||
| return res.status(400).json({ error: 'User not found' }); | ||
| } | ||
| expense.userId = userId; | ||
| } | ||
|
|
||
| if (spentAt !== undefined) { | ||
| expense.spentAt = spentAt; | ||
| } | ||
|
|
||
| if (title !== undefined) { | ||
| expense.title = title; | ||
| } | ||
|
|
||
| if (amount !== undefined) { | ||
| expense.amount = amount; | ||
| } | ||
|
|
||
| if (category !== undefined) { | ||
| expense.category = category; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The update handler also assigns There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This assignment updates the old |
||
| } | ||
|
|
||
| if (note !== undefined) { | ||
| expense.note = note; | ||
| } | ||
|
|
||
| await expense.save(); | ||
| res.status(200).json(expense); | ||
| }; | ||
|
|
||
| router.patch('/:id', updateHandler); | ||
| router.put('/:id', updateHandler); | ||
|
|
||
| router.delete('/:id', async (req, res) => { | ||
| const { id } = req.params; | ||
| const expense = await Expense.findByPk(id); | ||
|
|
||
| if (!expense) { | ||
| return res.status(404).json({ error: 'Expense not found' }); | ||
| } | ||
| await expense.destroy(); | ||
| res.status(204).send(); | ||
| }); | ||
|
|
||
| module.exports = router; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Category model exists, but you must ensure associations between Category and Expense are defined (e.g.,
Category.hasMany(Expense, { foreignKey: 'categoryId', constraints: false })andExpense.belongsTo(Category, { foreignKey: 'categoryId', constraints: false })) in your central models file. Also ensure DB-level FK constraints won't prevent test truncation (useconstraints: falseor avoid DB-level FK if tests truncate tables) — this ties to checklist items #4, #5 and #7.