Skip to content

Commit

Permalink
api gobarber
Browse files Browse the repository at this point in the history
  • Loading branch information
AdrianoBiolchi committed Aug 16, 2019
0 parents commit ffdd321
Show file tree
Hide file tree
Showing 43 changed files with 1,271 additions and 0 deletions.
8 changes: 8 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
root = true

[*]
indent_style = space
indent_size = 2
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
32 changes: 32 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
APP_URL=http://localhost:3333
NODE_ENV=development

# Auth
APP_SECRET=bootcampgobarbernode

# Database

DB_HOST=localhost
DB_USER=
DB_PASS=
DB_NAME=

# Mongo

MONGO_URL=

# Redis

REDIS_HOST=127.0.0.1
REDIS_PORT=6379

# Mail

MAIL_HOST=
MAIL_PORT=
MAIL_USER=
MAIL_PASS=

# Sentry

SENTRY_DSN=
25 changes: 25 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
module.exports = {
env: {
es6: true,
node: true,
},
extends: [
'airbnb-base', 'prettier'
],
plugins:['prettier'],
globals: {
Atomics: 'readonly',
SharedArrayBuffer: 'readonly',
},
parserOptions: {
ecmaVersion: 2018,
sourceType: 'module',
},
rules: {
"prettier/prettier": "error",
"class-methods-use-this": "off",
"no-param-reassign": "off",
"camelcase": "off",
"no-unused-vars": ["error", {"argsIgnorePattern": "next"}]
},
};
31 changes: 31 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js

tmp/uploads/*
!tmp/uploads/.gitkeep

dist
.env


# testing
/coverage

# production
/build

# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local

npm-debug.log*
yarn-debug.log*
yarn-error.log*
yarn.lock
4 changes: 4 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"singleQuote": true,
"trailingComma": "es5"
}
8 changes: 8 additions & 0 deletions .sequelizerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const { resolve } = require('path');

module.exports ={
config: resolve(__dirname, 'src', 'config','database.js'),
'models-path': resolve(__dirname, 'src', 'app','models'),
'migrations-path': resolve(__dirname, 'src', 'database','migrations'),
'seeders-path': resolve(__dirname, 'src', 'database','seeds'),
}
5 changes: 5 additions & 0 deletions nodemon.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"execMap":{
"js" : "sucrase-node"
}
}
42 changes: 42 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"name": "modulo02",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {
"@sentry/node": "5.4.3",
"bcryptjs": "^2.4.3",
"bee-queue": "^1.2.2",
"cors": "^2.8.5",
"date-fns": "^2.0.0-beta.2",
"dotenv": "^8.0.0",
"express": "^4.17.1",
"express-async-errors": "^3.1.1",
"express-handlebars": "^3.1.0",
"jsonwebtoken": "^8.5.1",
"mongoose": "^5.6.2",
"multer": "^1.4.1",
"nodemailer": "^6.2.1",
"nodemailer-express-handlebars": "^3.0.0",
"pg": "^7.11.0",
"pg-hstore": "^2.3.3",
"sequelize": "^5.8.12",
"youch": "^2.0.10",
"yup": "^0.27.0"
},
"scripts": {
"dev": "nodemon src/server.js",
"queue": "nodemon src/queue.js"
},
"devDependencies": {
"eslint": "^5.16.0",
"eslint-config-airbnb-base": "^13.1.0",
"eslint-config-prettier": "^6.0.0",
"eslint-plugin-import": "^2.18.0",
"eslint-plugin-prettier": "^3.1.0",
"nodemon": "^1.19.1",
"prettier": "^1.18.2",
"sequelize-cli": "^5.5.0",
"sucrase": "^3.10.1"
}
}
55 changes: 55 additions & 0 deletions src/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import 'dotenv/config';

import express from 'express';
import path from 'path';
import cors from 'cors';
import Youch from 'youch';
import * as Sentry from '@sentry/node';
import 'express-async-errors';

import routes from './routes';

import sentryConfig from './config/sentry';

import './database';

class App {
constructor() {
this.server = express();

Sentry.init(sentryConfig);

this.middlewares();
this.routes();
this.exceptionHandler();
}

middlewares() {
this.server.use(Sentry.Handlers.requestHandler());
this.server.use(cors()); // setar url se for
this.server.use(express.json());
this.server.use(
'/files',
express.static(path.resolve(__dirname, '..', 'tmp', 'uploads'))
);
}

routes() {
this.server.use(routes);
// The error handler must be before any other error middleware and after all controllers
this.server.use(Sentry.Handlers.errorHandler());
}

exceptionHandler() {
this.server.use(async (err, req, res, next) => {
if (process.env.NODE_ENV === 'development') {
const errors = await new Youch(err, req).toJSON();
return res.status(500).json(errors);
}

return res.status(500).json({ error: 'Internal Server Error' });
});
}
}

export default new App().server;
169 changes: 169 additions & 0 deletions src/app/controllers/AppointmentController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import * as Yup from 'yup';
import { startOfHour, parseISO, isBefore, format, subHours } from 'date-fns';
import pt from 'date-fns/locale/pt';
import Appointment from '../models/Appointment';
import User from '../models/User';
import File from '../models/File';
import Notification from '../schemas/Notification';

import Queue from '../../lib/Queue';
import CancellationMail from '../jobs/CancellationMail';

class AppointmentController {
async index(req, res) {
const { page = 1 } = req.query;
const appointments = await Appointment.findAll({
where: { user_id: req.userId, canceled_at: null },
order: ['date'],
attributes: ['id', 'date', 'past', 'cancelable'],
limit: 20,
offset: (page - 1) * 20,
include: [
{
model: User,
as: 'provider',
attributes: ['id', 'name'],
include: [
{
model: File,
as: 'avatar',
attributes: ['id', 'path', 'url'],
},
],
},
],
});

return res.json(appointments);
}

async store(req, res) {
const schema = Yup.object().shape({
provider_id: Yup.number().required(),
date: Yup.date().required(),
});

if (!(await schema.isValid(req.body))) {
return res.status(400).json({ error: 'Validation Fails' });
}

const { provider_id, date } = req.body;

/**
* Check if provider_id is provider
*/

const checkIsProvider = await User.findOne({
where: { id: provider_id, provider: true },
});

if (!checkIsProvider) {
return res
.status(401)
.json({ error: 'You can only create appointments with providers' });
}

if (provider_id === req.userId) {
return res.status(401).json({
error: 'You can not schedule with yourself.',
});
}

/**
*Pega a hora inteira, sempre 19:00, nunca 19:01, 02 ...
*Check for past dates
*/
const hourStart = startOfHour(parseISO(date));

// Verifica se a hora/dia escolhido já passou
if (isBefore(hourStart, new Date())) {
return res.status(400).json({ error: 'Past dates are not permitted' });
}

/**
* Check date availability
* Checa se a data está disponível
*/

const checkAvailability = await Appointment.findOne({
where: {
provider_id,
canceled_at: null,
date: hourStart,
},
});

if (checkAvailability) {
return res
.status(400)
.json({ error: 'Appointment date is not available' });
}

const appointment = await Appointment.create({
user_id: req.userId,
provider_id,
date,
});

/**
* Notify appointment provider
*/
const user = await User.findByPk(req.userId);

const formattedDate = format(
hourStart,
"'dia' dd 'de' MMMM', às' H:mm'h'",
{ locale: pt }
);

await Notification.create({
content: `Novo agendamento de ${user.name} para ${formattedDate}.`,
user: provider_id,
});

return res.json(appointment);
}

async delete(req, res) {
const appointment = await Appointment.findByPk(req.params.id, {
include: [
{
model: User,
as: 'provider',
attributes: ['name', 'email'],
},
{
model: User,
as: 'user',
attributes: ['name'],
},
],
});

if (appointment.user_id !== req.userId) {
return res.status(401).json({
error: "You don't have permission to cancel this appointment.",
});
}

const dateWithSub = subHours(appointment.date, 2);

if (isBefore(dateWithSub, new Date())) {
return res.status(401).json({
error: 'You can only cancel appointments 2 hours in advance.',
});
}

appointment.canceled_at = new Date();

await appointment.save();

await Queue.add(CancellationMail.key, {
appointment,
});

return res.json(appointment);
}
}

export default new AppointmentController();
Loading

0 comments on commit ffdd321

Please sign in to comment.