diff --git a/routes/cdn/contentDelivery.js b/routes/cdn/contentDelivery.js index 186280d..2c1d7de 100644 --- a/routes/cdn/contentDelivery.js +++ b/routes/cdn/contentDelivery.js @@ -8,32 +8,26 @@ const { FoodListing,Review } = require("../../models"); router.get("/getImageForListing", async (req, res) => { const { listingID, imageName } = req.query; if (!listingID || !imageName) { - res.status(400).send("ERROR: Invalid request parameters."); - return; + return res.status(400).send("ERROR: Invalid request parameters."); } // Find the listing const findListing = await FoodListing.findByPk(listingID); if (!findListing) { - res.status(404).send("ERROR: Listing not found."); - return; + return res.status(404).send("ERROR: Listing not found."); } const findImageName = await FileManager.prepFile(imageName); if (!findImageName.startsWith("SUCCESS")) { - res.status(404).send("ERROR: Image not found."); - return; + return res.status(404).send("ERROR: Image not found."); } const listingImages = findListing.images.split("|"); if (listingImages.includes(imageName) !== true) { - res.status(404).send("ERROR: Requested image does not belong to its corresponding listing."); - return; + return res.status(404).send("ERROR: Requested image does not belong to its corresponding listing."); } - res.status(200).sendFile(findImageName.substring("SUCCESS: File path: ".length)) - // Logger.log(`CDN GETIMAGEFORLISTING: Image(s) for listing ${listingID} sent successfully.`) - return; + return res.status(200).sendFile(findImageName.substring("SUCCESS: File path: ".length)); }); router.get("/getImageForReview", async (req, res) => { diff --git a/routes/cdn/coreData.js b/routes/cdn/coreData.js index 569a948..1ffa43d 100644 --- a/routes/cdn/coreData.js +++ b/routes/cdn/coreData.js @@ -2,12 +2,13 @@ const express = require("express"); const router = express.Router(); const path = require("path"); const FileManager = require("../../services/FileManager"); +const Extensions = require("../../services/Extensions"); const { FoodListing, Host, Guest, Admin, Review, Reservation, ReviewLike } = require("../../models"); const Logger = require("../../services/Logger"); const { Sequelize } = require('sequelize'); const Universal = require("../../services/Universal"); const { validateToken, checkUser } = require("../../middleware/auth"); -const Extensions = require("../../services/Extensions"); +const { Op } = require("sequelize"); router.get('/myAccount', validateToken, (req, res) => { const userInfo = req.user; @@ -43,11 +44,29 @@ router.get("/listings", async (req, res) => { // GET all food listings where: whereClause, include: includeClause }); - foodListings.map(listing => (listing.images == null || listing.images == "") ? listing.images = [] : listing.images = listing.images.split("|")); - res.status(200).json(foodListings); + + const listingsWithImagesArray = foodListings.map(listing => { + var listingJson = listing.toJSON(); + listingJson.images = listingJson.images ? listingJson.images.split("|") : []; + listingJson = Extensions.sanitiseData( + listingJson, + [], + [ + "password", + "address", + "createdAt", + "updatedAt", + "HostUserID" + ], + [] + ) + return listingJson; + }); + + return res.status(200).json(listingsWithImagesArray); } catch (error) { - Logger.log("CDN COREDATA LISTINGS ERROR: Failed to retrieve all published listings; error: " + error) - res.status(500).send("ERROR: Internal server error"); + Logger.log("CDN COREDATA LISTINGS ERROR: Failed to retrieve all published listings; error: " + error); + return res.status(500).send("ERROR: Internal server error"); } }); @@ -57,16 +76,16 @@ router.get("/checkFavouriteListing", async (req, res) => { // GET favourite list const userID = req.query.userID; const guest = await Guest.findByPk(userID); if (!guest) { - return res.status(404).send("Guest not found."); + return res.status(404).send("UERROR: Your account details were not found"); } const favouriteCuisines = guest.favCuisine.split("|"); if (favouriteCuisines.includes(listingID)) { - res.status(200).json({ message: "SUCCESS: Listing is a favourite", listingIsFavourite: true }); + return res.status(200).json({ message: "SUCCESS: Listing is a favourite", listingIsFavourite: true }); } else { - res.status(200).json({ message: "SUCCESS: Listing is not a favourite", listingIsFavourite: false }); + return res.status(200).json({ message: "SUCCESS: Listing is not a favourite", listingIsFavourite: false }); } } catch (error) { - res.status(500).send("ERROR: Internal server error"); + return res.status(500).send("ERROR: Internal server error"); } }); @@ -294,4 +313,55 @@ router.get("/getReviews", checkUser, async (req, res) => { // GET full reviews l } }) +router.get("/consolidateReviewsStatistics", async (req, res) => { // GET full reservations list + const { hostID } = req.query; + if (!hostID) { + return res.status(400).send("UERROR: One or more required payloads were not provided"); + } + try { + const findHost = await Host.findByPk(hostID); + if (!findHost) { + return res.status(404).send("ERROR: Host doesn't exist"); + } else { + const hostFoodRatings = await Review.findAll({ + where: { hostID }, + attributes: ['foodRating'] + }); + if (hostFoodRatings.length > 0) { + const oneStarRatings = hostFoodRatings.filter(rating => rating.foodRating === 1).length; + const twoStarRatings = hostFoodRatings.filter(rating => rating.foodRating === 2).length; + const threeStarRatings = hostFoodRatings.filter(rating => rating.foodRating === 3).length; + const fourStarRatings = hostFoodRatings.filter(rating => rating.foodRating === 4).length; + const fiveStarRatings = hostFoodRatings.filter(rating => rating.foodRating === 5).length; + const totalRatings = hostFoodRatings.length; + + const consolidatedData = { + oneStar: (oneStarRatings / totalRatings) * 100, + twoStar: (twoStarRatings / totalRatings) * 100, + threeStar: (threeStarRatings / totalRatings) * 100, + fourStar: (fourStarRatings / totalRatings) * 100, + fiveStar: (fiveStarRatings / totalRatings) * 100 + } + return res.status(200).json(consolidatedData); + } else { + return res.status(200).json( + { + message: "No reviews found.", + consolidatedData: { + oneStar: 0, + twoStar: 0, + threeStar: 0, + fourStar: 0, + fiveStar: 0 + } + } + ); + } + } + } catch (err) { + Logger.log(`CDN COREDATA CONSOLIDATEREVIEWSSTATISTICS ERROR: Failed to consolidate review statistics: ${err}.`); + return res.status(500).send("ERROR: An error occured while retrieving review statistics"); + } +}); + module.exports = { router, at: '/cdn' }; \ No newline at end of file diff --git a/routes/identity/MakanHistory.js b/routes/identity/MakanHistory.js new file mode 100644 index 0000000..ce707e0 --- /dev/null +++ b/routes/identity/MakanHistory.js @@ -0,0 +1,62 @@ +const express = require("express"); +const router = express.Router(); +const path = require("path"); +const FileManager = require("../../services/FileManager"); +const Extensions = require("../../services/Extensions"); +const { FoodListing, Host, Guest, Admin, Review, Reservation, ReviewLike } = require("../../models"); +const Logger = require("../../services/Logger"); +const { Sequelize } = require('sequelize'); +const Universal = require("../../services/Universal"); +const { validateToken, checkUser } = require("../../middleware/auth"); +const { Op } = require("sequelize"); + +router.get("/getGuestPastReservations", validateToken, async(req, res) => { + const userID = req.user.userID; + if (!userID) { + return res.status(400).send("ERROR: One or more required payloads were not provided"); + } + try { + const user = await Guest.findByPk(userID) || await Host.findByPk(userID); + if (!user) { + return res.status(404).send("ERROR: User doesn't exist"); + } else { + const pastReservations = await Reservation.findAll({ + where: { + guestID: userID, + datetime: { + [Op.lt]: new Date().toISOString() + } + } + }); + if (pastReservations.length > 0) { + const foodListings = await FoodListing.findAll({ + where: { + listingID: { + [Op.in]: pastReservations.map(reservation => reservation.listingID) + } + }, + include: [{ + model: Host, + attributes: ["username", "foodRating"] + }] + }); + if (foodListings) { + if (foodListings.length > 0) { + return res.status(200).json({ pastReservations: pastReservations, foodListings: foodListings }); + } else { + return res.status(400).send("ERROR: Could not find any food listings that were tied to this reservation"); + } + } else { + return res.status(400).send("ERROR: An error occured while retrieving food listings for the past reservations"); + } + } else { + return res.status(200).json({ pastReservations: [], foodListings: [] }); + } + } + } catch (err) { + Logger.log(`MAKAN_HISTORY GETGUESTPASTRESERVATIONS ERROR: ${err}`) + return res.status(500).send("ERROR: An error occured while retrieving user's past reservations"); + } +}); + +module.exports = { router, at: '/makanHistory' }; \ No newline at end of file diff --git a/routes/listings/listings.js b/routes/listings/listings.js index cf79f28..8b043ef 100644 --- a/routes/listings/listings.js +++ b/routes/listings/listings.js @@ -17,29 +17,28 @@ router.post("/addListing", validateToken, async (req, res) => { title: yup.string().trim().required(), shortDescription: yup.string().trim().required(), longDescription: yup.string().trim().required(), - portionPrice: yup.number().required(), - totalSlots: yup.number().required(), - datetime: yup.string().trim().required() + portionPrice: yup.number().min(1).max(10).required(), + totalSlots: yup.number().min(1).max(10).required(), + datetime: yup.string().trim().required(), + publishInstantly: yup.boolean().required() }); if (req.files.length === 0) { - res.status(400).send("ERROR: No image uploaded"); - return; + return res.status(400).send("UERROR: No image was uploaded"); } else { var validatedData; try { validatedData = await addListingSchema.validate(req.body, { abortEarly: false }); } catch (validationError) { - res.status(400).send(`ERROR: ${validationError.errors.join(', ')}`); - return; + return res.status(400).send("UERROR: One or more entered fields are invalid"); } if (err instanceof multer.MulterError) { Logger.log(`LISTINGS ADDLISTING: Image upload error: ${err}`); - res.status(400).send("ERROR: Image upload error"); + return res.status(400).send("UERROR: Image(s) not accepted"); } else if (err) { Logger.log(`LISTINGS ADDLISTING: Internal server error: ${err}`); - res.status(500).send("ERROR: Internal server error"); + return res.status(500).send("ERROR: Failed to process image(s)"); } var allImagesSuccess = false; for (let i = 0; i < req.files.length; i++) { @@ -57,15 +56,13 @@ router.post("/addListing", validateToken, async (req, res) => { for (let i = 0; i < req.files.length; i++) { await FileManager.deleteFile(req.files[i].filename); } - Logger.log(`LISTINGS ADDLISTING: One or more image checks failed. ${req.files.length} image(s) deleted successfully.`) - res.status(400).send("ERROR: Failed to upload image"); - return; + Logger.log(`LISTINGS ADDLISTING ERROR: One or more image checks failed. ${req.files.length} image(s) deleted successfully.`) + return res.status(400).send("ERROR: Failed to upload image(s)"); } const hostInfo = await Host.findByPk(req.user.userID); if (!hostInfo) { - res.status(404).send("ERROR: Host not found"); - return; + return res.status(404).send("UERROR: Your account details were not found"); } try { const encodedAddress = encodeURIComponent(String(hostInfo.address)); @@ -93,7 +90,7 @@ router.post("/addListing", validateToken, async (req, res) => { }); let approximateAddress = `${street}, ${city}`; if (state) { - approximateAddress += `, ${state}`; // For contexts outside of Singapore + approximateAddress += `, ${state}`; } const listingDetails = { @@ -109,24 +106,21 @@ router.post("/addListing", validateToken, async (req, res) => { address: hostInfo.address, hostID: hostInfo.userID, coordinates: coordinates.lat + "," + coordinates.lng, - published: true, + published: validatedData.publishInstantly, }; const addListingResponse = await FoodListing.create(listingDetails); if (addListingResponse) { - res.status(200).json({ + Logger.log(`LISTINGS ADDLISTING ERROR: Listing with listingID ${listingDetails.listingID} created successfully.`) + return res.status(200).json({ message: "SUCCESS: Food listing created successfully", listingDetails, }); - Logger.log(`LISTINGS ADDLISTING ERROR: Listing with listingID ${listingDetails.listingID} created successfully.`) - return; } else { - res.status(400).send("ERROR: Failed to create food listing"); - return; + return res.status(400).send("ERROR: Failed to create food listing"); } } catch (error) { - res.status(500).send("ERROR: Internal server error"); Logger.log(`LISTINGS ADDLISTING ERROR: Internal server error: ${error}`); - return; + return res.status(500).send("ERROR: Internal server error"); } } }); @@ -136,23 +130,19 @@ router.put("/toggleFavouriteListing", validateToken, async (req, res) => { const { userID } = req.user; const { listingID } = req.body; if (!userID || !listingID) { - res.status(400).send("ERROR: One or more required payloads were not provided"); - return; + return res.status(400).send("ERROR: One or more required payloads were not provided"); } const findUser = await Guest.findByPk(userID) || await Host.findByPk(userID); if (!findUser) { - res.status(404).send("ERROR: User not found"); - return; + return res.status(404).send("ERROR: Your account details were not found"); } const findListing = await FoodListing.findByPk(listingID); if (!findListing) { - res.status(404).send("ERROR: Listing not found"); - return; + return res.status(404).send("ERROR: Listing doesn't exist"); } else if (userID === findListing.hostID) { - res.status(400).send("ERROR: Host cannot favourite their own listing"); - return; + return res.status(400).send("UERROR: Hosts cannot add their own listings to favourites"); } let favCuisine = findUser.favCuisine || ''; @@ -167,25 +157,23 @@ router.put("/toggleFavouriteListing", validateToken, async (req, res) => { findUser.favCuisine = favCuisine; await findUser.save(); const favourite = favCuisine.split("|").includes(listingID); - res.status(200).json({ + return res.status(200).json({ message: `SUCCESS: Listing ${favourite ? 'added to' : 'removed from'} favourites successfully`, favourite: favourite }); } catch (error) { - res.status(400).send("ERROR: Failed to update user's favourites"); + return res.status(400).send("ERROR: Failed to add/remove listing from favourites"); } }); router.delete("/deleteListing", async (req, res) => { const { listingID } = req.body; if (!listingID) { - res.status(400).send("ERROR: One or more required payloads were not provided"); - return; + return res.status(400).send("ERROR: One or more required payloads were not provided"); } const findListing = await FoodListing.findByPk(listingID); if (!findListing) { - res.status(404).send("ERROR: Listing not found"); - return; + return res.status(404).send("ERROR: Listing doesn't exist"); } const listingImages = findListing.images.split("|"); for (let i = 0; i < listingImages.length; i++) { @@ -194,12 +182,10 @@ router.delete("/deleteListing", async (req, res) => { } const deleteListing = await FoodListing.destroy({ where: { listingID: listingID } }); if (deleteListing) { - res.status(200).json({ message: "SUCCESS: Listing deleted successfully" }); Logger.log(`LISTINGS DELETELISTING: Listing with listingID ${listingID} deleted successfully.`) - return; + return res.status(200).send("SUCCESS: Listing deleted successfully"); } else { - res.status(400).send("ERROR: Failed to delete listing"); - return; + return res.status(400).send("ERROR: Failed to delete listing"); } });