From 18d8aebf538ee0ab722df0d80cea5ede69e201a2 Mon Sep 17 00:00:00 2001 From: fikretellek Date: Tue, 21 May 2024 03:20:21 +0100 Subject: [PATCH 1/2] implemented hotel api --- hotel-bookings-api/functions/readBookings.js | 18 +++ hotel-bookings-api/functions/setNextId.js | 5 + .../functions/updateBookings.js | 11 ++ .../functions/validateNewBooking.js | 35 ++++++ hotel-bookings-api/server.js | 119 ++++++++++++++++-- 5 files changed, 175 insertions(+), 13 deletions(-) create mode 100644 hotel-bookings-api/functions/readBookings.js create mode 100644 hotel-bookings-api/functions/setNextId.js create mode 100644 hotel-bookings-api/functions/updateBookings.js create mode 100644 hotel-bookings-api/functions/validateNewBooking.js 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) => { From 0a085075d4f5162f0afe42189673595b8075cbc0 Mon Sep 17 00:00:00 2001 From: fikretellek Date: Fri, 14 Jun 2024 15:19:23 +0100 Subject: [PATCH 2/2] zip upload --- hotel-bookings-api.zip | Bin 0 -> 6189 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 hotel-bookings-api.zip diff --git a/hotel-bookings-api.zip b/hotel-bookings-api.zip new file mode 100644 index 0000000000000000000000000000000000000000..37526b88e0696643768a4eb5d48c245043c9f2c7 GIT binary patch literal 6189 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 X6|97TiByV