diff --git a/api/package.json b/api/package.json index 08226bc..a98cc3d 100644 --- a/api/package.json +++ b/api/package.json @@ -9,8 +9,8 @@ "npm": "^10.5.0" }, "scripts": { - "dev": "nodemon src/index.mjs", - "start": "node src/index.mjs", + "dev": "nodemon src/index.js", + "start": "node src/index.js", "format": "npx prettier . --write", "check": "npx eslint . --fix" }, diff --git a/api/src/index.js b/api/src/index.js index 820d6b9..ac07a5f 100644 --- a/api/src/index.js +++ b/api/src/index.js @@ -4,6 +4,9 @@ import cors from "cors"; import bodyParser from "body-parser"; import knex from "./database_client.js"; import nestedRouter from "./routers/nested.js"; +import mealsRouter from "./routers/meals.js"; +import reservationsRouter from "./routers/reservations.js"; +import reviewsRouter from "./routers/reviews.js"; const app = express(); app.use(cors()); @@ -11,6 +14,44 @@ app.use(bodyParser.json()); const apiRouter = express.Router(); +//Future meals +apiRouter.get("/future-meals", async (req, res) => { + const meals = await knex("meal").select("*").where("when", ">", new Date()); + res.json({ meals }); +}); + +//Past meals +apiRouter.get("/past-meals", async (req, res) => { + const meals = await knex("meal").select("*").where("when", "<", new Date()); + res.json({ meals }); +}); + +//All meals sorted by ID +apiRouter.get("/all-meals", async (req, res) => { + const meals = await knex("meal").select("*").orderBy("id"); + res.json({ meals }); +}); + + +//first meal +apiRouter.get("/first-meal", async (req, res) => { + const meal = await knex("meal").select("*").orderBy("id").first(); + if (!meal) { + return res.status(404).json({ message: "No meals found" }); + } + res.json({ meal }); +}); + +//last meal +apiRouter.get("/last-meal", async (req, res) => { + const meal = await knex("meal").select("*").orderBy("id", "desc").first(); + if (!meal) { + return res.status(404).json({ message: "No meals found" }); + } + res.json({ meal }); +}); + + // You can delete this route once you add your own routes apiRouter.get("/", async (req, res) => { const SHOW_TABLES_QUERY = @@ -24,6 +65,17 @@ apiRouter.get("/", async (req, res) => { // This nested router example can also be replaced with your own sub-router apiRouter.use("/nested", nestedRouter); + +//Sub-router for meals +apiRouter.use("/meals", mealsRouter); + +//Sub-router for reservations +apiRouter.use("/reservations", reservationsRouter); + +//sub-router for reviews +apiRouter.use("/reviews", reviewsRouter); + + app.use("/api", apiRouter); app.listen(process.env.PORT, () => { diff --git a/api/src/routers/meals.js b/api/src/routers/meals.js new file mode 100644 index 0000000..6d142a5 --- /dev/null +++ b/api/src/routers/meals.js @@ -0,0 +1,146 @@ +import express from "express"; +import knex from "../database_client.js"; +import bodyParser from "body-parser"; + +const mealsRouter = express.Router(); + +mealsRouter.use(express.json()); +mealsRouter.use(bodyParser.json()); +mealsRouter.use(bodyParser.urlencoded({ extended: true })); + +// Get all meals +mealsRouter.get("/", async (req, res) => { + let query = knex("meal"); + + const { maxPrice, availableReservations, title, dateAfter, dateBefore, limit, sortKey, sortDir } = req.query; + + try { + if (maxPrice) { + query = query.where("price", "<=", maxPrice); + } + + if (title) { + query = query.where("title", "like", `%${title}%`); + } + + if (dateAfter) { + query = query.where("when", ">=", dateAfter); + } + + if (dateBefore) { + query = query.where("when", "<=", dateBefore); + } + + if (availableReservations) { + if (availableReservations === "true") { + query = query.whereExists( + knex("reservation") + .select("meal_id") + .whereRaw("meal.id = reservation.meal_id") + .groupBy("reservation.meal_id") + .havingRaw("meal.max_reservations > COUNT(reservation.id)") + ); + } else { + query = query.whereNotExists( + knex("reservation") + .select("meal_id") + .whereRaw("meal.id = reservation.meal_id") + .groupBy("reservation.meal_id") + .havingRaw("meal.max_reservations > COUNT(reservation.id)") + ); + } + } + + if (sortKey && ["when", "max_reservations", "price"].includes(sortKey)) { + query = query.orderBy(sortKey, sortDir === "desc" ? "desc" : "asc"); + } + + if (limit) { + query = query.limit(Number(limit)); + } + + const meals = await query; + res.json({ meals }); + } catch (error) { + console.error("Error fetching meals:", error); + res.status(500).json({ message: "Error fetching meals" }); + } + }); + + +//Adds a new meal to the database +mealsRouter.post("/", async (req, res) => { + const { title, description, location, when, max_reservations, price, created_date } = req.body; + if (!title || !description || !when || !max_reservations || !price || !created_date) { + return res.status(400).json({ message: "Please provide all fields" }); + } + try { + // Insert the new meal + const insertQuery = ` + INSERT INTO meal (title, description, location, when, max_reservations, price, created_date) + VALUES (?, ?, ?, ?, ?, ?, ?) + `; + + await knex.raw(insertQuery, [title, description, location, when, max_reservations, price, created_date]); + + // Get the ID of the last inserted record using LAST_INSERT_ID() + const result = await knex.raw('SELECT LAST_INSERT_ID() AS meal_id'); + const mealId = result[0][0].meal_id; + + // Retrieve the inserted meal using the mealId + const meal = await knex.raw('SELECT * FROM meal WHERE id = ?', [mealId]); + + + return res.status(201).json({ meal }); + } catch (error) { + console.error("Error inserting meal:", error); + return res.status(500).json({ message: "Error adding meal" }); + } + } +); + +//get meal by id +mealsRouter.get("/:id", async (req, res) => { + const { id } = req.params; + const meal = await knex.raw("SELECT * FROM meal WHERE id = ?", [id]); + if (meal[0].length === 0) { + return res.status(404).json({ message: "Meal not found" }); + } + res.json({ meal: meal[0][0] }); + }); + +// update the meal by id +mealsRouter.put("/:id", async (req, res) => { + const { id } = req.params; + const { title, description, location, when, max_reservations, price, created_date } = req.body; + if (!title || !description || !when || !max_reservations || !price || !created_date) { + return res.status(400).json({ message: "Please provide all fields" }); + } + try { + const updateQuery = ` + UPDATE meal + SET title = ?, description = ?, location = ?, when = ?, max_reservations = ?, price = ?, created_date = ? + WHERE id = ? + `; + await knex.raw(updateQuery, [title, description, location, when, max_reservations, price, created_date, id]); + const meal = await knex.raw("SELECT * FROM meal WHERE id = ?", [id]); + res.json({ meal: meal[0][0] }); + } catch (error) { + console.error("Error updating meal:", error); + return res.status(500).json({ message: "Error updating meal" }); + } + }); + +//delete meal by id +mealsRouter.delete("/:id", async (req, res) => { + const { id } = req.params; + const meal = await knex.raw("SELECT * FROM meal WHERE id = ?", [id]); + if (meal[0].length === 0) { + return res.status(404).json({ message: "Meal not found" }); + } + await knex.raw("DELETE FROM meal WHERE id = ?", [id]); + res.json({ message: "Meal deleted" }); + }); + + +export default mealsRouter; diff --git a/api/src/routers/reservations.js b/api/src/routers/reservations.js new file mode 100644 index 0000000..28c3670 --- /dev/null +++ b/api/src/routers/reservations.js @@ -0,0 +1,112 @@ +import express from "express"; +import knex from "../database_client.js"; +import bodyParser from "body-parser"; + + +const reservationsRouter = express.Router(); +reservationsRouter.use(express.json()); +reservationsRouter.use(bodyParser.json()); +reservationsRouter.use(bodyParser.urlencoded({ extended: true })); + +// Get all reservations +reservationsRouter.get("/", async (req, res) => { + const reservations = await knex.select("*").from("reservation"); + res.json({ reservations }); +}); + +//Adds a new reservation to the database +reservationsRouter.post("/", async (req, res) => { + const { number_of_guests, meal_id, contact_phone_number, contact_name, contact_email } = req.body; + + if (!number_of_guests || !meal_id || !contact_phone_number || !contact_name || !contact_email) { + return res.status(400).json({ message: "Please provide all fields" }); + } + try { + const created_date = new Date().toISOString().slice(0, 10); + // Insert the new reservation + const [reservation] = await knex('reservation') + .insert({ + number_of_guests, + meal_id, + contact_phone_number, + contact_name, + contact_email, + created_date + }) + .returning('*'); + + return res.status(201).json({ reservation }); + + } catch (error) { + console.error("Error inserting reservation:", error); + return res.status(500).json({ message: "Error adding reservation" }); + } + +}); + + +//get reservation by id +reservationsRouter.get("/:id", async (req, res) => { + const { id } = req.params; + const reservation = await knex('reservation').where({ id }); + if (reservation.length === 0) { + return res.status(404).json({ message: "Reservation not found" }); + } + res.json({ reservation: reservation[0] }); +}); + +// update the reservation by id +reservationsRouter.put("/:id", async (req, res) => { + const { id } = req.params; + const { number_of_guests, meal_id, contact_phone_number, contact_name, contact_email } = req.body; + + if (!number_of_guests || !meal_id || !contact_phone_number || !contact_name || !contact_email) { + return res.status(400).json({ message: "Please provide all fields" }); + } + + try { + const updated_date = new Date().toISOString().slice(0, 10); + // Update the reservation + const [reservation] = await knex('reservation') + .where({ id }) + .update({ + number_of_guests, + meal_id, + contact_phone_number, + contact_name, + contact_email, + updated_date + }) + .returning('*'); + + res.json({ reservation }); + + } catch (error) { + console.error("Error updating reservation:", error); + return res.status(500).json({ message: "Error updating reservation" }); + } +}); + +//delete reservation by id +reservationsRouter.delete("/:id", async (req, res) => { + const { id } = req.params; + try { + const reservation = await knex('reservation').where({ id }).first(); + + if (!reservation) { + return res.status(404).json({ message: "Reservation not found" }); + } + + await knex('reservation').where({ id }).del(); + res.json({ message: "Reservation deleted successfully" }); + + } catch (error) { + console.error("Error deleting reservation:", error); + res.status(500).json({ message: "Error deleting reservation" }); + } +}); + + + + +export default reservationsRouter; diff --git a/api/src/routers/reviews.js b/api/src/routers/reviews.js new file mode 100644 index 0000000..0a64725 --- /dev/null +++ b/api/src/routers/reviews.js @@ -0,0 +1,113 @@ +import express from "express"; +import knex from "../database_client.js"; +import bodyParser from "body-parser"; +import e from "express"; + +const reviewsRouter = express.Router(); + +reviewsRouter.use(express.json()); +reviewsRouter.use(bodyParser.json()); +reviewsRouter.use(bodyParser.urlencoded({ extended: true })); + +// Get all reviews +reviewsRouter.get("/", async (req, res) => { + const reviews = await knex.select("*").from("review"); + res.json({ reviews }); +}); + +//Get all reviews for a specific meal. +reviewsRouter.get("/meals/:meal_id/reviews", async (req, res) => { + const { meal_id } = req.params; + const reviews = await knex("review").where({ meal_id }); + res.json({ reviews }); +} +); + +//Adds a new review to the database +reviewsRouter.post("/", async (req, res) => { + const { title, description, stars, meal_id } = req.body; + + if (!title || !description || !stars || !meal_id) { + return res.status(400).json({ message: "Please provide all fields" }); + } + try { + const created_date = new Date().toISOString().slice(0, 10); + // Insert the new review + const [review] = await knex('review') + .insert({ + title, + description, + stars, + meal_id, + created_date + }) + .returning('*'); + return res.status(201).json({ review }); + + } catch (error) { + console.error("Error inserting review:", error); + return res.status(500).json({ message: "Error adding review" }); + } + +} +); + +//get review by id +reviewsRouter.get("/:id", async (req, res) => { + const { id } = req.params; + const review = await knex('review').where({ id }); + if (review.length === 0) { + return res.status(404).json({ message: "Review not found" }); + } + res.json({ review: review[0] }); +}); + +// update the review by id +reviewsRouter.put("/:id", async (req, res) => { + const { id } = req.params; + const { title, description, stars, meal_id } = req.body; + + if (!title || !description || !stars || !meal_id) { + return res.status(400).json({ message: "Please provide all fields" }); + } + try { + // Update the review + const [review] = await knex('review') + .where({ id }) + .update({ + title, + description, + stars, + meal_id + }) + .returning('*'); + + return res.json({ review }); + + } catch (error) { + console.error("Error updating review:", error); + return res.status(500).json({ message: "Error updating review" }); + } + +}); + +//delete review by id +reviewsRouter.delete("/:id", async (req, res) => { + const { id } = req.params; + try { + const review = await knex('review').where({ id }).first(); + + if (!review) { + return res.status(404).json({ message: "Review not found" }); + } + + await knex('review').where({ id }).del(); + res.json({ message: "Review deleted successfully" }); + + } catch (error) { + console.error("Error deleting review:", error); + res.status(500).json({ message: "Error deleting review" }); + } +}); + +export default reviewsRouter; \ No newline at end of file diff --git a/api/src/schema.sql b/api/src/schema.sql new file mode 100644 index 0000000..d557351 --- /dev/null +++ b/api/src/schema.sql @@ -0,0 +1,37 @@ +CREATE DATABASE `meal-sharing` + DEFAULT CHARACTER SET = 'utf8mb4'; + +USE `meal-sharing`; + +CREATE TABLE meal ( + id INT PRIMARY KEY AUTO_INCREMENT, + title VARCHAR(255), + description TEXT, + location VARCHAR(255), + `when` DATE, + max_reservations INT, + price DECIMAL(5, 2), + created_date DATE +); + + +CREATE TABLE reservation ( + id INT PRIMARY KEY AUTO_INCREMENT, + number_of_guests INT, + meal_id INT, + created_date DATE, + contact_phone_number VARCHAR(255), + contact_name VARCHAR(255), + contact_email VARCHAR(255), + FOREIGN KEY (meal_id) REFERENCES meal(id) ON DELETE CASCADE +); + +CREATE TABLE review ( + id INT PRIMARY KEY AUTO_INCREMENT, + title VARCHAR(255), + description TEXT, + meal_id INT, + stars INT, + created_date DATE, + FOREIGN KEY (meal_id) REFERENCES meal(id) ON DELETE CASCADE +); \ No newline at end of file