Финална имплементација на случаи на употреба
Корисникот пополнува форма за додавање на нов рецепт
Најавен корисник се навигира кон страницата за листање на рецепти
Корисникот го клика копчето за додавање на рецепт и е пренасочен кон страница со форма за пополување на информации за рецептот
По пополнување на потребните информации корисникот го кликнува копчето за поденување на формата.
При испраќање на апликацијата преку POST барање најпрво преку jwt токенот се пребарува мејлот на корисникот. Потоа се прават некои основни проверки пред да се додаде апликацијата во базата
@PostMapping("/recipes/add") public ResponseEntity<?> addRecipeApplication( @RequestHeader("Authorization") String tokenHeader, @RequestBody RecipeApplicationDTO recipeApplicationDTO ) { String token = tokenHeader.substring(7); String email = jwtUtil.extractEmail(token); if (email == null) { return new ResponseEntity<>("Error finding user", HttpStatus.BAD_REQUEST); } if (recipeApplicationDTO.getRecipeName() == null || recipeApplicationDTO.getRecipeName().isEmpty()) { return new ResponseEntity<>("Recipe name is required", HttpStatus.BAD_REQUEST); } if (recipeApplicationDTO.getIngredients() == null || recipeApplicationDTO.getIngredients().isEmpty()) { return new ResponseEntity<>("Ingredients are required", HttpStatus.BAD_REQUEST); } recipeService.addRecipeApplication(recipeApplicationDTO); return new ResponseEntity<>(HttpStatus.OK); }
Во сервисниот дел апликацијата само се пренесува до последниот слој.
@Override public void addRecipeApplication(RecipeApplicationDTO recipeApplicationDTO) { recipeDAO.addRecipeApplication(recipeApplicationDTO); }
Во последниот слој се додава целиот рецепт во база и се превзема неговото id. Тоа id после тоа се користи за да се додадат состојките во посебна релациона база.
@Override public void addRecipeApplication(RecipeApplicationDTO recipeApplicationDTO) { String sql = "INSERT INTO recipe_application " + "(recipe_name, description, category, origin, meal_thumb, video_url) " + "VALUES (?, ?, ?, ?, ?, ?) RETURNING id"; Integer recipeId = jdbcTemplate.queryForObject(sql, new Object[] { recipeApplicationDTO.getRecipeName(), recipeApplicationDTO.getRecipeDesc(), recipeApplicationDTO.getRecipeCategory(), recipeApplicationDTO.getRecipeOrigin(), recipeApplicationDTO.getRecipeMealThumb(), recipeApplicationDTO.getRecipeVideoURL() }, Integer.class); if (recipeId == null) { throw new IllegalStateException("Failed to retrieve generated id for the recipe."); } recipeApplicationDTO.getIngredients().forEach(ingredient -> { jdbcTemplate.update("INSERT INTO recipe_application_ingredients (ingredient, dose, recipe_id) VALUES (?, ?, ?)", ingredient.getIngredient(), ingredient.getDose(), recipeId); }); }
Администраторот проверува поднесени рецепти
Администраторот се наоѓа на администраторската страница
Администраторот селектира одреден рецепт и му се прикажува податоци за рецептот
Листањето на сите апликации за нови рецепти се прави така што се прави GET барање до backend делот од апликацијата на патеката /admin/recipeapplications и како додатни параметри се праќаат page и size доколку администраторот претходно веќе започнал со листање на апликациите. Првото нешто што се прави во оваа функција е се иницијализира нова инстанца од интерфејсот Pageable и таа инстаца се праќа во тековната функција за да се превземат апликациите. Се превземаат рецептите преку query, се ставаат во листа и се праќаат назад до front-end делот од апликацијата.
@GetMapping("/admin/recipeapplications") public ResponseEntity<Page<RecipeApplicationDTO>> getRecipeApplication( @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "4") int size ) { Pageable pageable = PageRequest.of(page, size); Page<RecipeApplicationDTO> recipeApplications = recipeService.findAllRecipeApplications(pageable); return new ResponseEntity<>(recipeApplications, HttpStatus.OK); }
Истата инстанца од интерфејсот Pageable се препраќа до полседниот слој.
@Override public Page<RecipeApplicationDTO> findAllRecipeApplications(Pageable pageable) { return recipeDAO.findAllRecipeApplications(pageable); }
Во последниот дел се прави sql query за селектирање на рецепти со лимит и offset за да се усогласи со потребната страница која ја бара администраторот. Исто така претходно се превзема и целосниот број на апликации
@Override public Page<RecipeApplicationDTO> findAllRecipeApplications(Pageable pageable) { String sql = "SELECT ra.id AS recipe_id, ra.recipe_name, ra.description, ra.category, ra.origin, ra.meal_thumb, ra.video_url, " + "rai.ingredient, rai.dose " + "FROM recipe_application ra " + "LEFT JOIN recipe_application_ingredients rai ON ra.id = rai.recipe_id " + "ORDER BY ra.id " + "LIMIT ? OFFSET ?"; Integer count = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM recipe_application", Integer.class); RecipeApplicationRowMapper rowMapper = new RecipeApplicationRowMapper(); jdbcTemplate.query(sql, new Object[]{pageable.getPageSize(), pageable.getOffset()}, rowMapper); RecipeApplicationDTO recipe = rowMapper.getFinalRecipe(); List<RecipeApplicationDTO> recipeApplications = new ArrayList<>(); if (recipe != null) { recipeApplications.add(recipe); } return new PageImpl<>(recipeApplications, pageable, count != null ? count : 0); }
Најава преку Google
Ненајавен корисник
Ненајавениот корисник го кликнува копчето за најава преку google профил, го селектира посакуваниот профил и се логира.
Корисникот добива мејл кога доставувач ќе му ја прифати нарачката.
@Bean public ClientRegistrationRepository clientRegistrationRepository() { Dotenv dotenv = Dotenv.load(); String clientId = dotenv.get("GOOGLE_CLIENT_ID"); String clientSecret = dotenv.get("GOOGLE_CLIENT_SECRET"); ClientRegistration clientRegistration = ClientRegistration.withRegistrationId("google") .clientId(clientId) .clientSecret(clientSecret) .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) .redirectUri("{baseUrl}/login/oauth2/code/{registrationId}") .scope("openid", "profile", "email") .authorizationUri("https://accounts.google.com/o/oauth2/auth") .tokenUri("https://oauth2.googleapis.com/token") .userInfoUri("https://www.googleapis.com/oauth2/v3/userinfo") .jwkSetUri("https://www.googleapis.com/oauth2/v3/certs") .userNameAttributeName("sub") .clientName("Google") .build(); return new InMemoryClientRegistrationRepository(clientRegistration); }
Во овој репозиториум се наоѓа конфигурацијата за земање на api клучевите за google автентикација од .env датотеката.
@Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .csrf(csrf -> csrf.disable()) .authorizeHttpRequests(auth -> auth .requestMatchers( "/api/login", "/api/register", "/api/recipes/**", "/api/favorite", "/api/favorite/**", "/api/products", "/api/users/**", "/api/reviews", "/api/usertype", "/api/productOrders/**", "/api/orders/**", "/oauth2/**", "/login/oauth2/**" ).permitAll() .requestMatchers("/api/deliver/**").hasRole("DELIVERYPERSON") .requestMatchers("/api/admin/**").hasRole("ADMIN") .anyRequest().authenticated() ) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .oauth2Login(oauth2 -> oauth2 .userInfoEndpoint(userInfo -> userInfo .userService(customOAuth2UserService) ) .successHandler(oAuth2AuthenticationSuccessHandler) ) .addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); }
Во овој метод е имплементирано оAuth2 login со customOAuth2UserService сервисот и при успешно најавување се повикува и објектот oAuth2AuthenticationSuccessHandler
@Override public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { OAuth2User oauth2User = super.loadUser(userRequest); String email = oauth2User.getAttribute("email"); String firstName = oauth2User.getAttribute("given_name"); String lastName = oauth2User.getAttribute("family_name"); String phoneNumber = oauth2User.getAttribute("phone_number"); if (email != null) { processOAuthPostLogin(email, firstName, lastName, phoneNumber); return new DefaultOAuth2User( Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER")), oauth2User.getAttributes(), "email" ); } else { throw new OAuth2AuthenticationException("Email not found from OAuth2 provider"); } } public void processOAuthPostLogin(String email, String firstName, String lastName, String phoneNumber) { User existingUser = userDAO.findUserByEmail(email).orElse(null); if (existingUser == null) { User newUser = new User(); newUser.setEmail(email); newUser.setName(firstName != null ? firstName : ""); newUser.setSurname(lastName != null ? lastName : ""); newUser.setPhoneNumber(phoneNumber != null ? phoneNumber : ""); newUser.setPassword(""); newUser.setUserType(UserType.User); userDAO.save(newUser); } }
Овој метод се наоѓа во објектот customOAuth2UserService и неговата цел е да ги превземе податоците од Google акаунтот на корисникот и при успешно превземање на мејл адресата се повикува методот processOAuthPostLogin за пронаѓање на корисникот доколку постои, во спротивно се прави зачувување на корисникот во базата.
@Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { if (authentication instanceof OAuth2AuthenticationToken oauth2Token) { String email = oauth2Token.getPrincipal().getAttribute("email"); String firstName = oauth2Token.getPrincipal().getAttribute("given_name"); String lastName = oauth2Token.getPrincipal().getAttribute("family_name"); String phoneNumber = oauth2Token.getPrincipal().getAttribute("phone_number"); if (email != null) { UserDetails userDetails = userDetailsService.loadUserByUsername(email); String jwt = jwtUtil.generateToken(userDetails.getUsername()); String redirectUrl = String.format( "http://localhost:3000/loginSuccess?token=%s&firstName=%s&lastName=%s&email=%s&phoneNumber=%s&address=", jwt, firstName, lastName, email, phoneNumber != null ? phoneNumber : "" ); UriComponents uriComponents = ServletUriComponentsBuilder.fromUriString(redirectUrl).build(); response.sendRedirect(uriComponents.toUriString()); } else { response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Email not found from OAuth2 provider"); } } else { response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Authentication type not supported"); } }
При извршување на овој метод се пренасочува корисникот кон горенаведениот url каде што истовремено се сетира неговиот jwt токен.
Attachments (6)
- cookCraft2.png (64.8 KB ) - added by 5 weeks ago.
- cookCraft3.png (62.3 KB ) - added by 5 weeks ago.
- cookCraft4.png (35.8 KB ) - added by 5 weeks ago.
- cookCraft5.png (67.7 KB ) - added by 5 weeks ago.
- cookCraft1A.jpg (207.1 KB ) - added by 5 weeks ago.
- cookCraftLoginA.jpg (63.4 KB ) - added by 5 weeks ago.
Download all attachments as: .zip