Skip to content
Open

task #518

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion src/createServer.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,19 @@
'use strict';

const express = require('express');
Copy link
Copy Markdown

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 }) and Expense.belongsTo(Category, { foreignKey: 'categoryId', constraints: false })) in your central models file. Also ensure DB-level FK constraints won't prevent test truncation (use constraints: false or avoid DB-level FK if tests truncate tables) — this ties to checklist items #4, #5 and #7.

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
Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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)) so the app exposes endpoints to create/read/update/delete categories.

app.use('/categories', categoriesRouter);

return app;
};

module.exports = {
Expand Down
8 changes: 4 additions & 4 deletions src/db.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The model currently defines category as a STRING. The task requires using a dedicated Category model and storing a foreign key on Expense instead. Replace this category string field with a categoryId INTEGER (allowNull: true) and rely on associations to link Expense <> Category (see checklist item #5).

});

module.exports = {
Expand Down
22 changes: 22 additions & 0 deletions src/models/Category.model.js
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,
};
46 changes: 45 additions & 1 deletion src/models/Expense.model.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,54 @@

const { sequelize } = require('../db.js');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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 categoriesRouter = require('./routes/categories')).

const { DataTypes } = require('sequelize');

const Expense = sequelize.define(
// your code goes here
'Expense',
{
userId: {
type: DataTypes.INTEGER,
allowNull: false,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You register /users and /expenses routes but you don't mount a /categories route. Mount the categories router (e.g. app.use('/categories', categoriesRouter)) so the CRUD endpoints are available as required by the task.

},
spentAt: {
type: DataTypes.DATE,
allowNull: false,
},
title: {
type: DataTypes.STRING,
allowNull: false,
},
amount: {
type: DataTypes.FLOAT,
allowNull: false,
},
categoryId: {
type: DataTypes.INTEGER,
allowNull: true,
},
category: {
type: DataTypes.STRING,
allowNull: true,
},
note: {
type: DataTypes.STRING,
allowNull: true,
},
},
{
tableName: 'expenses',
timestamps: false,
},
);

Expense.prototype.toJSON = function () {
const values = { ...this.get() };

delete values.categoryId;

return values;
};

module.exports = {
Expense,
};
14 changes: 13 additions & 1 deletion src/models/User.model.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,20 @@

const { sequelize } = require('../db.js');

const { DataTypes } = require('sequelize');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are not importing the Category model here. Per the task you should import Category so route handlers can validate/resolve category references and/or include Category in queries (checklist items #5 and #6).

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
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

parseCategories treats categories as text values. If you switch to categoryId this parser should convert values to numbers (and validate them). If you keep name-based filtering, ensure you resolve names against the Category table before querying expenses.

Expand Down
8 changes: 8 additions & 0 deletions src/models/models.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,18 @@

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
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You import Category but never define associations between Category and Expense. Add Category.hasMany(Expense, { foreignKey: 'categoryId', constraints: false }) and Expense.belongsTo(Category, { foreignKey: 'categoryId', constraints: false }) (keep constraints: false if you must avoid DB-level FK constraints that can break truncation in tests).


Category.hasMany(Expense, { foreignKey: 'categoryId', constraints: false });
Expense.belongsTo(Category, { foreignKey: 'categoryId', constraints: false });

module.exports = {
models: {
User,
Expense,
Category,
},
};
73 changes: 73 additions & 0 deletions src/routes/categories.js
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
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You import only Expense and User from models. Per the new design you must also import Category here so you can validate categoryId and/or perform joins when querying/filtering expenses.


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
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This parseCategories helper parses category names. If you switch to a categoryId relation you should either remove/replace this with parsing numbers (IDs) or change it to map names -> category IDs before using in queries.

}
});

router.get('/', async (req, res) => {
const categories = await Category.findAll();

Comment on lines +26 to +28
Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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 where.categoryId filter; otherwise document that only numeric IDs are accepted.

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
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GET /expenses currently filters with where.category = { [Op.in]: categoryList }. After introducing a relation you must filter by categoryId (e.g., where.categoryId) or perform a join (include: Category) and filter on the Category field instead.

}
await category.destroy();
res.status(204).send();
});

const updateHandler = async (req, res) => {
Comment on lines +50 to +53
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider returning expenses with their Category included (Expense.findAll({ include: Category, ... })) so clients can access category names without extra requests once the relation is added.

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
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

POST handler destructures and accepts category as a string. Update the endpoint to accept categoryId (validate existence with Category.findByPk) or resolve the provided name to an existing Category before creating the expense.

router.patch('/:id', updateHandler);
router.put('/:id', updateHandler);

module.exports = router;
Loading
Loading