diff --git a/pom.xml b/pom.xml index 75a8346..23e262f 100644 --- a/pom.xml +++ b/pom.xml @@ -41,6 +41,11 @@ junit-jupiter-api test + + org.zalando + logbook-spring-boot-starter + 3.7.2 + diff --git a/src/main/java/ru/yandex/practicum/filmorate/FilmorateApplication.java b/src/main/java/ru/yandex/practicum/filmorate/FilmorateApplication.java index dca451b..8f860a2 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/FilmorateApplication.java +++ b/src/main/java/ru/yandex/practicum/filmorate/FilmorateApplication.java @@ -8,5 +8,4 @@ public class FilmorateApplication { public static void main(String[] args) { SpringApplication.run(FilmorateApplication.class, args); } - -} +} \ No newline at end of file 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 8e4ee87..766fff2 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java @@ -3,72 +3,59 @@ import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.*; -import ru.yandex.practicum.filmorate.exception.NotFoundException; -import ru.yandex.practicum.filmorate.exception.ValidationException; import ru.yandex.practicum.filmorate.model.Film; +import ru.yandex.practicum.filmorate.service.FilmService; +import ru.yandex.practicum.filmorate.storage.FilmStorage; import java.util.Collection; -import java.util.HashMap; -import java.util.Map; +import java.util.List; @Slf4j @RestController @RequestMapping("/films") public class FilmController { - private final Map films = new HashMap<>(); + private final FilmStorage filmStorage; + private final FilmService filmService; + + public FilmController(FilmStorage filmStorage, FilmService filmService) { + this.filmStorage = filmStorage; + this.filmService = filmService; + } @GetMapping public Collection findAll() { - return films.values(); + return filmStorage.findAll(); } - @PostMapping - public Film create(@Valid @RequestBody Film film) { - log.info("Попытка добавления нового фильма в коллекцию: {}", film.getName()); - if (film.getId() != null && films.containsKey(film.getId())) { - log.error("Ошибка добавления: Фильм уже имеется в коллекции"); - throw new ValidationException("Фильм уже имеется в коллекции"); - } - film.setId(getNextId()); - films.put(film.getId(), film); - log.info("Фильм: '{}', успешно добавлен в коллекцию", film.getName()); - return film; + @GetMapping("/{id}") + public Film getFilmById(@PathVariable Long id) { + return filmStorage.getFilmById(id); } - @PutMapping - public Film update(@Valid @RequestBody Film filmWithNewData) { - if (filmWithNewData.getId() == null || filmWithNewData.getId() == 0) { - throw new ValidationException("id должен быть указан"); - } - log.info("Попытка обновления данных о фильме: '{}'", filmWithNewData.getName()); + @PutMapping("/{id}/like/{userId}") + public void addLike(@PathVariable Long id, + @PathVariable Long userId) { + filmService.addLike(id, userId); + } - Film filmWithOldData = films.get(filmWithNewData.getId()); - if (filmWithOldData == null) { - log.error("Ошибка обновления: фильм не найден"); - throw new NotFoundException("Фильм не найден"); - } - // меняем релиз-дату - if (filmWithNewData.isValidReleaseDate()) { - filmWithOldData.setReleaseDate(filmWithNewData.getReleaseDate()); - } - // меняем название - filmWithOldData.setName(filmWithNewData.getName()); + @DeleteMapping("/{id}/like/{userId}") + public void deleteLike(@PathVariable Long id, + @PathVariable Long userId) { + filmService.deleteLike(id, userId); + } - //меняем описание - filmWithOldData.setDescription(filmWithNewData.getDescription()); + @PostMapping + public Film create(@Valid @RequestBody Film film) { + return filmStorage.create(film); + } - //меняем продолжительность фильма - filmWithOldData.setDuration(filmWithNewData.getDuration()); - log.info("Данные о фильме '{}' успешно обновлены", filmWithOldData.getName()); - return filmWithOldData; + @GetMapping("/popular") + public List getTopFilms(@RequestParam(name = "count", defaultValue = "10") int count) { + return filmService.getTopFilms(count); } - private long getNextId() { - long currentMaxId = films.keySet() - .stream() - .mapToLong(id -> id) - .max() - .orElse(0); - return ++currentMaxId; + @PutMapping + public Film update(@Valid @RequestBody Film filmWithNewData) { + return filmStorage.update(filmWithNewData); } -} +} \ 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 155657b..954ba1c 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java @@ -2,104 +2,68 @@ import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.*; -import ru.yandex.practicum.filmorate.exception.NotFoundException; -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.UserStorage; import java.util.Collection; -import java.util.HashMap; -import java.util.Map; +import java.util.List; @Slf4j @RestController @RequestMapping("/users") public class UserController { - // для пользователей по id - private final Map users = new HashMap<>(); - // для быстрой проверки уникальности email - private final Map usersByEmail = new HashMap<>(); - // для быстрой проверки уникальности login - private final Map usersByLogin = new HashMap<>(); +private final UserStorage userStorage; +private final UserService userService; + public UserController(UserStorage userStorage, UserService userService) { + this.userStorage = userStorage; + this.userService = userService; + } @GetMapping public Collection findAll() { - return users.values(); + return userStorage.findAll(); } - @PostMapping - public User create(@Valid @RequestBody User user) { - log.info("Попытка создания пользователя с email: {} и login: {}", user.getEmail(), user.getLogin()); - if (users.containsKey(user.getId())) { - log.error("Ошибка создания пользователя: id {} уже используется", user.getId()); - throw new ValidationException("Пользователь уже зарегистрирован"); - } - if (usersByEmail.containsKey(user.getEmail())) { - log.error("Ошибка создания пользователя: email {} уже используется", user.getEmail()); - throw new ValidationException("Данный email уже используется"); - } - if (usersByLogin.containsKey(user.getLogin())) { - log.error("Ошибка создания пользователя: login {} уже используется", user.getLogin()); - throw new ValidationException("Данный логин занят"); - } - user.setId(getNextId()); - users.put(user.getId(), user); - usersByEmail.put(user.getEmail(), user); - usersByLogin.put(user.getLogin(), user); - log.info("Пользователь успешно создан с id: {}, email: {}, login: {}", - user.getId(), user.getEmail(), user.getLogin()); - return user; + @GetMapping("/{id}") + public User getUserById(@PathVariable Long id) { + return userStorage.getUserById(id); } - @PutMapping - public User update(@Valid @RequestBody User userWithNewData) { - if (userWithNewData.getId() == null || userWithNewData.getId() == 0) { - throw new ValidationException("id должен быть указан"); - } - log.info("Попытка обновления пользователя с id: {}", userWithNewData.getId()); - - User userWithOldData = users.get(userWithNewData.getId()); - if (userWithOldData == null) { - log.error("Пользователь не найден"); - throw new NotFoundException("Пользователь не найден"); - } - if (!userWithNewData.getEmail().equals(userWithOldData.getEmail()) && - usersByEmail.containsKey(userWithNewData.getEmail())) { - log.error("Ошибка обновления: email {} уже используется", userWithNewData.getEmail()); - throw new ValidationException("Этот e-mail уже используется"); - } + @PutMapping("/{id}/friends/{friendId}") + public void addFriend(@PathVariable Long id, + @PathVariable Long friendId) { + userService.addFriend(id, friendId); + } - // обновляем e-mail - usersByEmail.remove(userWithOldData.getEmail()); - userWithOldData.setEmail(userWithNewData.getEmail()); - usersByEmail.put(userWithOldData.getEmail(), userWithOldData); + @DeleteMapping("/{id}/friends/{friendId}") + public void deleteFriend(@PathVariable Long id, + @PathVariable Long friendId) { + userService.deleteFriend(id, friendId); + } - // обновляем логин - if (!userWithNewData.getLogin().equals(userWithOldData.getLogin()) && - usersByLogin.containsKey(userWithNewData.getLogin())) { - log.error("Ошибка обновления: login {} уже используется", userWithNewData.getLogin()); - throw new ValidationException("Этот логин уже используется"); - } - usersByLogin.remove(userWithOldData.getLogin()); - userWithOldData.setLogin(userWithNewData.getLogin()); - usersByLogin.put(userWithOldData.getLogin(),userWithOldData); + @GetMapping("/{id}/friends") + public List getUserFriends(@PathVariable Long id) { + return userService.getFriends(id); + } - // обновляем имя - userWithOldData.setName(userWithNewData.getName()); + @GetMapping("/{id}/friends/common/{friendId}") + public List getCommonFriends(@PathVariable Long id, + @PathVariable Long friendId) { + return userService.getCommonFriends(id, friendId); + } - // обновляем дату рождения - userWithOldData.setBirthday(userWithNewData.getBirthday()); - log.info("Пользователь с id {} успешно обновлён", userWithOldData.getId()); - return userWithOldData; + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + public User create(@Valid @RequestBody User user) { + return userStorage.create(user); } - private long getNextId() { - long currentMaxId = users.keySet() - .stream() - .mapToLong(id -> id) - .max() - .orElse(0); - return ++currentMaxId; + @PutMapping + public User update(@Valid @RequestBody User userWithNewData) { + return userStorage.update(userWithNewData); } -} +} \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/exception/ConditionsNotMetException.java b/src/main/java/ru/yandex/practicum/filmorate/exception/ConditionsNotMetException.java new file mode 100644 index 0000000..c653ae4 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/exception/ConditionsNotMetException.java @@ -0,0 +1,7 @@ +package ru.yandex.practicum.filmorate.exception; + +public class ConditionsNotMetException extends RuntimeException { + public ConditionsNotMetException(String message) { + super(message); + } +} \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/exception/handler/GlobalExceptionHandler.java b/src/main/java/ru/yandex/practicum/filmorate/exception/handler/GlobalExceptionHandler.java index 845f060..f56b3fa 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/exception/handler/GlobalExceptionHandler.java +++ b/src/main/java/ru/yandex/practicum/filmorate/exception/handler/GlobalExceptionHandler.java @@ -3,6 +3,7 @@ import jakarta.servlet.http.HttpServletRequest; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import ru.yandex.practicum.filmorate.dto.ErrorResponse; @@ -26,8 +27,9 @@ public ResponseEntity handleNotFound(NotFoundException e, HttpSer return ResponseEntity.status(HttpStatus.NOT_FOUND).body(errorResponse); } - @ExceptionHandler(ValidationException.class) - public ResponseEntity handleValidation(ValidationException e, HttpServletRequest request) { + @ExceptionHandler({ValidationException.class, MethodArgumentNotValidException.class}) + public ResponseEntity handleValidation( + ValidationException e, HttpServletRequest request) { ErrorResponse errorResponse = new ErrorResponse( LocalDateTime.now(), HttpStatus.BAD_REQUEST.value(), @@ -49,4 +51,4 @@ public ResponseEntity handleOtherExceptions(Exception e, HttpServ ); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse); } -} +} \ No newline at end of file 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 4b61a5a..987b732 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/model/Film.java +++ b/src/main/java/ru/yandex/practicum/filmorate/model/Film.java @@ -2,12 +2,25 @@ import jakarta.validation.constraints.*; import lombok.Data; +import lombok.RequiredArgsConstructor; import java.time.LocalDate; +import java.util.HashSet; +import java.util.Set; @Data +@RequiredArgsConstructor public class Film { private Long id; + private Set likes = new HashSet<>(); + + public Film(Long id, LocalDate releaseDate, String name, String description, int duration) { + this.id = id; + this.releaseDate = releaseDate; + this.name = name; + this.description = description; + this.duration = duration; + } @NotNull private LocalDate releaseDate; @@ -28,4 +41,4 @@ public boolean isValidReleaseDate() { LocalDate minReleaseDate = LocalDate.of(1895,12,28); return !releaseDate.isBefore(minReleaseDate); } -} +} \ No newline at end of file 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 5d62895..1918db7 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/model/User.java +++ b/src/main/java/ru/yandex/practicum/filmorate/model/User.java @@ -4,14 +4,17 @@ import com.fasterxml.jackson.annotation.JsonProperty; import jakarta.validation.constraints.*; import lombok.Data; -import lombok.NoArgsConstructor; +import lombok.RequiredArgsConstructor; import java.time.LocalDate; +import java.util.HashSet; +import java.util.Set; @Data -@NoArgsConstructor +@RequiredArgsConstructor public class User { private Long id; + private Set friends = new HashSet<>(); @NotBlank(message = "Электронная почта не может быть пустой") @Email(message = "Электронная почта должна быть корректной и содержать символ '@'") @@ -43,4 +46,4 @@ public User(@JsonProperty("id") Long id, public void setName(String name) { this.name = (name == null || name.isBlank()) ? this.login : name; } -} +} \ 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..9a6435a --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/service/FilmService.java @@ -0,0 +1,81 @@ +package ru.yandex.practicum.filmorate.service; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import ru.yandex.practicum.filmorate.exception.NotFoundException; +import ru.yandex.practicum.filmorate.exception.ValidationException; +import ru.yandex.practicum.filmorate.model.Film; +import ru.yandex.practicum.filmorate.storage.FilmStorage; +import ru.yandex.practicum.filmorate.storage.UserStorage; + +import java.util.Comparator; +import java.util.List; + +@Slf4j +@Service +public class FilmService { + private final FilmStorage filmStorage; + private final UserStorage userStorage; + + @Autowired + public FilmService(FilmStorage filmStorage, UserStorage userStorage) { + this.filmStorage = filmStorage; + this.userStorage = userStorage; + } + + public void addLike(Long filmId, Long userId) { + log.debug("Попытка добавить лайк фильму {} от пользователя {}.", filmId, userId); + validateFilmAndUserExist(filmId, userId); + Film film = filmStorage.getFilmById(filmId); + + if (!film.getLikes().add(userId)) { + log.error("Пользователь с id {} уже добавил лайк фильму {}.", userId, filmId); + throw new ValidationException("Данный пользователь уже добавлял лайк этому фильму!"); + } + log.info("Пользователь {} поставил лайк фильму {}.", userId, filmId); + } + + public void deleteLike(Long filmId, Long userId) { + log.debug("Попытка удалить лайк у фильма {} от пользователя {}.", filmId, userId); + validateFilmAndUserExist(filmId, userId); + Film film = filmStorage.getFilmById(filmId); + + if (!film.getLikes().remove(userId)) { + log.error("Пользователь с id {} не ставил лайк фильму {}.", userId, filmId); + throw new ValidationException("Этот пользователь не ставил лайк данному фильму"); + } + log.info("Пользователь {} удалил лайк у фильма {}.", userId, filmId); + } + + public List getTopFilms(int count) { + log.debug("Запрос на получение топ фильмов"); + List topFilms = filmStorage.findAll().stream() + .sorted(Comparator.comparingInt(film -> -film.getLikes().size())) + .limit(count) + .toList(); + log.info("Выведен список из {} топ фильмов", topFilms.size()); + return topFilms; + } + + public void validateFilmAndUserExist(Long filmId, Long userId) { + log.trace("Валидация существования фильма с id {} и пользователя с id {}", filmId, userId); + if (filmId == null || userId == null) { + log.error("Оба идентификатора (filmId и userId) должны быть указаны"); + throw new ValidationException("Id должен быть указан"); + } + try { + filmStorage.getFilmById(filmId); + } catch (NotFoundException e) { + log.error("Фильм с id {} не найден.", filmId); + throw e; + } + try { + userStorage.getUserById(userId); + } catch (NotFoundException e) { + log.error("Пользователь с id {} не найден.", userId); + throw e; + } + log.trace("Валидация успешна для фильма с id {} и пользователя с id {}", filmId, userId); + } +} \ No newline at end of file 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..240dbdc --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/service/UserService.java @@ -0,0 +1,104 @@ +package ru.yandex.practicum.filmorate.service; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import ru.yandex.practicum.filmorate.exception.ConditionsNotMetException; +import ru.yandex.practicum.filmorate.exception.ValidationException; +import ru.yandex.practicum.filmorate.model.User; +import ru.yandex.practicum.filmorate.storage.UserStorage; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +@Slf4j +@Service +public class UserService { + private final UserStorage userStorage; + + @Autowired + public UserService(UserStorage userStorage) { + this.userStorage = userStorage; + } + + public void addFriend(Long id, Long friendId) { + log.debug("Попытка пользователя {} добавить в друзья пользователя {}", id, friendId); + User[] users = validateAndGetUsers(id, friendId); + User user1 = users[0]; + User user2 = users[1]; + + if (user1.getFriends().contains(friendId)) { + log.error("Пользователь с id {} уже имеется в друзьях у пользователя {}", friendId, id); + throw new ValidationException("Данный друг уже имеется в списке друзей!"); + } + user1.getFriends().add(friendId); + user2.getFriends().add(id); + + log.info("Пользователь {} успешно добавил в друзья пользователя {}", id, friendId); + } + + public List getFriends(Long id) { + log.debug("Попытка получить список друзей пользователя с id {}", id); + User user = userStorage.getUserById(id); + Set friendIds = user.getFriends(); + + log.info("Получен список друзей пользователя с id {}", id); + return friendIds.stream() + .map(userStorage::getUserById) + .toList(); + } + + public List getCommonFriends(Long id, Long friendsId) { + log.debug("Запрос на получение списка общих друзей"); + User[] users = validateAndGetUsers(id, friendsId); + User user1 = users[0]; + User user2 = users[1]; + + Set friendIdsUser1 = user1.getFriends(); + Set friendsIdsUser2 = user2.getFriends(); + + Set commonFriends = new HashSet<>(friendIdsUser1); + commonFriends.retainAll(friendsIdsUser2); + + log.info("Выведен список общих друзей"); + return commonFriends.stream() + .map(userStorage::getUserById) + .toList(); + } + + public void deleteFriend(Long id, Long friendsId) { + log.debug("Попытка пользователя {} удалить из друзей пользователя {}", id, friendsId); + User[] users = validateAndGetUsers(id, friendsId); + User user1 = users[0]; + User user2 = users[1]; + + if (!user1.getFriends().contains(friendsId)) { + log.warn("Пользователь с id {} отсутствует в списке друзей у пользователя {}", friendsId, id); + return; + } + + user1.getFriends().remove(friendsId); + user2.getFriends().remove(id); + + log.info("Пользователь {} успешно удалил из друзей пользователя {}", id, friendsId); + } + + private User[] validateAndGetUsers(Long id, Long friendsId) { + log.trace("Валидация существования пользователей с id {} и {}", id, friendsId); + if (id == null || friendsId == null) { + log.error("Отсутствует информация об ID"); + throw new ConditionsNotMetException("Id должен быть указан"); + } + if (id.equals(friendsId)) { + log.error("ID совпадают, неверно указаны идентификаторы пользователей"); + throw new ValidationException("Личный id, и id друга совпадают. Неверный ввод данных"); + } + + User user1 = userStorage.getUserById(id); + User user2 = userStorage.getUserById(friendsId); + + log.trace("Валидация пользователей с ID {} и {} прошла успешно", id, friendsId); + return new User[]{user1, user2}; + } +} \ No newline at end of file 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..0ab484c --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/FilmStorage.java @@ -0,0 +1,18 @@ +package ru.yandex.practicum.filmorate.storage; + +import ru.yandex.practicum.filmorate.model.Film; + +import java.util.Collection; + +public interface FilmStorage { + + Collection findAll(); + + Film getFilmById(Long id); + + Film create(Film film); + + Film update(Film filmWithNewData); + + long getNextId(); +} \ No newline at end of file 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..a057377 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryFilmStorage.java @@ -0,0 +1,92 @@ +package ru.yandex.practicum.filmorate.storage; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import ru.yandex.practicum.filmorate.exception.NotFoundException; +import ru.yandex.practicum.filmorate.exception.ValidationException; +import ru.yandex.practicum.filmorate.model.Film; + +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; + +@Slf4j +@Component +public class InMemoryFilmStorage implements FilmStorage { + private final Map films = new HashMap<>(); + + @Override + public Collection findAll() { + return films.values(); + } + + @Override + public Film getFilmById(Long id) { + log.debug("Попытка получить фильм с id {}.", id); + Film film = films.get(id); + if (film == null) { + log.error("Фильм с id {} не найден.", id); + throw new NotFoundException("Фильм с id " + id + " не найден."); + } + log.info("Получен фильм с id {}.", id); + return film; + } + + @Override + public Film create(Film film) { + log.info("Попытка добавления нового фильма в коллекцию: {}", film.getName()); + if (film.getId() != null && films.containsKey(film.getId())) { + log.error("Ошибка добавления: Фильм уже имеется в коллекции"); + throw new ValidationException("Фильм уже имеется в коллекции"); + } + film.setId(getNextId()); + films.put(film.getId(), film); + log.info("Фильм: '{}', успешно добавлен в коллекцию", film.getName()); + return film; + } + + @Override + public Film update(Film filmWithNewData) { + if (filmWithNewData.getId() == null || filmWithNewData.getId() == 0) { + throw new ValidationException("id должен быть указан"); + } + log.info("Попытка обновления данных о фильме: '{}'", filmWithNewData.getName()); + + Film filmWithOldData = films.get(filmWithNewData.getId()); + if (filmWithOldData == null) { + log.error("Ошибка обновления: фильм не найден"); + throw new NotFoundException("Фильм не найден"); + } + + Film updateFilm = new Film( + filmWithNewData.getId(), + filmWithNewData.getReleaseDate(), + filmWithNewData.getName(), + filmWithNewData.getDescription(), + filmWithNewData.getDuration() + ); + if (!updateFilm.isValidReleaseDate()) { + throw new ValidationException("Дата релиза должна быть не раньше 28 декабря 1895 года"); + } + if (filmWithNewData.getLikes() != null && !filmWithNewData.getLikes().isEmpty()) { + updateFilm.setLikes(new HashSet<>(filmWithNewData.getLikes())); + } else { + updateFilm.setLikes(filmWithOldData.getLikes()); + } + films.remove(filmWithOldData.getId(), filmWithOldData); + films.put(updateFilm.getId(), updateFilm); + log.info("Данные о фильме '{}' успешно обновлены", filmWithOldData.getName()); + return updateFilm; + } + + @Override + public long getNextId() { + long currentMaxId = films.keySet() + .stream() + .mapToLong(id -> id) + .max() + .orElse(0); + return ++currentMaxId; + } +} \ No newline at end of file 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..2e39cfc --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryUserStorage.java @@ -0,0 +1,130 @@ +package ru.yandex.practicum.filmorate.storage; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import ru.yandex.practicum.filmorate.exception.NotFoundException; +import ru.yandex.practicum.filmorate.exception.ValidationException; +import ru.yandex.practicum.filmorate.model.User; + +import java.util.*; + +@Slf4j +@Component +public class InMemoryUserStorage implements UserStorage { + // для пользователей по id + private final Map users = new HashMap<>(); + // для быстрой проверки уникальности email + private final Map usersByEmail = new HashMap<>(); + // для быстрой проверки уникальности login + private final Map usersByLogin = new HashMap<>(); + + @Override + public Collection findAll() { + return users.values(); + } + + @Override + public User getUserById(Long id) { + log.debug("Попытка полючить пользователя с id {}.", id); + User user = users.get(id); + if (user == null) { + log.error("Пользователь с id {} не найден.", id); + throw new NotFoundException("Пользователь с id " + id + " не найден."); + } + log.info("Пользователь с id {} получен.", id); + return user; + } + + @Override + public User create(User user) { + log.info("Попытка создания пользователя с email: {} и login: {}", user.getEmail(), user.getLogin()); + if (users.containsKey(user.getId())) { + log.error("Ошибка создания пользователя: id {} уже используется", user.getId()); + throw new ValidationException("Пользователь уже зарегистрирован"); + } + if (usersByEmail.containsKey(user.getEmail())) { + log.error("Ошибка создания пользователя: email {} уже используется", user.getEmail()); + throw new ValidationException("Данный email уже используется"); + } + if (usersByLogin.containsKey(user.getLogin())) { + log.error("Ошибка создания пользователя: login {} уже используется", user.getLogin()); + throw new ValidationException("Данный логин занят"); + } + user.setId(getNextId()); + users.put(user.getId(), user); + usersByEmail.put(user.getEmail(), user); + usersByLogin.put(user.getLogin(), user); + + log.info("Пользователь успешно создан с id: {}, email: {}, login: {}", + user.getId(), user.getEmail(), user.getLogin()); + return user; + } + + @Override + public User update(User userWithNewData) { + log.info("Попытка обновления пользователя с id: {}", userWithNewData.getId()); + + if (userWithNewData.getId() == null || userWithNewData.getId() == 0) { + throw new ValidationException("id должен быть указан"); + } + User userWithOldData = users.get(userWithNewData.getId()); + if (userWithOldData == null) { + log.error("Пользователь не найден"); + throw new NotFoundException("Пользователь не найден"); + } + // проверяем доступность email + if (!userWithNewData.getEmail().equals(userWithOldData.getEmail())) { + if (usersByEmail.containsKey(userWithNewData.getEmail())) { + User existingUserByEmail = usersByEmail.get(userWithNewData.getEmail()); + if (!existingUserByEmail.getId().equals(userWithNewData.getId())) { + log.error("Ошибка обновления: email {} уже используется", userWithNewData.getEmail()); + throw new ValidationException("Этот e-mail уже используется"); + } + } + } + + // проверяем доступность логина + if (!userWithNewData.getLogin().equals(userWithOldData.getLogin())) { + if (usersByLogin.containsKey(userWithNewData.getLogin())) { + User existingUserByLogin = usersByLogin.get(userWithNewData.getLogin()); + if (!existingUserByLogin.getId().equals(userWithNewData.getId())) { + log.error("Ошибка обновления: login {} уже используется", userWithNewData.getLogin()); + throw new ValidationException("Этот логин уже используется"); + } + } + } + + User updateUser = new User( + userWithNewData.getId(), + userWithNewData.getEmail(), + userWithNewData.getLogin(), + userWithNewData.getName(), + userWithNewData.getBirthday() + ); + if (userWithNewData.getFriends() != null && !userWithNewData.getFriends().isEmpty()) { + updateUser.setFriends(new HashSet<>(userWithNewData.getFriends())); + } else { + updateUser.setFriends(userWithOldData.getFriends()); + } + // удаляем старого пользователя и возвращаем нового + users.remove(userWithOldData.getId()); + usersByEmail.remove(userWithOldData.getEmail()); + usersByLogin.remove(userWithOldData.getLogin()); + users.put(userWithNewData.getId(), userWithNewData); + usersByLogin.put(userWithNewData.getLogin(), userWithNewData); + usersByEmail.put(userWithNewData.getEmail(), userWithNewData); + + log.info("Пользователь с id {} успешно обновлён", userWithOldData.getId()); + return updateUser; + } + + @Override + public long getNextId() { + long currentMaxId = users.keySet() + .stream() + .mapToLong(id -> id) + .max() + .orElse(0); + return ++currentMaxId; + } +} \ No newline at end of file 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..4b011ae --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/UserStorage.java @@ -0,0 +1,18 @@ +package ru.yandex.practicum.filmorate.storage; + +import ru.yandex.practicum.filmorate.model.User; + +import java.util.Collection; + +public interface UserStorage { + + Collection findAll(); + + User getUserById(Long id); + + User create(User user); + + User update(User userWithNewData); + + long getNextId(); +} \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 4c00e40..36f6ba6 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1,2 @@ server.port=8080 +logging.level.org.zalando.logbook: TRACE 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..e058d5e --- /dev/null +++ b/src/test/java/ru/yandex/practicum/filmorate/service/FilmServiceTest.java @@ -0,0 +1,78 @@ +package ru.yandex.practicum.filmorate.service; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import ru.yandex.practicum.filmorate.model.Film; +import ru.yandex.practicum.filmorate.model.User; +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.List; + +import static org.junit.jupiter.api.Assertions.*; + +class FilmServiceTest { + private FilmStorage filmStorage; + private FilmService filmService; + private UserStorage userStorage; + + @BeforeEach + void setUp() { + filmStorage = new InMemoryFilmStorage(); + userStorage = new InMemoryUserStorage(); + filmService = new FilmService(filmStorage, userStorage); + } + + @Test + void addLike() { + Film film = new Film(1L, LocalDate.of(1997, 1, 26), + "test", "description", 120); + + User user = userStorage.create(new User(1L, "user1@email.com", "user1", "user1", + LocalDate.of(1997,1,26))); + filmStorage.create(film); + filmService.addLike(film.getId(), user.getId()); + + assertEquals(1, filmStorage.getFilmById(film.getId()).getLikes().size()); + } + + @Test + void deleteLike() { + Film film = new Film(1L, LocalDate.of(1997, 1, 26), + "test", "description", 120); + + User user = userStorage.create(new User(1L, "user1@email.com", "user1", "user1", + LocalDate.of(1997,1,26))); + filmStorage.create(film); + filmService.addLike(film.getId(), user.getId()); + filmService.deleteLike(film.getId(), user.getId()); + + assertEquals(0, filmStorage.getFilmById(film.getId()).getLikes().size()); + + } + + @Test + void getTopFilms() { + Film film1 = new Film(1L, LocalDate.of(1997, 1, 26), + "test1", "description1", 120); + Film film2 = new Film(2L, LocalDate.of(1997, 1, 26), + "test2", "description2", 120); + + User user = userStorage.create(new User(1L, "user1@email.com", "user1", "user1", + LocalDate.of(1997,1,26))); + + filmStorage.create(film1); + filmStorage.create(film2); + + filmService.addLike(film1.getId(), user.getId()); + + List topFilms = filmService.getTopFilms(2); + + assertEquals(2, topFilms.size()); + assertEquals("test1", topFilms.getFirst().getName()); + + } +} \ No newline at end of file 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..9c510ee --- /dev/null +++ b/src/test/java/ru/yandex/practicum/filmorate/service/UserServiceTest.java @@ -0,0 +1,86 @@ +package ru.yandex.practicum.filmorate.service; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import ru.yandex.practicum.filmorate.model.User; +import ru.yandex.practicum.filmorate.storage.InMemoryUserStorage; +import ru.yandex.practicum.filmorate.storage.UserStorage; + +import java.time.LocalDate; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest +class UserServiceTest { + private UserService userService; + private UserStorage userStorage; + + @BeforeEach + void setup() { + userStorage = new InMemoryUserStorage() { + }; + userService = new UserService(userStorage); + } + + @Test + void shouldAddFriend() { + User user1 = new User(1L, "user1@email.com", "user1", "user1", + LocalDate.of(1997, 01, 26)); + User user2 = new User(2L, "user2@email.com", "user2", "user2", + LocalDate.of(1997, 01, 27)); + userStorage.create(user1); + userStorage.create(user2); + + userService.addFriend(1L, 2L); + assertTrue(user1.getFriends().contains(2L)); + assertTrue(user2.getFriends().contains(1L)); + } + + @Test + void shouldGetFriends() { + User user1 = new User(1L, "user1@email.com", "user1", "user1", + LocalDate.of(1997, 01, 26)); + userStorage.create(user1); + User theSameUser = userStorage.getUserById(1L); + assertEquals(user1, theSameUser); + } + + @Test + void getCommonFriends() { + User user1 = new User(1L, "user1@email.com", "user1", "user1", + LocalDate.of(1997, 01, 26)); + User user2 = new User(2L, "user2@email.com", "user2", "user2", + LocalDate.of(1997, 01, 27)); + User user3 = new User(3L, "user3@email.com", "user3", "user3", + LocalDate.of(1997, 01, 28)); + userStorage.create(user1); + userStorage.create(user2); + userStorage.create(user3); + + userService.addFriend(user1.getId(), user3.getId()); + userService.addFriend(user2.getId(), user3.getId()); + + List commonFriends = userService.getCommonFriends(user1.getId(), user2.getId()); + assertTrue(commonFriends.contains(user3)); + } + + @Test + void deleteFriend() { + User user1 = new User(1L, "user1@email.com", "user1", "user1", + LocalDate.of(1997, 01, 26)); + User user2 = new User(2L, "user2@email.com", "user2", "user2", + LocalDate.of(1997, 01, 27)); + + userStorage.create(user1); + userStorage.create(user2); + + userService.addFriend(user1.getId(), user2.getId()); + userService.deleteFriend(user1.getId(), user2.getId()); + + assertFalse(user1.getFriends().contains(user2.getId())); + assertFalse(user2.getFriends().contains(user1.getId())); + + } +} \ No newline at end of file diff --git a/src/test/java/ru/yandex/practicum/filmorate/storage/InMemoryFilmStorageTest.java b/src/test/java/ru/yandex/practicum/filmorate/storage/InMemoryFilmStorageTest.java new file mode 100644 index 0000000..f475804 --- /dev/null +++ b/src/test/java/ru/yandex/practicum/filmorate/storage/InMemoryFilmStorageTest.java @@ -0,0 +1,80 @@ +package ru.yandex.practicum.filmorate.storage; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import ru.yandex.practicum.filmorate.exception.NotFoundException; +import ru.yandex.practicum.filmorate.model.Film; + +import java.time.LocalDate; + +import static org.junit.jupiter.api.Assertions.*; + +class InMemoryFilmStorageTest { +private FilmStorage filmStorage; + +@BeforeEach + void setUp() { + filmStorage = new InMemoryFilmStorage() { + }; + } + + + @Test + void findAll() { + Film film1 = new Film(1L, LocalDate.of(1997, 1, 26), + "test1", "description1", 120); + Film film2 = new Film(2L, LocalDate.of(1997, 1, 26), + "test2", "description2", 120); + filmStorage.create(film1); + filmStorage.create(film2); + + assertEquals(2, filmStorage.findAll().size()); + assertEquals("test1", filmStorage.getFilmById(1L).getName()); + assertEquals("test2", filmStorage.getFilmById(2L).getName()); + } + + @Test + void getFilmById() { + Film film1 = new Film(1L, LocalDate.of(1997, 1, 26), + "test1", "description1", 120); + filmStorage.create(film1); + + assertEquals(film1, filmStorage.getFilmById(1L)); + } + + @Test + void getFilmByIdShouldThrowException() { + Exception exception = assertThrows(NotFoundException.class, () -> { + filmStorage.getFilmById(1L); + }); + assertEquals("Фильм с id 1 не найден.", exception.getMessage()); + } + + @Test + void create() { + Film film1 = new Film(1L, LocalDate.of(1997, 1, 26), + "test1", "description1", 120); + filmStorage.create(film1); + + assertEquals(1, filmStorage.findAll().size()); + assertEquals("test1", filmStorage.getFilmById(1L).getName()); + } + + @Test + void update() { + Film film1 = new Film(1L, LocalDate.of(1997, 1, 26), + "test1", "description1", 120); + + filmStorage.create(film1); + + Film updateFilm1 = new Film(1L, LocalDate.of(1997, 1, 26), + "updateFilm1", "updateDescription1", 130); + + + filmStorage.update(updateFilm1); + + assertEquals("updateFilm1", filmStorage.getFilmById(1L).getName()); + assertEquals("updateDescription1", filmStorage.getFilmById(1L).getDescription()); + assertEquals(130, filmStorage.getFilmById(1L).getDuration()); + } +} \ No newline at end of file diff --git a/src/test/java/ru/yandex/practicum/filmorate/storage/InMemoryUserStorageTest.java b/src/test/java/ru/yandex/practicum/filmorate/storage/InMemoryUserStorageTest.java new file mode 100644 index 0000000..81c5267 --- /dev/null +++ b/src/test/java/ru/yandex/practicum/filmorate/storage/InMemoryUserStorageTest.java @@ -0,0 +1,143 @@ +package ru.yandex.practicum.filmorate.storage; + +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.ValidationException; +import ru.yandex.practicum.filmorate.model.User; + +import java.time.LocalDate; + +import static org.junit.jupiter.api.Assertions.*; + +class InMemoryUserStorageTest { + private UserStorage userStorage; + + + @BeforeEach + void setUp() { + userStorage = new InMemoryUserStorage(); + } + + @Test + void findAll() { + User user1 = new User(1L, "user1@email.com", "user1", "user1", + LocalDate.of(1997, 01, 26)); + User user2 = new User(2L, "user2@email.com", "user2", "user2", + LocalDate.of(1997, 01, 27)); + userStorage.create(user1); + userStorage.create(user2); + + assertEquals(2, userStorage.findAll().size()); + } + + @Test + void getUserById() { + User user1 = new User(1L, "user1@email.com", "user1", "user1", + LocalDate.of(1997, 01, 26)); + + userStorage.create(user1); + assertEquals(user1, userStorage.getUserById(user1.getId())); + } + + @Test + void getUserByIdShouldThrowException() { + Exception exception = assertThrows(NotFoundException.class, () -> { + userStorage.getUserById(1L); + }); + assertEquals("Пользователь с id 1 не найден.", exception.getMessage()); + } + + @Test + void create() { + User user1 = new User(1L, "user1@email.com", "user1", "user1", + LocalDate.of(1997, 01, 26)); + User user2 = new User(2L, "user2@email.com", "user2", "user2", + LocalDate.of(1997, 01, 27)); + userStorage.create(user1); + userStorage.create(user2); + + assertEquals(user1, userStorage.getUserById(1L)); + assertEquals(user2, userStorage.getUserById(2L)); + assertEquals("user1", userStorage.getUserById(1L).getName()); + } + + @Test + void update() { + User user1 = new User(1L, "user1@email.com", "user1", "user1", + LocalDate.of(1997, 01, 26)); + User updateUser1 = new User(1L, "user1new@email.com", "newLogin1", "newUser1", + LocalDate.of(1997, 01, 26)); + + userStorage.create(user1); + userStorage.update(updateUser1); + + assertEquals("newUser1", userStorage.getUserById(user1.getId()).getName()); + assertEquals("newLogin1", userStorage.getUserById(user1.getId()).getLogin()); + } + + @Test + void createUserWithExistLogin() { + User user1 = new User(1L, "user1@email.com", "user1", "user1", + LocalDate.of(1997, 01, 26)); + + User user2 = new User(2L, "user2@email.com", "user1", "user2", + LocalDate.of(1997, 01, 26)); + + userStorage.create(user1); + Exception exception = assertThrows(ValidationException.class, () -> { + userStorage.create(user2); + }); + assertEquals("Данный логин занят", exception.getMessage()); + } + + @Test + void createUserWithExistEmail() { + User user1 = new User(1L, "user1@email.com", "user1", "user1", + LocalDate.of(1997, 01, 26)); + User user2 = new User(2L, "user1@email.com", "user2", "user2", + LocalDate.of(1997, 01, 26)); + + userStorage.create(user1); + Exception exception = assertThrows(ValidationException.class, () -> { + userStorage.create(user2); + }); + assertEquals("Данный email уже используется", exception.getMessage()); + } + + @Test + void updateUserWithExistLogin() { + User user1 = new User(1L, "user1@email.com", "user1", "user1", + LocalDate.of(1997, 01, 26)); + User user2 = new User(2L, "user2@email.com", "user2", "user2", + LocalDate.of(1997, 01, 26)); + User updateUser1 = new User(1L, "user1new@email.com", "user2", "user1", + LocalDate.of(1997, 01, 26)); + + userStorage.create(user1); + userStorage.create(user2); + + Exception exception = assertThrows(ValidationException.class, () -> { + userStorage.update(updateUser1); + }); + assertEquals("Этот логин уже используется", exception.getMessage()); + } + + @Test + void updateUserWithExistEmail() { + User user1 = new User(1L, "user1@email.com", "user1", "user1", + LocalDate.of(1997, 01, 26)); + User user2 = new User(2L, "user2@email.com", "user2", "user2", + LocalDate.of(1997, 01, 26)); + User updateUser1 = new User(1L, "user2@email.com", "user1", "user1", + LocalDate.of(1997, 01, 26)); + + userStorage.create(user1); + userStorage.create(user2); + + Exception exception = assertThrows(ValidationException.class, () -> { + userStorage.update(updateUser1); + }); + assertEquals("Этот e-mail уже используется", exception.getMessage()); + } +} \ No newline at end of file