Skip to content

Commit

Permalink
Merge pull request #1 from luiz123o/feat/Users-tasks
Browse files Browse the repository at this point in the history
User and tariff features.
  • Loading branch information
luiz123o committed Feb 18, 2021
2 parents df6605b + 6c13e7b commit 0b90b97
Show file tree
Hide file tree
Showing 12 changed files with 466 additions and 0 deletions.
50 changes: 50 additions & 0 deletions src/app/controllers/SessionController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import * as Yup from 'yup';
import jwt from 'jsonwebtoken';

import authConfig from '../../config/auth';
import User from '../models/User';

class SessionController {
async store(req, res) {
/**
* Validação de informações do Usuario.
*/
const schema = Yup.object().shape({
email: Yup.string()
.email()
.required(),
password: Yup.string().required(),
});

if (!(await schema.isValid(req.body))) {
return res.status(400).json({ error: 'Validation Fails' });
}
/**
* Sistema de Validação do usuario.
*/
const { email, password } = req.body;

const user = await User.findOne({ where: { email } });

// Verifico se o usuario existe utilizando o user que carrega as informações do email.
if (!user) {
return res.status(401).json({ error: 'user not found' });
}
// Verifico se a senha corresponde
if (!(await user.checkPassword(password))) {
return res.status(401).json({ error: 'Password does not match' });
}
const { id, name } = user;
return res.json({
user: {
id,
name,
email,
},
token: jwt.sign({ id }, authConfig.secret, {
expiresIn: authConfig.expiresIn,
}),
});
}
}
export default new SessionController();
116 changes: 116 additions & 0 deletions src/app/controllers/TaskController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import * as Yup from 'yup';
import {
startOfHour,
parseISO,
isBefore,
startOfDay,
endOfDay,
} from 'date-fns';

import { Op } from 'sequelize';

import Task from '../models/Task';

class TaskController {
async index(req, res) {
const { page = 1 } = req.query;
const { date } = req.query;
const parsedDate = parseISO(date);

/**
* Sistema de Listagem ordenado por data
*/
const tasks = await Task.findAll({
where: {
user_id: req.userId,
canceled_at: null,
date: { [Op.between]: [startOfDay(parsedDate), endOfDay(parsedDate)] },
},
order: ['date'],
attributes: [
'id',
'user_id',
'date',
'title',
'description',
'completed',
'created_at',
],
limit: 20,
offset: (page - 1) * 20,
});
return res.json(tasks);
}

async store(req, res) {
/**
* Validação de Dados
*/
const schema = Yup.object().shape({
title: Yup.string().required(),
description: Yup.string().required(),
date: Yup.date().required(),
completed: Yup.boolean(),
});
if (!(await schema.isValid(req.body))) {
return res.status(400).json({ error: 'Validation fails' });
}

const { title, description, date, completed } = req.body;
/**
* Check se a data é anterior a atual
*/
const hourStart = startOfHour(parseISO(date));

if (isBefore(hourStart, new Date())) {
return res.status(400).json({ error: 'Past dates are not permitted' });
}
const checkAvailability = await Task.findOne({
where: {
canceled_at: null,
date: hourStart,
},
});
if (checkAvailability) {
return res.status(400).json({ error: 'Task date is not available' });
}
/**
* Sistema de criação de tasks
*/
const tasks = await Task.create({
user_id: req.userId,
user: req.user,
title,
description,
date,
completed,
});

return res.json(tasks);
}

async update(req, res) {
const tasks = await Task.findByPk(req.params.id);
const { title, description, completed, date } = await tasks.update(
req.body
);
return res.json({ title, description, completed, date });
}

async delete(req, res) {
const tasks = await Task.findByPk(req.params.id);

/**
* Bloqueando a exclusão se uruario for diferente.
*/
if (tasks.user_id !== req.userId) {
return res
.status(401)
.json({ error: "you don't have permission to cancel this tasks" });
}
tasks.canceled_at = new Date();
await tasks.save();
return res.json(tasks);
}
}
export default new TaskController();
87 changes: 87 additions & 0 deletions src/app/controllers/UserController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import * as Yup from 'yup';
import User from '../models/User';

class UserController {
async store(req, res) {
/**
* Validação das informações!
*/
const schema = Yup.object().shape({
name: Yup.string().required(),
email: Yup.string()
.email()
.required(),
password: Yup.string()
.required()
.min(6),
});
if (!(await schema.isValid(req.body))) {
return res.status(400).json({ error: 'Validation fails' });
}
/**
* Sistema de Cadastro do Usuario
*/
// 1° Atribuo ao userExists a informação sobre o email.
const userExists = await User.findOne({ where: { email: req.body.email } });
// 2° Realizo a comparação, e retorno o erro caso exista um usuario com mesmo email.
if (userExists) {
return res.status(400).json({ error: 'User already exists.' });
}
// 3° Crio um novo objeto e atribuo ao user.
const user = await User.create(req.body);
// 4° Retorna as informações ao frontEnd.
return res.json(user);
}

async update(req, res) {
/**
* Validação das informações!
*/

const schema = Yup.object().shape({
name: Yup.string(),
email: Yup.string().email(),
oldPassword: Yup.string().min(6),
password: Yup.string()
.min(6)
.when('oldPassword', (oldPassword, field) =>
oldPassword ? field.required() : field
),
confirmePassword: Yup.string().when('password', (password, field) =>
password ? field.required().oneOf([Yup.ref('password')]) : field
),
});
if (!(await schema.isValid(req.body))) {
return res.status(400).json({ error: 'Validation fails' });
}

/**
* Sistema de Atualização de Cadastro
*/

// 1° Atribuo os valor a email e oldPassword.
const { email, oldPassword } = req.body;
// 2° Atribuo ao user o id do usuario.
const user = await User.findByPk(req.userId);
// 3° Comparo o email com o email cadastrado do usuario.
if (email !== user.email) {
// 4° Atribuo ao userExists a informação sobre o email.
const userExists = await User.findOne({ where: { email } });
// 5° Verifico se o novo email já existe, se Sim retorno Erro 400.
if (userExists) {
return res.status(400).json({ error: 'User already exists' });
}
}
// 6° Verifico se o oldPassword é igual.
if (oldPassword && !(await user.checkPassword(oldPassword))) {
return res.status(400).json({ error: 'Password does match' });
}
const { id, name } = await user.update(req.body);
return res.json({
id,
name,
email,
});
}
}
export default new UserController();
23 changes: 23 additions & 0 deletions src/app/middlewares/auth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import jwt from 'jsonwebtoken';
import { promisify } from 'util';
import authConfig from '../../config/auth';

export default async (req, res, next) => {
const authHeader = req.headers.authorization;

if (!authHeader) {
return res.status(400).json({ error: 'Token not provided' });
}

const [, token] = authHeader.split(' ');

try {
// promisify vai transformar a callback recebida da função verify do jwt
const decoded = await promisify(jwt.verify)(token, authConfig.secret);
req.userId = decoded.id;

return next();
} catch (err) {
return res.status(401).json({ error: 'Token invalid' });
}
};
25 changes: 25 additions & 0 deletions src/app/models/Task.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import Sequelize, { Model } from 'sequelize';

class Task extends Model {
static init(sequelize) {
super.init(
{
date: Sequelize.DATE,
title: Sequelize.STRING,
description: Sequelize.STRING,
completed: Sequelize.BOOLEAN,
created_at: Sequelize.DATE,
canceled_at: Sequelize.DATE,
},
{
sequelize,
}
);
return this;
}

static associate(models) {
this.belongsTo(models.User, { foreignKey: 'user_id' });
}
}
export default Task;
29 changes: 29 additions & 0 deletions src/app/models/User.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import Sequelize, { Model } from 'sequelize';
import bcrypt from 'bcryptjs';

class User extends Model {
static init(sequelize) {
super.init(
{
name: Sequelize.STRING,
email: Sequelize.STRING,
password: Sequelize.VIRTUAL,
password_hash: Sequelize.STRING,
},
{
sequelize,
}
);
this.addHook('beforeSave', async users => {
if (users.password) {
users.password_hash = await bcrypt.hash(users.password, 8);
}
});
return this;
}

checkPassword(password) {
return bcrypt.compare(password, this.password_hash);
}
}
export default User;
4 changes: 4 additions & 0 deletions src/config/auth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export default {
secret: process.env.APP_SECRET,
expiresIn: '7d',
};
16 changes: 16 additions & 0 deletions src/config/database.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
require('../bootstrap');

module.exports = {
dialect: process.env.DB_DIALECT || 'postgres',
host: process.env.DB_HOST,
username: process.env.DB_USER,
password: process.env.DB_PASS,
database: process.env.DB_NAME,
port: process.env.DB_PORT,
storage: './__tests__/database.sqlite',
define: {
timestamps: true,
underscored: true,
underscoredAll: true,
},
};
3 changes: 3 additions & 0 deletions src/config/sentry.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default {
dsn: process.env.SENTRY_DSN,
};
23 changes: 23 additions & 0 deletions src/database/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import Sequelize from 'sequelize';

import User from '../app/models/User';
import Task from '../app/models/Task';

import databaseConfig from '../config/database';

const models = [User, Task];

class Database {
constructor() {
this.init();
}

init() {
this.connection = new Sequelize(databaseConfig);

models
.map(model => model.init(this.connection))
.map(model => model.associate && model.associate(this.connection.models));
}
}
export default new Database();
Loading

0 comments on commit 0b90b97

Please sign in to comment.