diff --git a/hotel-bookings-api.zip b/hotel-bookings-api.zip new file mode 100644 index 0000000..37526b8 Binary files /dev/null and b/hotel-bookings-api.zip differ diff --git a/hotel-bookings-api/functions/readBookings.js b/hotel-bookings-api/functions/readBookings.js new file mode 100644 index 0000000..9b3038b --- /dev/null +++ b/hotel-bookings-api/functions/readBookings.js @@ -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; diff --git a/hotel-bookings-api/functions/setNextId.js b/hotel-bookings-api/functions/setNextId.js new file mode 100644 index 0000000..ebaffa6 --- /dev/null +++ b/hotel-bookings-api/functions/setNextId.js @@ -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; diff --git a/hotel-bookings-api/functions/updateBookings.js b/hotel-bookings-api/functions/updateBookings.js new file mode 100644 index 0000000..80f6de5 --- /dev/null +++ b/hotel-bookings-api/functions/updateBookings.js @@ -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; diff --git a/hotel-bookings-api/functions/validateNewBooking.js b/hotel-bookings-api/functions/validateNewBooking.js new file mode 100644 index 0000000..95fb359 --- /dev/null +++ b/hotel-bookings-api/functions/validateNewBooking.js @@ -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; +} diff --git a/hotel-bookings-api/server.js b/hotel-bookings-api/server.js index d81500f..18758be 100644 --- a/hotel-bookings-api/server.js +++ b/hotel-bookings-api/server.js @@ -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) => {