diff --git a/.github/workflows/backendCoverage.yml b/.github/workflows/backendCoverage.yml new file mode 100644 index 00000000..c2f642b6 --- /dev/null +++ b/.github/workflows/backendCoverage.yml @@ -0,0 +1,36 @@ +name: Running Backend Tests With Coverage + +on: + pull_request: + branches: + - group31-fall24 + - master + +jobs: + run_backend_tests_Coverage: + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [14.x] + steps: + - name: Checkout repository + uses: actions/checkout@v2 + with: + fetch-depth: 2 + - name: Set up Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - name: Install dependencies + run: | + cd Code/backend + touch .env + echo RECIPES_DB_URI=${{ secrets.TESTING_DB_URI }} >> .env + echo RECIPES_NS=${{ secrets.DB_NAME }} >> .env + echo PORT=${{ secrets.API_PORT }} >> .env + echo GMAIL=${{ secrets.GMAIL }} >> .env + npm install + - name: Run backend tests with coverage + run: | + cd Code/backend + npm run test-cov diff --git a/.github/workflows/backendTests.yml b/.github/workflows/backendTests.yml new file mode 100644 index 00000000..e456b42c --- /dev/null +++ b/.github/workflows/backendTests.yml @@ -0,0 +1,33 @@ +name: Running Backend Tests + +on: push + +jobs: + run_backend_tests: + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [14.x] + steps: + - name: Checkout repository + uses: actions/checkout@v2 + with: + fetch-depth: 2 + - name: Set up Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - name: Install dependencies + run: | + cd Code/backend + touch .env + echo RECIPES_DB_URI=${{ secrets.TESTING_DB_URI }} >> .env + echo RECIPES_NS=${{ secrets.DB_NAME }} >> .env + echo PORT=${{ secrets.API_PORT }} >> .env + echo GMAIL=${{ secrets.GMAIL }} >> .env + npm install + - name: Run backend tests + run: | + cd Code/backend + npm test + diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 4a29016f..d1862a67 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -1,6 +1,6 @@ name: Running Code Coverage -on: [push, pull_request] +on: workflow_dispatch jobs: build: diff --git a/Code/backend/.babelrc b/Code/backend/.babelrc new file mode 100644 index 00000000..522c7e1c --- /dev/null +++ b/Code/backend/.babelrc @@ -0,0 +1,12 @@ +{ + "presets": [ + [ + "@babel/preset-env", + { + "targets": { + "node": "current" + } + } + ] + ] +} \ No newline at end of file diff --git a/Code/backend/.env b/Code/backend/.env deleted file mode 100644 index 6613871c..00000000 --- a/Code/backend/.env +++ /dev/null @@ -1,4 +0,0 @@ -RECIPES_DB_URI=mongodb+srv://atharvajoshi067:ZgSvdar14OnteUZx@cluster0.9zuebnu.mongodb.net/?retryWrites=true&w=majority -RECIPES_NS=recipe_recommender -PORT=5000 -GMAIL= atharva.joshi17@siesgst.ac.in \ No newline at end of file diff --git a/Code/backend/__tests__/test.spec.js b/Code/backend/__tests__/test.spec.js index 9460c698..c34b03ff 100644 --- a/Code/backend/__tests__/test.spec.js +++ b/Code/backend/__tests__/test.spec.js @@ -1,7 +1,11 @@ -const mongodb = require("mongodb"); +//const mongodb = require("mongodb"); +import { TextEncoder, TextDecoder } from 'util'; +Object.assign(global, { TextDecoder, TextEncoder }); +import * as mongodb from "mongodb" const MongoClient = mongodb.MongoClient; // const request = require("supertest")(httplocalhost5000apiv1); const expect = require("chai").expect; +//import expect from "chai" // var util= require('util'); // var encoder = new util.TextEncoder('utf-8'); // @@ -23,8 +27,7 @@ function test_connectivity_func() { // Connection URI. Update username, password, and your-cluster-url to reflect your cluster. // See httpsdocs.mongodb.comecosystemdriversnode for more details - const uri = - "mongodb+srv://atharvajoshi067:ZgSvdar14OnteUZx@cluster0.9zuebnu.mongodb.net/recipe_recommender?retryWrites=true&w=majority"; + const uri = process.env.RECIPES_DB_URI; var result = false; try { // Connect to the MongoDB cluster diff --git a/Code/backend/__tests__/test1.js b/Code/backend/__tests__/test1.js index ee33f112..48581904 100644 --- a/Code/backend/__tests__/test1.js +++ b/Code/backend/__tests__/test1.js @@ -1,67 +1,80 @@ -const request = require("supertest")("http://localhost:5000/api/v1"); +import app from "../server"; +const baseURL = "/api/v1"; + +const request = require("supertest"); const expect = require("chai").expect; describe("GET /recipes", function () { it("is the API is functional test 1", async function () { - const response = await request.get("/recipes?CleanedIngredients=coconut"); + await request(app).get(baseURL + "/recipes/initDB") + const response = await request(app).get(baseURL + "/recipes?CleanedIngredients=coconut"); expect(response.status).to.eql(200); }); it("is the API is functional test 2", async function () { - const response = await request.get("/recipes?CleanedIngredients=COCONUT"); + await request(app).get(baseURL + "/recipes/initDB") + const response = await request(app).get(baseURL + "/recipes?CleanedIngredients=COCONUT"); expect(response.status).to.eql(200); }); it("is the API is functional test 3", async function () { - const response = await request.get("/recipes?CleanedIngredients=mango"); + await request(app).get(baseURL + "/recipes/initDB") + const response = await request(app).get(baseURL + "/recipes?CleanedIngredients=mango"); expect(response.status).to.eql(200); }); it("is the API is functional test 4", async function () { - const response = await request.get("/recipes?CleanedIngredients=MANGO"); + await request(app).get(baseURL + "/recipes/initDB") + const response = await request(app).get(baseURL + "/recipes?CleanedIngredients=MANGO"); expect(response.status).to.eql(200); }); it("is the API is functional test 5", async function () { - const response = await request.get("/recipes?CleanedIngredients=Mango"); + await request(app).get(baseURL + "/recipes/initDB") + const response = await request(app).get(baseURL + "/recipes?CleanedIngredients=Mango"); expect(response.status).to.eql(200); }); it("is the API is functional test 6", async function () { - const response = await request.get("/recipes?CleanedIngredients=mANGO"); + await request(app).get(baseURL + "/recipes/initDB") + const response = await request(app).get(baseURL + "/recipes?CleanedIngredients=mANGO"); expect(response.status).to.eql(200); }); it("is the API is functional test 7", async function () { - const response = await request.get( - "/recipes?CleanedIngredients={mango, salt}" + await request(app).get(baseURL + "/recipes/initDB") + const response = await request(app).get( + baseURL + "/recipes?CleanedIngredients={mango, salt}" ); expect(response.status).to.eql(200); }); it("is the API is functional test 8", async function () { - const response = await request.get( - "/recipes?CleanedIngredients={salt, mango}" + await request(app).get(baseURL + "/recipes/initDB") + const response = await request(app).get( + baseURL + "/recipes?CleanedIngredients={salt, mango}" ); expect(response.status).to.eql(200); }); it("is the API is functional test 8", async function () { - const response = await request.get("/recipes?CleanedIngredients={}"); + await request(app).get(baseURL + "/recipes/initDB") + const response = await request(app).get(baseURL + "/recipes?CleanedIngredients={}"); expect(response.status).to.eql(200); }); it("is the API is fetching the filtered ingredient", async function () { - const response = await request.get("/recipes?CleanedIngredients=pear"); + await request(app).get(baseURL + "/recipes/initDB") + const response = await request(app).get(baseURL + "/recipes?CleanedIngredients=pear"); expect(response.body.filters.CleanedIngredients).to.eql("pear"); }); diff --git a/Code/backend/__tests__/test2.js b/Code/backend/__tests__/test2.js index ead4559a..7f6514ff 100644 --- a/Code/backend/__tests__/test2.js +++ b/Code/backend/__tests__/test2.js @@ -1,57 +1,69 @@ -const request = require("supertest")("http://localhost:5000/api/v1"); +import app from "../server"; +const baseURL = "/api/v1"; + +const request = require("supertest"); const expect = require("chai").expect; describe("GET /recipes", function () { it("is the API is fetching the filtered ingredient", async function () { - const response = await request.get("/recipes?CleanedIngredients=pear"); + await request(app).get(baseURL + "/recipes/initDB") + const response = await request(app).get(baseURL + "/recipes?CleanedIngredients=pear"); expect(response.body.filters.CleanedIngredients).to.eql("pear"); }); it("is the API is fetching the filtered ingredient", async function () { - const response = await request.get("/recipes?CleanedIngredients=peach"); + await request(app).get(baseURL + "/recipes/initDB") + const response = await request(app).get(baseURL + "/recipes?CleanedIngredients=peach"); expect(response.body.filters.CleanedIngredients).to.eql("peach"); }); it("is the API is fetching the filtered ingredient", async function () { - const response = await request.get("/recipes?CleanedIngredients=salt"); + await request(app).get(baseURL + "/recipes/initDB") + const response = await request(app).get(baseURL + "/recipes?CleanedIngredients=salt"); expect(response.body.filters.CleanedIngredients).to.eql("salt"); }); it("is the API is fetching the filtered ingredient", async function () { - const response = await request.get("/recipes?CleanedIngredients=sugar"); + await request(app).get(baseURL + "/recipes/initDB") + const response = await request(app).get(baseURL + "/recipes?CleanedIngredients=sugar"); expect(response.body.filters.CleanedIngredients).to.eql("sugar"); }); it("is the API is fetching the filtered ingredient", async function () { - const response = await request.get("/recipes?CleanedIngredients=cheese"); + await request(app).get(baseURL + "/recipes/initDB") + const response = await request(app).get(baseURL + "/recipes?CleanedIngredients=cheese"); expect(response.body.filters.CleanedIngredients).to.eql("cheese"); }); it("is the API is fetching the filtered ingredient", async function () { - const response = await request.get("/recipes?CleanedIngredients=lemon"); + await request(app).get(baseURL + "/recipes/initDB") + const response = await request(app).get(baseURL + "/recipes?CleanedIngredients=lemon"); expect(response.body.filters.CleanedIngredients).to.eql("lemon"); }); it("is the API is fetching the filtered ingredient", async function () { - const response = await request.get("/recipes?CleanedIngredients=spinach"); + await request(app).get(baseURL + "/recipes/initDB") + const response = await request(app).get(baseURL + "/recipes?CleanedIngredients=spinach"); expect(response.body.filters.CleanedIngredients).to.eql("spinach"); }); it("is the API is fetching the filtered ingredient", async function () { - const response = await request.get("/recipes?CleanedIngredients=apple"); + await request(app).get(baseURL + "/recipes/initDB") + const response = await request(app).get(baseURL + "/recipes?CleanedIngredients=apple"); expect(response.body.filters.CleanedIngredients).to.eql("apple"); }); it("is the API is fetching the filtered ingredient", async function () { - const response = await request.get("/recipes?CleanedIngredients=tortillas"); + await request(app).get(baseURL + "/recipes/initDB") + const response = await request(app).get(baseURL + "/recipes?CleanedIngredients=tortillas"); expect(response.body.filters.CleanedIngredients).to.eql("tortillas"); }); diff --git a/Code/backend/__tests__/testAddRecipes.js b/Code/backend/__tests__/testAddRecipes.js new file mode 100644 index 00000000..8febfd93 --- /dev/null +++ b/Code/backend/__tests__/testAddRecipes.js @@ -0,0 +1,65 @@ +import app from "../server"; +const baseURL = "/api/v1/recipes"; + +const request = require("supertest"); +const expect = require("chai").expect; + +describe("POST /addRecipes", function () { + it("Should add a recipe if all fields are provided", async function() { + await request(app).get(baseURL + "/initDB"); + const recipe = { + recipeName: "Test_Recipe_1", + cookingTime: 20, + dietType: "Normal", + recipeRating: 3, + cuisine: "American", + imageURL: "", + recipeURL: "", + instructions: "1) Cook the food \n2) Eat the food", + ingredients: ["Meat", "Pasta", "Sauce"], + restaurants: ["Pasta House"], + locations: ["Raleigh"] + } + const response = await request(app).post(baseURL + "/addRecipe").send(recipe) + expect(response.status).to.equal(200) + const response2 = await request(app).get( + baseURL + "/getRecipeByName?recipeName=Test_Recipe_1" + ); + expect(response2.status).to.equal(200) + expect(response2.text.includes("Test_Recipe_1")).true; + }); + it("Should add a recipe if some fields are provided", async function() { + await request(app).get(baseURL + "/initDB"); + const recipe = { + recipeName: "Test_Recipe_2", + cookingTime: 20, + recipeRating: 3, + cuisine: "American", + instructions: "1) Cook the food \n2) Eat the food", + ingredients: ["Meat", "Pasta", "Sauce"], + restaurants: [], + locations: [] + } + const response = await request(app).post(baseURL + "/addRecipe").send(recipe) + expect(response.status).to.equal(200) + const response2 = await request(app).get( + baseURL + "/getRecipeByName?recipeName=Test_Recipe_2" + ); + expect(response2.status).to.equal(200) + expect(response2.text.includes("Test_Recipe_2")).true; + }); + it("Should not add a recipe if no name is provided", async function() { + await request(app).get(baseURL + "/initDB"); + const recipe = { + cookingTime: 20, + recipeRating: 3, + cuisine: "American", + instructions: "1) Cook the food \n2) Eat the food", + ingredients: ["Meat", "Pasta", "Sauce"], + restaurants: [], + locations: [] + } + const response = await request(app).post(baseURL + "/addRecipe").send(recipe) + expect(response.status).to.not.equal(200) + }); +}) \ No newline at end of file diff --git a/Code/backend/__tests__/testBookmarksAndUserProfile.js b/Code/backend/__tests__/testBookmarksAndUserProfile.js index 2e704b1e..d040a5a7 100644 --- a/Code/backend/__tests__/testBookmarksAndUserProfile.js +++ b/Code/backend/__tests__/testBookmarksAndUserProfile.js @@ -2,29 +2,83 @@ Copyright (c) 2023 Pannaga Rao, Harshitha, Prathima, Karthik */ -const request = require("supertest")("http://localhost:5000/api/v1/recipes"); +import app from "../server"; +const baseURL = "/api/v1/recipes"; + +const request = require("supertest"); const expect = require("chai").expect; -describe("Recipes API Tests", function () { +describe("Bookmarks API Tests", function () { describe("POST /addRecipeToProfile", function () { it("should successfully add a recipe to user's profile", async function () { - const recipeData = { recipeId: "1", title: "Test Recipe" }; - const response = await request.post("/addRecipeToProfile") - .send(recipeData) - + await request(app).get(baseURL + "/initDB") + const recipeData = { recipeId: "1", title: "Test Recipe"}; + const response = await request(app).post(baseURL + "/addRecipeToProfile") + .send({userName: "Test", recipe: recipeData}) expect(response.status).to.eql(200); + const response2 = await request(app).get(baseURL + "/getBookmarks") + .query({ userName: "Test" }); + expect(response2.text.includes('"recipeId":"1"')).true + }); + it("should successfully not add a recipe if no user is given", async function () { + await request(app).get(baseURL + "/initDB") + const recipeData = { recipeId: "2", title: "Test Recipe"}; + const response = await request(app).post(baseURL + "/addRecipeToProfile") + .send({recipe: recipeData}) + expect(response.status).not.to.eql(200); + expect(response.text.includes('"success":false')).true + }); + it("should successfully not add a recipe if no recipe is given", async function () { + await request(app).get(baseURL + "/initDB") + const response = await request(app).post(baseURL + "/addRecipeToProfile") + .send({userName: "Test"}) + + expect(response.status).not.to.eql(200); + expect(response.text.includes('"success":false')).true }); }); describe("Recipes API - Get Bookmarks Tests", function () { // Test with userName provided it("should return bookmarks for a given user", async function () { - const response = await request.get("/getBookmarks") + await request(app).get(baseURL + "/initDB") + const response = await request(app).get(baseURL + "/getBookmarks") .query({ userName: "Test" }); expect(response.status).to.eql(200); }); + it("should not return bookmarks for a nonexistent user", async function () { + await request(app).get(baseURL + "/initDB") + const response = await request(app).get(baseURL + "/getBookmarks") + .query({ userName: "Fake" }); + + expect(response.status).not.to.eql(200); + }); }); + describe("Recipes API - Delete Bookmarks Tests", function() { + it("Should remove an existing bookmark from a user", async function() { + await request(app).get(baseURL + "/initDB"); + const response = await request(app).post(baseURL + "/removeBookmark").send({userName:"TestB", recipeId:85}) + expect(response.status).to.equal(200) + expect(response.text.includes('"success":true')).true + const response2 = await request(app).get(baseURL + "/getBookmarks") + .query({ userName: "TestB" }); + expect(response2.text.includes("Test_Recipe_B")).false + }) + it("Should not remove a bookmark that does not exist", async function() { + await request(app).get(baseURL + "/initDB"); + const response = await request(app).post(baseURL + "/removeBookmark").send({userName:"TestB", recipeId:55}) + expect(response.status).to.equal(200) + expect(response.text.includes('"success":false')).true + }) + it("Should not remove a bookmark from a user that does not exist", async function() { + await request(app).get(baseURL + "/initDB"); + const response = await request(app).post(baseURL + "/removeBookmark").send({userName:"Fake", recipeId:85}) + expect(response.status).to.equal(200) + expect(response.text.includes('"success":false')).true + }) + }) + }); diff --git a/Code/backend/__tests__/testGetRecipes.js b/Code/backend/__tests__/testGetRecipes.js index 31ec32ce..9b1ff7b7 100644 --- a/Code/backend/__tests__/testGetRecipes.js +++ b/Code/backend/__tests__/testGetRecipes.js @@ -1,71 +1,96 @@ -const request = require("supertest")("http://localhost:5000/api/v1"); +import app from "../server"; +const baseURL = "/api/v1/recipes"; + +const request = require("supertest"); const expect = require("chai").expect; describe("GET /recipes", function () { it("is the API is functional test 1", async function () { - const response = await request.get("/recipes?CleanedIngredients=Tomato"); + await request(app).get(baseURL + "/initDB") + const response = await request(app).get(baseURL + "?CleanedIngredients=Tomato"); expect(response.status).to.eql(200); }); it("is the API is functional test 2", async function () { - const response = await request.get("/recipes?Cuisine=Indian"); + await request(app).get(baseURL + "/initDB") + const response = await request(app).get(baseURL + "?Cuisine=Indian"); expect(response.status).to.eql(200); }); it("is the API is fetching the filtered ingredient", async function () { - const response = await request.get( - "/recipes?CleanedIngredients=Mango&Cuisine=Indian" + await request(app).get(baseURL + "/initDB") + const response = await request(app).get( + baseURL + "?CleanedIngredients=Mango&Cuisine=Indian" ); expect(response.body.filters.CleanedIngredients).to.eql("Mango"); }); it("is the API is fetching the filtered ingredient", async function () { - const response = await request.get( - "/recipes?CleanedIngredients=Mango&Cuisine=Mexican" + await request(app).get(baseURL + "/initDB") + const response = await request(app).get( + baseURL + "?CleanedIngredients=Mango&Cuisine=Mexican" ); expect(response.body.filters.Cuisine).to.eql("Mexican"); }); it("is the API fetching the recipe components", async function () { - const response = await request.get( - "/recipes?CleanedIngredients=Mango&Cuisine=Indian" + await request(app).get(baseURL + "/initDB") + const response = await request(app).get( + baseURL + "?CleanedIngredients=Mango&Cuisine=Indian" ); expect(response.text.includes("Cleaned-Ingredients")).true; }); it("is the API fetching the recipe components", async function () { - const response = await request.get( - "/recipes?CleanedIngredients=Mango&Cuisine=Indian" + await request(app).get(baseURL + "/initDB") + const response = await request(app).get( + baseURL + "?CleanedIngredients=Mango&Cuisine=Indian" ); expect(response.text.includes('"Cuisine":"Indian"')).true; }); it("is the API fetching the recipe components", async function () { - const response = await request.get( - "/recipes?CleanedIngredients=Mango&Cuisine=Indian" + await request(app).get(baseURL + "/initDB") + const response = await request(app).get( + baseURL + "?CleanedIngredients=Mango&Cuisine=Indian" ); expect(response.text.includes("TotalTimeInMins")).true; }); it("is the API fetching the recipe components", async function () { - const response = await request.get( - "/recipes?CleanedIngredients=Mango&Cuisine=Indian" + await request(app).get(baseURL + "/initDB") + const response = await request(app).get( + baseURL + "?CleanedIngredients=Mango&Cuisine=Indian" ); expect(response.text.includes("Diet-type")).true; }); it("is the API fetching the recipe components", async function () { - const response = await request.get( - "/recipes?CleanedIngredients=Mango&Cuisine=Indian" + await request(app).get(baseURL + "/initDB") + const response = await request(app).get( + baseURL + "?CleanedIngredients=Mango&Cuisine=Indian" ); expect(response.text.includes("Recipe-rating")).true; }); it("is the API fetching the recipe by name", async function () { - const response = await request.get( - "/recipes/getRecipeByName?recipeName=andhra" + await request(app).get(baseURL + "/initDB") + const response = await request(app).get( + baseURL + "/getRecipeByName?recipeName=andhra" ); expect(response.text.includes("Andhra")).true; }); }); + +describe("GET /cuisines", function() { + it("Should return a list of distinct cuisine types", async function() { + await request(app).get(baseURL + "/initDB"); + const response = await request(app).get(baseURL + "/cuisines"); + + expect(response.status).to.equal(200); + const resArray = JSON.parse(response.text) + expect(resArray.indexOf('Indian')).to.equal(resArray.lastIndexOf("Indian")) + expect(resArray.indexOf('American')).to.equal(resArray.lastIndexOf("American")) + }) +}) diff --git a/Code/backend/__tests__/testIngredients.js b/Code/backend/__tests__/testIngredients.js new file mode 100644 index 00000000..e0765c97 --- /dev/null +++ b/Code/backend/__tests__/testIngredients.js @@ -0,0 +1,17 @@ +import app from "../server"; +const baseURL = "/api/v1/recipes"; + +const request = require("supertest"); +const expect = require("chai").expect; + +describe("GET /callIngredients", function () { + it("Should retrieve all ingredients in the ingredients list", async function() { + await request(app).get(baseURL + "/initDB"); + const response = await request(app).get(baseURL + "/callIngredients") + console.log(response.text) + expect(response.status).to.equal(200) + expect(response.text.includes("Bread")).true + const resArray = JSON.parse(response.text) + expect(resArray.length).to.equal(4) + }) +}); \ No newline at end of file diff --git a/Code/backend/__tests__/testLoginPage.js b/Code/backend/__tests__/testLoginPage.js index 7629050d..c1ecfb56 100644 --- a/Code/backend/__tests__/testLoginPage.js +++ b/Code/backend/__tests__/testLoginPage.js @@ -1,28 +1,64 @@ /* MIT License Copyright (c) 2023 Pannaga Rao, Harshitha, Prathima, Karthik */ +import app from "../server"; +const baseURL = "/api/v1/recipes"; -const request = require("supertest")("http://localhost:5000/api/v1/recipes"); +const request = require("supertest"); const expect = require("chai").expect; -describe("Recipes API Tests", function () { +describe("Login API Tests", function () { // Login Tests describe("GET /login", function () { it("should log in successfully with correct credentials", async function () { - const response = await request.get("/login") + await request(app).get(baseURL + "/initDB") + const response = await request(app).get(baseURL + "/login") .query({ userName: "Test", password: "admin" }); expect(response.status).to.eql(200); }); + it("should fail to log in with nonexistent user", async function () { + await request(app).get(baseURL + "/initDB") + const response = await request(app).get(baseURL + "/login") + .query({ userName: "NotReal", password: "fake" }); + expect(response.status).to.eql(200); + const resJSON = JSON.parse(response.text); + expect(resJSON.success).false; + expect(resJSON.user).undefined; + }); + it("should fail to log in with incorrect password", async function () { + await request(app).get(baseURL + "/initDB") + const response = await request(app).get(baseURL + "/login") + .query({ userName: "Test", password: "fake" }); + expect(response.status).to.eql(200); + const resJSON = JSON.parse(response.text); + expect(resJSON.success).false; + expect(resJSON.user).undefined; + }); }); describe("GET /signup", function () { it("should not sign up for existing users", async function () { - const response = await request.get("/signup") + await request(app).get(baseURL + "/initDB") + const response = await request(app).post(baseURL + "/signup") .query({ userName: "Test", password: "admin" }); expect(response.status).not.to.eql(200); }); + it("should sign up for new users", async function () { + await request(app).get(baseURL + "/initDB") + const response = await request(app).post(baseURL + "/signup") + .query({ userName: "Test2", password: "admin2" }); + + expect(response.status).to.eql(200); + }); + it("should not sign up for users without a password", async function () { + await request(app).get(baseURL + "/initDB") + const response = await request(app).post(baseURL + "/signup") + .query({ userName: "Test3" }); + + expect(response.status).not.to.eql(200); + }); }); }); diff --git a/Code/backend/__tests__/testMealPlan.js b/Code/backend/__tests__/testMealPlan.js new file mode 100644 index 00000000..05cc5758 --- /dev/null +++ b/Code/backend/__tests__/testMealPlan.js @@ -0,0 +1,33 @@ +const { ObjectId } = require("mongodb"); +import app from "../server"; +const baseURL = "/api/v1/recipes"; + +const request = require("supertest") +const expect = require("chai").expect; + +describe("Meal Plan", function() { + describe("Put /mealPlan", function() { + it("Should correctly update the meal plan with new recipes", async function() { + await request(app).get(baseURL + "/initDB") + const response0 = await request(app).get(baseURL + "/getRecipeByName?recipeName=BLT"); + const res0JSON = JSON.parse(response0.text) + expect(response0.status).to.eql(200); + const response = await request(app).put(baseURL + "/mealPlan") + .send({ recipeID: res0JSON["recipes"][0]['_id'], userName: "Test", weekDay: "monday" }); + expect(response.status).to.eql(200); + const response2 = await request(app).get(baseURL + "/mealPlan?userName=Test") + expect(response2.text.includes("monday")).true + expect(response2.text.includes(res0JSON["recipes"][0]['_id'])).true + }); + it("Should remove a recipe from the meal plan", async function() { + await request(app).get(baseURL + "/initDB") + const response = await request(app).put(baseURL + "/mealPlan") + .send({ recipeID: "", userName: "Test", weekDay: "monday" }); + expect(response.status).to.eql(200); + const response2 = await request(app).get(baseURL + "/mealPlan?userName=Test") + const res2JSON = JSON.parse(response2.text) + expect(response2.text.includes("monday")).true + expect(res2JSON.monday === "").true + }) + }) +}) \ No newline at end of file diff --git a/Code/backend/__tests__/testRatings.js b/Code/backend/__tests__/testRatings.js new file mode 100644 index 00000000..f5030ab0 --- /dev/null +++ b/Code/backend/__tests__/testRatings.js @@ -0,0 +1,24 @@ +const { ObjectId } = require("mongodb"); +import app from "../server"; +const baseURL = "/api/v1/recipes"; + +const request = require("supertest"); +const expect = require("chai").expect; + +describe("Ratings", function() { + + describe("PATCH /rateRecipe", function() { + it("Should correctly update the rating to be new average", async function() { + await request(app).get(baseURL + "/initDB") + const response0 = await request(app).get(baseURL + "/getRecipeByName?recipeName=BLT"); + const res0JSON = JSON.parse(response0.text) + expect(response0.status).to.eql(200); + const response = await request(app).patch(baseURL + "/rateRecipe") + .send({ recipeID: res0JSON["recipes"][0]['_id'], rating: 3 }); + expect(response.status).to.eql(200); + const response2 = await request(app).get(baseURL + "/getRecipeByName?recipeName=BLT"); + const res2JSON = JSON.parse(response2.text) + expect(res2JSON["recipes"][0]['Recipe-rating'] === 4).true + }) + }) +}) \ No newline at end of file diff --git a/Code/backend/api/recipes.controller.js b/Code/backend/api/recipes.controller.js index 79a30bfd..75dbdf60 100644 --- a/Code/backend/api/recipes.controller.js +++ b/Code/backend/api/recipes.controller.js @@ -2,56 +2,71 @@ import RecipesDAO from "../dao/recipesDAO.js"; export default class RecipesController { static async apiAuthLogin(req, res) { - let filters = {} - filters.userName = req.query.userName - filters.password = req.query.password + let filters = {}; + filters.userName = req.query.userName; + filters.password = req.query.password; const { success, user } = await RecipesDAO.getUser({ - filters - }) + filters, + }); res.json({ success, user }); } static async apiAuthSignup(req, res) { if (req.body) { - let data = {} - data.userName = req.body.userName - data.password = req.body.password + let data = {}; + data.userName = req.body.userName; + data.password = req.body.password; const { success, user } = await RecipesDAO.addUser({ - data - }) + data, + }); res.json({ success, user }); } } static async apiGetBookmarks(req, res) { if (req.query.userName) { - const bookmarks = await RecipesDAO.getBookmarks(req.query.userName) - console.log(bookmarks) + const bookmarks = await RecipesDAO.getBookmarks(req.query.userName); res.json({ bookmarks }); } else { - res.json("Username not given") + res.json("Username not given"); } } static async apiPostRecipeToProfile(req, res) { - if (req.body) { - const { userName, recipe } = req.body; - try { - const response = RecipesDAO.addRecipeToProfile(userName, recipe) - res.json(response) - } catch (e) { - console.log(`error: ${e}`) - } - } else { - res.json({ success: false }) + const { userName, recipe } = req.body; + + if (!userName || !recipe) { + return res + .status(400) + .json({ success: false, message: "Missing userName or recipe" }); } + try { + const result = await RecipesDAO.addRecipeToProfile(userName, recipe); + res.json(result); + } catch (e) { + console.error("Error in apiPostRecipeToProfile:", e); + res.status(500).json({ + success: false, + message: "Internal server error", + error: e.message, + }); + } + } + + static async apiRemoveRecipeFromProfile(req, res) { + const { userName, recipeId } = req.body; + try { + const result = await RecipesDAO.removeBookmark(userName, recipeId); + res.json(result); + } catch (e) { + res.status(500).json({ error: e }); + } } - + static async apiGetRecipeByName(req, res) { let filters = {}; //Checking the query to find the required results - console.log(req.query) if (req.query.recipeName) { filters.recipeName = req.query.recipeName; } @@ -61,7 +76,7 @@ export default class RecipesController { }); let response = { - recipes: recipesList + recipes: recipesList, }; res.json(response); } @@ -112,7 +127,17 @@ export default class RecipesController { try { let response = await RecipesDAO.addRecipe(req.body); res.json(response); - } catch(e) { + } catch (e) { + console.log(`api, ${e}`); + res.status(500).json({ error: e }); + } + } + + static async apiPatchRecipeRating(req, res, next) { + try { + let response = await RecipesDAO.rateRecipe(req.body); + res.json(response); + } catch (e) { console.log(`api, ${e}`); res.status(500).json({ error: e }); } @@ -122,10 +147,39 @@ export default class RecipesController { try { let ingredients = await RecipesDAO.getIngredients(); res.json(ingredients); - } catch(e) { + } catch (e) { + res.status(500).json({ error: e }); + } + } + + static async apiAddtoPlan(req, res, next) { + try { + let response = await RecipesDAO.addRecipeToMealPlan( + req.body.userName, + req.body.recipeID, + req.body.weekDay + ); + res.json(response); + } catch (e) { res.status(500).json({ error: e }); } } -} + static async apiGetMealPlan(req, res, next) { + try { + let response = await RecipesDAO.getMealPlan(req.query.userName); + res.json(response); + } catch (e) { + res.status(500).json({ error: e }); + } + } + static async apiInitDB(req, res) { + try { + let response = await RecipesDAO.initDB(); + res.json(response); + } catch (e) { + res.status(500).json({ error: e }); + } + } +} diff --git a/Code/backend/api/recipes.route.js b/Code/backend/api/recipes.route.js index 3c3d7134..ae74e1f0 100644 --- a/Code/backend/api/recipes.route.js +++ b/Code/backend/api/recipes.route.js @@ -10,7 +10,7 @@ router.route("/cuisines").get(RecipesCtrl.apiGetRecipeCuisines); router.route("/addRecipe").post(RecipesCtrl.apiPostRecipe); -router.route('/callIngredients').get(RecipesCtrl.apiGetIngredients); +router.route("/callIngredients").get(RecipesCtrl.apiGetIngredients); router.route("/signup").post(RecipesCtrl.apiAuthSignup); @@ -22,4 +22,12 @@ router.route("/addRecipeToProfile").post(RecipesCtrl.apiPostRecipeToProfile); router.route("/getRecipeByName").get(RecipesCtrl.apiGetRecipeByName); +router.route("/rateRecipe").patch(RecipesCtrl.apiPatchRecipeRating); + +router.route("/removeBookmark").post(RecipesCtrl.apiRemoveRecipeFromProfile); + +router.route("/mealPlan").put(RecipesCtrl.apiAddtoPlan).get(RecipesCtrl.apiGetMealPlan) + +router.route("/initDB").get(RecipesCtrl.apiInitDB) + export default router; diff --git a/Code/backend/dao/recipesDAO.js b/Code/backend/dao/recipesDAO.js index 50b7a208..15f2f9c9 100644 --- a/Code/backend/dao/recipesDAO.js +++ b/Code/backend/dao/recipesDAO.js @@ -1,4 +1,4 @@ -import mongodb from "mongodb"; +import * as mongodb from "mongodb"; import nodemailer from "nodemailer"; import password from "./mail_param.js"; const pass = password.password; @@ -16,8 +16,11 @@ export default class RecipesDAO { } try { recipes = await conn.db(process.env.RECIPES_NS).collection("recipe"); - ingredients = await conn.db(process.env.RECIPES_NS).collection("ingredient_list"); + ingredients = await conn + .db(process.env.RECIPES_NS) + .collection("ingredient_list"); users = await conn.db(process.env.RECIPES_NS).collection("user"); + //console.log("db started") } catch (e) { console.error( `Unable to establish a collection handle in recipesDAO: ${e}` @@ -29,17 +32,17 @@ export default class RecipesDAO { let query; let cursor; let user; - query = { "userName": filters.userName } + query = { userName: filters.userName }; if (filters) { cursor = await users.findOne(query); if (cursor.userName) { if (cursor.password == filters.password) { - return { success: true, user: cursor } + return { success: true, user: cursor }; } else { - return { success: false } + return { success: false }; } } else { - return { success: false } + return { success: false }; } } } @@ -48,36 +51,33 @@ export default class RecipesDAO { let query; let cursor; let user; - query = { "userName": data.userName } - console.log(query) + query = { userName: data.userName }; if (data) { cursor = await users.findOne(query); - console.log(cursor) - if (cursor!==null) { - return {success: false} + if (cursor !== null) { + return { success: false }; } else { - const res = await users.insertOne(data) - return { success: true } + const res = await users.insertOne(data); + return { success: true }; } } } - + //function to get bookmarks static async getBookmarks(userName) { let query; let cursor; let user; - query = { "userName": userName } - console.log(query) + query = { userName: userName }; try { cursor = await users.findOne(query); if (cursor.userName) { return cursor.bookmarks; } else { - return { bookmarks: [] } + return { bookmarks: [] }; } } catch (e) { - console.log(`error: ${e}`) + console.log(`error: ${e}`); } } @@ -87,23 +87,25 @@ export default class RecipesDAO { if (filters) { if ("recipeName" in filters) { const words = filters["recipeName"].split(" "); - const regexPattern = words.map(word => `(?=.*\\b${word}\\b)`).join(''); + const regexPattern = words + .map((word) => `(?=.*\\b${word}\\b)`) + .join(""); const regex = new RegExp(regexPattern, "i"); - query = { "TranslatedRecipeName": { $regex: regex } }; + query = { TranslatedRecipeName: { $regex: regex } }; // query["Cuisine"] = "Indian"; } let recipesList; try { recipesList = await recipes .find(query) - .collation({ locale: "en", strength: 2 }).toArray(); - return { recipesList } + .collation({ locale: "en", strength: 2 }) + .toArray(); + return { recipesList }; } catch (e) { console.error(`Unable to issue find command, ${e}`); return { recipesList: [], totalNumRecipess: 0 }; } } - } //Function to get the Recipe List @@ -121,14 +123,10 @@ export default class RecipesDAO { const str1 = filters["CleanedIngredients"][i]; str += "(?=.*" + str1 + ")"; } - console.log(str); query = { "Cleaned-Ingredients": { $regex: str } }; query["Cuisine"] = filters["Cuisine"]; - console.log(query); var email = filters["Email"]; var flagger = filters["Flag"]; - console.log(email); - console.log(flagger); } } @@ -208,15 +206,15 @@ export default class RecipesDAO { // Function to add a recipe static async addRecipe(recipe) { - console.log("Inside addRecipe"); - console.log(recipe); let inputRecipe = {}; inputRecipe["TranslatedRecipeName"] = recipe["recipeName"]; inputRecipe["TotalTimeInMins"] = recipe["cookingTime"]; inputRecipe["Diet-type"] = recipe["dietType"]; inputRecipe["Recipe-rating"] = recipe["recipeRating"]; + inputRecipe["Times-rated"] = 1; inputRecipe["Cuisine"] = recipe["cuisine"]; inputRecipe["image-url"] = recipe["imageURL"]; + inputRecipe["ImageFile"]=recipe["imageFile"] inputRecipe["URL"] = recipe["recipeURL"]; inputRecipe["TranslatedInstructions"] = recipe["instructions"]; var ingredients = ""; @@ -232,42 +230,180 @@ export default class RecipesDAO { } inputRecipe["Restaurant"] = restaurants; inputRecipe["Restaurant-Location"] = locations; - console.log("Input Recipe"); - console.log(inputRecipe); let response = {}; - try{ + try { response = await recipes.insertOne(inputRecipe); return response; - } catch(e){ + } catch (e) { console.error(`Unable to add recipe, ${e}`); return response; } } - //function to add recipe to user profile - static async addRecipeToProfile(userName, recipe) { - let response; - console.log(userName) - try { - response = await users.updateOne( - { userName: userName }, - { $push: { bookmarks: recipe } } - ) - console.log(response) - return response; - } catch (e) { - console.log(`Unable to add recipe, ${e}`) + static async rateRecipe(ratingBody) { + let r = await recipes + .find({ _id: new ObjectId(ratingBody.recipeID) }) + .collation({ locale: "en", strength: 2 }) + .toArray(); + let recipe = r[0]; + let timesRated = recipe["Times-rated"] ? Number(recipe["Times-rated"]) : 1; + let newRating = Number(recipe["Recipe-rating"]) * timesRated; + newRating += ratingBody.rating; + timesRated++; + newRating /= timesRated; + await recipes.updateOne( + { _id: new ObjectId(ratingBody.recipeID) }, + { $set: { "Times-rated": timesRated, "Recipe-rating": newRating } } + ); + } + + //function to add recipe to user profile + static async addRecipeToProfile(userName, recipe) { + try { + //console.log(`Attempting to add recipe to profile for user: ${userName}`); + + // First, check if the recipe already exists in the user's bookmarks + const user = await users.findOne({ userName: userName }); + if (!user) { + return { success: false, message: "User not found" }; + } + + const existingBookmark = user.bookmarks + ? user.bookmarks.find( + (bookmark) => bookmark._id.toString() === recipe._id.toString() + ) + : null; + if (existingBookmark) { + console.log("Recipe already bookmarked"); + return { success: false, message: "Recipe already bookmarked" }; + } + + // If the recipe doesn't exist, add it to the bookmarks + const updateResult = await users.updateOne( + { userName: userName }, + { $addToSet: { bookmarks: recipe } } + ); + + //console.log("Update result:", updateResult); + + if (updateResult.modifiedCount === 0) { + console.log("No changes made to bookmarks"); + return { success: false, message: "No changes made to bookmarks" }; + } + + //console.log("Recipe added to bookmarks successfully"); + return { + success: true, + message: "Recipe added to bookmarks successfully", + }; + } catch (e) { + console.error(`Error in addRecipeToProfile: ${e}`); + throw e; + } + } + + static async removeBookmark(userName, recipeId) { + try { + const updateResponse = await users.updateOne( + { userName: userName }, + { $pull: { bookmarks: { _id: recipeId } } } + ); + + if (updateResponse.modifiedCount === 1) { + return { success: true, message: "Bookmark removed successfully" }; + } else if (updateResponse.matchedCount === 0) { + return { success: false, message: "User not found" }; + } else { + return { + success: false, + message: "Bookmark not found or already removed", + }; + } + } catch (e) { + console.error(`DAO: Unable to remove bookmark:`, e); + throw e; + } + } + + static async addRecipeToMealPlan(userName, recipeID, weekDay) { + let response; + try { + if(!recipeID) { + throw new Error("recipe id not defined") + } + let updateBody = JSON.parse( + '{ "meal-plan.' + weekDay + '": "' + recipeID + '" }' + ); + response = await users.updateOne( + { userName: userName }, + { $set: updateBody } + ); + return response; + } catch (e) { + console.log(`Unable to add recipe to meal plan, ${e}`); + } + } + + static async getMealPlan(userName) { + let cursor; + let mealPlanResponse = { + sunday: "", + monday: "", + tuesday: "", + wednesday: "", + thursday: "", + friday: "", + saturday: "", + }; + try { + cursor = await users.findOne({ userName: userName }); + if (cursor.userName) { + let plan = cursor['meal-plan'] ? cursor['meal-plan'] : {} + for(const day in plan) { + if(plan[day] != "") { + let recipe = await recipes.findOne({_id: new ObjectId(plan[day])}) + let dayPlan = {} + dayPlan[day] = recipe + mealPlanResponse = {...mealPlanResponse, ...dayPlan} + } + } + return mealPlanResponse + } else { + throw new Error(`Cannot find user with name ${userName}`); } + } catch (e) { + console.log(`error: ${e}`); } - - static async getIngredients(){ + } + + static async getIngredients() { let response = {}; - try{ - response = await ingredients.distinct('item_name'); + try { + response = await ingredients.distinct("item_name"); return response; - }catch(e){ + } catch (e) { console.error(`Unable to get ingredients, ${e}`); return response; } - } + } + + static async initDB() { + if(recipes) { + return {success: true} + } + try { + await mongodb.MongoClient.connect(process.env.RECIPES_DB_URI, { + maxPoolSize: 50, + wtimeoutMS: 2500, + useNewUrlParser: true, + }).then(async (client) => { + await this.injectDB(client) + return {success: true} + }) + + } catch (e) { + console.log(e); + return {success: false} + } + } } diff --git a/Code/backend/index.js b/Code/backend/index.js index 0ca44ac2..03a1517a 100644 --- a/Code/backend/index.js +++ b/Code/backend/index.js @@ -1,5 +1,5 @@ import app from "./server.js"; -import mongodb from "mongodb"; +import * as mongodb from "mongodb"; import dotenv from "dotenv"; import recipesDAO from "./dao/recipesDAO.js"; dotenv.config(); diff --git a/Code/backend/package.json b/Code/backend/package.json index 20c53293..c178b394 100644 --- a/Code/backend/package.json +++ b/Code/backend/package.json @@ -5,7 +5,8 @@ "main": "index.js", "type": "module", "scripts": { - "test": "set CI=true && jest", + "test": "set CI=true && jest --setupFiles dotenv/config", + "test-cov": "set CI=true && jest --setupFiles dotenv/config --coverage", "lint": "eslint 'dao/**.js'", "format": "prettier --check ./dao", "format:fix": "prettier --write ./dao" @@ -23,7 +24,11 @@ "nodemon": "^2.0.13" }, "devDependencies": { + "@babel/core": "^7.26.0", + "@babel/preset-env": "^7.26.0", + "@shelf/jest-mongodb": "^4.3.2", "babel-eslint": "^10.1.0", + "babel-jest": "^29.7.0", "eslint": "^7.32.0", "eslint-plugin-react": "^7.26.1", "jest": "^27.3.1", @@ -32,12 +37,14 @@ }, "jest": { "collectCoverageFrom": [ - "/__tests__/*.{js,jsx}" + "api/*.js", + "dao/*.js" ], "coveragePathIgnorePatterns": [ "/node_modules/", "package.json", "package-lock.json" - ] + ], + "globalSetup": "./test_resources/setup.js" } } diff --git a/Code/backend/test_resources/setup.js b/Code/backend/test_resources/setup.js new file mode 100644 index 00000000..2192e41d --- /dev/null +++ b/Code/backend/test_resources/setup.js @@ -0,0 +1,93 @@ +import { TextEncoder, TextDecoder } from 'util'; +import mongodb from "mongodb" +const MongoClient = mongodb.MongoClient; +import dotenv from 'dotenv' +dotenv.config() + +export default function (globalConfig, projectConfig) { + + console.log('Setting up for tests') + const users = {"users": [ + { + "userName": "Test", + "password": "admin", + }, + { + "userName": "TestB", + "password": "adminB", + "bookmarks": [ + { + "TranslatedRecipeName": "Test_Recipe_B", + "_id": 85 + } + ] + } + ]} + const recipes = { + "recipes": [ + { + "TranslatedRecipeName": "BLT", + "TotalTimeInMins": "15", + "Diet-type": "", + "Recipe-rating": 5, + "Times-rated": 1, + "Cuisine": "American", + "image-url": "", + "URL": "", + "TranslatedInstructions": "Cook sandwich", + "Cleaned-Ingredients": "Bacon%Lettuce%Tomato%Bread%", + "Restaurant": "", + "Restaurant-Location": "" + }, + { + "TranslatedRecipeName": "Andhra", + "TotalTimeInMins": "20", + "Diet-type": "Vegetarian", + "Recipe-rating": 5, + "Times-rated": 1, + "Cuisine": "Indian", + "image-url": "", + "URL": "", + "TranslatedInstructions": "Cook the food", + "Cleaned-Ingredients": "Mango%Rice%", + "Restaurant": "", + "Restaurant-Location": "" + }, + { + "TranslatedRecipeName": "AndhraTwo", + "TotalTimeInMins": "20", + "Diet-type": "Vegetarian", + "Recipe-rating": 5, + "Times-rated": 1, + "Cuisine": "Indian", + "image-url": "", + "URL": "", + "TranslatedInstructions": "Cook the food", + "Cleaned-Ingredients": "Bread%", + "Restaurant": "", + "Restaurant-Location": "" + } + ] + } + const ingredients = {"ingredients": [ + {"item_name": "Tomato"}, {"item_name": "Bread"}, {"item_name": "Lettuce"}, {"item_name": "Bacon"}, + ]} + Object.assign(global, { TextDecoder, TextEncoder }); + const uri = process.env.RECIPES_DB_URI; + var mongoClient = MongoClient.connect(uri, { + useNewUrlParser: true, + maxPoolSize: 50, + wtimeoutMS: 2500, + }).then(async (client) => { + const recipeCollection = client.db(process.env.RECIPES_NS).collection("recipe")//.then(async (recipeCollection) => { + await recipeCollection.deleteMany({}) + await recipeCollection.insertMany(recipes.recipes) + const userCollection = client.db(process.env.RECIPES_NS).collection("user")//.then(async (recipeCollection) => { + await userCollection.deleteMany({}) + await userCollection.insertMany(users.users) + const ingredientCollection = client.db(process.env.RECIPES_NS).collection("ingredient_list")//.then(async (recipeCollection) => { + await ingredientCollection.deleteMany({}) + await ingredientCollection.insertMany(ingredients.ingredients) + client.close() + }); +}; \ No newline at end of file diff --git a/Code/backend/test_resources/teardown.js b/Code/backend/test_resources/teardown.js new file mode 100644 index 00000000..6ba2e1d8 --- /dev/null +++ b/Code/backend/test_resources/teardown.js @@ -0,0 +1,20 @@ +import mongodb from "mongodb" +const MongoClient = mongodb.MongoClient; +import dotenv from 'dotenv' +dotenv.config() + +export default async function (globalConfig, projectConfig) { + const uri = process.env.RECIPES_DB_URI; + console.log(uri) + var mongoClient = MongoClient.connect(uri, { + useNewUrlParser: true, + maxPoolSize: 50, + wtimeoutMS: 2500, + }).then(async (client) => { + const recipeCollection = await client.db(process.env.RECIPES_NS).collection("recipe")//.then(async (recipeCollection) => { + await recipeCollection.deleteMany({}) + const userCollection = await client.db(process.env.RECIPES_NS).collection("user")//.then(async (recipeCollection) => { + await userCollection.deleteMany({}) + client.close() + }); +}; \ No newline at end of file diff --git a/Code/backend/test_resources/testRecipes.json b/Code/backend/test_resources/testRecipes.json new file mode 100644 index 00000000..e9a76269 --- /dev/null +++ b/Code/backend/test_resources/testRecipes.json @@ -0,0 +1,32 @@ +{ + "recipes": [ + { + "TranslatedRecipeName": "BLT", + "TotalTimeInMins": "15", + "Diet-type": "", + "Recipe-rating": 5, + "Times-rated": 1, + "Cuisine": "", + "image-url": "", + "URL": "", + "TranslatedInstructions": "Cook sandwich", + "Cleaned-Ingredients": "Bacon%Lettuce%Tomato%Bread%", + "Restaurant": "", + "Restaurant-Location": "" + }, + { + "TranslatedRecipeName": "Andhra", + "TotalTimeInMins": "20", + "Diet-type": "Vegetarian", + "Recipe-rating": 5, + "Times-rated": 1, + "Cuisine": "Indian", + "image-url": "", + "URL": "", + "TranslatedInstructions": "Cook the food", + "Cleaned-Ingredients": "Mango%Rice%", + "Restaurant": "", + "Restaurant-Location": "" + } + ] +} \ No newline at end of file diff --git a/Code/backend/test_resources/testUsers.json b/Code/backend/test_resources/testUsers.json new file mode 100644 index 00000000..c33780e2 --- /dev/null +++ b/Code/backend/test_resources/testUsers.json @@ -0,0 +1,8 @@ +{ + "users": [ + { + "userName": "Test", + "password": "admin" + } + ] +} \ No newline at end of file diff --git a/Code/frontend/src/App.js b/Code/frontend/src/App.js index 08ea45f8..b21bca65 100644 --- a/Code/frontend/src/App.js +++ b/Code/frontend/src/App.js @@ -1,4 +1,4 @@ -// +// import Form from "./components/Form.js"; import Header from "./components/Header"; import recipeDB from "./apis/recipeDB"; @@ -11,12 +11,17 @@ import Nav from "./components/Navbar.js"; import SearchByRecipe from "./components/SearchByRecipe.js"; import Login from "./components/Login.js"; import UserProfile from "./components/UserProfile.js"; +import LandingPage from "./components/LandingPage.js"; +import BookMarksRecipeList from "./components/BookMarksRecipeList"; // Import BookMarksRecipeList +import UserMealPlan from "./components/UserMealPlan.js"; // Main component of the project class App extends Component { // constructor for the App Component - constructor() { - super(); + constructor(props) { + super(props); + + // this.handleRemoveBookmark = this.handleRemoveBookmark.bind(this); this.state = { cuisine: "", @@ -24,81 +29,94 @@ class App extends Component { ingredients: new Set(), recipeList: [], recipeByNameList: [], + searchName: "", email: "", flag: false, isLoading: false, isLoggedIn: false, isProfileView: false, - userData: {} + isMealPlanView: false, + userData: { + bookmarks: [], + }, }; } - handleBookMarks = ()=> { + handleBookMarks = () => { this.setState({ - isProfileView: true - }) - } + isProfileView: true, + isMealPlanView: false + }); + }; - handleProfileView = ()=> { + handleMealPlan = () => { this.setState({ - isProfileView: false + isProfileView: false, + isMealPlanView: true }) } - handleSignup = async (userName, password)=> { + handleProfileView = () => { + this.setState({ + isProfileView: false, + isMealPlanView: false, + }); + }; + + handleSignup = async (userName, password) => { try { const response = await recipeDB.post("/recipes/signup", { - userName, - password + userName, + password, }); - console.log(response.data) + console.log(response.data); if (response.data.success) { - alert("Successfully Signed up!") + alert("Successfully Signed up!"); this.setState({ isLoggedIn: true, - userData: response.data.user - }) - localStorage.setItem("userName", response.data.user.userName) - console.log(response.data.user) + userData: response.data.user, + }); + localStorage.setItem("userName", response.data.user.userName); + console.log(response.data.user); } else { - alert("User already exists") + alert("User already exists"); } } catch (err) { console.log(err); } - } + }; handleLogin = async (userName, password) => { try { const response = await recipeDB.get("/recipes/login", { params: { userName, - password + password, }, }); - console.log(response.data) + console.log(response.data); if (response.data.success) { this.setState({ isLoggedIn: true, - userData: response.data.user - }) - localStorage.setItem("userName", response.data.user.userName) - console.log(response.data.user) - alert("Successfully logged in!") + userData: response.data.user, + }); + localStorage.setItem("userName", response.data.user.userName); + console.log(response.data.user); + alert("Successfully logged in!"); } else { - console.log("Credentials are incorrect") + console.log("Credentials are incorrect"); } } catch (err) { console.log(err); } - } + }; // Function to get the user input from the Form component on Submit action handleSubmit = async (formDict) => { this.setState({ - isLoading: true - }) - console.log(formDict) + isLoading: true, + }); + console.log(formDict); this.setState({ // cuisine: cuisineInput, //NoIngredients: noIngredientsInput, @@ -118,20 +136,23 @@ class App extends Component { handleRecipesByName = (recipeName) => { this.setState({ - isLoading: true - }) - recipeDB.get("/recipes/getRecipeByName", { - params: { - recipeName: recipeName - } - }).then(res => { - console.log(res.data); - this.setState({ - recipeByNameList: res.data.recipes, - isLoading: false + isLoading: true, + searchName: recipeName, + }); + recipeDB + .get("/recipes/getRecipeByName", { + params: { + recipeName: recipeName, + }, }) - }) - } + .then((res) => { + console.log(res.data); + this.setState({ + recipeByNameList: res.data.recipes, + isLoading: false, + }); + }); + }; getRecipeDetails = async (ingredient, cuis, mail, flag) => { try { @@ -145,29 +166,101 @@ class App extends Component { }); this.setState({ recipeList: response.data.recipes, - isLoading: false + isLoading: false, }); } catch (err) { console.log(err); } }; - handleLogout = ()=> { - console.log("logged out") + handleLogout = () => { + console.log("logged out"); this.setState({ - isLoggedIn: false - }) - } + isLoggedIn: false, + showLogin: false, + userData: {}, + }); + }; + + handleBookMarks = async () => { + // Fetch bookmarks when navigating to profile view + const userName = localStorage.getItem("userName"); + try { + const response = await recipeDB.get("/recipes/getBookmarks", { + params: { userName }, + }); + this.setState({ + isProfileView: true, + userData: { + ...this.state.userData, + bookmarks: response.data.recipes, // Set fetched bookmarks to state + }, + }); + } catch (err) { + console.error("Error fetching bookmarks", err); + } + }; + + handleRemoveBookmark = async (recipeId) => { + const userName = localStorage.getItem("userName"); + + try { + const response = await recipeDB.post("/recipes/removeBookmark", { + userName, + recipeId, + }); + + if (response.data.success) { + this.setState((prevState) => ({ + userData: { + ...prevState.userData, + bookmarks: prevState.userData.bookmarks.filter( + (recipe) => (recipe.id || recipe._id) !== recipeId // Remove based on recipeId + ), + }, + })); + } else { + throw new Error(response.data.message || "Failed to remove bookmark"); + } + } catch (error) { + console.error("Failed to remove bookmark:", error); + } + }; + + handleProfileView = () => { + this.setState({ + isProfileView: false, + isMealPlanView: false + }); + }; render() { return (
{props.user.userName}
+