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
459 changes: 322 additions & 137 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"license": "GPL-3.0",
"dependencies": {
"cors": "^2.8.5",
"express": "^4.19.2",
"express": "^4.22.1",
"pg": "^8.12.0",
"sequelize": "^6.37.3"
},
Expand Down
5 changes: 4 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
# Accounting app (with Node.js and PostgreSQL)

Take the Accounting app from prev lesson and use PostgreSQL as a storage

- Implement CRUD page to manage categories

# Note

- You can sync models by running `npm run test`
- in local env, tests interect with your local database, so rows can be changed or removed


**Read [the guideline](https://github.com/mate-academy/js_task-guideline/blob/master/README.md) before start**

.
73 changes: 73 additions & 0 deletions src/api/categories.router.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
const { Router } = require('express');
const categoriesService = require('../services/categories.service.js');

const categoriesRouter = Router();

categoriesRouter.get('/', async (req, res) => {
const categories = await categoriesService.getAll();

res.status(200).json(categories);
});

categoriesRouter.get('/:id', async (req, res) => {
const id = +req.params.id;

if (!id || Number.isNaN(id)) {
res.sendStatus(400);
}

const category = await categoriesService.get(id);

if (!category) {
res.sendStatus(404);
}

res.status(200).json(category);
});

categoriesRouter.post('/', async (req, res) => {
const { category } = req.body;

if (!category) {
res.sendStatus(400);
}

const created = await categoriesService.create(req.body.category);

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

categoriesRouter.patch('/:id', async (req, res) => {
const id = +req.params.id;
const { category } = req.body;

if (!id || Number.isNaN(id) || !category) {
res.sendStatus(400);
}

const updated = await categoriesService.update(id, category);

if (!updated) {
res.sendStatus(404);
}

res.status(200).json(updated);
});

categoriesRouter.delete('/:id', async (req, res) => {
const id = +req.params.id;

if (!id || Number.isNaN(id)) {
res.sendStatus(404);
}

const deleted = await categoriesService.remove(id);

if (!deleted) {
res.sendStatus(404);
}

res.sendStatus(204);
});

module.exports = categoriesRouter;
108 changes: 108 additions & 0 deletions src/api/expenses.router.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
const { Router } = require('express');
const expensesService = require('../services/expenses.service.js');
const usersService = require('../services/users.service.js');

const expensesRouter = Router();

expensesRouter.get('/', async (req, res) => {
let expenses = await expensesService.getAll();
const { userId, categories, from, to } = req.query;

if (userId && Number.isNaN(+userId)) {
return res.status(400).json({ message: 'Invalid userId' });
}

if (userId) {
expenses = expenses.filter((exp) => exp.userId === +userId);
}

if (categories) {
const cat =
categories
.toString()
.split(',')
.map((item) => item.trim()) || '';

if (cat) {
for (const value of cat) {
expenses = expenses.filter((exp) => exp.category.includes(value));
}
}
}

if (from) {
expenses = expenses.filter(
(exp) => new Date(exp.spentAt) >= new Date(from),
);
}

if (to) {
expenses = expenses.filter((exp) => new Date(exp.spentAt) <= new Date(to));
}
res.status(200).json([...expenses]);
});

expensesRouter.get('/:id', async (req, res) => {
const id = Number(req.params.id);

if (Number.isNaN(id)) {
return res.status(400).json({ message: 'Invalid id' });
}

const [exp] = await expensesService.get('id', id);

if (!exp) {
return res.sendStatus(404);
}

res.status(200).json(exp);
});

expensesRouter.post('/', async (req, res) => {
const body = req.body;
const { title } = { ...body };
const user = await usersService.get(req.body.userId);

if (!body || !title || !user) {
return res.sendStatus(400);
}

const exp = await expensesService.add(body);

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

expensesRouter.patch('/:id', async (req, res) => {
const id = Number(req.params.id);
const body = req.body;

if (Number.isNaN(id)) {
return res.sendStatus(400);
}

const exp = await expensesService.update(id, body);

if (!exp) {
return res.sendStatus(404);
}

res.status(200).json(exp);
});

expensesRouter.delete('/:id', async (req, res) => {
const id = Number(req.params.id);

if (Number.isNaN(id)) {
return res.status(400).json({ message: 'Invalid id' });
}

const exp = await expensesService.remove(id);

if (!exp) {
return res.sendStatus(404);
}

res.sendStatus(204);
});

module.exports = expensesRouter;
73 changes: 73 additions & 0 deletions src/api/users.router.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
const { Router } = require('express');
const usersService = require('../services/users.service');

const usersRouter = Router();

usersRouter.get('/', async (req, res) => {
const users = await usersService.getAll();

res.json(users);
});

usersRouter.get('/:id', async (req, res) => {
const id = Number(req.params.id);

if (Number.isNaN(id)) {
return res.sendStatus(400);
}

const user = await usersService.get(id);

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

res.status(200).json(user);
});

usersRouter.post('/', async (req, res) => {
const name = req.body.name;

if (!name) {
return res.sendStatus(400);
}

const user = await usersService.add(name);

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

usersRouter.delete('/:id', async (req, res) => {
const id = Number(req.params.id);

if (Number.isNaN(id)) {
return res.sendStatus(400);
}

const user = await usersService.remove(id);

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

res.sendStatus(204);
});

usersRouter.patch('/:id', async (req, res) => {
const id = Number(req.params.id);

if (Number.isNaN(id)) {
return res.sendStatus(400);
}

const body = req.body;
const user = await usersService.update(id, body);

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

res.json(user);
});

module.exports = usersRouter;
17 changes: 16 additions & 1 deletion src/createServer.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,22 @@
'use strict';

const express = require('express');
const cors = require('cors');
const usersRouter = require('./api/users.router');
const expensesRouter = require('./api/expenses.router');
const categoriesRouter = require('./api/categories.router');

const createServer = () => {
// your code goes here
const app = express();

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

app.use('/users', usersRouter);
app.use('/expenses', expensesRouter);
app.use('/categories', categoriesRouter);

return app;
};

module.exports = {
Expand Down
27 changes: 27 additions & 0 deletions src/models/Categories.model.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
'use strict';

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

const Categories = sequelize.define(
'Categories',
{
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
},
category: {
type: DataTypes.STRING,
allowNull: false,
},
},
{
tableName: 'categories',
timestamps: false,
},
);

module.exports = {
Categories,
};
37 changes: 36 additions & 1 deletion src/models/Expense.model.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,44 @@
'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.STRING,
allowNull: false,
},
amount: {
type: DataTypes.INTEGER,
allowNull: false,
},
category: {
type: DataTypes.STRING,
},
note: {
type: DataTypes.STRING,
},
},
{
tableName: 'expenses',
timestamps: false,
},
);

module.exports = {
Expand Down
Loading
Loading