Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>

<dependency>
<groupId>org.zalando</groupId>
<artifactId>logbook-spring-boot-starter</artifactId>
<version>3.7.2</version>
</dependency>

</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,69 +1,121 @@
package ru.yandex.practicum.filmorate.controller;

import org.springframework.web.bind.annotation.RestController;

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 lombok.extern.slf4j.Slf4j;
import ru.yandex.practicum.filmorate.exception.NotFoundException;
import ru.yandex.practicum.filmorate.model.Film;
import org.springframework.web.bind.annotation.*;
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.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/films")
@Slf4j
@Validated
public class FilmController {
private final Map<Long, Film> films = new HashMap<>();
private Long idCounter = 1L;
private final FilmService filmService;
private final UserService userService;

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() {
this.filmService = new FilmService(new InMemoryFilmStorage());
this.userService = new UserService(new InMemoryUserStorage());
}

@Autowired
public FilmController(FilmService filmService, UserService userService) {
this.filmService = filmService;
this.userService = userService;
}

@GetMapping
public List<Film> getAllFilms() {
log.info("Fetching all films");
return filmService.getAllFilms();
}

@GetMapping("/{id}")
public Film getFilm(@Positive @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;
try {
return filmService.updateFilm(film);
} catch (NotFoundException e) {
log.warn("Film not found: {}", film.getId());
throw new ValidationException("Film not found");
}
}

@GetMapping
public List<Film> getAllFilms() {
log.info("Получен запрос всех фильмов. Текущее количество: {}", films.size());
return new ArrayList<>(films.values());
@DeleteMapping("/{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(@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(@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<Film> getPopularFilms(@RequestParam(defaultValue = "10") @Positive int count) {
log.info("Fetching top {} popular films", count);
return filmService.getPopularFilms(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");
}
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");
}
}
}


Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
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;
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;

import java.util.Map;

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

В класс хорошо бы добавить логирование. Это полезно при разборе логов. Помогает быстрее понять когда и в каком случае возникла та или иная ошибка.


@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, String>> handleMethodArgumentNotValid(MethodArgumentNotValidException ex) {
log.error("MethodArgumentNotValidException: {}", ex.getMessage(), 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<Map<String, String>> 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)
.body(Map.of("error", msg));
}
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(Map.of("error", msg));
}

@ExceptionHandler(NotFoundException.class)
public ResponseEntity<Map<String, String>> 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<Map<String, String>> 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"));
}
}
Original file line number Diff line number Diff line change
@@ -1,67 +1,119 @@
package ru.yandex.practicum.filmorate.controller;

import jakarta.validation.Valid;
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;
import org.springframework.web.bind.annotation.*;
import ru.yandex.practicum.filmorate.service.UserService;
import ru.yandex.practicum.filmorate.storage.InMemoryUserStorage;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/users")
@Slf4j
@Validated
public class UserController {
private final Map<Long, User> 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() {
this.userService = new UserService(new InMemoryUserStorage());
}

@Autowired
public UserController(UserService userService) {
this.userService = userService;
}

@GetMapping
public List<User> getAllUsers() {
log.info("Fetching all users");
return userService.getAllUsers();
}

@GetMapping("/{id}")
public User getUser(@Positive @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;
try {
return userService.updateUser(user);
} catch (NotFoundException e) {
log.warn("User not found: {}", user.getId());
throw new ValidationException("User not found");
}
}

@GetMapping
public List<User> getAllUsers() {
log.info("Получен запрос всех пользователей. Текущее количество: {}", users.size());
return new ArrayList<>(users.values());
@DeleteMapping("/{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(@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(@Positive @PathVariable Long id,
@Positive @PathVariable Long friendId) {
log.info("User {} removes friend {}", id, friendId);
userService.removeFriend(id, friendId);
}

@GetMapping("/{id}/friends")
public List<User> getFriends(@Positive @PathVariable Long id) {
log.info("Fetching friends of user {}", id);
return userService.getFriends(id);
}

@GetMapping("/{id}/friends/common/{otherId}")
public List<User> getCommonFriends(@Positive @PathVariable Long id,
@Positive @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 == 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");
}
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 invalid");
throw new ValidationException("Birthday is invalid");
}
}
}
}
Loading