From 6fa161262120213680bc23f11ada513e04a0a564 Mon Sep 17 00:00:00 2001 From: maliwahn Date: Sat, 30 Sep 2023 03:55:28 +0300 Subject: [PATCH 01/11] 1 commit --- pom.xml | 6 + .../filmorate/controller/ErrorHandler.java | 32 +++++ .../filmorate/controller/FilmController.java | 55 +++++-- .../filmorate/controller/UserController.java | 51 +++++-- .../exception/InternalServiceException.java | 12 ++ .../exception/ObjectNotFoundException.java | 13 ++ .../exception/ValidationException.java | 10 +- .../filmorate/manager/FilmManager.java | 50 ------- .../filmorate/manager/UserManager.java | 51 ------- .../filmorate/model/ErrorResponse.java | 18 +++ .../practicum/filmorate/model/Film.java | 36 ++++- .../practicum/filmorate/model/User.java | 28 +++- .../filmorate/service/FilmService.java | 51 +++++++ .../filmorate/service/UserService.java | 64 +++++++++ .../filmorate/storage/FilmStorage.java | 19 +++ .../storage/InMemoryFilmStorage.java | 61 ++++++++ .../storage/InMemoryUserStorage.java | 62 ++++++++ .../filmorate/storage/UserStorage.java | 21 +++ .../ValidationManager.java | 2 +- .../managerTest/FilmControllerTest.java | 90 ++++++++++++ .../managerTest/FilmManagerTest.java | 53 ------- .../FilmorateApplicationTests.java | 17 +++ .../managerTest/UserControllerTest.java | 136 ++++++++++++++++++ .../managerTest/UserManagerTest.java | 45 ------ 24 files changed, 746 insertions(+), 237 deletions(-) create mode 100644 src/main/java/ru/yandex/practicum/filmorate/controller/ErrorHandler.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/exception/InternalServiceException.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/exception/ObjectNotFoundException.java delete mode 100644 src/main/java/ru/yandex/practicum/filmorate/manager/FilmManager.java delete mode 100644 src/main/java/ru/yandex/practicum/filmorate/manager/UserManager.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/model/ErrorResponse.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/service/FilmService.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/service/UserService.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/storage/FilmStorage.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryFilmStorage.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryUserStorage.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/storage/UserStorage.java rename src/main/java/ru/yandex/practicum/filmorate/{manager => storage}/ValidationManager.java (97%) create mode 100644 src/test/java/ru/yandex/practicum/filmorate/managerTest/FilmControllerTest.java delete mode 100644 src/test/java/ru/yandex/practicum/filmorate/managerTest/FilmManagerTest.java create mode 100644 src/test/java/ru/yandex/practicum/filmorate/managerTest/FilmorateApplicationTests.java create mode 100644 src/test/java/ru/yandex/practicum/filmorate/managerTest/UserControllerTest.java delete mode 100644 src/test/java/ru/yandex/practicum/filmorate/managerTest/UserManagerTest.java diff --git a/pom.xml b/pom.xml index b576ebc..915c484 100644 --- a/pom.xml +++ b/pom.xml @@ -22,6 +22,12 @@ spring-boot-starter-web + + jakarta.validation + jakarta.validation-api + 3.0.2 + + org.springframework.boot spring-boot-starter-test diff --git a/src/main/java/ru/yandex/practicum/filmorate/controller/ErrorHandler.java b/src/main/java/ru/yandex/practicum/filmorate/controller/ErrorHandler.java new file mode 100644 index 0000000..ffb9996 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/ErrorHandler.java @@ -0,0 +1,32 @@ +package ru.yandex.practicum.filmorate.controller; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; +import ru.yandex.practicum.filmorate.exception.ObjectNotFoundException; +import ru.yandex.practicum.filmorate.exception.ValidationException; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import ru.yandex.practicum.filmorate.exception.InternalServiceException; +import ru.yandex.practicum.filmorate.model.ErrorResponse; +import org.springframework.web.bind.annotation.ExceptionHandler; + +@RestControllerAdvice +public class ErrorHandler { // класс с обработчиками исключений + + @ExceptionHandler + @ResponseStatus(HttpStatus.NOT_FOUND) + public ErrorResponse handleNotFoundException(final ObjectNotFoundException exception) { + return new ErrorResponse(exception.getMessage()); + } + + @ExceptionHandler + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public ErrorResponse handleInternalServiceException(final InternalServiceException exception) { + return new ErrorResponse(exception.getMessage()); + } + + @ExceptionHandler + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ErrorResponse handleValidationException(final ValidationException exception) { + return new ErrorResponse(exception.getMessage()); + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java b/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java index 294b2fc..4342c9a 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java @@ -1,31 +1,62 @@ package ru.yandex.practicum.filmorate.controller; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; -import ru.yandex.practicum.filmorate.manager.FilmManager; import ru.yandex.practicum.filmorate.model.Film; - +import ru.yandex.practicum.filmorate.service.FilmService; import java.util.List; @RestController -@RequestMapping ("/films") +@RequestMapping("/films") +@RequiredArgsConstructor public class FilmController { - FilmManager filmManager = new FilmManager(); + @Autowired + private final FilmService filmService; + + // Метод для создания нового фильма + @PostMapping + public Film createFilm(@Valid @RequestBody Film film) { + return filmService.createFilm(film); + } + // Метод для получения списка всех фильмов @GetMapping - private List getFilm() { - return filmManager.getFilmsList(); + public List getFilms() { + return filmService.getFilms(); } + // Метод для обновления информации о фильме @PutMapping - private Film update(@RequestBody Film film) { - return filmManager.updateFilm(film); + public Film updateFilm(@Valid @RequestBody Film film) { + return filmService.updateFilm(film); } - @PostMapping - private Film add(@RequestBody Film film) { - return filmManager.addFilm(film); + // Метод для получения информации о фильме по его идентификатору + @GetMapping("/{id}") + public Film getFilmById(@PathVariable Long id) { + return filmService.getFilmById(id); + } + + // Метод для получения списка популярных фильмов + @GetMapping("/popular") + public List getPopularMovies(@RequestParam(defaultValue = "10") Integer count) { + return filmService.getPopularFilms(count); + } + + // Метод для добавления лайка к фильму от пользователя + @PutMapping("/{id}/like/{userId}") + public void addLike(@PathVariable Long id, @PathVariable Long userId) { + filmService.addLike(id, userId); + } + + // Метод для удаления лайка от пользователя к фильму + @DeleteMapping("/{id}/like/{userId}") + public void removeLike(@PathVariable Long id, @PathVariable Long userId) { + filmService.deleteLike(id, userId); } -} +} \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java b/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java index 57f1346..682efa1 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java @@ -1,30 +1,59 @@ package ru.yandex.practicum.filmorate.controller; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; -import ru.yandex.practicum.filmorate.manager.UserManager; import ru.yandex.practicum.filmorate.model.User; - +import ru.yandex.practicum.filmorate.service.UserService; import java.util.List; @RestController @RequestMapping("/users") +@RequiredArgsConstructor public class UserController { - UserManager userManager = new UserManager(); + @Autowired + private final UserService userService; + + @PostMapping + public User createUser(@Valid @RequestBody User user) { + return userService.createUser(user); + } @GetMapping - private List getUser() { - return userManager.getUserList(); + public List getUsers() { + return userService.getUsers(); } @PutMapping - private User update(@RequestBody User user) { - return userManager.updateUser(user); + public User updateUser(@Valid @RequestBody User user) { + return userService.updateUser(user); } - @PostMapping - private User add(@RequestBody User user) { - return userManager.addUser(user); + @GetMapping("/{id}") + public User getUserById(@PathVariable Long id) { + return userService.getUserById(id); } -} + @PutMapping("/{id}/friends/{friendId}") + public void addFriend(@PathVariable Long id, @PathVariable Long friendId) { + userService.addFriend(id, friendId); + } + + @DeleteMapping("/{id}/friends/{friendId}") + public void removeFriend(@PathVariable Long id, @PathVariable Long friendId) { + userService.deleteFriend(id, friendId); + } + + @GetMapping("/{id}/friends") + public List getFriends(@PathVariable Long id) { + return userService.getFriends(id); + } + + @GetMapping("/{id}/friends/common/{otherId}") + public List getCommonFriends(@PathVariable Long id, @PathVariable Long otherId) { + return userService.getCommonFriends(id, otherId); + } +} \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/exception/InternalServiceException.java b/src/main/java/ru/yandex/practicum/filmorate/exception/InternalServiceException.java new file mode 100644 index 0000000..e98fff3 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/exception/InternalServiceException.java @@ -0,0 +1,12 @@ +package ru.yandex.practicum.filmorate.exception; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class InternalServiceException extends RuntimeException { // Класс представляет собой + // пользовательское исключение, которое может возникать в случае проблем с внутренней службой или ошибок выполнения операций. + + public InternalServiceException(final String message) { + super(message); + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/exception/ObjectNotFoundException.java b/src/main/java/ru/yandex/practicum/filmorate/exception/ObjectNotFoundException.java new file mode 100644 index 0000000..d3d5fbe --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/exception/ObjectNotFoundException.java @@ -0,0 +1,13 @@ +package ru.yandex.practicum.filmorate.exception; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class ObjectNotFoundException extends IllegalArgumentException { + // Класс используется для обозначения ситуации, когда объект не найден + + public ObjectNotFoundException(final String message) { + super(message); + log.error(message); + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/exception/ValidationException.java b/src/main/java/ru/yandex/practicum/filmorate/exception/ValidationException.java index 52dc49c..0996822 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/exception/ValidationException.java +++ b/src/main/java/ru/yandex/practicum/filmorate/exception/ValidationException.java @@ -1,7 +1,13 @@ package ru.yandex.practicum.filmorate.exception; -public class ValidationException extends RuntimeException { - public ValidationException(String message) { +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class ValidationException extends IllegalArgumentException { + // Класс используется для представления ошибок валидации данных + + public ValidationException(final String message) { super(message); + log.error(message); } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/manager/FilmManager.java b/src/main/java/ru/yandex/practicum/filmorate/manager/FilmManager.java deleted file mode 100644 index 8875ba2..0000000 --- a/src/main/java/ru/yandex/practicum/filmorate/manager/FilmManager.java +++ /dev/null @@ -1,50 +0,0 @@ -package ru.yandex.practicum.filmorate.manager; - -import lombok.Data; -import lombok.extern.slf4j.Slf4j; - - -import ru.yandex.practicum.filmorate.exception.ValidationException; -import ru.yandex.practicum.filmorate.model.Film; - - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; - - -@Slf4j -@Data -public class FilmManager { - private HashMap allFilms = new HashMap<>(); - private int filmId; - - - public Film addFilm(Film film) { - ValidationManager.allFilmExceptions(film.getName(), film.getDescription(), film.getReleaseDate(), film.getDuration()); - createId(film); - allFilms.put(filmId, film); - log.info("Фильм создан", film.getName(), film.getId()); - return film; - } - - public void createId(Film film) { - film.setId(++filmId); - } - - public Film updateFilm(Film film) throws ValidationException { - if (allFilms.containsKey(film.getId())) { - ValidationManager.allFilmExceptions(film.getName(), film.getDescription(), film.getReleaseDate(), film.getDuration()); - allFilms.put(film.getId(), film); - return film; - } else { - throw new ValidationException("Фильма с таким айди нет"); - } - } - - public List getFilmsList() { - List list = new ArrayList<>(allFilms.values()); - return list; - } - -} diff --git a/src/main/java/ru/yandex/practicum/filmorate/manager/UserManager.java b/src/main/java/ru/yandex/practicum/filmorate/manager/UserManager.java deleted file mode 100644 index f5ee8fb..0000000 --- a/src/main/java/ru/yandex/practicum/filmorate/manager/UserManager.java +++ /dev/null @@ -1,51 +0,0 @@ -package ru.yandex.practicum.filmorate.manager; - -import lombok.Data; -import lombok.extern.slf4j.Slf4j; - - -import ru.yandex.practicum.filmorate.exception.ValidationException; -import ru.yandex.practicum.filmorate.model.User; - - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; - -@Slf4j -@Data -public class UserManager { - private HashMap allUsers = new HashMap<>(); - private int userId; - - - public User addUser(User user) { - if (user.getName() == null || user.getName().isEmpty()) { - user.setName(user.getLogin()); - } - ValidationManager.allUserExceptions(user.getEmail(), user.getLogin(), user.getName(), user); - createId(user); - allUsers.put(userId, user); - log.info("Пользователь создан", user.getName(), user.getId()); - return user; - } - - public void createId(User user) { - user.setId(++userId); - } - - public User updateUser(User user) throws ValidationException { - if (allUsers.containsKey(user.getId())) { - ValidationManager.allUserExceptions(user.getEmail(), user.getLogin(), user.getName(), user); - allUsers.put(user.getId(), user); - return user; - } else { - throw new ValidationException("Пользователя с таким айди нет"); - } - } - - public List getUserList() { - List list = new ArrayList<>(allUsers.values()); - return list; - } -} diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/ErrorResponse.java b/src/main/java/ru/yandex/practicum/filmorate/model/ErrorResponse.java new file mode 100644 index 0000000..8d454fd --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/model/ErrorResponse.java @@ -0,0 +1,18 @@ +package ru.yandex.practicum.filmorate.model; + +public class ErrorResponse { + + private final String error; + + // Конструктор, принимающий сообщение об ошибке + public ErrorResponse(String error) { + this.error = error; + } + + // Геттер для получения сообщения об ошибке + public String getError() { + return error; + } +} + + diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/Film.java b/src/main/java/ru/yandex/practicum/filmorate/model/Film.java index 10c2bd7..f6d426f 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/model/Film.java +++ b/src/main/java/ru/yandex/practicum/filmorate/model/Film.java @@ -1,19 +1,41 @@ package ru.yandex.practicum.filmorate.model; -import lombok.AllArgsConstructor; -import lombok.Data; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.PositiveOrZero; +import jakarta.validation.constraints.Size; +import lombok.*; import java.time.LocalDate; +import java.util.Set; @Data @AllArgsConstructor public class Film { - private int id; - private String name; - private String description; - private LocalDate releaseDate; - private double duration; + //Класс представляет объект фильма и содержит различные свойства и методы для работы с ними + @PositiveOrZero + private Long id; // Идентификатор фильма + @NotNull + private String name; // Название фильма + @Size(min = 1, max = 200) + private String description; // Описание фильма + private LocalDate releaseDate; // Дата выхода фильма + @Positive + private long duration; // Продолжительность фильма в минутах + private Set likes; // Множество идентификаторов пользователей, поставивших лайки фильму + public void addLike(Long userId) { // Метод для добавления лайка от пользователя к фильму + likes.add(userId); + } + + public void removeLike(Long userId) { // Метод для удаления лайка от пользователя к фильму + likes.remove(userId); + } + + public int getLikesQuantity() { // Метод для получения количества лайков у фильма + return likes.size(); + } } + diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/User.java b/src/main/java/ru/yandex/practicum/filmorate/model/User.java index c73ccdc..53d57e5 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/model/User.java +++ b/src/main/java/ru/yandex/practicum/filmorate/model/User.java @@ -1,17 +1,35 @@ package ru.yandex.practicum.filmorate.model; -import lombok.AllArgsConstructor; -import lombok.Data; +import jakarta.validation.constraints.*; +import lombok.*; import java.time.LocalDate; +import java.util.Set; @Data @AllArgsConstructor -public class User { - private int id; +public class User { // Класс аналогичен классу Film только с информацией о пользователе + @PositiveOrZero + private Long id; + @Email private String email; + @NotNull private String login; private String name; + @PastOrPresent private LocalDate birthday; -} + private Set friends; + + public void addFriend(Long id) { + friends.add(id); + } + + public void removeFriend(Long id) { + friends.remove(id); + } + + public int getFriendsQuantity() { + return friends.size(); + } +} \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/FilmService.java b/src/main/java/ru/yandex/practicum/filmorate/service/FilmService.java new file mode 100644 index 0000000..159fe1f --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/service/FilmService.java @@ -0,0 +1,51 @@ +package ru.yandex.practicum.filmorate.service; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import ru.yandex.practicum.filmorate.exception.ObjectNotFoundException; +import ru.yandex.practicum.filmorate.model.Film; +import ru.yandex.practicum.filmorate.storage.InMemoryFilmStorage; + +import java.util.Comparator; +import java.util.stream.Collectors; +import java.util.List; + +@Slf4j +@Service +@RequiredArgsConstructor +public class FilmService extends InMemoryFilmStorage { + //Этот класс представляет сервис для работы с данными фильмов. + + private final UserService userService; + + // Метод для добавления лайка к фильму от пользователя + public void addLike(Long filmId, Long userId) { + Film film = getFilmById(filmId); + userService.getUserById(userId); + if (film == null) { + throw new ObjectNotFoundException("Фильма с таким id не существует" + filmId); + } + film.addLike(userId); + log.info("поставлен лайк", userId, filmId); + } + + // Метод для удаления лайка от пользователя к фильму + public void deleteLike(Long filmId, Long userId) { + Film film = getFilmById(filmId); + userService.getUserById(userId); + if (film == null) { + throw new ObjectNotFoundException("Фильма с таким id не существует" + filmId); + } + film.removeLike(userId); + log.info("лайк удалён", userId, filmId); + } + + // Метод для получения списка популярных фильмов + public List getPopularFilms(int count) { + return getFilms().stream() + .sorted(Comparator.comparingInt(Film::getLikesQuantity).reversed()) + .limit(count).collect(Collectors.toList()); + } +} + diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/UserService.java b/src/main/java/ru/yandex/practicum/filmorate/service/UserService.java new file mode 100644 index 0000000..d06f9b2 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/service/UserService.java @@ -0,0 +1,64 @@ +package ru.yandex.practicum.filmorate.service; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import ru.yandex.practicum.filmorate.exception.ObjectNotFoundException; +import ru.yandex.practicum.filmorate.model.User; +import ru.yandex.practicum.filmorate.storage.InMemoryUserStorage; + +import java.util.*; +import java.util.stream.Collectors; + +@Slf4j +@Service +@RequiredArgsConstructor +public class UserService extends InMemoryUserStorage { + //Этот класс представляет сервис для работы с данными о пользователе + + public void addFriend(Long userId, Long friendId) { // метод добавления друга + User user = getUserById(userId); + User friend = getUserById(friendId); + user.addFriend(friendId); + friend.addFriend(userId); + } + + public void deleteFriend(Long userId, Long friendId) { // метод удаления друга + User user = getUserById(userId); + User friend = getUserById(friendId); + user.removeFriend(friendId); + friend.removeFriend(userId); + } + + public List getFriends(Long userId) { // метод получения списка друзей + User user = getUserById(userId); + Set friends = user.getFriends(); + if (friends.isEmpty()) { + throw new ObjectNotFoundException("Список друзей пользователя " + userId + " пуст"); + } + return friends.stream() + .map(this::getUserById) + .collect(Collectors.toList()); + } + + public List getCommonFriends(Long userId, Long friendId) { // метод предназначен для получения списка общих друзей между двумя пользователями + User user = getUserById(userId); + User friend = getUserById(friendId); + Set userFriends = user.getFriends(); + Set friendFriends = friend.getFriends(); + Set commonFriends = new HashSet<>(); + + for (Long friendUserId : userFriends) { + if (friendFriends.contains(friendUserId)) { + commonFriends.add(friendUserId); + } + } + + List commonFriendUsers = new ArrayList<>(); + for (Long commonFriendId : commonFriends) { + commonFriendUsers.add(getUserById(commonFriendId)); + } + + return commonFriendUsers; + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/FilmStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/FilmStorage.java new file mode 100644 index 0000000..963f11e --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/FilmStorage.java @@ -0,0 +1,19 @@ +package ru.yandex.practicum.filmorate.storage; + +import ru.yandex.practicum.filmorate.model.Film; + +import java.util.List; + +public interface FilmStorage { + + Film createFilm(Film film); + + void deleteFilms(); + + Film getFilmById(Long id); + + Film updateFilm(Film film); + + List getFilms(); + +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryFilmStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryFilmStorage.java new file mode 100644 index 0000000..776d9ee --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryFilmStorage.java @@ -0,0 +1,61 @@ +package ru.yandex.practicum.filmorate.storage; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import ru.yandex.practicum.filmorate.exception.ObjectNotFoundException; + +import ru.yandex.practicum.filmorate.model.Film; + +import java.util.*; + +@Slf4j +@Component +public class InMemoryFilmStorage implements FilmStorage { + private final Map films; + private Long id; + + public InMemoryFilmStorage() { + films = new HashMap<>(); + id = 0L; + } + + @Override + public Film createFilm(Film film) { + ValidationManager.allFilmExceptions(film.getName(), film.getDescription(), film.getReleaseDate(), film.getDuration()); + films.put(film.getId(), film); + log.info("фильм создан", film.getName(), film.getId()); + return film; + } + + @Override + public void deleteFilms() { + films.clear(); + } + + @Override + public Film updateFilm(Film film) { + if (films.containsKey(film.getId())) { + ValidationManager.allFilmExceptions(film.getName(), film.getDescription(), film.getReleaseDate(), film.getDuration()); + films.put(film.getId(), film); + log.info("фильм обновлён", film.getName(), film.getId()); + return film; + } else { + throw new ObjectNotFoundException("Такого фильма не существует"); + } + } + + @Override + public Film getFilmById(Long id) { + if (!films.containsKey(id)) { + throw new ObjectNotFoundException("Фильма с таким id не существует" + id + "'"); + } + return films.get(id); + } + + @Override + public List getFilms() { + return new ArrayList<>(films.values()); + } + + +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryUserStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryUserStorage.java new file mode 100644 index 0000000..e829621 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryUserStorage.java @@ -0,0 +1,62 @@ +package ru.yandex.practicum.filmorate.storage; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import ru.yandex.practicum.filmorate.exception.ObjectNotFoundException; +import ru.yandex.practicum.filmorate.exception.ValidationException; +import ru.yandex.practicum.filmorate.model.User; + +import java.time.LocalDate; +import java.util.*; + +@Slf4j +@Component +public class InMemoryUserStorage implements UserStorage { + private final Map users; + private Long id; + + public InMemoryUserStorage() { + users = new HashMap<>(); + id = 0L; + } + + @Override + public User createUser(User user) { + ValidationManager.allUserExceptions(user.getEmail(), user.getLogin(), user.getName(), user); + users.put(user.getId(), user); + log.info("Пользователь создан", user.getEmail(), user.getId()); + return user; + } + + @Override + public User getUserById(Long id) { + if (!users.containsKey(id)) { + throw new ObjectNotFoundException("Пользователя с таким id не существует " + id + "'"); + } + return users.get(id); + } + + @Override + public User updateUser(User user) { + if (users.containsKey(user.getId())) { + ValidationManager.allUserExceptions(user.getEmail(), user.getLogin(), user.getName(), user); + users.put(user.getId(), user); + log.info("Пользователь обновлён", user.getLogin(), user.getId()); + return user; + } else { + throw new ObjectNotFoundException("Пользователя не существует"); + } + } + + @Override + public void deleteUsers() { + users.clear(); + } + + @Override + public List getUsers() { + return new ArrayList<>(users.values()); + } + + + } diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/UserStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/UserStorage.java new file mode 100644 index 0000000..8738e39 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/UserStorage.java @@ -0,0 +1,21 @@ +package ru.yandex.practicum.filmorate.storage; + +import ru.yandex.practicum.filmorate.model.User; + +import ru.yandex.practicum.filmorate.model.User; + +import java.util.List; + +public interface UserStorage { + + User createUser(User user); + + User updateUser(User user); + + void deleteUsers(); + + User getUserById(Long id); + + List getUsers(); + +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/manager/ValidationManager.java b/src/main/java/ru/yandex/practicum/filmorate/storage/ValidationManager.java similarity index 97% rename from src/main/java/ru/yandex/practicum/filmorate/manager/ValidationManager.java rename to src/main/java/ru/yandex/practicum/filmorate/storage/ValidationManager.java index 0bae36e..b7efaf5 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/manager/ValidationManager.java +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/ValidationManager.java @@ -1,4 +1,4 @@ -package ru.yandex.practicum.filmorate.manager; +package ru.yandex.practicum.filmorate.storage; import ru.yandex.practicum.filmorate.exception.ValidationException; import ru.yandex.practicum.filmorate.model.User; diff --git a/src/test/java/ru/yandex/practicum/filmorate/managerTest/FilmControllerTest.java b/src/test/java/ru/yandex/practicum/filmorate/managerTest/FilmControllerTest.java new file mode 100644 index 0000000..fd7ef9e --- /dev/null +++ b/src/test/java/ru/yandex/practicum/filmorate/managerTest/FilmControllerTest.java @@ -0,0 +1,90 @@ +package ru.yandex.practicum.filmorate.managerTest; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import ru.yandex.practicum.filmorate.controller.FilmController; +import ru.yandex.practicum.filmorate.exception.ValidationException; +import ru.yandex.practicum.filmorate.model.Film; +import ru.yandex.practicum.filmorate.model.User; +import ru.yandex.practicum.filmorate.service.FilmService; +import ru.yandex.practicum.filmorate.service.UserService; +import ru.yandex.practicum.filmorate.storage.FilmStorage; +import ru.yandex.practicum.filmorate.storage.InMemoryFilmStorage; +import ru.yandex.practicum.filmorate.storage.InMemoryUserStorage; +import ru.yandex.practicum.filmorate.storage.UserStorage; + +import java.time.LocalDate; +import java.util.HashSet; + +public class FilmControllerTest { + private FilmStorage storage = new InMemoryFilmStorage(); + private UserStorage userStorage = new InMemoryUserStorage(); + private UserService userService = new UserService(); + private FilmService service = new FilmService(userService); + private FilmController controller = new FilmController(service); + private final Film negativeDurationFilm = new Film(1L, "Movie", + "Описание фильма", + LocalDate.of(1995, 2, 2), -15, new HashSet<>()); + private final User user = new User(2L, "vanya@ya.ru", "vanya", "Vanya", + LocalDate.of(1999, 3, 5), new HashSet<>()); + private final Film film = new Film(1L, "Film", "Описание фильма", + LocalDate.of(1995, 2, 2), 120, new HashSet<>()); + private final Film updatedFilm = new Film(1L, "Film", + "Описание этого фильма", + LocalDate.of(1995, 2, 2), 120, new HashSet<>()); + private final Film noNamedFilm = new Film(1L, "", "Описание фильма", + LocalDate.of(1995, 2, 2), 120, new HashSet<>()); + private final Film longDescpriptionFilm = new Film(1L, "Film", + "Жесть какая-то, а не фильм", + LocalDate.of(1995, 2, 2), 120, new HashSet<>()); + + @AfterEach + public void afterEach() { + storage.deleteFilms(); + } + + @Test + void createFilmTest() { + controller.createFilm(film); + + Assertions.assertEquals(1, controller.getFilms().size()); + } + + @Test + void updateFilmTest() { + controller.createFilm(film); + controller.updateFilm(updatedFilm); + + Assertions.assertEquals("Описание этого фильма", updatedFilm.getDescription()); + Assertions.assertEquals(1, controller.getFilms().size()); + } + + @Test + void createFilmEmptyNameTest() { + Assertions.assertThrows(ValidationException.class, () -> controller.createFilm(noNamedFilm)); + Assertions.assertEquals(0, controller.getFilms().size()); + } + + @Test + void getFilmByIdTest() { + controller.createFilm(film); + Film thisFilm = controller.getFilmById(film.getId()); + + Assertions.assertEquals(1, thisFilm.getId()); + } + + @Test + void createFilmDuration0Test() { + Assertions.assertThrows(ValidationException.class, () -> controller.createFilm(negativeDurationFilm)); + Assertions.assertEquals(0, controller.getFilms().size()); + } + + @Test + void createFilm1895Test() { + film.setReleaseDate(LocalDate.of(1895, 2, 2)); + + Assertions.assertThrows(ValidationException.class, () -> controller.createFilm(film)); + Assertions.assertEquals(0, controller.getFilms().size()); + } +} diff --git a/src/test/java/ru/yandex/practicum/filmorate/managerTest/FilmManagerTest.java b/src/test/java/ru/yandex/practicum/filmorate/managerTest/FilmManagerTest.java deleted file mode 100644 index b6e8fd6..0000000 --- a/src/test/java/ru/yandex/practicum/filmorate/managerTest/FilmManagerTest.java +++ /dev/null @@ -1,53 +0,0 @@ -package ru.yandex.practicum.filmorate.managerTest; - -import org.junit.Test; -import org.junit.jupiter.api.Assertions; - - -import ru.yandex.practicum.filmorate.manager.FilmManager; -import ru.yandex.practicum.filmorate.model.Film; - -import java.time.LocalDate; -import java.util.ArrayList; -import java.util.List; - - -public class FilmManagerTest { - FilmManager manager = new FilmManager(); - private final Film film = new Film(1, "Film", "Описание фильма", - LocalDate.of(1995, 2, 2), 120); - private final Film updatedFilm = new Film(2, "Drill", - "Drill этого фильма", - LocalDate.of(2000, 3, 1), 180); - - @Test - public void addFilmTest() { - manager.addFilm(film); - - Assertions.assertEquals(1, manager.getAllFilms().size()); - } - - @Test - public void updateFilmTest() { - manager.addFilm(film); - manager.updateFilm(updatedFilm); - - Assertions.assertEquals("Drill этого фильма", updatedFilm.getDescription()); - Assertions.assertEquals(2, manager.getAllFilms().size()); - } - - @Test - public void testGetFilmsList() { - manager.addFilm(film); - manager.addFilm(updatedFilm); - List expectedList = new ArrayList<>(manager.getAllFilms().values()); - List actualList = new ArrayList<>(); - actualList.add(film); - actualList.add(updatedFilm); - - Assertions.assertEquals(expectedList, actualList); - } -} - - - diff --git a/src/test/java/ru/yandex/practicum/filmorate/managerTest/FilmorateApplicationTests.java b/src/test/java/ru/yandex/practicum/filmorate/managerTest/FilmorateApplicationTests.java new file mode 100644 index 0000000..51ff2e1 --- /dev/null +++ b/src/test/java/ru/yandex/practicum/filmorate/managerTest/FilmorateApplicationTests.java @@ -0,0 +1,17 @@ +package ru.yandex.practicum.filmorate.managerTest; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class FilmorateApplicationTests { + + @Test + void testTest() { + } + +} + + + + diff --git a/src/test/java/ru/yandex/practicum/filmorate/managerTest/UserControllerTest.java b/src/test/java/ru/yandex/practicum/filmorate/managerTest/UserControllerTest.java new file mode 100644 index 0000000..c1250bc --- /dev/null +++ b/src/test/java/ru/yandex/practicum/filmorate/managerTest/UserControllerTest.java @@ -0,0 +1,136 @@ +package ru.yandex.practicum.filmorate.managerTest; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import ru.yandex.practicum.filmorate.controller.UserController; +import ru.yandex.practicum.filmorate.exception.ValidationException; +import ru.yandex.practicum.filmorate.model.User; +import ru.yandex.practicum.filmorate.service.UserService; +import ru.yandex.practicum.filmorate.storage.InMemoryUserStorage; + +import java.time.LocalDate; +import java.util.HashSet; +import java.util.List; + +public class UserControllerTest { + private InMemoryUserStorage storage = new InMemoryUserStorage(); + private UserService service = new UserService(); + private UserController controller = new UserController(service); + private final User user = new User(1L, "vanya@yandex.ru", "login", "Vanya", + LocalDate.of(1995, 1, 1), new HashSet<>()); + private final User updatedUser = new User(1L, "oleg@yandex.ru", "login", "Vanya", + LocalDate.of(1995, 2, 1), new HashSet<>()); + private final User emptyNameUser = new User(6L, "nikita@yandex.ru", "login1", null, + LocalDate.of(1995, 1, 1), new HashSet<>()); + private final User incorrectEmailUser = new User(3L, "somebody once told me, the world is gonna roll me", + "loglog", "Dasha", LocalDate.of(1997, 8, 13), new HashSet<>()); + private final User emptyEmailUser = new User(1L, "", "puss in boots", null, + LocalDate.of(1990, 1, 1), new HashSet<>()); + private final User commonFriend = new User(19L, "friend@yandex.ru", "friend", "Alexander", + LocalDate.of(1888, 5, 5), new HashSet<>()); + + @AfterEach + public void afterEach() { + storage.deleteUsers(); + } + + @Test + void createUserTest() { + controller.createUser(user); + + Assertions.assertEquals(1, controller.getUsers().size()); + } + + @Test + void updateTest() { + controller.createUser(user); + controller.updateUser(updatedUser); + + Assertions.assertEquals("oleg@yandex.ru", updatedUser.getEmail()); + Assertions.assertEquals(user.getId(), updatedUser.getId()); + Assertions.assertEquals(1, controller.getUsers().size()); + } + + @Test + void createUserFutureTest() { + user.setBirthday(LocalDate.of(2024, 6, 28)); + + Assertions.assertThrows(ValidationException.class, () -> controller.createUser(user)); + Assertions.assertEquals(0, controller.getUsers().size()); + } + + @Test + void createUserNameIsEmptyTest() { + controller.createUser(emptyNameUser); + + Assertions.assertEquals(6, emptyNameUser.getId()); + Assertions.assertEquals("login1", emptyNameUser.getName()); + } + + @Test + void createUserEmailIsEmptyTest() { + Assertions.assertThrows(ValidationException.class, () -> controller.createUser(emptyEmailUser)); + Assertions.assertEquals(0, controller.getUsers().size()); + } + + @Test + void createUserLoginIsEmptyTest() { + user.setLogin(""); + + Assertions.assertThrows(ValidationException.class, () -> controller.createUser(user)); + Assertions.assertEquals(0, controller.getUsers().size()); + } + + @Test + void createUserEmailIncorrectTest() { + Assertions.assertThrows(ValidationException.class, () -> controller.createUser(incorrectEmailUser)); + Assertions.assertEquals(0, controller.getUsers().size()); + } + + @Test + void deleteFriendTest() { + controller.createUser(user); + controller.createUser(emptyNameUser); + controller.addFriend(user.getId(), emptyNameUser.getId()); + controller.removeFriend(user.getId(), emptyNameUser.getId()); + + Assertions.assertEquals(0, user.getFriendsQuantity()); + Assertions.assertEquals(0, emptyNameUser.getFriendsQuantity()); + } + + @Test + void getFriendsTest() { + controller.createUser(user); + controller.createUser(emptyNameUser); + controller.createUser(commonFriend); + controller.addFriend(user.getId(), emptyNameUser.getId()); + controller.addFriend(user.getId(), commonFriend.getId()); + List listOfUsersFriends = controller.getFriends(user.getId()); + + Assertions.assertEquals(2, listOfUsersFriends.size()); + } + + @Test + void addFriendTest() { + controller.createUser(user); + controller.createUser(emptyNameUser); + controller.addFriend(user.getId(), emptyNameUser.getId()); + + Assertions.assertTrue(user.getFriendsQuantity() != 0); + Assertions.assertTrue(emptyNameUser.getFriendsQuantity() != 0); + } + + @Test + void getCommonFriendsTest() { + controller.createUser(user); + controller.createUser(emptyNameUser); + controller.addFriend(user.getId(), emptyNameUser.getId()); + controller.createUser(commonFriend); + controller.addFriend(user.getId(), commonFriend.getId()); + controller.addFriend(emptyNameUser.getId(), commonFriend.getId()); + List commonFriendList = controller.getCommonFriends(user.getId(), emptyNameUser.getId()); + + Assertions.assertEquals(1, commonFriendList.size()); + } +} \ No newline at end of file diff --git a/src/test/java/ru/yandex/practicum/filmorate/managerTest/UserManagerTest.java b/src/test/java/ru/yandex/practicum/filmorate/managerTest/UserManagerTest.java deleted file mode 100644 index f98355a..0000000 --- a/src/test/java/ru/yandex/practicum/filmorate/managerTest/UserManagerTest.java +++ /dev/null @@ -1,45 +0,0 @@ -package ru.yandex.practicum.filmorate.managerTest; - -import org.junit.Test; -import org.junit.jupiter.api.Assertions; -import ru.yandex.practicum.filmorate.manager.UserManager; -import ru.yandex.practicum.filmorate.model.User; - -import java.time.LocalDate; -import java.util.ArrayList; -import java.util.List; - -public class UserManagerTest { - UserManager manager = new UserManager(); - private final User user = new User(1, "di@mail.ru", "balda", "aa", LocalDate.of(1995, 1, 1)); - private final User updatedUser = new User(1, "xi@mail.ru", "nefritoviy", "sterzen", LocalDate.of(2000, 1, 1)); - - @Test - public void addUserTest() { - manager.addUser(user); - - Assertions.assertEquals(1, manager.getAllUsers().size()); - } - - @Test - public void updateFilmTest() { - manager.addUser(user); - manager.updateUser(updatedUser); - - Assertions.assertEquals("xi@mail.ru", updatedUser.getEmail()); - Assertions.assertEquals(user.getId(), updatedUser.getId()); - Assertions.assertEquals(1, manager.getAllUsers().size()); - } - - @Test - public void testGetFilmsList() { - manager.addUser(user); - manager.addUser(updatedUser); - List expectedList = new ArrayList<>(manager.getAllUsers().values()); - List actualList = new ArrayList<>(); - actualList.add(user); - actualList.add(updatedUser); - - Assertions.assertEquals(expectedList, actualList); - } -} From 3aa6a0ae323339fbcdaf22ecaa3b82b7d15824cb Mon Sep 17 00:00:00 2001 From: maliwahn Date: Sat, 30 Sep 2023 03:57:47 +0300 Subject: [PATCH 02/11] 1 commit --- .../practicum/filmorate/storage/InMemoryUserStorage.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryUserStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryUserStorage.java index e829621..887bbff 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryUserStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryUserStorage.java @@ -3,10 +3,9 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import ru.yandex.practicum.filmorate.exception.ObjectNotFoundException; -import ru.yandex.practicum.filmorate.exception.ValidationException; import ru.yandex.practicum.filmorate.model.User; -import java.time.LocalDate; + import java.util.*; @Slf4j From 1b1917a20c76376bc15f06f150239308da6ba170 Mon Sep 17 00:00:00 2001 From: maliwahn Date: Sat, 30 Sep 2023 04:12:16 +0300 Subject: [PATCH 03/11] 1 commit --- .../storage/InMemoryUserStorage.java | 30 +++++++++++++++++-- .../filmorate/storage/ValidationManager.java | 18 ----------- 2 files changed, 27 insertions(+), 21 deletions(-) diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryUserStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryUserStorage.java index 887bbff..7c05e4a 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryUserStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryUserStorage.java @@ -3,9 +3,11 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import ru.yandex.practicum.filmorate.exception.ObjectNotFoundException; +import ru.yandex.practicum.filmorate.exception.ValidationException; import ru.yandex.practicum.filmorate.model.User; +import java.time.LocalDate; import java.util.*; @Slf4j @@ -21,7 +23,7 @@ public InMemoryUserStorage() { @Override public User createUser(User user) { - ValidationManager.allUserExceptions(user.getEmail(), user.getLogin(), user.getName(), user); + validate(user); users.put(user.getId(), user); log.info("Пользователь создан", user.getEmail(), user.getId()); return user; @@ -38,7 +40,7 @@ public User getUserById(Long id) { @Override public User updateUser(User user) { if (users.containsKey(user.getId())) { - ValidationManager.allUserExceptions(user.getEmail(), user.getLogin(), user.getName(), user); + validate(user); users.put(user.getId(), user); log.info("Пользователь обновлён", user.getLogin(), user.getId()); return user; @@ -57,5 +59,27 @@ public List getUsers() { return new ArrayList<>(users.values()); } - + private void validate(User user) { + if (user.getBirthday().isAfter(LocalDate.now()) || user.getBirthday() == null) { + throw new ValidationException("Некорректная дата рождения" + user.getId() + "'"); + } + if (user.getEmail() == null || user.getEmail().isBlank() || !user.getEmail().contains("@")) { + throw new ValidationException("Некорректный email" + user.getId() + "'"); + } + if (user.getName() == null || user.getName().isBlank()) { + user.setName(user.getLogin()); + log.info("Пользователь создан", user.getId(), user.getName()); + } + if (user.getLogin().isBlank() || user.getLogin().isEmpty()) { + throw new ValidationException("Некорректный логин" + user.getId() + "'"); + } + if (user.getFriends() == null) { + user.setFriends(new HashSet<>()); + } + if (user.getId() == null || user.getId() <= 0) { + user.setId(++id); + } } + + +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/ValidationManager.java b/src/main/java/ru/yandex/practicum/filmorate/storage/ValidationManager.java index b7efaf5..f4b0fa0 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/storage/ValidationManager.java +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/ValidationManager.java @@ -24,22 +24,4 @@ public static void allFilmExceptions(String name, String description, LocalDate throw new ValidationException("Неверная длительность фильма"); } } - - public static void allUserExceptions(String email, String login, String name, User user) throws ValidationException { - if (email == null || email.isBlank() || !email.contains("@")) { - throw new ValidationException("Email не должен быть пустым и должен содержать @"); - } - - if (login.isBlank() || login.isEmpty()) { - throw new ValidationException("Логин не может быть пустым и содержать пробелы"); - } - - if (name == null || name.isEmpty()) { - throw new ValidationException("Вместо имени будет использован логин"); - } - - if (user.getBirthday().isAfter(LocalDate.now()) || user.getBirthday() == null) { - throw new ValidationException("Некорректная дата рождения" + user.getId() + "'"); - } - } } From 5833dd009cdb92ad89b17a865fae8aeb3fc2d86f Mon Sep 17 00:00:00 2001 From: maliwahn Date: Sat, 30 Sep 2023 04:13:31 +0300 Subject: [PATCH 04/11] 1 commit --- .../yandex/practicum/filmorate/storage/ValidationManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/ValidationManager.java b/src/main/java/ru/yandex/practicum/filmorate/storage/ValidationManager.java index f4b0fa0..f4e5972 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/storage/ValidationManager.java +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/ValidationManager.java @@ -1,7 +1,7 @@ package ru.yandex.practicum.filmorate.storage; import ru.yandex.practicum.filmorate.exception.ValidationException; -import ru.yandex.practicum.filmorate.model.User; + import java.time.LocalDate; From 1bdfc6af9e5ab5c3ec519956cc1a09ff5833263a Mon Sep 17 00:00:00 2001 From: maliwahn Date: Sat, 30 Sep 2023 04:18:33 +0300 Subject: [PATCH 05/11] 1 commit --- .../storage/InMemoryFilmStorage.java | 27 +++++++++++++++++-- .../filmorate/storage/ValidationManager.java | 27 ------------------- 2 files changed, 25 insertions(+), 29 deletions(-) delete mode 100644 src/main/java/ru/yandex/practicum/filmorate/storage/ValidationManager.java diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryFilmStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryFilmStorage.java index 776d9ee..4449e2b 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryFilmStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryFilmStorage.java @@ -4,8 +4,10 @@ import org.springframework.stereotype.Component; import ru.yandex.practicum.filmorate.exception.ObjectNotFoundException; +import ru.yandex.practicum.filmorate.exception.ValidationException; import ru.yandex.practicum.filmorate.model.Film; +import java.time.LocalDate; import java.util.*; @Slf4j @@ -21,7 +23,7 @@ public InMemoryFilmStorage() { @Override public Film createFilm(Film film) { - ValidationManager.allFilmExceptions(film.getName(), film.getDescription(), film.getReleaseDate(), film.getDuration()); + validate(film); films.put(film.getId(), film); log.info("фильм создан", film.getName(), film.getId()); return film; @@ -35,7 +37,7 @@ public void deleteFilms() { @Override public Film updateFilm(Film film) { if (films.containsKey(film.getId())) { - ValidationManager.allFilmExceptions(film.getName(), film.getDescription(), film.getReleaseDate(), film.getDuration()); + validate(film); films.put(film.getId(), film); log.info("фильм обновлён", film.getName(), film.getId()); return film; @@ -57,5 +59,26 @@ public List getFilms() { return new ArrayList<>(films.values()); } + private void validate(Film film) { + if (film.getReleaseDate() == null || + film.getReleaseDate().isBefore(LocalDate.of(1895, 12, 28))) { + throw new ValidationException("Некорректная дата"); + } + if (film.getName().isEmpty() || film.getName().isBlank()) { + throw new ValidationException("Отсутствует название фильма"); + } + if (film.getDuration() <= 0) { + throw new ValidationException("Длительность фильма не может быть меньше нуля"); + } + if (film.getDescription().length() > 200 || film.getDescription().length() == 0) { + throw new ValidationException("Максимальное количество символов 200"); + } + if (film.getId() == null || film.getId() <= 0) { + film.setId(++id); + } + if (film.getLikes() == null) { + film.setLikes(new HashSet<>()); + } + } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/ValidationManager.java b/src/main/java/ru/yandex/practicum/filmorate/storage/ValidationManager.java deleted file mode 100644 index f4e5972..0000000 --- a/src/main/java/ru/yandex/practicum/filmorate/storage/ValidationManager.java +++ /dev/null @@ -1,27 +0,0 @@ -package ru.yandex.practicum.filmorate.storage; - -import ru.yandex.practicum.filmorate.exception.ValidationException; - - -import java.time.LocalDate; - -public class ValidationManager { - - public static void allFilmExceptions(String name, String description, LocalDate date, double time) throws ValidationException { - if (name == null || name.isBlank()) { - throw new ValidationException("Поле name не должно быть пустым"); - } - - if (description.length() > 200 || description.length() == 0) { - throw new ValidationException("Максимальная длина должна быть не больше 200 символов"); - } - - if (date == null || date.isBefore(LocalDate.of(1895, 12, 28))) { - throw new ValidationException("Некорректная дата"); - } - - if (time <= 0) { - throw new ValidationException("Неверная длительность фильма"); - } - } -} From c05044f76b22dddd528e303f38ffa82a6294ba9c Mon Sep 17 00:00:00 2001 From: maliwahn Date: Thu, 14 Dec 2023 02:12:52 +0300 Subject: [PATCH 06/11] =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BF=D0=BE=D0=B4=D1=80=D0=BE=D0=B1?= =?UTF-8?q?=D0=BD=D1=8B=D1=85=20=D0=BA=D0=BE=D0=BC=D0=BC=D0=B5=D0=BD=D1=82?= =?UTF-8?q?=D0=B0=D1=80=D0=B8=D0=B5=D0=B2=20commit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../filmorate/controller/ErrorHandler.java | 16 ++++++++++---- .../filmorate/service/FilmService.java | 15 +++++++++---- .../filmorate/service/UserService.java | 21 ++++++++++++++----- .../filmorate/storage/FilmStorage.java | 16 +++++++++----- .../storage/InMemoryFilmStorage.java | 2 +- .../filmorate/storage/UserStorage.java | 15 ++++++++----- 6 files changed, 61 insertions(+), 24 deletions(-) diff --git a/src/main/java/ru/yandex/practicum/filmorate/controller/ErrorHandler.java b/src/main/java/ru/yandex/practicum/filmorate/controller/ErrorHandler.java index ffb9996..349bc16 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/ErrorHandler.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/ErrorHandler.java @@ -10,23 +10,31 @@ import org.springframework.web.bind.annotation.ExceptionHandler; @RestControllerAdvice -public class ErrorHandler { // класс с обработчиками исключений +public class ErrorHandler { /* класс ErrorHandler, который отвечает за обработку исключений в приложении. +В нем определены несколько методов-обработчиков с аннотацией @ExceptionHandler, каждый из которых отвечает +за обработку конкретного типа исключения. */ + @ExceptionHandler @ResponseStatus(HttpStatus.NOT_FOUND) public ErrorResponse handleNotFoundException(final ObjectNotFoundException exception) { return new ErrorResponse(exception.getMessage()); - } + } /* обрабатывает исключение ObjectNotFoundException с помощью аннотации @ResponseStatus, которая устанавливает + код состояния HTTP для возвращаемого ответа. В данном случае установлен код состояния 404 (NOT_FOUND), что означает, + что запрошенный ресурс не найден. Метод возвращает объект ErrorResponse с сообщением об ошибке. */ @ExceptionHandler @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public ErrorResponse handleInternalServiceException(final InternalServiceException exception) { return new ErrorResponse(exception.getMessage()); - } + } /* обрабатывает исключение InternalServiceException и устанавливает код состояния 500 (INTERNAL_SERVER_ERROR) + для возвращаемого ответа. + */ @ExceptionHandler @ResponseStatus(HttpStatus.BAD_REQUEST) public ErrorResponse handleValidationException(final ValidationException exception) { return new ErrorResponse(exception.getMessage()); - } + } /*обрабатывает исключение ValidationException и устанавливает код состояния 400 (BAD_REQUEST) для возвращаемого ответа. + */ } diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/FilmService.java b/src/main/java/ru/yandex/practicum/filmorate/service/FilmService.java index 159fe1f..8a8d4d7 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/service/FilmService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/FilmService.java @@ -15,11 +15,14 @@ @Service @RequiredArgsConstructor public class FilmService extends InMemoryFilmStorage { - //Этот класс представляет сервис для работы с данными фильмов. + /*Этот класс представляет сервис для работы с данными фильмов. В нем используется аннотация @Slf4j, которая добавляет логгирование с использованием библиотеки SLF4J. + Также класс помечен аннотацией @Service, что указывает, что он является компонентом сервиса. + */ private final UserService userService; - // Метод для добавления лайка к фильму от пользователя + /* метод для добавления лайка к фильму от пользователя. Он принимает идентификаторы фильма (`filmId`) и пользователя (`userId`), + проверяет существование фильма и пользователя, а затем вызывает метод `addLike` у объекта фильма для добавления лайка. */ public void addLike(Long filmId, Long userId) { Film film = getFilmById(filmId); userService.getUserById(userId); @@ -30,7 +33,9 @@ public void addLike(Long filmId, Long userId) { log.info("поставлен лайк", userId, filmId); } - // Метод для удаления лайка от пользователя к фильму + /* метод для удаления лайка от пользователя к фильму. Аналогичен методу `addLike`, + но вызывает метод `removeLike` у объекта фильма для удаления лайка. + */ public void deleteLike(Long filmId, Long userId) { Film film = getFilmById(filmId); userService.getUserById(userId); @@ -41,7 +46,9 @@ public void deleteLike(Long filmId, Long userId) { log.info("лайк удалён", userId, filmId); } - // Метод для получения списка популярных фильмов + /* метод для получения списка популярных фильмов. Он сортирует список фильмов по количеству лайков в обратном порядке + и возвращает заданное количество самых популярных фильмов. + */ public List getPopularFilms(int count) { return getFilms().stream() .sorted(Comparator.comparingInt(Film::getLikesQuantity).reversed()) diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/UserService.java b/src/main/java/ru/yandex/practicum/filmorate/service/UserService.java index d06f9b2..a9e8986 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/service/UserService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/UserService.java @@ -14,23 +14,31 @@ @Service @RequiredArgsConstructor public class UserService extends InMemoryUserStorage { - //Этот класс представляет сервис для работы с данными о пользователе + /* Класс UserService представляет сервис для работы с данными о пользователях. + Он наследуется от класса InMemoryUserStorage, что предполагает наличие методов для получения, добавления и удаления пользователей. + */ - public void addFriend(Long userId, Long friendId) { // метод добавления друга + public void addFriend(Long userId, Long friendId) { /* добавляет друга пользователю. + Он принимает идентификаторы пользователя и друга, получает объекты пользователей по их идентификаторам и добавляет друга в список друзей пользователя, а также пользователя в список друзей друга. + */ User user = getUserById(userId); User friend = getUserById(friendId); user.addFriend(friendId); friend.addFriend(userId); } - public void deleteFriend(Long userId, Long friendId) { // метод удаления друга + public void deleteFriend(Long userId, Long friendId) { /* удаляет друга у пользователя. + Он также принимает идентификаторы пользователя и друга, получает объекты пользователей и удаляет друга из списка друзей пользователя и пользователя из списка друзей друга. + */ User user = getUserById(userId); User friend = getUserById(friendId); user.removeFriend(friendId); friend.removeFriend(userId); } - public List getFriends(Long userId) { // метод получения списка друзей + public List getFriends(Long userId) { /* возвращает список друзей пользователя. + Он принимает идентификатор пользователя, получает объект пользователя по его идентификатору и возвращает список объектов друзей пользователя. + */ User user = getUserById(userId); Set friends = user.getFriends(); if (friends.isEmpty()) { @@ -41,7 +49,10 @@ public List getFriends(Long userId) { // метод получения с .collect(Collectors.toList()); } - public List getCommonFriends(Long userId, Long friendId) { // метод предназначен для получения списка общих друзей между двумя пользователями + public List getCommonFriends(Long userId, Long friendId) { /* возвращает список общих друзей между двумя пользователями. + Он принимает идентификаторы пользователя и друга, получает объекты пользователей, получает списки идентификаторов друзей каждого пользователя и находит общих друзей. + Затем метод возвращает список объектов общих друзей. + */ User user = getUserById(userId); User friend = getUserById(friendId); Set userFriends = user.getFriends(); diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/FilmStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/FilmStorage.java index 963f11e..d5a9145 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/storage/FilmStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/FilmStorage.java @@ -6,14 +6,20 @@ public interface FilmStorage { - Film createFilm(Film film); + Film createFilm(Film film); /* метод предназначен для создания нового фильма в хранилище. Принимает объект класса `Film` в качестве параметра и возвращает созданный фильм. - void deleteFilms(); + */ - Film getFilmById(Long id); + void deleteFilms(); /* метод удаления всех фильмов из хранилища. Не возвращает результат. + */ - Film updateFilm(Film film); + Film getFilmById(Long id); /* метод получения фильма по его идентификатору. Принимает идентификатор фильма и возвращает соответствующий объект класса `Film` + */ - List getFilms(); + Film updateFilm(Film film); /* метод обновления информации о фильме в хранилище. Принимает объект класса `Film` с новыми данными фильма и возвращает обновленный объект `Film`. + */ + + List getFilms(); /* метод получения всех фильмов из хранилища. Возвращает список объектов класса `Film`. + */ } diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryFilmStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryFilmStorage.java index 4449e2b..77dc65d 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryFilmStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryFilmStorage.java @@ -42,7 +42,7 @@ public Film updateFilm(Film film) { log.info("фильм обновлён", film.getName(), film.getId()); return film; } else { - throw new ObjectNotFoundException("Такого фильма не существует"); + throw new ObjectNotFoundException("Обновление фильма невозможно, так как он еще не был создан"); } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/UserStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/UserStorage.java index 8738e39..9b0b787 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/storage/UserStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/UserStorage.java @@ -8,14 +8,19 @@ public interface UserStorage { - User createUser(User user); + User createUser(User user); /* метод предназначен для создания нового пользователя в хранилище. Принимает объект класса `User` в качестве параметра и возвращает созданного пользователя. + */ - User updateUser(User user); + User updateUser(User user); /* метод обновления информации о пользователе в хранилище. Принимает объект класса `User` с новыми данными пользователя и возвращает обновленный объект `User`. + */ - void deleteUsers(); + void deleteUsers(); /* метод удаления всех пользователей из хранилища. Не возвращает результат. + */ - User getUserById(Long id); + User getUserById(Long id); /* метод получения пользователя по его идентификатору. Принимает идентификатор пользователя и возвращает соответствующий объект класса `User` + */ - List getUsers(); + List getUsers(); /* метод получения всех пользователей из хранилища. Возвращает список объектов класса `User`. + */ } From 270581008c624acce0e5a553dc28d69bdfc8236b Mon Sep 17 00:00:00 2001 From: maliwahn Date: Tue, 19 Dec 2023 21:14:28 +0300 Subject: [PATCH 07/11] =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BF=D0=BE=D0=B4=D1=80=D0=BE=D0=B1?= =?UTF-8?q?=D0=BD=D1=8B=D1=85=20=D0=BA=D0=BE=D0=BC=D0=BC=D0=B5=D0=BD=D1=82?= =?UTF-8?q?=D0=B0=D1=80=D0=B8=D0=B5=D0=B2=202=20commit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../filmorate/controller/ErrorHandler.java | 31 +++++++++++----- .../filmorate/controller/FilmController.java | 28 +++++++++++---- .../exception/InternalServiceException.java | 8 +++-- .../exception/ObjectNotFoundException.java | 5 +-- .../exception/ValidationException.java | 5 +-- .../filmorate/model/ErrorResponse.java | 8 +++-- .../practicum/filmorate/model/Film.java | 35 ++++++++++++++----- .../practicum/filmorate/model/User.java | 5 ++- .../filmorate/service/FilmService.java | 27 ++++++++------ .../filmorate/service/UserService.java | 35 ++++++++++++------- .../filmorate/storage/FilmStorage.java | 25 ++++++++++--- .../filmorate/storage/UserStorage.java | 26 +++++++++++--- 12 files changed, 170 insertions(+), 68 deletions(-) diff --git a/src/main/java/ru/yandex/practicum/filmorate/controller/ErrorHandler.java b/src/main/java/ru/yandex/practicum/filmorate/controller/ErrorHandler.java index 349bc16..212a372 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/ErrorHandler.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/ErrorHandler.java @@ -10,31 +10,44 @@ import org.springframework.web.bind.annotation.ExceptionHandler; @RestControllerAdvice -public class ErrorHandler { /* класс ErrorHandler, который отвечает за обработку исключений в приложении. -В нем определены несколько методов-обработчиков с аннотацией @ExceptionHandler, каждый из которых отвечает -за обработку конкретного типа исключения. */ +public class ErrorHandler { + /** + * класс ErrorHandler, который отвечает за обработку исключений в приложении. + * В нем определены несколько методов-обработчиков с аннотацией @ExceptionHandler, каждый из которых отвечает + * за обработку конкретного типа исключения. + */ @ExceptionHandler @ResponseStatus(HttpStatus.NOT_FOUND) public ErrorResponse handleNotFoundException(final ObjectNotFoundException exception) { return new ErrorResponse(exception.getMessage()); - } /* обрабатывает исключение ObjectNotFoundException с помощью аннотации @ResponseStatus, которая устанавливает - код состояния HTTP для возвращаемого ответа. В данном случае установлен код состояния 404 (NOT_FOUND), что означает, - что запрошенный ресурс не найден. Метод возвращает объект ErrorResponse с сообщением об ошибке. */ + } + + /** + * обрабатывает исключение ObjectNotFoundException с помощью аннотации @ResponseStatus, которая устанавливает + * код состояния HTTP для возвращаемого ответа. В данном случае установлен код состояния 404 (NOT_FOUND), что означает, + * что запрошенный ресурс не найден. Метод возвращает объект ErrorResponse с сообщением об ошибке. + */ @ExceptionHandler @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public ErrorResponse handleInternalServiceException(final InternalServiceException exception) { return new ErrorResponse(exception.getMessage()); - } /* обрабатывает исключение InternalServiceException и устанавливает код состояния 500 (INTERNAL_SERVER_ERROR) - для возвращаемого ответа. + } + + /** + * обрабатывает исключение InternalServiceException и устанавливает код состояния 500 (INTERNAL_SERVER_ERROR) + * для возвращаемого ответа. */ @ExceptionHandler @ResponseStatus(HttpStatus.BAD_REQUEST) public ErrorResponse handleValidationException(final ValidationException exception) { return new ErrorResponse(exception.getMessage()); - } /*обрабатывает исключение ValidationException и устанавливает код состояния 400 (BAD_REQUEST) для возвращаемого ответа. + } + + /** + * обрабатывает исключение ValidationException и устанавливает код состояния 400 (BAD_REQUEST) для возвращаемого ответа. */ } diff --git a/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java b/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java index 4342c9a..0231a05 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java @@ -17,43 +17,57 @@ public class FilmController { @Autowired private final FilmService filmService; - // Метод для создания нового фильма + /** + * Метод для создания нового фильма + */ @PostMapping public Film createFilm(@Valid @RequestBody Film film) { return filmService.createFilm(film); } - // Метод для получения списка всех фильмов + /** + * Метод для получения списка всех фильмов + */ @GetMapping public List getFilms() { return filmService.getFilms(); } - // Метод для обновления информации о фильме + /** + * Метод для обновления информации о фильме + */ @PutMapping public Film updateFilm(@Valid @RequestBody Film film) { return filmService.updateFilm(film); } - // Метод для получения информации о фильме по его идентификатору + /** + * Метод для получения информации о фильме по его идентификатору + */ @GetMapping("/{id}") public Film getFilmById(@PathVariable Long id) { return filmService.getFilmById(id); } - // Метод для получения списка популярных фильмов + /** + * Метод для получения списка популярных фильмов + */ @GetMapping("/popular") public List getPopularMovies(@RequestParam(defaultValue = "10") Integer count) { return filmService.getPopularFilms(count); } - // Метод для добавления лайка к фильму от пользователя + /** + * Метод для добавления лайка к фильму от пользователя + */ @PutMapping("/{id}/like/{userId}") public void addLike(@PathVariable Long id, @PathVariable Long userId) { filmService.addLike(id, userId); } - // Метод для удаления лайка от пользователя к фильму + /** + * Метод для удаления лайка от пользователя к фильму + */ @DeleteMapping("/{id}/like/{userId}") public void removeLike(@PathVariable Long id, @PathVariable Long userId) { filmService.deleteLike(id, userId); diff --git a/src/main/java/ru/yandex/practicum/filmorate/exception/InternalServiceException.java b/src/main/java/ru/yandex/practicum/filmorate/exception/InternalServiceException.java index e98fff3..8334ed7 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/exception/InternalServiceException.java +++ b/src/main/java/ru/yandex/practicum/filmorate/exception/InternalServiceException.java @@ -3,9 +3,11 @@ import lombok.extern.slf4j.Slf4j; @Slf4j -public class InternalServiceException extends RuntimeException { // Класс представляет собой - // пользовательское исключение, которое может возникать в случае проблем с внутренней службой или ошибок выполнения операций. - +public class InternalServiceException extends RuntimeException { + /** + * Класс представляет собой + * пользовательское исключение, которое может возникать в случае проблем с внутренней службой или ошибок выполнения операций. + */ public InternalServiceException(final String message) { super(message); } diff --git a/src/main/java/ru/yandex/practicum/filmorate/exception/ObjectNotFoundException.java b/src/main/java/ru/yandex/practicum/filmorate/exception/ObjectNotFoundException.java index d3d5fbe..0880155 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/exception/ObjectNotFoundException.java +++ b/src/main/java/ru/yandex/practicum/filmorate/exception/ObjectNotFoundException.java @@ -4,8 +4,9 @@ @Slf4j public class ObjectNotFoundException extends IllegalArgumentException { - // Класс используется для обозначения ситуации, когда объект не найден - + /** + * Класс используется для обозначения ситуации, когда объект не найден + */ public ObjectNotFoundException(final String message) { super(message); log.error(message); diff --git a/src/main/java/ru/yandex/practicum/filmorate/exception/ValidationException.java b/src/main/java/ru/yandex/practicum/filmorate/exception/ValidationException.java index 0996822..f882db4 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/exception/ValidationException.java +++ b/src/main/java/ru/yandex/practicum/filmorate/exception/ValidationException.java @@ -4,8 +4,9 @@ @Slf4j public class ValidationException extends IllegalArgumentException { - // Класс используется для представления ошибок валидации данных - + /** + * Класс используется для представления ошибок валидации данных + */ public ValidationException(final String message) { super(message); log.error(message); diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/ErrorResponse.java b/src/main/java/ru/yandex/practicum/filmorate/model/ErrorResponse.java index 8d454fd..4473519 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/model/ErrorResponse.java +++ b/src/main/java/ru/yandex/practicum/filmorate/model/ErrorResponse.java @@ -4,12 +4,16 @@ public class ErrorResponse { private final String error; - // Конструктор, принимающий сообщение об ошибке + /** + * Конструктор, принимающий сообщение об ошибке + */ public ErrorResponse(String error) { this.error = error; } - // Геттер для получения сообщения об ошибке + /** + * Геттер для получения сообщения об ошибке + */ public String getError() { return error; } diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/Film.java b/src/main/java/ru/yandex/practicum/filmorate/model/Film.java index f6d426f..f72a10a 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/model/Film.java +++ b/src/main/java/ru/yandex/practicum/filmorate/model/Film.java @@ -13,18 +13,37 @@ @Data @AllArgsConstructor public class Film { - //Класс представляет объект фильма и содержит различные свойства и методы для работы с ними - + /** + * Класс представляет объект фильма и содержит различные свойства и методы для работы с ними + */ @PositiveOrZero - private Long id; // Идентификатор фильма + private Long id; + /** + * Идентификатор фильма + */ @NotNull - private String name; // Название фильма + private String name; + /** + * Название фильма + */ @Size(min = 1, max = 200) - private String description; // Описание фильма - private LocalDate releaseDate; // Дата выхода фильма + private String description; + /** + * Описание фильма + */ + private LocalDate releaseDate; + /** + * Дата выхода фильма + */ @Positive - private long duration; // Продолжительность фильма в минутах - private Set likes; // Множество идентификаторов пользователей, поставивших лайки фильму + private long duration; + /** + * Продолжительность фильма в минутах + */ + private Set likes; + /** + * Множество идентификаторов пользователей, поставивших лайки фильму + */ public void addLike(Long userId) { // Метод для добавления лайка от пользователя к фильму likes.add(userId); diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/User.java b/src/main/java/ru/yandex/practicum/filmorate/model/User.java index 53d57e5..67a3288 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/model/User.java +++ b/src/main/java/ru/yandex/practicum/filmorate/model/User.java @@ -9,7 +9,10 @@ @Data @AllArgsConstructor -public class User { // Класс аналогичен классу Film только с информацией о пользователе +public class User { + /** + * Класс аналогичен классу Film только с информацией о пользователе + */ @PositiveOrZero private Long id; @Email diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/FilmService.java b/src/main/java/ru/yandex/practicum/filmorate/service/FilmService.java index 8a8d4d7..065f0a0 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/service/FilmService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/FilmService.java @@ -15,14 +15,17 @@ @Service @RequiredArgsConstructor public class FilmService extends InMemoryFilmStorage { - /*Этот класс представляет сервис для работы с данными фильмов. В нем используется аннотация @Slf4j, которая добавляет логгирование с использованием библиотеки SLF4J. - Также класс помечен аннотацией @Service, что указывает, что он является компонентом сервиса. - */ + /** + * Этот класс представляет сервис для работы с данными фильмов. В нем используется аннотация @Slf4j, которая добавляет логгирование с использованием библиотеки SLF4J. + * Также класс помечен аннотацией @Service, что указывает, что он является компонентом сервиса. + */ private final UserService userService; - /* метод для добавления лайка к фильму от пользователя. Он принимает идентификаторы фильма (`filmId`) и пользователя (`userId`), - проверяет существование фильма и пользователя, а затем вызывает метод `addLike` у объекта фильма для добавления лайка. */ + /** + * метод для добавления лайка к фильму от пользователя. Он принимает идентификаторы фильма (`filmId`) и пользователя (`userId`), + * проверяет существование фильма и пользователя, а затем вызывает метод `addLike` у объекта фильма для добавления лайка. + */ public void addLike(Long filmId, Long userId) { Film film = getFilmById(filmId); userService.getUserById(userId); @@ -33,9 +36,10 @@ public void addLike(Long filmId, Long userId) { log.info("поставлен лайк", userId, filmId); } - /* метод для удаления лайка от пользователя к фильму. Аналогичен методу `addLike`, - но вызывает метод `removeLike` у объекта фильма для удаления лайка. - */ + /** + * метод для удаления лайка от пользователя к фильму. Аналогичен методу `addLike`, + * но вызывает метод `removeLike` у объекта фильма для удаления лайка. + */ public void deleteLike(Long filmId, Long userId) { Film film = getFilmById(filmId); userService.getUserById(userId); @@ -46,9 +50,10 @@ public void deleteLike(Long filmId, Long userId) { log.info("лайк удалён", userId, filmId); } - /* метод для получения списка популярных фильмов. Он сортирует список фильмов по количеству лайков в обратном порядке - и возвращает заданное количество самых популярных фильмов. - */ + /** + * метод для получения списка популярных фильмов. Он сортирует список фильмов по количеству лайков в обратном порядке + * и возвращает заданное количество самых популярных фильмов. + */ public List getPopularFilms(int count) { return getFilms().stream() .sorted(Comparator.comparingInt(Film::getLikesQuantity).reversed()) diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/UserService.java b/src/main/java/ru/yandex/practicum/filmorate/service/UserService.java index a9e8986..8522422 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/service/UserService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/UserService.java @@ -14,31 +14,35 @@ @Service @RequiredArgsConstructor public class UserService extends InMemoryUserStorage { - /* Класс UserService представляет сервис для работы с данными о пользователях. - Он наследуется от класса InMemoryUserStorage, что предполагает наличие методов для получения, добавления и удаления пользователей. + /** + * Класс UserService представляет сервис для работы с данными о пользователях. + * Он наследуется от класса InMemoryUserStorage, что предполагает наличие методов для получения, добавления и удаления пользователей. */ - public void addFriend(Long userId, Long friendId) { /* добавляет друга пользователю. - Он принимает идентификаторы пользователя и друга, получает объекты пользователей по их идентификаторам и добавляет друга в список друзей пользователя, а также пользователя в список друзей друга. - */ + public void addFriend(Long userId, Long friendId) { User user = getUserById(userId); User friend = getUserById(friendId); user.addFriend(friendId); friend.addFriend(userId); } - public void deleteFriend(Long userId, Long friendId) { /* удаляет друга у пользователя. - Он также принимает идентификаторы пользователя и друга, получает объекты пользователей и удаляет друга из списка друзей пользователя и пользователя из списка друзей друга. + /** + * добавляет друга пользователю. + * Он принимает идентификаторы пользователя и друга, получает объекты пользователей по их идентификаторам и добавляет друга в список друзей пользователя, а также пользователя в список друзей друга. */ + + public void deleteFriend(Long userId, Long friendId) { User user = getUserById(userId); User friend = getUserById(friendId); user.removeFriend(friendId); friend.removeFriend(userId); } - public List getFriends(Long userId) { /* возвращает список друзей пользователя. - Он принимает идентификатор пользователя, получает объект пользователя по его идентификатору и возвращает список объектов друзей пользователя. + /** + * удаляет друга у пользователя. + * Он также принимает идентификаторы пользователя и друга, получает объекты пользователей и удаляет друга из списка друзей пользователя и пользователя из списка друзей друга. */ + public List getFriends(Long userId) { User user = getUserById(userId); Set friends = user.getFriends(); if (friends.isEmpty()) { @@ -49,10 +53,11 @@ public List getFriends(Long userId) { /* возвращает список .collect(Collectors.toList()); } - public List getCommonFriends(Long userId, Long friendId) { /* возвращает список общих друзей между двумя пользователями. - Он принимает идентификаторы пользователя и друга, получает объекты пользователей, получает списки идентификаторов друзей каждого пользователя и находит общих друзей. - Затем метод возвращает список объектов общих друзей. - */ + /** + * возвращает список друзей пользователя. + * Он принимает идентификатор пользователя, получает объект пользователя по его идентификатору и возвращает список объектов друзей пользователя. + */ + public List getCommonFriends(Long userId, Long friendId) { User user = getUserById(userId); User friend = getUserById(friendId); Set userFriends = user.getFriends(); @@ -72,4 +77,8 @@ public List getCommonFriends(Long userId, Long friendId) { /* возвра return commonFriendUsers; } + /** возвращает список общих друзей между двумя пользователями. + Он принимает идентификаторы пользователя и друга, получает объекты пользователей, получает списки идентификаторов друзей каждого пользователя и находит общих друзей. + Затем метод возвращает список объектов общих друзей. + */ } diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/FilmStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/FilmStorage.java index d5a9145..d74b802 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/storage/FilmStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/FilmStorage.java @@ -5,21 +5,36 @@ import java.util.List; public interface FilmStorage { + /** + * Этот интерфейс FilmStorage служит для описания контракта, который должен быть реализован классом, работающим с хранилищем фильмов. + * Он определяет набор методов, которые должны быть предоставлены классом, такие как создание, обновление, удаление и получение фильма из хранилища. + */ - Film createFilm(Film film); /* метод предназначен для создания нового фильма в хранилище. Принимает объект класса `Film` в качестве параметра и возвращает созданный фильм. + Film createFilm(Film film); + /** + * метод предназначен для создания нового фильма в хранилище. Принимает объект класса `Film` в качестве параметра и возвращает созданный фильм. */ - void deleteFilms(); /* метод удаления всех фильмов из хранилища. Не возвращает результат. + void deleteFilms(); + + /** + * метод удаления всех фильмов из хранилища. Не возвращает результат. */ - Film getFilmById(Long id); /* метод получения фильма по его идентификатору. Принимает идентификатор фильма и возвращает соответствующий объект класса `Film` + Film getFilmById(Long id); + + /** + * метод получения фильма по его идентификатору. Принимает идентификатор фильма и возвращает соответствующий объект класса `Film` */ - Film updateFilm(Film film); /* метод обновления информации о фильме в хранилище. Принимает объект класса `Film` с новыми данными фильма и возвращает обновленный объект `Film`. + Film updateFilm(Film film); + + /** + * метод обновления информации о фильме в хранилище. Принимает объект класса `Film` с новыми данными фильма и возвращает обновленный объект `Film`. */ - List getFilms(); /* метод получения всех фильмов из хранилища. Возвращает список объектов класса `Film`. + List getFilms(); /** метод получения всех фильмов из хранилища. Возвращает список объектов класса `Film`. */ } diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/UserStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/UserStorage.java index 9b0b787..4bc72bc 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/storage/UserStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/UserStorage.java @@ -7,20 +7,36 @@ import java.util.List; public interface UserStorage { + /** + * Интерфейс UserStorage нужен для определения контракта, который должен быть реализован классом, работающим с хранилищем пользователей. + * Он определяет набор методов, которые должны быть предоставлены классом, такие как создание, обновление, удаление и получение пользователя из хранилища. + */ + + User createUser(User user); - User createUser(User user); /* метод предназначен для создания нового пользователя в хранилище. Принимает объект класса `User` в качестве параметра и возвращает созданного пользователя. + /** + * метод предназначен для создания нового пользователя в хранилище. Принимает объект класса `User` в качестве параметра и возвращает созданного пользователя. */ - User updateUser(User user); /* метод обновления информации о пользователе в хранилище. Принимает объект класса `User` с новыми данными пользователя и возвращает обновленный объект `User`. + User updateUser(User user); + + /** + * метод обновления информации о пользователе в хранилище. Принимает объект класса `User` с новыми данными пользователя и возвращает обновленный объект `User`. */ - void deleteUsers(); /* метод удаления всех пользователей из хранилища. Не возвращает результат. + void deleteUsers(); + + /** + * метод удаления всех пользователей из хранилища. Не возвращает результат. */ - User getUserById(Long id); /* метод получения пользователя по его идентификатору. Принимает идентификатор пользователя и возвращает соответствующий объект класса `User` + User getUserById(Long id); + + /** + * метод получения пользователя по его идентификатору. Принимает идентификатор пользователя и возвращает соответствующий объект класса `User` */ - List getUsers(); /* метод получения всех пользователей из хранилища. Возвращает список объектов класса `User`. + List getUsers(); /** метод получения всех пользователей из хранилища. Возвращает список объектов класса `User`. */ } From 8eb32266cf2ee2fad436ee1e1e8a677b8ea0eb03 Mon Sep 17 00:00:00 2001 From: maliwahn Date: Fri, 1 Mar 2024 05:40:33 +0300 Subject: [PATCH 08/11] =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BF=D0=BE=D0=B4=D1=80=D0=BE=D0=B1?= =?UTF-8?q?=D0=BD=D1=8B=D1=85=20=D0=BA=D0=BE=D0=BC=D0=BC=D0=B5=D0=BD=D1=82?= =?UTF-8?q?=D0=B0=D1=80=D0=B8=D0=B5=D0=B2=202=20commit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- HELP.md | 11 +- README.md | 51 + pom.xml | 50 +- .../filmorate/FilmorateApplication.java | 2 + .../filmorate/controller/ErrorHandler.java | 53 - .../filmorate/controller/FilmController.java | 76 +- .../filmorate/controller/UserController.java | 59 +- .../exception/InternalServiceException.java | 14 - .../exception/ObjectNotFoundException.java | 14 - .../exception/ValidationException.java | 14 - .../filmorate/model/ErrorResponse.java | 22 - .../practicum/filmorate/model/Film.java | 60 +- .../practicum/filmorate/model/User.java | 44 +- .../filmorate/service/FilmService.java | 91 +- .../filmorate/service/UserService.java | 85 +- .../filmorate/storage/FilmStorage.java | 40 - .../storage/InMemoryFilmStorage.java | 110 +- .../storage/InMemoryUserStorage.java | 107 +- .../filmorate/storage/UserStorage.java | 42 - src/main/java/sprint.json | 1047 ----------------- src/main/resources/application.properties | 8 +- .../filmorate/FilmorateApplicationTests.java | 11 +- .../managerTest/FilmControllerTest.java | 90 -- .../FilmorateApplicationTests.java | 17 - .../managerTest/UserControllerTest.java | 136 --- 25 files changed, 329 insertions(+), 1925 deletions(-) delete mode 100644 src/main/java/ru/yandex/practicum/filmorate/controller/ErrorHandler.java delete mode 100644 src/main/java/ru/yandex/practicum/filmorate/exception/InternalServiceException.java delete mode 100644 src/main/java/ru/yandex/practicum/filmorate/exception/ObjectNotFoundException.java delete mode 100644 src/main/java/ru/yandex/practicum/filmorate/exception/ValidationException.java delete mode 100644 src/main/java/ru/yandex/practicum/filmorate/model/ErrorResponse.java delete mode 100644 src/main/java/ru/yandex/practicum/filmorate/storage/FilmStorage.java delete mode 100644 src/main/java/ru/yandex/practicum/filmorate/storage/UserStorage.java delete mode 100644 src/main/java/sprint.json delete mode 100644 src/test/java/ru/yandex/practicum/filmorate/managerTest/FilmControllerTest.java delete mode 100644 src/test/java/ru/yandex/practicum/filmorate/managerTest/FilmorateApplicationTests.java delete mode 100644 src/test/java/ru/yandex/practicum/filmorate/managerTest/UserControllerTest.java diff --git a/HELP.md b/HELP.md index 0ca1809..ac035f8 100644 --- a/HELP.md +++ b/HELP.md @@ -1,17 +1,12 @@ -# Read Me First -The following was discovered as part of building this project: - -* The JVM level was changed from '11' to '17', review the [JDK Version Range](https://github.com/spring-projects/spring-framework/wiki/Spring-Framework-Versions#jdk-version-range) on the wiki for more details. - # Getting Started ### Reference Documentation For further reference, please consider the following sections: * [Official Apache Maven documentation](https://maven.apache.org/guides/index.html) -* [Spring Boot Maven Plugin Reference Guide](https://docs.spring.io/spring-boot/docs/3.1.3/maven-plugin/reference/html/) -* [Create an OCI image](https://docs.spring.io/spring-boot/docs/3.1.3/maven-plugin/reference/html/#build-image) -* [Spring Web](https://docs.spring.io/spring-boot/docs/3.1.3/reference/htmlsingle/index.html#web) +* [Spring Boot Maven Plugin Reference Guide](https://docs.spring.io/spring-boot/docs/2.7.13/maven-plugin/reference/html/) +* [Create an OCI image](https://docs.spring.io/spring-boot/docs/2.7.13/maven-plugin/reference/html/#build-image) +* [Spring Web](https://docs.spring.io/spring-boot/docs/2.7.13/reference/htmlsingle/#web) ### Guides The following guides illustrate how to use some features concretely: diff --git a/README.md b/README.md index 2cf454a..43c373b 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,53 @@ # java-filmorate Template repository for Filmorate project. + +![Sheme data base for filmorate](db_filmorate.jpg) + +В таблице User храняться все пльзователи приложения +В таблице Friends храняться все дружественные связи со статусом их подтвреждения + +В таблице Film храняться все фильмы +В талице MPA храняться все рейтинги фильмов, а в Film добавляется только индекс нужного рейтинга +сделано так что бы убрать избыточность +В таблице genre храняться все жанры и так как связь к фильма многие ко многим выделена таблица для связи genre_film +В таблице likes храняться все id пользователе которым понравился фильма + +Запросы + +1.Для получения таблицы всей информации обо всех пользователях +SLECT * +FROM user; + +2.Для получения таблицы всех пользователей и их друзей +SELECT u.name, +f.isApproved, +u2.name +FROM user u +JOIN friend f ON f.id_user1 = u.id; +JOIN user u2 ON u2.id = f.id_user2 + +2.Для получения таблицы конкретного пользователя и его друзей +SELECT u.name, +f.isApproved, +u2.name +FROM user u +JOIN friend f ON f.id_user1 = u.id; +JOIN user u2 ON u2.id = f.id_user2 +WHERE u.id = {id} + +3.Для полученя таблицы всех фильмов и информации о них +SELECT * +FROM film + +4.Для получения таблицы всех пользователе кому понравился конкретный фильма +SELECT likes.id_user +FROM film f +JOIN likes_film likes ON likes.id_film = f.id +WHERE f.id = {id} + +5.Для получения таблицы всех жанров конкретного фильма +SELECT g.name +FROM film f +JOIN genre_film gf ON gf.id_film = film.id +JOIN genre g ON g.id = gf.id_genre +WHERE f.id = {id} \ No newline at end of file diff --git a/pom.xml b/pom.xml index 915c484..0da5ac4 100644 --- a/pom.xml +++ b/pom.xml @@ -5,12 +5,13 @@ org.springframework.boot spring-boot-starter-parent - 2.7.15 + 2.7.13 - ru.yandex.practicum.com.example + ru.yandex.practicum filmorate 0.0.1-SNAPSHOT + jar filmorate Demo project for Spring Boot @@ -18,33 +19,44 @@ - org.springframework.boot - spring-boot-starter-web - + org.projectlombok + lombok + 1.18.28 + provided + - jakarta.validation - jakarta.validation-api - 3.0.2 + org.springframework.boot + spring-boot-starter-data-jdbc org.springframework.boot - spring-boot-starter-test - test + spring-boot-starter-data-jpa + - org.projectlombok - lombok - 1.18.20 - compile + com.h2database + h2 + runtime + - junit - junit - test - - + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-validation + + + + org.springframework.boot + spring-boot-starter-test + test + + diff --git a/src/main/java/ru/yandex/practicum/filmorate/FilmorateApplication.java b/src/main/java/ru/yandex/practicum/filmorate/FilmorateApplication.java index 3c12a84..0625195 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/FilmorateApplication.java +++ b/src/main/java/ru/yandex/practicum/filmorate/FilmorateApplication.java @@ -1,9 +1,11 @@ package ru.yandex.practicum.filmorate; import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication +@AutoConfiguration public class FilmorateApplication { public static void main(String[] args) { diff --git a/src/main/java/ru/yandex/practicum/filmorate/controller/ErrorHandler.java b/src/main/java/ru/yandex/practicum/filmorate/controller/ErrorHandler.java deleted file mode 100644 index 212a372..0000000 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/ErrorHandler.java +++ /dev/null @@ -1,53 +0,0 @@ -package ru.yandex.practicum.filmorate.controller; - -import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.ResponseStatus; -import ru.yandex.practicum.filmorate.exception.ObjectNotFoundException; -import ru.yandex.practicum.filmorate.exception.ValidationException; -import org.springframework.web.bind.annotation.RestControllerAdvice; -import ru.yandex.practicum.filmorate.exception.InternalServiceException; -import ru.yandex.practicum.filmorate.model.ErrorResponse; -import org.springframework.web.bind.annotation.ExceptionHandler; - -@RestControllerAdvice -public class ErrorHandler { - /** - * класс ErrorHandler, который отвечает за обработку исключений в приложении. - * В нем определены несколько методов-обработчиков с аннотацией @ExceptionHandler, каждый из которых отвечает - * за обработку конкретного типа исключения. - */ - - - @ExceptionHandler - @ResponseStatus(HttpStatus.NOT_FOUND) - public ErrorResponse handleNotFoundException(final ObjectNotFoundException exception) { - return new ErrorResponse(exception.getMessage()); - } - - /** - * обрабатывает исключение ObjectNotFoundException с помощью аннотации @ResponseStatus, которая устанавливает - * код состояния HTTP для возвращаемого ответа. В данном случае установлен код состояния 404 (NOT_FOUND), что означает, - * что запрошенный ресурс не найден. Метод возвращает объект ErrorResponse с сообщением об ошибке. - */ - - @ExceptionHandler - @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) - public ErrorResponse handleInternalServiceException(final InternalServiceException exception) { - return new ErrorResponse(exception.getMessage()); - } - - /** - * обрабатывает исключение InternalServiceException и устанавливает код состояния 500 (INTERNAL_SERVER_ERROR) - * для возвращаемого ответа. - */ - - @ExceptionHandler - @ResponseStatus(HttpStatus.BAD_REQUEST) - public ErrorResponse handleValidationException(final ValidationException exception) { - return new ErrorResponse(exception.getMessage()); - } - - /** - * обрабатывает исключение ValidationException и устанавливает код состояния 400 (BAD_REQUEST) для возвращаемого ответа. - */ -} diff --git a/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java b/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java index 0231a05..1169ed7 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java @@ -1,76 +1,36 @@ package ru.yandex.practicum.filmorate.controller; - -import jakarta.validation.Valid; -import lombok.RequiredArgsConstructor; -import org.springframework.beans.factory.annotation.Autowired; +import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.*; +import ru.yandex.practicum.filmorate.exception.NotFoundException; +import ru.yandex.practicum.filmorate.exception.ValidateException; import ru.yandex.practicum.filmorate.model.Film; -import ru.yandex.practicum.filmorate.service.FilmService; +import ru.yandex.practicum.filmorate.service.Service; -import java.util.List; +import javax.validation.Valid; -@RestController +@Slf4j @RequestMapping("/films") -@RequiredArgsConstructor -public class FilmController { - @Autowired - private final FilmService filmService; +public class FilmController extends Controller { - /** - * Метод для создания нового фильма - */ - @PostMapping - public Film createFilm(@Valid @RequestBody Film film) { - return filmService.createFilm(film); + + public FilmController(Service service) { + super(service); } - /** - * Метод для получения списка всех фильмов - */ - @GetMapping - public List getFilms() { - return filmService.getFilms(); + @PostMapping + public Film add(@RequestBody @Valid Film model) throws ValidateException { + return super.add(model); } - /** - * Метод для обновления информации о фильме - */ @PutMapping - public Film updateFilm(@Valid @RequestBody Film film) { - return filmService.updateFilm(film); + public Film update(@RequestBody @Valid Film model) throws NotFoundException { + return super.update(model); } - /** - * Метод для получения информации о фильме по его идентификатору - */ @GetMapping("/{id}") - public Film getFilmById(@PathVariable Long id) { - return filmService.getFilmById(id); - } - - /** - * Метод для получения списка популярных фильмов - */ - @GetMapping("/popular") - public List getPopularMovies(@RequestParam(defaultValue = "10") Integer count) { - return filmService.getPopularFilms(count); - } - - /** - * Метод для добавления лайка к фильму от пользователя - */ - @PutMapping("/{id}/like/{userId}") - public void addLike(@PathVariable Long id, @PathVariable Long userId) { - filmService.addLike(id, userId); - } - - /** - * Метод для удаления лайка от пользователя к фильму - */ - @DeleteMapping("/{id}/like/{userId}") - public void removeLike(@PathVariable Long id, @PathVariable Long userId) { - filmService.deleteLike(id, userId); + public Film get(@PathVariable int id) throws NotFoundException { + return super.get(id); } -} \ No newline at end of file +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java b/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java index 682efa1..39ee5fb 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java @@ -1,59 +1,38 @@ package ru.yandex.practicum.filmorate.controller; - -import jakarta.validation.Valid; -import lombok.RequiredArgsConstructor; -import org.springframework.beans.factory.annotation.Autowired; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.web.bind.annotation.*; +import ru.yandex.practicum.filmorate.exception.NotFoundException; +import ru.yandex.practicum.filmorate.exception.ValidateException; import ru.yandex.practicum.filmorate.model.User; import ru.yandex.practicum.filmorate.service.UserService; -import java.util.List; +import javax.validation.Valid; -@RestController +@Slf4j @RequestMapping("/users") -@RequiredArgsConstructor -public class UserController { - @Autowired - private final UserService userService; +public class UserController extends Controller { - @PostMapping - public User createUser(@Valid @RequestBody User user) { - return userService.createUser(user); + private static final String SERVICE = "UserService"; + + public UserController(@Qualifier(SERVICE) UserService service) { + super(service); } - @GetMapping - public List getUsers() { - return userService.getUsers(); + @PostMapping + public User add(@RequestBody @Valid User model) throws ValidateException { + return super.add(model); } @PutMapping - public User updateUser(@Valid @RequestBody User user) { - return userService.updateUser(user); + public User update(@RequestBody @Valid User model) throws NotFoundException { + return super.update(model); } @GetMapping("/{id}") - public User getUserById(@PathVariable Long id) { - return userService.getUserById(id); - } - - @PutMapping("/{id}/friends/{friendId}") - public void addFriend(@PathVariable Long id, @PathVariable Long friendId) { - userService.addFriend(id, friendId); + public User get(@PathVariable int id) throws NotFoundException { + return super.get(id); } - @DeleteMapping("/{id}/friends/{friendId}") - public void removeFriend(@PathVariable Long id, @PathVariable Long friendId) { - userService.deleteFriend(id, friendId); - } - - @GetMapping("/{id}/friends") - public List getFriends(@PathVariable Long id) { - return userService.getFriends(id); - } - - @GetMapping("/{id}/friends/common/{otherId}") - public List getCommonFriends(@PathVariable Long id, @PathVariable Long otherId) { - return userService.getCommonFriends(id, otherId); - } -} \ No newline at end of file +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/exception/InternalServiceException.java b/src/main/java/ru/yandex/practicum/filmorate/exception/InternalServiceException.java deleted file mode 100644 index 8334ed7..0000000 --- a/src/main/java/ru/yandex/practicum/filmorate/exception/InternalServiceException.java +++ /dev/null @@ -1,14 +0,0 @@ -package ru.yandex.practicum.filmorate.exception; - -import lombok.extern.slf4j.Slf4j; - -@Slf4j -public class InternalServiceException extends RuntimeException { - /** - * Класс представляет собой - * пользовательское исключение, которое может возникать в случае проблем с внутренней службой или ошибок выполнения операций. - */ - public InternalServiceException(final String message) { - super(message); - } -} diff --git a/src/main/java/ru/yandex/practicum/filmorate/exception/ObjectNotFoundException.java b/src/main/java/ru/yandex/practicum/filmorate/exception/ObjectNotFoundException.java deleted file mode 100644 index 0880155..0000000 --- a/src/main/java/ru/yandex/practicum/filmorate/exception/ObjectNotFoundException.java +++ /dev/null @@ -1,14 +0,0 @@ -package ru.yandex.practicum.filmorate.exception; - -import lombok.extern.slf4j.Slf4j; - -@Slf4j -public class ObjectNotFoundException extends IllegalArgumentException { - /** - * Класс используется для обозначения ситуации, когда объект не найден - */ - public ObjectNotFoundException(final String message) { - super(message); - log.error(message); - } -} diff --git a/src/main/java/ru/yandex/practicum/filmorate/exception/ValidationException.java b/src/main/java/ru/yandex/practicum/filmorate/exception/ValidationException.java deleted file mode 100644 index f882db4..0000000 --- a/src/main/java/ru/yandex/practicum/filmorate/exception/ValidationException.java +++ /dev/null @@ -1,14 +0,0 @@ -package ru.yandex.practicum.filmorate.exception; - -import lombok.extern.slf4j.Slf4j; - -@Slf4j -public class ValidationException extends IllegalArgumentException { - /** - * Класс используется для представления ошибок валидации данных - */ - public ValidationException(final String message) { - super(message); - log.error(message); - } -} diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/ErrorResponse.java b/src/main/java/ru/yandex/practicum/filmorate/model/ErrorResponse.java deleted file mode 100644 index 4473519..0000000 --- a/src/main/java/ru/yandex/practicum/filmorate/model/ErrorResponse.java +++ /dev/null @@ -1,22 +0,0 @@ -package ru.yandex.practicum.filmorate.model; - -public class ErrorResponse { - - private final String error; - - /** - * Конструктор, принимающий сообщение об ошибке - */ - public ErrorResponse(String error) { - this.error = error; - } - - /** - * Геттер для получения сообщения об ошибке - */ - public String getError() { - return error; - } -} - - diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/Film.java b/src/main/java/ru/yandex/practicum/filmorate/model/Film.java index f72a10a..515c284 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/model/Film.java +++ b/src/main/java/ru/yandex/practicum/filmorate/model/Film.java @@ -1,60 +1,22 @@ package ru.yandex.practicum.filmorate.model; +import lombok.Builder; +import lombok.Data; -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Positive; -import jakarta.validation.constraints.PositiveOrZero; -import jakarta.validation.constraints.Size; -import lombok.*; - +import javax.validation.constraints.Positive; import java.time.LocalDate; -import java.util.Set; +import java.util.TreeSet; @Data -@AllArgsConstructor -public class Film { - /** - * Класс представляет объект фильма и содержит различные свойства и методы для работы с ними - */ - @PositiveOrZero - private Long id; - /** - * Идентификатор фильма - */ - @NotNull +@Builder +public class Film extends Model { + private int id; private String name; - /** - * Название фильма - */ - @Size(min = 1, max = 200) private String description; - /** - * Описание фильма - */ private LocalDate releaseDate; - /** - * Дата выхода фильма - */ @Positive - private long duration; - /** - * Продолжительность фильма в минутах - */ - private Set likes; - /** - * Множество идентификаторов пользователей, поставивших лайки фильму - */ - - public void addLike(Long userId) { // Метод для добавления лайка от пользователя к фильму - likes.add(userId); - } - - public void removeLike(Long userId) { // Метод для удаления лайка от пользователя к фильму - likes.remove(userId); - } - - public int getLikesQuantity() { // Метод для получения количества лайков у фильма - return likes.size(); - } + private int duration; + private Mpa mpa; + private TreeSet genres = new TreeSet<>(); + private TreeSet likes = new TreeSet<>(); } - diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/User.java b/src/main/java/ru/yandex/practicum/filmorate/model/User.java index 67a3288..8c7a324 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/model/User.java +++ b/src/main/java/ru/yandex/practicum/filmorate/model/User.java @@ -1,38 +1,32 @@ package ru.yandex.practicum.filmorate.model; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; -import jakarta.validation.constraints.*; -import lombok.*; - +import javax.validation.constraints.Email; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; import java.time.LocalDate; -import java.util.Set; +import java.util.List; @Data +@Builder @AllArgsConstructor -public class User { - /** - * Класс аналогичен классу Film только с информацией о пользователе - */ - @PositiveOrZero - private Long id; +public class User extends Model { + private int id; + private String name; @Email + @NotBlank + @NotNull + @NotEmpty private String email; @NotNull + @NotBlank + @NotEmpty private String login; - private String name; - @PastOrPresent private LocalDate birthday; - private Set friends; - - public void addFriend(Long id) { - friends.add(id); - } - - public void removeFriend(Long id) { - friends.remove(id); - } + private List friends; - public int getFriendsQuantity() { - return friends.size(); - } -} \ No newline at end of file +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/FilmService.java b/src/main/java/ru/yandex/practicum/filmorate/service/FilmService.java index 065f0a0..694ca68 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/service/FilmService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/FilmService.java @@ -1,63 +1,54 @@ package ru.yandex.practicum.filmorate.service; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; -import ru.yandex.practicum.filmorate.exception.ObjectNotFoundException; +import ru.yandex.practicum.filmorate.exception.ValidateException; import ru.yandex.practicum.filmorate.model.Film; -import ru.yandex.practicum.filmorate.storage.InMemoryFilmStorage; +import ru.yandex.practicum.filmorate.storage.StorageFilm; -import java.util.Comparator; -import java.util.stream.Collectors; -import java.util.List; +import java.time.LocalDate; +import java.util.Objects; +import java.util.TreeSet; -@Slf4j @Service -@RequiredArgsConstructor -public class FilmService extends InMemoryFilmStorage { - /** - * Этот класс представляет сервис для работы с данными фильмов. В нем используется аннотация @Slf4j, которая добавляет логгирование с использованием библиотеки SLF4J. - * Также класс помечен аннотацией @Service, что указывает, что он является компонентом сервиса. - */ - - private final UserService userService; - - /** - * метод для добавления лайка к фильму от пользователя. Он принимает идентификаторы фильма (`filmId`) и пользователя (`userId`), - * проверяет существование фильма и пользователя, а затем вызывает метод `addLike` у объекта фильма для добавления лайка. - */ - public void addLike(Long filmId, Long userId) { - Film film = getFilmById(filmId); - userService.getUserById(userId); - if (film == null) { - throw new ObjectNotFoundException("Фильма с таким id не существует" + filmId); - } - film.addLike(userId); - log.info("поставлен лайк", userId, filmId); +public class FilmService extends ServiceFilm { + + // protected static final String FILM_STORAGE = "inMemoryFilmStorage"; + protected static final String FILM_STORAGE = "FilmDBStorage"; + protected static final String FILM_NAME_BLANK_EXCEPTION = "Не заполнено название фильма"; + protected static final String FILM_DESCRIPTION_EXCEPTION = "Длинна описания привышает 200 символов"; + protected static final String FILM_DATE_PRODUCE_EXCEPTION = "Дата выпуска не может быть раньше появления самого кино"; + protected static final String FILM_DURATION_EXCEPTION = "Продолжительность не может быть меньше или ровна 0"; + + + public FilmService(@Qualifier(FILM_STORAGE) StorageFilm storage) { + super(storage); } - /** - * метод для удаления лайка от пользователя к фильму. Аналогичен методу `addLike`, - * но вызывает метод `removeLike` у объекта фильма для удаления лайка. - */ - public void deleteLike(Long filmId, Long userId) { - Film film = getFilmById(filmId); - userService.getUserById(userId); - if (film == null) { - throw new ObjectNotFoundException("Фильма с таким id не существует" + filmId); + protected void validate(Film film) throws ValidateException { + if (Objects.isNull(film.getGenres())) { + film.setGenres(new TreeSet<>()); + } + + if (Objects.isNull(film.getLikes())) { + film.setLikes(new TreeSet<>()); } - film.removeLike(userId); - log.info("лайк удалён", userId, filmId); - } - /** - * метод для получения списка популярных фильмов. Он сортирует список фильмов по количеству лайков в обратном порядке - * и возвращает заданное количество самых популярных фильмов. - */ - public List getPopularFilms(int count) { - return getFilms().stream() - .sorted(Comparator.comparingInt(Film::getLikesQuantity).reversed()) - .limit(count).collect(Collectors.toList()); + if (Objects.isNull(film.getName()) || film.getName().isBlank() || film.getName().isEmpty()) { + throw new ValidateException(FILM_NAME_BLANK_EXCEPTION); + } + + if (film.getDescription().length() > 200) { + throw new ValidateException(FILM_DESCRIPTION_EXCEPTION); + } + final String dateStartFilmEpoch = "1895-12-28"; + if (film.getReleaseDate().isBefore(LocalDate.parse(dateStartFilmEpoch))) { + throw new ValidateException(FILM_DATE_PRODUCE_EXCEPTION); + } + + if (film.getDuration() <= 0) { + throw new ValidateException(FILM_DURATION_EXCEPTION); + } } -} +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/UserService.java b/src/main/java/ru/yandex/practicum/filmorate/service/UserService.java index 8522422..7ae6bf0 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/service/UserService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/UserService.java @@ -1,84 +1,39 @@ package ru.yandex.practicum.filmorate.service; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; -import ru.yandex.practicum.filmorate.exception.ObjectNotFoundException; +import ru.yandex.practicum.filmorate.exception.ValidateException; import ru.yandex.practicum.filmorate.model.User; -import ru.yandex.practicum.filmorate.storage.InMemoryUserStorage; +import ru.yandex.practicum.filmorate.storage.StorageUser; -import java.util.*; -import java.util.stream.Collectors; +import java.time.LocalDate; +import java.util.Objects; -@Slf4j -@Service -@RequiredArgsConstructor -public class UserService extends InMemoryUserStorage { - /** - * Класс UserService представляет сервис для работы с данными о пользователях. - * Он наследуется от класса InMemoryUserStorage, что предполагает наличие методов для получения, добавления и удаления пользователей. - */ +@Service("UserService") +public class UserService extends ServiceUser { + protected static final String USER_STORAGE = "userDBStorage"; + protected static final String USER_LOGIN_EXCEPTION = "Логин пользователя не может содержать пробелы"; + protected static final String USER_BIRTH_DATE_EXCEPTION = "Дата рождения не может быть в будущем"; - public void addFriend(Long userId, Long friendId) { - User user = getUserById(userId); - User friend = getUserById(friendId); - user.addFriend(friendId); - friend.addFriend(userId); + public UserService(@Qualifier(USER_STORAGE) StorageUser storage) { + super(storage); } - /** - * добавляет друга пользователю. - * Он принимает идентификаторы пользователя и друга, получает объекты пользователей по их идентификаторам и добавляет друга в список друзей пользователя, а также пользователя в список друзей друга. - */ + @Override + protected void validate(User user) throws ValidateException { - public void deleteFriend(Long userId, Long friendId) { - User user = getUserById(userId); - User friend = getUserById(friendId); - user.removeFriend(friendId); - friend.removeFriend(userId); - } - /** - * удаляет друга у пользователя. - * Он также принимает идентификаторы пользователя и друга, получает объекты пользователей и удаляет друга из списка друзей пользователя и пользователя из списка друзей друга. - */ - public List getFriends(Long userId) { - User user = getUserById(userId); - Set friends = user.getFriends(); - if (friends.isEmpty()) { - throw new ObjectNotFoundException("Список друзей пользователя " + userId + " пуст"); + if (user.getLogin().contains(" ")) { + throw new ValidateException(USER_LOGIN_EXCEPTION); } - return friends.stream() - .map(this::getUserById) - .collect(Collectors.toList()); - } - - /** - * возвращает список друзей пользователя. - * Он принимает идентификатор пользователя, получает объект пользователя по его идентификатору и возвращает список объектов друзей пользователя. - */ - public List getCommonFriends(Long userId, Long friendId) { - User user = getUserById(userId); - User friend = getUserById(friendId); - Set userFriends = user.getFriends(); - Set friendFriends = friend.getFriends(); - Set commonFriends = new HashSet<>(); - for (Long friendUserId : userFriends) { - if (friendFriends.contains(friendUserId)) { - commonFriends.add(friendUserId); - } + if (Objects.isNull(user.getName()) || user.getName().isEmpty() || user.getName().isBlank()) { + user.setName(user.getLogin()); } - List commonFriendUsers = new ArrayList<>(); - for (Long commonFriendId : commonFriends) { - commonFriendUsers.add(getUserById(commonFriendId)); + if (user.getBirthday().isAfter(LocalDate.now())) { + throw new ValidateException(USER_BIRTH_DATE_EXCEPTION); } - return commonFriendUsers; } - /** возвращает список общих друзей между двумя пользователями. - Он принимает идентификаторы пользователя и друга, получает объекты пользователей, получает списки идентификаторов друзей каждого пользователя и находит общих друзей. - Затем метод возвращает список объектов общих друзей. - */ } diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/FilmStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/FilmStorage.java deleted file mode 100644 index d74b802..0000000 --- a/src/main/java/ru/yandex/practicum/filmorate/storage/FilmStorage.java +++ /dev/null @@ -1,40 +0,0 @@ -package ru.yandex.practicum.filmorate.storage; - -import ru.yandex.practicum.filmorate.model.Film; - -import java.util.List; - -public interface FilmStorage { - /** - * Этот интерфейс FilmStorage служит для описания контракта, который должен быть реализован классом, работающим с хранилищем фильмов. - * Он определяет набор методов, которые должны быть предоставлены классом, такие как создание, обновление, удаление и получение фильма из хранилища. - */ - - Film createFilm(Film film); - - /** - * метод предназначен для создания нового фильма в хранилище. Принимает объект класса `Film` в качестве параметра и возвращает созданный фильм. - */ - - void deleteFilms(); - - /** - * метод удаления всех фильмов из хранилища. Не возвращает результат. - */ - - Film getFilmById(Long id); - - /** - * метод получения фильма по его идентификатору. Принимает идентификатор фильма и возвращает соответствующий объект класса `Film` - */ - - Film updateFilm(Film film); - - /** - * метод обновления информации о фильме в хранилище. Принимает объект класса `Film` с новыми данными фильма и возвращает обновленный объект `Film`. - */ - - List getFilms(); /** метод получения всех фильмов из хранилища. Возвращает список объектов класса `Film`. - */ - -} diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryFilmStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryFilmStorage.java index 77dc65d..224f587 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryFilmStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryFilmStorage.java @@ -1,84 +1,78 @@ package ru.yandex.practicum.filmorate.storage; -import lombok.extern.slf4j.Slf4j; +import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; -import ru.yandex.practicum.filmorate.exception.ObjectNotFoundException; - -import ru.yandex.practicum.filmorate.exception.ValidationException; import ru.yandex.practicum.filmorate.model.Film; +import ru.yandex.practicum.filmorate.model.FromTo; +import ru.yandex.practicum.filmorate.model.Model; -import java.time.LocalDate; -import java.util.*; +import java.util.List; +import java.util.Map; +import java.util.Set; -@Slf4j -@Component -public class InMemoryFilmStorage implements FilmStorage { - private final Map films; - private Long id; +@Component("inMemoryFilmStorage") +@RequiredArgsConstructor +public class InMemoryFilmStorage implements StorageFilm { + protected final Map filmMap; + private Integer id = 0; - public InMemoryFilmStorage() { - films = new HashMap<>(); - id = 0L; + public boolean isExist(int id) { + return filmMap.containsKey(id); } - @Override - public Film createFilm(Film film) { - validate(film); - films.put(film.getId(), film); - log.info("фильм создан", film.getName(), film.getId()); - return film; + public void update(Model model) { + filmMap.put(model.getId(), (Film) model); + } + + public Model save(Model model) { + id++; + model.setId(id); + filmMap.put(id, (Film) model); + return model; + } + + public Film get(int id) { + return filmMap.get(id); + } + + public void delete(int id) { + filmMap.remove(id); + } + + public Map getModelMap() { + return filmMap; + } + + public void removeIdFromIdSet(FromTo films) { } @Override - public void deleteFilms() { - films.clear(); + public T getGenreById(int id) { + return null; } @Override - public Film updateFilm(Film film) { - if (films.containsKey(film.getId())) { - validate(film); - films.put(film.getId(), film); - log.info("фильм обновлён", film.getName(), film.getId()); - return film; - } else { - throw new ObjectNotFoundException("Обновление фильма невозможно, так как он еще не был создан"); - } + public List getGenreList() { + return null; } @Override - public Film getFilmById(Long id) { - if (!films.containsKey(id)) { - throw new ObjectNotFoundException("Фильма с таким id не существует" + id + "'"); - } - return films.get(id); + public T getMpa(int id) { + return null; } @Override - public List getFilms() { - return new ArrayList<>(films.values()); + public List getMpaList() { + return null; } - private void validate(Film film) { - if (film.getReleaseDate() == null || - film.getReleaseDate().isBefore(LocalDate.of(1895, 12, 28))) { - throw new ValidationException("Некорректная дата"); - } - if (film.getName().isEmpty() || film.getName().isBlank()) { - throw new ValidationException("Отсутствует название фильма"); - } - if (film.getDuration() <= 0) { - throw new ValidationException("Длительность фильма не может быть меньше нуля"); - } - if (film.getDescription().length() > 200 || film.getDescription().length() == 0) { - throw new ValidationException("Максимальное количество символов 200"); - } - if (film.getId() == null || film.getId() <= 0) { - film.setId(++id); - } - if (film.getLikes() == null) { - film.setLikes(new HashSet<>()); - } + @Override + public Film addToSet(FromTo filmLikes) { + Film film = filmMap.get(filmLikes.getFrom()); + Set likes = film.getLikes(); + + likes.add(filmLikes.getTo()); + return film; } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryUserStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryUserStorage.java index 7c05e4a..93ba3e0 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryUserStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryUserStorage.java @@ -1,85 +1,70 @@ package ru.yandex.practicum.filmorate.storage; -import lombok.extern.slf4j.Slf4j; +import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; -import ru.yandex.practicum.filmorate.exception.ObjectNotFoundException; -import ru.yandex.practicum.filmorate.exception.ValidationException; +import ru.yandex.practicum.filmorate.model.FromTo; +import ru.yandex.practicum.filmorate.model.Model; import ru.yandex.practicum.filmorate.model.User; +import java.util.List; +import java.util.Map; -import java.time.LocalDate; -import java.util.*; +@Component("inMemoryUserStorage") +@RequiredArgsConstructor +public class InMemoryUserStorage implements StorageUser { + private final Map userMap; + private Integer id = 0; -@Slf4j -@Component -public class InMemoryUserStorage implements UserStorage { - private final Map users; - private Long id; - - public InMemoryUserStorage() { - users = new HashMap<>(); - id = 0L; + public boolean isExist(int id) { + return userMap.containsKey(id); } - @Override - public User createUser(User user) { - validate(user); - users.put(user.getId(), user); - log.info("Пользователь создан", user.getEmail(), user.getId()); - return user; + public void update(Model model) { + userMap.put(model.getId(), (User) model); } - @Override - public User getUserById(Long id) { - if (!users.containsKey(id)) { - throw new ObjectNotFoundException("Пользователя с таким id не существует " + id + "'"); - } - return users.get(id); + public Model save(Model model) { + id++; + model.setId(id); + userMap.put(id, (User) model); + + return model; } - @Override - public User updateUser(User user) { - if (users.containsKey(user.getId())) { - validate(user); - users.put(user.getId(), user); - log.info("Пользователь обновлён", user.getLogin(), user.getId()); - return user; - } else { - throw new ObjectNotFoundException("Пользователя не существует"); - } + public User get(int id) { + return userMap.get(id); } - @Override - public void deleteUsers() { - users.clear(); + public void delete(int id) { + userMap.remove(id); } @Override - public List getUsers() { - return new ArrayList<>(users.values()); + public Map getModelMap() { + return userMap; } - private void validate(User user) { - if (user.getBirthday().isAfter(LocalDate.now()) || user.getBirthday() == null) { - throw new ValidationException("Некорректная дата рождения" + user.getId() + "'"); - } - if (user.getEmail() == null || user.getEmail().isBlank() || !user.getEmail().contains("@")) { - throw new ValidationException("Некорректный email" + user.getId() + "'"); - } - if (user.getName() == null || user.getName().isBlank()) { - user.setName(user.getLogin()); - log.info("Пользователь создан", user.getId(), user.getName()); - } - if (user.getLogin().isBlank() || user.getLogin().isEmpty()) { - throw new ValidationException("Некорректный логин" + user.getId() + "'"); - } - if (user.getFriends() == null) { - user.setFriends(new HashSet<>()); - } - if (user.getId() == null || user.getId() <= 0) { - user.setId(++id); - } + public void removeIdFromIdSet(FromTo user) { + User user1 = userMap.get(user.getFrom()); + User user2 = userMap.get(user.getTo()); + List userFr1 = user1.getFriends(); + List userFr2 = user2.getFriends(); + + userFr1.remove(user2.getId()); + userFr2.remove(user1.getId()); + } + public User addToSet(FromTo user) { + User user1 = userMap.get(user.getFrom()); + User user2 = userMap.get(user.getTo()); + List userFr1 = user1.getFriends(); + List userFr2 = user2.getFriends(); + + userFr1.add(user2.getId()); + userFr2.add(user1.getId()); + + return user1; + } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/UserStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/UserStorage.java deleted file mode 100644 index 4bc72bc..0000000 --- a/src/main/java/ru/yandex/practicum/filmorate/storage/UserStorage.java +++ /dev/null @@ -1,42 +0,0 @@ -package ru.yandex.practicum.filmorate.storage; - -import ru.yandex.practicum.filmorate.model.User; - -import ru.yandex.practicum.filmorate.model.User; - -import java.util.List; - -public interface UserStorage { - /** - * Интерфейс UserStorage нужен для определения контракта, который должен быть реализован классом, работающим с хранилищем пользователей. - * Он определяет набор методов, которые должны быть предоставлены классом, такие как создание, обновление, удаление и получение пользователя из хранилища. - */ - - User createUser(User user); - - /** - * метод предназначен для создания нового пользователя в хранилище. Принимает объект класса `User` в качестве параметра и возвращает созданного пользователя. - */ - - User updateUser(User user); - - /** - * метод обновления информации о пользователе в хранилище. Принимает объект класса `User` с новыми данными пользователя и возвращает обновленный объект `User`. - */ - - void deleteUsers(); - - /** - * метод удаления всех пользователей из хранилища. Не возвращает результат. - */ - - User getUserById(Long id); - - /** - * метод получения пользователя по его идентификатору. Принимает идентификатор пользователя и возвращает соответствующий объект класса `User` - */ - - List getUsers(); /** метод получения всех пользователей из хранилища. Возвращает список объектов класса `User`. - */ - -} diff --git a/src/main/java/sprint.json b/src/main/java/sprint.json deleted file mode 100644 index 541e79a..0000000 --- a/src/main/java/sprint.json +++ /dev/null @@ -1,1047 +0,0 @@ -{ - "info": { - "_postman_id": "e337606f-5df6-47d0-9784-7fa89c1b51fe", - "name": "sprint09/controllers-films-users", - "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", - "_exporter_id": "2036415" - }, - "item": [ - { - "name": "users", - "item": [ - { - "name": "User create", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"Status code is 200 or 201\", function () {\r", - " pm.expect(pm.response.code).to.be.oneOf([200,201]);\r", - "});\r", - "pm.test(\"Has user create response\", function () {\r", - " pm.response.to.be.withBody;\r", - " pm.response.to.be.json;\r", - "});\r", - "pm.test(\"Test user 'id' field\", function () {\r", - " var jsonData = pm.response.json();\r", - " pm.expect(jsonData).to.have.property('id');\r", - " pm.expect(jsonData.id, '\"id\" must be 1').to.eql(1);\r", - "});\r", - "pm.test(\"Test user 'email' field\", function () {\r", - " var jsonData = pm.response.json();\r", - " pm.expect(jsonData).to.have.property('email');\r", - " pm.expect(jsonData.email, '\"email\" must be \"mail@mail.ru\"').to.eql('mail@mail.ru');\r", - "});\r", - "pm.test(\"Test user 'name' field\", function () {\r", - " var jsonData = pm.response.json();\r", - " pm.expect(jsonData).to.have.property('name');\r", - " pm.expect(jsonData.name, '\"name\" must be \"Nick Name\"').to.eql('Nick Name');\r", - "});\r", - "pm.test(\"Test user 'login' field\", function () {\r", - " var jsonData = pm.response.json();\r", - " pm.expect(jsonData).to.have.property('login');\r", - " pm.expect(jsonData.login, '\"login\" field must be \"dolore\"').to.eql('dolore'); \r", - "});\r", - "pm.test(\"Test user 'birthday' field\", function () {\r", - " var jsonData = pm.response.json();\r", - " pm.expect(jsonData).to.have.property('birthday');\r", - " pm.expect(jsonData.birthday, '\"birthday\" field must be \"1946-08-20\"').to.eql('1946-08-20');\r", - "});" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "*/*" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"login\": \"dolore\",\n \"name\": \"Nick Name\",\n \"email\": \"mail@mail.ru\",\n \"birthday\": \"1946-08-20\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{baseUrl}}/users", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "users" - ] - } - }, - "response": [] - }, - { - "name": "User create Fail login", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"Status code is 500 or 400\", function () {\r", - " pm.expect(pm.response.code).to.be.oneOf([500,400]);\r", - "});\r", - "pm.test(\"Has error response\", function () {\r", - " pm.response.to.be.withBody;\r", - " pm.response.to.be.json;\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "*/*" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"login\": \"dolore ullamco\",\n \"email\": \"yandex@mail.ru\",\n \"birthday\": \"2446-08-20\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{baseUrl}}/users", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "users" - ] - } - }, - "response": [] - }, - { - "name": "User create Fail email", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"Status code is 500 or 400\", function () {\r", - " pm.expect(pm.response.code).to.be.oneOf([500,400]);\r", - "});\r", - "pm.test(\"Has error response\", function () {\r", - " pm.response.to.be.withBody;\r", - " pm.response.to.be.json;\r", - "});" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "*/*" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"login\": \"dolore ullamco\",\n \"name\": \"\",\n \"email\": \"mail.ru\",\n \"birthday\": \"1980-08-20\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{baseUrl}}/users", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "users" - ] - } - }, - "response": [] - }, - { - "name": "User create Fail birthday", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"Status code is 500 or 400\", function () {\r", - " pm.expect(pm.response.code).to.be.oneOf([500,400]);\r", - "});\r", - "pm.test(\"Has error response\", function () {\r", - " pm.response.to.be.withBody;\r", - " pm.response.to.be.json;\r", - "});" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "*/*" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"login\": \"dolore\",\n \"name\": \"\",\n \"email\": \"test@mail.ru\",\n \"birthday\": \"2446-08-20\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{baseUrl}}/users", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "users" - ] - } - }, - "response": [] - }, - { - "name": "User update", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"Status code is 200\", function () {\r", - " pm.response.to.be.ok;\r", - "});\r", - "pm.test(\"Has user update response\", function () {\r", - " pm.response.to.be.withBody;\r", - " pm.response.to.be.json;\r", - "});\r", - "pm.test(\"Test user 'id' field\", function () {\r", - " var jsonData = pm.response.json();\r", - " pm.expect(jsonData).to.have.property('id');\r", - " pm.expect(jsonData.id, '\"id\" must be 1').to.eql(1);\r", - "});\r", - "pm.test(\"Test user 'email' field\", function () {\r", - " var jsonData = pm.response.json();\r", - " pm.expect(jsonData).to.have.property('email');\r", - " pm.expect(jsonData.email, '\"email\" must be \"mail@yandex.ru\"').to.eql('mail@yandex.ru');\r", - "});\r", - "pm.test(\"Test user 'name' field\", function () {\r", - " var jsonData = pm.response.json();\r", - " pm.expect(jsonData).to.have.property('name');\r", - " pm.expect(jsonData.name, '\"name\" must be \"est adipisicing\"').to.eql('est adipisicing');\r", - "});\r", - "pm.test(\"Test user 'login' field\", function () {\r", - " var jsonData = pm.response.json();\r", - " pm.expect(jsonData).to.have.property('login');\r", - " pm.expect(jsonData.login, '\"login\" field must be \"doloreUpdate\"').to.eql('doloreUpdate'); \r", - "});\r", - "pm.test(\"Test user 'birthday' field\", function () {\r", - " var jsonData = pm.response.json();\r", - " pm.expect(jsonData).to.have.property('birthday');\r", - " pm.expect(jsonData.birthday, '\"birthday\" field must be \"1976-09-20\"').to.eql('1976-09-20');\r", - "});" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PUT", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "*/*" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"login\": \"doloreUpdate\",\n \"name\": \"est adipisicing\",\n \"id\": 1,\n \"email\": \"mail@yandex.ru\",\n \"birthday\": \"1976-09-20\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{baseUrl}}/users", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "users" - ] - } - }, - "response": [] - }, - { - "name": "User update unknown", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"Status code is 500\", function () {\r", - " pm.expect(pm.response.code).to.be.oneOf([500, 404]);\r", - "});\r", - "pm.test(\"Has error response\", function () {\r", - " pm.response.to.be.withBody;\r", - " pm.response.to.be.json;\r", - "});" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PUT", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "*/*" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"login\": \"doloreUpdate\",\n \"name\": \"est adipisicing\",\n \"id\": 9999,\n \"email\": \"mail@yandex.ru\",\n \"birthday\": \"1976-09-20\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{baseUrl}}/users", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "users" - ] - } - }, - "response": [] - }, - { - "name": "User get All", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"Status code is 200\", function () {\r", - " pm.response.to.be.ok;\r", - "});\r", - "pm.test(\"Test list user response\", function () {\r", - " pm.response.to.be.withBody;\r", - " pm.response.to.be.json;\r", - " var jsonData = pm.response.json();\r", - " pm.expect(jsonData.length, 'List length must be 1').to.eql(1);\r", - "});\r", - "pm.test(\"Test user[0] 'id' field\", function () {\r", - " var jsonData = pm.response.json();\r", - " pm.expect(jsonData[0]).to.have.property('id');\r", - " pm.expect(jsonData[0].id, '\"id\" must be 1').to.eql(1);\r", - "});\r", - "pm.test(\"Test user[0] 'email' field\", function () {\r", - " var jsonData = pm.response.json();\r", - " pm.expect(jsonData[0]).to.have.property('email');\r", - " pm.expect(jsonData[0].email, '\"email\" must be \"mail@yandex.ru\"').to.eql('mail@yandex.ru');\r", - "});\r", - "pm.test(\"Test user[0] 'name' field\", function () {\r", - " var jsonData = pm.response.json();\r", - " pm.expect(jsonData[0]).to.have.property('name');\r", - " pm.expect(jsonData[0].name, '\"name\" must be \"est adipisicing\"').to.eql('est adipisicing');\r", - "});\r", - "pm.test(\"Test user[0] 'login' field\", function () {\r", - " var jsonData = pm.response.json();\r", - " pm.expect(jsonData[0]).to.have.property('login');\r", - " pm.expect(jsonData[0].login, '\"login\" field must be \"doloreUpdate\"').to.eql('doloreUpdate'); \r", - "});\r", - "pm.test(\"Test user[0] 'birthday' field\", function () {\r", - " var jsonData = pm.response.json();\r", - " pm.expect(jsonData[0]).to.have.property('birthday');\r", - " pm.expect(jsonData[0].birthday, '\"birthday\" field must be \"1976-09-20\"').to.eql('1976-09-20');\r", - "});\r", - "\r", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Accept", - "value": "*/*" - } - ], - "url": { - "raw": "{{baseUrl}}/users", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "users" - ] - } - }, - "response": [] - }, - { - "name": "Create user with empty name", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"Status code is 200 or 201\", function () {\r", - " pm.expect(pm.response.code).to.be.oneOf([200,201]);\r", - "});\r", - "pm.test(\"Has user create response\", function () {\r", - " pm.response.to.be.withBody;\r", - " pm.response.to.be.json;\r", - "});\r", - "pm.test(\"Test user 'id' field\", function () {\r", - " var jsonData = pm.response.json();\r", - " pm.expect(jsonData).to.have.property('id');\r", - " pm.expect(jsonData.id, '\"id\" must be 2').to.eql(2);\r", - "});\r", - "pm.test(\"Test user 'email' field\", function () {\r", - " var jsonData = pm.response.json();\r", - " pm.expect(jsonData).to.have.property('email');\r", - " pm.expect(jsonData.email, '\"email\" must be \"friend@common.ru\"').to.eql('friend@common.ru');\r", - "});\r", - "pm.test(\"Test user 'name' field\", function () {\r", - " var jsonData = pm.response.json();\r", - " pm.expect(jsonData).to.have.property('name');\r", - " pm.expect(jsonData.name, '\"name\" must be \"common\"').to.eql('common');\r", - "});\r", - "pm.test(\"Test user 'login' field\", function () {\r", - " var jsonData = pm.response.json();\r", - " pm.expect(jsonData).to.have.property('login');\r", - " pm.expect(jsonData.login, '\"login\" field must be \"common\"').to.eql('common'); \r", - "});\r", - "pm.test(\"Test user 'birthday' field\", function () {\r", - " var jsonData = pm.response.json();\r", - " pm.expect(jsonData).to.have.property('birthday');\r", - " pm.expect(jsonData.birthday, '\"birthday\" field must be \"2000-08-20\"').to.eql('2000-08-20');\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "*/*" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"login\": \"common\",\n \"email\": \"friend@common.ru\",\n \"birthday\": \"2000-08-20\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{baseUrl}}/users", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "users" - ] - } - }, - "response": [] - } - ] - }, - { - "name": "films", - "item": [ - { - "name": "Film create", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"Status code is 200 or 201\", function () {\r", - " pm.expect(pm.response.code).to.be.oneOf([200,201]);\r", - "});\r", - "pm.test(\"Has film create response\", function () {\r", - " pm.response.to.be.withBody;\r", - " pm.response.to.be.json;\r", - "});\r", - "pm.test(\"Test film 'id' field\", function () {\r", - " var jsonData = pm.response.json();\r", - " pm.expect(jsonData).to.have.property('id');\r", - " pm.expect(jsonData.id, '\"id\" must be 1').to.eql(1);\r", - "});\r", - "pm.test(\"Test film 'name' field\", function () {\r", - " var jsonData = pm.response.json();\r", - " pm.expect(jsonData).to.have.property('name');\r", - " pm.expect(jsonData.name, '\"name\" must be \"nisi eiusmod\"').to.eql('nisi eiusmod');\r", - "});\r", - "pm.test(\"Test film 'description' field\", function () {\r", - " var jsonData = pm.response.json();\r", - " pm.expect(jsonData).to.have.property('description');\r", - " pm.expect(jsonData.description, '\"description\" must be \"adipisicing\"').to.eql('adipisicing');\r", - "});\r", - "pm.test(\"Test film 'releaseDate' field\", function () {\r", - " var jsonData = pm.response.json();\r", - " pm.expect(jsonData).to.have.property('releaseDate');\r", - " pm.expect(jsonData.releaseDate, '\"releaseDate\" field must be \"1967-03-25\"').to.eql('1967-03-25');\r", - "});\r", - "pm.test(\"Test film 'duration' field\", function () {\r", - " var jsonData = pm.response.json();\r", - " pm.expect(jsonData).to.have.property('duration');\r", - " pm.expect(jsonData.duration, '\"duration\" field must be 100').to.eql(100); \r", - "});\r", - "\r", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "*/*" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"nisi eiusmod\",\n \"description\": \"adipisicing\",\n \"releaseDate\": \"1967-03-25\",\n \"duration\": 100\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{baseUrl}}/films", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "films" - ] - } - }, - "response": [] - }, - { - "name": "Film create Fail name", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"Status code is 500 or 400\", function () {\r", - " pm.expect(pm.response.code).to.be.oneOf([500,400]);\r", - "});\r", - "pm.test(\"Has error response\", function () {\r", - " pm.response.to.be.withBody;\r", - " pm.response.to.be.json;\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "*/*" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"\",\n \"description\": \"Description\",\n \"releaseDate\": \"1900-03-25\",\n \"duration\": 200\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{baseUrl}}/films", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "films" - ] - } - }, - "response": [] - }, - { - "name": "Film create Fail description", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"Status code is 500 or 400\", function () {\r", - " pm.expect(pm.response.code).to.be.oneOf([500,400]);\r", - "});\r", - "pm.test(\"Has error response\", function () {\r", - " pm.response.to.be.withBody;\r", - " pm.response.to.be.json;\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "*/*" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"Film name\",\n \"description\": \"Пятеро друзей ( комик-группа «Шарло»), приезжают в город Бризуль. Здесь они хотят разыскать господина Огюста Куглова, который задолжал им деньги, а именно 20 миллионов. о Куглов, который за время «своего отсутствия», стал кандидатом Коломбани.\",\n \"releaseDate\": \"1900-03-25\",\n \"duration\": 200\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{baseUrl}}/films", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "films" - ] - } - }, - "response": [] - }, - { - "name": "Film create Fail releaseDate", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"Status code is 500 or 400\", function () {\r", - " pm.expect(pm.response.code).to.be.oneOf([500,400]);\r", - "});\r", - "pm.test(\"Has error response\", function () {\r", - " pm.response.to.be.withBody;\r", - " pm.response.to.be.json;\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "*/*" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"Name\",\n \"description\": \"Description\",\n \"releaseDate\": \"1890-03-25\",\n \"duration\": 200\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{baseUrl}}/films", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "films" - ] - } - }, - "response": [] - }, - { - "name": "Film create Fail duration", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"Status code is 500 or 400\", function () {\r", - " pm.expect(pm.response.code).to.be.oneOf([500,400]);\r", - "});\r", - "pm.test(\"Has error response\", function () {\r", - " pm.response.to.be.withBody;\r", - " pm.response.to.be.json;\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "*/*" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"Name\",\n \"description\": \"Descrition\",\n \"releaseDate\": \"1980-03-25\",\n \"duration\": -200\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{baseUrl}}/films", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "films" - ] - } - }, - "response": [] - }, - { - "name": "Film update", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"Status code is 200\", function () {\r", - " pm.response.to.be.ok;\r", - "});\r", - "pm.test(\"Has film update response\", function () {\r", - " pm.response.to.be.withBody;\r", - " pm.response.to.be.json;\r", - "});\r", - "\r", - "pm.test(\"Test film 'id' field\", function () {\r", - " var jsonData = pm.response.json();\r", - " pm.expect(jsonData).to.have.property('id');\r", - " pm.expect(jsonData.id, '\"id\" must be 1').to.eql(1);\r", - "});\r", - "pm.test(\"Test film 'name' field\", function () {\r", - " var jsonData = pm.response.json();\r", - " pm.expect(jsonData).to.have.property('name');\r", - " pm.expect(jsonData.name, '\"name\" must be \"Film Updated\"').to.eql('Film Updated');\r", - "});\r", - "pm.test(\"Test film 'description' field\", function () {\r", - " var jsonData = pm.response.json();\r", - " pm.expect(jsonData).to.have.property('description');\r", - " pm.expect(jsonData.description, '\"description\" must be \"New film update decription\"').to.eql('New film update decription');\r", - "});\r", - "pm.test(\"Test film 'releaseDate' field\", function () {\r", - " var jsonData = pm.response.json();\r", - " pm.expect(jsonData).to.have.property('releaseDate');\r", - " pm.expect(jsonData.releaseDate, '\"releaseDate\" field must be \"1989-04-17\"').to.eql('1989-04-17');\r", - "});\r", - "pm.test(\"Test film 'duration' field\", function () {\r", - " var jsonData = pm.response.json();\r", - " pm.expect(jsonData).to.have.property('duration');\r", - " pm.expect(jsonData.duration, '\"duration\" field must be 190').to.eql(190); \r", - "});\r", - "\r", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PUT", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "*/*" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"id\": 1,\n \"name\": \"Film Updated\",\n \"releaseDate\": \"1989-04-17\",\n \"description\": \"New film update decription\",\n \"duration\": 190,\n \"rate\": 4\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{baseUrl}}/films", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "films" - ] - } - }, - "response": [] - }, - { - "name": "Film update unknown", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"Status code is 500\", function () {\r", - " pm.expect(pm.response.code).to.be.oneOf([500, 404]);\r", - "});\r", - "pm.test(\"Has error response\", function () {\r", - " pm.response.to.be.withBody;\r", - " pm.response.to.be.json;\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PUT", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "*/*" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"id\": 9999,\n \"name\": \"Film Updated\",\n \"releaseDate\": \"1989-04-17\",\n \"description\": \"New film update decription\",\n \"duration\": 190,\n \"rate\": 4\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{baseUrl}}/films", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "films" - ] - } - }, - "response": [] - }, - { - "name": "Film get All", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"Status code is 200\", function () {\r", - " pm.response.to.be.ok;\r", - "});\r", - "pm.test(\"Test list film response\", function () {\r", - " pm.response.to.be.withBody;\r", - " pm.response.to.be.json;\r", - " var jsonData = pm.response.json();\r", - " pm.expect(jsonData.length, 'List length must be 1').to.eql(1);\r", - "});\r", - "pm.test(\"Test film[0] 'id' field\", function () {\r", - " var jsonData = pm.response.json();\r", - " pm.expect(jsonData[0]).to.have.property('id');\r", - " pm.expect(jsonData[0].id, '\"id\" must be 1').to.eql(1);\r", - "});\r", - "pm.test(\"Test film[0] 'name' field\", function () {\r", - " var jsonData = pm.response.json();\r", - " pm.expect(jsonData[0]).to.have.property('name');\r", - " pm.expect(jsonData[0].name, '\"name\" must be \"Film Updated\"').to.eql('Film Updated');\r", - "});\r", - "pm.test(\"Test film[0] 'description' field\", function () {\r", - " var jsonData = pm.response.json();\r", - " pm.expect(jsonData[0]).to.have.property('description');\r", - " pm.expect(jsonData[0].description, '\"description\" must be \"New film update decription\"').to.eql('New film update decription');\r", - "});\r", - "pm.test(\"Test film[0] 'releaseDate' field\", function () {\r", - " var jsonData = pm.response.json();\r", - " pm.expect(jsonData[0]).to.have.property('releaseDate');\r", - " pm.expect(jsonData[0].releaseDate, '\"releaseDate\" field must be \"1989-04-17\"').to.eql('1989-04-17');\r", - "});\r", - "pm.test(\"Test film[0] 'duration' field\", function () {\r", - " var jsonData = pm.response.json();\r", - " pm.expect(jsonData[0]).to.have.property('duration');\r", - " pm.expect(jsonData[0].duration, '\"duration\" field must be 190').to.eql(190); \r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Accept", - "value": "*/*" - } - ], - "url": { - "raw": "{{baseUrl}}/films", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "films" - ] - } - }, - "response": [] - } - ] - } - ], - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - } - ], - "variable": [ - { - "key": "baseUrl", - "value": "http://localhost:8080", - "type": "string" - } - ] -} \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 8b13789..939a8c1 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1,7 @@ - +spring.h2.console.enabled=true +spring.sql.init.mode=always +# ? jdbc-url ???????, ??? ?????? ????? ????????? ? ???? +spring.datasource.url=jdbc:h2:file:./db/filmorate +spring.datasource.driverClassName=org.h2.Driver +spring.datasource.username=sa +spring.datasource.password=password \ No newline at end of file diff --git a/src/test/java/ru/yandex/practicum/filmorate/FilmorateApplicationTests.java b/src/test/java/ru/yandex/practicum/filmorate/FilmorateApplicationTests.java index 660412e..9035f0a 100644 --- a/src/test/java/ru/yandex/practicum/filmorate/FilmorateApplicationTests.java +++ b/src/test/java/ru/yandex/practicum/filmorate/FilmorateApplicationTests.java @@ -1,13 +1,20 @@ package ru.yandex.practicum.filmorate; +import lombok.RequiredArgsConstructor; import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.context.SpringBootTest; + @SpringBootTest +@AutoConfigureTestDatabase +@RequiredArgsConstructor(onConstructor_ = @Autowired) class FilmorateApplicationTests { + @Test - void contextLoads() { - } + public void test() { + } } diff --git a/src/test/java/ru/yandex/practicum/filmorate/managerTest/FilmControllerTest.java b/src/test/java/ru/yandex/practicum/filmorate/managerTest/FilmControllerTest.java deleted file mode 100644 index fd7ef9e..0000000 --- a/src/test/java/ru/yandex/practicum/filmorate/managerTest/FilmControllerTest.java +++ /dev/null @@ -1,90 +0,0 @@ -package ru.yandex.practicum.filmorate.managerTest; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; -import ru.yandex.practicum.filmorate.controller.FilmController; -import ru.yandex.practicum.filmorate.exception.ValidationException; -import ru.yandex.practicum.filmorate.model.Film; -import ru.yandex.practicum.filmorate.model.User; -import ru.yandex.practicum.filmorate.service.FilmService; -import ru.yandex.practicum.filmorate.service.UserService; -import ru.yandex.practicum.filmorate.storage.FilmStorage; -import ru.yandex.practicum.filmorate.storage.InMemoryFilmStorage; -import ru.yandex.practicum.filmorate.storage.InMemoryUserStorage; -import ru.yandex.practicum.filmorate.storage.UserStorage; - -import java.time.LocalDate; -import java.util.HashSet; - -public class FilmControllerTest { - private FilmStorage storage = new InMemoryFilmStorage(); - private UserStorage userStorage = new InMemoryUserStorage(); - private UserService userService = new UserService(); - private FilmService service = new FilmService(userService); - private FilmController controller = new FilmController(service); - private final Film negativeDurationFilm = new Film(1L, "Movie", - "Описание фильма", - LocalDate.of(1995, 2, 2), -15, new HashSet<>()); - private final User user = new User(2L, "vanya@ya.ru", "vanya", "Vanya", - LocalDate.of(1999, 3, 5), new HashSet<>()); - private final Film film = new Film(1L, "Film", "Описание фильма", - LocalDate.of(1995, 2, 2), 120, new HashSet<>()); - private final Film updatedFilm = new Film(1L, "Film", - "Описание этого фильма", - LocalDate.of(1995, 2, 2), 120, new HashSet<>()); - private final Film noNamedFilm = new Film(1L, "", "Описание фильма", - LocalDate.of(1995, 2, 2), 120, new HashSet<>()); - private final Film longDescpriptionFilm = new Film(1L, "Film", - "Жесть какая-то, а не фильм", - LocalDate.of(1995, 2, 2), 120, new HashSet<>()); - - @AfterEach - public void afterEach() { - storage.deleteFilms(); - } - - @Test - void createFilmTest() { - controller.createFilm(film); - - Assertions.assertEquals(1, controller.getFilms().size()); - } - - @Test - void updateFilmTest() { - controller.createFilm(film); - controller.updateFilm(updatedFilm); - - Assertions.assertEquals("Описание этого фильма", updatedFilm.getDescription()); - Assertions.assertEquals(1, controller.getFilms().size()); - } - - @Test - void createFilmEmptyNameTest() { - Assertions.assertThrows(ValidationException.class, () -> controller.createFilm(noNamedFilm)); - Assertions.assertEquals(0, controller.getFilms().size()); - } - - @Test - void getFilmByIdTest() { - controller.createFilm(film); - Film thisFilm = controller.getFilmById(film.getId()); - - Assertions.assertEquals(1, thisFilm.getId()); - } - - @Test - void createFilmDuration0Test() { - Assertions.assertThrows(ValidationException.class, () -> controller.createFilm(negativeDurationFilm)); - Assertions.assertEquals(0, controller.getFilms().size()); - } - - @Test - void createFilm1895Test() { - film.setReleaseDate(LocalDate.of(1895, 2, 2)); - - Assertions.assertThrows(ValidationException.class, () -> controller.createFilm(film)); - Assertions.assertEquals(0, controller.getFilms().size()); - } -} diff --git a/src/test/java/ru/yandex/practicum/filmorate/managerTest/FilmorateApplicationTests.java b/src/test/java/ru/yandex/practicum/filmorate/managerTest/FilmorateApplicationTests.java deleted file mode 100644 index 51ff2e1..0000000 --- a/src/test/java/ru/yandex/practicum/filmorate/managerTest/FilmorateApplicationTests.java +++ /dev/null @@ -1,17 +0,0 @@ -package ru.yandex.practicum.filmorate.managerTest; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class FilmorateApplicationTests { - - @Test - void testTest() { - } - -} - - - - diff --git a/src/test/java/ru/yandex/practicum/filmorate/managerTest/UserControllerTest.java b/src/test/java/ru/yandex/practicum/filmorate/managerTest/UserControllerTest.java deleted file mode 100644 index c1250bc..0000000 --- a/src/test/java/ru/yandex/practicum/filmorate/managerTest/UserControllerTest.java +++ /dev/null @@ -1,136 +0,0 @@ -package ru.yandex.practicum.filmorate.managerTest; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; -import ru.yandex.practicum.filmorate.controller.UserController; -import ru.yandex.practicum.filmorate.exception.ValidationException; -import ru.yandex.practicum.filmorate.model.User; -import ru.yandex.practicum.filmorate.service.UserService; -import ru.yandex.practicum.filmorate.storage.InMemoryUserStorage; - -import java.time.LocalDate; -import java.util.HashSet; -import java.util.List; - -public class UserControllerTest { - private InMemoryUserStorage storage = new InMemoryUserStorage(); - private UserService service = new UserService(); - private UserController controller = new UserController(service); - private final User user = new User(1L, "vanya@yandex.ru", "login", "Vanya", - LocalDate.of(1995, 1, 1), new HashSet<>()); - private final User updatedUser = new User(1L, "oleg@yandex.ru", "login", "Vanya", - LocalDate.of(1995, 2, 1), new HashSet<>()); - private final User emptyNameUser = new User(6L, "nikita@yandex.ru", "login1", null, - LocalDate.of(1995, 1, 1), new HashSet<>()); - private final User incorrectEmailUser = new User(3L, "somebody once told me, the world is gonna roll me", - "loglog", "Dasha", LocalDate.of(1997, 8, 13), new HashSet<>()); - private final User emptyEmailUser = new User(1L, "", "puss in boots", null, - LocalDate.of(1990, 1, 1), new HashSet<>()); - private final User commonFriend = new User(19L, "friend@yandex.ru", "friend", "Alexander", - LocalDate.of(1888, 5, 5), new HashSet<>()); - - @AfterEach - public void afterEach() { - storage.deleteUsers(); - } - - @Test - void createUserTest() { - controller.createUser(user); - - Assertions.assertEquals(1, controller.getUsers().size()); - } - - @Test - void updateTest() { - controller.createUser(user); - controller.updateUser(updatedUser); - - Assertions.assertEquals("oleg@yandex.ru", updatedUser.getEmail()); - Assertions.assertEquals(user.getId(), updatedUser.getId()); - Assertions.assertEquals(1, controller.getUsers().size()); - } - - @Test - void createUserFutureTest() { - user.setBirthday(LocalDate.of(2024, 6, 28)); - - Assertions.assertThrows(ValidationException.class, () -> controller.createUser(user)); - Assertions.assertEquals(0, controller.getUsers().size()); - } - - @Test - void createUserNameIsEmptyTest() { - controller.createUser(emptyNameUser); - - Assertions.assertEquals(6, emptyNameUser.getId()); - Assertions.assertEquals("login1", emptyNameUser.getName()); - } - - @Test - void createUserEmailIsEmptyTest() { - Assertions.assertThrows(ValidationException.class, () -> controller.createUser(emptyEmailUser)); - Assertions.assertEquals(0, controller.getUsers().size()); - } - - @Test - void createUserLoginIsEmptyTest() { - user.setLogin(""); - - Assertions.assertThrows(ValidationException.class, () -> controller.createUser(user)); - Assertions.assertEquals(0, controller.getUsers().size()); - } - - @Test - void createUserEmailIncorrectTest() { - Assertions.assertThrows(ValidationException.class, () -> controller.createUser(incorrectEmailUser)); - Assertions.assertEquals(0, controller.getUsers().size()); - } - - @Test - void deleteFriendTest() { - controller.createUser(user); - controller.createUser(emptyNameUser); - controller.addFriend(user.getId(), emptyNameUser.getId()); - controller.removeFriend(user.getId(), emptyNameUser.getId()); - - Assertions.assertEquals(0, user.getFriendsQuantity()); - Assertions.assertEquals(0, emptyNameUser.getFriendsQuantity()); - } - - @Test - void getFriendsTest() { - controller.createUser(user); - controller.createUser(emptyNameUser); - controller.createUser(commonFriend); - controller.addFriend(user.getId(), emptyNameUser.getId()); - controller.addFriend(user.getId(), commonFriend.getId()); - List listOfUsersFriends = controller.getFriends(user.getId()); - - Assertions.assertEquals(2, listOfUsersFriends.size()); - } - - @Test - void addFriendTest() { - controller.createUser(user); - controller.createUser(emptyNameUser); - controller.addFriend(user.getId(), emptyNameUser.getId()); - - Assertions.assertTrue(user.getFriendsQuantity() != 0); - Assertions.assertTrue(emptyNameUser.getFriendsQuantity() != 0); - } - - @Test - void getCommonFriendsTest() { - controller.createUser(user); - controller.createUser(emptyNameUser); - controller.addFriend(user.getId(), emptyNameUser.getId()); - controller.createUser(commonFriend); - controller.addFriend(user.getId(), commonFriend.getId()); - controller.addFriend(emptyNameUser.getId(), commonFriend.getId()); - List commonFriendList = controller.getCommonFriends(user.getId(), emptyNameUser.getId()); - - Assertions.assertEquals(1, commonFriendList.size()); - } -} \ No newline at end of file From 9dd24c5d69f9de960e17835b361836a864e29dd6 Mon Sep 17 00:00:00 2001 From: maliwahn Date: Fri, 1 Mar 2024 05:41:48 +0300 Subject: [PATCH 09/11] =?UTF-8?q?11=20=D1=82=D0=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- db_filmorate.jpg | Bin 0 -> 175818 bytes .../filmorate/controller/Controller.java | 39 +++ .../controller/FilmLikeController.java | 38 +++ .../controller/GenresController.java | 30 +++ .../filmorate/controller/MpaController.java | 30 +++ .../controller/UserLikeController.java | 44 ++++ .../exception/ControllerException.java | 28 +++ .../exception/NotFoundException.java | 7 + .../exception/ValidateException.java | 7 + .../filmorate/model/ExceptionEntity.java | 14 ++ .../practicum/filmorate/model/FilmLikes.java | 9 + .../practicum/filmorate/model/FriendsTo.java | 7 + .../practicum/filmorate/model/FromTo.java | 12 + .../practicum/filmorate/model/Genre.java | 14 ++ .../practicum/filmorate/model/Model.java | 9 + .../yandex/practicum/filmorate/model/Mpa.java | 12 + .../service/ManageFriendsUserService.java | 65 +++++ .../service/ManageLikeFilmService.java | 62 +++++ .../practicum/filmorate/service/Service.java | 73 ++++++ .../filmorate/service/ServiceFilm.java | 44 ++++ .../filmorate/service/ServiceUser.java | 16 ++ .../filmorate/storage/FilmDBStorage.java | 234 ++++++++++++++++++ .../practicum/filmorate/storage/Storage.java | 27 ++ .../filmorate/storage/StorageFilm.java | 15 ++ .../filmorate/storage/StorageUser.java | 6 + .../filmorate/storage/UserDBStorage.java | 122 +++++++++ src/main/resources/data.sql | 12 + src/main/resources/schema.sql | 43 ++++ .../controller/FilmControllerTest.java | 42 ++++ .../controller/UserControllerTest.java | 37 +++ .../filmorate/service/FilmServiceTest.java | 42 ++++ .../service/ManageFriendsToServiceTest.java | 51 ++++ .../filmorate/service/UserServiceTest.java | 38 +++ .../filmorate/storage/FilmDBStorageTest.java | 85 +++++++ .../filmorate/storage/UserDBStorageTest.java | 55 ++++ 35 files changed, 1369 insertions(+) create mode 100644 db_filmorate.jpg create mode 100644 src/main/java/ru/yandex/practicum/filmorate/controller/Controller.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/controller/FilmLikeController.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/controller/GenresController.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/controller/MpaController.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/controller/UserLikeController.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/exception/ControllerException.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/exception/NotFoundException.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/exception/ValidateException.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/model/ExceptionEntity.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/model/FilmLikes.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/model/FriendsTo.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/model/FromTo.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/model/Genre.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/model/Model.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/model/Mpa.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/service/ManageFriendsUserService.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/service/ManageLikeFilmService.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/service/Service.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/service/ServiceFilm.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/service/ServiceUser.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/storage/FilmDBStorage.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/storage/Storage.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/storage/StorageFilm.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/storage/StorageUser.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/storage/UserDBStorage.java create mode 100644 src/main/resources/data.sql create mode 100644 src/main/resources/schema.sql create mode 100644 src/test/java/ru/yandex/practicum/filmorate/controller/FilmControllerTest.java create mode 100644 src/test/java/ru/yandex/practicum/filmorate/controller/UserControllerTest.java create mode 100644 src/test/java/ru/yandex/practicum/filmorate/service/FilmServiceTest.java create mode 100644 src/test/java/ru/yandex/practicum/filmorate/service/ManageFriendsToServiceTest.java create mode 100644 src/test/java/ru/yandex/practicum/filmorate/service/UserServiceTest.java create mode 100644 src/test/java/ru/yandex/practicum/filmorate/storage/FilmDBStorageTest.java create mode 100644 src/test/java/ru/yandex/practicum/filmorate/storage/UserDBStorageTest.java diff --git a/db_filmorate.jpg b/db_filmorate.jpg new file mode 100644 index 0000000000000000000000000000000000000000..29136568306a66332653f547277be858b572ac53 GIT binary patch literal 175818 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 F=I;P)cBlXV literal 0 HcmV?d00001 diff --git a/src/main/java/ru/yandex/practicum/filmorate/controller/Controller.java b/src/main/java/ru/yandex/practicum/filmorate/controller/Controller.java new file mode 100644 index 0000000..d80f457 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/Controller.java @@ -0,0 +1,39 @@ +package ru.yandex.practicum.filmorate.controller; + +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import ru.yandex.practicum.filmorate.exception.NotFoundException; +import ru.yandex.practicum.filmorate.exception.ValidateException; +import ru.yandex.practicum.filmorate.model.Model; +import ru.yandex.practicum.filmorate.service.Service; + +import java.util.List; + +@RequiredArgsConstructor +public abstract class Controller { + + protected final Service service; + + + public T add(T model) throws ValidateException { + + return service.addModel(model); + } + + + public T update(T model) throws NotFoundException { + service.updateModel(model); + return model; + } + + @GetMapping + public List getModelList() { + return service.getModelList(); + } + + public T get(int id) throws NotFoundException { + T model = service.getModelById(id); + + return model; + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/controller/FilmLikeController.java b/src/main/java/ru/yandex/practicum/filmorate/controller/FilmLikeController.java new file mode 100644 index 0000000..fe782fe --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/FilmLikeController.java @@ -0,0 +1,38 @@ +package ru.yandex.practicum.filmorate.controller; + +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.web.bind.annotation.*; +import ru.yandex.practicum.filmorate.exception.NotFoundException; +import ru.yandex.practicum.filmorate.model.Film; +import ru.yandex.practicum.filmorate.model.FilmLikes; +import ru.yandex.practicum.filmorate.service.ManageLikeFilmService; +import ru.yandex.practicum.filmorate.service.ServiceFilm; + +import java.util.List; + +@RestController +public class FilmLikeController extends FilmController { + protected static final String USE_SERVICE = "ManageLikeFilmService"; + private final ManageLikeFilmService manageLikeFilmService; + + public FilmLikeController(@Qualifier(USE_SERVICE) ServiceFilm filmService) { + super(filmService); + manageLikeFilmService = (ManageLikeFilmService) filmService; + } + + @PutMapping("/{id}/like/{userId}") + public void setLike(@PathVariable int id, @PathVariable int userId) { + manageLikeFilmService.addLike(new FilmLikes(id, userId)); + } + + @DeleteMapping("/{id}/like/{userId}") + public void deleteLike(@PathVariable int id, @PathVariable int userId) throws NotFoundException { + manageLikeFilmService.deleteLike(new FilmLikes(id, userId)); + } + + @GetMapping("/popular") + public List getFilmsPopular(@RequestParam(defaultValue = "10") int count) { + + return manageLikeFilmService.getFilmsPopular(count); + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/controller/GenresController.java b/src/main/java/ru/yandex/practicum/filmorate/controller/GenresController.java new file mode 100644 index 0000000..878dfc3 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/GenresController.java @@ -0,0 +1,30 @@ +package ru.yandex.practicum.filmorate.controller; + +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import ru.yandex.practicum.filmorate.exception.NotFoundException; +import ru.yandex.practicum.filmorate.model.Genre; +import ru.yandex.practicum.filmorate.service.ServiceFilm; + +import java.util.List; + +@RestController +@RequestMapping("/genres") +@RequiredArgsConstructor +public class GenresController { + private final ServiceFilm filmService; + + @GetMapping("/{id}") + public Genre getGenreByID(@PathVariable int id) throws NotFoundException { + return filmService.getGenre(id); + } + + @GetMapping + public List getGenreList() { + return filmService.getGenreList(); + } + +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/controller/MpaController.java b/src/main/java/ru/yandex/practicum/filmorate/controller/MpaController.java new file mode 100644 index 0000000..ba96ad6 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/MpaController.java @@ -0,0 +1,30 @@ +package ru.yandex.practicum.filmorate.controller; + +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import ru.yandex.practicum.filmorate.exception.NotFoundException; +import ru.yandex.practicum.filmorate.model.Mpa; +import ru.yandex.practicum.filmorate.service.ServiceFilm; + +import java.util.List; + +@RestController +@RequestMapping("/mpa") +@RequiredArgsConstructor +public class MpaController { + private final ServiceFilm filmService; + + @GetMapping + public List getMpaList() { + return filmService.getMpaList(); + } + + @GetMapping("/{id}") + public Mpa getMpaById(@PathVariable int id) throws NotFoundException { + return filmService.getMpa(id); + } + +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/controller/UserLikeController.java b/src/main/java/ru/yandex/practicum/filmorate/controller/UserLikeController.java new file mode 100644 index 0000000..adec0f5 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/UserLikeController.java @@ -0,0 +1,44 @@ +package ru.yandex.practicum.filmorate.controller; + +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.web.bind.annotation.*; +import ru.yandex.practicum.filmorate.exception.NotFoundException; +import ru.yandex.practicum.filmorate.model.FriendsTo; +import ru.yandex.practicum.filmorate.model.User; +import ru.yandex.practicum.filmorate.service.ManageFriendsUserService; +import ru.yandex.practicum.filmorate.service.UserService; + +import java.util.List; + +@RestController +public class UserLikeController extends UserController { + + private static final String SERVICE = "ManageFriendsUserService"; + private final ManageFriendsUserService manageFriendsUserService; + + public UserLikeController(@Qualifier(SERVICE) UserService filmService) { + super(filmService); + manageFriendsUserService = (ManageFriendsUserService) filmService; + } + + @PutMapping("/{idUser}/friends/{idFriend}") + public void addToFriends(@PathVariable int idUser, @PathVariable int idFriend) throws NotFoundException { + manageFriendsUserService.addToFriends(new FriendsTo(idUser, idFriend)); + } + + @DeleteMapping("/{idUser}/friends/{idFriend}") + public void deleteFromFriends(@PathVariable int idUser, @PathVariable int idFriend) { + manageFriendsUserService.removeFriends(new FriendsTo(idUser, idFriend)); + } + + @GetMapping("/{idUser}/friends") + public List getFriend(@PathVariable Integer idUser) throws NotFoundException { + return manageFriendsUserService.getFriends(idUser); + } + + @GetMapping("/{idUser}/friends/common/{idFriend}") + public List getCommonFriend(@PathVariable int idUser, @PathVariable int idFriend) throws NotFoundException { + return manageFriendsUserService.getCommonFriends(new FriendsTo(idUser, idFriend)); + } + +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/exception/ControllerException.java b/src/main/java/ru/yandex/practicum/filmorate/exception/ControllerException.java new file mode 100644 index 0000000..14de323 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/exception/ControllerException.java @@ -0,0 +1,28 @@ +package ru.yandex.practicum.filmorate.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import ru.yandex.practicum.filmorate.model.ExceptionEntity; + +@RestControllerAdvice +public class ControllerException { + @ExceptionHandler + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ExceptionEntity handlerValidateException(final ValidateException validateException) { + return new ExceptionEntity("validateException", validateException.getMessage()); + } + + @ExceptionHandler + @ResponseStatus(HttpStatus.NOT_FOUND) + public ExceptionEntity handlerNotFoundException(final NotFoundException notFoundException) { + return new ExceptionEntity("not found exception", notFoundException.getMessage()); + } + + @ExceptionHandler + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public ExceptionEntity handlerException(final Exception exception) { + return new ExceptionEntity("exception", exception.getMessage()); + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/exception/NotFoundException.java b/src/main/java/ru/yandex/practicum/filmorate/exception/NotFoundException.java new file mode 100644 index 0000000..df92130 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/exception/NotFoundException.java @@ -0,0 +1,7 @@ +package ru.yandex.practicum.filmorate.exception; + +public class NotFoundException extends Exception { + public NotFoundException(String message) { + super(message); + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/exception/ValidateException.java b/src/main/java/ru/yandex/practicum/filmorate/exception/ValidateException.java new file mode 100644 index 0000000..4d0b7f3 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/exception/ValidateException.java @@ -0,0 +1,7 @@ +package ru.yandex.practicum.filmorate.exception; + +public class ValidateException extends Exception { + public ValidateException(String message) { + super(message); + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/ExceptionEntity.java b/src/main/java/ru/yandex/practicum/filmorate/model/ExceptionEntity.java new file mode 100644 index 0000000..65da990 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/model/ExceptionEntity.java @@ -0,0 +1,14 @@ +package ru.yandex.practicum.filmorate.model; + +import lombok.AllArgsConstructor; +import lombok.Data; + + +@Data +@AllArgsConstructor +public class ExceptionEntity { + private String error; + private String message; + + +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/FilmLikes.java b/src/main/java/ru/yandex/practicum/filmorate/model/FilmLikes.java new file mode 100644 index 0000000..9a46b72 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/model/FilmLikes.java @@ -0,0 +1,9 @@ +package ru.yandex.practicum.filmorate.model; + + +public class FilmLikes extends FromTo { + + public FilmLikes(Integer filmId, Integer userId) { + super(filmId, userId); + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/FriendsTo.java b/src/main/java/ru/yandex/practicum/filmorate/model/FriendsTo.java new file mode 100644 index 0000000..5d560c4 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/model/FriendsTo.java @@ -0,0 +1,7 @@ +package ru.yandex.practicum.filmorate.model; + +public class FriendsTo extends FromTo { + public FriendsTo(int idUser, int idFriend) { + super(idUser, idFriend); + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/FromTo.java b/src/main/java/ru/yandex/practicum/filmorate/model/FromTo.java new file mode 100644 index 0000000..204282f --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/model/FromTo.java @@ -0,0 +1,12 @@ +package ru.yandex.practicum.filmorate.model; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public abstract class FromTo { + protected Integer from; + protected Integer to; + +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/Genre.java b/src/main/java/ru/yandex/practicum/filmorate/model/Genre.java new file mode 100644 index 0000000..78d8e62 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/model/Genre.java @@ -0,0 +1,14 @@ +package ru.yandex.practicum.filmorate.model; + + +public class Genre extends Model implements Comparable { + public Genre(int id, String name) { + this.setId(id); + this.setName(name); + } + + @Override + public int compareTo(Genre o) { + return this.getId() - o.id; + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/Model.java b/src/main/java/ru/yandex/practicum/filmorate/model/Model.java new file mode 100644 index 0000000..53a595c --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/model/Model.java @@ -0,0 +1,9 @@ +package ru.yandex.practicum.filmorate.model; + +import lombok.Data; + +@Data +public abstract class Model { + protected int id; + protected String name; +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/Mpa.java b/src/main/java/ru/yandex/practicum/filmorate/model/Mpa.java new file mode 100644 index 0000000..d294de2 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/model/Mpa.java @@ -0,0 +1,12 @@ +package ru.yandex.practicum.filmorate.model; + +public class Mpa extends Model { + public Mpa(int id, String name) { + setId(id); + setName(name); + } + + public Mpa(int id) { + setId(id); + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/ManageFriendsUserService.java b/src/main/java/ru/yandex/practicum/filmorate/service/ManageFriendsUserService.java new file mode 100644 index 0000000..6e5fe77 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/service/ManageFriendsUserService.java @@ -0,0 +1,65 @@ +package ru.yandex.practicum.filmorate.service; + +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Service; +import ru.yandex.practicum.filmorate.exception.NotFoundException; +import ru.yandex.practicum.filmorate.model.FriendsTo; +import ru.yandex.practicum.filmorate.model.Model; +import ru.yandex.practicum.filmorate.model.User; +import ru.yandex.practicum.filmorate.storage.StorageUser; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + + +@Service("ManageFriendsUserService") +public class ManageFriendsUserService extends UserService { + protected static final String NEGATIVE_ID_EXCEPTION = "Id пользователя не может быть меньше нуля"; + + public ManageFriendsUserService(@Qualifier(UserService.USER_STORAGE) StorageUser storage) { + super(storage); + } + + public void addToFriends(FriendsTo users) throws NotFoundException { + if (users.getFrom() < 1 || users.getTo() < 1) { + throw new NotFoundException(NEGATIVE_ID_EXCEPTION); + } + storage.addToSet(users); + } + + public void removeFriends(FriendsTo user) { + storage.removeIdFromIdSet(user); + } + + public List getCommonFriends(FriendsTo user) throws NotFoundException { + User user1 = super.getModelById(user.getFrom()); + User user2 = super.getModelById(user.getTo()); + List idFriends = user1.getFriends().stream().filter(e -> user2.getFriends().stream() + .anyMatch(e1 -> Objects.equals(e, e1))).collect(Collectors.toList()); + + return idFriends.stream().map(e -> { + Model model; + try { + model = getModelById(e); + } catch (NotFoundException exception) { + return null; + } + return model; + }) + .collect(Collectors.toList()).stream().map(e -> (User) e) + .collect(Collectors.toList()); + } + + public List getFriends(int id) throws NotFoundException { + User user = super.getModelById(id); + List userList = new ArrayList<>(); + + for (int idUser : user.getFriends()) { + userList.add(super.getModelById(idUser)); + } + + return userList; + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/ManageLikeFilmService.java b/src/main/java/ru/yandex/practicum/filmorate/service/ManageLikeFilmService.java new file mode 100644 index 0000000..6490d93 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/service/ManageLikeFilmService.java @@ -0,0 +1,62 @@ +package ru.yandex.practicum.filmorate.service; + +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.stereotype.Service; +import ru.yandex.practicum.filmorate.exception.NotFoundException; +import ru.yandex.practicum.filmorate.model.Film; +import ru.yandex.practicum.filmorate.model.FilmLikes; +import ru.yandex.practicum.filmorate.storage.StorageFilm; +import ru.yandex.practicum.filmorate.storage.StorageUser; + +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +@Service("ManageLikeFilmService") +public class ManageLikeFilmService extends FilmService { + private final StorageUser storageUser; + + public ManageLikeFilmService(@Qualifier(FilmService.FILM_STORAGE) StorageFilm storage, + @Qualifier(UserService.USER_STORAGE) StorageUser storageUser) { + super(storage); + this.storageUser = storageUser; + } + + public Film addLike(FilmLikes filmLikes) { + return storage.addToSet(filmLikes); + } + + public Film deleteLike(FilmLikes filmLikes) throws NotFoundException { + validateExistUser(filmLikes.getTo()); + + storage.removeIdFromIdSet(filmLikes); + + Film film = (Film) getModelById(filmLikes.getFrom()); + + return film; + } + + public Set getLikes(Integer idFilm) throws NotFoundException { + Film film = (Film) getModelById(idFilm); + Set likes = film.getLikes(); + + return likes; + } + + public List getFilmsPopular(int count) { + List filmList = super.getModelList(); + + return filmList.stream().sorted((e1, e2) -> e2.getLikes().size() - e1.getLikes().size()) + .limit(count).collect(Collectors.toList()); + } + + private boolean validateExistUser(int userId) throws NotFoundException { + try { + storageUser.isExist(userId); + } catch (EmptyResultDataAccessException e) { + throw new NotFoundException(String.format(UserService.NOT_FOUND_ID_EXCEPTION, userId)); + } + return true; + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/Service.java b/src/main/java/ru/yandex/practicum/filmorate/service/Service.java new file mode 100644 index 0000000..f81be5f --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/service/Service.java @@ -0,0 +1,73 @@ +package ru.yandex.practicum.filmorate.service; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.dao.EmptyResultDataAccessException; +import ru.yandex.practicum.filmorate.exception.NotFoundException; +import ru.yandex.practicum.filmorate.exception.ValidateException; +import ru.yandex.practicum.filmorate.model.Film; +import ru.yandex.practicum.filmorate.model.Model; +import ru.yandex.practicum.filmorate.storage.Storage; + +import java.util.*; + +@Slf4j +@RequiredArgsConstructor +public abstract class Service { + public static String NOT_FOUND_ID_EXCEPTION = "модель с требуемым id = %d не найдена"; + + protected final Storage storage; + + public T addModel(T model) throws ValidateException { + try { + validate(model); + if (model instanceof Film) { + Film film = (Film) model; + if (Objects.isNull(film.getGenres())) { + film.setGenres(new TreeSet<>()); + } + + if (Objects.isNull(film.getLikes())) { + film.setLikes(new TreeSet<>()); + } + } + return (T) storage.save(model); + } catch (ValidateException ex) { + log.warn(ex.getMessage()); + throw new ValidateException(ex.getMessage()); + } + } + + public T updateModel(T model) throws NotFoundException { + try { + if (storage.isExist(model.getId())) { + validate(model); + storage.update(model); + + } + } catch (EmptyResultDataAccessException exception) { + log.warn(String.format(NOT_FOUND_ID_EXCEPTION, model.getId())); + throw new NotFoundException(String.format(NOT_FOUND_ID_EXCEPTION, model.getId())); + } catch (ValidateException e) { + throw new RuntimeException(e); + } + + return model; + } + + public List getModelList() { + return new ArrayList((Collection) storage.getModelMap().values()); + } + + protected abstract void validate(T model) throws ValidateException; + + public T getModelById(int id) throws NotFoundException { + try { + return storage.get(id); + } catch (EmptyResultDataAccessException exception) { + throw new NotFoundException(String.format(NOT_FOUND_ID_EXCEPTION, id)); + } + } + + +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/ServiceFilm.java b/src/main/java/ru/yandex/practicum/filmorate/service/ServiceFilm.java new file mode 100644 index 0000000..1782142 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/service/ServiceFilm.java @@ -0,0 +1,44 @@ +package ru.yandex.practicum.filmorate.service; + +import org.springframework.dao.EmptyResultDataAccessException; +import ru.yandex.practicum.filmorate.exception.NotFoundException; +import ru.yandex.practicum.filmorate.model.Film; +import ru.yandex.practicum.filmorate.model.Genre; +import ru.yandex.practicum.filmorate.model.Model; +import ru.yandex.practicum.filmorate.model.Mpa; +import ru.yandex.practicum.filmorate.storage.StorageFilm; + +import java.util.List; + +public abstract class ServiceFilm extends Service { + private static StorageFilm storageFilm; + + public ServiceFilm(StorageFilm storage) { + super(storage); + storageFilm = storage; + } + + public T getGenre(int id) throws NotFoundException { + try { + return storageFilm.getGenreById(id); + } catch (EmptyResultDataAccessException e) { + throw new NotFoundException("Genre не найден"); + } + } + + public List getGenreList() { + return (List) storageFilm.getGenreList(); + } + + public T getMpa(int id) throws NotFoundException { + try { + return storageFilm.getMpa(id); + } catch (EmptyResultDataAccessException e) { + throw new NotFoundException("mpa не айден"); + } + } + + public List getMpaList() { + return (List) storageFilm.getMpaList(); + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/ServiceUser.java b/src/main/java/ru/yandex/practicum/filmorate/service/ServiceUser.java new file mode 100644 index 0000000..c8949c2 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/service/ServiceUser.java @@ -0,0 +1,16 @@ +package ru.yandex.practicum.filmorate.service; + +import lombok.extern.slf4j.Slf4j; +import ru.yandex.practicum.filmorate.model.User; +import ru.yandex.practicum.filmorate.storage.StorageUser; + +@Slf4j +public abstract class ServiceUser extends Service { + + protected ServiceUser(StorageUser storage) { + super(storage); + NOT_FOUND_ID_EXCEPTION = "Пользователь с требуемым id не найден"; + } + + +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/FilmDBStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/FilmDBStorage.java new file mode 100644 index 0000000..b460d97 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/FilmDBStorage.java @@ -0,0 +1,234 @@ +package ru.yandex.practicum.filmorate.storage; + +import lombok.AllArgsConstructor; +import org.springframework.jdbc.core.BatchPreparedStatementSetter; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.support.GeneratedKeyHolder; +import org.springframework.jdbc.support.KeyHolder; +import org.springframework.stereotype.Repository; +import ru.yandex.practicum.filmorate.model.*; + +import java.sql.Date; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.*; + +@Repository("FilmDBStorage") +@AllArgsConstructor +public class FilmDBStorage implements StorageFilm { + private JdbcTemplate jdbcTemplate; + + @Override + public boolean isExist(int id) { + return get(id) != null; + } + + @Override + public void update(Model model) { + Film film = (Film) model; + String sql = "UPDATE film SET title = ?, description = ?, release_date = ?, duration = ?, mpa_id = ? WHERE film_id = ?"; + + jdbcTemplate.update(sql, film.getName(), film.getDescription(), film.getReleaseDate(), film.getDuration(), + film.getMpa().getId(), film.getId()); + updateGenres(film); + updateLikes(film); + } + + @Override + public Model save(Model model) { + Film film = (Film) model; + String sql = "INSERT INTO film (title, description, release_date, duration, mpa_id) VALUES (?,?,?,?,?)"; + KeyHolder keyHolder = new GeneratedKeyHolder(); + + + jdbcTemplate.update(connection -> { + PreparedStatement stm = connection.prepareStatement(sql, new String[]{"film_id"}); + stm.setString(1, film.getName()); + stm.setString(2, film.getDescription()); + stm.setDate(3, Date.valueOf(film.getReleaseDate())); + stm.setInt(4, film.getDuration()); + stm.setInt(5, film.getMpa().getId()); + + return stm; + }, keyHolder); + film.setId(keyHolder.getKey().intValue()); + saveGenres(film); + updateLikes(film); + + + return model; + } + + private void saveGenres(Film film) { + + String sqlGenre = "INSERT INTO genre_film (genre_id, film_id) VALUES (?,?)"; + Iterator iterator = film.getGenres().iterator(); + + jdbcTemplate.batchUpdate(sqlGenre, new BatchPreparedStatementSetter() { + @Override + public void setValues(PreparedStatement ps, int i) throws SQLException { + ps.setInt(2, film.getId()); + ps.setInt(1, iterator.next().getId()); + } + + @Override + public int getBatchSize() { + return film.getGenres().size(); + } + }); + } + + private void updateLikes(Film film) { + String sqlDelGenre = "DELETE FROM likes_film WHERE film_id = ?"; + + jdbcTemplate.update(sqlDelGenre, film.getId()); + + String sqlGenre = "INSERT INTO likes_film (user_id, film_id) VALUES (?,?)"; + Iterator iterator = film.getLikes().iterator(); + jdbcTemplate.batchUpdate(sqlGenre, new BatchPreparedStatementSetter() { + @Override + public void setValues(PreparedStatement ps, int i) throws SQLException { + ps.setInt(2, film.getId()); + ps.setInt(1, iterator.next()); + } + + @Override + public int getBatchSize() { + return film.getLikes().size(); + } + }); + } + + private void updateGenres(Film film) { + String sqlDelGenre = "DELETE FROM genre_film WHERE film_id = ?"; + + jdbcTemplate.update(sqlDelGenre, film.getId()); + + String sqlGenre = "INSERT INTO genre_film (genre_id, film_id) VALUES (?,?)"; + Iterator iterator = film.getGenres().iterator(); + + jdbcTemplate.batchUpdate(sqlGenre, new BatchPreparedStatementSetter() { + @Override + public void setValues(PreparedStatement ps, int i) throws SQLException { + ps.setInt(2, film.getId()); + ps.setInt(1, iterator.next().getId()); + } + + @Override + public int getBatchSize() { + return film.getGenres().size(); + } + }); + } + + @Override + public Film get(int id) { + String sqlNew = "SELECT f.film_id, f.title, f.description, f.release_date, f.duration, f.mpa_id, g.genre_id, g.name, lf.user_id\n" + + "FROM genre as g \n" + + "left JOIN genre_film as gf on gf.genre_id = g.genre_id \n" + + "right JOIN film AS f ON f.film_id = gf.film_id \n" + + "left JOIN likes_film AS lf ON lf.film_id = f.film_id \n" + + "WHERE f.film_id = ?"; + Film film = jdbcTemplate.queryForObject(sqlNew, this::mapRowToFilm, id); + + return film; + } + + private Film mapRowToFilm(ResultSet rs, int rowNum) throws SQLException { + TreeSet filmGenre = new TreeSet<>(); + TreeSet likes = new TreeSet<>(); + + Film film = Film.builder().id(rs.getInt("film_id")) + .name(rs.getString("title")) + .description(rs.getString("description")) + .genres(filmGenre) + .duration(rs.getInt("duration")) + .releaseDate(rs.getDate("release_date").toLocalDate()) + .mpa(getMpa(rs.getInt("mpa_id"))) + .likes(likes).build(); + + do { + if (rs.getInt("genre_id") != 0) { + filmGenre.add(new Genre(rs.getInt("genre_id"), rs.getString("name"))); + } + + if (rs.getInt("user_id") != 0) { + likes.add(rs.getInt("user_id")); + } + + } while (rs.next()); + + return film; + } + + + @Override + public void delete(int id) { + String sql = "DELETE FROM film WHERE film_id = ?"; + + jdbcTemplate.update(sql, id); + } + + @Override + public Map getModelMap() { + String sql1 = "SELECT film_id FROM FILM"; + Map filmMap = new HashMap<>(); + List idsList = jdbcTemplate.query(sql1, (rs, nm) -> rs.getInt("film_id")); + + for (int id : idsList) { + filmMap.put(id, get(id)); + } + + return filmMap; + } + + @Override + public void removeIdFromIdSet(FromTo user) { + String sql = "DELETE FROM likes_film WHERE film_id = ? AND user_id = ?"; + + jdbcTemplate.update(sql, user.getFrom(), user.getTo()); + + } + + @Override + public T addToSet(FromTo filmLikes) { + String sql = "INSERT INTO likes_film (film_id, user_id) VALUES (?,?)"; + + jdbcTemplate.update(sql, filmLikes.getFrom(), filmLikes.getTo()); + + return null; + } + + @Override + public Genre getGenreById(int id) { + String sql = "SELECT * FROM genre WHERE genre_id = ?"; + + return jdbcTemplate.queryForObject(sql, + (rs, rowNum) -> new Genre(rs.getInt("genre_id"), rs.getString("name")), id); + } + + @Override + public List getGenreList() { + String sql = "SELECT * FROM genre "; + + return jdbcTemplate.query(sql, + ((rs, rowNum) -> new Genre(rs.getInt("genre_id"), rs.getString("name")))); + } + + @Override + public Mpa getMpa(int id) { + String sql = "SELECT * FROM MPA WHERE mpa_id = ?"; + + return jdbcTemplate.queryForObject(sql, + (rs, rowNum) -> new Mpa(rs.getInt("mpa_id"), rs.getString("name")), id); + } + + @Override + public List getMpaList() { + String sql = "SELECT * FROM MPA"; + + return jdbcTemplate.query(sql, (rs, rowNum) -> new Mpa(rs.getInt("mpa_id"), + rs.getString("name"))); + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/Storage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/Storage.java new file mode 100644 index 0000000..b3c576d --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/Storage.java @@ -0,0 +1,27 @@ +package ru.yandex.practicum.filmorate.storage; + +import ru.yandex.practicum.filmorate.model.FromTo; +import ru.yandex.practicum.filmorate.model.Model; + +import java.util.Map; + +public interface Storage { + + boolean isExist(int id); + + void update(Model model); + + Model save(Model model); + + T get(int id); + + void delete(int id); + + Map getModelMap(); + + void removeIdFromIdSet(FromTo user); + + T addToSet(FromTo filmLikes); + + +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/StorageFilm.java b/src/main/java/ru/yandex/practicum/filmorate/storage/StorageFilm.java new file mode 100644 index 0000000..151d09d --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/StorageFilm.java @@ -0,0 +1,15 @@ +package ru.yandex.practicum.filmorate.storage; + +import ru.yandex.practicum.filmorate.model.Model; + +import java.util.List; + +public interface StorageFilm extends Storage { + T getGenreById(int id); + + List getGenreList(); + + T getMpa(int id); + + List getMpaList(); +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/StorageUser.java b/src/main/java/ru/yandex/practicum/filmorate/storage/StorageUser.java new file mode 100644 index 0000000..0173ece --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/StorageUser.java @@ -0,0 +1,6 @@ +package ru.yandex.practicum.filmorate.storage; + + +public interface StorageUser extends Storage { + +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/UserDBStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/UserDBStorage.java new file mode 100644 index 0000000..7ddc69a --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/UserDBStorage.java @@ -0,0 +1,122 @@ +package ru.yandex.practicum.filmorate.storage; + +import lombok.AllArgsConstructor; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.support.GeneratedKeyHolder; +import org.springframework.jdbc.support.KeyHolder; +import org.springframework.stereotype.Repository; +import ru.yandex.practicum.filmorate.model.FromTo; +import ru.yandex.practicum.filmorate.model.Model; +import ru.yandex.practicum.filmorate.model.User; + +import java.sql.Date; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Repository("userDBStorage") +@AllArgsConstructor +public class UserDBStorage implements StorageUser { + private JdbcTemplate jdbcTemplate; + + @Override + public boolean isExist(int id) { + return get(id) != null; + } + + @Override + public void update(Model model) { + User user = (User) model; + String sql = "UPDATE users SET name = ?, birth_date = ?, email = ?, login = ? WHERE user_id = ?"; + jdbcTemplate.update(sql, user.getName(), user.getBirthday(), user.getEmail(), user.getLogin(), user.getId()); + } + + @Override + public User save(Model model) { + User user = (User) model; + String sql = ("INSERT INTO users (name, birth_date, email, login) VALUES (?,?,?,?)"); + KeyHolder keyHolder = new GeneratedKeyHolder(); + + jdbcTemplate.update(con -> { + PreparedStatement stmt = con.prepareStatement(sql, new String[]{"user_id"}); + stmt.setString(1, user.getName()); + stmt.setDate(2, Date.valueOf(user.getBirthday())); + stmt.setString(3, user.getEmail()); + stmt.setString(4, user.getLogin()); + return stmt; + }, keyHolder); + user.setId(keyHolder.getKey().intValue()); + return user; + } + + @Override + public User get(int id) { + String sqlUserLikes = "SELECT u.user_id, u.name, u.email, u.login, u.birth_date, fr.friend_id\n" + + "FROM users as u\n" + + "LEFT JOIN friend as fr ON fr.user_id = u.user_id\n" + + "WHERE u.user_id = ?\n"; + ; + User user = jdbcTemplate.queryForObject(sqlUserLikes, this::rowMapperUser, id); + + return user; + + } + + private Integer mapRowToInteger(ResultSet rs, int rowNum) throws SQLException { + return rs.getInt("friend_id"); + } + + private User rowMapperUser(ResultSet rs, int idRow) throws SQLException { + List likes = new ArrayList<>(); + User user = User.builder().id(rs.getInt("user_id")) + .name(rs.getString("name")) + .email(rs.getString("email")) + .login(rs.getString("login")) + .birthday(rs.getDate("birth_date").toLocalDate()) + .friends(likes).build(); + do { + if (rs.getInt("friend_id") != 0) { + likes.add(rs.getInt("friend_id")); + } + } while (rs.next()); + return user; + } + + @Override + public void delete(int id) { + String sql = "DELETE FROM users WHERE user_id = ?"; + jdbcTemplate.update(sql, id); + + } + + @Override + public Map getModelMap() { + String sql = "SELECT user_id FROM users"; + Map mapUsers = new HashMap<>(); + List usersIdList = jdbcTemplate.query(sql, (rs, r) -> rs.getInt("user_id")); + + for (int id : usersIdList) { + mapUsers.put(id, get(id)); + } + + return mapUsers; + } + + @Override + public void removeIdFromIdSet(FromTo user) { + String sql = "DELETE FROM friend WHERE user_id = ? AND friend_id = ?"; + jdbcTemplate.update(sql, user.getFrom(), user.getTo()); + } + + @Override + public Model addToSet(FromTo userFriend) { + String sql = "INSERT INTO friend (user_id, friend_id, IS_APPROVED) VALUES (?,?,?);"; + jdbcTemplate.update(sql, userFriend.getFrom(), userFriend.getTo(), true); + return null; + } + +} diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql new file mode 100644 index 0000000..2634c74 --- /dev/null +++ b/src/main/resources/data.sql @@ -0,0 +1,12 @@ +INSERT INTO genre (name) VALUES ('Комедия'); +INSERT INTO genre (name) VALUES ('Драма'); +INSERT INTO genre (name) VALUES ('Мультфильм'); +INSERT INTO genre (name) VALUES ('Триллер'); +INSERT INTO genre (name) VALUES ('Документальный'); +INSERT INTO genre (name) VALUES ('Боевик'); + +INSERT INTO MPA (NAME) VALUES ('G'); +INSERT INTO MPA (NAME) VALUES ('PG'); +INSERT INTO MPA (NAME) VALUES ('PG-13'); +INSERT INTO MPA (NAME) VALUES ('R'); +INSERT INTO MPA (NAME) VALUES ('NC-17'); \ No newline at end of file diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql new file mode 100644 index 0000000..8bf0ed5 --- /dev/null +++ b/src/main/resources/schema.sql @@ -0,0 +1,43 @@ +create TABLE IF NOT EXISTS users ( + user_id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + name VARCHAR(20), + birth_date DATE, + email VARCHAR(40), + login VARCHAR(20) +); + +create TABLE IF NOT EXISTS MPA ( + mpa_id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + name varchar +); + +create TABLE IF NOT EXISTS film ( + film_id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + title varchar(100), + description varchar(255), + release_date date, + duration integer, + mpa_id integer REFERENCES MPA (mpa_id) ON delete CASCADE +); + +create TABLE IF NOT EXISTS friend ( + user_id INTEGER REFERENCES USERS (user_id) ON delete CASCADE, + is_approved BIT, + friend_id INTEGER REFERENCES USERS (user_id) ON delete CASCADE +); + +create TABLE IF NOT EXISTS genre ( + genre_id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + name VARCHAR(100) +); + +create TABLE IF NOT EXISTS genre_film ( + genre_id INTEGER REFERENCES genre (genre_id) ON delete CASCADE, + film_id INTEGER REFERENCES film (film_id) ON delete CASCADE +); + +create TABLE IF NOT EXISTS likes_film( + film_id INTEGER REFERENCES film(film_id) ON delete CASCADE, + user_id INTEGER REFERENCES users(user_id) ON delete CASCADE +); + diff --git a/src/test/java/ru/yandex/practicum/filmorate/controller/FilmControllerTest.java b/src/test/java/ru/yandex/practicum/filmorate/controller/FilmControllerTest.java new file mode 100644 index 0000000..4b9c88c --- /dev/null +++ b/src/test/java/ru/yandex/practicum/filmorate/controller/FilmControllerTest.java @@ -0,0 +1,42 @@ +package ru.yandex.practicum.filmorate.controller; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import ru.yandex.practicum.filmorate.exception.NotFoundException; +import ru.yandex.practicum.filmorate.exception.ValidateException; +import ru.yandex.practicum.filmorate.model.Film; +import ru.yandex.practicum.filmorate.service.ManageLikeFilmService; +import ru.yandex.practicum.filmorate.storage.InMemoryFilmStorage; +import ru.yandex.practicum.filmorate.storage.InMemoryUserStorage; + +import java.time.LocalDate; +import java.util.HashMap; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class FilmControllerTest { + private FilmController filmController; + + @BeforeEach + void setUp() { + filmController = new FilmController(new ManageLikeFilmService(new InMemoryFilmStorage(new HashMap<>()), + new InMemoryUserStorage(new HashMap<>()))); + } + + @Test + void add() throws ValidateException, NotFoundException { + Film film = Film.builder() + .description("description") + .duration(60) + .releaseDate(LocalDate.of(2022, 02, 23)).build(); + + assertThrows(ValidateException.class, () -> filmController.add(film)); + film.setName("film1"); + Film film1 = filmController.add(film); + assertEquals(1, filmController.getModelList().size()); + film1.setName("FilmVasya"); + filmController.update(film1); + assertEquals("FilmVasya", ((Film) filmController.getModelList().get(0)).getName()); + } +} \ No newline at end of file diff --git a/src/test/java/ru/yandex/practicum/filmorate/controller/UserControllerTest.java b/src/test/java/ru/yandex/practicum/filmorate/controller/UserControllerTest.java new file mode 100644 index 0000000..1ae22ee --- /dev/null +++ b/src/test/java/ru/yandex/practicum/filmorate/controller/UserControllerTest.java @@ -0,0 +1,37 @@ +package ru.yandex.practicum.filmorate.controller; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import ru.yandex.practicum.filmorate.exception.NotFoundException; +import ru.yandex.practicum.filmorate.exception.ValidateException; +import ru.yandex.practicum.filmorate.model.User; +import ru.yandex.practicum.filmorate.service.ManageFriendsUserService; +import ru.yandex.practicum.filmorate.storage.InMemoryUserStorage; + +import java.time.LocalDate; +import java.util.HashMap; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class UserControllerTest { + private UserController userController; + + @BeforeEach + void setUp() { + userController = new UserController(new ManageFriendsUserService(new InMemoryUserStorage(new HashMap<>()))); + } + + @Test + void add() throws ValidateException, NotFoundException { + User user = User.builder() + .email("my@email.ru") + .login("login1") + .birthday(LocalDate.of(1994, 6, 12)).build(); + + User user1 = userController.add(user); + assertEquals(1, userController.getModelList().size()); + user1.setName("Vasya"); + userController.update(user1); + assertEquals("Vasya", ((User) userController.getModelList().get(0)).getName()); + } +} \ No newline at end of file diff --git a/src/test/java/ru/yandex/practicum/filmorate/service/FilmServiceTest.java b/src/test/java/ru/yandex/practicum/filmorate/service/FilmServiceTest.java new file mode 100644 index 0000000..0f15249 --- /dev/null +++ b/src/test/java/ru/yandex/practicum/filmorate/service/FilmServiceTest.java @@ -0,0 +1,42 @@ +package ru.yandex.practicum.filmorate.service; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import ru.yandex.practicum.filmorate.exception.ValidateException; +import ru.yandex.practicum.filmorate.model.Film; +import ru.yandex.practicum.filmorate.storage.InMemoryFilmStorage; + +import java.time.LocalDate; +import java.util.HashMap; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +class FilmServiceTest { + private FilmService filmService; + + @BeforeEach + void setUp() { + filmService = new FilmService(new InMemoryFilmStorage(new HashMap<>())); + } + + @Test + void addFilm() { + Film film = Film.builder().description("description") + .duration(60) + .releaseDate(LocalDate.of(2022, 02, 23)) + .build(); + + assertThrows(ValidateException.class, () -> filmService.addModel(film)); + film.setName(" "); + assertThrows(ValidateException.class, () -> filmService.addModel(film)); + film.setDescription(" wqeqwrwefsdgsdevygfghdfgsdrthdfhdfgsgsdgdsgdgfdfgdfggggggfdffhljhjlhkjlhiuhiulhjlknjhkjdsafjdfasofuawejflkasjfls;jfiasojfeiajs;fe;kasjdfksjfdiosjfisjfsidhgdfgskdjfksdjfjsif fjs kfj sdkfjas;lfd ;sjfkas"); + film.setName("film1"); + assertThrows(ValidateException.class, () -> filmService.addModel(film)); + film.setDescription("description"); + film.setReleaseDate(LocalDate.of(1895, 12, 25)); + assertThrows(ValidateException.class, () -> filmService.addModel(film)); + film.setReleaseDate(LocalDate.of(2022, 02, 23)); + film.setDuration(-1); + assertThrows(ValidateException.class, () -> filmService.addModel(film)); + } +} \ No newline at end of file diff --git a/src/test/java/ru/yandex/practicum/filmorate/service/ManageFriendsToServiceTest.java b/src/test/java/ru/yandex/practicum/filmorate/service/ManageFriendsToServiceTest.java new file mode 100644 index 0000000..9bc06c2 --- /dev/null +++ b/src/test/java/ru/yandex/practicum/filmorate/service/ManageFriendsToServiceTest.java @@ -0,0 +1,51 @@ +package ru.yandex.practicum.filmorate.service; + +import org.junit.jupiter.api.Test; +import ru.yandex.practicum.filmorate.exception.NotFoundException; +import ru.yandex.practicum.filmorate.exception.ValidateException; +import ru.yandex.practicum.filmorate.model.FriendsTo; +import ru.yandex.practicum.filmorate.model.User; +import ru.yandex.practicum.filmorate.storage.InMemoryUserStorage; +import ru.yandex.practicum.filmorate.storage.StorageUser; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.HashMap; + +public class ManageFriendsToServiceTest { + private final StorageUser userStorage = new InMemoryUserStorage(new HashMap<>()); + private final UserService userService = new UserService(userStorage); + private final ManageFriendsUserService manageFriendsUserService = new ManageFriendsUserService(userStorage); + + @Test + public void addFriend() throws ValidateException, NotFoundException { + User user = User.builder() + .birthday(LocalDate.of(1994, 6, 12)) + .email("my@email.ru") + .login("vasyaPupkin") + .friends(new ArrayList<>()) + .build(); + + User user1 = User.builder() + .birthday(LocalDate.of(1994, 6, 12)) + .email("123my@email.ru") + .login("vasislisaPupkina") + .friends(new ArrayList<>()) + .build(); + + User user3 = User.builder() + .birthday(LocalDate.of(1994, 6, 12)) + .email("123@email.ru") + .login("vasisPupkin") + .friends(new ArrayList<>()) + .build(); + + manageFriendsUserService.addToFriends(new FriendsTo(userService.addModel(user3).getId(), userService.addModel(user).getId())); + manageFriendsUserService.addToFriends(new FriendsTo(userService.addModel(user1).getId(), user3.getId())); + + + manageFriendsUserService.addToFriends(new FriendsTo(userService.addModel(user1).getId(), userService.addModel(user).getId())); + System.out.println(user1.getFriends()); + System.out.println(manageFriendsUserService.getCommonFriends(new FriendsTo(user.getId(), user1.getId()))); + } +} diff --git a/src/test/java/ru/yandex/practicum/filmorate/service/UserServiceTest.java b/src/test/java/ru/yandex/practicum/filmorate/service/UserServiceTest.java new file mode 100644 index 0000000..177d1ea --- /dev/null +++ b/src/test/java/ru/yandex/practicum/filmorate/service/UserServiceTest.java @@ -0,0 +1,38 @@ +package ru.yandex.practicum.filmorate.service; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import ru.yandex.practicum.filmorate.exception.ValidateException; +import ru.yandex.practicum.filmorate.model.User; +import ru.yandex.practicum.filmorate.storage.InMemoryUserStorage; + +import java.time.LocalDate; +import java.util.HashMap; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class UserServiceTest { + private Service userService; + + @BeforeEach + void setUp() { + userService = new UserService((new InMemoryUserStorage(new HashMap<>()))); + } + + @Test + void addUser() throws ValidateException { + User user = User.builder() + .email("my@email.ru") + .login("vasya pupkin") + .birthday(LocalDate.of(1994, 6, 12)) + .build(); + + assertThrows(ValidateException.class, () -> userService.addModel(user)); + user.setLogin("vasyaPupkin"); + userService.addModel(user); + assertEquals(user.getLogin(), user.getName()); + user.setBirthday(LocalDate.of(2024, 2, 23)); + assertThrows(ValidateException.class, () -> userService.addModel(user)); + } +} \ No newline at end of file diff --git a/src/test/java/ru/yandex/practicum/filmorate/storage/FilmDBStorageTest.java b/src/test/java/ru/yandex/practicum/filmorate/storage/FilmDBStorageTest.java new file mode 100644 index 0000000..ffa3eb6 --- /dev/null +++ b/src/test/java/ru/yandex/practicum/filmorate/storage/FilmDBStorageTest.java @@ -0,0 +1,85 @@ +package ru.yandex.practicum.filmorate.storage; + +import lombok.RequiredArgsConstructor; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.dao.EmptyResultDataAccessException; +import ru.yandex.practicum.filmorate.model.*; + +import java.time.LocalDate; +import java.util.Collections; +import java.util.Objects; +import java.util.TreeSet; + +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest +@AutoConfigureTestDatabase +@RequiredArgsConstructor(onConstructor_ = @Autowired) +class FilmDBStorageTest { + + private final FilmDBStorage storageFilm; + private final UserDBStorage userDBStorage; + TreeSet genreTreeSet = new TreeSet<>(); + TreeSet likes = new TreeSet<>(); + + User user = User.builder().name("user").email("user@mail.ru").login("userLogin").birthday(LocalDate.of(1993, 2, 12)).build(); + Film film = Film.builder().name("name").genres(genreTreeSet).description("description") + .releaseDate(LocalDate.of(2021, 3, 23)).duration(45).mpa(new Mpa(1)) + .likes(likes).build(); + Film film2 = Film.builder().name("name2").genres(genreTreeSet).description("description2") + .releaseDate(LocalDate.of(2021, 3, 23)).duration(45).mpa(new Mpa(1)) + .likes(likes).build(); + + + @Test + public void save() { + Collections.addAll(genreTreeSet, new Genre(1, null), new Genre(3, null)); + Collections.addAll(likes, 1); + userDBStorage.save(user); + storageFilm.save(film); + storageFilm.save(film2); + + Film filmGet = storageFilm.get(1); + + assertEquals(1, filmGet.getId()); + assertEquals(2, storageFilm.get(2).getId()); + storageFilm.delete(1); + assertThrows(EmptyResultDataAccessException.class, () -> storageFilm.get(1)); + assertTrue(storageFilm.get(2).getLikes().stream().anyMatch(e -> Objects.equals(e, 1))); + storageFilm.removeIdFromIdSet(new FilmLikes(2, 1)); + assertEquals(0, storageFilm.get(2).getLikes().size()); + + } + + @Test + public void update() { + storageFilm.save(film); + + Film updateWithName = Film.builder().name("nameUpdate").genres(genreTreeSet).description("descriptionUpdate") + .releaseDate(LocalDate.of(2021, 3, 23)).duration(45).mpa(new Mpa(1)) + .likes(likes).id(1).build(); + + storageFilm.update(updateWithName); + + Film updateGet = storageFilm.get(1); + + assertEquals(updateGet.getName(), "nameUpdate"); + + Collections.addAll(genreTreeSet, new Genre(2, null)); + + storageFilm.update(updateWithName); + assertTrue(storageFilm.get(1).getGenres().stream().anyMatch(e -> Objects.equals(e.getId(), 2))); + } + + @Test + void getMpaGenres() { + assertEquals(1, storageFilm.getMpa(1).getId()); + assertEquals(5, storageFilm.getMpaList().size()); + assertEquals(1, storageFilm.getGenreById(1).getId()); + assertEquals(6, storageFilm.getGenreList().size()); + + } +} \ No newline at end of file diff --git a/src/test/java/ru/yandex/practicum/filmorate/storage/UserDBStorageTest.java b/src/test/java/ru/yandex/practicum/filmorate/storage/UserDBStorageTest.java new file mode 100644 index 0000000..b084625 --- /dev/null +++ b/src/test/java/ru/yandex/practicum/filmorate/storage/UserDBStorageTest.java @@ -0,0 +1,55 @@ +package ru.yandex.practicum.filmorate.storage; + +import lombok.RequiredArgsConstructor; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.dao.EmptyResultDataAccessException; +import ru.yandex.practicum.filmorate.model.User; + +import java.time.LocalDate; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@SpringBootTest +@AutoConfigureTestDatabase +@RequiredArgsConstructor(onConstructor_ = @Autowired) +class UserDBStorageTest { + + private final UserDBStorage userStorage; + + @Test + public void save() { + User user1 = User.builder() + .name("test") + .email("testEmail") + .login("testlogin") + .birthday(LocalDate.of(1994, 2, 1)) + .build(); + + userStorage.save(user1); + Optional userOptional = Optional.of(userStorage.get(1)); + + assertThat(userOptional) + .isPresent() + .hasValueSatisfying(user -> + assertThat(user).hasFieldOrPropertyWithValue("id", 1) + ); + + user1.setId(1); + user1.setName("Update"); + userStorage.update(user1); + + User userUp = userStorage.get(1); + assertEquals("Update", userUp.getName()); + + userStorage.delete(1); + assertThrows(EmptyResultDataAccessException.class, () -> userStorage.get(1)); + + } + +} \ No newline at end of file From fbf36a62c22543739caec35175dd0141e40f9190 Mon Sep 17 00:00:00 2001 From: maliwahn Date: Fri, 1 Mar 2024 06:05:41 +0300 Subject: [PATCH 10/11] =?UTF-8?q?=D0=BF=D0=B5=D1=80=D0=B5=D0=BD=D0=BE?= =?UTF-8?q?=D1=81=20=D1=84=D0=B0=D0=B9=D0=BB=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../filmorate/FilmorateApplicationTests.java | 0 .../controller/FilmControllerTest.java | 6 ++--- .../controller/UserControllerTest.java | 4 ++-- .../filmorate/service/FilmServiceTest.java | 10 ++++----- .../service/ManageFriendsToServiceTest.java | 0 .../filmorate/service/UserServiceTest.java | 6 ++--- .../filmorate/storage/FilmDBStorageTest.java | 22 +++++++++---------- .../filmorate/storage/UserDBStorageTest.java | 8 +++---- 8 files changed, 28 insertions(+), 28 deletions(-) rename src/test/{java/ru/yandex/practicum => }/filmorate/FilmorateApplicationTests.java (100%) rename src/test/{java/ru/yandex/practicum => }/filmorate/controller/FilmControllerTest.java (83%) rename src/test/{java/ru/yandex/practicum => }/filmorate/controller/UserControllerTest.java (86%) rename src/test/{java/ru/yandex/practicum => }/filmorate/service/FilmServiceTest.java (73%) rename src/test/{java/ru/yandex/practicum => }/filmorate/service/ManageFriendsToServiceTest.java (100%) rename src/test/{java/ru/yandex/practicum => }/filmorate/service/UserServiceTest.java (80%) rename src/test/{java/ru/yandex/practicum => }/filmorate/storage/FilmDBStorageTest.java (73%) rename src/test/{java/ru/yandex/practicum => }/filmorate/storage/UserDBStorageTest.java (83%) diff --git a/src/test/java/ru/yandex/practicum/filmorate/FilmorateApplicationTests.java b/src/test/filmorate/FilmorateApplicationTests.java similarity index 100% rename from src/test/java/ru/yandex/practicum/filmorate/FilmorateApplicationTests.java rename to src/test/filmorate/FilmorateApplicationTests.java diff --git a/src/test/java/ru/yandex/practicum/filmorate/controller/FilmControllerTest.java b/src/test/filmorate/controller/FilmControllerTest.java similarity index 83% rename from src/test/java/ru/yandex/practicum/filmorate/controller/FilmControllerTest.java rename to src/test/filmorate/controller/FilmControllerTest.java index 4b9c88c..1029b41 100644 --- a/src/test/java/ru/yandex/practicum/filmorate/controller/FilmControllerTest.java +++ b/src/test/filmorate/controller/FilmControllerTest.java @@ -31,12 +31,12 @@ void add() throws ValidateException, NotFoundException { .duration(60) .releaseDate(LocalDate.of(2022, 02, 23)).build(); - assertThrows(ValidateException.class, () -> filmController.add(film)); + Assertions.assertThrows(ValidateException.class, () -> filmController.add(film)); film.setName("film1"); Film film1 = filmController.add(film); - assertEquals(1, filmController.getModelList().size()); + Assertions.assertEquals(1, filmController.getModelList().size()); film1.setName("FilmVasya"); filmController.update(film1); - assertEquals("FilmVasya", ((Film) filmController.getModelList().get(0)).getName()); + Assertions.assertEquals("FilmVasya", ((Film) filmController.getModelList().get(0)).getName()); } } \ No newline at end of file diff --git a/src/test/java/ru/yandex/practicum/filmorate/controller/UserControllerTest.java b/src/test/filmorate/controller/UserControllerTest.java similarity index 86% rename from src/test/java/ru/yandex/practicum/filmorate/controller/UserControllerTest.java rename to src/test/filmorate/controller/UserControllerTest.java index 1ae22ee..8c1d953 100644 --- a/src/test/java/ru/yandex/practicum/filmorate/controller/UserControllerTest.java +++ b/src/test/filmorate/controller/UserControllerTest.java @@ -29,9 +29,9 @@ void add() throws ValidateException, NotFoundException { .birthday(LocalDate.of(1994, 6, 12)).build(); User user1 = userController.add(user); - assertEquals(1, userController.getModelList().size()); + Assertions.assertEquals(1, userController.getModelList().size()); user1.setName("Vasya"); userController.update(user1); - assertEquals("Vasya", ((User) userController.getModelList().get(0)).getName()); + Assertions.assertEquals("Vasya", ((User) userController.getModelList().get(0)).getName()); } } \ No newline at end of file diff --git a/src/test/java/ru/yandex/practicum/filmorate/service/FilmServiceTest.java b/src/test/filmorate/service/FilmServiceTest.java similarity index 73% rename from src/test/java/ru/yandex/practicum/filmorate/service/FilmServiceTest.java rename to src/test/filmorate/service/FilmServiceTest.java index 0f15249..0cccdba 100644 --- a/src/test/java/ru/yandex/practicum/filmorate/service/FilmServiceTest.java +++ b/src/test/filmorate/service/FilmServiceTest.java @@ -26,17 +26,17 @@ void addFilm() { .releaseDate(LocalDate.of(2022, 02, 23)) .build(); - assertThrows(ValidateException.class, () -> filmService.addModel(film)); + Assertions.assertThrows(ValidateException.class, () -> filmService.addModel(film)); film.setName(" "); - assertThrows(ValidateException.class, () -> filmService.addModel(film)); + Assertions.assertThrows(ValidateException.class, () -> filmService.addModel(film)); film.setDescription(" wqeqwrwefsdgsdevygfghdfgsdrthdfhdfgsgsdgdsgdgfdfgdfggggggfdffhljhjlhkjlhiuhiulhjlknjhkjdsafjdfasofuawejflkasjfls;jfiasojfeiajs;fe;kasjdfksjfdiosjfisjfsidhgdfgskdjfksdjfjsif fjs kfj sdkfjas;lfd ;sjfkas"); film.setName("film1"); - assertThrows(ValidateException.class, () -> filmService.addModel(film)); + Assertions.assertThrows(ValidateException.class, () -> filmService.addModel(film)); film.setDescription("description"); film.setReleaseDate(LocalDate.of(1895, 12, 25)); - assertThrows(ValidateException.class, () -> filmService.addModel(film)); + Assertions.assertThrows(ValidateException.class, () -> filmService.addModel(film)); film.setReleaseDate(LocalDate.of(2022, 02, 23)); film.setDuration(-1); - assertThrows(ValidateException.class, () -> filmService.addModel(film)); + Assertions.assertThrows(ValidateException.class, () -> filmService.addModel(film)); } } \ No newline at end of file diff --git a/src/test/java/ru/yandex/practicum/filmorate/service/ManageFriendsToServiceTest.java b/src/test/filmorate/service/ManageFriendsToServiceTest.java similarity index 100% rename from src/test/java/ru/yandex/practicum/filmorate/service/ManageFriendsToServiceTest.java rename to src/test/filmorate/service/ManageFriendsToServiceTest.java diff --git a/src/test/java/ru/yandex/practicum/filmorate/service/UserServiceTest.java b/src/test/filmorate/service/UserServiceTest.java similarity index 80% rename from src/test/java/ru/yandex/practicum/filmorate/service/UserServiceTest.java rename to src/test/filmorate/service/UserServiceTest.java index 177d1ea..b9de37a 100644 --- a/src/test/java/ru/yandex/practicum/filmorate/service/UserServiceTest.java +++ b/src/test/filmorate/service/UserServiceTest.java @@ -28,11 +28,11 @@ void addUser() throws ValidateException { .birthday(LocalDate.of(1994, 6, 12)) .build(); - assertThrows(ValidateException.class, () -> userService.addModel(user)); + Assertions.assertThrows(ValidateException.class, () -> userService.addModel(user)); user.setLogin("vasyaPupkin"); userService.addModel(user); - assertEquals(user.getLogin(), user.getName()); + Assertions.assertEquals(user.getLogin(), user.getName()); user.setBirthday(LocalDate.of(2024, 2, 23)); - assertThrows(ValidateException.class, () -> userService.addModel(user)); + Assertions.assertThrows(ValidateException.class, () -> userService.addModel(user)); } } \ No newline at end of file diff --git a/src/test/java/ru/yandex/practicum/filmorate/storage/FilmDBStorageTest.java b/src/test/filmorate/storage/FilmDBStorageTest.java similarity index 73% rename from src/test/java/ru/yandex/practicum/filmorate/storage/FilmDBStorageTest.java rename to src/test/filmorate/storage/FilmDBStorageTest.java index ffa3eb6..9d548d1 100644 --- a/src/test/java/ru/yandex/practicum/filmorate/storage/FilmDBStorageTest.java +++ b/src/test/filmorate/storage/FilmDBStorageTest.java @@ -44,13 +44,13 @@ public void save() { Film filmGet = storageFilm.get(1); - assertEquals(1, filmGet.getId()); - assertEquals(2, storageFilm.get(2).getId()); + Assertions.assertEquals(1, filmGet.getId()); + Assertions.assertEquals(2, storageFilm.get(2).getId()); storageFilm.delete(1); - assertThrows(EmptyResultDataAccessException.class, () -> storageFilm.get(1)); - assertTrue(storageFilm.get(2).getLikes().stream().anyMatch(e -> Objects.equals(e, 1))); + Assertions.assertThrows(EmptyResultDataAccessException.class, () -> storageFilm.get(1)); + Assertions.assertTrue(storageFilm.get(2).getLikes().stream().anyMatch(e -> Objects.equals(e, 1))); storageFilm.removeIdFromIdSet(new FilmLikes(2, 1)); - assertEquals(0, storageFilm.get(2).getLikes().size()); + Assertions.assertEquals(0, storageFilm.get(2).getLikes().size()); } @@ -66,20 +66,20 @@ public void update() { Film updateGet = storageFilm.get(1); - assertEquals(updateGet.getName(), "nameUpdate"); + Assertions.assertEquals(updateGet.getName(), "nameUpdate"); Collections.addAll(genreTreeSet, new Genre(2, null)); storageFilm.update(updateWithName); - assertTrue(storageFilm.get(1).getGenres().stream().anyMatch(e -> Objects.equals(e.getId(), 2))); + Assertions.assertTrue(storageFilm.get(1).getGenres().stream().anyMatch(e -> Objects.equals(e.getId(), 2))); } @Test void getMpaGenres() { - assertEquals(1, storageFilm.getMpa(1).getId()); - assertEquals(5, storageFilm.getMpaList().size()); - assertEquals(1, storageFilm.getGenreById(1).getId()); - assertEquals(6, storageFilm.getGenreList().size()); + Assertions.assertEquals(1, storageFilm.getMpa(1).getId()); + Assertions.assertEquals(5, storageFilm.getMpaList().size()); + Assertions.assertEquals(1, storageFilm.getGenreById(1).getId()); + Assertions.assertEquals(6, storageFilm.getGenreList().size()); } } \ No newline at end of file diff --git a/src/test/java/ru/yandex/practicum/filmorate/storage/UserDBStorageTest.java b/src/test/filmorate/storage/UserDBStorageTest.java similarity index 83% rename from src/test/java/ru/yandex/practicum/filmorate/storage/UserDBStorageTest.java rename to src/test/filmorate/storage/UserDBStorageTest.java index b084625..781de1b 100644 --- a/src/test/java/ru/yandex/practicum/filmorate/storage/UserDBStorageTest.java +++ b/src/test/filmorate/storage/UserDBStorageTest.java @@ -34,10 +34,10 @@ public void save() { userStorage.save(user1); Optional userOptional = Optional.of(userStorage.get(1)); - assertThat(userOptional) + Assertions.assertThat(userOptional) .isPresent() .hasValueSatisfying(user -> - assertThat(user).hasFieldOrPropertyWithValue("id", 1) + Assertions.assertThat(user).hasFieldOrPropertyWithValue("id", 1) ); user1.setId(1); @@ -45,10 +45,10 @@ public void save() { userStorage.update(user1); User userUp = userStorage.get(1); - assertEquals("Update", userUp.getName()); + Assertions.assertEquals("Update", userUp.getName()); userStorage.delete(1); - assertThrows(EmptyResultDataAccessException.class, () -> userStorage.get(1)); + Assertions.assertThrows(EmptyResultDataAccessException.class, () -> userStorage.get(1)); } From e81b433d53a998bd409a1ad4d084e7a942e1e46c Mon Sep 17 00:00:00 2001 From: maliwahn Date: Fri, 1 Mar 2024 07:26:02 +0300 Subject: [PATCH 11/11] =?UTF-8?q?=D0=BF=D0=B5=D1=80=D0=B5=D0=BD=D0=BE?= =?UTF-8?q?=D1=81=20=D1=84=D0=B0=D0=B9=D0=BB=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../filmorate/service/UserServiceTest.java | 38 ------------------- .../filmorate/FilmorateApplicationTests.java | 0 .../controller/FilmControllerTest.java | 6 +-- .../controller/UserControllerTest.java | 4 +- .../filmorate/service/FilmServiceTest.java | 10 ++--- .../service/ManageFriendsToServiceTest.java | 0 .../filmorate/storage/FilmDBStorageTest.java | 22 +++++------ .../filmorate/storage/UserDBStorageTest.java | 8 ++-- 8 files changed, 25 insertions(+), 63 deletions(-) delete mode 100644 src/test/filmorate/service/UserServiceTest.java rename src/test/{ => java/ru/yandex/practicum}/filmorate/FilmorateApplicationTests.java (100%) rename src/test/{ => java/ru/yandex/practicum}/filmorate/controller/FilmControllerTest.java (83%) rename src/test/{ => java/ru/yandex/practicum}/filmorate/controller/UserControllerTest.java (86%) rename src/test/{ => java/ru/yandex/practicum}/filmorate/service/FilmServiceTest.java (73%) rename src/test/{ => java/ru/yandex/practicum}/filmorate/service/ManageFriendsToServiceTest.java (100%) rename src/test/{ => java/ru/yandex/practicum}/filmorate/storage/FilmDBStorageTest.java (73%) rename src/test/{ => java/ru/yandex/practicum}/filmorate/storage/UserDBStorageTest.java (83%) diff --git a/src/test/filmorate/service/UserServiceTest.java b/src/test/filmorate/service/UserServiceTest.java deleted file mode 100644 index b9de37a..0000000 --- a/src/test/filmorate/service/UserServiceTest.java +++ /dev/null @@ -1,38 +0,0 @@ -package ru.yandex.practicum.filmorate.service; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import ru.yandex.practicum.filmorate.exception.ValidateException; -import ru.yandex.practicum.filmorate.model.User; -import ru.yandex.practicum.filmorate.storage.InMemoryUserStorage; - -import java.time.LocalDate; -import java.util.HashMap; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - -class UserServiceTest { - private Service userService; - - @BeforeEach - void setUp() { - userService = new UserService((new InMemoryUserStorage(new HashMap<>()))); - } - - @Test - void addUser() throws ValidateException { - User user = User.builder() - .email("my@email.ru") - .login("vasya pupkin") - .birthday(LocalDate.of(1994, 6, 12)) - .build(); - - Assertions.assertThrows(ValidateException.class, () -> userService.addModel(user)); - user.setLogin("vasyaPupkin"); - userService.addModel(user); - Assertions.assertEquals(user.getLogin(), user.getName()); - user.setBirthday(LocalDate.of(2024, 2, 23)); - Assertions.assertThrows(ValidateException.class, () -> userService.addModel(user)); - } -} \ No newline at end of file diff --git a/src/test/filmorate/FilmorateApplicationTests.java b/src/test/java/ru/yandex/practicum/filmorate/FilmorateApplicationTests.java similarity index 100% rename from src/test/filmorate/FilmorateApplicationTests.java rename to src/test/java/ru/yandex/practicum/filmorate/FilmorateApplicationTests.java diff --git a/src/test/filmorate/controller/FilmControllerTest.java b/src/test/java/ru/yandex/practicum/filmorate/controller/FilmControllerTest.java similarity index 83% rename from src/test/filmorate/controller/FilmControllerTest.java rename to src/test/java/ru/yandex/practicum/filmorate/controller/FilmControllerTest.java index 1029b41..4b9c88c 100644 --- a/src/test/filmorate/controller/FilmControllerTest.java +++ b/src/test/java/ru/yandex/practicum/filmorate/controller/FilmControllerTest.java @@ -31,12 +31,12 @@ void add() throws ValidateException, NotFoundException { .duration(60) .releaseDate(LocalDate.of(2022, 02, 23)).build(); - Assertions.assertThrows(ValidateException.class, () -> filmController.add(film)); + assertThrows(ValidateException.class, () -> filmController.add(film)); film.setName("film1"); Film film1 = filmController.add(film); - Assertions.assertEquals(1, filmController.getModelList().size()); + assertEquals(1, filmController.getModelList().size()); film1.setName("FilmVasya"); filmController.update(film1); - Assertions.assertEquals("FilmVasya", ((Film) filmController.getModelList().get(0)).getName()); + assertEquals("FilmVasya", ((Film) filmController.getModelList().get(0)).getName()); } } \ No newline at end of file diff --git a/src/test/filmorate/controller/UserControllerTest.java b/src/test/java/ru/yandex/practicum/filmorate/controller/UserControllerTest.java similarity index 86% rename from src/test/filmorate/controller/UserControllerTest.java rename to src/test/java/ru/yandex/practicum/filmorate/controller/UserControllerTest.java index 8c1d953..1ae22ee 100644 --- a/src/test/filmorate/controller/UserControllerTest.java +++ b/src/test/java/ru/yandex/practicum/filmorate/controller/UserControllerTest.java @@ -29,9 +29,9 @@ void add() throws ValidateException, NotFoundException { .birthday(LocalDate.of(1994, 6, 12)).build(); User user1 = userController.add(user); - Assertions.assertEquals(1, userController.getModelList().size()); + assertEquals(1, userController.getModelList().size()); user1.setName("Vasya"); userController.update(user1); - Assertions.assertEquals("Vasya", ((User) userController.getModelList().get(0)).getName()); + assertEquals("Vasya", ((User) userController.getModelList().get(0)).getName()); } } \ No newline at end of file diff --git a/src/test/filmorate/service/FilmServiceTest.java b/src/test/java/ru/yandex/practicum/filmorate/service/FilmServiceTest.java similarity index 73% rename from src/test/filmorate/service/FilmServiceTest.java rename to src/test/java/ru/yandex/practicum/filmorate/service/FilmServiceTest.java index 0cccdba..0f15249 100644 --- a/src/test/filmorate/service/FilmServiceTest.java +++ b/src/test/java/ru/yandex/practicum/filmorate/service/FilmServiceTest.java @@ -26,17 +26,17 @@ void addFilm() { .releaseDate(LocalDate.of(2022, 02, 23)) .build(); - Assertions.assertThrows(ValidateException.class, () -> filmService.addModel(film)); + assertThrows(ValidateException.class, () -> filmService.addModel(film)); film.setName(" "); - Assertions.assertThrows(ValidateException.class, () -> filmService.addModel(film)); + assertThrows(ValidateException.class, () -> filmService.addModel(film)); film.setDescription(" wqeqwrwefsdgsdevygfghdfgsdrthdfhdfgsgsdgdsgdgfdfgdfggggggfdffhljhjlhkjlhiuhiulhjlknjhkjdsafjdfasofuawejflkasjfls;jfiasojfeiajs;fe;kasjdfksjfdiosjfisjfsidhgdfgskdjfksdjfjsif fjs kfj sdkfjas;lfd ;sjfkas"); film.setName("film1"); - Assertions.assertThrows(ValidateException.class, () -> filmService.addModel(film)); + assertThrows(ValidateException.class, () -> filmService.addModel(film)); film.setDescription("description"); film.setReleaseDate(LocalDate.of(1895, 12, 25)); - Assertions.assertThrows(ValidateException.class, () -> filmService.addModel(film)); + assertThrows(ValidateException.class, () -> filmService.addModel(film)); film.setReleaseDate(LocalDate.of(2022, 02, 23)); film.setDuration(-1); - Assertions.assertThrows(ValidateException.class, () -> filmService.addModel(film)); + assertThrows(ValidateException.class, () -> filmService.addModel(film)); } } \ No newline at end of file diff --git a/src/test/filmorate/service/ManageFriendsToServiceTest.java b/src/test/java/ru/yandex/practicum/filmorate/service/ManageFriendsToServiceTest.java similarity index 100% rename from src/test/filmorate/service/ManageFriendsToServiceTest.java rename to src/test/java/ru/yandex/practicum/filmorate/service/ManageFriendsToServiceTest.java diff --git a/src/test/filmorate/storage/FilmDBStorageTest.java b/src/test/java/ru/yandex/practicum/filmorate/storage/FilmDBStorageTest.java similarity index 73% rename from src/test/filmorate/storage/FilmDBStorageTest.java rename to src/test/java/ru/yandex/practicum/filmorate/storage/FilmDBStorageTest.java index 9d548d1..ffa3eb6 100644 --- a/src/test/filmorate/storage/FilmDBStorageTest.java +++ b/src/test/java/ru/yandex/practicum/filmorate/storage/FilmDBStorageTest.java @@ -44,13 +44,13 @@ public void save() { Film filmGet = storageFilm.get(1); - Assertions.assertEquals(1, filmGet.getId()); - Assertions.assertEquals(2, storageFilm.get(2).getId()); + assertEquals(1, filmGet.getId()); + assertEquals(2, storageFilm.get(2).getId()); storageFilm.delete(1); - Assertions.assertThrows(EmptyResultDataAccessException.class, () -> storageFilm.get(1)); - Assertions.assertTrue(storageFilm.get(2).getLikes().stream().anyMatch(e -> Objects.equals(e, 1))); + assertThrows(EmptyResultDataAccessException.class, () -> storageFilm.get(1)); + assertTrue(storageFilm.get(2).getLikes().stream().anyMatch(e -> Objects.equals(e, 1))); storageFilm.removeIdFromIdSet(new FilmLikes(2, 1)); - Assertions.assertEquals(0, storageFilm.get(2).getLikes().size()); + assertEquals(0, storageFilm.get(2).getLikes().size()); } @@ -66,20 +66,20 @@ public void update() { Film updateGet = storageFilm.get(1); - Assertions.assertEquals(updateGet.getName(), "nameUpdate"); + assertEquals(updateGet.getName(), "nameUpdate"); Collections.addAll(genreTreeSet, new Genre(2, null)); storageFilm.update(updateWithName); - Assertions.assertTrue(storageFilm.get(1).getGenres().stream().anyMatch(e -> Objects.equals(e.getId(), 2))); + assertTrue(storageFilm.get(1).getGenres().stream().anyMatch(e -> Objects.equals(e.getId(), 2))); } @Test void getMpaGenres() { - Assertions.assertEquals(1, storageFilm.getMpa(1).getId()); - Assertions.assertEquals(5, storageFilm.getMpaList().size()); - Assertions.assertEquals(1, storageFilm.getGenreById(1).getId()); - Assertions.assertEquals(6, storageFilm.getGenreList().size()); + assertEquals(1, storageFilm.getMpa(1).getId()); + assertEquals(5, storageFilm.getMpaList().size()); + assertEquals(1, storageFilm.getGenreById(1).getId()); + assertEquals(6, storageFilm.getGenreList().size()); } } \ No newline at end of file diff --git a/src/test/filmorate/storage/UserDBStorageTest.java b/src/test/java/ru/yandex/practicum/filmorate/storage/UserDBStorageTest.java similarity index 83% rename from src/test/filmorate/storage/UserDBStorageTest.java rename to src/test/java/ru/yandex/practicum/filmorate/storage/UserDBStorageTest.java index 781de1b..b084625 100644 --- a/src/test/filmorate/storage/UserDBStorageTest.java +++ b/src/test/java/ru/yandex/practicum/filmorate/storage/UserDBStorageTest.java @@ -34,10 +34,10 @@ public void save() { userStorage.save(user1); Optional userOptional = Optional.of(userStorage.get(1)); - Assertions.assertThat(userOptional) + assertThat(userOptional) .isPresent() .hasValueSatisfying(user -> - Assertions.assertThat(user).hasFieldOrPropertyWithValue("id", 1) + assertThat(user).hasFieldOrPropertyWithValue("id", 1) ); user1.setId(1); @@ -45,10 +45,10 @@ public void save() { userStorage.update(user1); User userUp = userStorage.get(1); - Assertions.assertEquals("Update", userUp.getName()); + assertEquals("Update", userUp.getName()); userStorage.delete(1); - Assertions.assertThrows(EmptyResultDataAccessException.class, () -> userStorage.get(1)); + assertThrows(EmptyResultDataAccessException.class, () -> userStorage.get(1)); }