Skip to content

NW6 | Fikret Ellek | Module Servers | [TECH ED] 🏝️ Stretch challenges / WEEK 3 #188

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

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
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
Binary file added hotel-bookings-api.zip
Binary file not shown.
18 changes: 18 additions & 0 deletions hotel-bookings-api/functions/readBookings.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import fs from "fs/promises";
import path from "path";
import { fileURLToPath } from "url";

const readBookings = (bookings) => {
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const filePath = path.join(__dirname, "../bookings.json");
return (req, res, next) => {
fs.readFile(filePath).then((bookingsData) => {
bookings.length = 0;
const parsedBookings = JSON.parse(bookingsData);
bookings.push(...parsedBookings);
next();
});
};
};

export default readBookings;
5 changes: 5 additions & 0 deletions hotel-bookings-api/functions/setNextId.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const setNextId = (newElm, arr) => {
newElm.id = arr.reduce((acc, elm) => (elm.id > acc ? elm.id : acc), 0) + 1;
};

export default setNextId;
11 changes: 11 additions & 0 deletions hotel-bookings-api/functions/updateBookings.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import fs from "fs/promises";
import path from "path";
import { fileURLToPath } from "url";

const updateBookings = async (updatedBookings) => {
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const filePath = path.join(__dirname, "../bookings.json");
await fs.writeFile(filePath, updatedBookings);
};

export default updateBookings;
35 changes: 35 additions & 0 deletions hotel-bookings-api/functions/validateNewBooking.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
export default function validateNewBooking(reservation) {
const expectedKeys = {
title: "string",
givenName: "string",
familyName: "string",
email: "string",
roomId: "number",
checkInDate: "string",
checkOutDate: "string",
};

const errors = [];

for (const [key, type] of Object.entries(expectedKeys)) {
const value = reservation[key];

if (!(key in reservation)) {
return `Missing ${key}`;
}
if (value === null || value === undefined || value === "") {
return `Please enter a ${key}`;
}
if (typeof value !== type) {
return `Invalid ${key}`;
}
if (type === "string" && value.trim() === "") {
return `Please enter a ${key}`;
}
const emailPattern = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
if (key === "email" && !emailPattern.test(value)) {
return `Invalid email format`;
}
}
return true;
}
119 changes: 106 additions & 13 deletions hotel-bookings-api/server.js
Original file line number Diff line number Diff line change
@@ -1,39 +1,132 @@
// import all the stuff we need
import express from "express";
import cors from "cors";
import { fileURLToPath } from "url";
import path from "path";
import fs from "fs/promises";
import bookings from "./bookings.json" assert { type: "json" };
import moment from "moment";

import validateNewBooking from "./functions/validateNewBooking.js";
import setNextId from "./functions/setNextId.js";
import readBookings from "./functions/readBookings.js";
import updateBookings from "./functions/updateBookings.js";

// initialise the server
const app = express();
let bookings = [];

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

// Add other routes and logic as needed
app.use(readBookings(bookings));

// GET /bookings
app.get("/bookings", (req, res) => {
res.json(bookings);
});

app.post("/bookings", (req, res) => {
const newBooking = req.body;

const validationResult = validateNewBooking(newBooking);
if (validationResult !== true) {
return res.status(400).json({ error: validationResult });
}

setNextId(newBooking, bookings);
bookings.push(newBooking);
updateBookings(JSON.stringify(bookings));

res.status(201).json({ success: "Booking created!" });
});

app.get("/bookings/search", (req, res) => {
const searchTerm = req.query.term;

const matchingBookings = bookings.filter((booking) => {
const { email, givenName, familyName } = booking;
const emailMatch = email.toLowerCase().includes(searchTerm.toLowerCase());
const firstNameMatch = givenName.toLowerCase().includes(searchTerm.toLowerCase());
const surnameMatch = familyName.toLowerCase().includes(searchTerm.toLowerCase());
return emailMatch || firstNameMatch || surnameMatch;
});
if (matchingBookings.length > 0) {
res.status(200).json(matchingBookings);
} else {
res.status(404).json({ error: "Booking not found!" });
}
});

app.get("/bookings/search-date", (req, res) => {
const searchDate = req.query.date;
const matchingBookings = bookings.filter((booking) => {
const checkIn = moment(booking.checkInDate);
const checkOut = moment(booking.checkOutDate);
const search = moment(searchDate);
return search.isSameOrAfter(checkIn) && search.isSameOrBefore(checkOut);
});

if (matchingBookings.length > 0) {
res.status(200).json(matchingBookings);
} else {
res.status(404).json({ error: "Booking not found!" });
}
});

app.get("/bookings/search", (req, res) => {
const searchTerm = req.query.term;
console.log("Search Term:", searchTerm);

const matchingBookings = bookings.filter((booking) => {
const { email, givenName, familyName } = booking;
const emailMatch = email.toLowerCase().includes(searchTerm.toLowerCase());
const firstNameMatch = givenName.toLowerCase().includes(searchTerm.toLowerCase());
const surnameMatch = familyName.toLowerCase().includes(searchTerm.toLowerCase());

console.log("Email Match:", emailMatch);
console.log("First Name Match:", firstNameMatch);
console.log("Surname Match:", surnameMatch);

return emailMatch || firstNameMatch || surnameMatch;
});

console.log("Matching Bookings:", matchingBookings);

if (matchingBookings.length > 0) {
res.status(200).json(matchingBookings);
} else {
res.status(404).json({ error: "Booking not found!" });
}
});

app.get("/bookings/:id", (req, res) => {
const requestedId = req.params.id;
const requestedBooking = bookings.find((booking) => booking.id === parseInt(requestedId));

if (requestedBooking) {
res.status(200).json(requestedBooking);
} else {
res.status(404).json({ error: "Booking not found!" });
}
});

app.delete("/bookings/:id", (req, res) => {
const deleteId = req.params.id;
const bookingIndex = bookings.findIndex((booking) => booking.id === parseInt(deleteId));

if (bookingIndex !== -1) {
bookings.splice(bookingIndex, 1);
updateBookings(JSON.stringify(bookings));
res.status(200).json({ success: "Booking deleted!" });
} else {
res.status(404).json({ error: "Booking not found!" });
}
});

const listener = app.listen(process.env.PORT || 3000, () => {
console.log("Your app is listening on port " + listener.address().port);
});

// Render simple views for testing and exploring
// You can safely delete everything below if you wish

// Set EJS as the templating engine for the app
app.set("view engine", "ejs");
// Calculate __dirname in ES module
const __dirname = path.dirname(fileURLToPath(import.meta.url));
app.set("views", path.join(__dirname, "views"));
// HERE WE MAKE ROUTES SAME AS ANY ENDPOINT, BUT USE RENDER INSTEAD OF SIMPLY RETURNING DATA
app.get("/", (req, res) => {
// Use render to load up an ejs view file
res.render("index", { title: "Hotel Booking Server" });
});
app.get("/guests", (req, res) => {
Expand Down