diff --git a/client/src/pages/Search.jsx b/client/src/pages/Search.jsx index 4d5636c..ee66474 100644 --- a/client/src/pages/Search.jsx +++ b/client/src/pages/Search.jsx @@ -20,6 +20,7 @@ export default function Search() { const handleSubmit = async (e) => { e.preventDefault(); try { + setFoods([]) setLoading(true); setError(null); const response = await axios.get( @@ -28,12 +29,21 @@ export default function Search() { withCredentials: true, } ); - setFoods(response.data.products); + if (response.status === 200) { + setFoods(response.data.products); + } } catch (error) { console.error("Error fetching user foods:", error); - // Set appropriate error message - if (error.response && error.response.status === 429) { - setError("Rate limit reached. Please try again later."); + if (error.response) { + if (error.response.status === 401) { + setError("Authorization Error: Please log in again"); + } else if (error.response.status === 429) { + setError("Rate limit reached. Please try again later."); + } else if (error.response.status === 400 || error.response.status === 404) { + setError(`Products not found in OpenFoodFacts database.`); + } else { + setError("An unexpected error occurred while fetching products."); + } } else { setError("An error occurred while fetching products."); } diff --git a/server/controllers/products.controller.js b/server/controllers/products.controller.js index 71a1048..fb7cc6b 100644 --- a/server/controllers/products.controller.js +++ b/server/controllers/products.controller.js @@ -8,39 +8,60 @@ async function searchAndDisplayProducts(req, res) { // Check if the searchTerm is a barcode const isBarcode = /^\d+$/.test(searchTerm.trim()); + + let productData; + if ( isBarcode && searchTerm.trim().length >= 8 && searchTerm.trim().length <= 14 ) { - // Retrieve product by barcode - const product = await ProductsModel.displayProductByBarcode( + productData = await ProductsModel.displayProductByBarcode( searchTerm, req ); - if (Object.keys(product).length === 0) { - return res.status(404).send("Product not found"); - } else if (product.error) { - return res.status(429).json({ error: product.error }); - } else { - return res.status(200).json(product); - } } else { - // Retrieve data by search term - const productsData = await ProductsModel.displayProductsBySearchTerm( + productData = await ProductsModel.displayProductsBySearchTerm( searchTerm, req ); + } - if (productsData.error) { - return res.status(429).json({ error: productsData.error }); + // Check for specific error messages and handle accordingly + if (productData.error) { + if (productData.error === "Authorization token is missing") { + return res.status(401).json({ + error: `${productData.error}`, + products: [], + }); } - - return res.status(200).json(productsData); + if (productData.error === "OpenFoodFacts API rate limit reached") { + return res.status(429).json({ + error: `${productData.error}. Please try again later.`, + products: [], + }); + } + // If no product(s) data returned + if (Object.keys(productData).length === 0) { + return res.status(404).json({ + error: `Products not found in OpenFoodFacts database`, + products: [], + }); + } + return res.status(400).json({ + error: `Products not found in OpenFoodFacts database`, + products: [], + }); } + // Successful response + return res.status(200).json(productData); } catch (error) { console.error("Error searching product:", error); - return res.status(500).json({ error: "Internal server error" }); + // Generic internal server error handler + return res.status(500).json({ + error: "Internal server error. Please try again later.", + products: [], + }); } } diff --git a/server/models/products.model.js b/server/models/products.model.js index 0e8c25b..192e951 100644 --- a/server/models/products.model.js +++ b/server/models/products.model.js @@ -135,9 +135,19 @@ ProductModel.formatProductData = function (responseProduct) { ProductModel.displayProductByBarcode = async function (barcode, req) { try { + // Ensure barcode and request are provided + if (!barcode) { + throw new Error("Barcode is required"); + } + + if (!req || !req.cookies || !req.cookies.token) { + throw new Error("Authorization token is missing"); + } // Get the user ID from the JWT token in the request cookies const user = getUserFromToken(req); - + if (!user || !user.id) { + throw new Error("Invalid user authentication"); + } // Fetch user-specific foods from the database const userFoods = await UserModel.getUserFoods(user.id); @@ -148,6 +158,9 @@ ProductModel.displayProductByBarcode = async function (barcode, req) { // Fetch product data from OpenFoodFacts API endpoint const productData = await this.fetchOFFProductByBarcode(barcode); + if (!productData || Object.keys(productData).length === 0) { + throw new Error("Products not found in OpenFoodFacts database"); + } // Format the product data from the external API const product = this.formatProductData(productData); @@ -174,22 +187,32 @@ ProductModel.displayProductByBarcode = async function (barcode, req) { ], }; } catch (error) { - if (error.message === "OpenFoodFacts API rate limit reached") { - return { - products: [], - error: "Rate limit reached. Please try again later.", - }; - } + console.error( + `Error displaying product by barcode: ${error.response.status} ${error.response.statusText}` + ); - console.error("Error displaying product by barcode:", error); - throw new Error(`Error displaying product by barcode: ${error.message}`); + return { + products: [], + error: error, + }; } }; ProductModel.displayProductsBySearchTerm = async function (searchTerm, req) { try { + // Ensure barcode and request are provided + if (!searchTerm) { + throw new Error("Search Term is required"); + } + + if (!req || !req.cookies || !req.cookies.token) { + throw new Error("Authorization token is missing"); + } // Get the user ID from the JWT token in the request cookies const user = getUserFromToken(req); + if (!user || !user.id) { + throw new Error("Invalid user authentication"); + } // Fetch user-specific foods from the database const userFoods = await UserModel.getUserFoods(user.id); @@ -201,6 +224,9 @@ ProductModel.displayProductsBySearchTerm = async function (searchTerm, req) { // Fetch products data from OpenFoodFacts API endpoint const productsData = await this.fetchOFFProductsBySearch(searchTerm); + if (!productsData || productsData.data.count === 0) { + throw new Error("Products not found in OpenFoodFacts database"); + } // Format the productsData to local database standard productsData.data.products.forEach((productData, index) => { @@ -223,15 +249,12 @@ ProductModel.displayProductsBySearchTerm = async function (searchTerm, req) { return productsData.data; } catch (error) { - if (error.message === "OpenFoodFacts API rate limit reached") { - return { - products: [], - error: "Rate limit reached. Please try again later.", - }; - } - console.error("Error searching product by search term:", error); - throw error; + + return { + products: [], + error: error, + }; } }; diff --git a/server/models/users.model.js b/server/models/users.model.js index 251e1da..19a950b 100644 --- a/server/models/users.model.js +++ b/server/models/users.model.js @@ -128,7 +128,11 @@ const UsersModel = { if (!user) { throw new Error("User not found"); } - return user.foods; + // Ensure foods.products and foods.wholefoods are arrays + return { + products: user.foods.products || [], + wholefoods: user.foods.wholefoods || [], + }; } catch (error) { throw new Error(`Error retrieving user's foods: ${error.message}`); }