diff --git a/build.gradle b/build.gradle index b1e9824..13730be 100644 --- a/build.gradle +++ b/build.gradle @@ -22,6 +22,7 @@ repositories { dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + implementation 'org.springframework.security:spring-security-crypto' developmentOnly 'org.springframework.boot:spring-boot-devtools' runtimeOnly 'mysql:mysql-connector-java' testImplementation('org.springframework.boot:spring-boot-starter-test') { diff --git a/recipes_table_and_data.sql b/recipes_table_and_data.sql new file mode 100644 index 0000000..9809b63 --- /dev/null +++ b/recipes_table_and_data.sql @@ -0,0 +1,158 @@ +-- MySQL dump 10.13 Distrib 8.0.16, for Win64 (x86_64) +-- +-- Host: localhost Database: recipes +-- ------------------------------------------------------ +-- Server version 8.0.16 + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; + SET NAMES utf8 ; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +-- +-- Table structure for table `hibernate_sequence` +-- + +DROP TABLE IF EXISTS `hibernate_sequence`; +/*!40101 SET @saved_cs_client = @@character_set_client */; + SET character_set_client = utf8mb4 ; +CREATE TABLE `hibernate_sequence` ( + `next_val` bigint(20) DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `hibernate_sequence` +-- + +LOCK TABLES `hibernate_sequence` WRITE; +/*!40000 ALTER TABLE `hibernate_sequence` DISABLE KEYS */; +INSERT INTO `hibernate_sequence` VALUES (21); +/*!40000 ALTER TABLE `hibernate_sequence` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `recipe` +-- + +DROP TABLE IF EXISTS `recipe`; +/*!40101 SET @saved_cs_client = @@character_set_client */; + SET character_set_client = utf8mb4 ; +CREATE TABLE `recipe` ( + `id` int(11) NOT NULL, + `category` int(11) NOT NULL, + `directions` varchar(1000) DEFAULT NULL, + `img` varchar(500) DEFAULT NULL, + `ingredients` varchar(1000) NOT NULL, + `name` varchar(255) DEFAULT NULL, + `tag` int(11) DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `recipe` +-- + +LOCK TABLES `recipe` WRITE; +/*!40000 ALTER TABLE `recipe` DISABLE KEYS */; +INSERT INTO `recipe` VALUES (1,3,'\"1. Slice oni1. Slice onions -- 1/8\"\" thick. 2. Melt butter, place onions in it, saute slowly for 1 1/2 hours in a large soup pot. 3. Add all other ingredients except bouillon, saute over low heat 10 minutes more. \"','https://photos.riverfronttimes.com/wp-content/uploads/2020/04/03-FRENCH-ONION.jpg','\"3 pounds3 pounds (1360 grams) Onions, raw 8 tbsp Butter, unsalted 1.50 tsp Pepper, black 1 tsp, crumbled Bay Leaf 2 tbsp Paprika 8 fl oz White Wine 12 cups Beef Broth 0.75 cup spelt flour 2 tsp Sea Salt (1360 grams) Onions, raw\"','Famous Barr\'s French Onion Soup',0),(3,5,'\"1. Preheat oven to 350° F. Line muffin tins with 12 foil cupcake papers. Place a vanilla wafer in the bottom of each cupcake paper. 2. In mixing bowl, beat cream cheese and fat-free cream cheese until smooth. Add sugar and vanilla and mix well. Add eggs and beat until smooth. ','https://sparkpeo.hs.llnwd.net/e1/resize/630m620/e2/guid/Mini-Cheesecakes-RECIPE/fdf44cec-936c-4abd-bc71-675fdb5aac2f.jpg','\"12 low-fat vanilla wafers 3 oz. cream cheese, at room temperature 12 oz. fat-free cream cheese, at room temperature 1/2 cup sugar 1/2 teaspoon vanilla 2 eggs cherry pie filling\"','Mini Cheesecakes',0),(5,0,'Combine the first 3 ingredients in a gallon-size ziploc bag, shake it up, and then add the salmon. Allow to marinate in the refrigerator for an hour, turning after half an hour. Pour the salmon and the marinade into a baking dish and bake in a 350 degree oven, covered with foil, for 15 minutes. The salmon is done when it flakes easily at the thickest part. Enjoy! Makes (4) 3 ounce servings. ','https://sparkpeo.hs.llnwd.net/e2/guid/Worlds-Best-(and-Easiest)-Salmon/d6637946-83d1-4cbb-90c2-a40ab6ea8780.jpg','\"1/4 cup pure maple syrup (NOT pancake syrup!) or honey 1/4 cup soy sauce 2-3 cloves minced garlic 12 ounces fresh or thawed salmon\"','World\'s Best (and Easiest) Salmon',5),(8,2,'Preheat the oven or toaster oven to 425 degrees F. Brush both sides of the eggplant with the oil and season with the salt and pepper. Arrange on a baking sheet and bake until browned and almost tender, 6 to 8 minutes, turning once. Spread 1 tablespoon of pasta sauce on each eggplant slice. Top with the shredded cheese. Bake until the cheese melts, 3 to 5 minutes. Serve hot.','https://sparkpeo.hs.llnwd.net/e2/guid/-Mini-Eggplant-Pizzas/f098cccb-7de1-4738-a18a-ab6774ca6325.jpg','\"1 eggplant - 3 inches in diameter, peeled and cut into 4 half-inch thick slices 4 teaspoons olive oil 1/2 teaspoon salt 1/8 teaspoon ground black pepper 1/4 cup pasta sauce 1/2 cup shredded part-skim mozzarella cheese\"','Mini Eggplant Pizzas',1),(9,4,'1. Wash tomatoes and cilantro.\r\n2. Dice tomatoes, onions, chop cilantro, jalapenos, and the optional ingredients (avocado, cucumber)\r\n3. Put ingredients in a bowl.\r\n4. Add salt, garlic, the juice of half a lemon. Mix it up and serve.','https://sparkpeo.hs.llnwd.net/e2/guid/Pico-de-Gallo---Authentic-Mexican-Salsa/23188500-3d62-4362-a3fe-8885b798241e.jpg','3 large diced tomatoes 1 diced medium sized onion 1/4 bunch of cilantro (use more or less depending on your taste) juice of half a lemon 1/2 teaspoon of minced garlic 1 tsp of salt 2 jalapenos (or more if you prefer it hotter)','Pico de Gallo - Authentic Mexican Salsa',1),(10,2,'1. Preheat oven to 425 degrees F.\r\n2. In a shallow pan, toss potatoes and carrots with oil, salt and pepper.\r\n3. Nestle peeled garlic cloves amongst the vegetables and scatter the rosemary on top.\r\n4. Arrange the chicken among the vegetables and bake uncovered for 30 minutes.\r\n5. Meanwhile, stir the mustard and honey together.\r\n6. Remove the pan from the oven. Carefully take the chicken from the pan to another clean plate. Spread the honey-mustard mixture over the chicken.\r\n7. Stir vegetables in the pan, return coated chicken to the pan, and place pan back into the oven. Bake 10-20 minutes, until chicken is cooked and vegetables are tender.','https://sparkpeo.hs.llnwd.net/e4/nw/2/6/l26327171.jpg','1 lb. potatoes, cut into wedges\r\n2 lbs. chicken, rinsed\r\n6 medium carrots, sliced\r\n2 Tablespoons olive oil\r\n1-1/2 Tablespoons honey\r\n3 Tablespoons mustard\r\n1 teaspoon dried rosemary\r\n2 heads garlic, peeled\r\nsalt and pepper to taste','Honey Mustard Roasted Chicken',4),(11,5,'Place all ingredients in blender and process to desired consistency. Makes 3 servings approx 3/4 cup each.','https://sparkpeo.hs.llnwd.net/e2/guid/5-Minute-Berry-Smoothie/d702b10b-cab7-4bbd-92a6-6cf53d2131b6.jpg','1 cup berries any type (I like Kirkland\'s Frozen Mixed Berry Blend)\r\n1 small banana (6\")\r\n1 cup Low Fat Vanilla Yogurt (I used Mountain High which is made with fructose, if you use an artificially sweetened product the calories will be lower)\r\n1/2 cup skim milk or enough as needed to make to a drinking consistency','5-Minute Berry Smoothie',0),(12,5,'CRUST: Spread the sugar cookie dough over a 14-inch pizza pan. Bake in a 375 degree oven for 12 minutes or until lightly golden brown. Cool in the pan.\r\n\r\nTOPPING: Blend the cream cheese with the sugar and vanilla until completely mixed. Spread in a thin layer over the cooled crust.\r\n\r\nFRUIT LAYER: Creatively arrange the fruit in circles while slightly overlapping the slices around the crust.\r\n\r\nGLAZE: Bring the water and preserves to a boil, stirring constantly. Lightly brush this glaze on top on the fruit to preserve the color. Refrigerate until ready to serve.','https://sparkpeo.hs.llnwd.net/e2/guid/Fruit-Pizza-/824e0313-21dd-4a0c-ba52-e7c88b8cff25.jpg','1/2 package of refrigerated sugar cookie dough\r\n8 ounces reduced-fat cream cheese, whipped\r\n1/3 cup powdered sugar\r\n1/2 teaspoon vanilla\r\n1 tablespoon water\r\n1/4 cup apricot preserves\r\nFruit of your choice (sliced bananas, sliced strawberries, sliced kiwi, seedless grapes cut in half, blueberries, melon balls sliced in half)','Fruit Pizza',0),(13,5,'CRUST: Spread the sugar cookie dough over a 14-inch pizza pan. Bake in a 375 degree oven for 12 minutes or until lightly golden brown. Cool in the pan.\r\n\r\nTOPPING: Blend the cream cheese with the sugar and vanilla until completely mixed. Spread in a thin layer over the cooled crust.\r\n\r\nFRUIT LAYER: Creatively arrange the fruit in circles while slightly overlapping the slices around the crust.\r\n\r\n','https://sparkpeo.hs.llnwd.net/e2/guid/Fruit-Pizza-/824e0313-21dd-4a0c-ba52-e7c88b8cff25.jpg','1/2 package of refrigerated sugar cookie dough\r\n8 ounces reduced-fat cream cheese, whipped\r\n1/3 cup powdered sugar\r\n1/2 teaspoon vanilla\r\n1 tablespoon water\r\n1/4 cup apricot preserves\r\nFruit of your choice (sliced bananas, sliced strawberries, sliced kiwi, seedless grapes cut in half, blueberries, melon balls sliced in half)','Fruit Pizza',0),(14,5,'CRUST: Spread the sugar cookie dough over a 14-inch pizza pan. Bake in a 375 degree oven for 12 minutes or until lightly golden brown. Cool in the pan.\r\n\r\nTOPPING: Blend the cream cheese with the sugar and vanilla until completely mixed. Spread in a thin layer over the cooled crust.\r\n\r\nFRUIT LAYER: Creatively arrange the fruit in circles while slightly overlapping the slices around the crust.\r\n\r\n','https://sparkpeo.hs.llnwd.net/e2/guid/Fruit-Pizza-/824e0313-21dd-4a0c-ba52-e7c88b8cff25.jpg','1/2 package of refrigerated sugar cookie dough\r\n8 ounces reduced-fat cream cheese, whipped\r\n1/3 cup powdered sugar\r\n1/2 teaspoon vanilla\r\n1 tablespoon water\r\n1/4 cup apricot preserves\r\nFruit of your choice (sliced bananas, sliced strawberries, sliced kiwi, seedless grapes cut in half, blueberries, melon balls sliced in half)','Fruit Pizza',0),(15,5,'CRUST: Spread the sugar cookie dough over a 14-inch pizza pan. Bake in a 375 degree oven for 12 minutes or until lightly golden brown. Cool in the pan.\r\n\r\nTOPPING: Blend the cream cheese with the sugar and vanilla until completely mixed. Spread in a thin layer over the cooled crust.\r\n\r\nFRUIT LAYER: Creatively arrange the fruit in circles while slightly overlapping the slices around the crust.\r\n\r\n','https://sparkpeo.hs.llnwd.net/e2/guid/Fruit-Pizza-/824e0313-21dd-4a0c-ba52-e7c88b8cff25.jpg','1/2 package of refrigerated sugar cookie dough\r\n8 ounces reduced-fat cream cheese, whipped\r\n1/3 cup powdered sugar\r\n1/2 teaspoon vanilla\r\n1 tablespoon water\r\n1/4 cup apricot preserves\r\nFruit of your choice (sliced bananas, sliced strawberries, sliced kiwi, seedless grapes cut in half, blueberries, melon balls sliced in half)','Fruit Pizza',0),(16,5,'CRUST: Spread the sugar cookie dough over a 14-inch pizza pan. Bake in a 375 degree oven for 12 minutes or until lightly golden brown. Cool in the pan.\r\n\r\nTOPPING: Blend the cream cheese with the sugar and vanilla until completely mixed. Spread in a thin layer over the cooled crust.\r\n\r\nFRUIT LAYER: Creatively arrange the fruit in circles while slightly overlapping the slices around the crust.\r\n\r\n','https://sparkpeo.hs.llnwd.net/e2/guid/Fruit-Pizza-/824e0313-21dd-4a0c-ba52-e7c88b8cff25.jpg','1/2 package of refrigerated sugar cookie dough\r\n8 ounces reduced-fat cream cheese, whipped\r\n1/3 cup powdered sugar\r\n1/2 teaspoon vanilla\r\n1 tablespoon water\r\n1/4 cup apricot preserves\r\nFruit of your choice (sliced bananas, sliced strawberries, sliced kiwi, seedless grapes cut in half, blueberries, melon balls sliced in half)','Fruit Pizza',0),(17,3,'Crumble sausage into a Dutch oven; add onion. Cook and stir over medium heat until meat is no longer pink.\r\n\r\nAdd garlic; cook and stir 2 minutes longer.\r\n\r\nAdd the broth and tomatoes. Bring to a boil.\r\n\r\nStir in tortellini; return to a boil. Reduce heat; simmer, uncovered, for 5-8 minutes or until pasta is tender, stirring occasionally.\r\n\r\nAdd the spinach, basil, pepper and pepper flakes; cook 2-3 minutes longer or until spinach is wilted. Serve with cheese if desired.\r\n\r\n','https://sparkpeo.hs.llnwd.net/e2/guid/Rustic-Italian-Tortellini-Soup/57d3d1d0-0779-4198-a233-ca08a758e6dd.jpg','3 Italian turkey sausage links (4 ounces each), casings removed (I used the \"hot\" sausage version)\r\n1 medium onion, chopped\r\n6 garlic cloves, minced\r\n4 cups reduced-sodium chicken broth\r\n1 can (14-1/2 ounces) diced tomatoes, undrained\r\n1 package (9 ounces) refrigerated cheese tortellini\r\n1 package (6 ounces) fresh baby spinach, coarsely chopped\r\n2-1/4 teaspoons minced fresh basil or 3/4 teaspoon dried basil (or, you can substitute thyme and oregano)\r\n1/4 teaspoon pepper','Rustic Italian Tortellini Soup',4),(18,2,'1. Place chicken in 13x9x2\" glass baking dish.\r\n\r\n2. Mix lemon juice, vinegar, lemon peel, oregano, and onions. Pour over chicken, cover and marinate in refrigerator several hours or overnight, turning occasionally.\r\n\r\n3. Sprinkle with salt, pepper, and paprika.\r\n\r\n4. Cover and bake at 325º F for 30 minutes. Uncover and bake 30 minutes more or until done.\r\n\r\n','https://sparkpeo.hs.llnwd.net/e2/guid/Easy-Lemon-Chicken-/1217b5a4-46f2-4eb0-8ccc-00eb59111056.jpg','1-1/2 lb. chicken breast, skinned and fat removed\r\n3 lemons, juiced and zested, flesh cut into segments\r\n1 Tablespoon vinegar (or balsamic vinegar)\r\n3 teaspoons chopped fresh oregano or 1 teaspoon dried oregano, crushed\r\n1 medium onion, sliced\r\n1/4 teaspoon salt\r\nBlack pepper to taste\r\n1/2 teaspoon paprika','Easy Lemon Chicken',4),(19,5,'Beat egg whites and dash of salt until soft peaks form. Gradually add in sugar while beating until peaks are stiff and glossy.\r\nFold in coconut.\r\nDrop by rounded teaspoon onto greased baking sheet.\r\nBake at 325*F 18-20 minutes until set and very slightly browned.\r\nCenter will still be soft.','https://sparkpeo.hs.llnwd.net/e2/guid/Coconut-Meringue-Cookies/fe015d9c-5250-4e5f-aa81-d5a337cdaaa6.jpg','1-1/2 cups sweetened shredded coconut\r\n2 egg whites\r\n1/4 tsp vanilla extract\r\nDash of salt\r\n2/3 cup granulated sugar','Coconut Meringue Cookies',2),(20,1,'Add all ingredients to blender or food processor and process to desired consistency. Add salt (about 1/2 tsp) to taste. Serve chilled. Enjoy with your favorite chips or tortillas.','https://sparkpeo.hs.llnwd.net/e2/guid/Coach-Nicoles-Fresh-&-Skinny-Guacamole/2d0c18b7-6bf7-45c9-8858-d45734e9fcfa.jpg','2 ripe avocados, peeled and chopped\r\n1/3 medium organic* cucumber, chopped\r\n1/3 medium onion, chopped (I use red onion)\r\n1 garlic clove, minced\r\n1 tsp cumin powder\r\n1 squeeze lemon (about 1-2 Tbsp juice)\r\nSalt to taste (about 1/2 tsp)','Coach Nicole\'s Fresh & Skinny Guacamole',1); +/*!40000 ALTER TABLE `recipe` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `recipe_ingredient` +-- + +DROP TABLE IF EXISTS `recipe_ingredient`; +/*!40101 SET @saved_cs_client = @@character_set_client */; + SET character_set_client = utf8mb4 ; +CREATE TABLE `recipe_ingredient` ( + `id` int(11) NOT NULL, + `weight` int(11) DEFAULT NULL, + `recipe_id` int(11) DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `FK_RECIPE_INGREDIENT` (`recipe_id`), + CONSTRAINT `FK_RECIPE_INGREDIENT` FOREIGN KEY (`recipe_id`) REFERENCES `recipe` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `recipe_ingredient` +-- + +LOCK TABLES `recipe_ingredient` WRITE; +/*!40000 ALTER TABLE `recipe_ingredient` DISABLE KEYS */; +/*!40000 ALTER TABLE `recipe_ingredient` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `user` +-- + +DROP TABLE IF EXISTS `user`; +/*!40101 SET @saved_cs_client = @@character_set_client */; + SET character_set_client = utf8mb4 ; +CREATE TABLE `user` ( + `id` int(11) NOT NULL, + `email` varchar(255) DEFAULT NULL, + `pw_hash` varchar(255) DEFAULT NULL, + `user_role` int(11) DEFAULT NULL, + `username` varchar(255) DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `user` +-- + +LOCK TABLES `user` WRITE; +/*!40000 ALTER TABLE `user` DISABLE KEYS */; +/*!40000 ALTER TABLE `user` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `user_recipe` +-- + +DROP TABLE IF EXISTS `user_recipe`; +/*!40101 SET @saved_cs_client = @@character_set_client */; + SET character_set_client = utf8mb4 ; +CREATE TABLE `user_recipe` ( + `id` int(11) NOT NULL, + `recipe_id` int(11) NOT NULL, + `user_id` int(11) NOT NULL, + PRIMARY KEY (`id`), + KEY `FK_USER_RECIPE` (`recipe_id`), + KEY `FK_USER` (`user_id`), + CONSTRAINT `FK_USER` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`), + CONSTRAINT `FK_USER_RECIPE` FOREIGN KEY (`recipe_id`) REFERENCES `recipe` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `user_recipe` +-- + +LOCK TABLES `user_recipe` WRITE; +/*!40000 ALTER TABLE `user_recipe` DISABLE KEYS */; +/*!40000 ALTER TABLE `user_recipe` ENABLE KEYS */; +UNLOCK TABLES; +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; + +-- Dump completed on 2020-12-04 9:33:57 diff --git a/src/main/java/org/launchcode/recipeapp/AuthenticationFilter.java b/src/main/java/org/launchcode/recipeapp/AuthenticationFilter.java new file mode 100644 index 0000000..6349c88 --- /dev/null +++ b/src/main/java/org/launchcode/recipeapp/AuthenticationFilter.java @@ -0,0 +1,59 @@ +package org.launchcode.recipeapp; + + + +import org.launchcode.recipeapp.controllers.AuthenticationController; +import org.launchcode.recipeapp.models.User; +import org.launchcode.recipeapp.models.data.UserRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.io.IOException; +import java.util.Arrays; +import java.util.List; + +public class AuthenticationFilter extends HandlerInterceptorAdapter { + + @Autowired + UserRepository userRepository; + + @Autowired + AuthenticationController authenticationController; + + private static final List whitelist = Arrays.asList("/home", "/login", "/register", "/logout", "/css", "/recipes/all", "/recipes/display", "/search"); + + private static boolean isWhitelisted(String path) { + for (String pathRoot : whitelist) { + if (path.startsWith(pathRoot)) { + return true; + } + } + return false; + } + + @Override + public boolean preHandle(HttpServletRequest request, + HttpServletResponse response, + Object handler) throws IOException { + + if (isWhitelisted(request.getRequestURI())) { + return true; + + } + + HttpSession session = request.getSession(); + User user = authenticationController.getUserFromSession(session); + + // The user is logged in + if (user != null) { + return true; + } + + // The user is NOT logged in + response.sendRedirect("/home"); + return false; + } +} diff --git a/src/main/java/org/launchcode/recipeapp/WebApplicationConfig.java b/src/main/java/org/launchcode/recipeapp/WebApplicationConfig.java new file mode 100644 index 0000000..433ea72 --- /dev/null +++ b/src/main/java/org/launchcode/recipeapp/WebApplicationConfig.java @@ -0,0 +1,20 @@ +package org.launchcode.recipeapp; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class WebApplicationConfig implements WebMvcConfigurer { + + @Bean + public AuthenticationFilter authenticationFilter() { + return new AuthenticationFilter(); + } + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(authenticationFilter()); + } +} \ No newline at end of file diff --git a/src/main/java/org/launchcode/recipeapp/controllers/AuthenticationController.java b/src/main/java/org/launchcode/recipeapp/controllers/AuthenticationController.java new file mode 100644 index 0000000..e16ea0b --- /dev/null +++ b/src/main/java/org/launchcode/recipeapp/controllers/AuthenticationController.java @@ -0,0 +1,125 @@ +package org.launchcode.recipeapp.controllers; + +import org.launchcode.recipeapp.models.Category; +import org.launchcode.recipeapp.models.data.UserRepository; +import org.launchcode.recipeapp.models.dto.LoginFormDTO; +import org.launchcode.recipeapp.models.User; +import org.launchcode.recipeapp.models.dto.RegistrationFormDTO; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.validation.Errors; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PostMapping; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; +import javax.validation.Valid; +import java.util.Optional; + +@Controller +public class +AuthenticationController { + + @Autowired + UserRepository userRepository; + + private static final String userSessionKey = "user"; + + public User getUserFromSession(HttpSession session) { + return (User) session.getAttribute(userSessionKey); + } + + private static void setUserInSession(HttpSession session, User user) { + session.setAttribute(userSessionKey, user); + } + + @GetMapping("/register") + public String displayRegistrationForm(Model model) { + model.addAttribute(new RegistrationFormDTO()); + model.addAttribute("title", "Register"); + return "register"; + } + + @PostMapping("/register") + public String processRegistrationForm(@ModelAttribute @Valid RegistrationFormDTO registrationFormDTO, + Errors errors, HttpServletRequest request, + Model model) { + + if (errors.hasErrors()) { + model.addAttribute("title", "Register"); + return "register"; + } + + User existingUser = userRepository.findByUsername(registrationFormDTO.getUsername()); + + if (existingUser != null) { + errors.rejectValue("username", "username.already exists", "A user with that username already exists"); + model.addAttribute("title", "Register"); + return "register"; + } + + String password = registrationFormDTO.getPassword(); + String verifyPassword = registrationFormDTO.getVerifyPassword(); + if (!password.equals(verifyPassword)) { + errors.rejectValue("password", "passwords.mismatch", "Passwords do not match"); + model.addAttribute("title", "Register"); + return "register"; + } + + User newUser = new User(registrationFormDTO.getUsername(), registrationFormDTO.getPassword()); + userRepository.save(newUser); + setUserInSession(request.getSession(), newUser); + + return "redirect:/home"; + } + + @GetMapping("/login") + public String displayLoginForm(Model model) { + model.addAttribute(new LoginFormDTO()); + model.addAttribute("title", "Log In"); + model.addAttribute("categories", Category.values()); + return "login"; + } + + @PostMapping("/login") + public String processLoginForm(@ModelAttribute @Valid LoginFormDTO loginFormDTO, + Errors errors, HttpServletRequest request, + Model model) { + + if (errors.hasErrors()) { + model.addAttribute("title", "Log In"); + return "login"; + } + + + User theUser = userRepository.findByUsername(loginFormDTO.getUsername()); + + if (theUser == null) { + errors.rejectValue("username", "user.invalid", "The given username does not exist"); + model.addAttribute("title", "Log In"); + return "login"; + } + + String password = loginFormDTO.getPassword(); + + if (!theUser.isMatchingPassword(password)) { + errors.rejectValue("password", "password.invalid", "Invalid password"); + model.addAttribute("title", "Log In"); + return "login"; + } + + setUserInSession(request.getSession(), theUser); + + return "redirect:/home"; + } + + + @GetMapping("/logout") + public String logout(HttpServletRequest request){ + request.getSession().invalidate(); + return "redirect:/login"; + } + +} diff --git a/src/main/java/org/launchcode/recipeapp/controllers/HomeController.java b/src/main/java/org/launchcode/recipeapp/controllers/HomeController.java index 60ae7b5..b2c7625 100644 --- a/src/main/java/org/launchcode/recipeapp/controllers/HomeController.java +++ b/src/main/java/org/launchcode/recipeapp/controllers/HomeController.java @@ -1,16 +1,14 @@ package org.launchcode.recipeapp.controllers; -import org.launchcode.recipeapp.data.RecipeRepository; +import org.launchcode.recipeapp.models.Category; import org.launchcode.recipeapp.models.Recipe; +import org.launchcode.recipeapp.models.data.RecipeRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; -import java.util.ArrayList; -import java.util.List; - /** * @author Oksana */ @@ -24,15 +22,12 @@ public HomeController(RecipeRepository recipeRepository) { this.recipeRepository = recipeRepository; } - @GetMapping("") + @GetMapping("/home") public String home(Model model) { - model.addAttribute("title", "Saint Louis Best Recipes"); - - List recipes = new ArrayList<>(); Iterable all = recipeRepository.findAll(); - all.forEach(recipes::add); - - model.addAttribute("recipes", recipes); + model.addAttribute("categories", Category.values()); + model.addAttribute("recipes", all); + model.addAttribute("title", "Saint Louis Best Recipes"); return "index"; } diff --git a/src/main/java/org/launchcode/recipeapp/controllers/RecipeController.java b/src/main/java/org/launchcode/recipeapp/controllers/RecipeController.java index d087f3b..a61280e 100644 --- a/src/main/java/org/launchcode/recipeapp/controllers/RecipeController.java +++ b/src/main/java/org/launchcode/recipeapp/controllers/RecipeController.java @@ -1,26 +1,25 @@ package org.launchcode.recipeapp.controllers; -import org.launchcode.recipeapp.data.RecipeRepository; -import org.launchcode.recipeapp.models.Category; -import org.launchcode.recipeapp.models.Recipe; -import org.launchcode.recipeapp.models.Tag; +import org.launchcode.recipeapp.models.*; +import org.launchcode.recipeapp.models.data.RecipeRepository; +import org.launchcode.recipeapp.models.data.ReviewRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.validation.Errors; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.servlet.mvc.support.RedirectAttributes; import javax.validation.Valid; +import java.util.ArrayList; +import java.util.List; import java.util.Optional; -/** - * @author Oksana - */ @Controller @RequestMapping("recipes") public class RecipeController { @@ -32,6 +31,9 @@ public RecipeController(RecipeRepository recipeRepository) { this.recipeRepository = recipeRepository; } + @Autowired + public ReviewRepository reviewRepository; + @GetMapping public String getListOfRecipes(Model model) { Iterable recipes = recipeRepository.findAll(); @@ -50,7 +52,7 @@ public String createRecipe(Model model) { model.addAttribute("categories", categories); model.addAttribute("tags", tags); - return "recipes/create"; + return "/recipes/create"; } @PostMapping("create") @@ -72,19 +74,162 @@ public String createRecipe(@ModelAttribute @Valid Recipe newRecipe, @GetMapping("display") public String displayRecipe(@RequestParam Integer recipeId, Model model) { - + model.addAttribute("review", new Review()); + model.addAttribute("categories", Category.values()); Optional result = recipeRepository.findById(recipeId); - if (result.isEmpty()) { + if (result.isEmpty()) { // invalid id model.addAttribute("title", "Invalid Recipe ID: " + recipeId); - } else { + } else { // valid id Recipe recipe = result.get(); + model.addAttribute("title", recipe.getName()); + model.addAttribute("recipe", recipe); + + Integer numComments = recipe.getNumComments(); + List reviews = recipe.getReviews(); + + if (reviews.isEmpty()) { // no reviews + model.addAttribute("numRatings", "0"); + model.addAttribute("averageRating", "No ratings"); + model.addAttribute("comments", "No comments yet"); + } else { // has reviews + model.addAttribute("averageRating", recipe.getAverageRating()); + model.addAttribute("numRatings", recipe.getReviews().size()); + + if(numComments != 0){ // has comments + model.addAttribute("comments", "Comments"); + } else if (numComments == 0 || numComments == null){ // no comments + model.addAttribute("comments", "No comments yet"); + } + } + + } + + return "recipes/display"; + } + @PostMapping("display") + public String processReviewForm(@ModelAttribute @Valid Review newReview, Errors errors, + @RequestParam Integer recipeId, + Model model) { + System.out.println(errors.hasErrors()); + Recipe recipe = recipeRepository.findById(recipeId).get(); + + if (errors.hasErrors()) { model.addAttribute("title", recipe.getName()); model.addAttribute("recipe", recipe); + model.addAttribute("averageRating", recipe.getAverageRating()); + model.addAttribute("numRatings", recipe.getReviews().size()); + Integer numComments = recipe.getNumComments(); + + if(numComments != 0){ // has comments + model.addAttribute("comments", "Comments"); + } else if (numComments == 0 || numComments == null){ // no comments + model.addAttribute("comments", "No comments yet"); + } + return "recipes/display"; } + Review review = new Review(recipe, newReview.getRating(),newReview.getComment(), newReview.getName()); + + review.setTimestamp(); + reviewRepository.save(review); + recipe.setAverageRating(); + recipe.setNumComments(review); + recipeRepository.save(recipe); + + model.addAttribute("title", recipe.getName()); + model.addAttribute("recipe", recipe); + model.addAttribute("review", review); + model.addAttribute("averageRating", recipe.getAverageRating()); + + model.addAttribute("numRatings", recipe.getReviews().size()); + + Integer numComments = recipe.getNumComments(); + if(numComments != 0){ // has comments + model.addAttribute("comments", "Comments"); + } else if (numComments == 0 || numComments == null){ // no comments + model.addAttribute("comments", "No comments yet"); + } return "recipes/display"; } + + @GetMapping("all") + public String getAllRecipes (Model model){ + + List all = ((List) recipeRepository.findAll()); + + model.addAttribute("recipes", all); + model.addAttribute("categories", Category.values()); + + return "recipes/all"; + + } + + @GetMapping("edit/{recipeId}") + public String displayEditForm(Model model, @PathVariable int recipeId) { + + Category[] categories = Category.values(); + Tag[] tags = Tag.values(); + Optional recipeOpt = recipeRepository.findById(recipeId); + if (recipeOpt.isPresent()) { + Recipe recipe = recipeOpt.get(); + model.addAttribute("recipe", recipe); + model.addAttribute("title", "Edit recipe " + recipe.getName()); + model.addAttribute("recipeId", recipe.getId()); + } else { + model.addAttribute("recipe", new Recipe()); + } + model.addAttribute("categories", categories); + model.addAttribute("tags", tags); + + return "recipes/edit"; + + } + + @PostMapping("edit") + public String processEditForm(Integer recipeId, @ModelAttribute @Valid Recipe newRecipe, + Errors errors, Model model, RedirectAttributes redirectAttrs) { + if (errors.hasErrors()) { + model.addAttribute("title", "Edit Recipe"); + return "recipes/edit"; + } + Optional recipeOpt = recipeRepository.findById(recipeId); + if (recipeOpt.isPresent()) { + Recipe recipe = recipeOpt.get(); + recipe.setCategory(newRecipe.getCategory()); + recipe.setDirections(newRecipe.getDirections()); + recipe.setImg(newRecipe.getImg()); + recipe.setIngredients(newRecipe.getIngredients()); + recipe.setName(newRecipe.getName()); + recipe.setTag(newRecipe.getTag()); + + + Recipe savedRecipe = recipeRepository.save(recipe); + Iterable recipes = recipeRepository.findAll(); + + redirectAttrs.addAttribute("recipes", recipes); + + } + return "redirect:"; + + } + + @RequestMapping("/delete/{recipeId}") + public String handleDeleteUser(@PathVariable Integer recipeId) { + Optional recipeOpt = recipeRepository.findById(recipeId); + if (recipeOpt.isPresent()) { + recipeRepository.deleteById(recipeId); + } + + return "redirect:/recipes"; + } + + @RequestMapping("/save/{recipeId}") + public String saveRecipeToUser(@PathVariable Integer recipeId) { + return "index"; + } + } + diff --git a/src/main/java/org/launchcode/recipeapp/controllers/SearchController.java b/src/main/java/org/launchcode/recipeapp/controllers/SearchController.java new file mode 100644 index 0000000..48b9ee6 --- /dev/null +++ b/src/main/java/org/launchcode/recipeapp/controllers/SearchController.java @@ -0,0 +1,176 @@ +package org.launchcode.recipeapp.controllers; + + +import org.launchcode.recipeapp.models.Category; +import org.launchcode.recipeapp.models.Recipe; +import org.launchcode.recipeapp.models.SortParameter; +import org.launchcode.recipeapp.models.Tag; +import org.launchcode.recipeapp.models.data.RecipeRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.*; + +import java.util.*; + +@Controller +@RequestMapping("search") +public class SearchController { + + @Autowired + private RecipeRepository recipeRepository; + + List foundRecipes = new ArrayList<>(); + + + @ModelAttribute + public void initValues(Model model) { + model.addAttribute("filterTypes", Tag.values()); + } + + @GetMapping("") + public String renderSearch(Model model) { + model.addAttribute("categories", Category.values()); + model.addAttribute("sort", SortParameter.values()); + model.addAttribute("filterTypes", Tag.values()); + + return "search"; + } + + // Search by keyword + @PostMapping(value = "/keywordResults") + public String searchRecipeByKeyword(Model model, @RequestParam String keyword) { + foundRecipes.clear(); + String lower_val = keyword.toLowerCase(); + + List recipeList = new ArrayList<>(); + Iterable recipesIter = recipeRepository.findAll(); + recipesIter.forEach(recipeList::add); + + for (Recipe recipe : recipeList) { + if (recipe.getName().toLowerCase().contains(lower_val)) { + foundRecipes.add(recipe); + } else if (recipe.getIngredients().toLowerCase().contains(lower_val)) { + foundRecipes.add(recipe); + } else if (recipe.getDirections().toLowerCase().contains(lower_val)) { + foundRecipes.add(recipe); + } else if (recipe.getCategory().toString().toLowerCase().contains(lower_val)) { + foundRecipes.add(recipe); + } else if (recipe.getTag().toString().toLowerCase().contains(lower_val)) { + foundRecipes.add(recipe); + } + } + model.addAttribute("recipes", foundRecipes); + model.addAttribute("categories", Category.values()); + model.addAttribute("sort", SortParameter.values()); + return "search"; + } + + + //filter by category + @PostMapping(value = "/categoryResults") + public String searchRecipeByCategory(@RequestParam Category category, Model model) { + foundRecipes.clear(); + Iterable recipes = recipeRepository.findAll(); + + if (category == null) { + for (Recipe recipe : recipes) { + foundRecipes.add(recipe); + model.addAttribute("recipes", foundRecipes); + model.addAttribute("categories", Category.values()); + model.addAttribute("sort", SortParameter.values()); + } + } else { + for (Recipe recipe : recipes) { + if (recipe.getCategory().name().toLowerCase().equals(category.name().toLowerCase())) { + foundRecipes.add(recipe); + } + } + model.addAttribute("recipes", foundRecipes); + model.addAttribute("categories", Category.values()); + model.addAttribute("sort", SortParameter.values()); + + } + return "search"; + } + + + //sort search results + @PostMapping(value = "/sort") + public String sortSearchResults(@RequestParam SortParameter sortParameter, Model model) { + Iterable recipes = foundRecipes; + + if ((sortParameter.getName().equals("Recipe Name: A-Z"))) { + List sortedRecipes = new ArrayList<>(); + for (Recipe recipe : recipes) { + sortedRecipes.add(recipe); + Collections.sort(sortedRecipes, new Recipe.SortByNameAsc()); + + } + model.addAttribute("recipes", sortedRecipes); + model.addAttribute("categories", Category.values()); + model.addAttribute("sort", SortParameter.values()); + + //sort is descending name + } else if ((sortParameter.getName().equals("Recipe Name: Z-A"))) { + List sortedRecipes = new ArrayList<>(); + for (Recipe recipe : recipes) { + sortedRecipes.add(recipe); + Collections.sort(sortedRecipes, new Recipe.SortByNameDesc()); + + } + model.addAttribute("recipes", sortedRecipes); + model.addAttribute("categories", Category.values()); + model.addAttribute("sort", SortParameter.values()); + + //sort is ascending averageRating + } else if ((sortParameter.getName().equals("Average Rating: High-Low"))) { + List sortedRecipes = new ArrayList<>(); + for (Recipe recipe : recipes) { + sortedRecipes.add(recipe); + Collections.sort(sortedRecipes, new Recipe.SortByRatingAsc()); + + } + model.addAttribute("recipes", sortedRecipes); + model.addAttribute("categories", Category.values()); + model.addAttribute("sort", SortParameter.values()); + + //sort is descending averageRating + } else if ((sortParameter.getName().equals("Average Rating: Low-High"))) { + List sortedRecipes = new ArrayList<>(); + for (Recipe recipe : recipes) { + sortedRecipes.add(recipe); + Collections.sort(sortedRecipes, new Recipe.SortByRatingDsc()); + + } + model.addAttribute("recipes", sortedRecipes); + model.addAttribute("categories", Category.values()); + model.addAttribute("sort", SortParameter.values()); + + } + return "search"; + } + + + // filter search results + @PostMapping(value = "/filter") + public String filterSearchResults(@RequestParam Tag tag, Model model) { + Iterable recipes = foundRecipes; + List filteredRecipe = new ArrayList<>(); + + for (Recipe recipe : recipes) { + for (Tag tagValue : tag.values()) { + if (recipe.getTag().getName().toLowerCase().equals(tagValue.getName().toLowerCase())) { + filteredRecipe.add(recipe); + + model.addAttribute("recipes", filteredRecipe); + model.addAttribute("categories", Category.values()); + model.addAttribute("sort", SortParameter.values()); + } + + } + } + return "search"; + } + + } \ No newline at end of file diff --git a/src/main/java/org/launchcode/recipeapp/controllers/UserController.java b/src/main/java/org/launchcode/recipeapp/controllers/UserController.java new file mode 100644 index 0000000..c31c7aa --- /dev/null +++ b/src/main/java/org/launchcode/recipeapp/controllers/UserController.java @@ -0,0 +1,117 @@ +package org.launchcode.recipeapp.controllers; + +import org.launchcode.recipeapp.models.Recipe; +import org.launchcode.recipeapp.models.SortParameter; +import org.launchcode.recipeapp.models.User; +import org.launchcode.recipeapp.models.UserRecipe; +import org.launchcode.recipeapp.models.data.RecipeRepository; +import org.launchcode.recipeapp.models.data.UserRecipeRepository; +import org.launchcode.recipeapp.models.data.UserRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.servlet.mvc.support.RedirectAttributes; + +import javax.servlet.http.HttpServletRequest; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +/** + * @author Oksana + */ +@Controller +@RequestMapping("users") +public class UserController { + + @Autowired + private UserRepository userRepository; + + @Autowired + private UserRecipeRepository userRecipeRepository; + + @Autowired + private RecipeRepository recipeRepository; + + + @GetMapping() + public String getAllUsers(Model model) { + List users = new ArrayList<>(); + + Iterable usersIter = userRepository.findAll(); + + + usersIter.forEach(users::add); + + + model.addAttribute("users", users); + return "users/index"; + } + + @GetMapping("/profile") + public String getUserProfile(HttpServletRequest request, Model model, RedirectAttributes redirectAttributes) { + User sessionUser = (User) request.getSession().getAttribute("user"); + if (sessionUser == null) { + model.addAttribute("title", "No user found"); + } else { + List recipes = new ArrayList<>(); + List userRecipes = userRecipeRepository.getAllByUser(sessionUser); + + for (UserRecipe userRecipe : userRecipes) { + Recipe recipe = userRecipe.getRecipe(); + recipes.add(recipe); + } + model.addAttribute("sort", SortParameter.values()); + model.addAttribute("title", sessionUser.getUsername()); + model.addAttribute("user", sessionUser); + model.addAttribute("recipes", recipes); + model.addAttribute("title1", redirectAttributes.getAttribute("title1")); + + } + return "users/profile"; + } + + @PostMapping("/addRecipe/{id}") + public String addRecipe(@PathVariable Integer id, HttpServletRequest request, Model model, + RedirectAttributes redirectAttrs) { + User sessionUser = (User) request.getSession().getAttribute("user"); + + Optional recipeOptional = recipeRepository.findById(id); + if (recipeOptional.isPresent()) { + User user = userRepository.getById(sessionUser.getId()); + Recipe recipe = recipeOptional.get(); + + + UserRecipe userRecipe = new UserRecipe(); + userRecipe.setUser(user); + userRecipe.setRecipe(recipe); + + List allByUser = userRecipeRepository.getAllByUser(user); + for (UserRecipe userrecipe:allByUser) { + + if (!userrecipe.getRecipe().equals(recipe)) { + userRecipeRepository.save(userRecipe); + } else { + model.addAttribute("title1", "This recipe has already been added to your profile "); + model.addAttribute("recipe", recipe); + return "recipes/display"; + } + } + + model.addAttribute("user", userRecipe.getUser()); + model.addAttribute("recipe", userRecipe.getRecipe()); + } + + return "redirect:/users/profile"; + + } +} + + + + + diff --git a/src/main/java/org/launchcode/recipeapp/models/Category.java b/src/main/java/org/launchcode/recipeapp/models/Category.java index 2ea0a1b..92a507f 100644 --- a/src/main/java/org/launchcode/recipeapp/models/Category.java +++ b/src/main/java/org/launchcode/recipeapp/models/Category.java @@ -1,12 +1,23 @@ package org.launchcode.recipeapp.models; -/** - * @author Oksana - */ public enum Category { - BEVERAGES, APPETIZER, ENTREE, SOUP, SALAD, DESSERT; + BEVERAGES ("Beverage"), + APPETIZER ("Appetizer"), + ENTREE ("Entree"), + SOUP ("Soup"), + SIDES ("Side"), + DESSERT ("Dessert"); + private String name; + + Category(String name) { + this.name = name; + } + + public String getName() { + return name; + } } diff --git a/src/main/java/org/launchcode/recipeapp/models/FilterTypes.java b/src/main/java/org/launchcode/recipeapp/models/FilterTypes.java new file mode 100644 index 0000000..b7cda8f --- /dev/null +++ b/src/main/java/org/launchcode/recipeapp/models/FilterTypes.java @@ -0,0 +1,21 @@ +package org.launchcode.recipeapp.models; + +public enum FilterTypes { + + BEVERAGES ("Beverage"), + APPETIZER ("Appetizer"), + ENTREE ("Entree"), + SOUP ("Soup"), + SIDES ("Side"), + DESSERT ("Dessert"); + + private String name; + + FilterTypes (String name) { + this.name = name; + } + + public String getName() { + return name; + } + } diff --git a/src/main/java/org/launchcode/recipeapp/models/Recipe.java b/src/main/java/org/launchcode/recipeapp/models/Recipe.java index f1ad6f6..e97a205 100644 --- a/src/main/java/org/launchcode/recipeapp/models/Recipe.java +++ b/src/main/java/org/launchcode/recipeapp/models/Recipe.java @@ -5,11 +5,9 @@ import javax.persistence.OneToMany; import javax.validation.constraints.NotNull; import java.util.ArrayList; +import java.util.Comparator; import java.util.List; -/** - * @author Oksana - */ @Entity public class Recipe extends AbstractEntity { @@ -23,17 +21,71 @@ public class Recipe extends AbstractEntity { @NotNull(message = "Category required") private Category category; + private SortParameter sortParameter; + private Tag tag; private String img; + private Double averageRating; + private Integer numComments = 0; + + @OneToMany(mappedBy = "recipe") + private final List reviews = new ArrayList<>(); + @OneToMany(mappedBy = "recipe", cascade = {CascadeType.MERGE, CascadeType.REMOVE}) @NotNull(message = "User is required") private List users = new ArrayList<>(); + + //// Used for sorting in ascending order of name + public static class SortByNameAsc implements Comparator { + public int compare(Recipe a, Recipe b) { + return a.name.compareTo(b.name); + } + + } + + //// Used for sorting in descending order of name + public static class SortByNameDesc implements Comparator { + public int compare(Recipe a, Recipe b) { + return b.name.compareTo(a.name); + } + + } + + //// Used for sorting in ascending order of averageRating + public static class SortByRatingAsc implements Comparator { + public int compare(Recipe a, Recipe b) { + if (a.averageRating == null && b.averageRating == null) + return 0; + if (a.averageRating == null) + return 1; + else if (b.averageRating == null) + return -1; + return b.averageRating.compareTo(a.averageRating); + } + } + + //// Used for sorting in descending order of averageRating + public static class SortByRatingDsc implements Comparator { + public int compare(Recipe a, Recipe b) { + if (a.averageRating == null && b.averageRating == null) + return 0; + if (b.averageRating == null) + return 1; + else if (a.averageRating == null) + return -1; + return a.averageRating.compareTo(b.averageRating); + } + } + + public Recipe() { } + + // Getters and Setters public String getName() { return name; } @@ -42,7 +94,6 @@ public void setName(String name) { this.name = name; } - public Category getCategory() { return category; } @@ -51,6 +102,10 @@ public void setCategory(Category category) { this.category = category; } + public SortParameter getSortParameter() { + return sortParameter; + } + public List getUsers() { return users; } @@ -90,4 +145,60 @@ public Tag getTag() { public void setTag(Tag tag) { this.tag = tag; } -} + + public List getReviews() { + return reviews; + } + + + public Double getAverageRating() { + return averageRating; + } + + public void setAverageRating() { + List reviewList = getReviews(); + double numRatings = reviewList.size(); + double sumRatings = 0.0; + + for(int i =0; i < numRatings; i++){ + double reviewRating = reviewList.get(i).getRating(); + sumRatings += reviewRating; + } + double average = Double.parseDouble(String.format("%.1f",(double)sumRatings / numRatings)); + averageRating = average; + } + + public Integer getNumComments() { + return numComments; + } + + + public Integer setNumComments(Review review){ + if(numComments == null){ + numComments = 0; + } + if (!review.getComment().isEmpty()){ + numComments ++; + } + return numComments; + } + + + + @Override + public String toString() { + return "Recipe{" + + "name='" + name + '\'' + + ", ingredients='" + ingredients + '\'' + + ", directions='" + directions + '\'' + + ", category=" + category + + ", tag=" + tag + + ", img='" + img + '\'' + + ", averageRating=" + averageRating + + ", reviews=" + reviews + + ", users=" + users + + '}'; + } + + +} \ No newline at end of file diff --git a/src/main/java/org/launchcode/recipeapp/models/Review.java b/src/main/java/org/launchcode/recipeapp/models/Review.java new file mode 100644 index 0000000..d67be34 --- /dev/null +++ b/src/main/java/org/launchcode/recipeapp/models/Review.java @@ -0,0 +1,88 @@ +package org.launchcode.recipeapp.models; + +import javax.persistence.Entity; +import javax.persistence.ManyToOne; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +@Entity +public class Review extends AbstractEntity{ + + @ManyToOne + private Recipe recipe; + + @NotBlank(message = "Name is required") + @Size(min=2, message = "Name is required") + private String name; + + @Size(max=250, message = "Comments must be under 250 characters") + private String comment; + + @NotNull(message = "Rating is required") + private Integer rating; + + private String timestamp; + + + + public Review(Recipe recipe, Integer rating, String comment, String name) { + this(); + this.recipe = recipe; + this.rating = rating; + this.comment = comment; + this.name = name; + } + + public Review() { + } + + // getters and setters + public Recipe getRecipe() { + return recipe; + } + + public void setRecipe(Recipe recipe) { + this.recipe = recipe; + } + + public Integer getRating() { + return rating; + } + + public void setRating(Integer rating) { + this.rating = rating; + } + + public String getComment() { + return comment; + } + + public void setComment(String comment) { + this.comment = comment; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getTimestamp() { + return timestamp; + } + + public void setTimestamp() { + LocalDateTime timestampObj = LocalDateTime.now(); + DateTimeFormatter dateObj = DateTimeFormatter.ofPattern("MMMM d, yyyy"); + DateTimeFormatter timeObj = DateTimeFormatter.ofPattern("h:mm a"); + String dateTime = timestampObj.format(dateObj) + " at " + timestampObj.format(timeObj); + timestamp = dateTime; + } + + +} diff --git a/src/main/java/org/launchcode/recipeapp/models/SortParameter.java b/src/main/java/org/launchcode/recipeapp/models/SortParameter.java new file mode 100644 index 0000000..eb07c89 --- /dev/null +++ b/src/main/java/org/launchcode/recipeapp/models/SortParameter.java @@ -0,0 +1,22 @@ +package org.launchcode.recipeapp.models; + +public enum SortParameter { + NAME_ASCENDING ("Recipe Name: A-Z"), + NAME_DESCENDING ("Recipe Name: Z-A"), + RATING_ASCENDING ("Average Rating: High-Low"), + RATING_DESCENDING ("Average Rating: Low-High"), + TIME_ASCENDING ("Total Time: High-Low"), + TIME_DESCENDING ("Total Time: Low-High"); + + + private String name; + + SortParameter(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} + diff --git a/src/main/java/org/launchcode/recipeapp/models/Tag.java b/src/main/java/org/launchcode/recipeapp/models/Tag.java index d234337..269c475 100644 --- a/src/main/java/org/launchcode/recipeapp/models/Tag.java +++ b/src/main/java/org/launchcode/recipeapp/models/Tag.java @@ -4,6 +4,20 @@ * @author Oksana */ public enum Tag { - GLUTEN_FREE, VEGETARIAN, NUT_FREE, DAIRY_FREE, MEAT, SEAFOOD; + GLUTEN_FREE ("Gluten Free"), + VEGETARIAN("Vegetarian"), + NUT_FREE ("Nut Free"), + DAIRY_FREE ("Dairy Free"), + MEAT ("Meat"), + SEAFOOD ("Seafood"); + private String name; + + Tag(String name) { + this.name = name; + } + + public String getName() { + return name; + } } diff --git a/src/main/java/org/launchcode/recipeapp/models/User.java b/src/main/java/org/launchcode/recipeapp/models/User.java index ff9bf7d..d228489 100644 --- a/src/main/java/org/launchcode/recipeapp/models/User.java +++ b/src/main/java/org/launchcode/recipeapp/models/User.java @@ -1,5 +1,7 @@ package org.launchcode.recipeapp.models; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; + import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; @@ -7,6 +9,7 @@ import javax.persistence.Table; import javax.validation.constraints.Email; import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; import java.util.ArrayList; import java.util.List; @@ -14,15 +17,16 @@ * @author Oksana */ @Entity -@Table public class User extends AbstractEntity { - @NotBlank(message = "User userName name is required") + /* @updated AH*/ + + private static final BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); + + + @NotBlank(message = "UserName name is required") private String username; - @NotBlank(message = "Email is required") - @Email(message = "Invalid email. Try again.") - private String email; @NotBlank(message = "Password is required") private String pwHash; @@ -37,34 +41,18 @@ public class User extends AbstractEntity { public User() { } - public User(String username, String email, String pwHash) { + public User(String username, String password) { this.username = username; - this.email = email; - this.pwHash = pwHash; + this.pwHash = encoder.encode(password); } public String getUsername() { return username; } - public void setUsername(String username) { - this.username = username; - } - - public String getEmail() { - return email; - } - - public void setEmail(String email) { - this.email = email; - } - - public String getPwHash() { - return pwHash; - } + public boolean isMatchingPassword(String password) { + return encoder.matches(password, pwHash); - public void setPwHash(String pwHash) { - this.pwHash = pwHash; } public Role getRole() { diff --git a/src/main/java/org/launchcode/recipeapp/models/UserRecipe.java b/src/main/java/org/launchcode/recipeapp/models/UserRecipe.java index 4904707..9322cc7 100644 --- a/src/main/java/org/launchcode/recipeapp/models/UserRecipe.java +++ b/src/main/java/org/launchcode/recipeapp/models/UserRecipe.java @@ -1,5 +1,8 @@ package org.launchcode.recipeapp.models; +import lombok.Data; +import lombok.EqualsAndHashCode; + import javax.persistence.CascadeType; import javax.persistence.Entity; import javax.persistence.FetchType; @@ -12,6 +15,8 @@ /** * @author Oksana */ +@Data +@EqualsAndHashCode(callSuper = true) @Entity @Table public class UserRecipe extends AbstractEntity { @@ -33,4 +38,20 @@ public class UserRecipe extends AbstractEntity { foreignKey = @ForeignKey(name = "FK_USER_RECIPE")) @NotNull(message = "") private Recipe recipe; + + public User getUser() { + return user; + } + + public void setUser(User user) { + this.user = user; + } + + public Recipe getRecipe() { + return recipe; + } + + public void setRecipe(Recipe recipe) { + this.recipe = recipe; + } } diff --git a/src/main/java/org/launchcode/recipeapp/data/RecipeRepository.java b/src/main/java/org/launchcode/recipeapp/models/data/RecipeRepository.java similarity index 71% rename from src/main/java/org/launchcode/recipeapp/data/RecipeRepository.java rename to src/main/java/org/launchcode/recipeapp/models/data/RecipeRepository.java index 0b34639..0ec92ae 100644 --- a/src/main/java/org/launchcode/recipeapp/data/RecipeRepository.java +++ b/src/main/java/org/launchcode/recipeapp/models/data/RecipeRepository.java @@ -1,4 +1,5 @@ -package org.launchcode.recipeapp.data; +package org.launchcode.recipeapp.models.data; + import org.launchcode.recipeapp.models.Recipe; import org.springframework.data.repository.CrudRepository; @@ -7,5 +8,5 @@ * @author Oksana */ public interface RecipeRepository extends CrudRepository { - Recipe findByName (String name); + Recipe findByName (String name); } diff --git a/src/main/java/org/launchcode/recipeapp/models/data/ReviewRepository.java b/src/main/java/org/launchcode/recipeapp/models/data/ReviewRepository.java new file mode 100644 index 0000000..f1a1577 --- /dev/null +++ b/src/main/java/org/launchcode/recipeapp/models/data/ReviewRepository.java @@ -0,0 +1,10 @@ +package org.launchcode.recipeapp.models.data; + +import org.launchcode.recipeapp.models.Review; +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface ReviewRepository extends CrudRepository { + +} diff --git a/src/main/java/org/launchcode/recipeapp/models/data/UserRecipeRepository.java b/src/main/java/org/launchcode/recipeapp/models/data/UserRecipeRepository.java new file mode 100644 index 0000000..dbb33f0 --- /dev/null +++ b/src/main/java/org/launchcode/recipeapp/models/data/UserRecipeRepository.java @@ -0,0 +1,16 @@ +package org.launchcode.recipeapp.models.data; + +import org.launchcode.recipeapp.models.User; +import org.launchcode.recipeapp.models.UserRecipe; +import org.springframework.data.repository.CrudRepository; + +import java.util.List; + +/** + * @author Oksana + */ +public interface UserRecipeRepository extends CrudRepository { + + List getAllByUser(User user); + +} diff --git a/src/main/java/org/launchcode/recipeapp/data/UserRepository.java b/src/main/java/org/launchcode/recipeapp/models/data/UserRepository.java similarity index 67% rename from src/main/java/org/launchcode/recipeapp/data/UserRepository.java rename to src/main/java/org/launchcode/recipeapp/models/data/UserRepository.java index 950c202..0ec8100 100644 --- a/src/main/java/org/launchcode/recipeapp/data/UserRepository.java +++ b/src/main/java/org/launchcode/recipeapp/models/data/UserRepository.java @@ -1,14 +1,12 @@ -package org.launchcode.recipeapp.data; +package org.launchcode.recipeapp.models.data; import org.launchcode.recipeapp.models.User; import org.springframework.data.repository.CrudRepository; import org.springframework.stereotype.Repository; -/** - * @author Oksana - */ @Repository public interface UserRepository extends CrudRepository { + User findByUsername(String username); - User findByUsername (String userName); + User getById(Integer id); } diff --git a/src/main/java/org/launchcode/recipeapp/models/dto/ActiveRecipeDTO.java b/src/main/java/org/launchcode/recipeapp/models/dto/ActiveRecipeDTO.java new file mode 100644 index 0000000..e531b35 --- /dev/null +++ b/src/main/java/org/launchcode/recipeapp/models/dto/ActiveRecipeDTO.java @@ -0,0 +1,23 @@ +package org.launchcode.recipeapp.models.dto; + +import lombok.Data; +import org.launchcode.recipeapp.models.Recipe; + +/** + * @author Oksana + */ +@Data +public class ActiveRecipeDTO { + + private Recipe recipe; + + private boolean isActive; + + public Recipe getRecipe() { + return recipe; + } + + public boolean isActive() { + return isActive; + } +} diff --git a/src/main/java/org/launchcode/recipeapp/models/dto/LoginFormDTO.java b/src/main/java/org/launchcode/recipeapp/models/dto/LoginFormDTO.java new file mode 100644 index 0000000..0f71083 --- /dev/null +++ b/src/main/java/org/launchcode/recipeapp/models/dto/LoginFormDTO.java @@ -0,0 +1,35 @@ +package org.launchcode.recipeapp.models.dto; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; + +public class LoginFormDTO { + + @NotNull + @NotBlank + @Size(min = 3, max = 20, message = "Invalid username. Must be between 3 and 20 characters.") + private String username; + + @NotNull + @NotBlank + @Size(min = 5, max = 30, message = "Invalid password. Must be between 5 and 30 characters.") + private String password; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + +} diff --git a/src/main/java/org/launchcode/recipeapp/models/dto/RegistrationFormDTO.java b/src/main/java/org/launchcode/recipeapp/models/dto/RegistrationFormDTO.java new file mode 100644 index 0000000..02ce0cb --- /dev/null +++ b/src/main/java/org/launchcode/recipeapp/models/dto/RegistrationFormDTO.java @@ -0,0 +1,15 @@ +package org.launchcode.recipeapp.models.dto; + +public class RegistrationFormDTO extends LoginFormDTO { + + private String verifyPassword; + + public String getVerifyPassword() { + return verifyPassword; + } + + public void setVerifyPassword(String verifyPassword) { + this.verifyPassword = verifyPassword; + } + +} \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 1529f2a..3e0d309 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,14 +1,18 @@ -# Datasource MySql + +# Database connection settings spring.datasource.url=jdbc:mysql://localhost:3306/recipes spring.datasource.username=recipe-app -spring.datasource.password=1password -spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver +spring.datasource.password=1Password! + # Specify the DBMS spring.jpa.database = MYSQL + # Show or not log for each sql query spring.jpa.show-sql = true + # Hibernate ddl auto (create, create-drop, update) spring.jpa.hibernate.ddl-auto = update + # Use spring.jpa.properties.* for Hibernate native properties (the prefix is # stripped before adding them to the entity manager) spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL8Dialect diff --git a/src/main/resources/static/css/rating.css b/src/main/resources/static/css/rating.css index cc7d6bd..7cbf28b 100644 --- a/src/main/resources/static/css/rating.css +++ b/src/main/resources/static/css/rating.css @@ -1,50 +1,29 @@ -.rating-area { - overflow: hidden; - width: 900px; - margin: 0 auto; - background-color: #a89a76; -} -.rating-area:not(:checked) > input { - display: none; -} -.rating-area:not(:checked) > label { - float: none ; - margin-left: 18px; - width: 42px; - padding: 0; - cursor: pointer; - font-size: 32px; - line-height: 32px; - color: lightgrey; - text-shadow: 1px 1px #bbb; -} -.rating-area:not(:checked) > label:before { - content: '★'; +fieldset, label { margin: 0; padding: 0; } +.rating { + border: none; + float: left; } -.rating-area > input:checked ~ label { - color: gold; - text-shadow: 1px 1px #c60; +.rating > input { display: none; } +.rating > label:before { + margin: 5px; + font-size: 1.25em; + font-family: FontAwesome; + display: inline-block; + content: "\f005"; } -.rating-area:not(:checked) > label:hover, -.rating-area:not(:checked) > label:hover ~ label { - color: gold; -} -.rating-area > input:checked + label:hover, -.rating-area > input:checked + label:hover ~ label, -.rating-area > input:checked ~ label:hover, -.rating-area > input:checked ~ label:hover ~ label, -.rating-area > label:hover ~ input:checked ~ label { - color: gold; - text-shadow: 1px 1px goldenrod; -} -.rate-area > label:active { - position: relative; - background-color: #a89a76; -} -.btn-primary { - width: 220px; + +.rating > label { + color: #ddd; + float: right; } +/***** Highlight Stars on Hover *****/ +.rating > input:checked ~ label, /* show gold star when clicked */ +.rating:not(:checked) > label:hover, /* hover current star */ +.rating:not(:checked) > label:hover ~ label { color: #FFD700; } /* hover previous stars in list */ + +.rating > input:checked + label:hover, /* hover current star when changing rating */ +.rating > input:checked ~ label:hover, \ No newline at end of file diff --git a/src/main/resources/static/css/style.css b/src/main/resources/static/css/style.css index af3b406..3064da8 100644 --- a/src/main/resources/static/css/style.css +++ b/src/main/resources/static/css/style.css @@ -1,31 +1,48 @@ + body { - background-color: #c2abab; - background-image: url("photo/BG1.jpg"); + background-color: white; +/* background-image: url("photo/BG1.jpg");*/ background-size: cover; } + +.container-xxl { + max-width: 900px; + /*background-color: blue;*/ + +} +.navbar > .container-xxl { + display: flex; + flex-wrap: inherit; + align-items: center; + justify-content: flex-end; + flex-direction: row; + /*background-color: blue;*/ +} section { margin-top: 72px; } .card-img-top { - width: 100%; height: 420px; } .card { margin-left: 190px; - width: 70%; + width: 100%; border: solid 2px; border-radius: 5px; border-color: #91701f; - /*height: 420px;*/ + } .card-body { background-color: #d1cab8; } -.container-xxl { - width: 1200px; + +.container { + display: flex; + justify-content: center; + max-width: 1320px; } .offset-md-1{ width: 970px; @@ -33,26 +50,36 @@ section { .centered { display: flex; justify-content: center; - color: #543f09; + color: #4d3b0b; font-weight: bold; } .img-big { - margin-left: 200px; - width: 900px; - height: 450px; + margin-left: 55px; + width: 750px; + /*width: 680px;*/ + height: 400px; border: solid 1px; border-radius: 7px; border-color: #91701f; } .table-striped { - margin-left: 200px; - width: 900px; - background-color: #e6e2d8; + margin-left: 54px; + width: 680px; + background-color: bisque; } .save_button { display: flex; justify-content: end; } + +.carousel-control-next-icon { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23000' viewBox='0 0 16 16'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e"); +} + +.carousel-control-prev-icon { + + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23000' viewBox='0 0 16 16'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e"); +} /*.carousel-control-prev-icon {*/ /* background-image: url("photo/Left.png");*/ /*}*/ @@ -60,10 +87,205 @@ section { /* background-image: url("photo/Right.png");*/ /*}*/ + .carousel-item { - width: 80%; + width: 100%; + } .carousel-control-prev, .carousel-control-next { - width: 30%; + width: 50%; +} + + +label { + display: inline-block; + width: 200px; + margin: 5px; + text-align: left; + font-size: 25px; + color: #4d3b0b; + font-weight: bold; } +input[type=text], input[type=password], select { + display: inline-block; + justify-content: center; + width: 442px; + height: 45px; +} + +button { + padding: 5px; + /*margin: 10px;*/ + margin-right: 10px; + background-color: #0a67f2; +} +h1 { + font-weight: bold; + color: #4d3b0b; + font-style: italic; + margin-left: 100px; +} +.img-smal { + width: 150px; +} + +input[type="text"], input[type="password"], select { + display: inline-block; + justify-content: center; + + height: 42px; +} +[type="button"]:not(:disabled), [type="reset"]:not(:disabled), [type="submit"]:not(:disabled), button:not(:disabled) { + height: 40px; + margin-top: 1px; + background-color: #a4893a; +} +.d-flex { + width: 370px; + margin-bottom: 5px; + margin-top: 4px; +} +.btn btn-light { + margin-bottom: 9px; +} +.container { + max-width: 700px; +} +.container-recipe { + max-width: 700px; +} + + +.forma { + display: flex; + flex-direction: row; + justify-content: center; +} +.form-group { + display: flex; + flex-direction: row; + justify-content: center; + +} +.alert alert-secondary { + display: flex; + flex-direction: row; + justify-content: center; +} + +.alert-secondary { + color: #383d41; + background-color: #e2e3e5; + border-color: #d6d8db; + width: 40%; + display: flex; + flex-direction: row; + justify-items: center; + height: 82px; + margin-top: 10px; +} + +.form-control { + width: 250px; +} + +.navbar navbar-expand-lg navbar-light bg-light{ + /*background-color: blue;*/ +} + +.img-med { + width: 85%; + display: flex; + margin: auto; +} + +.title-recipes { + text-align: center; + margin: 25px; + font-size: 3.5rem; + color: #a3220d; + font-weight: 600; +} + +.table { + width: 100%; + max-width: 100%; + margin-bottom: 1rem; + background-color: #f2f2f2 !important;; +} + +.table table-striped { + background-color: #bbbbbb; +} +.btn btn-primary { + background-color: #3c96ff; + width: 350px !important; + +} +/*[type="button"]:not(:disabled), [type="reset"]:not(:disabled), [type="submit"]:not(:disabled), button:not(:disabled) {*/ +/* width: 205px;*/ +/* background-color: #3c96ff;*/ +/*}*/ +th { + width: 165px; +} +.row { + + margin-right: 1px !important; + margin-left: 1px !important; +} +.navbar-brand { + + color: #f2f2f2 !important; +} +.bg-light { + background-color: #0a67f2 !important; +} +.right-header-btn { + display: flex; + + width: 20%; + justify-content: flex-end; +} +.left-header-btn { + display: flex; + width: 50%; + justify-content: flex-end; +} +.left-space-header { + margin-top: 8px; + margin-right: 10px; +} +.navbar-expand-lg .navbar-collapse { + display: contents !important; +} +.btn-light { + width: 105px; + height: 35px; + border-color: blue; +} + + +.error { + color: red; +} + +.sort { + background color: blue; + position: fixed; + right: 10px; + top: 120px; +} + +ul { + list-style: outside none none; +} + + + + + + + + diff --git a/src/main/resources/templates/fragments.html b/src/main/resources/templates/fragments.html index 6a33ff9..7a0dceb 100644 --- a/src/main/resources/templates/fragments.html +++ b/src/main/resources/templates/fragments.html @@ -7,18 +7,64 @@ Famous Saint Louis Recipes - - - - + + + +
-
-

Recipes R Us

-
+ + + + +

Hello Friends!

- +
diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html index bb3b506..a21afa9 100644 --- a/src/main/resources/templates/index.html +++ b/src/main/resources/templates/index.html @@ -3,6 +3,9 @@
+
+

To review recipes you need log in. You can do it here.

+
@@ -14,11 +17,13 @@


th:class="${iterstat.index} == 0 ? 'carousel-item active':'carousel-item'">
-
- ... -
+
+ ... +
diff --git a/src/main/resources/templates/login.html b/src/main/resources/templates/login.html new file mode 100644 index 0000000..e507713 --- /dev/null +++ b/src/main/resources/templates/login.html @@ -0,0 +1,32 @@ + + + + +
+
+
+
+
+
+
+ +

+
+
+ +

+
+ + +
+ +

Don't have an account? Register for one.

+
+
+
+ + diff --git a/src/main/resources/templates/recipes/all.html b/src/main/resources/templates/recipes/all.html new file mode 100644 index 0000000..e374a9d --- /dev/null +++ b/src/main/resources/templates/recipes/all.html @@ -0,0 +1,29 @@ + + + + +
+
+


+
+

To review recipes you need log in. You can do it here.

+
+ + + + + + + + + +
Recipes
+ + + +
+ +
+
+ + diff --git a/src/main/resources/templates/recipes/create.html b/src/main/resources/templates/recipes/create.html index 535bbab..95e692c 100644 --- a/src/main/resources/templates/recipes/create.html +++ b/src/main/resources/templates/recipes/create.html @@ -6,72 +6,62 @@ -
+
+
+



-

Recipe:

- -
- -
- - -
-
-
- - -
-
-
- - -
-
- -
- -
-

+
+ +
+

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

+
+ +
+

+
+ + +

+
+ +
+ +
+
- diff --git a/src/main/resources/templates/recipes/display.html b/src/main/resources/templates/recipes/display.html index 1471c59..62b8ae1 100644 --- a/src/main/resources/templates/recipes/display.html +++ b/src/main/resources/templates/recipes/display.html @@ -1,59 +1,93 @@ - + + + +
- -


- - +


+
+
+ +

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

- - - + + + - - - + + + - - - - - - + + + + + + + - +
Ingredients:
Ingredients:
Directions:
Directions:
Category:
Tags:
Category:
Tags:

- Add to favorite - - -
-
- - +
+
+
+

Leave a Review

+
+ +
+ + + + + +
+



+
+ +


+
+
+ +

+ +
+
+ +
+
+
+

+
+ + + + + + + +
+
+ + + + + +
NameCommentDate
+
+
+
+ + diff --git a/src/main/resources/templates/recipes/edit.html b/src/main/resources/templates/recipes/edit.html index 5b14525..5c7be1b 100644 --- a/src/main/resources/templates/recipes/edit.html +++ b/src/main/resources/templates/recipes/edit.html @@ -2,8 +2,55 @@ + Title + +
+



+ +
+

Edit Recipe

+
+
+ +
+

+
+ +
+

+
+ + +

+
+ + +

+
+ +
+

+
+ + +
+ + + + +
+
+
+ +
+ diff --git a/src/main/resources/templates/recipes/index.html b/src/main/resources/templates/recipes/index.html index 95a4189..f090ec6 100644 --- a/src/main/resources/templates/recipes/index.html +++ b/src/main/resources/templates/recipes/index.html @@ -2,8 +2,8 @@ - -
+
+

Recipes:

@@ -15,6 +15,14 @@

Recipes:

+ + Edit + + + + + Delete + @@ -22,3 +30,4 @@

Recipes:

+ diff --git a/src/main/resources/templates/register.html b/src/main/resources/templates/register.html new file mode 100644 index 0000000..59e4210 --- /dev/null +++ b/src/main/resources/templates/register.html @@ -0,0 +1,31 @@ + + + + +
+
+
+
+
+ +

+
+
+ +

+
+
+ +
+ + +
+
+ + diff --git a/src/main/resources/templates/search.html b/src/main/resources/templates/search.html new file mode 100644 index 0000000..449dbb8 --- /dev/null +++ b/src/main/resources/templates/search.html @@ -0,0 +1,52 @@ + + + + +
+ + +
+ +

+ + + +
+
+ +
+
+ +
+
    + +
  • + + +
  • + +
+
+ + +
+
+
+ +
+ + +
+
+
+ +
+
+ + \ No newline at end of file diff --git a/src/main/resources/templates/users/profile.html b/src/main/resources/templates/users/profile.html new file mode 100644 index 0000000..13c2940 --- /dev/null +++ b/src/main/resources/templates/users/profile.html @@ -0,0 +1,36 @@ + + + + +
+ + +
+ +

+ +
+
+
+ +
+ + + + + + + + + +
Description
+
+ + +
+
+ +
+ +