= Финална имплементација на случаи на употреба == Корисникот пополнува форма за додавање на нов рецепт Најавен корисник се навигира кон страницата за листање на рецепти [[Image(cookCraft1A.jpg, height=450, width=900)]] Корисникот го клика копчето за додавање на рецепт и е пренасочен кон страница со форма за пополување на информации за рецептот [[Image(cookCraft2.png, height=450, width=900)]] По пополнување на потребните информации корисникот го кликнува копчето за поденување на формата. [[Image(cookCraft3.png, height=450, width=900)]] ** При испраќање на апликацијата преку 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); }); } }}} == Администраторот проверува поднесени рецепти Администраторот се наоѓа на администраторската страница [[Image(cookCraft4.png, height=570, width=600)]] Администраторот селектира одреден рецепт и му се прикажува податоци за рецептот [[Image(cookCraft5.png, height=570, width=650)]] ** Листањето на сите апликации за нови рецепти се прави така што се прави GET барање до backend делот од апликацијата на патеката /admin/recipeapplications и како додатни параметри се праќаат page и size доколку администраторот претходно веќе започнал со листање на апликациите. Првото нешто што се прави во оваа функција е се иницијализира нова инстанца од интерфејсот Pageable и таа инстаца се праќа во тековната функција за да се превземат апликациите. Се превземаат рецептите преку query, се ставаат во листа и се праќаат назад до front-end делот од апликацијата. ** {{{ @GetMapping("/admin/recipeapplications") public ResponseEntity> getRecipeApplication( @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "4") int size ) { Pageable pageable = PageRequest.of(page, size); Page recipeApplications = recipeService.findAllRecipeApplications(pageable); return new ResponseEntity<>(recipeApplications, HttpStatus.OK); } }}} ** Истата инстанца од интерфејсот Pageable се препраќа до полседниот слој. ** {{{ @Override public Page findAllRecipeApplications(Pageable pageable) { return recipeDAO.findAllRecipeApplications(pageable); } }}} ** Во последниот дел се прави sql query за селектирање на рецепти со лимит и offset за да се усогласи со потребната страница која ја бара администраторот. Исто така претходно се превзема и целосниот број на апликации ** {{{ @Override public Page 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 recipeApplications = new ArrayList<>(); if (recipe != null) { recipeApplications.add(recipe); } return new PageImpl<>(recipeApplications, pageable, count != null ? count : 0); } }}} == Најава преку Google === Ненајавен корисник Ненајавениот корисник го кликнува копчето за најава преку google профил, го селектира посакуваниот профил и се логира. [[Image(cookCraftLoginA.jpg)]] == Корисникот добива мејл кога доставувач ќе му ја прифати нарачката. {{{ @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 токен. **