Skip to content
Open
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
84 changes: 84 additions & 0 deletions src/controllers/controllerExpense.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
const expenseService = require('../services/serviceExpenses');
const userService = require('../services/serviceUsers');

const getAll = (req, res) => {
const { userId, categories, from, to } = req.query;

const expenses = expenseService.getAllExpenses(userId, categories, from, to);

res.json(expenses);
};

const getById = (req, res) => {
const { id } = req.params;
const expense = expenseService.getExpenseById(id);

if (!expense) {
return res.status(404).send('Expense not found');
}

res.json(expense);
};

const create = (req, res) => {
const { userId, spentAt, title, amount, category, note } = req.body;

if (!userId || !spentAt || !title || amount === undefined || !category) {
return res
.status(400)
.send(
'All fields are required: userId, spentAt, title, amount, category',
);
}

const user = userService.getUserById(Number(userId));

if (!user) {
return res.status(400).send('User not found');
Comment on lines +36 to +37
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

When creating an expense the handler returns HTTP 400 if the referenced user is missing. Per the requirements/tests, expected-but-missing entities must return HTTP 404. Change this to for example: return res.status(404).send('User not found');

}

const expense = expenseService.createExpense(
Number(userId),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Good: you pass Number(userId) into createExpense, ensuring the stored userId is numeric and addressing the previous type-consistency concern.

spentAt,
title,
amount,
category,
note,
);
Comment on lines +40 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.

You convert userId to Number when checking the user (getUserById(Number(userId))) but then pass the original userId to createExpense. For consistency and to avoid type mismatches, consider passing Number(userId) into createExpense as well (or ensure the service handles string IDs).


res.status(201).json(expense);
};

const update = (req, res) => {
const { id } = req.params;

const existingExpense = expenseService.getExpenseById(id);

if (!existingExpense) {
return res.status(404).send('Expense not found');
}

const updates = req.body;
const expense = expenseService.updateExpense(id, updates);

res.json(expense);
};

const remove = (req, res) => {
const { id } = req.params;
const deleted = expenseService.deleteExpense(id);

if (!deleted) {
return res.status(404).send('Expense not found');
}

res.sendStatus(204);
};

module.exports = {
getAll,
getById,
create,
update,
remove,
};
66 changes: 66 additions & 0 deletions src/controllers/controllerUser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
const userService = require('../services/serviceUsers');

const getAll = (_req, res) => {
const users = userService.getAllUsers();

res.json(users);
};

const getById = (req, res) => {
const { id } = req.params;
const user = userService.getUserById(id);

if (!user) {
return res.status(404).send('User not found');
}

res.json(user);
};

const create = (req, res) => {
const { name } = req.body;

if (!name) {
return res.status(400).send('Name is required');
}

const newUser = userService.createUser(name);

res.status(201).json(newUser);
};

const update = (req, res) => {
const { id } = req.params;
const { name } = req.body;

if (!name) {
return res.status(400).send('Name is required');
}

const user = userService.updateUser(id, name);

if (!user) {
return res.status(404).send('User not found');
}

res.json(user);
};

const remove = (req, res) => {
const { id } = req.params;
const isDeleted = userService.deleteUser(id);

if (!isDeleted) {
return res.status(404).send('User not found');
}

res.sendStatus(204);
};

module.exports = {
getAll,
getById,
create,
update,
remove,
};
22 changes: 18 additions & 4 deletions src/createServer.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,25 @@
'use strict';

// const express = require('express');
const express = require('express');
const cors = require('cors');
const userRouter = require('./routes/routeUsers');
const expensesRouter = require('./routes/routeExpenses');
const userService = require('./services/serviceUsers');
const expenseService = require('./services/serviceExpenses');

function createServer() {
// Use express to create a server
// Add a routes to the server
// Return the server (express app)
userService.clearUsers();
expenseService.clearExpenses();

const app = express();

app.use(cors());
app.use(express.json());

app.use('/users', userRouter);
app.use('/expenses', expensesRouter);

return app;
}

module.exports = {
Expand Down
13 changes: 13 additions & 0 deletions src/models/Expense.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
class Expense {
constructor(id, userId, spentAt, title, amount, category, note) {
this.id = id;
this.userId = userId;
this.spentAt = spentAt;
this.title = title;
this.amount = amount;
this.category = category;
this.note = note;
}
}

module.exports = { Expense };
8 changes: 8 additions & 0 deletions src/models/User.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
class User {
constructor(id, name) {
this.id = id;
this.name = name;
}
}

module.exports = { User };
11 changes: 11 additions & 0 deletions src/routes/routeExpenses.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const express = require('express');
const router = express.Router();
const expenseController = require('../controllers/controllerExpense');

router.get('/', expenseController.getAll);
router.get('/:id', expenseController.getById);
router.post('/', expenseController.create);
router.patch('/:id', expenseController.update);
router.delete('/:id', expenseController.remove);

module.exports = router;
11 changes: 11 additions & 0 deletions src/routes/routeUsers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const express = require('express');
const router = express.Router();
const userController = require('../controllers/controllerUser');

router.get('/', userController.getAll);
router.get('/:id', userController.getById);
router.post('/', userController.create);
router.patch('/:id', userController.update);
router.delete('/:id', userController.remove);

module.exports = router;
111 changes: 111 additions & 0 deletions src/services/serviceExpenses.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/* eslint-disable comma-dangle, prettier/prettier */

const { Expense } = require('../models/Expense');

let expenses = [];
let nextExpenseId = 1;

const getAllExpenses = (userId, categories, from, to) => {
let result = expenses;
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 a shallow copy of the internal array instead of the direct reference to avoid accidental external mutation. For example, initialize result as let result = expenses.slice(); so callers won't be able to mutate the service's internal expenses array by accident.


if (userId) {
result = result.filter((expense) => expense.userId === Number(userId));
}

if (categories) {
const categoryList = Array.isArray(categories) ? categories : [categories];

result = result.filter((expense) =>
categoryList.includes(expense.category),);
}

if (from) {
const fromDate = new Date(from);

result = result.filter((expense) => new Date(expense.spentAt) >= fromDate);
}

if (to) {
const toDate = new Date(to);

result = result.filter((expense) => new Date(expense.spentAt) <= toDate);
}

return result;
};

const getExpenseById = (id) => {
return expenses.find((expense) => expense.id === Number(id));
};

const createExpense = (userId, spentAt, title, amount, category, note) => {
const expense = new Expense(
nextExpenseId++,
Number(userId),
spentAt,
title,
Number(amount),
category,
note || '',
);

expenses.push(expense);

return expense;
};

const updateExpense = (id, updates) => {
const expense = getExpenseById(id);

if (!expense) {
return null;
}

if (updates.spentAt !== undefined) {
expense.spentAt = updates.spentAt;
}

if (updates.title !== undefined) {
expense.title = updates.title;
}

if (updates.amount !== undefined) {
expense.amount = Number(updates.amount);
}

if (updates.category !== undefined) {
expense.category = updates.category;
}

if (updates.note !== undefined) {
expense.note = updates.note;
}

return expense;
};

const deleteExpense = (id) => {
const index = expenses.findIndex((expense) => expense.id === Number(id));

if (index !== -1) {
expenses.splice(index, 1);

return true;
}

return false;
};

const clearExpenses = () => {
expenses = [];
nextExpenseId = 1;
};

module.exports = {
getAllExpenses,
getExpenseById,
createExpense,
updateExpense,
deleteExpense,
clearExpenses,
};
56 changes: 56 additions & 0 deletions src/services/serviceUsers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
const { User } = require('../models/User');

let users = [];
let nextUserId = 1;

const getAllUsers = () => {
return users;
Comment on lines +6 to +7
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

getAllUsers returns the internal users array directly. To avoid accidental external mutation of the service state, return a shallow copy instead (for example: return users.slice(); or return [...users];).

};

const getUserById = (id) => {
return users.find((user) => user.id === Number(id));
};

const createUser = (name) => {
const user = new User(nextUserId++, name);

users.push(user);

return user;
};

const updateUser = (id, name) => {
const user = getUserById(id);

if (user) {
user.name = name;
}

return user;
};

const deleteUser = (id) => {
const index = users.findIndex((user) => user.id === Number(id));

if (index !== -1) {
users.splice(index, 1);

return true;
}

return false;
};

const clearUsers = () => {
users = [];
nextUserId = 1;
};

module.exports = {
getAllUsers,
getUserById,
createUser,
updateUser,
deleteUser,
clearUsers,
};