From bfef99b5f293c2c0297f02efacdea3c8a5c6e102 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=95=D0=BB=D0=B5=D0=BD=D0=B0=20=D0=91=D0=B0=D0=BA=D0=B0?= =?UTF-8?q?=D0=BB=D1=8C=D1=81=D0=BA=D0=B0=D1=8F?= Date: Fri, 21 Jun 2024 10:28:09 +0400 Subject: [PATCH] =?UTF-8?q?=D0=B2=D1=81=D0=B5=20=D1=80=D0=B0=D0=B1=D0=BE?= =?UTF-8?q?=D1=82=D0=B0=D0=B5=D1=82,=20=D1=82=D0=BE=D0=BB=D1=8C=D0=BA?= =?UTF-8?q?=D0=BE=20=D0=B2=D0=BE=D1=82...=20=D0=BF=D0=B0=D0=B3=D0=B8=D0=BD?= =?UTF-8?q?=D0=B0=D1=86=D0=B8=D1=8E=20=D0=B1=D1=8B=20=D0=B5=D1=89=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/configurations/Constants.java | 2 +- .../favorites/api/FavoriteController.java | 174 +++++++++++++---- .../repository/FavoriteRepository.java | 4 +- .../favorites/service/FavoriteService.java | 9 + .../movies/api/MovieUserController.java | 2 + .../backend/viewed/api/ViewedController.java | 178 ++++++++++++++---- .../viewed/repository/ViewedRepository.java | 4 +- .../main/resources/templates/favorites.html | 15 -- .../src/main/resources/templates/favs.html | 68 +++++++ .../src/main/resources/templates/index.html | 4 +- .../src/main/resources/templates/movies.html | 11 +- .../src/main/resources/templates/viewed.html | 67 +++++++ data.mv.db | Bin 368640 -> 475136 bytes 13 files changed, 440 insertions(+), 98 deletions(-) delete mode 100644 backend/src/main/resources/templates/favorites.html create mode 100644 backend/src/main/resources/templates/favs.html create mode 100644 backend/src/main/resources/templates/viewed.html diff --git a/backend/src/main/java/com/example/backend/core/configurations/Constants.java b/backend/src/main/java/com/example/backend/core/configurations/Constants.java index 5ca3a8d..fd340f3 100644 --- a/backend/src/main/java/com/example/backend/core/configurations/Constants.java +++ b/backend/src/main/java/com/example/backend/core/configurations/Constants.java @@ -6,7 +6,7 @@ public class Constants { public static final String SEQUENCE_NAME = "hibernate_sequence"; - public static final int DEFUALT_PAGE_SIZE = 10; + public static final int DEFUALT_PAGE_SIZE = 5; public static final String REDIRECT_VIEW = "redirect:"; diff --git a/backend/src/main/java/com/example/backend/favorites/api/FavoriteController.java b/backend/src/main/java/com/example/backend/favorites/api/FavoriteController.java index 4713b32..3ebfe2d 100644 --- a/backend/src/main/java/com/example/backend/favorites/api/FavoriteController.java +++ b/backend/src/main/java/com/example/backend/favorites/api/FavoriteController.java @@ -1,77 +1,181 @@ package com.example.backend.favorites.api; import java.util.List; +import java.util.ArrayList; +import org.springframework.ui.Model; import org.modelmapper.ModelMapper; -import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Controller; 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.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.backend.core.configurations.Constants; import com.example.backend.favorites.model.FavoriteEntity; import com.example.backend.favorites.service.FavoriteService; +import com.example.backend.movies.api.MovieDTO; +import com.example.backend.movies.api.MovieUserDTO; +import com.example.backend.movies.model.MovieEntity; import com.example.backend.movies.service.MovieService; +import com.example.backend.users.model.UserEntity; import com.example.backend.users.service.UserService; +import com.example.backend.viewed.model.ViewedEntity; +import com.example.backend.viewed.service.ViewedService; -import jakarta.validation.Valid; - -@RestController -@RequestMapping(Constants.API_URL + "/favorite") +@Controller +@RequestMapping(FavoriteController.URL) public class FavoriteController { + public static final String URL = "/favs"; + private static final String FAVORITE_VIEW = "favs"; + private static final String PAGE_ATTRIBUTE = "page"; + private final FavoriteService favoriteService; private final UserService userService; private final MovieService movieService; private final ModelMapper modelMapper; + private final ViewedService viewedServise; public FavoriteController(FavoriteService favoriteService, UserService userService, - MovieService movieService, ModelMapper modelMapper) { + MovieService movieService, ModelMapper modelMapper, ViewedService viewedServise) { this.modelMapper = modelMapper; this.userService = userService; this.favoriteService = favoriteService; + this.viewedServise = viewedServise; this.movieService = movieService; } - private FavoriteDto toDto(FavoriteEntity entity) { - return modelMapper.map(entity, FavoriteDto.class); + private MovieDTO toDtoMovie(MovieEntity entity) { + return modelMapper.map(entity, MovieDTO.class); } - private FavoriteEntity toEntity(FavoriteDto dto) { - final FavoriteEntity entity = modelMapper.map(dto, FavoriteEntity.class); - entity.setMovie(movieService.get(dto.getMovieId())); - entity.setUser(userService.get(dto.getUserId())); - return entity; + private List getListMovieUserDTOs(Integer categorieId, int page) { + List movies = movieService.getAll(categorieId, page, Constants.DEFUALT_PAGE_SIZE) + .stream() + .map(this::toDtoMovie) + .toList(); + List muDTO = new ArrayList(); + + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + Integer currentUserId = userService.getCurrentUserId(authentication.getName()); + + List favs = favoriteService.getAll(currentUserId); + List viewes = viewedServise.getAll(currentUserId); + + for (MovieDTO movieDTO : movies) { + MovieUserDTO newMovDto = new MovieUserDTO(); + newMovDto.setMovieDTO(movieDTO); + newMovDto.setCountViewes(movieService.countView(movieDTO.getId())); + + for (FavoriteEntity favoriteEntity : favs) { + if ((int) favoriteEntity.getMovie().getId() == (int) movieDTO.getId()) { + newMovDto.setFavorite(true); + break; + } + newMovDto.setFavorite(false); + } + + for (ViewedEntity viewedEntity : viewes) { + if ((int) viewedEntity.getMovie().getId() == (int) movieDTO.getId()) { + newMovDto.setViewed(true); + break; + } + newMovDto.setViewed(false); + } + + muDTO.add(newMovDto); + } + return muDTO; } - @GetMapping - public List getAll(@RequestParam(name = "userId", defaultValue = "0") Integer userId) { - return favoriteService.getAll(userId).stream().map(this::toDto).toList(); + @GetMapping() + public String getAll(Model model, @RequestParam(name = PAGE_ATTRIBUTE, defaultValue = "0") int page) { + List muDTO = getListMovieUserDTOs(0, page); + model.addAttribute("movies", muDTO); + model.addAttribute(PAGE_ATTRIBUTE, page); + return FAVORITE_VIEW; } - @GetMapping("/{id}") - public FavoriteDto get(@PathVariable(name = "id") Integer id) { - return toDto(favoriteService.get(id)); + @PostMapping("/changeFavStatus/{id}") + public String changeFavStatus(@PathVariable(name = "id") Integer movieId, + @RequestParam(name = PAGE_ATTRIBUTE, defaultValue = "0") int page, + Model model) { + + boolean isFavorite = false; + + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + Integer currentUserId = userService.getCurrentUserId(authentication.getName()); + + FavoriteEntity fav = favoriteService.findByUserIdAndMovieId(currentUserId, movieId); + + if (fav == null) { + isFavorite = false; + } else { + isFavorite = true; + } + + if (isFavorite == false) { + + UserEntity user = userService.get(currentUserId); + MovieEntity movie = movieService.get(movieId); + + FavoriteEntity newFav = new FavoriteEntity(null, user, movie); + + favoriteService.create(newFav); + + } else { + + FavoriteEntity delFav = favoriteService.findByUserIdAndMovieId(currentUserId, movieId); + + favoriteService.delete(delFav.getId()); + } + + model.addAttribute(PAGE_ATTRIBUTE, page); + + return Constants.REDIRECT_VIEW + URL; } - @PostMapping - public FavoriteDto create(@RequestBody @Valid FavoriteDto dto) { - return toDto(favoriteService.create(toEntity(dto))); + @PostMapping("/changeViewStatus/{id}") + public String changeViewStatus(@PathVariable(name = "id") Integer movieId, + @RequestParam(name = PAGE_ATTRIBUTE, defaultValue = "0") int page, + Model model) { + + boolean isViewed = false; + + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + Integer currentUserId = userService.getCurrentUserId(authentication.getName()); + + ViewedEntity view = viewedServise.findByUserIdAndMovieId(currentUserId, movieId); + + if (view == null) { + isViewed = false; + } else { + isViewed = true; + } + + if (isViewed == false) { + + UserEntity user = userService.get(currentUserId); + MovieEntity movie = movieService.get(movieId); + + ViewedEntity newView = new ViewedEntity(null, user, movie); + + viewedServise.create(newView); + + } else { + + ViewedEntity delView = viewedServise.findByUserIdAndMovieId(currentUserId, movieId); + + viewedServise.delete(delView.getId()); + } + + model.addAttribute(PAGE_ATTRIBUTE, page); + + return Constants.REDIRECT_VIEW + URL; } - @PutMapping("/{id}") - public FavoriteDto update(@PathVariable(name = "id") Integer id, - @RequestBody FavoriteDto dto) { - return toDto(favoriteService.update(id, toEntity(dto))); - } - - @DeleteMapping("/{id}") - public FavoriteDto delete(@PathVariable(name = "id") Integer id) { - return toDto(favoriteService.delete(id)); - } } diff --git a/backend/src/main/java/com/example/backend/favorites/repository/FavoriteRepository.java b/backend/src/main/java/com/example/backend/favorites/repository/FavoriteRepository.java index 667dfea..806baf3 100644 --- a/backend/src/main/java/com/example/backend/favorites/repository/FavoriteRepository.java +++ b/backend/src/main/java/com/example/backend/favorites/repository/FavoriteRepository.java @@ -3,10 +3,12 @@ package com.example.backend.favorites.repository; import java.util.Optional; import java.util.List; import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.PagingAndSortingRepository; import com.example.backend.favorites.model.FavoriteEntity; -public interface FavoriteRepository extends CrudRepository { +public interface FavoriteRepository + extends CrudRepository, PagingAndSortingRepository { List findByUserId(Integer userId); diff --git a/backend/src/main/java/com/example/backend/favorites/service/FavoriteService.java b/backend/src/main/java/com/example/backend/favorites/service/FavoriteService.java index faab61f..21dcc3d 100644 --- a/backend/src/main/java/com/example/backend/favorites/service/FavoriteService.java +++ b/backend/src/main/java/com/example/backend/favorites/service/FavoriteService.java @@ -3,12 +3,16 @@ package com.example.backend.favorites.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.Sort; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.example.backend.core.errors.NotFoundException; import com.example.backend.favorites.model.FavoriteEntity; import com.example.backend.favorites.repository.FavoriteRepository; +import com.example.backend.users.model.UserEntity; @Service public class FavoriteService { @@ -26,6 +30,11 @@ public class FavoriteService { return repository.findByUserId(userId); } + @Transactional(readOnly = true) + public Page getAll(int page, int size) { + return repository.findAll(PageRequest.of(page, size, Sort.by("id"))); + } + @Transactional(readOnly = true) public FavoriteEntity get(Integer id) { return repository.findById(id).orElseThrow(() -> new NotFoundException(FavoriteEntity.class, id)); diff --git a/backend/src/main/java/com/example/backend/movies/api/MovieUserController.java b/backend/src/main/java/com/example/backend/movies/api/MovieUserController.java index 0cb0df8..f576e15 100644 --- a/backend/src/main/java/com/example/backend/movies/api/MovieUserController.java +++ b/backend/src/main/java/com/example/backend/movies/api/MovieUserController.java @@ -1,5 +1,6 @@ package com.example.backend.movies.api; +import java.util.Map; import java.util.List; import java.util.ArrayList; @@ -21,6 +22,7 @@ import org.springframework.ui.Model; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; +import com.example.backend.core.api.PageAttributesMapper; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; diff --git a/backend/src/main/java/com/example/backend/viewed/api/ViewedController.java b/backend/src/main/java/com/example/backend/viewed/api/ViewedController.java index 6e2ef78..2d2aac3 100644 --- a/backend/src/main/java/com/example/backend/viewed/api/ViewedController.java +++ b/backend/src/main/java/com/example/backend/viewed/api/ViewedController.java @@ -1,78 +1,182 @@ package com.example.backend.viewed.api; import java.util.List; +import java.util.ArrayList; import org.modelmapper.ModelMapper; -import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.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.backend.core.configurations.Constants; +import com.example.backend.favorites.model.FavoriteEntity; +import com.example.backend.favorites.service.FavoriteService; import com.example.backend.viewed.model.ViewedEntity; import com.example.backend.viewed.service.ViewedService; +import com.example.backend.movies.api.MovieDTO; +import com.example.backend.movies.api.MovieUserDTO; +import com.example.backend.movies.model.MovieEntity; import com.example.backend.movies.service.MovieService; +import com.example.backend.users.model.UserEntity; import com.example.backend.users.service.UserService; -import jakarta.validation.Valid; - -@RestController -@RequestMapping(Constants.API_URL + "/viewed") +@Controller +@RequestMapping(ViewedController.URL) public class ViewedController { - private final ViewedService viewedService; + public static final String URL = "/viewed"; + private static final String FAVORITE_VIEW = "viewed"; + private static final String PAGE_ATTRIBUTE = "page"; + + private final FavoriteService favoriteService; private final UserService userService; private final MovieService movieService; private final ModelMapper modelMapper; + private final ViewedService viewedServise; public ViewedController( - ViewedService viewedService, UserService userService, - MovieService movieService, ModelMapper modelMapper) { + FavoriteService favoriteService, UserService userService, + MovieService movieService, ModelMapper modelMapper, ViewedService viewedServise) { this.modelMapper = modelMapper; this.userService = userService; - this.viewedService = viewedService; + this.favoriteService = favoriteService; + this.viewedServise = viewedServise; this.movieService = movieService; } - private ViewedDto toDto(ViewedEntity entity) { - return modelMapper.map(entity, ViewedDto.class); + private MovieDTO toDtoMovie(MovieEntity entity) { + return modelMapper.map(entity, MovieDTO.class); } - private ViewedEntity toEntity(ViewedDto dto) { - final ViewedEntity entity = modelMapper.map(dto, ViewedEntity.class); - entity.setMovie(movieService.get(dto.getMovieId())); - entity.setUser(userService.get(dto.getUserId())); - return entity; + private List getListMovieUserDTOs(Integer categorieId, int page) { + List movies = movieService.getAll(categorieId, page, Constants.DEFUALT_PAGE_SIZE) + .stream() + .map(this::toDtoMovie) + .toList(); + List muDTO = new ArrayList(); + + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + Integer currentUserId = userService.getCurrentUserId(authentication.getName()); + + List favs = favoriteService.getAll(currentUserId); + List viewes = viewedServise.getAll(currentUserId); + + for (MovieDTO movieDTO : movies) { + MovieUserDTO newMovDto = new MovieUserDTO(); + newMovDto.setMovieDTO(movieDTO); + newMovDto.setCountViewes(movieService.countView(movieDTO.getId())); + + for (FavoriteEntity favoriteEntity : favs) { + if ((int) favoriteEntity.getMovie().getId() == (int) movieDTO.getId()) { + newMovDto.setFavorite(true); + break; + } + newMovDto.setFavorite(false); + } + + for (ViewedEntity viewedEntity : viewes) { + if ((int) viewedEntity.getMovie().getId() == (int) movieDTO.getId()) { + newMovDto.setViewed(true); + break; + } + newMovDto.setViewed(false); + } + + muDTO.add(newMovDto); + } + return muDTO; } - @GetMapping - public List getAll(@RequestParam(name = "userId", defaultValue = "0") Integer userId) { - return viewedService.getAll(userId).stream().map(this::toDto).toList(); + @GetMapping() + public String getAll(Model model, @RequestParam(name = PAGE_ATTRIBUTE, defaultValue = "0") int page) { + List muDTO = getListMovieUserDTOs(0, page); + model.addAttribute("movies", muDTO); + + model.addAttribute(PAGE_ATTRIBUTE, page); + return FAVORITE_VIEW; } - @GetMapping("/{id}") - public ViewedDto get(@PathVariable(name = "id") Integer id) { - return toDto(viewedService.get(id)); + @PostMapping("/changeFavStatus/{id}") + public String changeFavStatus(@PathVariable(name = "id") Integer movieId, + @RequestParam(name = PAGE_ATTRIBUTE, defaultValue = "0") int page, + Model model) { + + boolean isFavorite = false; + + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + Integer currentUserId = userService.getCurrentUserId(authentication.getName()); + + FavoriteEntity fav = favoriteService.findByUserIdAndMovieId(currentUserId, movieId); + + if (fav == null) { + isFavorite = false; + } else { + isFavorite = true; + } + + if (isFavorite == false) { + + UserEntity user = userService.get(currentUserId); + MovieEntity movie = movieService.get(movieId); + + FavoriteEntity newFav = new FavoriteEntity(null, user, movie); + + favoriteService.create(newFav); + + } else { + + FavoriteEntity delFav = favoriteService.findByUserIdAndMovieId(currentUserId, movieId); + + favoriteService.delete(delFav.getId()); + } + + model.addAttribute(PAGE_ATTRIBUTE, page); + + return Constants.REDIRECT_VIEW + URL; } - @PostMapping - public ViewedDto create(@RequestBody @Valid ViewedDto dto) { - return toDto(viewedService.create(toEntity(dto))); - } + @PostMapping("/changeViewStatus/{id}") + public String changeViewStatus(@PathVariable(name = "id") Integer movieId, + @RequestParam(name = PAGE_ATTRIBUTE, defaultValue = "0") int page, + Model model) { - @PutMapping("/{id}") - public ViewedDto update(@PathVariable(name = "id") Integer id, - @RequestBody ViewedDto dto) { - return toDto(viewedService.update(id, toEntity(dto))); - } + boolean isViewed = false; - @DeleteMapping("/{id}") - public ViewedDto delete(@PathVariable(name = "id") Integer id) { - return toDto(viewedService.delete(id)); + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + Integer currentUserId = userService.getCurrentUserId(authentication.getName()); + + ViewedEntity view = viewedServise.findByUserIdAndMovieId(currentUserId, movieId); + + if (view == null) { + isViewed = false; + } else { + isViewed = true; + } + + if (isViewed == false) { + + UserEntity user = userService.get(currentUserId); + MovieEntity movie = movieService.get(movieId); + + ViewedEntity newView = new ViewedEntity(null, user, movie); + + viewedServise.create(newView); + + } else { + + ViewedEntity delView = viewedServise.findByUserIdAndMovieId(currentUserId, movieId); + + viewedServise.delete(delView.getId()); + } + + model.addAttribute(PAGE_ATTRIBUTE, page); + + return Constants.REDIRECT_VIEW + URL; } } diff --git a/backend/src/main/java/com/example/backend/viewed/repository/ViewedRepository.java b/backend/src/main/java/com/example/backend/viewed/repository/ViewedRepository.java index 604c54e..b87f070 100644 --- a/backend/src/main/java/com/example/backend/viewed/repository/ViewedRepository.java +++ b/backend/src/main/java/com/example/backend/viewed/repository/ViewedRepository.java @@ -4,10 +4,12 @@ import java.util.Optional; import java.util.List; import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.PagingAndSortingRepository; import com.example.backend.viewed.model.ViewedEntity; -public interface ViewedRepository extends CrudRepository { +public interface ViewedRepository + extends CrudRepository, PagingAndSortingRepository { List findByUserId(Integer userId); diff --git a/backend/src/main/resources/templates/favorites.html b/backend/src/main/resources/templates/favorites.html deleted file mode 100644 index b537184..0000000 --- a/backend/src/main/resources/templates/favorites.html +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - Просмотренные - - - -
- -
- - - \ No newline at end of file diff --git a/backend/src/main/resources/templates/favs.html b/backend/src/main/resources/templates/favs.html new file mode 100644 index 0000000..57ae58e --- /dev/null +++ b/backend/src/main/resources/templates/favs.html @@ -0,0 +1,68 @@ + + + + + Избранные + + + + +
+ +

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

+ +
+
+ +
+ + + +
+
+
+
+ +
+
+ + +
+
+ + +
+
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/backend/src/main/resources/templates/index.html b/backend/src/main/resources/templates/index.html index b2b29de..0879ebd 100644 --- a/backend/src/main/resources/templates/index.html +++ b/backend/src/main/resources/templates/index.html @@ -49,8 +49,8 @@ th:classappend="${activeLink.startsWith('/categories') ? 'active' : '' }">Категории Фильмы - Избранные + Избранные Просмотренные diff --git a/backend/src/main/resources/templates/movies.html b/backend/src/main/resources/templates/movies.html index a83ca76..106b7fc 100644 --- a/backend/src/main/resources/templates/movies.html +++ b/backend/src/main/resources/templates/movies.html @@ -107,7 +107,7 @@
-
-
+
+
+ + +
+
+ + + + + + + + \ No newline at end of file diff --git a/data.mv.db b/data.mv.db index 7c0c8da50561ecd2c2944bf5fc0b8119ae39c9b4..0e140d7aa2bc2b365d7a9ea5b3be605cf260fc8d 100644 GIT binary patch delta 38670 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 W0TDuZA}C70ACAW)Ev-?P%ksZ0`J^-e