Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implementing user story 3 #165

Closed
14,758 changes: 5,524 additions & 9,234 deletions back-end/package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion back-end/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,4 @@
"nodemon": "^2.0.6",
"supertest": "^6.1.1"
}
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,33 @@
exports.up = function (knex) {
return knex.schema.createTable("reservations", (table) => {
table.increments("reservation_id").primary();
table.string("first_name").notNullable();
table.string("last_name", null).notNullable();
table.string("mobile_number", null).notNullable();
table.date("reservation_date").notNullable();
table.time("reservation_time").notNullable();
table.integer("people", null).unsigned().notNullable();
table.timestamps(true, true);
});
};

exports.down = function (knex) {
return knex.schema.dropTable("reservations");
};

// exports.up = function (knex) {
// return knex.schema.createTable("reservations", (table) => {
// table.increments("reservation_id").primary();
// table.string("first_name").notNullable();
// table.string("last_name").notNullable();
// table.string("mobile_number").notNullable();
// table.date("date_of_reservation").notNullable();
// table.time("time_of_reservation").notNullable();
// table.integer("people").unsigned().notNullable();
// table.timestamps(true, true);
// });
// };

// exports.down = function (knex) {
// return knex.schema.dropTable("reservations");
// };
6 changes: 5 additions & 1 deletion back-end/src/db/seeds/00-reservations.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
const reservations = require("./00-reservations.json");
exports.seed = function (knex) {
return knex.raw("TRUNCATE TABLE reservations RESTART IDENTITY CASCADE");
return knex
.raw("TRUNCATE TABLE reservations RESTART IDENTITY CASCADE")
.then(() => knex("reservations")
.insert(reservations));
};
15 changes: 15 additions & 0 deletions back-end/src/errors/asyncErrorBoundary.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
function asyncErrorBoundary(delegate, defaultStatus) {
return (request, response, next) => {
Promise.resolve()
.then(() => delegate(request, response, next))
.catch((error = {}) => {
const { status = defaultStatus, message = error } = error;
next({
status,
message,
});
});
};
}

module.exports = asyncErrorBoundary;
20 changes: 20 additions & 0 deletions back-end/src/errors/hasProperties.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
function hasProperties(...properties) {
return function (req, res, next) {
const { data = {} } = req.body;

try {
properties.forEach((property) => {
if (!data[property]) {
const error = new Error(`A '${property}' property is required.`);
error.status = 400;
throw error;
}
});
next();
} catch (error) {
next(error);
}
};
}

module.exports = hasProperties;
8 changes: 8 additions & 0 deletions back-end/src/errors/methodNotAllowed.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
function methodNotAllowed(req, res, next) {
next({
status: 405,
message: `${req.method} not allowed for ${req.originalUrl}`,
});
}

module.exports = methodNotAllowed;
187 changes: 183 additions & 4 deletions back-end/src/reservations/reservations.controller.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,191 @@
const reservationsService = require("./reservations.service");
const asyncErrorBoundary = require("../errors/asyncErrorBoundary");
/**
* List handler for reservation resources
*/
async function list(req, res) {
res.json({
data: [],
// async function list(req, res) {
// res.json({ data: await reservationsService.list() });
// }
const hasProperties = require("../errors/hasProperties");

const hasRequiredProperties = hasProperties(
"first_name",
"last_name",
"mobile_number",
"reservation_date",
"reservation_time",
"people"
);

async function list(req, res, _next) {
const { date } = req.query;
if (date) {
return res.json({ data: await reservationsService.listOnDate(date) });
} else {
data = await reservationsService.list();
return res.json({ data });
}
}

function hasBodyData(req, res, next) {
const { data } = req.body;
if (!data)
next({
status: 400,
});
next();
}

function peopleIsValid(req, res, next) {
const { people } = req.body.data;
if (!people || !Number.isInteger(people) || people <= 0) {
return next({
status: 400,
message: `people`,
});
}
next();
}

// Validate the name
function nameIsValid(req, res, next) {
const { first_name, last_name } = req.body.data;
const error = { status: 400 };
if (!first_name || !first_name.length) {
error.message = `first_name`;
return next(error);
}
if (!last_name || !last_name.length) {
error.message = `last_name`;
return next(error);
}

next();
}

// Validate mobile
function mobileNumberIsValid(req, res, next) {
const { mobile_number } = req.body.data;
if (!mobile_number)
return next({
status: 400,
message: "mobile_number",
});
next();
}

// Validate reservation date
function dateIsValid(req, res, next) {
const { reservation_date } = req.body.data;
if (!reservation_date || new Date(reservation_date) == "Invalid Date")
return next({
status: 400,
message: "reservation_date",
});
next();
}

// Validate reservation time
function timeIsValid(req, res, next) {
let { reservation_time } = req.body.data;

const error = {
status: 400,
message: "reservation_time",
};
if (!reservation_time) return next(error);
if (reservation_time[2] === ":") {
reservation_time = reservation_time.replace(":", "");
reservation_time = reservation_time.substring(0, 4);
}
res.locals.hour = reservation_time.substring(0, 2);
res.locals.mins = reservation_time.substring(2, 4);
if (Number.isInteger(Number(reservation_time))) {
next();
} else {
next(error);
}
}

function dateIsNotTuesday(req, res, next) {
const { reservation_date } = req.body.data;
const newDate = new Date(reservation_date);
const UTCDay = newDate.getUTCDay();

if (UTCDay === 2) {
return next({
status: 400,
message: `The restaurant is closed on Tuesdays. Please enter a date that is not a Tuesday for your reservation`,
});
}
next();
}

function dateIsNotInFuture(req, res, next) {
const { reservation_date } = req.body.data;
const { reservation_time } = req.body.data;
const resDate = new Date(`${reservation_date} ${reservation_time} UTC`);
const todaysDateUnformatted = new Date();
const userTimeZoneOffset = todaysDateUnformatted.getTimezoneOffset() * 60000;
const todaysDate = new Date(
todaysDateUnformatted.getTime() - userTimeZoneOffset
);

if (resDate - todaysDate > 0) {
return next();
}
next({
status: 400,
message: `The reservation_date must be in the future`,
});
}

function duringOpenHours(req, res, next) {
const { reservation_time } = req.body.data;
const hoursString = reservation_time.slice(0, 2);
const minutesString = reservation_time.slice(3, 5);
const hour = Number(hoursString);
const minutes = Number(minutesString);

if ((hour == 10 && minutes <= 30) || hour < 10) {
return next({
status: 400,
message: `Reservations can only be made between 10:30 AM until 9:30 PM`,
});
} else if ((hour == 21 && minutes >= 30) || (hour == 22 && minutes < 30)) {
return next({
status: 400,
message: `Reservations can only be made between 10:30 AM until 9:30 PM`,
});
} else if ((hour == 22 && minutes >= 30) || hour > 22) {
return next({
status: 400,
message: `Reservations can only be made between 10:30 AM until 9:30 PM`,
});
}
next();
}

async function create(req, res, next) {
res
.status(201)
.json({ data: await reservationsService.create(req.body.data) })
.catch(next);
}

module.exports = {
list,
list: asyncErrorBoundary(list),
create: [
hasRequiredProperties,
peopleIsValid,
hasBodyData,
nameIsValid,
mobileNumberIsValid,
dateIsValid,
timeIsValid,
dateIsNotTuesday,
dateIsNotInFuture,
duringOpenHours,
asyncErrorBoundary(create),
],
};
7 changes: 6 additions & 1 deletion back-end/src/reservations/reservations.router.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@

const router = require("express").Router();
const controller = require("./reservations.controller");
const methodNotAllowed = require("../errors/methodNotAllowed");

router.route("/").get(controller.list);
router
.route("/")
.get(controller.list)
.post(controller.create)
.all(methodNotAllowed)

module.exports = router;
27 changes: 27 additions & 0 deletions back-end/src/reservations/reservations.service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
const knex = require("../db/connection");

function list() {
return knex("reservations").select("*").orderBy("reservation_time").returning("*")
}

function listOnDate(reservation_date) {
return knex("reservations")
.select("*")
.where({ reservation_date })
.orderBy("reservation_time");
}



function create(reservation) {
return knex("reservations")
.insert(reservation)
.returning("*")
.then((createdRecords) => createdRecords[0])
}

module.exports = {
list,
listOnDate,
create,
}
3 changes: 3 additions & 0 deletions back-end/src/server.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@

const pg = require('pg');
pg.defaults.ssl = process.env.NODE_ENV === "production" ? { rejectUnauthorized: false } : false;
const { PORT = 5001 } = process.env;

const app = require("./app");
Expand Down