diff --git a/build.gradle b/build.gradle index 2f776a6..233f023 100644 --- a/build.gradle +++ b/build.gradle @@ -36,6 +36,14 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'com.h2database:h2:2.2.224' + implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' + implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect:3.3.0' + runtimeOnly 'org.webjars.npm:bootstrap:5.3.3' + runtimeOnly 'org.webjars.npm:bootstrap-icons:1.11.3' + + implementation 'org.springframework.boot:spring-boot-starter-security' + implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6' + testImplementation 'org.springframework.boot:spring-boot-starter-test' } diff --git a/data.mv.db b/data.mv.db index 8989e4e..75e0800 100644 Binary files a/data.mv.db and b/data.mv.db differ diff --git a/src/main/java/com/example/demo/DemoApplication.java b/src/main/java/com/example/demo/DemoApplication.java index 766cd00..c306c0d 100644 --- a/src/main/java/com/example/demo/DemoApplication.java +++ b/src/main/java/com/example/demo/DemoApplication.java @@ -10,6 +10,7 @@ import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import com.example.demo.core.configuration.Constants; import com.example.demo.games.model.GameEntity; import com.example.demo.games.service.GameService; import com.example.demo.genres.model.GenreEntity; @@ -19,57 +20,61 @@ import com.example.demo.orders.service.OrderService; import com.example.demo.types.model.TypeEntity; import com.example.demo.types.service.TypeService; import com.example.demo.users.model.UserEntity; +import com.example.demo.users.model.UserRole; import com.example.demo.users.service.UserService; - @SpringBootApplication public class DemoApplication implements CommandLineRunner { - private final Logger log = LoggerFactory.getLogger(DemoApplication.class); + private final Logger log = LoggerFactory.getLogger(DemoApplication.class); - private final TypeService typeService; - private final GenreService genreService; - private final GameService gameService; - private final OrderService orderService; - private final UserService userService; + private final TypeService typeService; + private final GenreService genreService; + private final GameService gameService; + private final OrderService orderService; + private final UserService userService; - public DemoApplication(TypeService typeService, GenreService genreService, GameService gameService, OrderService orderService, UserService userService){ - this.typeService = typeService; - this.gameService = gameService; - this.genreService = genreService; - this.orderService = orderService; - this.userService = userService; - } + public DemoApplication(TypeService typeService, GenreService genreService, GameService gameService, + OrderService orderService, UserService userService) { + this.typeService = typeService; + this.gameService = gameService; + this.genreService = genreService; + this.orderService = orderService; + this.userService = userService; + } - public static void main(String[] args) { - SpringApplication.run(DemoApplication.class, args); - } + public static void main(String[] args) { + SpringApplication.run(DemoApplication.class, args); + } - @Override - public void run(String... args) throws Exception{ - log.info("start"); - // final var type1 = typeService.create(new TypeEntity("ААА")); - // final var type2 = typeService.create(new TypeEntity("АА")); + @Override + public void run(String... args) throws Exception { + log.info("start"); - // final var genre1 = genreService.create(new GenreEntity("Приключения")); - // final var genre2 = genreService.create(new GenreEntity("Симулятор")); + log.info("Create default user values"); + final var admin = new UserEntity("admin", "admin@mail.com", "admin"); + admin.setRole(UserRole.ADMIN); + userService.create(admin); + final var user1 = userService.create(new UserEntity("user", "user@gmail.com", Constants.DEFAULT_PASSWORD)); - // final List genres1 = new ArrayList(); - // genres1.add(genre1); - // genres1.add(genre2); + final var type1 = typeService.create(new TypeEntity("ААА")); + final var type2 = typeService.create(new TypeEntity("АА")); - // final List genres2 = new ArrayList(); - // genres2.add(genre2); + final var genre1 = genreService.create(new GenreEntity("Приключения")); + final var genre2 = genreService.create(new GenreEntity("Симулятор")); - // final var game1 = gameService.create(new GameEntity(type1,"Game1",2100.0,"good game", genres1)); - // final var game2 = gameService.create(new GameEntity( type2, "Game2", 1200.0,"bad game", genres2)); - // final List games = new ArrayList(); - // games.add(game1); - // games.add(game2); + final List genres1 = new ArrayList(); + genres1.add(genre1); + genres1.add(genre2); - // var user1 = userService.create(new UserEntity( "login1", "email@mail.com", "qwerty123")); - // var user2 = userService.create(new UserEntity( "login2", "email@gmail.com", "qwerty1234")); + final List genres2 = new ArrayList(); + genres2.add(genre2); - // orderService.create(7, new OrderEntity(user1,games)); - // orderService.create(8, new OrderEntity(user2,games)); - } + final var game1 = gameService.create(new GameEntity(type1, "Game1", 2100.0, "good game", genres1)); + final var game2 = gameService.create(new GameEntity(type2, "Game2", 1200.0, "bad game", genres2)); + final List games = new ArrayList(); + games.add(game1); + games.add(game2); + + orderService.create(user1.getId(), new OrderEntity(games)); + } } diff --git a/src/main/java/com/example/demo/core/api/GlobalController.java b/src/main/java/com/example/demo/core/api/GlobalController.java new file mode 100644 index 0000000..bc3f2ca --- /dev/null +++ b/src/main/java/com/example/demo/core/api/GlobalController.java @@ -0,0 +1,28 @@ +package com.example.demo.core.api; + +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ModelAttribute; + +import com.example.demo.core.session.SessionCart; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpSession; + +@ControllerAdvice +public class GlobalController { + private final SessionCart cart; + + public GlobalController(SessionCart cart) { + this.cart = cart; + } + + @ModelAttribute("servletPath") + String getRequestServletPath(HttpServletRequest request) { + return request.getServletPath(); + } + + @ModelAttribute("totalCart") + double getTotalCart(HttpSession session) { + return cart.getSum(); + } +} diff --git a/src/main/java/com/example/demo/core/api/PageAttributesMapper.java b/src/main/java/com/example/demo/core/api/PageAttributesMapper.java new file mode 100644 index 0000000..74ee38d --- /dev/null +++ b/src/main/java/com/example/demo/core/api/PageAttributesMapper.java @@ -0,0 +1,18 @@ +package com.example.demo.core.api; + +import java.util.Map; +import java.util.function.Function; + +import org.springframework.data.domain.Page; + +public class PageAttributesMapper { + private PageAttributesMapper() { + } + + public static Map toAttributes(Page page, Function mapper) { + return Map.of( + "items", page.getContent().stream().map(mapper::apply).toList(), + "currentPage", page.getNumber(), + "totalPages", page.getTotalPages()); + } +} diff --git a/src/main/java/com/example/demo/core/api/PageDto.java b/src/main/java/com/example/demo/core/api/PageDto.java deleted file mode 100644 index 4cae429..0000000 --- a/src/main/java/com/example/demo/core/api/PageDto.java +++ /dev/null @@ -1,97 +0,0 @@ -package com.example.demo.core.api; - -import java.util.ArrayList; -import java.util.List; - -public class PageDto { - private List items = new ArrayList<>(); - private int itemsCount; - private int currentPage; - private int currentSize; - private int totalPages; - private long totalItems; - private boolean isFirst; - private boolean isLast; - private boolean hasNext; - private boolean hasPrevious; - - public List getItems() { - return items; - } - - public void setItems(List items) { - this.items = items; - } - - public int getItemsCount() { - return itemsCount; - } - - public void setItemsCount(int itemsCount) { - this.itemsCount = itemsCount; - } - - public int getCurrentPage() { - return currentPage; - } - - public void setCurrentPage(int currentPage) { - this.currentPage = currentPage; - } - - public int getCurrentSize() { - return currentSize; - } - - public void setCurrentSize(int currentSize) { - this.currentSize = currentSize; - } - - public int getTotalPages() { - return totalPages; - } - - public void setTotalPages(int totalPages) { - this.totalPages = totalPages; - } - - public long getTotalItems() { - return totalItems; - } - - public void setTotalItems(long totalItems) { - this.totalItems = totalItems; - } - - public boolean isFirst() { - return isFirst; - } - - public void setFirst(boolean isFirst) { - this.isFirst = isFirst; - } - - public boolean isLast() { - return isLast; - } - - public void setLast(boolean isLast) { - this.isLast = isLast; - } - - public boolean isHasNext() { - return hasNext; - } - - public void setHasNext(boolean hasNext) { - this.hasNext = hasNext; - } - - public boolean isHasPrevious() { - return hasPrevious; - } - - public void setHasPrevious(boolean hasPrevious) { - this.hasPrevious = hasPrevious; - } -} diff --git a/src/main/java/com/example/demo/core/api/PageDtoMapper.java b/src/main/java/com/example/demo/core/api/PageDtoMapper.java deleted file mode 100644 index e8d3dd0..0000000 --- a/src/main/java/com/example/demo/core/api/PageDtoMapper.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.example.demo.core.api; - -import java.util.function.Function; - -import org.springframework.data.domain.Page; - -public class PageDtoMapper { - private PageDtoMapper() { - } - - public static PageDto toDto(Page page, Function mapper) { - final PageDto dto = new PageDto<>(); - dto.setItems(page.getContent().stream().map(mapper::apply).toList()); - dto.setItemsCount(page.getNumberOfElements()); - dto.setCurrentPage(page.getNumber()); - dto.setCurrentSize(page.getSize()); - dto.setTotalPages(page.getTotalPages()); - dto.setTotalItems(page.getTotalElements()); - dto.setFirst(page.isFirst()); - dto.setLast(page.isLast()); - dto.setHasNext(page.hasNext()); - dto.setHasPrevious(page.hasPrevious()); - return dto; - } -} diff --git a/src/main/java/com/example/demo/core/configuration/Constants.java b/src/main/java/com/example/demo/core/configuration/Constants.java index 42de69f..a6e6497 100644 --- a/src/main/java/com/example/demo/core/configuration/Constants.java +++ b/src/main/java/com/example/demo/core/configuration/Constants.java @@ -2,8 +2,17 @@ package com.example.demo.core.configuration; public class Constants { public static final String SEQUENCE_NAME = "hibernate_sequence"; - public static final String API_URL = "/api/1.0"; - public static final String DEFAULT_PAGE_SIZE = "5"; + + public static final int DEFAULT_PAGE_SIZE = 5; + + public static final String REDIRECT_VIEW = "redirect:"; + + public static final String ADMIN_PREFIX = "/admin"; + + public static final String LOGIN_URL = "/login"; + public static final String LOGOUT_URL = "/logout"; + + public static final String DEFAULT_PASSWORD = "123456"; private Constants() { } diff --git a/src/main/java/com/example/demo/core/configuration/MapperConfiguration.java b/src/main/java/com/example/demo/core/configuration/MapperConfiguration.java index a5ad6f3..44defae 100644 --- a/src/main/java/com/example/demo/core/configuration/MapperConfiguration.java +++ b/src/main/java/com/example/demo/core/configuration/MapperConfiguration.java @@ -1,13 +1,23 @@ package com.example.demo.core.configuration; import org.modelmapper.ModelMapper; +import org.modelmapper.PropertyMap; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import com.example.demo.core.model.BaseEntity; + @Configuration public class MapperConfiguration { @Bean ModelMapper modelMapper() { - return new ModelMapper(); + final ModelMapper mapper = new ModelMapper(); + mapper.addMappings(new PropertyMap() { + @Override + protected void configure() { + skip(destination.getId()); + } + }); + return mapper; } } diff --git a/src/main/java/com/example/demo/core/configuration/WebConfiguration.java b/src/main/java/com/example/demo/core/configuration/WebConfiguration.java index 762e85a..6316aa8 100644 --- a/src/main/java/com/example/demo/core/configuration/WebConfiguration.java +++ b/src/main/java/com/example/demo/core/configuration/WebConfiguration.java @@ -1,15 +1,13 @@ package com.example.demo.core.configuration; import org.springframework.context.annotation.Configuration; -import org.springframework.lang.NonNull; -import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class WebConfiguration implements WebMvcConfigurer { @Override - public void addCorsMappings(@NonNull CorsRegistry registry) { - registry.addMapping("/**") - .allowedMethods("GET", "POST", "PUT", "DELETE"); + public void addViewControllers(ViewControllerRegistry registry) { + registry.addViewController("/login").setViewName("login"); } } diff --git a/src/main/java/com/example/demo/core/error/AdviceController.java b/src/main/java/com/example/demo/core/error/AdviceController.java new file mode 100644 index 0000000..72c9937 --- /dev/null +++ b/src/main/java/com/example/demo/core/error/AdviceController.java @@ -0,0 +1,53 @@ +package com.example.demo.core.error; + +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.servlet.ModelAndView; + +import jakarta.servlet.http.HttpServletRequest; + +@ControllerAdvice +public class AdviceController { +private final Logger log = LoggerFactory.getLogger(AdviceController.class); + + private static Throwable getRootCause(Throwable throwable) { + Throwable rootCause = throwable; + while (rootCause.getCause() != null && rootCause.getCause() != rootCause) { + rootCause = rootCause.getCause(); + } + return rootCause; + } + + private static Map getAttributes(HttpServletRequest request, Throwable throwable) { + final Throwable rootCause = getRootCause(throwable); + final StackTraceElement firstError = rootCause.getStackTrace()[0]; + return Map.of( + "message", rootCause.getMessage(), + "url", request.getRequestURL(), + "exception", rootCause.getClass().getName(), + "file", firstError.getFileName(), + "method", firstError.getMethodName(), + "line", firstError.getLineNumber()); + } + + @ExceptionHandler(value = Exception.class) + public ModelAndView defaultErrorHandler(HttpServletRequest request, Throwable throwable) throws Throwable { + if (AnnotationUtils.findAnnotation(throwable.getClass(), + ResponseStatus.class) != null) { + throw throwable; + } + + log.error("{}", throwable.getMessage()); + throwable.printStackTrace(); + final ModelAndView model = new ModelAndView(); + model.addAllObjects(getAttributes(request, throwable)); + model.setViewName("error"); + return model; + } +} diff --git a/src/main/java/com/example/demo/core/security/SecurityConfiguration.java b/src/main/java/com/example/demo/core/security/SecurityConfiguration.java new file mode 100644 index 0000000..0b194f0 --- /dev/null +++ b/src/main/java/com/example/demo/core/security/SecurityConfiguration.java @@ -0,0 +1,63 @@ +package com.example.demo.core.security; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.dao.DaoAuthenticationProvider; +import org.springframework.security.config.Customizer; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer.FrameOptionsConfig; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; + +import com.example.demo.core.configuration.Constants; +import com.example.demo.users.api.UserSignupController; +import com.example.demo.users.model.UserRole; + +@Configuration +@EnableWebSecurity +public class SecurityConfiguration { +@Bean + SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception { + httpSecurity.headers(headers -> headers.frameOptions(FrameOptionsConfig::sameOrigin)); + httpSecurity.csrf(AbstractHttpConfigurer::disable); + httpSecurity.cors(Customizer.withDefaults()); + + httpSecurity.authorizeHttpRequests(requests -> requests + .requestMatchers("/css/**", "/webjars/**", "/*.svg") + .permitAll()); + + httpSecurity.authorizeHttpRequests(requests -> requests + .requestMatchers(Constants.ADMIN_PREFIX + "/**").hasRole(UserRole.ADMIN.name()) + .requestMatchers("/h2-console/**").hasRole(UserRole.ADMIN.name()) + .requestMatchers(UserSignupController.URL).anonymous() + .requestMatchers(Constants.LOGIN_URL).anonymous() + .anyRequest().authenticated()); + + httpSecurity.formLogin(formLogin -> formLogin + .loginPage(Constants.LOGIN_URL)); + + httpSecurity.rememberMe(rememberMe -> rememberMe.key("uniqueAndSecret")); + + httpSecurity.logout(logout -> logout + .deleteCookies("JSESSIONID")); + + return httpSecurity.build(); + } + + @Bean + DaoAuthenticationProvider authenticationProvider(UserDetailsService userDetailsService) { + final DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(); + authProvider.setUserDetailsService(userDetailsService); + authProvider.setPasswordEncoder(passwordEncoder()); + return authProvider; + } + + @Bean + PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } +} diff --git a/src/main/java/com/example/demo/core/security/UserPrincipal.java b/src/main/java/com/example/demo/core/security/UserPrincipal.java new file mode 100644 index 0000000..ba72b74 --- /dev/null +++ b/src/main/java/com/example/demo/core/security/UserPrincipal.java @@ -0,0 +1,64 @@ +package com.example.demo.core.security; + +import java.util.Collection; +import java.util.Set; + +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import com.example.demo.users.model.UserEntity; + +public class UserPrincipal implements UserDetails{ +private final long id; + private final String username; + private final String password; + private final Set roles; + private final boolean active; + + public UserPrincipal(UserEntity user) { + this.id = user.getId(); + this.username = user.getLogin(); + this.password = user.getPassword(); + this.roles = Set.of(user.getRole()); + this.active = true; + } + + public Long getId() { + return id; + } + + @Override + public String getUsername() { + return username; + } + + @Override + public String getPassword() { + return password; + } + + @Override + public Collection getAuthorities() { + return roles; + } + + @Override + public boolean isEnabled() { + return active; + } + + @Override + public boolean isAccountNonExpired() { + return isEnabled(); + } + + @Override + public boolean isAccountNonLocked() { + return isEnabled(); + } + + @Override + public boolean isCredentialsNonExpired() { + return isEnabled(); + } +} diff --git a/src/main/java/com/example/demo/core/session/SessionCart.java b/src/main/java/com/example/demo/core/session/SessionCart.java new file mode 100644 index 0000000..d4696a6 --- /dev/null +++ b/src/main/java/com/example/demo/core/session/SessionCart.java @@ -0,0 +1,14 @@ +package com.example.demo.core.session; + +import java.util.HashMap; + +import com.example.demo.users.api.UserCartDto; + +public class SessionCart extends HashMap { + public double getSum() { + return this.values().stream() + .map(item -> item.getPrice()) + .mapToDouble(Double::doubleValue) + .sum(); + } +} diff --git a/src/main/java/com/example/demo/core/session/SessionHelper.java b/src/main/java/com/example/demo/core/session/SessionHelper.java new file mode 100644 index 0000000..6ee3d8c --- /dev/null +++ b/src/main/java/com/example/demo/core/session/SessionHelper.java @@ -0,0 +1,16 @@ +package com.example.demo.core.session; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Scope; +import org.springframework.context.annotation.ScopedProxyMode; +import org.springframework.web.context.WebApplicationContext; + +@Configuration +public class SessionHelper { + @Bean + @Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS) + SessionCart todos() { + return new SessionCart(); + } +} diff --git a/src/main/java/com/example/demo/games/api/GameController.java b/src/main/java/com/example/demo/games/api/GameController.java index 7b8f82f..e72cc73 100644 --- a/src/main/java/com/example/demo/games/api/GameController.java +++ b/src/main/java/com/example/demo/games/api/GameController.java @@ -1,8 +1,11 @@ package com.example.demo.games.api; import java.util.List; +import java.util.Map; import org.modelmapper.ModelMapper; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -11,10 +14,8 @@ import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; -import com.example.demo.core.api.PageDto; -import com.example.demo.core.api.PageDtoMapper; +import com.example.demo.core.api.PageAttributesMapper; import com.example.demo.core.configuration.Constants; import com.example.demo.games.model.GameEntity; import com.example.demo.games.service.GameService; @@ -24,23 +25,31 @@ import com.example.demo.types.service.TypeService; import jakarta.validation.Valid; -@RestController -@RequestMapping(Constants.API_URL + "/game") +@Controller +@RequestMapping(GameController.URL) public class GameController { + + public static final String URL = Constants.ADMIN_PREFIX + "/game"; + private static final String GAME_VIEW = "game"; + private static final String GAME_EDIT_VIEW = "game-edit"; + private static final String PAGE_ATTRIBUTE = "page"; + private static final String GAME_ATTRIBUTE = "game"; + private final GameService gameService; private final TypeService typeService; private final GenreService genreService; private final ModelMapper modelMapper; - public GameController(GameService gameService, TypeService typeService, GenreService genreService, ModelMapper modelMapper){ + public GameController(GameService gameService, TypeService typeService, GenreService genreService, + ModelMapper modelMapper) { this.gameService = gameService; this.genreService = genreService; this.modelMapper = modelMapper; this.typeService = typeService; } - private GameDto toDto(GameEntity entity){ - //return modelMapper.map(entity, GameDto.class); + private GameDto toDto(GameEntity entity) { + // return modelMapper.map(entity, GameDto.class); var dto = new GameDto(); dto.setId(entity.getId()); dto.setGenres(entity.getGenres().stream().map(GenreEntity::getId).toList()); @@ -51,44 +60,46 @@ public class GameController { return dto; } - private GameEntity toEntity(GameDto dto){ + private GameEntity toEntity(GameDto dto) { + // return modelMapper.map(dto, GameEntity.class); final GameEntity entity = modelMapper.map(dto, GameEntity.class); entity.setType(typeService.get(dto.getTypeId())); var genres = dto.getGenres(); List genresList = genreService.getAllById(genres); - for(var genre : genresList){ + for (var genre : genresList) { entity.setGenres(genre); } return entity; } @GetMapping - public PageDto getAll( - @RequestParam(name = "typeId", defaultValue = "0") long typeId, - //@RequestParam(name = "genres", defaultValue = "") List genres, - @RequestParam(name = "genreId", defaultValue = "0") long genre, - @RequestParam(name = "page", defaultValue = "0") int page, - @RequestParam(name = "size", defaultValue = Constants.DEFAULT_PAGE_SIZE) int size){ - return PageDtoMapper.toDto(gameService.getAll(typeId, genre, page, size), this::toDto); + public String getAll( + @RequestParam(name = PAGE_ATTRIBUTE, defaultValue = "0") int page, + Model model) { + final Map attributes = PageAttributesMapper.toAttributes( + gameService.getAll(0, 0, page, Constants.DEFAULT_PAGE_SIZE), this::toDto); + model.addAllAttributes(attributes); + model.addAttribute(PAGE_ATTRIBUTE, page); + return GAME_VIEW; } @GetMapping("/{id}") - public GameDto get(@PathVariable(name = "id") Long id){ + public GameDto get(@PathVariable(name = "id") Long id) { return toDto(gameService.get(id)); } @PostMapping - public GameDto create(@RequestBody @Valid GameDto dto){ + public GameDto create(@RequestBody @Valid GameDto dto) { return toDto(gameService.create(toEntity(dto))); } @PutMapping("/{id}") - public GameDto update(@PathVariable(name = "id") Long id, @RequestBody GameDto dto){ + public GameDto update(@PathVariable(name = "id") Long id, @RequestBody GameDto dto) { return toDto(gameService.update(id, toEntity(dto))); } @DeleteMapping("/{id}") - public GameDto delete(@PathVariable(name = "id") Long id){ + public GameDto delete(@PathVariable(name = "id") Long id) { return toDto(gameService.delete(id)); } } diff --git a/src/main/java/com/example/demo/games/api/GameDto.java b/src/main/java/com/example/demo/games/api/GameDto.java index c7ef495..1a6e39f 100644 --- a/src/main/java/com/example/demo/games/api/GameDto.java +++ b/src/main/java/com/example/demo/games/api/GameDto.java @@ -3,14 +3,11 @@ package com.example.demo.games.api; import java.util.ArrayList; import java.util.List; -import com.fasterxml.jackson.annotation.JsonProperty; - import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; public class GameDto { - @JsonProperty(access = JsonProperty.Access.READ_ONLY) private Long id; @NotNull @Min(1) @@ -25,52 +22,52 @@ public class GameDto { @NotBlank private String description; - public Long getId(){ + public Long getId() { return id; } - public void setId(Long id){ + public void setId(Long id) { this.id = id; } - public Long getTypeId(){ + public Long getTypeId() { return typeId; } - public void setTypeId(Long typeId){ + public void setTypeId(Long typeId) { this.typeId = typeId; } - public List getGenres(){ + public List getGenres() { return genres; } - public void setGenres(List genres){ + public void setGenres(List genres) { this.genres.clear(); this.genres.addAll(genres); } - public Double getPrice(){ + public Double getPrice() { return price; } - public void setPrice(Double price){ + public void setPrice(Double price) { this.price = price; } - public String getName(){ + public String getName() { return name; } - public void setName(String name){ + public void setName(String name) { this.name = name; } - public String getDescription(){ + public String getDescription() { return description; } - public void setDescription(String description){ + public void setDescription(String description) { this.description = description; } diff --git a/src/main/java/com/example/demo/games/service/GameService.java b/src/main/java/com/example/demo/games/service/GameService.java index cd9279a..3bafb07 100644 --- a/src/main/java/com/example/demo/games/service/GameService.java +++ b/src/main/java/com/example/demo/games/service/GameService.java @@ -1,8 +1,11 @@ package com.example.demo.games.service; +import java.util.Collection; import java.util.List; //import java.util.List; import java.util.Objects; +import java.util.stream.StreamSupport; + import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Service; @@ -18,48 +21,57 @@ import com.example.demo.genres.service.GenreService; public class GameService { private final GameRepository repository; - public GameService(GameRepository repository, GenreService genreService){ + public GameService(GameRepository repository, GenreService genreService) { this.repository = repository; } @Transactional(readOnly = true) - public List getAll(long typeId, long genre){ + public List getAll(long typeId, long genre) { - if(!Objects.equals(typeId, 0L) && !Objects.equals(genre, 0L)){ + if (!Objects.equals(typeId, 0L) && !Objects.equals(genre, 0L)) { return repository.findByTypeIdAndGenres(typeId, genre); } - if(Objects.equals(typeId, 0L) && !Objects.equals(genre, 0L)){ + if (Objects.equals(typeId, 0L) && !Objects.equals(genre, 0L)) { return repository.findByGenres(genre); } - if(!Objects.equals(typeId, 0L) && Objects.equals(genre, 0L)){ + if (!Objects.equals(typeId, 0L) && Objects.equals(genre, 0L)) { return repository.findByTypeId(typeId); } return repository.findAll(); } @Transactional(readOnly = true) - public Page getAll(long typeId, long genre, int page, int size){ + public Page getAll(long typeId, long genre, int page, int size) { final Pageable pageRequest = PageRequest.of(page, size); - if(!Objects.equals(typeId, 0L) && !Objects.equals(genre, 0L)){ + if (!Objects.equals(typeId, 0L) && !Objects.equals(genre, 0L)) { return repository.findByTypeIdAndGenres(typeId, genre, pageRequest); } - if(Objects.equals(typeId, 0L) && !Objects.equals(genre, 0L)){ + if (Objects.equals(typeId, 0L) && !Objects.equals(genre, 0L)) { return repository.findByGenres(genre, pageRequest); } - if(!Objects.equals(typeId, 0L) && Objects.equals(genre, 0L)){ + if (!Objects.equals(typeId, 0L) && Objects.equals(genre, 0L)) { return repository.findByTypeId(typeId, pageRequest); } return repository.findAll(pageRequest); } @Transactional(readOnly = true) - public GameEntity get(Long id){ + public GameEntity get(Long id) { return repository.findOneById(id).orElseThrow(() -> new NotFoundException(GameEntity.class, id)); } + @Transactional(readOnly = true) + public List getByIds(Collection ids) { + final List games = StreamSupport.stream(repository.findAllById(ids).spliterator(), false).toList(); + if (games.size() < ids.size()) { + throw new IllegalArgumentException("Invalid type"); + } + return games; + } + @Transactional - public GameEntity create(GameEntity entity){ + public GameEntity create(GameEntity entity) { if (entity == null) { throw new IllegalArgumentException("Entity is null"); } @@ -67,21 +79,21 @@ public class GameService { } @Transactional - public GameEntity update(Long id, GameEntity entity){ + public GameEntity update(Long id, GameEntity entity) { final GameEntity existEntity = get(id); existEntity.setName(entity.getName()); existEntity.setPrice(entity.getPrice()); existEntity.setDescription(entity.getDescription()); existEntity.setType(entity.getType()); var genres = entity.getGenres(); - for(var genre : genres){ + for (var genre : genres) { existEntity.setGenres(genre); } return repository.save(existEntity); } @Transactional - public GameEntity delete(Long id){ + public GameEntity delete(Long id) { final GameEntity existEntity = get(id); repository.delete(existEntity); return existEntity; diff --git a/src/main/java/com/example/demo/genres/api/GenreController.java b/src/main/java/com/example/demo/genres/api/GenreController.java index 852f406..e6c6b12 100644 --- a/src/main/java/com/example/demo/genres/api/GenreController.java +++ b/src/main/java/com/example/demo/genres/api/GenreController.java @@ -1,16 +1,14 @@ package com.example.demo.genres.api; -import java.util.List; - import org.modelmapper.ModelMapper; -import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.validation.BindingResult; 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.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; import com.example.demo.core.configuration.Constants; import com.example.demo.genres.model.GenreEntity; @@ -18,47 +16,90 @@ import com.example.demo.genres.service.GenreService; import jakarta.validation.Valid; -@RestController -@RequestMapping(Constants.API_URL + "/genre") +@Controller +@RequestMapping(GenreController.URL) public class GenreController { + + public static final String URL = Constants.ADMIN_PREFIX + "/genre"; + private static final String GENRE_VIEW = "genre"; + private static final String GENRE_EDIT_VIEW = "genre-edit"; + private static final String GENRE_ATTRIBUTE = "genre"; + private final GenreService genreService; private final ModelMapper modelMapper; - public GenreController(GenreService genreService, ModelMapper modelMapper){ + public GenreController(GenreService genreService, ModelMapper modelMapper) { this.genreService = genreService; this.modelMapper = modelMapper; } - private GenreDto toDto(GenreEntity entity){ + private GenreDto toDto(GenreEntity entity) { return modelMapper.map(entity, GenreDto.class); } - private GenreEntity toEntity(GenreDto dto){ + private GenreEntity toEntity(GenreDto dto) { return modelMapper.map(dto, GenreEntity.class); } @GetMapping - public List getAll(){ - return genreService.getAll().stream().map(this::toDto).toList(); + public String getAll(Model model) { + model.addAttribute( + "items", + genreService.getAll().stream() + .map(this::toDto) + .toList()); + return GENRE_VIEW; } - @GetMapping("/{id}") - public GenreDto get(@PathVariable(name="id") Long id){ - return toDto(genreService.get(id)); + @GetMapping("/edit/") + public String create(Model model) { + model.addAttribute(GENRE_ATTRIBUTE, new GenreDto()); + return GENRE_EDIT_VIEW; } - @PostMapping - public GenreDto create(@RequestBody @Valid GenreDto dto){ - return toDto(genreService.create(toEntity(dto))); + @PostMapping("/edit/") + public String create( + @ModelAttribute(name = GENRE_ATTRIBUTE) @Valid GenreDto genre, + BindingResult bindingResult, + Model model) { + if (bindingResult.hasErrors()) { + return GENRE_EDIT_VIEW; + } + genreService.create(toEntity(genre)); + return Constants.REDIRECT_VIEW + URL; } - @PutMapping("/{id}") - public GenreDto update(@PathVariable(name="id") Long id, @RequestBody GenreDto dto){ - return toDto(genreService.update(id, toEntity(dto))); + @GetMapping("/edit/{id}") + public String update( + @PathVariable(name = "id") Long id, + Model model) { + if (id <= 0) { + throw new IllegalArgumentException(); + } + model.addAttribute(GENRE_ATTRIBUTE, toDto(genreService.get(id))); + return GENRE_EDIT_VIEW; } - - @DeleteMapping("/{id}") - public GenreDto delete(@PathVariable(name="id") Long id){ - return toDto(genreService.delete(id)); + + @PostMapping("/edit/{id}") + public String update( + @PathVariable(name = "id") Long id, + @ModelAttribute(name = GENRE_ATTRIBUTE) @Valid GenreDto genre, + BindingResult bindingResult, + Model model) { + if (bindingResult.hasErrors()) { + return GENRE_EDIT_VIEW; + } + if (id <= 0) { + throw new IllegalArgumentException(); + } + genreService.update(id, toEntity(genre)); + return Constants.REDIRECT_VIEW + URL; + } + + @PostMapping("/delete/{id}") + public String delete( + @PathVariable(name = "id") Long id) { + genreService.delete(id); + return Constants.REDIRECT_VIEW + URL; } } diff --git a/src/main/java/com/example/demo/orders/api/OrderController.java b/src/main/java/com/example/demo/orders/api/OrderController.java deleted file mode 100644 index 7e2c55b..0000000 --- a/src/main/java/com/example/demo/orders/api/OrderController.java +++ /dev/null @@ -1,83 +0,0 @@ -package com.example.demo.orders.api; - -import org.modelmapper.ModelMapper; -import org.springframework.web.bind.annotation.DeleteMapping; -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.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - - -import com.example.demo.core.configuration.Constants; -import com.example.demo.games.model.GameEntity; -import com.example.demo.games.service.GameService; -import com.example.demo.orders.model.OrderEntity; -import com.example.demo.orders.service.OrderService; -import com.example.demo.users.service.UserService; -import com.example.demo.core.api.PageDtoMapper; - -import jakarta.validation.Valid; - -@RestController -@RequestMapping(Constants.API_URL + "/user/{user}/order") -public class OrderController { - private final GameService gameService; - private final ModelMapper modelMapper; - private final OrderService orderService; - - public OrderController(GameService gameService, ModelMapper modelMapper, OrderService orderService, UserService userService){ - this.gameService = gameService; - this.modelMapper = modelMapper; - this.orderService = orderService; - } - - private OrderDto toDto(OrderEntity entity){ - var dto = new OrderDto(); - dto.setId(entity.getId()); - dto.setUserId(entity.getUser().getId()); - dto.setGames(entity.getGames().stream().map(GameEntity::getId).toList()); - return dto; - } - - private OrderEntity toEntity(OrderDto dto){ - final OrderEntity entity = modelMapper.map(dto, OrderEntity.class); - var games = dto.getGames(); - for(var game : games){ - entity.setGames(gameService.get(game)); - } - return entity; - } - - @GetMapping - public com.example.demo.core.api.PageDto getAll( - @RequestParam(name = "userId", defaultValue = "") Long userId, - @RequestParam(name = "page", defaultValue = "0") int page, - @RequestParam(name = "size", defaultValue = Constants.DEFAULT_PAGE_SIZE)int size){ - return PageDtoMapper.toDto(orderService.getAll(userId, page, size), this::toDto); - } - - - @GetMapping("/{id}") - public OrderDto get( - @PathVariable(name = "user") Long userId, - @PathVariable(name = "id")Long id) { - return toDto(orderService.get(userId, id)); - } - - @PostMapping - public OrderDto create( - @PathVariable(name = "user") Long userId, - @RequestBody @Valid OrderDto dto){ - return toDto(orderService.create(userId, toEntity(dto))); - } - - @DeleteMapping("/{id}") - public OrderDto delete( - @PathVariable(name = "user") Long userId, - @PathVariable(name = "id") Long id){ - return toDto(orderService.delete(userId, id)); - } -} diff --git a/src/main/java/com/example/demo/orders/api/OrderDto.java b/src/main/java/com/example/demo/orders/api/OrderDto.java index abf1d68..d25035c 100644 --- a/src/main/java/com/example/demo/orders/api/OrderDto.java +++ b/src/main/java/com/example/demo/orders/api/OrderDto.java @@ -3,40 +3,27 @@ package com.example.demo.orders.api; import java.util.ArrayList; import java.util.List; -import com.fasterxml.jackson.annotation.JsonProperty; - -import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotNull; public class OrderDto { - @JsonProperty(access = JsonProperty.Access.READ_ONLY) private Long id; @NotNull - @Min(1) - private Long userId; - @NotNull private final List games = new ArrayList<>(); - public Long getId(){ + public Long getId() { return id; } - public void setId(Long id){ + + public void setId(Long id) { this.id = id; } - public List getGames(){ + public List getGames() { return games; } - public void setGames(List games){ + + public void setGames(List games) { this.games.clear(); this.games.addAll(games); } - - public Long getUserId(){ - return userId; - } - - public void setUserId(Long userId){ - this.userId = userId; - } } diff --git a/src/main/java/com/example/demo/orders/model/OrderEntity.java b/src/main/java/com/example/demo/orders/model/OrderEntity.java index e71cd60..e2678fb 100644 --- a/src/main/java/com/example/demo/orders/model/OrderEntity.java +++ b/src/main/java/com/example/demo/orders/model/OrderEntity.java @@ -18,7 +18,7 @@ import jakarta.persistence.Table; @Entity @Table(name = "orders") -public class OrderEntity extends BaseEntity{ +public class OrderEntity extends BaseEntity { // @Column(nullable = false) // private double sum; @ManyToOne @@ -27,54 +27,57 @@ public class OrderEntity extends BaseEntity{ @ManyToMany() private Set games = new HashSet<>(); - public OrderEntity(){ + public OrderEntity() { } - public OrderEntity(UserEntity user, List games){ - this.user = user; + public OrderEntity(List games) { this.games.clear(); this.games.addAll(games); } // public double getSum(){ - // for(var game : games){ - // sum += game.getPrice(); - // } - - // return sum; + // for(var game : games){ + // sum += game.getPrice(); // } - public UserEntity getUser(){ + // return sum; + // } + + public UserEntity getUser() { return user; } - public void setUser(UserEntity user){ + + public void setUser(UserEntity user) { this.user = user; - if(!user.getOrders().contains(this)){ + if (!user.getOrders().contains(this)) { user.getOrders().add(this); } } - public Set getGames(){ + public Set getGames() { return games; } - public void setGames(GameEntity game){ + + public void setGames(GameEntity game) { this.games.add(game); } @Override - public int hashCode(){ + public int hashCode() { return Objects.hash(id, games); } @Override - public boolean equals(Object obj){ - if(this == obj) return true; - if(obj == null || getClass() != obj.getClass()) return false; + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null || getClass() != obj.getClass()) + return false; final OrderEntity other = (OrderEntity) obj; return Objects.equals(other.getId(), id) - //&& Objects.equals(other.getSum(), sum) - && Objects.equals(other.getGames(), games); + // && Objects.equals(other.getSum(), sum) + && Objects.equals(other.getGames(), games); } } diff --git a/src/main/java/com/example/demo/orders/repository/OrderRepository.java b/src/main/java/com/example/demo/orders/repository/OrderRepository.java index 31d4cdf..9015a75 100644 --- a/src/main/java/com/example/demo/orders/repository/OrderRepository.java +++ b/src/main/java/com/example/demo/orders/repository/OrderRepository.java @@ -11,7 +11,8 @@ import org.springframework.data.repository.PagingAndSortingRepository; import com.example.demo.orders.model.OrderEntity; -public interface OrderRepository extends CrudRepository, PagingAndSortingRepository { +public interface OrderRepository + extends CrudRepository, PagingAndSortingRepository { Optional findOneByUserIdAndId(long userId, long id); List findByUserId(long userId); @@ -20,6 +21,6 @@ public interface OrderRepository extends CrudRepository, Pagi Page findByUserId(long userId, Pageable pageable); List findAll(); - - //Можно сделать запрос на сумму JPQL + + // Можно сделать запрос на сумму JPQL } diff --git a/src/main/java/com/example/demo/orders/service/OrderService.java b/src/main/java/com/example/demo/orders/service/OrderService.java index af1bf3e..210ec35 100644 --- a/src/main/java/com/example/demo/orders/service/OrderService.java +++ b/src/main/java/com/example/demo/orders/service/OrderService.java @@ -1,11 +1,13 @@ package com.example.demo.orders.service; import java.util.List; +import java.util.stream.StreamSupport; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; +import org.springframework.data.domain.Sort; import org.springframework.transaction.annotation.Transactional; import com.example.demo.core.error.NotFoundException; @@ -19,37 +21,38 @@ public class OrderService { private final OrderRepository repository; private final UserService userService; - public OrderService(OrderRepository repository, UserService userService){ + public OrderService(OrderRepository repository, UserService userService) { this.repository = repository; this.userService = userService; } - @Transactional(readOnly = true) - public Page getAll(long userId, int page, int size){ - final Pageable pageRequest = PageRequest.of(page, size); + public Page getAll(long userId, int page, int size) { + final Pageable pageRequest = PageRequest.of(page, size, Sort.by("id")); userService.get(userId); return repository.findByUserId(userId, pageRequest); } @Transactional(readOnly = true) - public List getAll(long userId){ + public List getAll(long userId) { userService.get(userId); return repository.findByUserId(userId); } - public List getAll(){ + + public List getAll() { return repository.findAll(); } @Transactional(readOnly = true) - public OrderEntity get(long userId, long id){ + public OrderEntity get(long userId, long id) { userService.get(userId); - return repository.findOneByUserIdAndId(userId, id).orElseThrow(() -> new NotFoundException(OrderEntity.class, id)); + return repository.findOneByUserIdAndId(userId, id) + .orElseThrow(() -> new NotFoundException(OrderEntity.class, id)); } @Transactional - public OrderEntity create(long userId, OrderEntity entity){ - if(entity == null){ + public OrderEntity create(long userId, OrderEntity entity) { + if (entity == null) { throw new IllegalArgumentException("Entity is null"); } final UserEntity existsUser = userService.get(userId); @@ -58,7 +61,17 @@ public class OrderService { } @Transactional - public OrderEntity delete(long userId, long id){ + public List createAll(long userId, List entities) { + if (entities == null || entities.isEmpty()) { + throw new IllegalArgumentException("Orders list is null or empty"); + } + final UserEntity existsUser = userService.get(userId); + entities.forEach(entity -> entity.setUser(existsUser)); + return StreamSupport.stream(repository.saveAll(entities).spliterator(), false).toList(); + } + + @Transactional + public OrderEntity delete(long userId, long id) { userService.get(userId); final OrderEntity existsEntity = get(userId, id); repository.delete(existsEntity); diff --git a/src/main/java/com/example/demo/types/api/TypeController.java b/src/main/java/com/example/demo/types/api/TypeController.java index 2938544..af64bb9 100644 --- a/src/main/java/com/example/demo/types/api/TypeController.java +++ b/src/main/java/com/example/demo/types/api/TypeController.java @@ -1,16 +1,14 @@ package com.example.demo.types.api; -import java.util.List; - import org.modelmapper.ModelMapper; -import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.validation.BindingResult; 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.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; import com.example.demo.core.configuration.Constants; import com.example.demo.types.model.TypeEntity; @@ -18,47 +16,90 @@ import com.example.demo.types.service.TypeService; import jakarta.validation.Valid; -@RestController -@RequestMapping(Constants.API_URL + "/type") +@Controller +@RequestMapping(TypeController.URL) public class TypeController { + + public static final String URL = Constants.ADMIN_PREFIX + "/type"; + private static final String TYPE_VIEW = "type"; + private static final String TYPE_EDIT_VIEW = "type-edit"; + private static final String TYPE_ATTRIBUTE = "type"; + private final TypeService typeService; private final ModelMapper modelMapper; - public TypeController(TypeService typeService, ModelMapper modelMapper){ + public TypeController(TypeService typeService, ModelMapper modelMapper) { this.typeService = typeService; this.modelMapper = modelMapper; } - private TypeDto toDto(TypeEntity entity){ + private TypeDto toDto(TypeEntity entity) { return modelMapper.map(entity, TypeDto.class); } - private TypeEntity toEntity(TypeDto dto){ + private TypeEntity toEntity(TypeDto dto) { return modelMapper.map(dto, TypeEntity.class); } @GetMapping - public List getAll(){ - return typeService.getAll().stream().map(this::toDto).toList(); + public String getAll(Model model) { + model.addAttribute( + "items", + typeService.getAll().stream() + .map(this::toDto) + .toList()); + return TYPE_VIEW; } - @GetMapping("/{id}") - public TypeDto get(@PathVariable(name = "id") Long id){ - return toDto(typeService.get(id)); + @GetMapping("/edit/") + public String create(Model model) { + model.addAttribute(TYPE_ATTRIBUTE, new TypeDto()); + return TYPE_EDIT_VIEW; } - @PostMapping - public TypeDto create(@RequestBody @Valid TypeDto dto){ - return toDto(typeService.create(toEntity(dto))); + @PostMapping("/edit/") + public String create( + @ModelAttribute(name = TYPE_ATTRIBUTE) @Valid TypeDto type, + BindingResult bindingResult, + Model model) { + if (bindingResult.hasErrors()) { + return TYPE_EDIT_VIEW; + } + typeService.create(toEntity(type)); + return Constants.REDIRECT_VIEW + URL; } - @PutMapping("/{id}") - public TypeDto update(@PathVariable(name = "id") Long id, @RequestBody TypeDto dto){ - return toDto(typeService.update(id, toEntity(dto))); + @GetMapping("/edit/{id}") + public String update( + @PathVariable(name = "id") Long id, + Model model) { + if (id <= 0) { + throw new IllegalArgumentException(); + } + model.addAttribute(TYPE_ATTRIBUTE, toDto(typeService.get(id))); + return TYPE_EDIT_VIEW; } - @DeleteMapping("/{id}") - public TypeDto delete(@PathVariable(name = "id") Long id){ - return toDto(typeService.delete(id)); + @PostMapping("/edit/{id}") + public String update( + @PathVariable(name = "id") Long id, + @ModelAttribute(name = TYPE_ATTRIBUTE) @Valid TypeDto type, + BindingResult bindingResult, + Model model) { + if (bindingResult.hasErrors()) { + return TYPE_EDIT_VIEW; + } + if (id <= 0) { + throw new IllegalArgumentException(); + } + typeService.update(id, toEntity(type)); + return Constants.REDIRECT_VIEW + URL; + } + + @PostMapping("/delete/{id}") + public String delete( + @PathVariable(name = "id") Long id) { + typeService.delete(id); + return Constants.REDIRECT_VIEW + URL; } } diff --git a/src/main/java/com/example/demo/types/api/TypeDto.java b/src/main/java/com/example/demo/types/api/TypeDto.java index e451a22..98f3fff 100644 --- a/src/main/java/com/example/demo/types/api/TypeDto.java +++ b/src/main/java/com/example/demo/types/api/TypeDto.java @@ -1,30 +1,27 @@ package com.example.demo.types.api; -import com.fasterxml.jackson.annotation.JsonProperty; - import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; public class TypeDto { - @JsonProperty(access = JsonProperty.Access.READ_ONLY) private Long id; @NotBlank @Size(min = 1, max = 50) private String name; - public Long getId(){ + public Long getId() { return id; } - - public void setId(Long id){ + + public void setId(Long id) { this.id = id; } - public String getName(){ + public String getName() { return name; } - - public void setName(String name){ + + public void setName(String name) { this.name = name; } } diff --git a/src/main/java/com/example/demo/users/api/UserCartController.java b/src/main/java/com/example/demo/users/api/UserCartController.java new file mode 100644 index 0000000..3be1044 --- /dev/null +++ b/src/main/java/com/example/demo/users/api/UserCartController.java @@ -0,0 +1,128 @@ +package com.example.demo.users.api; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +import org.modelmapper.ModelMapper; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.SessionAttributes; +import org.springframework.web.bind.support.SessionStatus; + +import com.example.demo.core.configuration.Constants; +import com.example.demo.core.security.UserPrincipal; +import com.example.demo.core.session.SessionCart; +import com.example.demo.games.api.GameDto; +import com.example.demo.games.model.GameEntity; +import com.example.demo.games.service.GameService; +import com.example.demo.genres.model.GenreEntity; +import com.example.demo.orders.model.OrderEntity; +import com.example.demo.orders.service.OrderService; + +import jakarta.validation.Valid; + +@Controller +@RequestMapping(UserCartController.URL) +@SessionAttributes("games") +public class UserCartController { + public static final String URL = "/cart"; + private static final String ORDER_VIEW = "cart"; + private static final String ORDER_ATTRIBUTE = "order"; + private static final String CART_ATTRIBUTE = "cart"; + + private final GameService gameService; + private final OrderService orderService; + private final SessionCart cart; + private final ModelMapper modelMapper; + + public UserCartController( + GameService gameService, + OrderService orderService, + SessionCart cart, + ModelMapper modelMapper) { + this.gameService = gameService; + this.orderService = orderService; + this.cart = cart; + this.modelMapper = modelMapper; + } + + private GameDto toGameDto(GameEntity entity) { + var dto = new GameDto(); + dto.setId(entity.getId()); + dto.setGenres(entity.getGenres().stream().map(GenreEntity::getId).toList()); + dto.setDescription(entity.getDescription()); + dto.setName(entity.getName()); + dto.setPrice(entity.getPrice()); + dto.setTypeId(entity.getType().getId()); + return dto; + } + + private List toOrderEntities(Collection dtos) { + final Set gameIds = dtos.stream() + .map(UserCartDto::getGame) + .collect(Collectors.toSet()); + final Map games = gameService.getByIds(gameIds).stream() + .collect(Collectors.toMap(GameEntity::getId, Function.identity())); + return dtos.stream() + .map(dto -> { + final OrderEntity entity = modelMapper.map(dto, OrderEntity.class); + entity.setGames(games.get(dto.getGame())); + return entity; + }).toList(); + } + + @GetMapping + public String getCart(Model model) { + model.addAttribute("games", + gameService.getAll(0, 0).stream() + .map(this::toGameDto) + .toList()); + model.addAttribute(ORDER_ATTRIBUTE, new UserCartDto()); + model.addAttribute(CART_ATTRIBUTE, cart.values()); + return ORDER_VIEW; + } + + @PostMapping + public String addOrderToCart( + @ModelAttribute(name = ORDER_ATTRIBUTE) @Valid UserCartDto order, + BindingResult bindingResult, + SessionStatus status, + Model model) { + if (bindingResult.hasErrors()) { + return ORDER_VIEW; + } + status.setComplete(); + order.setGameName(gameService.get(order.getGame()).getName()); + order.setPrice(gameService.get(order.getGame()).getPrice()); + cart.computeIfPresent(order.hashCode(), (key, value) -> { + return value; + }); + cart.put(order.hashCode(), order); + return Constants.REDIRECT_VIEW + URL; + } + + @PostMapping("/save") + public String saveCart( + Model model, + @AuthenticationPrincipal UserPrincipal principal) { + orderService.createAll(principal.getId(), toOrderEntities(cart.values())); + cart.clear(); + return Constants.REDIRECT_VIEW + URL; + } + + @PostMapping("/clear") + public String clearCart() { + cart.clear(); + return Constants.REDIRECT_VIEW + URL; + } +} diff --git a/src/main/java/com/example/demo/users/api/UserCartDto.java b/src/main/java/com/example/demo/users/api/UserCartDto.java new file mode 100644 index 0000000..b95281c --- /dev/null +++ b/src/main/java/com/example/demo/users/api/UserCartDto.java @@ -0,0 +1,53 @@ +package com.example.demo.users.api; + +import java.util.Objects; + +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotNull; + +public class UserCartDto { + @NotNull + private Long game; + private String gameName; + @Min(1000) + private Double price; + + public Long getGame() { + return game; + } + + public void setGame(Long gameId) { + this.game = gameId; + } + + public String getGameName() { + return gameName; + } + + public void setGameName(String gameName) { + this.gameName = gameName; + } + + public Double getPrice() { + return price; + } + + public void setPrice(Double price) { + this.price = price; + } + + @Override + public int hashCode() { + return Objects.hash(game, price); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null || getClass() != obj.getClass()) + return false; + UserCartDto other = (UserCartDto) obj; + return Objects.equals(game, other.game) && Objects.equals(price, other.price); + } +} diff --git a/src/main/java/com/example/demo/users/api/UserController.java b/src/main/java/com/example/demo/users/api/UserController.java index 102cf11..8f2f882 100644 --- a/src/main/java/com/example/demo/users/api/UserController.java +++ b/src/main/java/com/example/demo/users/api/UserController.java @@ -1,18 +1,22 @@ package com.example.demo.users.api; +import java.util.Map; + import org.modelmapper.ModelMapper; -import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.validation.BindingResult; 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.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; + import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.mvc.support.RedirectAttributes; + +import com.example.demo.core.api.PageAttributesMapper; -import com.example.demo.core.api.PageDto; -import com.example.demo.core.api.PageDtoMapper; import com.example.demo.core.configuration.Constants; import com.example.demo.orders.service.OrderService; import com.example.demo.users.model.UserEntity; @@ -20,49 +24,103 @@ import com.example.demo.users.service.UserService; import jakarta.validation.Valid; -@RestController -@RequestMapping(Constants.API_URL+"/user") +@Controller +@RequestMapping(UserController.URL) public class UserController { + + public static final String URL = Constants.ADMIN_PREFIX + "/user"; + private static final String USER_VIEW = "user"; + private static final String USER_EDIT_VIEW = "user-edit"; + private static final String PAGE_ATTRIBUTE = "page"; + private static final String USER_ATTRIBUTE = "user"; + private final ModelMapper modelMapper; private final UserService userService; - public UserController(OrderService orderService, ModelMapper modelMapper, UserService userService){ + public UserController(OrderService orderService, ModelMapper modelMapper, UserService userService) { this.modelMapper = modelMapper; this.userService = userService; } - private UserDto toDto(UserEntity entity){ + private UserDto toDto(UserEntity entity) { return modelMapper.map(entity, UserDto.class); } - private UserEntity toEntity(UserDto dto){ + private UserEntity toEntity(UserDto dto) { return modelMapper.map(dto, UserEntity.class); } @GetMapping - public PageDto getAll( - @RequestParam(name = "page", defaultValue = "0") int page, - @RequestParam(name = "size", defaultValue = Constants.DEFAULT_PAGE_SIZE) int size) { - return PageDtoMapper.toDto(userService.getAll(page, size), this::toDto); + public String getAll( + @RequestParam(name = PAGE_ATTRIBUTE, defaultValue = "0") int page, + Model model) { + final Map attributes = PageAttributesMapper.toAttributes( + userService.getAll(page, Constants.DEFAULT_PAGE_SIZE), this::toDto); + model.addAllAttributes(attributes); + model.addAttribute(PAGE_ATTRIBUTE, page); + return USER_VIEW; } - @GetMapping("/{id}") - public UserDto get(@PathVariable(name = "id") Long id){ - return toDto(userService.get(id)); + @GetMapping("/edit/") + public String create( + @RequestParam(name = PAGE_ATTRIBUTE, defaultValue = "0") int page, + Model model) { + model.addAttribute(USER_ATTRIBUTE, new UserDto()); + model.addAttribute(PAGE_ATTRIBUTE, page); + return USER_EDIT_VIEW; } - @PostMapping - public UserDto create(@RequestBody @Valid UserDto dto){ - return toDto(userService.create(toEntity(dto))); + @PostMapping("/edit/") + public String create( + @RequestParam(name = PAGE_ATTRIBUTE, defaultValue = "0") int page, + @ModelAttribute(name = USER_ATTRIBUTE) @Valid UserDto user, + BindingResult bindingResult, + Model model, + RedirectAttributes redirectAttributes) { + if (bindingResult.hasErrors()) { + model.addAttribute(PAGE_ATTRIBUTE, page); + return USER_EDIT_VIEW; + } + redirectAttributes.addAttribute(PAGE_ATTRIBUTE, page); + userService.create(toEntity(user)); + return Constants.REDIRECT_VIEW + URL; } - @PutMapping("/{id}") - public UserDto update(@PathVariable(name = "id") Long id, @RequestBody UserDto dto){ - return toDto(userService.update(id, toEntity(dto))); + @GetMapping("/edit/{id}") + public String update( + @PathVariable(name = "id") Long id, + @RequestParam(name = PAGE_ATTRIBUTE, defaultValue = "0") int page, + Model model) { + if (id <= 0) { + throw new IllegalArgumentException(); + } + model.addAttribute(USER_ATTRIBUTE, toDto(userService.get(id))); + model.addAttribute(PAGE_ATTRIBUTE, page); + return USER_EDIT_VIEW; } - @DeleteMapping("/{id}") - public UserDto delete(@PathVariable(name = "id")Long id){ + @PostMapping("/edit/{id}") + public String update( + @PathVariable(name = "id") Long id, + @RequestParam(name = PAGE_ATTRIBUTE, defaultValue = "0") int page, + @ModelAttribute(name = USER_ATTRIBUTE) @Valid UserDto user, + BindingResult bindingResult, + Model model, + RedirectAttributes redirectAttributes) { + if (bindingResult.hasErrors()) { + model.addAttribute(PAGE_ATTRIBUTE, page); + return USER_EDIT_VIEW; + } + if (id <= 0) { + throw new IllegalArgumentException(); + } + redirectAttributes.addAttribute(PAGE_ATTRIBUTE, page); + userService.update(id, toEntity(user)); + return Constants.REDIRECT_VIEW + URL; + } + + @PostMapping("/delete/{id}") + public UserDto delete(@PathVariable(name = "id") Long id) { return toDto(userService.delete(id)); } } diff --git a/src/main/java/com/example/demo/users/api/UserDto.java b/src/main/java/com/example/demo/users/api/UserDto.java index a0e0584..11ca987 100644 --- a/src/main/java/com/example/demo/users/api/UserDto.java +++ b/src/main/java/com/example/demo/users/api/UserDto.java @@ -1,12 +1,9 @@ package com.example.demo.users.api; -import com.fasterxml.jackson.annotation.JsonProperty; - import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; public class UserDto { - @JsonProperty(access = JsonProperty.Access.READ_ONLY) private Long id; @NotBlank @Size(min = 2, max = 20) @@ -14,35 +11,37 @@ public class UserDto { @NotBlank @Size(min = 2, max = 20) private String email; - @NotBlank - @Size(min = 2, max = 20) - private String password; + private String role; - public Long getId(){ + public Long getId() { return id; } - public void setId(Long id){ + + public void setId(Long id) { this.id = id; } - public String getLogin(){ + public String getLogin() { return login; } - public void setLogin(String login){ + + public void setLogin(String login) { this.login = login; } - public String getEmail(){ + public String getEmail() { return email; } - public void setEmail(String email){ + + public void setEmail(String email) { this.email = email; } - public String getPassword(){ - return password; + public String getRole() { + return role; } - public void setPassword(String password){ - this.password = password; + + public void setRole(String role) { + this.role = role; } } diff --git a/src/main/java/com/example/demo/users/api/UserProfileController.java b/src/main/java/com/example/demo/users/api/UserProfileController.java new file mode 100644 index 0000000..f480f40 --- /dev/null +++ b/src/main/java/com/example/demo/users/api/UserProfileController.java @@ -0,0 +1,82 @@ +package com.example.demo.users.api; + +import org.modelmapper.ModelMapper; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; + +import com.example.demo.core.api.PageAttributesMapper; +import com.example.demo.core.configuration.Constants; +import com.example.demo.core.security.UserPrincipal; +import com.example.demo.games.api.GameDto; +import com.example.demo.games.model.GameEntity; +import com.example.demo.games.service.GameService; +import com.example.demo.genres.model.GenreEntity; +import com.example.demo.orders.api.OrderDto; +import com.example.demo.orders.model.OrderEntity; +import com.example.demo.orders.service.OrderService; +import com.example.demo.users.service.UserService; + +@Controller +public class UserProfileController { + private static final String PROFILE_VIEW = "profile"; + + private static final String PAGE_ATTRIBUTE = "page"; + private static final String TYPEID_ATTRIBUTE = "gameId"; + private static final String PROFILE_ATTRIBUTE = "profile"; + + private final OrderService orderService; + private final GameService gameService; + private final UserService userService; + private final ModelMapper modelMapper; + + public UserProfileController( + OrderService orderService, + GameService gameService, + UserService userService, + ModelMapper modelMapper) { + this.orderService = orderService; + this.gameService = gameService; + this.userService = userService; + this.modelMapper = modelMapper; + } + + private OrderDto toDto(OrderEntity entity) { + var dto = new OrderDto(); + dto.setId(entity.getId()); + dto.setGames(entity.getGames().stream().map(GameEntity::getId).toList()); + return dto; + } + + private GameDto toGameDto(GameEntity entity) { + var dto = new GameDto(); + dto.setId(entity.getId()); + dto.setGenres(entity.getGenres().stream().map(GenreEntity::getId).toList()); + dto.setDescription(entity.getDescription()); + dto.setName(entity.getName()); + dto.setPrice(entity.getPrice()); + dto.setTypeId(entity.getType().getId()); + return dto; + } + + @GetMapping + public String getProfile( + @RequestParam(name = PAGE_ATTRIBUTE, defaultValue = "0") int page, + @RequestParam(name = TYPEID_ATTRIBUTE, defaultValue = "0") int typeId, + Model model, + @AuthenticationPrincipal UserPrincipal principal) { + final long userId = principal.getId(); + model.addAttribute(PAGE_ATTRIBUTE, page); + model.addAttribute(TYPEID_ATTRIBUTE, typeId); + model.addAllAttributes(PageAttributesMapper.toAttributes( + orderService.getAll(userId, page, Constants.DEFAULT_PAGE_SIZE), + this::toDto)); + model.addAttribute("games", + gameService.getAll(0, 0).stream() + .map(this::toGameDto) + .toList()); + return PROFILE_VIEW; + } +} diff --git a/src/main/java/com/example/demo/users/api/UserSignupController.java b/src/main/java/com/example/demo/users/api/UserSignupController.java new file mode 100644 index 0000000..01d0509 --- /dev/null +++ b/src/main/java/com/example/demo/users/api/UserSignupController.java @@ -0,0 +1,63 @@ +package com.example.demo.users.api; + +import java.util.Objects; + +import org.modelmapper.ModelMapper; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +import com.example.demo.core.configuration.Constants; +import com.example.demo.users.model.UserEntity; +import com.example.demo.users.service.UserService; + +import jakarta.validation.Valid; + +@Controller +@RequestMapping(UserSignupController.URL) +public class UserSignupController { + public static final String URL = "/signup"; + + private static final String SIGNUP_VIEW = "signup"; + private static final String USER_ATTRIBUTE = "user"; + + private final UserService userService; + private final ModelMapper modelMapper; + + public UserSignupController( + UserService userService, + ModelMapper modelMapper) { + this.userService = userService; + this.modelMapper = modelMapper; + } + + private UserEntity toEntity(UserSignupDto dto) { + return modelMapper.map(dto, UserEntity.class); + } + + @GetMapping + public String getSignup(Model model) { + model.addAttribute(USER_ATTRIBUTE, new UserSignupDto()); + return SIGNUP_VIEW; + } + @PostMapping + public String signup( + @ModelAttribute(name = USER_ATTRIBUTE) @Valid UserSignupDto user, + BindingResult bindingResult, + Model model) { + if (bindingResult.hasErrors()) { + return SIGNUP_VIEW; + } + if (!Objects.equals(user.getPassword(), user.getPasswordConfirm())) { + bindingResult.rejectValue("password", "signup:passwords", "Пароли не совпадают."); + model.addAttribute(USER_ATTRIBUTE, user); + return SIGNUP_VIEW; + } + userService.create(toEntity(user)); + return Constants.REDIRECT_VIEW + Constants.LOGIN_URL + "?signup"; + } +} diff --git a/src/main/java/com/example/demo/users/api/UserSignupDto.java b/src/main/java/com/example/demo/users/api/UserSignupDto.java new file mode 100644 index 0000000..9530e92 --- /dev/null +++ b/src/main/java/com/example/demo/users/api/UserSignupDto.java @@ -0,0 +1,51 @@ +package com.example.demo.users.api; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +public class UserSignupDto { + @NotBlank + @Size(min = 3, max = 20) + private String login; + @NotBlank + @Size(min = 3, max = 20) + private String email; + @NotBlank + @Size(min = 3, max = 20) + private String password; + @NotBlank + @Size(min = 3, max = 20) + private String passwordConfirm; + + public String getLogin() { + return login; + } + + public void setLogin(String login) { + this.login = login; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getPasswordConfirm() { + return passwordConfirm; + } + + public void setPasswordConfirm(String passwordConfirm) { + this.passwordConfirm = passwordConfirm; + } +} diff --git a/src/main/java/com/example/demo/users/model/UserEntity.java b/src/main/java/com/example/demo/users/model/UserEntity.java index e093a4a..ddd8663 100644 --- a/src/main/java/com/example/demo/users/model/UserEntity.java +++ b/src/main/java/com/example/demo/users/model/UserEntity.java @@ -20,8 +20,9 @@ public class UserEntity extends BaseEntity{ private String login; @Column(nullable = false, unique = true, length = 20) private String email; - @Column(nullable = false, unique = true, length = 20) + @Column(nullable = false, length = 60) private String password; + private UserRole role; @OneToMany(mappedBy = "user", cascade = CascadeType.ALL) @OrderBy("id ASC") private List orders = new ArrayList<>(); @@ -33,6 +34,7 @@ public class UserEntity extends BaseEntity{ this.login = login; this.email = email; this.password = password; + this.role = UserRole.USER; } public String getLogin(){ @@ -56,6 +58,14 @@ public class UserEntity extends BaseEntity{ this.password = password; } + public UserRole getRole() { + return role; + } + + public void setRole(UserRole role) { + this.role = role; + } + public List getOrders(){ return orders; } diff --git a/src/main/java/com/example/demo/users/model/UserRole.java b/src/main/java/com/example/demo/users/model/UserRole.java new file mode 100644 index 0000000..86056ca --- /dev/null +++ b/src/main/java/com/example/demo/users/model/UserRole.java @@ -0,0 +1,15 @@ +package com.example.demo.users.model; + +import org.springframework.security.core.GrantedAuthority; + +public enum UserRole implements GrantedAuthority{ + ADMIN, + USER; + + private static final String PREFIX = "ROLE_"; + + @Override + public String getAuthority() { + return PREFIX + this.name(); + } +} diff --git a/src/main/java/com/example/demo/users/service/UserService.java b/src/main/java/com/example/demo/users/service/UserService.java index 040b5d4..867d956 100644 --- a/src/main/java/com/example/demo/users/service/UserService.java +++ b/src/main/java/com/example/demo/users/service/UserService.java @@ -4,60 +4,100 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; +import java.util.Optional; import java.util.stream.StreamSupport; import org.springframework.data.domain.Page; +import org.springframework.data.domain.Sort; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.data.domain.PageRequest; +import org.springframework.util.StringUtils; +import com.example.demo.core.configuration.Constants; import com.example.demo.core.error.NotFoundException; +import com.example.demo.core.security.UserPrincipal; import com.example.demo.users.model.UserEntity; +import com.example.demo.users.model.UserRole; import com.example.demo.users.repository.UserRepository; @Service -public class UserService { +public class UserService implements UserDetailsService { private final UserRepository repository; + private final PasswordEncoder passwordEncoder; - public UserService(UserRepository repository){ + public UserService(UserRepository repository, PasswordEncoder passwordEncoder) { this.repository = repository; + this.passwordEncoder = passwordEncoder; + } + + private void checkLogin(Long id, String login) { + final Optional existsUser = repository.findByLoginIgnoreCase(login); + if (existsUser.isPresent() && !existsUser.get().getId().equals(id)) { + throw new IllegalArgumentException( + String.format("User with login %s is already exists", login)); + } } @Transactional(readOnly = true) - public List getAll(){ + public List getAll() { return StreamSupport.stream(repository.findAll().spliterator(), false).toList(); } @Transactional(readOnly = true) public Page getAll(int page, int size) { - return repository.findAll(PageRequest.of(page, size)); + return repository.findAll(PageRequest.of(page, size, Sort.by("id"))); } @Transactional(readOnly = true) - public UserEntity get(Long id){ - return repository.findById(id).orElseThrow(() -> new NotFoundException(UserEntity.class, id)); + public UserEntity get(Long id) { + return repository.findById(id).orElseThrow(() -> new NotFoundException(UserEntity.class, id)); + } + + @Transactional(readOnly = true) + public UserEntity getByLogin(String login) { + return repository.findByLoginIgnoreCase(login) + .orElseThrow(() -> new IllegalArgumentException("Invalid login")); } @Transactional - public UserEntity create(UserEntity entity){ + public UserEntity create(UserEntity entity) { if (entity == null) { throw new IllegalArgumentException("Entity is null"); } + checkLogin(null, entity.getLogin()); + final String password = Optional.ofNullable(entity.getPassword()).orElse(""); + entity.setPassword( + passwordEncoder.encode( + StringUtils.hasText(password.strip()) ? password : Constants.DEFAULT_PASSWORD)); + entity.setRole(Optional.ofNullable(entity.getRole()).orElse(UserRole.USER)); + repository.save(entity); return repository.save(entity); } @Transactional - public UserEntity update(Long id, UserEntity entity){ + public UserEntity update(Long id, UserEntity entity) { final UserEntity existEntity = get(id); + checkLogin(id, entity.getLogin()); existEntity.setLogin(entity.getLogin()); existEntity.setEmail(entity.getEmail()); - existEntity.setPassword(entity.getPassword()); repository.save(existEntity); return existEntity; } @Transactional - public UserEntity delete(Long id){ + public UserEntity delete(Long id) { final UserEntity existEntity = get(id); repository.delete(existEntity); return existEntity; } + + @Override + @Transactional(readOnly = true) + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + final UserEntity existsUser = getByLogin(username); + return new UserPrincipal(existsUser); + } } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index f803d58..97cc8c1 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,3 +1,4 @@ +# Server spring.main.banner-mode=off server.port=8080 diff --git a/src/main/resources/public/css/style.css b/src/main/resources/public/css/style.css new file mode 100644 index 0000000..7423490 --- /dev/null +++ b/src/main/resources/public/css/style.css @@ -0,0 +1,67 @@ +html, +body { + height: 100%; +} + +h1 { + font-size: 1.5em; +} + +h2 { + font-size: 1.25em; +} + +h3 { + font-size: 1.1em; +} + +td form { + margin: 0; + padding: 0; + margin-top: -.25em; +} + +.button-fixed-width { + width: 150px; +} + +.button-link { + padding: 0; +} + +.invalid-feedback { + display: block; +} + +.w-10 { + width: 10% !important; +} + +.my-navbar { + background-color: #3c3c3c !important; +} + +.my-navbar .link a:hover { + text-decoration: underline; +} + +.my-navbar .logo { + width: 26px; + height: 26px; +} + +.my-footer { + background-color: #2c2c2c; + height: 32px; + color: rgba(255, 255, 255, 0.5); +} + +.cart-image { + width: 3.1rem; + padding: 0.25rem; + border-radius: 0.5rem; +} + +.cart-item { + height: auto; +} \ No newline at end of file diff --git a/src/main/resources/public/favicon.svg b/src/main/resources/public/favicon.svg new file mode 100644 index 0000000..7a161ba --- /dev/null +++ b/src/main/resources/public/favicon.svg @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/src/main/resources/templates/cart.html b/src/main/resources/templates/cart.html new file mode 100644 index 0000000..a19c372 --- /dev/null +++ b/src/main/resources/templates/cart.html @@ -0,0 +1,68 @@ + + + + + Корзина + + + +
+
+
+ Корзина +
+ +
+
+
+
+
+ [[${cartItem.gameName}]] + +
+
+
+
+ +
+
+
+ +
+
+
+
+
+
+ + +
+
+ + +
+
+
+ + + + + \ No newline at end of file diff --git a/src/main/resources/templates/default.html b/src/main/resources/templates/default.html new file mode 100644 index 0000000..2be9f25 --- /dev/null +++ b/src/main/resources/templates/default.html @@ -0,0 +1,75 @@ + + + + + + + + My shop + + + + + + + + +
+
+
+ Автор, [[${#dates.year(#dates.createNow())}]] +
+ + + \ No newline at end of file diff --git a/src/main/resources/templates/error.html b/src/main/resources/templates/error.html new file mode 100644 index 0000000..faa6b0a --- /dev/null +++ b/src/main/resources/templates/error.html @@ -0,0 +1,37 @@ + + + + + Ошибка + + + +
+
    + +
  • + Неизвестная ошибка +
  • +
    + +
  • + Ошибка: [[${message}]] +
  • +
    + +
  • + Адрес: [[${url}]] +
  • +
  • + Класс исключения: [[${exception}]] +
  • +
  • + [[${method}]] ([[${file}]]:[[${line}]]) +
  • +
    +
+ На главную +
+ + + \ No newline at end of file diff --git a/src/main/resources/templates/game.html b/src/main/resources/templates/game.html new file mode 100644 index 0000000..43bd0cb --- /dev/null +++ b/src/main/resources/templates/game.html @@ -0,0 +1,55 @@ + + + + + Игры + + + +
+ +

Данные отсутствуют

+ +

Игры

+ + + + + + + + + + + + + + + + + + + + + + + + +
IDНазваниеЦенаОписание
+
+ +
+
+
+ +
+
+
+
+
+ + + \ No newline at end of file diff --git a/src/main/resources/templates/genre-edit.html b/src/main/resources/templates/genre-edit.html new file mode 100644 index 0000000..7d981b7 --- /dev/null +++ b/src/main/resources/templates/genre-edit.html @@ -0,0 +1,28 @@ + + + + + Редакторовать жанр + + + +
+
+
+ + +
+
+ + +
+
+
+ + Отмена +
+
+
+ + + \ No newline at end of file diff --git a/src/main/resources/templates/genre.html b/src/main/resources/templates/genre.html new file mode 100644 index 0000000..817ccfc --- /dev/null +++ b/src/main/resources/templates/genre.html @@ -0,0 +1,51 @@ + + + + + Жанры игр + + + +
+ +

Данные отсутствуют

+ +

Жанры игр

+ + + + + + + + + + + + + + + + + + + + +
IDЖанр
+
+ +
+
+
+ +
+
+
+
+
+ + + \ No newline at end of file diff --git a/src/main/resources/templates/login.html b/src/main/resources/templates/login.html new file mode 100644 index 0000000..a44fde8 --- /dev/null +++ b/src/main/resources/templates/login.html @@ -0,0 +1,42 @@ + + + + + Вход + + + +
+
+
+ Неверный логин или пароль +
+
+ Выход успешно произведен +
+
+ Пользователь успешно создан +
+
+ + +
+
+ + +
+
+ + +
+
+ + Регистрация +
+
+
+ + + \ No newline at end of file diff --git a/src/main/resources/templates/orders.html b/src/main/resources/templates/orders.html new file mode 100644 index 0000000..8a5340c --- /dev/null +++ b/src/main/resources/templates/orders.html @@ -0,0 +1,62 @@ + + + + + + +

Данные отсутствуют

+ + + + + + + + + + + + + + + + + + + +
IDИгры
+ + + + + + + + + + + + +
Название игры
+
+
+ + + +
+
+
+ + + +
+ + + + \ No newline at end of file diff --git a/src/main/resources/templates/pagination.html b/src/main/resources/templates/pagination.html new file mode 100644 index 0000000..b11664a --- /dev/null +++ b/src/main/resources/templates/pagination.html @@ -0,0 +1,51 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/templates/profile.html b/src/main/resources/templates/profile.html new file mode 100644 index 0000000..57e7454 --- /dev/null +++ b/src/main/resources/templates/profile.html @@ -0,0 +1,25 @@ + + + + + Личный кабинет + + + +
+ + +
+
+ +
+
+
+ + + \ No newline at end of file diff --git a/src/main/resources/templates/signup.html b/src/main/resources/templates/signup.html new file mode 100644 index 0000000..ef77769 --- /dev/null +++ b/src/main/resources/templates/signup.html @@ -0,0 +1,40 @@ + + + + + Вход + + + +
+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+
+ + Отмена +
+
+
+ + + \ No newline at end of file diff --git a/src/main/resources/templates/type-edit.html b/src/main/resources/templates/type-edit.html new file mode 100644 index 0000000..91f03ca --- /dev/null +++ b/src/main/resources/templates/type-edit.html @@ -0,0 +1,28 @@ + + + + + Редакторовать тип заказа + + + +
+
+
+ + +
+
+ + +
+
+
+ + Отмена +
+
+
+ + + \ No newline at end of file diff --git a/src/main/resources/templates/type.html b/src/main/resources/templates/type.html new file mode 100644 index 0000000..0385aa1 --- /dev/null +++ b/src/main/resources/templates/type.html @@ -0,0 +1,50 @@ + + + + + Типы игр + + + +
+ +

Данные отсутствуют

+ +

Типы игр

+ + + + + + + + + + + + + + + + + + + +
IDТип игры
+
+ +
+
+
+ +
+
+
+
+
+ + + \ No newline at end of file diff --git a/src/main/resources/templates/user-edit.html b/src/main/resources/templates/user-edit.html new file mode 100644 index 0000000..90d2254 --- /dev/null +++ b/src/main/resources/templates/user-edit.html @@ -0,0 +1,34 @@ + + + + + Редакторовать пользователя + + + +
+
+
+ + +
+
+ + +
+
+
+ + +
+
+
+ + Отмена +
+
+
+ + + \ No newline at end of file diff --git a/src/main/resources/templates/user.html b/src/main/resources/templates/user.html new file mode 100644 index 0000000..1fa8c7b --- /dev/null +++ b/src/main/resources/templates/user.html @@ -0,0 +1,58 @@ + + + + + Пользователи + + + +
+ +

Данные отсутствуют

+ +

Пользователи

+ + + + + + + + + + + + + + + + + + + + + +
IDИмя пользователяПочта
+
+ + +
+
+
+ + +
+
+
+ + +
+ + + \ No newline at end of file diff --git a/src/test/java/com/example/demo/OrderServiceTest.java b/src/test/java/com/example/demo/OrderServiceTest.java index 9124b3d..70038f7 100644 --- a/src/test/java/com/example/demo/OrderServiceTest.java +++ b/src/test/java/com/example/demo/OrderServiceTest.java @@ -53,27 +53,27 @@ class OrderServiceTest { private OrderEntity order1; @BeforeEach - void createData(){ + void createData() { genre1 = genreService.create(new GenreEntity("Приключения")); genre2 = genreService.create(new GenreEntity("Симулятор")); type1 = typeService.create(new TypeEntity("Игра")); type2 = typeService.create(new TypeEntity("Программа")); - genres1 = new ArrayList(); - genres1.add(genre1); - genres1.add(genre2); + genres1 = new ArrayList(); + genres1.add(genre1); + genres1.add(genre2); - genres2 = new ArrayList(); - genres2.add(genre2); + genres2 = new ArrayList(); + genres2.add(genre2); - game1 = gameService.create(new GameEntity(type1,"Game1",2100.0,"good game", genres1)); - game2 = gameService.create(new GameEntity( type2, "Game2", 1200.0,"bad game", genres2)); - games = new ArrayList(); - games.add(game1); - games.add(game2); + game1 = gameService.create(new GameEntity(type1, "Game1", 2100.0, "good game", genres1)); + game2 = gameService.create(new GameEntity(type2, "Game2", 1200.0, "bad game", genres2)); + games = new ArrayList(); + games.add(game1); + games.add(game2); - user1 = userService.create(new UserEntity( "login1", "email@mail.com", "qwerty123")); - order1 = orderService.create(user1.getId(), new OrderEntity(user1,games)); + user1 = userService.create(new UserEntity("login1", "email@mail.com", "qwerty123")); + order1 = orderService.create(user1.getId(), new OrderEntity(games)); removeData(); genre1 = genreService.create(new GenreEntity("Приключения")); @@ -81,50 +81,51 @@ class OrderServiceTest { type1 = typeService.create(new TypeEntity("Игра")); type2 = typeService.create(new TypeEntity("Программа")); - genres1 = new ArrayList(); - genres1.add(genre1); - genres1.add(genre2); + genres1 = new ArrayList(); + genres1.add(genre1); + genres1.add(genre2); - genres2 = new ArrayList(); - genres2.add(genre2); + genres2 = new ArrayList(); + genres2.add(genre2); - game1 = gameService.create(new GameEntity(type1,"Game1",2100.0,"good game", genres1)); - game2 = gameService.create(new GameEntity( type2, "Game2", 1200.0,"bad game", genres2)); - games = new ArrayList(); - games.add(game1); - games.add(game2); + game1 = gameService.create(new GameEntity(type1, "Game1", 2100.0, "good game", genres1)); + game2 = gameService.create(new GameEntity(type2, "Game2", 1200.0, "bad game", genres2)); + games = new ArrayList(); + games.add(game1); + games.add(game2); - user1 = userService.create(new UserEntity( "login1", "email@mail.com", "qwerty123")); - order1 = orderService.create(user1.getId(), new OrderEntity(user1,games)); - orderService.create(user1.getId(), new OrderEntity(user1,games)); + user1 = userService.create(new UserEntity("login1", "email@mail.com", "qwerty123")); + order1 = orderService.create(user1.getId(), new OrderEntity(games)); + orderService.create(user1.getId(), new OrderEntity(games)); } @AfterEach - void removeData(){ - orderService.getAll().forEach(item -> orderService.delete(item.getUser().getId(),item.getId())); + void removeData() { + orderService.getAll().forEach(item -> orderService.delete(item.getUser().getId(), item.getId())); userService.getAll().forEach(item -> userService.delete(item.getId())); - gameService.getAll(0,0).forEach(item -> gameService.delete(item.getId())); + gameService.getAll(0, 0).forEach(item -> gameService.delete(item.getId())); typeService.getAll().forEach(item -> typeService.delete(item.getId())); genreService.getAll().forEach(item -> genreService.delete(item.getId())); } @Test - void getTest(){ + void getTest() { Assertions.assertThrows(NotFoundException.class, () -> gameService.get(0L)); } @Test @Order(1) - void createTest(){ + void createTest() { Assertions.assertEquals(2, orderService.getAll(user1.getId()).size()); } @Test @Order(2) - void deleteTest(){ - orderService.delete(user1.getId(),order1.getId()); + void deleteTest() { + orderService.delete(user1.getId(), order1.getId()); Assertions.assertEquals(1, orderService.getAll(user1.getId()).size()); } } -//зависимости, core->security, user(loadUserByUserName, userrole), core->configuration, дохуя контроллеров юзера core->session +// зависимости, core->security, user(loadUserByUserName, userrole), +// core->configuration, дохуя контроллеров юзера core->session