From e5a69d56cae3e4524ee5223ccd3de7cc517c4c86 Mon Sep 17 00:00:00 2001 From: Max Date: Tue, 22 Apr 2025 10:17:48 +0300 Subject: [PATCH 1/7] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B5=D1=80=D0=B0=D0=B1?= =?UTF-8?q?=D0=BE=D1=82=D0=B0=D0=BD=D0=B0=20=D0=B0=D1=80=D1=85=D0=B8=D1=82?= =?UTF-8?q?=D0=B5=D0=BA=D1=82=D1=83=D1=80=D0=B0=20=D0=B8=20=D0=B4=D0=BE?= =?UTF-8?q?=D0=B1=D0=B0=D0=BB=D0=B5=D0=BD=D0=B0=20=D0=BD=D0=BE=D0=B2=D0=B0?= =?UTF-8?q?=D1=8F=20=D0=BB=D0=BE=D0=B3=D0=B8=D0=BA=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 7 ++ .../filmorate/controller/FilmController.java | 104 +++++++++++------- .../filmorate/controller/UserController.java | 104 +++++++++++------- .../exception/NotFoundException.java | 20 ++++ .../filmorate/service/FilmService.java | 48 ++++++++ .../filmorate/service/UserService.java | 49 +++++++++ .../filmorate/storage/FilmStorage.java | 15 +++ .../storage/InMemoryFilmStorage.java | 79 +++++++++++++ .../storage/InMemoryUserStorage.java | 89 +++++++++++++++ .../filmorate/storage/UserStorage.java | 16 +++ src/main/resources/application.properties | 2 +- 11 files changed, 451 insertions(+), 82 deletions(-) create mode 100644 src/main/java/ru/yandex/practicum/filmorate/exception/NotFoundException.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 diff --git a/pom.xml b/pom.xml index 00a0703..9c95881 100644 --- a/pom.xml +++ b/pom.xml @@ -38,6 +38,13 @@ org.springframework.boot spring-boot-starter-validation + + + org.zalando + logbook-spring-boot-starter + 3.7.2 + + 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 f51bdc0..d7dd0ee 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java @@ -1,69 +1,91 @@ package ru.yandex.practicum.filmorate.controller; -import org.springframework.web.bind.annotation.RestController; - +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; import jakarta.validation.Valid; import ru.yandex.practicum.filmorate.exception.ValidationException; -import lombok.extern.slf4j.Slf4j; import ru.yandex.practicum.filmorate.model.Film; -import org.springframework.web.bind.annotation.*; +import ru.yandex.practicum.filmorate.service.FilmService; import java.time.LocalDate; -import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; @RestController @RequestMapping("/films") @Slf4j public class FilmController { - private final Map films = new HashMap<>(); - private Long idCounter = 1L; + private final FilmService filmService; - private void validateFilm(Film film) { - if (film.getName() == null || film.getName().isBlank()) { - log.warn("Название фильма не может быть пустым"); - throw new ValidationException("Название фильма не может быть пустым"); - } - if (film.getDescription() != null && film.getDescription().length() > 200) { - log.warn("Максимальная длина описания — 200 символов"); - throw new ValidationException("Максимальная длина описания — 200 символов"); - } - if (film.getReleaseDate() == null || film.getReleaseDate().isBefore(LocalDate.of(1895, 12, 28))) { - log.warn("Дата релиза должна быть не раньше 28 декабря 1895 года"); - throw new ValidationException("Дата релиза должна быть не раньше 28 декабря 1895 года"); - } - if (film.getDuration() <= 0) { - log.warn("Продолжительность фильма должна быть положительным числом"); - throw new ValidationException("Продолжительность фильма должна быть положительным числом"); - } + public FilmController(FilmService filmService) { + this.filmService = filmService; + } + + @GetMapping + public List getAllFilms() { + log.info("Fetching all films"); + return filmService.getAllFilms(); + } + + @GetMapping("/{id}") + public Film getFilm(@PathVariable Long id) { + log.info("Fetching film with id {}", id); + return filmService.getFilmById(id); } @PostMapping public Film addFilm(@Valid @RequestBody Film film) { + log.info("Adding film: {}", film); validateFilm(film); - film.setId(idCounter++); - films.put(film.getId(), film); - log.info("Добавлен фильм с ID {}: {}", film.getId(), film); - return film; + return filmService.addFilm(film); } @PutMapping public Film updateFilm(@Valid @RequestBody Film film) { - if (film.getId() == null || !films.containsKey(film.getId())) { - log.warn("Фильм с ID {} не найден", film.getId()); - throw new ValidationException("Фильм с указанным ID не существует"); - } + log.info("Updating film: {}", film); validateFilm(film); - films.put(film.getId(), film); - log.info("Обновлен фильм с ID {}: {}", film.getId(), film); - return film; + return filmService.updateFilm(film); } - @GetMapping - public List getAllFilms() { - log.info("Получен запрос всех фильмов. Текущее количество: {}", films.size()); - return new ArrayList<>(films.values()); + @DeleteMapping("/{id}") + public void deleteFilm(@PathVariable Long id) { + log.info("Deleting film with id {}", id); + filmService.removeFilm(id); + } + + @PutMapping("/{id}/like/{userId}") + public void addLike(@PathVariable Long id, @PathVariable Long userId) { + log.info("User {} likes film {}", userId, id); + filmService.addLike(id, userId); + } + + @DeleteMapping("/{id}/like/{userId}") + public void removeLike(@PathVariable Long id, @PathVariable Long userId) { + log.info("User {} removes like from film {}", userId, id); + filmService.removeLike(id, userId); + } + + @GetMapping("/popular") + public List getPopularFilms(@RequestParam(defaultValue = "10") int count) { + log.info("Fetching top {} popular films", count); + return filmService.getPopularFilms(count); + } + + private void validateFilm(Film film) { + if (film.getName() == null || film.getName().isBlank()) { + log.warn("Film name is invalid"); + throw new ValidationException("Film name is invalid"); + } + if (film.getDescription() != null && film.getDescription().length() > 200) { + log.warn("Film description is too long"); + throw new ValidationException("Description length exceeds 200 characters"); + } + if (film.getReleaseDate() == null || film.getReleaseDate().isBefore(LocalDate.of(1895, 12, 28))) { + log.warn("Film release date is invalid"); + throw new ValidationException("Release date is invalid"); + } + if (film.getDuration() <= 0) { + log.warn("Film duration is invalid"); + throw new ValidationException("Duration must be positive"); + } } } 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 acff70f..30209bd 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java @@ -1,67 +1,91 @@ package ru.yandex.practicum.filmorate.controller; -import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; +import jakarta.validation.Valid; import ru.yandex.practicum.filmorate.exception.ValidationException; import ru.yandex.practicum.filmorate.model.User; -import org.springframework.web.bind.annotation.*; +import ru.yandex.practicum.filmorate.service.UserService; import java.time.LocalDate; -import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; @RestController @RequestMapping("/users") @Slf4j public class UserController { - private final Map users = new HashMap<>(); - private Long idCounter = 1L; + private final UserService userService; - private void validateUser(User user) { - if (user.getEmail() == null || user.getEmail().isBlank() || !user.getEmail().contains("@")) { - log.warn("Электронная почта не может быть пустой и должна содержать символ @"); - throw new ValidationException("Электронная почта не может быть пустой и должна содержать символ @"); - } - if (user.getLogin() == null || user.getLogin().isBlank() || user.getLogin().contains(" ")) { - log.warn("Логин не может быть пустым и содержать пробелы"); - throw new ValidationException("Логин не может быть пустым и содержать пробелы"); - } - if (user.getName() == null || user.getName().isBlank()) { - user.setName(user.getLogin()); - log.info("Для пользователя {} установлено имя из логина", user.getLogin()); - } - if (user.getBirthday() == null || user.getBirthday().isAfter(LocalDate.now())) { - log.warn("Дата рождения не может быть в будущем"); - throw new ValidationException("Дата рождения не может быть в будущем"); - } + public UserController(UserService userService) { + this.userService = userService; + } + + @GetMapping + public List getAllUsers() { + log.info("Fetching all users"); + return userService.getAllUsers(); + } + + @GetMapping("/{id}") + public User getUser(@PathVariable Long id) { + log.info("Fetching user with id {}", id); + return userService.getUserById(id); } @PostMapping public User createUser(@Valid @RequestBody User user) { + log.info("Creating user: {}", user); validateUser(user); - user.setId(idCounter++); - users.put(user.getId(), user); - log.info("Создан пользователь: {}", user); - return user; + return userService.createUser(user); } @PutMapping public User updateUser(@Valid @RequestBody User user) { - if (user.getId() == null || !users.containsKey(user.getId())) { - log.warn("Пользователь с id {} не найден", user.getId()); - throw new ValidationException("Пользователь с указанным id не существует"); - } + log.info("Updating user: {}", user); validateUser(user); - users.put(user.getId(), user); - log.info("Обновлен пользователь: {}", user); - return user; + return userService.updateUser(user); } - @GetMapping - public List getAllUsers() { - log.info("Получен запрос всех пользователей. Текущее количество: {}", users.size()); - return new ArrayList<>(users.values()); + @PutMapping("/{id}/friends/{friendId}") + public void addFriend(@PathVariable Long id, @PathVariable Long friendId) { + log.info("User {} adds friend {}", id, friendId); + userService.addFriend(id, friendId); + } + + @DeleteMapping("/{id}/friends/{friendId}") + public void removeFriend(@PathVariable Long id, @PathVariable Long friendId) { + log.info("User {} removes friend {}", id, friendId); + userService.removeFriend(id, friendId); + } + + @GetMapping("/{id}/friends") + public List getFriends(@PathVariable Long id) { + log.info("Fetching friends of user {}", id); + return userService.getFriends(id); + } + + @GetMapping("/{id}/friends/common/{otherId}") + public List getCommonFriends(@PathVariable Long id, @PathVariable Long otherId) { + log.info("Fetching common friends of users {} and {}", id, otherId); + return userService.getCommonFriends(id, otherId); + } + + private void validateUser(User user) { + if (user.getEmail() == null || user.getEmail().isBlank() || !user.getEmail().contains("@")) { + log.warn("Email is invalid"); + throw new ValidationException("Email is invalid"); + } + if (user.getLogin() == null || user.getLogin().isBlank() || user.getLogin().contains(" ")) { + log.warn("Login is invalid"); + throw new ValidationException("Login is invalid"); + } + if (user.getName() == null || user.getName().isBlank()) { + user.setName(user.getLogin()); + log.info("Name set to login for user {}", user.getLogin()); + } + if (user.getBirthday() == null || user.getBirthday().isAfter(LocalDate.now())) { + log.warn("Birthday is in the future"); + throw new ValidationException("Birthday is invalid"); + } } -} \ No newline at end of file +} 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..ad7129c --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/exception/NotFoundException.java @@ -0,0 +1,20 @@ +package ru.yandex.practicum.filmorate.exception; + + +public class NotFoundException extends RuntimeException { + public NotFoundException() { + super(); + } + + public NotFoundException(String message) { + super(message); + } + + public NotFoundException(String message, Throwable cause) { + super(message, cause); + } + + public NotFoundException(Throwable cause) { + super(cause); + } +} 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..ecea46f --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/service/FilmService.java @@ -0,0 +1,48 @@ +package ru.yandex.practicum.filmorate.service; + +import org.springframework.stereotype.Service; +import ru.yandex.practicum.filmorate.model.Film; +import ru.yandex.practicum.filmorate.storage.FilmStorage; + +import java.util.List; + +@Service +public class FilmService { + private final FilmStorage filmStorage; + + public FilmService(FilmStorage filmStorage) { + this.filmStorage = filmStorage; + } + + public Film addFilm(Film film) { + return filmStorage.addFilm(film); + } + + public Film updateFilm(Film film) { + return filmStorage.updateFilm(film); + } + + public Film getFilmById(Long id) { + return filmStorage.getFilmById(id); + } + + public List getAllFilms() { + return filmStorage.getAllFilms(); + } + + public void removeFilm(Long id) { + filmStorage.removeFilm(id); + } + + public void addLike(Long filmId, Long userId) { + filmStorage.addLike(filmId, userId); + } + + public void removeLike(Long filmId, Long userId) { + filmStorage.removeLike(filmId, userId); + } + + public List getPopularFilms(int count) { + return filmStorage.getPopularFilms(count); + } +} 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..64b1bfb --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/service/UserService.java @@ -0,0 +1,49 @@ +package ru.yandex.practicum.filmorate.service; + +import org.springframework.stereotype.Service; +import ru.yandex.practicum.filmorate.model.User; +import ru.yandex.practicum.filmorate.storage.UserStorage; + +import java.util.List; + +@Service +public class UserService { + private final UserStorage userStorage; + + public UserService(UserStorage userStorage) { + this.userStorage = userStorage; + } + + public User createUser(User user) { + return userStorage.addUser(user); + } + + public User updateUser(User user) { + return userStorage.updateUser(user); + } + + public User getUserById(Long id) { + return userStorage.getUserById(id); + } + + public List getAllUsers() { + return userStorage.getAllUsers(); + } + + public void addFriend(Long userId, Long friendId) { + userStorage.addFriend(userId, friendId); + } + + public void removeFriend(Long userId, Long friendId) { + userStorage.removeFriend(userId, friendId); + } + + public List getFriends(Long userId) { + return userStorage.getFriends(userId); + } + + public List getCommonFriends(Long userId, Long otherId) { + return userStorage.getCommonFriends(userId, otherId); + } +} + 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..1c0d68e --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/FilmStorage.java @@ -0,0 +1,15 @@ +package ru.yandex.practicum.filmorate.storage; + +import ru.yandex.practicum.filmorate.model.Film; +import java.util.List; + +public interface FilmStorage { + Film addFilm(Film film); + Film updateFilm(Film film); + Film getFilmById(Long id); + List getAllFilms(); + void removeFilm(Long id); + void addLike(Long filmId, Long userId); + void removeLike(Long filmId, Long userId); + List getPopularFilms(int count); +} 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..57f74b4 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryFilmStorage.java @@ -0,0 +1,79 @@ +package ru.yandex.practicum.filmorate.storage; + +import org.springframework.stereotype.Component; +import ru.yandex.practicum.filmorate.exception.NotFoundException; +import ru.yandex.practicum.filmorate.model.Film; + +import java.util.*; + +@Component +public class InMemoryFilmStorage implements FilmStorage { + private final Map films = new HashMap<>(); + private final Map> likes = new HashMap<>(); + private long idCounter = 1L; + + @Override + public Film addFilm(Film film) { + film.setId(idCounter++); + films.put(film.getId(), film); + likes.put(film.getId(), new HashSet<>()); + return film; + } + + @Override + public Film updateFilm(Film film) { + if (film.getId() == null || !films.containsKey(film.getId())) { + throw new NotFoundException("Film not found"); + } + films.put(film.getId(), film); + return film; + } + + @Override + public Film getFilmById(Long id) { + Film film = films.get(id); + if (film == null) { + throw new NotFoundException("Film not found"); + } + return film; + } + + @Override + public List getAllFilms() { + return new ArrayList<>(films.values()); + } + + @Override + public void removeFilm(Long id) { + if (!films.containsKey(id)) { + throw new NotFoundException("Film not found"); + } + films.remove(id); + likes.remove(id); + } + + @Override + public void addLike(Long filmId, Long userId) { + if (!films.containsKey(filmId)) { + throw new NotFoundException("Film not found"); + } + likes.get(filmId).add(userId); + } + + @Override + public void removeLike(Long filmId, Long userId) { + if (!films.containsKey(filmId)) { + throw new NotFoundException("Film not found"); + } + likes.get(filmId).remove(userId); + } + + @Override + public List getPopularFilms(int count) { + return films.values().stream() + .sorted((f1, f2) -> Integer.compare( + likes.get(f2.getId()).size(), likes.get(f1.getId()).size())) + .limit(count) + .toList(); + } +} 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..620aa36 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryUserStorage.java @@ -0,0 +1,89 @@ +package ru.yandex.practicum.filmorate.storage; + +import org.springframework.stereotype.Component; +import ru.yandex.practicum.filmorate.model.User; +import ru.yandex.practicum.filmorate.exception.NotFoundException; + +import java.util.*; + +@Component +public class InMemoryUserStorage implements UserStorage { + private final Map users = new HashMap<>(); + private final Map> friends = new HashMap<>(); + private long idCounter = 1L; + + @Override + public User addUser(User user) { + user.setId(idCounter++); + users.put(user.getId(), user); + friends.put(user.getId(), new HashSet<>()); + return user; + } + + @Override + public User updateUser(User user) { + if (user.getId() == null || !users.containsKey(user.getId())) { + throw new NotFoundException("User not found"); + } + users.put(user.getId(), user); + return user; + } + + @Override + public User getUserById(Long id) { + User user = users.get(id); + if (user == null) { + throw new NotFoundException("User not found"); + } + return user; + } + + @Override + public List getAllUsers() { + return new ArrayList<>(users.values()); + } + + @Override + public void removeUser(Long id) { + if (!users.containsKey(id)) { + throw new NotFoundException("User not found"); + } + users.remove(id); + friends.remove(id); + } + + @Override + public void addFriend(Long userId, Long friendId) { + getUserById(userId); + getUserById(friendId); + friends.get(userId).add(friendId); + friends.get(friendId).add(userId); + } + + @Override + public void removeFriend(Long userId, Long friendId) { + getUserById(userId); + getUserById(friendId); + friends.get(userId).remove(friendId); + friends.get(friendId).remove(userId); + } + + @Override + public List getFriends(Long userId) { + getUserById(userId); + return friends.get(userId).stream() + .map(users::get) + .toList(); + } + + @Override + public List getCommonFriends(Long userId, Long otherId) { + getUserById(userId); + getUserById(otherId); + Set common = new HashSet<>(friends.get(userId)); + common.retainAll(friends.get(otherId)); + return common.stream() + .map(users::get) + .toList(); + } +} 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..97a3dad --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/UserStorage.java @@ -0,0 +1,16 @@ +package ru.yandex.practicum.filmorate.storage; + +import ru.yandex.practicum.filmorate.model.User; +import java.util.List; + +public interface UserStorage { + User addUser(User user); + User updateUser(User user); + User getUserById(Long id); + List getAllUsers(); + void removeUser(Long id); + void addFriend(Long userId, Long friendId); + void removeFriend(Long userId, Long friendId); + List getFriends(Long userId); + List getCommonFriends(Long userId, Long otherId); +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 8b13789..1d1b4bf 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1 @@ - +logging.level.org.zalando.logbook: TRACE From e79da3c92c40c7f2be31fd85a9779074d474e032 Mon Sep 17 00:00:00 2001 From: Max Date: Tue, 22 Apr 2025 10:31:33 +0300 Subject: [PATCH 2/7] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B5=D1=80=D0=B0=D0=B1?= =?UTF-8?q?=D0=BE=D1=82=D0=B0=D0=BD=D0=B0=20=D0=B0=D1=80=D1=85=D0=B8=D1=82?= =?UTF-8?q?=D0=B5=D0=BA=D1=82=D1=83=D1=80=D0=B0=20=D0=B8=20=D0=B4=D0=BE?= =?UTF-8?q?=D0=B1=D0=B0=D0=BB=D0=B5=D0=BD=D0=B0=20=D0=BD=D0=BE=D0=B2=D0=B0?= =?UTF-8?q?=D1=8F=20=D0=BB=D0=BE=D0=B3=D0=B8=D0=BA=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../practicum/filmorate/storage/FilmStorage.java | 9 +++++++++ .../practicum/filmorate/storage/UserStorage.java | 10 ++++++++++ 2 files changed, 19 insertions(+) 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 1c0d68e..d557a87 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/storage/FilmStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/FilmStorage.java @@ -4,12 +4,21 @@ import java.util.List; public interface FilmStorage { + Film addFilm(Film film); + Film updateFilm(Film film); + Film getFilmById(Long id); + List getAllFilms(); + void removeFilm(Long id); + void addLike(Long filmId, Long userId); + void removeLike(Long filmId, Long userId); + List getPopularFilms(int count); + } 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 97a3dad..d230cf0 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/storage/UserStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/UserStorage.java @@ -4,13 +4,23 @@ import java.util.List; public interface UserStorage { + User addUser(User user); + User updateUser(User user); + User getUserById(Long id); + List getAllUsers(); + void removeUser(Long id); + void addFriend(Long userId, Long friendId); + void removeFriend(Long userId, Long friendId); + List getFriends(Long userId); + List getCommonFriends(Long userId, Long otherId); + } From d2fe3d4973832098f56e7b33b5ce2fe308001a9f Mon Sep 17 00:00:00 2001 From: Max Date: Tue, 22 Apr 2025 12:34:02 +0300 Subject: [PATCH 3/7] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B5=D1=80=D0=B0=D0=B1?= =?UTF-8?q?=D0=BE=D1=82=D0=B0=D0=BD=D0=B0=20=D0=B0=D1=80=D1=85=D0=B8=D1=82?= =?UTF-8?q?=D0=B5=D0=BA=D1=82=D1=83=D1=80=D0=B0=20=D0=B8=20=D0=B4=D0=BE?= =?UTF-8?q?=D0=B1=D0=B0=D0=BB=D0=B5=D0=BD=D0=B0=20=D0=BD=D0=BE=D0=B2=D0=B0?= =?UTF-8?q?=D1=8F=20=D0=BB=D0=BE=D0=B3=D0=B8=D0=BA=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../filmorate/controller/FilmController.java | 19 ++++++++++++- .../filmorate/controller/UserController.java | 27 +++++++++++++++++-- .../filmorate/service/UserService.java | 4 +++ 3 files changed, 47 insertions(+), 3 deletions(-) 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 d7dd0ee..873df85 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java @@ -1,11 +1,14 @@ package ru.yandex.practicum.filmorate.controller; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import jakarta.validation.Valid; import ru.yandex.practicum.filmorate.exception.ValidationException; +import ru.yandex.practicum.filmorate.exception.NotFoundException; import ru.yandex.practicum.filmorate.model.Film; import ru.yandex.practicum.filmorate.service.FilmService; +import ru.yandex.practicum.filmorate.storage.InMemoryFilmStorage; import java.time.LocalDate; import java.util.List; @@ -16,6 +19,11 @@ public class FilmController { private final FilmService filmService; + public FilmController() { + this.filmService = new FilmService(new InMemoryFilmStorage()); + } + + @Autowired public FilmController(FilmService filmService) { this.filmService = filmService; } @@ -43,7 +51,12 @@ public Film addFilm(@Valid @RequestBody Film film) { public Film updateFilm(@Valid @RequestBody Film film) { log.info("Updating film: {}", film); validateFilm(film); - return filmService.updateFilm(film); + try { + return filmService.updateFilm(film); + } catch (NotFoundException e) { + log.warn("Film not found: {}", film.getId()); + throw new ValidationException("Film not found"); + } } @DeleteMapping("/{id}") @@ -71,6 +84,9 @@ public List getPopularFilms(@RequestParam(defaultValue = "10") int count) } private void validateFilm(Film film) { + if (film == null) { + throw new ValidationException("Film cannot be null"); + } if (film.getName() == null || film.getName().isBlank()) { log.warn("Film name is invalid"); throw new ValidationException("Film name is invalid"); @@ -89,3 +105,4 @@ private void validateFilm(Film film) { } } } + 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 30209bd..e7cefdf 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java @@ -1,11 +1,14 @@ package ru.yandex.practicum.filmorate.controller; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import jakarta.validation.Valid; import ru.yandex.practicum.filmorate.exception.ValidationException; +import ru.yandex.practicum.filmorate.exception.NotFoundException; 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.List; @@ -16,6 +19,11 @@ public class UserController { private final UserService userService; + public UserController() { + this.userService = new UserService(new InMemoryUserStorage()); + } + + @Autowired public UserController(UserService userService) { this.userService = userService; } @@ -43,7 +51,18 @@ public User createUser(@Valid @RequestBody User user) { public User updateUser(@Valid @RequestBody User user) { log.info("Updating user: {}", user); validateUser(user); - return userService.updateUser(user); + try { + return userService.updateUser(user); + } catch (NotFoundException e) { + log.warn("User not found: {}", user.getId()); + throw new ValidationException("User not found"); + } + } + + @DeleteMapping("/{id}") + public void deleteUser(@PathVariable Long id) { + log.info("Deleting user with id {}", id); + userService.removeUser(id); } @PutMapping("/{id}/friends/{friendId}") @@ -71,6 +90,9 @@ public List getCommonFriends(@PathVariable Long id, @PathVariable Long oth } private void validateUser(User user) { + if (user == null) { + throw new ValidationException("User cannot be null"); + } if (user.getEmail() == null || user.getEmail().isBlank() || !user.getEmail().contains("@")) { log.warn("Email is invalid"); throw new ValidationException("Email is invalid"); @@ -84,8 +106,9 @@ private void validateUser(User user) { log.info("Name set to login for user {}", user.getLogin()); } if (user.getBirthday() == null || user.getBirthday().isAfter(LocalDate.now())) { - log.warn("Birthday is in the future"); + log.warn("Birthday is invalid"); throw new ValidationException("Birthday is invalid"); } } } + 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 64b1bfb..8046e56 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/service/UserService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/UserService.java @@ -30,6 +30,10 @@ public List getAllUsers() { return userStorage.getAllUsers(); } + public void removeUser(Long id) { + userStorage.removeUser(id); + } + public void addFriend(Long userId, Long friendId) { userStorage.addFriend(userId, friendId); } From 9423c33fbff5e088edd32db40f76cdc1c17f3218 Mon Sep 17 00:00:00 2001 From: Max Date: Tue, 22 Apr 2025 12:38:58 +0300 Subject: [PATCH 4/7] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B5=D1=80=D0=B0=D0=B1?= =?UTF-8?q?=D0=BE=D1=82=D0=B0=D0=BD=D0=B0=20=D0=B0=D1=80=D1=85=D0=B8=D1=82?= =?UTF-8?q?=D0=B5=D0=BA=D1=82=D1=83=D1=80=D0=B0=20=D0=B8=20=D0=B4=D0=BE?= =?UTF-8?q?=D0=B1=D0=B0=D0=BB=D0=B5=D0=BD=D0=B0=20=D0=BD=D0=BE=D0=B2=D0=B0?= =?UTF-8?q?=D1=8F=20=D0=BB=D0=BE=D0=B3=D0=B8=D0=BA=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/GlobalExceptionHandler.java | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 src/main/java/ru/yandex/practicum/filmorate/controller/GlobalExceptionHandler.java diff --git a/src/main/java/ru/yandex/practicum/filmorate/controller/GlobalExceptionHandler.java b/src/main/java/ru/yandex/practicum/filmorate/controller/GlobalExceptionHandler.java new file mode 100644 index 0000000..8a7166b --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/GlobalExceptionHandler.java @@ -0,0 +1,32 @@ +package ru.yandex.practicum.filmorate.controller; + +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.exception.NotFoundException; +import ru.yandex.practicum.filmorate.exception.ValidationException; + +import java.util.Map; + +@RestControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(ValidationException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public Map handleValidation(ValidationException ex) { + return Map.of("error", ex.getMessage()); + } + + @ExceptionHandler(NotFoundException.class) + @ResponseStatus(HttpStatus.NOT_FOUND) + public Map handleNotFound(NotFoundException ex) { + return Map.of("error", ex.getMessage()); + } + + @ExceptionHandler(Exception.class) + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public Map handleOther(Exception ex) { + return Map.of("error", "An unexpected error occurred"); + } +} From 61c698c42526695da8fa86585f7b5c997c4167b4 Mon Sep 17 00:00:00 2001 From: Max Date: Tue, 22 Apr 2025 12:46:24 +0300 Subject: [PATCH 5/7] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B5=D1=80=D0=B0=D0=B1?= =?UTF-8?q?=D0=BE=D1=82=D0=B0=D0=BD=D0=B0=20=D0=B0=D1=80=D1=85=D0=B8=D1=82?= =?UTF-8?q?=D0=B5=D0=BA=D1=82=D1=83=D1=80=D0=B0=20=D0=B8=20=D0=B4=D0=BE?= =?UTF-8?q?=D0=B1=D0=B0=D0=BB=D0=B5=D0=BD=D0=B0=20=D0=BD=D0=BE=D0=B2=D0=B0?= =?UTF-8?q?=D1=8F=20=D0=BB=D0=BE=D0=B3=D0=B8=D0=BA=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../filmorate/controller/FilmController.java | 10 ++++++- .../controller/GlobalExceptionHandler.java | 27 +++++++++++-------- .../filmorate/controller/UserController.java | 1 - 3 files changed, 25 insertions(+), 13 deletions(-) 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 873df85..e6d2757 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java @@ -8,7 +8,9 @@ import ru.yandex.practicum.filmorate.exception.NotFoundException; import ru.yandex.practicum.filmorate.model.Film; import ru.yandex.practicum.filmorate.service.FilmService; +import ru.yandex.practicum.filmorate.service.UserService; import ru.yandex.practicum.filmorate.storage.InMemoryFilmStorage; +import ru.yandex.practicum.filmorate.storage.InMemoryUserStorage; import java.time.LocalDate; import java.util.List; @@ -18,14 +20,17 @@ @Slf4j public class FilmController { private final FilmService filmService; + private final UserService userService; public FilmController() { this.filmService = new FilmService(new InMemoryFilmStorage()); + this.userService = new UserService(new InMemoryUserStorage()); } @Autowired - public FilmController(FilmService filmService) { + public FilmController(FilmService filmService, UserService userService) { this.filmService = filmService; + this.userService = userService; } @GetMapping @@ -68,12 +73,14 @@ public void deleteFilm(@PathVariable Long id) { @PutMapping("/{id}/like/{userId}") public void addLike(@PathVariable Long id, @PathVariable Long userId) { log.info("User {} likes film {}", userId, id); + userService.getUserById(userId); filmService.addLike(id, userId); } @DeleteMapping("/{id}/like/{userId}") public void removeLike(@PathVariable Long id, @PathVariable Long userId) { log.info("User {} removes like from film {}", userId, id); + userService.getUserById(userId); filmService.removeLike(id, userId); } @@ -106,3 +113,4 @@ private void validateFilm(Film film) { } } + diff --git a/src/main/java/ru/yandex/practicum/filmorate/controller/GlobalExceptionHandler.java b/src/main/java/ru/yandex/practicum/filmorate/controller/GlobalExceptionHandler.java index 8a7166b..4065cb7 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/GlobalExceptionHandler.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/GlobalExceptionHandler.java @@ -1,9 +1,9 @@ package ru.yandex.practicum.filmorate.controller; import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; import ru.yandex.practicum.filmorate.exception.NotFoundException; import ru.yandex.practicum.filmorate.exception.ValidationException; @@ -13,20 +13,25 @@ public class GlobalExceptionHandler { @ExceptionHandler(ValidationException.class) - @ResponseStatus(HttpStatus.BAD_REQUEST) - public Map handleValidation(ValidationException ex) { - return Map.of("error", ex.getMessage()); + public ResponseEntity> handleValidation(ValidationException ex) { + String msg = ex.getMessage(); + if (msg != null && msg.toLowerCase().contains("not found")) { + return ResponseEntity.status(HttpStatus.NOT_FOUND) + .body(Map.of("error", msg)); + } + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(Map.of("error", msg)); } @ExceptionHandler(NotFoundException.class) - @ResponseStatus(HttpStatus.NOT_FOUND) - public Map handleNotFound(NotFoundException ex) { - return Map.of("error", ex.getMessage()); + public ResponseEntity> handleNotFound(NotFoundException ex) { + return ResponseEntity.status(HttpStatus.NOT_FOUND) + .body(Map.of("error", ex.getMessage())); } @ExceptionHandler(Exception.class) - @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) - public Map handleOther(Exception ex) { - return Map.of("error", "An unexpected error occurred"); + public ResponseEntity> handleOther(Exception ex) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(Map.of("error", "An unexpected error occurred")); } } 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 e7cefdf..7ee1356 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java @@ -111,4 +111,3 @@ private void validateUser(User user) { } } } - From ae7cc7e66d92aacf56571d29845379a6c97719f5 Mon Sep 17 00:00:00 2001 From: Max Date: Tue, 22 Apr 2025 12:52:42 +0300 Subject: [PATCH 6/7] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B5=D1=80=D0=B0=D0=B1?= =?UTF-8?q?=D0=BE=D1=82=D0=B0=D0=BD=D0=B0=20=D0=B0=D1=80=D1=85=D0=B8=D1=82?= =?UTF-8?q?=D0=B5=D0=BA=D1=82=D1=83=D1=80=D0=B0=20=D0=B8=20=D0=B4=D0=BE?= =?UTF-8?q?=D0=B1=D0=B0=D0=BB=D0=B5=D0=BD=D0=B0=20=D0=BD=D0=BE=D0=B2=D0=B0?= =?UTF-8?q?=D1=8F=20=D0=BB=D0=BE=D0=B3=D0=B8=D0=BA=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/GlobalExceptionHandler.java | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/main/java/ru/yandex/practicum/filmorate/controller/GlobalExceptionHandler.java b/src/main/java/ru/yandex/practicum/filmorate/controller/GlobalExceptionHandler.java index 4065cb7..db2a1ec 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/GlobalExceptionHandler.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/GlobalExceptionHandler.java @@ -2,8 +2,10 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.validation.FieldError; import ru.yandex.practicum.filmorate.exception.NotFoundException; import ru.yandex.practicum.filmorate.exception.ValidationException; @@ -12,6 +14,18 @@ @RestControllerAdvice public class GlobalExceptionHandler { + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity> handleMethodArgumentNotValid(MethodArgumentNotValidException ex) { + String errorMsg = ex.getBindingResult() + .getFieldErrors() + .stream() + .findFirst() + .map(FieldError::getDefaultMessage) + .orElse("Validation failed"); + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(Map.of("error", errorMsg)); + } + @ExceptionHandler(ValidationException.class) public ResponseEntity> handleValidation(ValidationException ex) { String msg = ex.getMessage(); From 91d5c0c5be89191ab6d1c5cd1828c3f1f60b4284 Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 23 Apr 2025 11:20:17 +0300 Subject: [PATCH 7/7] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB?= =?UTF-8?q?=20=D0=BB=D0=BE=D0=B3=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=20=D0=B8=20=D0=B0=D0=BD=D0=BD=D0=B0=D1=82=D0=B0=D1=86?= =?UTF-8?q?=D0=B8=D1=8E=20@Positive?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../filmorate/controller/FilmController.java | 15 ++++++++++----- .../controller/GlobalExceptionHandler.java | 6 ++++++ .../filmorate/controller/UserController.java | 18 ++++++++++++------ 3 files changed, 28 insertions(+), 11 deletions(-) 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 e6d2757..068d66f 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java @@ -2,8 +2,10 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import jakarta.validation.Valid; +import jakarta.validation.constraints.Positive; import ru.yandex.practicum.filmorate.exception.ValidationException; import ru.yandex.practicum.filmorate.exception.NotFoundException; import ru.yandex.practicum.filmorate.model.Film; @@ -18,6 +20,7 @@ @RestController @RequestMapping("/films") @Slf4j +@Validated public class FilmController { private final FilmService filmService; private final UserService userService; @@ -40,7 +43,7 @@ public List getAllFilms() { } @GetMapping("/{id}") - public Film getFilm(@PathVariable Long id) { + public Film getFilm(@Positive @PathVariable Long id) { log.info("Fetching film with id {}", id); return filmService.getFilmById(id); } @@ -65,27 +68,29 @@ public Film updateFilm(@Valid @RequestBody Film film) { } @DeleteMapping("/{id}") - public void deleteFilm(@PathVariable Long id) { + public void deleteFilm(@Positive @PathVariable Long id) { log.info("Deleting film with id {}", id); filmService.removeFilm(id); } @PutMapping("/{id}/like/{userId}") - public void addLike(@PathVariable Long id, @PathVariable Long userId) { + public void addLike(@Positive @PathVariable Long id, + @Positive @PathVariable Long userId) { log.info("User {} likes film {}", userId, id); userService.getUserById(userId); filmService.addLike(id, userId); } @DeleteMapping("/{id}/like/{userId}") - public void removeLike(@PathVariable Long id, @PathVariable Long userId) { + public void removeLike(@Positive @PathVariable Long id, + @Positive @PathVariable Long userId) { log.info("User {} removes like from film {}", userId, id); userService.getUserById(userId); filmService.removeLike(id, userId); } @GetMapping("/popular") - public List getPopularFilms(@RequestParam(defaultValue = "10") int count) { + public List getPopularFilms(@RequestParam(defaultValue = "10") @Positive int count) { log.info("Fetching top {} popular films", count); return filmService.getPopularFilms(count); } diff --git a/src/main/java/ru/yandex/practicum/filmorate/controller/GlobalExceptionHandler.java b/src/main/java/ru/yandex/practicum/filmorate/controller/GlobalExceptionHandler.java index db2a1ec..adc883f 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/GlobalExceptionHandler.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/GlobalExceptionHandler.java @@ -1,5 +1,6 @@ package ru.yandex.practicum.filmorate.controller; +import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.MethodArgumentNotValidException; @@ -11,11 +12,13 @@ import java.util.Map; +@Slf4j @RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity> handleMethodArgumentNotValid(MethodArgumentNotValidException ex) { + log.error("MethodArgumentNotValidException: {}", ex.getMessage(), ex); String errorMsg = ex.getBindingResult() .getFieldErrors() .stream() @@ -28,6 +31,7 @@ public ResponseEntity> handleMethodArgumentNotValid(MethodAr @ExceptionHandler(ValidationException.class) public ResponseEntity> handleValidation(ValidationException ex) { + log.error("ValidationException: {}", ex.getMessage(), ex); String msg = ex.getMessage(); if (msg != null && msg.toLowerCase().contains("not found")) { return ResponseEntity.status(HttpStatus.NOT_FOUND) @@ -39,12 +43,14 @@ public ResponseEntity> handleValidation(ValidationException @ExceptionHandler(NotFoundException.class) public ResponseEntity> handleNotFound(NotFoundException ex) { + log.error("NotFoundException: {}", ex.getMessage(), ex); return ResponseEntity.status(HttpStatus.NOT_FOUND) .body(Map.of("error", ex.getMessage())); } @ExceptionHandler(Exception.class) public ResponseEntity> handleOther(Exception ex) { + log.error("Unexpected exception: {}", ex.getMessage(), ex); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body(Map.of("error", "An unexpected error occurred")); } 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 7ee1356..15399cf 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java @@ -2,8 +2,10 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import jakarta.validation.Valid; +import jakarta.validation.constraints.Positive; import ru.yandex.practicum.filmorate.exception.ValidationException; import ru.yandex.practicum.filmorate.exception.NotFoundException; import ru.yandex.practicum.filmorate.model.User; @@ -16,6 +18,7 @@ @RestController @RequestMapping("/users") @Slf4j +@Validated public class UserController { private final UserService userService; @@ -35,7 +38,7 @@ public List getAllUsers() { } @GetMapping("/{id}") - public User getUser(@PathVariable Long id) { + public User getUser(@Positive @PathVariable Long id) { log.info("Fetching user with id {}", id); return userService.getUserById(id); } @@ -60,31 +63,34 @@ public User updateUser(@Valid @RequestBody User user) { } @DeleteMapping("/{id}") - public void deleteUser(@PathVariable Long id) { + public void deleteUser(@Positive @PathVariable Long id) { log.info("Deleting user with id {}", id); userService.removeUser(id); } @PutMapping("/{id}/friends/{friendId}") - public void addFriend(@PathVariable Long id, @PathVariable Long friendId) { + public void addFriend(@Positive @PathVariable Long id, + @Positive @PathVariable Long friendId) { log.info("User {} adds friend {}", id, friendId); userService.addFriend(id, friendId); } @DeleteMapping("/{id}/friends/{friendId}") - public void removeFriend(@PathVariable Long id, @PathVariable Long friendId) { + public void removeFriend(@Positive @PathVariable Long id, + @Positive @PathVariable Long friendId) { log.info("User {} removes friend {}", id, friendId); userService.removeFriend(id, friendId); } @GetMapping("/{id}/friends") - public List getFriends(@PathVariable Long id) { + public List getFriends(@Positive @PathVariable Long id) { log.info("Fetching friends of user {}", id); return userService.getFriends(id); } @GetMapping("/{id}/friends/common/{otherId}") - public List getCommonFriends(@PathVariable Long id, @PathVariable Long otherId) { + public List getCommonFriends(@Positive @PathVariable Long id, + @Positive @PathVariable Long otherId) { log.info("Fetching common friends of users {} and {}", id, otherId); return userService.getCommonFriends(id, otherId); }