Skip to content
Open
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
11 changes: 11 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,17 @@
<version>3.7.2</version>
</dependency>

<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

</dependencies>

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

import jakarta.validation.Valid;
import jakarta.validation.constraints.Positive;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
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;
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;
import java.util.Map;

@RestController
@RequestMapping("/films")
Expand All @@ -25,11 +23,6 @@ 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, UserService userService) {
this.filmService = filmService;
Expand All @@ -38,84 +31,57 @@ public FilmController(FilmService filmService, 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);
return filmService.addFilm(film);
}

@PutMapping
public Film updateFilm(@Valid @RequestBody Film film) {
log.info("Updating film: {}", film);
validateFilm(film);
try {
return filmService.updateFilm(film);
} catch (NotFoundException e) {
log.warn("Film not found: {}", film.getId());
throw new ValidationException("Film not found");
}
return filmService.updateFilm(film);
}

@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);
public ResponseEntity<Map<String, String>> addLike(@PathVariable Long id,
@PathVariable Long userId) {
if (id <= 0 || userId <= 0) {
throw new ValidationException("Film ID and User ID must be positive and non-zero");
}
userService.getUserById(userId);
filmService.addLike(id, userId);
return ResponseEntity.ok(Map.of("message", "Like added"));
}

@DeleteMapping("/{id}/like/{userId}")
public void removeLike(@Positive @PathVariable Long id,
@Positive @PathVariable Long userId) {
log.info("User {} removes like from film {}", userId, id);
public ResponseEntity<Map<String, String>> removeLike(@PathVariable Long id,
@PathVariable Long userId) {
if (id <= 0 || userId <= 0) {
throw new ValidationException("Film ID and User ID must be positive and non-zero");
}
userService.getUserById(userId);
filmService.removeLike(id, userId);
return ResponseEntity.ok(Map.of("message", "Like removed"));
}

@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");
public List<Film> getPopularFilms(@RequestParam(defaultValue = "10") int count) {
if (count <= 0) {
throw new ValidationException("Count must be positive");
}
return filmService.getPopularFilms(count);
}
}


Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package ru.yandex.practicum.filmorate.controller;

import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import ru.yandex.practicum.filmorate.model.Genre;
import ru.yandex.practicum.filmorate.storage.GenreDbStorage;

import java.util.List;

@RestController
@RequestMapping("/genres")
@RequiredArgsConstructor
public class GenreController {
private final GenreDbStorage genreStorage;

@GetMapping
public List<Genre> getAll() {
return genreStorage.findAll();
}

@GetMapping("/{id}")
public Genre getById(@PathVariable int id) {
return genreStorage.findById(id);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,40 +18,30 @@ public class GlobalExceptionHandler {

@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, String>> handleMethodArgumentNotValid(MethodArgumentNotValidException ex) {
log.error("MethodArgumentNotValidException: {}", ex.getMessage(), ex);
String errorMsg = ex.getBindingResult()
.getFieldErrors()
.stream()
log.error("Validation error: {}", ex.getMessage());
String errorMsg = ex.getBindingResult().getFieldErrors().stream()
.findFirst()
.map(FieldError::getDefaultMessage)
.orElse("Validation failed");
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(Map.of("error", errorMsg));
return ResponseEntity.badRequest().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));
log.error("ValidationException: {}", ex.getMessage());
return ResponseEntity.badRequest().body(Map.of("error", ex.getMessage()));
}

@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()));
log.error("NotFoundException: {}", ex.getMessage());
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);
log.error("Unexpected exception: {}", ex.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(Map.of("error", "An unexpected error occurred"));
.body(Map.of("error", "An unexpected error occurred: " + ex.getMessage()));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package ru.yandex.practicum.filmorate.controller;

import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import ru.yandex.practicum.filmorate.model.Mpa;
import ru.yandex.practicum.filmorate.storage.MpaDbStorage;

import java.util.List;

@RestController
@RequestMapping("/mpa")
@RequiredArgsConstructor
public class MpaController {
private final MpaDbStorage mpaStorage;

@GetMapping
public List<Mpa> getAll() {
return mpaStorage.findAll();
}

@GetMapping("/{id}")
public Mpa getById(@PathVariable int id) {
return mpaStorage.findById(id);
}
}
Original file line number Diff line number Diff line change
@@ -1,119 +1,57 @@
package ru.yandex.practicum.filmorate.controller;

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 lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
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;

@RestController
@RequestMapping("/users")
@Slf4j
@Validated
@RequiredArgsConstructor
public class UserController {
private final UserService userService;

public UserController() {
this.userService = new UserService(new InMemoryUserStorage());
@PostMapping
public User create(@Valid @RequestBody User user) {
return userService.createUser(user);
}

@Autowired
public UserController(UserService userService) {
this.userService = userService;
@PutMapping
public User update(@Valid @RequestBody User user) {
return userService.updateUser(user);
}

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

@GetMapping("/{id}")
public User getUser(@Positive @PathVariable Long id) {
log.info("Fetching user with id {}", id);
public User getUser(@PathVariable long id) {
return userService.getUserById(id);
}

@PostMapping
public User createUser(@Valid @RequestBody User user) {
log.info("Creating user: {}", user);
validateUser(user);
return userService.createUser(user);
}

@PutMapping
public User updateUser(@Valid @RequestBody User user) {
log.info("Updating user: {}", user);
validateUser(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(@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);
public void addFriend(@PathVariable long id, @PathVariable long 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);
public void removeFriend(@PathVariable long id, @PathVariable long friendId) {
userService.removeFriend(id, friendId);
}

@GetMapping("/{id}/friends")
public List<User> getFriends(@Positive @PathVariable Long id) {
log.info("Fetching friends of user {}", id);
public List<User> getFriends(@PathVariable long 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);
public List<User> getCommonFriends(@PathVariable long id, @PathVariable long 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