diff --git a/apps/api/src/main/java/com/fridgetoplate/controller/RecipeController.java b/apps/api/src/main/java/com/fridgetoplate/controller/RecipeController.java index b524fe0b..6a1b6c97 100644 --- a/apps/api/src/main/java/com/fridgetoplate/controller/RecipeController.java +++ b/apps/api/src/main/java/com/fridgetoplate/controller/RecipeController.java @@ -36,9 +36,9 @@ public List findRecipesByRecipename(@PathVariable(value = " return recipeService.getRecipesByRecipeName(recipename); } - @PutMapping("/update-ratingAndViews/{id}") - public RecipeFrontendModel updateRatingAndViews(@PathVariable(value = "id") String id, @RequestBody RecipeFrontendModel recipe){ - return recipeService.updateRatingAndViews(recipe); + @PutMapping("/increaseViews/{id}") + public RecipeFrontendModel updateRatingAndViews(@PathVariable(value = "id") String id){ + return recipeService.increaseViews(id); } @PutMapping("/{id}") diff --git a/apps/api/src/main/java/com/fridgetoplate/model/NotificationModel.java b/apps/api/src/main/java/com/fridgetoplate/model/NotificationModel.java index 08104d0a..c9360304 100644 --- a/apps/api/src/main/java/com/fridgetoplate/model/NotificationModel.java +++ b/apps/api/src/main/java/com/fridgetoplate/model/NotificationModel.java @@ -7,8 +7,8 @@ import lombok.Data; -@Data @DynamoDBTable(tableName = "notifications") +@Data public class NotificationModel { String notificationId; @@ -51,7 +51,7 @@ public String getType(){ } @DynamoDBAttribute(attributeName = "metadata") - public String getMetaData(){ + public String getMetadata(){ return metadata; } } diff --git a/apps/api/src/main/java/com/fridgetoplate/repository/NotificationsRepository.java b/apps/api/src/main/java/com/fridgetoplate/repository/NotificationsRepository.java index 47cadc1e..72ce8f88 100644 --- a/apps/api/src/main/java/com/fridgetoplate/repository/NotificationsRepository.java +++ b/apps/api/src/main/java/com/fridgetoplate/repository/NotificationsRepository.java @@ -1,7 +1,5 @@ package com.fridgetoplate.repository; -import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; @@ -9,11 +7,7 @@ import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper; import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBQueryExpression; -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBScanExpression; -import com.amazonaws.services.dynamodbv2.datamodeling.PaginatedScanList; import com.amazonaws.services.dynamodbv2.model.AttributeValue; -import com.fridgetoplate.frontendmodels.NotificationsResponseModel; -import com.fridgetoplate.model.IngredientModel; import com.fridgetoplate.model.NotificationModel; import graphql.com.google.common.collect.ImmutableMap; @@ -37,6 +31,10 @@ public List findAllByUser(String userId){ return dynamoDBMapper.query(NotificationModel.class, query); } + public void delete(NotificationModel notification){ + dynamoDBMapper.delete(notification); + } + public void deleteAll(List notifications){ dynamoDBMapper.batchDelete(notifications); } diff --git a/apps/api/src/main/java/com/fridgetoplate/repository/RecipeRepository.java b/apps/api/src/main/java/com/fridgetoplate/repository/RecipeRepository.java index d9af4dc1..7c6defc3 100644 --- a/apps/api/src/main/java/com/fridgetoplate/repository/RecipeRepository.java +++ b/apps/api/src/main/java/com/fridgetoplate/repository/RecipeRepository.java @@ -2,25 +2,16 @@ package com.fridgetoplate.repository; import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper; import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBQueryExpression; -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBSaveExpression; import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBScanExpression; -import com.amazonaws.services.dynamodbv2.datamodeling.PaginatedScanList; import com.amazonaws.services.dynamodbv2.model.AttributeValue; import com.amazonaws.services.dynamodbv2.model.ComparisonOperator; import com.amazonaws.services.dynamodbv2.model.Condition; -import com.amazonaws.services.dynamodbv2.model.ExpectedAttributeValue; -import com.fridgetoplate.frontendmodels.RecipeFrontendModel; -import com.fridgetoplate.frontendmodels.RecipePreferencesFrontendModel; import com.fridgetoplate.interfaces.Explore; -import com.fridgetoplate.interfaces.RecipeDesc; -import com.fridgetoplate.model.Ingredient; import com.fridgetoplate.model.IngredientModel; import com.fridgetoplate.model.RecipeModel; -import com.fridgetoplate.model.Review; import graphql.com.google.common.collect.ImmutableMap; -import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -165,5 +156,4 @@ public List filterSearch(Explore explore){ } - } diff --git a/apps/api/src/main/java/com/fridgetoplate/service/NotificationService.java b/apps/api/src/main/java/com/fridgetoplate/service/NotificationService.java index c5f6eaea..055fc1fd 100644 --- a/apps/api/src/main/java/com/fridgetoplate/service/NotificationService.java +++ b/apps/api/src/main/java/com/fridgetoplate/service/NotificationService.java @@ -13,9 +13,7 @@ import com.fridgetoplate.interfaces.RecipeDesc; import com.fridgetoplate.model.NotificationModel; import com.fridgetoplate.repository.NotificationsRepository; -import com.fridgetoplate.repository.ProfileRepository; import com.fridgetoplate.model.ProfileModel; -import com.fridgetoplate.utils.NotificationsUtils; @Service public class NotificationService { @@ -28,8 +26,6 @@ public class NotificationService { @Autowired private ExploreService exploreService; - private NotificationsUtils utils = new NotificationsUtils(); - Random random = new Random(); public NotificationModel save(NotificationModel notification){ @@ -71,19 +67,18 @@ public String clearAllNotificationOfType(String userId, String type){ List notifications = notificationsRepository.findAllByUser(userId); - List deletableNotifications = new ArrayList<>(); for (NotificationModel notificationModel : notifications) { if (notificationModel.getType().equals(type)) { - deletableNotifications.add(notificationModel); + System.out.println(notificationModel.toString()); + notificationsRepository.delete(notificationModel); } } - notificationsRepository.deleteAll(deletableNotifications); return "Successfully cleared all " + type + " notifications"; } - @Scheduled(cron = "0 0 8 * * ?") + @Scheduled(cron = "0 30 6 * * *") public void breakfastNotificationsPush(){ random.setSeed(System.currentTimeMillis()); @@ -92,7 +87,7 @@ public void breakfastNotificationsPush(){ //1. Get random users for(; selectedUsers.size() < allUsers.size() * 0.6 ;){ - int index = random.nextInt(0, allUsers.size()); + int index = random.nextInt(allUsers.size()); ProfileModel currentProfile = allUsers.get(index); if(!selectedUsers.contains(currentProfile)){ @@ -110,7 +105,7 @@ public void breakfastNotificationsPush(){ // 3. Create notifications for(int i = 0; i < selectedUsers.size(); i++){ - int index = random.nextInt(0, allUsers.size()); + int index = random.nextInt(allUsers.size()); RecipeDesc currRecipeDesc = recipes.get(index); ProfileModel currentUser = selectedUsers.get(i); @@ -120,15 +115,15 @@ public void breakfastNotificationsPush(){ newNotification.setUserId(currentUser.getUsername()); newNotification.setNotificationPic(currRecipeDesc.getRecipeImage()); newNotification.setType("recommendation"); - newNotification.setTitle("We recommend " + currRecipeDesc.getRecipeImage() + " for breakfast today"); + newNotification.setTitle("We recommend " + currRecipeDesc.getName() + " for breakfast today"); newNotification.setBody("Good time to try a new breakfast recipe! Explore our collection and find something delicious to kickstart your day."); + newNotification.setMetadata("/recipe/" + currRecipeDesc.getRecipeId()); - System.out.println(newNotification.toString()); this.save(newNotification); } } - @Scheduled(cron = "0 0 12 * * ?") + @Scheduled(cron = "0 0 12 * * *") public void lunchtimeNotificationsPush() { random.setSeed(System.currentTimeMillis()); @@ -137,7 +132,7 @@ public void lunchtimeNotificationsPush() { //1. Get random users for(; selectedUsers.size() < allUsers.size() * 0.6 ;){ - int index = random.nextInt(0, allUsers.size()); + int index = random.nextInt(allUsers.size()); ProfileModel currentProfile = allUsers.get(index); if(!selectedUsers.contains(currentProfile)){ @@ -155,7 +150,7 @@ public void lunchtimeNotificationsPush() { // 3. Create notifications for(int i = 0; i < selectedUsers.size(); i++){ - int index = random.nextInt(0, allUsers.size()); + int index = random.nextInt(allUsers.size()); RecipeDesc currRecipeDesc = recipes.get(index); ProfileModel currentUser = selectedUsers.get(i); @@ -165,15 +160,15 @@ public void lunchtimeNotificationsPush() { newNotification.setUserId(currentUser.getUsername()); newNotification.setNotificationPic(currRecipeDesc.getRecipeImage()); newNotification.setType("recommendation"); - newNotification.setTitle("Have a delicious " + currRecipeDesc.getRecipeImage() + " for lunch today"); + newNotification.setTitle("Have a delicious " + currRecipeDesc.getName() + " for lunch today"); newNotification.setBody("Lunch hour is approaching! Discover a tasty lunch recipe from our selection and enjoy a flavorful midday meal."); + newNotification.setMetadata("/recipe/" + currRecipeDesc.getRecipeId()); - System.out.println(newNotification.toString()); this.save(newNotification); } } - @Scheduled(cron = "0 16 * * * ?") + @Scheduled(cron = "0 0 17 * * *") public void dinnertimeNotificationsPush() { random.setSeed(System.currentTimeMillis()); @@ -182,7 +177,7 @@ public void dinnertimeNotificationsPush() { //1. Get random users for(; selectedUsers.size() < allUsers.size() * 0.6 ;){ - int index = random.nextInt(0, allUsers.size()); + int index = random.nextInt(allUsers.size()); ProfileModel currentProfile = allUsers.get(index); if(!selectedUsers.contains(currentProfile)){ @@ -200,7 +195,7 @@ public void dinnertimeNotificationsPush() { // 3. Create notifications for(int i = 0; i < selectedUsers.size(); i++){ - int index = random.nextInt(0, allUsers.size()); + int index = random.nextInt(allUsers.size()); RecipeDesc currRecipeDesc = recipes.get(index); ProfileModel currentUser = selectedUsers.get(i); @@ -210,10 +205,10 @@ public void dinnertimeNotificationsPush() { newNotification.setUserId(currentUser.getUsername()); newNotification.setNotificationPic(currRecipeDesc.getRecipeImage()); newNotification.setType("recommendation"); - newNotification.setTitle("Why not try " + currRecipeDesc.getRecipeImage() + " for dinner today"); + newNotification.setTitle("Why not try " + currRecipeDesc.getName() + " for dinner today"); newNotification.setBody("Dinnertime is here! Explore our variety of dinner recipes and cook up something special for your evening."); + newNotification.setMetadata("/recipe/" + currRecipeDesc.getRecipeId()); - System.out.println(newNotification.toString()); this.save(newNotification); } } diff --git a/apps/api/src/main/java/com/fridgetoplate/service/RecipeService.java b/apps/api/src/main/java/com/fridgetoplate/service/RecipeService.java index 6fd6fcc4..bb59a2f3 100644 --- a/apps/api/src/main/java/com/fridgetoplate/service/RecipeService.java +++ b/apps/api/src/main/java/com/fridgetoplate/service/RecipeService.java @@ -82,18 +82,16 @@ public RecipeFrontendModel findById(String id){ if (youtubeId == null) { try { - YoubuteItem[] videos = externalApiService.spoonacularVideoSearch(name).getItems(); + YoubuteItem[] videos = externalApiService.spoonacularVideoSearch(name + " Recipe").getItems(); if (videos.length > 0) { youtubeId = videos[0].getId().videoId; recipeModel.setYoutubeId(youtubeId); recipeRepository.saveRecipe(recipeModel); - } else { - recipeModel.setYoutubeId(""); - } + } } catch (Exception e) { - recipeModel.setYoutubeId(""); + e.printStackTrace(); } } @@ -166,14 +164,16 @@ public RecipeFrontendModel save(RecipeFrontendModel recipe){ try { - YoubuteItem[] videos = externalApiService.spoonacularVideoSearch(recipe.getName()).getItems(); + YoubuteItem[] videos = externalApiService.spoonacularVideoSearch(recipe.getName() + " Recipe").getItems(); if (videos.length > 0) { model.setYoutubeId(videos[0].getId().videoId); } else { model.setYoutubeId(""); } - } catch (Exception e) {} + } catch (Exception e) { + e.printStackTrace(); + } } recipeRepository.saveRecipe(model); @@ -194,7 +194,7 @@ public RecipeFrontendModel save(RecipeFrontendModel recipe){ public RecipeFrontendModel update(RecipeFrontendModel recipe){ - RecipeModel model = new RecipeModel(); + RecipeModel model = recipeRepository.findById(recipe.getRecipeId()); model.setRecipeId(recipe.getRecipeId()); model.setDifficulty(recipe.getDifficulty()); model.setRecipeImage(recipe.getRecipeImage()); @@ -206,14 +206,11 @@ public RecipeFrontendModel update(RecipeFrontendModel recipe){ model.setSteps(recipe.getSteps()); model.setCreator(recipe.getCreator()); model.setServings(recipe.getServings()); - model.setViews(0); model.setRating(recipe.getRating()); model.setYoutubeId(recipe.getYoutubeId()); recipeRepository.saveRecipe(model); - recipe.setRecipeId(model.getRecipeId()); - List currIngredients = this.findIngredientsByRecipeId(recipe.getRecipeId()); recipeRepository.removeIngredients(currIngredients); @@ -433,81 +430,63 @@ public List findIngredientsByRecipeId(String recipeId){ return this.recipeRepository.findIngredientsByRecipeId(recipeId); } - public RecipeFrontendModel updateRatingAndViews(RecipeFrontendModel recipe){ + public RecipeFrontendModel increaseViews(String recipeId) { - RecipeModel model = new RecipeModel(); - RecipeModel recipeModel = recipeRepository.findById(recipe.getRecipeId()); + RecipeModel recipeModel = recipeRepository.findById(recipeId); + if (recipeModel != null) { + + recipeModel.setViews(recipeModel.getViews() + 1); + recipeRepository.saveRecipe(recipeModel); - if(recipeModel.getRating() != null && !recipeModel.getRating().equals(recipe.getRating())) { - model.setViews(recipeModel.getViews()); - } - - if(recipeModel.getRating() != null && recipeModel.getRating().equals(recipe.getRating())) { - model.setViews(recipeModel.getViews() + 1); - } + if(!recipeModel.getCreator().equals("Spoonacular")) { - model.setRecipeId(recipe.getRecipeId()); - model.setDifficulty(recipe.getDifficulty()); - model.setRecipeImage(recipe.getRecipeImage()); - model.setName(recipe.getName()); - model.setTags(recipe.getTags()); - model.setMeal(recipe.getMeal()); - model.setDescription(recipe.getDescription()); - model.setPrepTime(recipe.getPrepTime()); - model.setSteps(recipe.getSteps()); - model.setCreator(recipe.getCreator()); - model.setServings(recipe.getServings()); - model.setRating(recipe.getRating()); - - recipeRepository.saveRecipe(model); - - if (!model.getCreator().equals("Spoonacular")) { NotificationModel notif = new NotificationModel(); switch (recipeModel.getViews()) { case 25: - notif.setUserId(model.getCreator()); - notif.setMetadata("/recipe/" + model.getRecipeId()); - notif.setNotificationPic(model.getRecipeImage()); - notif.setTitle(model.getName() + " just receached 25 views"); + notif.setUserId(recipeModel.getCreator()); + notif.setMetadata("/recipe/" + recipeModel.getRecipeId()); + notif.setNotificationPic(recipeModel.getRecipeImage()); + notif.setTitle(recipeModel.getName() + " just receached 25 views"); notif.setTitle("Congratulations! Your recipe just hit 25 views. Keep cooking and sharing!"); notificationService.save(notif); break; case 100: - notif.setUserId(model.getCreator()); - notif.setMetadata("/recipe/" + model.getRecipeId()); - notif.setNotificationPic(model.getRecipeImage()); - notif.setTitle(model.getName() + " just receached 100 views"); + notif.setUserId(recipeModel.getCreator()); + notif.setMetadata("/recipe/" + recipeModel.getRecipeId()); + notif.setNotificationPic(recipeModel.getRecipeImage()); + notif.setTitle(recipeModel.getName() + " just receached 100 views"); notif.setTitle("Wow, your recipe has reached 100 views! You're cooking up a storm!"); notificationService.save(notif); break; case 500: - notif.setUserId(model.getCreator()); - notif.setMetadata("/recipe/" + model.getRecipeId()); - notif.setNotificationPic(model.getRecipeImage()); - notif.setTitle(model.getName() + " just receached 500 views"); + notif.setUserId(recipeModel.getCreator()); + notif.setMetadata("/recipe/" + recipeModel.getRecipeId()); + notif.setNotificationPic(recipeModel.getRecipeImage()); + notif.setTitle(recipeModel.getName() + " just receached 500 views"); notif.setTitle("500 views on your recipe! You're a culinary sensation!"); notificationService.save(notif); break; case 1000: - notif.setUserId(model.getCreator()); - notif.setMetadata("/recipe/" + model.getRecipeId()); - notif.setNotificationPic(model.getRecipeImage()); - notif.setTitle(model.getName() + " just receached 1000 views"); + notif.setUserId(recipeModel.getCreator()); + notif.setMetadata("/recipe/" + recipeModel.getRecipeId()); + notif.setNotificationPic(recipeModel.getRecipeImage()); + notif.setTitle(recipeModel.getName() + " just receached 1000 views"); notif.setTitle("Incredible! Your recipe has been viewed 1000 times. You're a recipe rockstar!"); notificationService.save(notif); break; } } + } - return recipe; + return this.findById(recipeId); } private Double numberIngredients(RecipeFrontendModel recipe, List ingredients) { diff --git a/apps/api/src/main/java/com/fridgetoplate/service/RecommendService.java b/apps/api/src/main/java/com/fridgetoplate/service/RecommendService.java index 5d30d8c3..3b69ce47 100644 --- a/apps/api/src/main/java/com/fridgetoplate/service/RecommendService.java +++ b/apps/api/src/main/java/com/fridgetoplate/service/RecommendService.java @@ -43,8 +43,14 @@ public List getRecipeRecommendations(RecommendFrontendModel userReco SpoonacularRecipeConverter converter = new SpoonacularRecipeConverter(); //1. Query External API and convert to Recipe - RecipeFrontendModel[] apiQueryResults = converter.unconvert(apiService.spoonacularRecipeSearch(recipePreferences, userRecommendation.getIngredients()).getResults()); - + RecipeFrontendModel[] apiQueryResults; + try { + apiQueryResults = converter.unconvert(apiService.spoonacularRecipeSearch(recipePreferences, userRecommendation.getIngredients()).getResults()); + } catch (Exception e) { + e.printStackTrace(); + apiQueryResults = new RecipeFrontendModel[0]; + } + //2. Add External API recipes to DB if(apiQueryResults.length != 0) recipeService.saveBatch( apiQueryResults ); diff --git a/apps/api/src/main/java/com/fridgetoplate/service/ReviewService.java b/apps/api/src/main/java/com/fridgetoplate/service/ReviewService.java index d3a6bfb7..32b5419b 100644 --- a/apps/api/src/main/java/com/fridgetoplate/service/ReviewService.java +++ b/apps/api/src/main/java/com/fridgetoplate/service/ReviewService.java @@ -25,9 +25,23 @@ public class ReviewService { public Review saveReview(Review review) { + reviewRepository.save(review); + NotificationModel notif = new NotificationModel(); RecipeFrontendModel recipe = recipService.findById(review.getRecipeId()); + + //Update Rating + List reviews = reviewRepository.getReviewsById(recipe.getRecipeId()); + Double totalRating = 0.0; + for (Review recipeReview : reviews) { + totalRating += recipeReview.getRating(); + } + + recipe.setRating(totalRating / reviews.size()); + recipService.update(recipe); + + // Create Notification notif.setUserId(recipe.getCreator()); notif.setMetadata("/recipe/" + recipe.getRecipeId()); notif.setNotificationPic(recipe.getRecipeImage()); @@ -36,23 +50,33 @@ public Review saveReview(Review review) { notificationService.save(notif); - return reviewRepository.save(review); + return review; } public String deleteReview(String recipeId, String reviewId){ - return reviewRepository.delete(recipeId, reviewId); + reviewRepository.delete(recipeId, reviewId); + + //Update Rating + RecipeFrontendModel recipe = recipService.findById(recipeId); + List reviews = reviewRepository.getReviewsById(recipe.getRecipeId()); + Double totalRating = 0.0; + for (Review recipeReview : reviews) { + totalRating += recipeReview.getRating(); + } + + recipe.setRating(totalRating / reviews.size()); + recipService.update(recipe); + + return "Deleted Review Successfully"; } public String removeReviews(List reviews) { reviewRepository.removeReviews(reviews); - return "REVIEWS SUCCESSFULLY DELETED"; + return "Deleted All Reviews Successfully"; } public List getReviewsById(String recipeId) { return reviewRepository.getReviewsById(recipeId); } - - - } diff --git a/apps/api/src/main/java/com/fridgetoplate/utils/NotificationsUtils.java b/apps/api/src/main/java/com/fridgetoplate/utils/NotificationsUtils.java deleted file mode 100644 index 9dd28c59..00000000 --- a/apps/api/src/main/java/com/fridgetoplate/utils/NotificationsUtils.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.fridgetoplate.utils; - -import java.util.List; - -public class NotificationsUtils { - - public String[] breakfastTitleList = { - "Rise and dine, it's breakfast time! 🌅☕", - "Start your day sunny-side up with breakfast! 🍳🥓", - "Don't 'toastpone' breakfast - it's here! 🍞🥐", - "Time to 'cereal'ously enjoy breakfast! 🥣", - "It's 'oat-standing' breakfast time! 🥣", - "A 'bagel' a day keeps the hunger away – enjoy breakfast! 🥯" - }; - - public String[] lunchtimeTitleList = { - "Lunch o'clock is 'pasta'ble right now! 🍝", - "Let's 'wrap' this up - it's lunchtime! 🌯", - "Time for a 'slice' of lunchtime delight! 🍕", - "Lunch is served, 'burrito'-tifully! 🌯🍴", - "It's 'wok' o'clock for lunch! 🥡", - }; - - public String[] dinnertimeTitleList = { - "Dinner is ready to 'roll' into your evening! 🍣", - "Time to 'steak' your claim on dinner! 🥩", - "Dinner: where 'grill' meets 'thrill'! 🍔🔥", - "Time to 'taco' 'bout dinner plans! 🌮", - "Don't 'chicken' out on dinner - it's delicious! 🍗", - }; - -} diff --git a/apps/api/src/main/java/com/fridgetoplate/utils/SpoonacularRecipeConverter.java b/apps/api/src/main/java/com/fridgetoplate/utils/SpoonacularRecipeConverter.java index baa26e83..ff48b57d 100644 --- a/apps/api/src/main/java/com/fridgetoplate/utils/SpoonacularRecipeConverter.java +++ b/apps/api/src/main/java/com/fridgetoplate/utils/SpoonacularRecipeConverter.java @@ -40,7 +40,7 @@ public RecipeFrontendModel[] unconvert(SpoonacularRecipe[] spoonacularRecipes) { List currentRecipeSteps = new ArrayList(); - newRecipe.setRecipeImage(currentRecipe.getImage()); + newRecipe.setRecipeImage(convertImageUrl(currentRecipe.getImage())); newRecipe.setName(currentRecipe.getTitle()); @@ -122,23 +122,30 @@ public RecipeFrontendModel[] unconvert(SpoonacularRecipe[] spoonacularRecipes) { newRecipe.setDescription(this.cleanSummary(currentRecipe.getSummary())); if (currentRecipe.getDishTypes().length > 0) { - String meal = currentRecipe.getDishTypes()[0]; - - if (meal.equals("morning meal")) { - meal = "breakfast"; - } else if (!meal.equals("breakfast") && - !meal.equals("snack") && - !meal.equals("lunch") && - !meal.equals("dinner") && - !meal.equals("dessert") && - !meal.equals("soup") && - !meal.equals("beverage") && - !meal.equals("salad") - ) { - meal = "snack"; + + for (String dishType : currentRecipe.getDishTypes()) { + if (dishType.equals("morning meal")) { + newRecipe.setMeal("breakfast"); + break; + } else if (dishType.equals("breakfast") || + dishType.equals("snack") || + dishType.equals("lunch") || + dishType.equals("dinner") || + dishType.equals("dessert") || + dishType.equals("soup") || + dishType.equals("beverage") || + dishType.equals("salad") + ) + { + newRecipe.setMeal(dishType); + break; + } } - newRecipe.setMeal(meal); + if (newRecipe.getMeal() == null) { + newRecipe.setMeal("snack"); + } + } else { newRecipe.setMeal("dinner"); } @@ -185,4 +192,13 @@ private String cleanSummary(String summary) { return summary; } + + private String convertImageUrl(String url) { + int i = url.indexOf("-"); + if (i >= 0) { + url = url.substring(0, i + 1); + } + + return url + "636x393"; + } } diff --git a/libs/app/core/src/core.module.ts b/libs/app/core/src/core.module.ts index 6deae65a..c5d5f74c 100644 --- a/libs/app/core/src/core.module.ts +++ b/libs/app/core/src/core.module.ts @@ -16,6 +16,7 @@ import { NgxsModule } from '@ngxs/store'; import { ErrorState } from '@fridge-to-plate/app/error/data-access'; import { NgxsRouterPluginModule } from '@ngxs/router-plugin'; import { AuthState } from '@fridge-to-plate/app/auth/data-access'; +import { ExploreState } from '@fridge-to-plate/app/explore/data-access'; import { UndoState } from '@fridge-to-plate/app/undo/data-access'; import { InfoState } from '@fridge-to-plate/app/info/data-access'; import { environment } from '@fridge-to-plate/app/environments/utils'; diff --git a/libs/app/explore/data-access/src/explore.module.ts b/libs/app/explore/data-access/src/explore.module.ts index 9a813643..6e912e4a 100644 --- a/libs/app/explore/data-access/src/explore.module.ts +++ b/libs/app/explore/data-access/src/explore.module.ts @@ -1,8 +1,13 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; -import { ExploreAPI } from './explore.api'; +import { NgxsModule } from '@ngxs/store'; +import { ExploreState } from './explore.state'; @NgModule({ - imports: [CommonModule], + imports: [ + CommonModule, + NgxsModule.forFeature([ExploreState]) + ], + }) export class ExploreDataAccessModule {} diff --git a/libs/app/explore/data-access/src/explore.state.ts b/libs/app/explore/data-access/src/explore.state.ts index 8c6c7742..42ff2670 100644 --- a/libs/app/explore/data-access/src/explore.state.ts +++ b/libs/app/explore/data-access/src/explore.state.ts @@ -1,13 +1,11 @@ import { Injectable } from "@angular/core"; -import { CategorySearch, RetrieveProfile, RetrieveRecipe } from "@fridge-to-plate/app/explore/utils"; +import { CategorySearch } from "@fridge-to-plate/app/explore/utils"; import { Action, Selector, State, StateContext, Store } from "@ngxs/store"; import { ExploreAPI } from "./explore.api"; import { IExplore } from "@fridge-to-plate/app/explore/utils"; -import { IIngredient } from "@fridge-to-plate/app/ingredient/utils"; import { IRecipe } from "@fridge-to-plate/app/recipe/utils"; import { ShowError } from "@fridge-to-plate/app/error/utils"; -import { Navigate } from "@ngxs/router-plugin"; -import { ShowInfo } from "@fridge-to-plate/app/info/utils"; +import { patch } from "@ngxs/store/operators"; export interface ExploreStateModel { explore: IExplore | null; @@ -45,7 +43,7 @@ export class ExploreState { } @Action(CategorySearch) - async CategorySearch({ setState } : StateContext, { search } : CategorySearch) { + async CategorySearch({ setState, patchState } : StateContext, { search } : CategorySearch) { setState({ explore: search, @@ -55,14 +53,13 @@ export class ExploreState { (await this.exploreAPI.searchCategory(search)).subscribe({ next: data => { - setState({ - explore: search, + patchState({ recipes: data }); }, error: error => { - this.store.dispatch(new ShowError(error)); + this.store.dispatch(new ShowError("Unable to Retrieve Search Results")); console.log(error); } }); diff --git a/libs/app/explore/feature/src/explore.page.html b/libs/app/explore/feature/src/explore.page.html index de54931f..91e97eed 100644 --- a/libs/app/explore/feature/src/explore.page.html +++ b/libs/app/explore/feature/src/explore.page.html @@ -9,7 +9,12 @@
-

Recent searches

+ +

Recent searches

+ + +
+
@@ -71,10 +76,11 @@

Categories

+

{{retunedRecipes.length}} Result(s) for {{searchTerm}}

-

{{retunedRecipes.length}} Result(s) for {{searchTerm}}

+ Categories *ngIf="loading === true" class="text-center justify-center pt-44 sm:pt-12" > -
-
-
- -
-
- -
-
- -
-
-
- -
-
-
-
+
diff --git a/libs/app/explore/feature/src/explore.page.ts b/libs/app/explore/feature/src/explore.page.ts index 11c62f6d..8a0bd8d5 100644 --- a/libs/app/explore/feature/src/explore.page.ts +++ b/libs/app/explore/feature/src/explore.page.ts @@ -113,6 +113,9 @@ export class ExplorePage { this.showRecipes = true; this.currSearch = false; } + + this.searchTerm = search.type.charAt(0).toUpperCase() + search.type.slice(1); + }) } diff --git a/libs/app/home/data-access/src/home.state.ts b/libs/app/home/data-access/src/home.state.ts index a426c61b..0a797d6f 100644 --- a/libs/app/home/data-access/src/home.state.ts +++ b/libs/app/home/data-access/src/home.state.ts @@ -1,7 +1,7 @@ import { IRecipe } from '@fridge-to-plate/app/recipe/utils'; import { Action, Selector, State, StateContext, Store } from '@ngxs/store'; import { Injectable } from '@angular/core'; -import { RetrieveFeaturedRecipes } from '../../utils/src/home.actions'; +import {ClearFeaturedRecipes, RetrieveFeaturedRecipes} from '../../utils/src/home.actions'; import { ExploreAPI } from '@fridge-to-plate/app/explore/data-access'; import { IExplore } from '@fridge-to-plate/app/explore/utils'; import { ShowError } from '@fridge-to-plate/app/error/utils'; @@ -32,7 +32,7 @@ export class HomeState { @Action(RetrieveFeaturedRecipes) async getRecipe({ setState, getState }: StateContext, { meal }: RetrieveFeaturedRecipes) { - + if (getState().meal !== meal) { const explore : IExplore = { type: meal, @@ -40,7 +40,7 @@ export class HomeState { tags: [], difficulty: "", }; - + (await this.api.searchCategory(explore)).subscribe({ next: data => { setState({ @@ -53,6 +53,14 @@ export class HomeState { } }); } - + } -} \ No newline at end of file + + @Action(ClearFeaturedRecipes) + async clearAllRecipes({ setState }: StateContext){ + setState({ + meal: '', + featuredRecipes: null + }) + } +} diff --git a/libs/app/home/feature/src/home.page.html b/libs/app/home/feature/src/home.page.html index 805ed83c..92861ea7 100644 --- a/libs/app/home/feature/src/home.page.html +++ b/libs/app/home/feature/src/home.page.html @@ -12,16 +12,29 @@

- -
-

Featured Recipes

-
-
- +

Featured Recipes

+ +
+
+
+ +
-
-
- +
+ + + +
+

Oops, we had an issue on our side.

+

Try refreshing the page to get some recipes

+
+ +
+ + + +
+
diff --git a/libs/app/home/feature/src/home.page.ts b/libs/app/home/feature/src/home.page.ts index 687c4b10..dfb01268 100644 --- a/libs/app/home/feature/src/home.page.ts +++ b/libs/app/home/feature/src/home.page.ts @@ -1,12 +1,12 @@ -import { Component, NgZone } from '@angular/core'; +import {Component, NgZone, OnInit} from '@angular/core'; import { ProfileState } from '@fridge-to-plate/app/profile/data-access'; import { IProfile } from '@fridge-to-plate/app/profile/utils'; import { Select, Store } from '@ngxs/store'; -import { Observable } from 'rxjs'; +import {delay, Observable} from 'rxjs'; import { IRecipe } from '@fridge-to-plate/app/recipe/utils'; import { Router } from '@angular/router'; import { HomeState } from '../../data-access/src/home.state'; -import { RetrieveFeaturedRecipes } from '../../utils/src/home.actions'; +import {ClearFeaturedRecipes, RetrieveFeaturedRecipes} from '../../utils/src/home.actions'; @Component({ selector: 'fridge-to-plate-home', @@ -14,12 +14,14 @@ import { RetrieveFeaturedRecipes } from '../../utils/src/home.actions'; styleUrls: ['./home.page.css'], }) // eslint-disable-next-line @angular-eslint/component-class-suffix -export class HomePage { +export class HomePage implements OnInit { mealType = 'breakfast' messageHeader = ''; @Select(ProfileState.getProfile) profile$ !: Observable; @Select(HomeState.getFeaturedRecipes) featuredRecipes$ !: Observable; - + + featuredRecipesDelayed$: Observable = this.featuredRecipes$.pipe(delay(2000)); + constructor(private readonly router: Router, private readonly ngZone: NgZone, private store: Store){ const currentTime = new Date(); const currentHour = currentTime.getHours(); @@ -39,8 +41,6 @@ export class HomePage { this.mealType = 'snack'; this.messageHeader = `Time for a snack! What do you feel like making?`; } - - this.store.dispatch(new RetrieveFeaturedRecipes(this.mealType)); } goToRecommend(): void { @@ -48,4 +48,10 @@ export class HomePage { this.router.navigate(['/recommend']); }); } + + ngOnInit() { + this.store.dispatch(new ClearFeaturedRecipes()); + this.store.dispatch(new RetrieveFeaturedRecipes(this.mealType)); + } + } diff --git a/libs/app/home/utils/src/home.actions.ts b/libs/app/home/utils/src/home.actions.ts index 4f3fe0da..d15c9197 100644 --- a/libs/app/home/utils/src/home.actions.ts +++ b/libs/app/home/utils/src/home.actions.ts @@ -1,4 +1,9 @@ export class RetrieveFeaturedRecipes { static readonly type = '[Home] RetrieveFeaturedRecipes'; constructor(public readonly meal: string) {} -} \ No newline at end of file +} + +export class ClearFeaturedRecipes { + static readonly type = '[Home] ClearFeaturedRecipes'; + constructor() {} +} diff --git a/libs/app/login/feature/src/login.page.html b/libs/app/login/feature/src/login.page.html index bc116f47..32f68b53 100644 --- a/libs/app/login/feature/src/login.page.html +++ b/libs/app/login/feature/src/login.page.html @@ -1,6 +1,6 @@ -
+
@@ -25,9 +25,9 @@

Hey,
- + -

+
diff --git a/libs/app/login/feature/src/login.page.ts b/libs/app/login/feature/src/login.page.ts index 360f146e..81846a55 100644 --- a/libs/app/login/feature/src/login.page.ts +++ b/libs/app/login/feature/src/login.page.ts @@ -27,7 +27,7 @@ export class LoginPage { constructor(private store: Store) { } onSignIn(form: NgForm){ - + if (form.valid) { this.store.dispatch(new Login(this.username, this.password)); } @@ -42,7 +42,7 @@ export class LoginPage { } guest() { - this.store.dispatch(new Navigate(['/recommend'])); + this.store.dispatch(new Navigate(['/home'])); } } diff --git a/libs/app/notifications/data-access/src/notifications.state.ts b/libs/app/notifications/data-access/src/notifications.state.ts index 8b8eb03c..80308fdd 100644 --- a/libs/app/notifications/data-access/src/notifications.state.ts +++ b/libs/app/notifications/data-access/src/notifications.state.ts @@ -31,25 +31,7 @@ export interface NotificationsStateModel { name: 'notifications', defaults: { generalNotifications: [], - recommendationNotification: [ - { - userId: 'JohnDoe', - notificationPic: '/assets/Fridge Logo Transparent.png', - title: 'Pure authentic Italian Dish', - metadata: 'b6df9e16-4916-4869-a7d9-eb0293142f1f', - type: 'recommendation', - notificationId: '280e3937-0447-40d6-ac43-d089495932ba', - }, - { - userId: 'BobtheBuilder', - notificationPic: '/assets/Fridge Logo Transparent.png', - title: 'Pure authentic Italian Dish', - body: 'The dish is good if you have no choice', - metadata: 'b6df9e16-4916-4869-a7d9-eb0293142f1f', - type: 'recommendation', - notificationId: '280e3937-0447-40d6-ac43-d089495932ba', - }, - ], + recommendationNotification: [], }, }) @Injectable() diff --git a/libs/app/notifications/feature/src/notifications.page.html b/libs/app/notifications/feature/src/notifications.page.html index 5fb2485b..7034885a 100644 --- a/libs/app/notifications/feature/src/notifications.page.html +++ b/libs/app/notifications/feature/src/notifications.page.html @@ -1,4 +1,4 @@ -
+

Shopping List

-
    +
    • - {{ ingredient.name }} +
      +
      + {{ ingredient.name }} +
      +
      + {{ingredient.amount}}{{ingredient.unit}} +
      +
      +
    + +

    No Recipes in Meal Plan

    +
diff --git a/libs/app/recipe/data-access/src/recipe.api.ts b/libs/app/recipe/data-access/src/recipe.api.ts index 18c177af..0ef76d47 100644 --- a/libs/app/recipe/data-access/src/recipe.api.ts +++ b/libs/app/recipe/data-access/src/recipe.api.ts @@ -19,9 +19,11 @@ export class RecipeAPI { const url = this.baseUrl + '/create'; return this.http.post(url, recipe); } - updateRecipeRatingAndViews(recipe: IRecipe): Observable { - const url = this.baseUrl + '/update-ratingAndViews/' + recipe.recipeId; - return this.http.put(url, recipe); + + increaseViews(recipeId: string): Observable { + const url = this.baseUrl + '/increaseViews/' + recipeId; + console.log(recipeId); + return this.http.put(url, null); } deleteRecipe(id: string): Observable { diff --git a/libs/app/recipe/data-access/src/recipe.state.ts b/libs/app/recipe/data-access/src/recipe.state.ts index 1c198d80..54091bc3 100644 --- a/libs/app/recipe/data-access/src/recipe.state.ts +++ b/libs/app/recipe/data-access/src/recipe.state.ts @@ -1,4 +1,4 @@ -import { ChangeMeasurementType, IRecipe, IncreaseViews, RetrieveMealPlanIngredients, UpdateRecipeRatingAndViews } from '@fridge-to-plate/app/recipe/utils'; +import { ChangeMeasurementType, IRecipe, IncreaseViews, RetrieveMealPlanIngredients } from '@fridge-to-plate/app/recipe/utils'; import { Action, Selector, State, StateContext, Store } from '@ngxs/store'; import { Injectable } from '@angular/core'; import { RecipeAPI } from './recipe.api'; @@ -64,6 +64,10 @@ export class RecipeState { { recipeId }: RetrieveRecipe ) { + patchState({ + recipe: null + }); + this.api.getRecipeById(recipeId).subscribe( (recipe) => { if (recipe) { @@ -71,44 +75,13 @@ export class RecipeState { patchState({ recipe: recipe, }); - } else { - this.store.dispatch( - new ShowError( - 'Error: Something is wrong with the recipe: ' + recipe - ) - ); - } + } }, (error: Error) => { - console.error('Failed to retrieve recipe: ', error); - this.store.dispatch(new ShowInfo("Could Not Retrieve Recipe")); + this.store.dispatch(new ShowError("Could Not Retrieve Recipe")); } ); } - @Action(UpdateRecipeRatingAndViews) - updateRecipeRatingAndViews( - { patchState }: StateContext, - { recipe }: UpdateRecipeRatingAndViews - ) { - - patchState({ - recipe: recipe, - }); - - this.api.updateRecipeRatingAndViews(recipe).subscribe( - () => { - patchState({ - recipe: recipe, - }); - this.store.dispatch(new ShowSuccess('Recipe Rating Updated Successfully')); - }, - (error: Error) => { - console.error('Failed to update recipe:', error); - this.store.dispatch(new ShowError(error.message)); - } - ); - - } @Action(UpdateRecipe) updateRecipe( @@ -127,21 +100,16 @@ export class RecipeState { this.store.dispatch(new ShowSuccess('Successfully Updated Recipe')); }, (error: Error) => { - console.error('Failed to update recipe:', error); - this.store.dispatch(new ShowError(error.message)); + this.store.dispatch(new ShowError("Failed to Update Recipe")); } ); } @Action(IncreaseViews) increaseViews( - { getState }: StateContext + { recipeId }: IncreaseViews ) { - const recipe = getState().recipe; - - if (recipe) { - this.store.dispatch(new UpdateRecipeRatingAndViews(recipe)); - } + this.api.increaseViews(recipeId).subscribe(); } @Action(AddReview) @@ -173,7 +141,6 @@ export class RecipeState { } } - this.store.dispatch(new UpdateRecipeRatingAndViews(updatedRecipe)); this.store.dispatch(new ShowSuccess('Successfully Added Review')); }, error: error => { @@ -206,8 +173,6 @@ export class RecipeState { updatedRecipe.rating = sumRatings / updatedRecipe.reviews.length; } - this.store.dispatch(new UpdateRecipeRatingAndViews(updatedRecipe)); - (await this.api.deleteReview(updatedRecipe.recipeId as string, reviewId)).subscribe(); this.store.dispatch(new ShowInfo('Review Deleted')); } diff --git a/libs/app/recipe/feature/src/recipe.page.html b/libs/app/recipe/feature/src/recipe.page.html index eec3212c..9139c291 100644 --- a/libs/app/recipe/feature/src/recipe.page.html +++ b/libs/app/recipe/feature/src/recipe.page.html @@ -75,7 +75,7 @@

Tags

Creator

-

{{ recipe?.creator }}

+

{{ recipe.creator }}

@@ -83,7 +83,7 @@

Creator

Description

-

{{ recipe?.description }}

+

{{ recipe.description }}

diff --git a/libs/app/recipe/feature/src/recipe.page.spec.ts b/libs/app/recipe/feature/src/recipe.page.spec.ts index 2fb2f462..8d3209f5 100644 --- a/libs/app/recipe/feature/src/recipe.page.spec.ts +++ b/libs/app/recipe/feature/src/recipe.page.spec.ts @@ -168,16 +168,16 @@ describe('RecipeDetailPageComponent', () => { expect(component.safeUrl).toBe(undefined); }); - it('should toggle isDescriptionExpanded from false to true', () => { - component.isDescriptionExpanded = false; + it('should toggle isDescriptionUnexpanded from false to true', () => { + component.isDescriptionUnexpanded = false; component.toggleDescriptionExpanded(); - expect(component.isDescriptionExpanded).toBe(true); + expect(component.isDescriptionUnexpanded).toBe(true); }); it('should toggle isDescriptionExpanded from true to false', () => { - component.isDescriptionExpanded = true; + component.isDescriptionUnexpanded = true; component.toggleDescriptionExpanded(); - expect(component.isDescriptionExpanded).toBe(false); + expect(component.isDescriptionUnexpanded).toBe(false); }); it('should dispatch ingredients change', () => { diff --git a/libs/app/recipe/feature/src/recipe.page.ts b/libs/app/recipe/feature/src/recipe.page.ts index 2cb13599..98f53e36 100644 --- a/libs/app/recipe/feature/src/recipe.page.ts +++ b/libs/app/recipe/feature/src/recipe.page.ts @@ -4,7 +4,7 @@ import { ActivatedRoute } from '@angular/router'; import { Location } from '@angular/common'; import { Select, Store } from '@ngxs/store'; import { RecipeState } from '@fridge-to-plate/app/recipe/data-access'; -import { Observable } from 'rxjs'; +import { Observable, take } from 'rxjs'; import { ShowError } from '@fridge-to-plate/app/error/utils'; import { Navigate } from '@ngxs/router-plugin'; import { @@ -40,12 +40,9 @@ export class RecipePage implements OnInit { ) {} hasTags = false; - isDescriptionExpanded = false; + isDescriptionUnexpanded = true; presentIngredients: IIngredient[] = []; missingIngredients: IIngredient[] = []; - toggleDescriptionExpanded() { - this.isDescriptionExpanded = !this.isDescriptionExpanded; - } ngOnInit(): void { this.forceLoading = true; @@ -73,8 +70,8 @@ export class RecipePage implements OnInit { this.recipe$.subscribe((stateRecipe) => { this.recipe = stateRecipe; if (this.recipe) { - this.store.dispatch(new IncreaseViews()); - + this.store.dispatch(new IncreaseViews(`${this.recipe.recipeId}`)); + if (stateRecipe.youtubeId) { this.safeUrl = this._sanitizer.bypassSecurityTrustResourceUrl( `https://www.youtube.com/embed/${this.recipe.youtubeId}` @@ -112,4 +109,9 @@ export class RecipePage implements OnInit { changeIngredientUnits() { this.store.dispatch(new ChangeMeasurementType(this.measurementUnit)); } + + toggleDescriptionExpanded() { + this.isDescriptionUnexpanded = !this.isDescriptionUnexpanded; + } + } diff --git a/libs/app/recipe/ui/src/custom-skeleton-loader/custom-skeleton-loader.component.css b/libs/app/recipe/ui/src/custom-skeleton-loader/custom-skeleton-loader.component.css new file mode 100644 index 00000000..e69de29b diff --git a/libs/app/recipe/ui/src/custom-skeleton-loader/custom-skeleton-loader.component.html b/libs/app/recipe/ui/src/custom-skeleton-loader/custom-skeleton-loader.component.html new file mode 100644 index 00000000..8f74c3d8 --- /dev/null +++ b/libs/app/recipe/ui/src/custom-skeleton-loader/custom-skeleton-loader.component.html @@ -0,0 +1,38 @@ +
+
+
+ +
+
+ +
+
+ +
+
+
+ +
+
+
+
diff --git a/libs/app/recipe/ui/src/custom-skeleton-loader/custom-skeleton-loader.component.spec.ts b/libs/app/recipe/ui/src/custom-skeleton-loader/custom-skeleton-loader.component.spec.ts new file mode 100644 index 00000000..200d76d0 --- /dev/null +++ b/libs/app/recipe/ui/src/custom-skeleton-loader/custom-skeleton-loader.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { CustomSkeletonLoaderComponent } from './custom-skeleton-loader.component'; + +describe('CustomSkeletonLoaderComponent', () => { + let component: CustomSkeletonLoaderComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [CustomSkeletonLoaderComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(CustomSkeletonLoaderComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/libs/app/recipe/ui/src/custom-skeleton-loader/custom-skeleton-loader.component.ts b/libs/app/recipe/ui/src/custom-skeleton-loader/custom-skeleton-loader.component.ts new file mode 100644 index 00000000..e081059f --- /dev/null +++ b/libs/app/recipe/ui/src/custom-skeleton-loader/custom-skeleton-loader.component.ts @@ -0,0 +1,8 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'fridge-to-plate-custom-skeleton-loader', + templateUrl: './custom-skeleton-loader.component.html', + styleUrls: ['./custom-skeleton-loader.component.css'], +}) +export class CustomSkeletonLoaderComponent {} diff --git a/libs/app/recipe/ui/src/meal-plan-modal/meal-plan-modal.component.html b/libs/app/recipe/ui/src/meal-plan-modal/meal-plan-modal.component.html index 4a2659d4..3d56c624 100644 --- a/libs/app/recipe/ui/src/meal-plan-modal/meal-plan-modal.component.html +++ b/libs/app/recipe/ui/src/meal-plan-modal/meal-plan-modal.component.html @@ -6,13 +6,15 @@
-

Choose Specific Meal

+

Add to Meal Plan

+ +