Repository navigation
feat: add film reviews with useful rating #7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
79 changes: 79 additions & 0 deletions
79
src/main/java/ru/yandex/practicum/filmorate/controller/ReviewController.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| 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.web.bind.annotation.*; | ||
| import ru.yandex.practicum.filmorate.model.Review; | ||
| import ru.yandex.practicum.filmorate.service.ReviewService; | ||
|
|
||
| import java.util.Collection; | ||
|
|
||
| @RestController | ||
| @RequestMapping("/reviews") | ||
| @Slf4j | ||
| public class ReviewController { | ||
|
|
||
| private final ReviewService reviewService; | ||
|
|
||
| @Autowired | ||
| public ReviewController(ReviewService reviewService) { | ||
| this.reviewService = reviewService; | ||
| } | ||
|
|
||
| @PostMapping | ||
| public Review create(@Valid @RequestBody Review review) { | ||
| log.info("POST /reviews : userId={}, filmId={}", review.getUserId(), review.getFilmId()); | ||
| return reviewService.add(review); | ||
| } | ||
|
|
||
| @PutMapping | ||
| public Review update(@Valid @RequestBody Review review) { | ||
| log.info("PUT /reviews : id={}", review.getReviewId()); | ||
| return reviewService.update(review); | ||
| } | ||
|
|
||
| @DeleteMapping("/{id}") | ||
| public void delete(@PathVariable long id) { | ||
| log.info("DELETE /reviews/{}", id); | ||
| reviewService.delete(id); | ||
| } | ||
|
|
||
| @GetMapping("/{id}") | ||
| public Review findById(@PathVariable long id) { | ||
| log.info("GET /reviews/{}", id); | ||
| return reviewService.findById(id); | ||
| } | ||
|
|
||
| @GetMapping | ||
| public Collection<Review> findMostUseful( | ||
| @RequestParam(required = false) Long filmId, | ||
| @RequestParam(defaultValue = "10") int count) { | ||
| log.info("GET /reviews?filmId={}&count={}", filmId, count); | ||
| return reviewService.findMostUseful(filmId, count); | ||
| } | ||
|
|
||
| @PutMapping("/{id}/like/{userId}") | ||
| public void addLike(@PathVariable long id, @PathVariable long userId) { | ||
| log.info("PUT /reviews/{}/like/{}", id, userId); | ||
| reviewService.addLike(id, userId); | ||
| } | ||
|
|
||
| @PutMapping("/{id}/dislike/{userId}") | ||
| public void addDislike(@PathVariable long id, @PathVariable long userId) { | ||
| log.info("PUT /reviews/{}/dislike/{}", id, userId); | ||
| reviewService.addDislike(id, userId); | ||
| } | ||
|
|
||
| @DeleteMapping("/{id}/like/{userId}") | ||
| public void removeLike(@PathVariable long id, @PathVariable long userId) { | ||
| log.info("DELETE /reviews/{}/like/{}", id, userId); | ||
| reviewService.removeLike(id, userId); | ||
| } | ||
|
|
||
| @DeleteMapping("/{id}/dislike/{userId}") | ||
| public void removeDislike(@PathVariable long id, @PathVariable long userId) { | ||
| log.info("DELETE /reviews/{}/dislike/{}", id, userId); | ||
| reviewService.removeDislike(id, userId); | ||
| } | ||
| } |
24 changes: 24 additions & 0 deletions
24
src/main/java/ru/yandex/practicum/filmorate/model/Review.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| package ru.yandex.practicum.filmorate.model; | ||
|
|
||
| import jakarta.validation.constraints.NotBlank; | ||
| import jakarta.validation.constraints.NotNull; | ||
| import lombok.Data; | ||
|
|
||
| @Data | ||
| public class Review { | ||
| private Long reviewId; | ||
|
|
||
| @NotBlank(message = "Текст отзыва не может быть пустым") | ||
| private String content; | ||
|
|
||
| @NotNull(message = "Тип отзыва должен быть указан") | ||
| private Boolean isPositive; | ||
|
|
||
| @NotNull(message = "Автор отзыва должен быть указан") | ||
| private Long userId; | ||
|
|
||
| @NotNull(message = "Фильм должен быть указан") | ||
| private Long filmId; | ||
|
|
||
| private int useful; | ||
| } |
118 changes: 118 additions & 0 deletions
118
src/main/java/ru/yandex/practicum/filmorate/service/ReviewService.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,118 @@ | ||
| package ru.yandex.practicum.filmorate.service; | ||
|
|
||
| import lombok.extern.slf4j.Slf4j; | ||
| import org.springframework.beans.factory.annotation.Autowired; | ||
| import org.springframework.beans.factory.annotation.Qualifier; | ||
| 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.Review; | ||
| import ru.yandex.practicum.filmorate.storage.film.FilmStorage; | ||
| import ru.yandex.practicum.filmorate.storage.review.ReviewStorage; | ||
| import ru.yandex.practicum.filmorate.storage.user.UserStorage; | ||
|
|
||
| import java.util.Collection; | ||
|
|
||
| @Service | ||
| @Slf4j | ||
| public class ReviewService { | ||
|
|
||
| private final ReviewStorage reviewStorage; | ||
| private final FilmStorage filmStorage; | ||
| private final UserStorage userStorage; | ||
|
|
||
| @Autowired | ||
| public ReviewService(ReviewStorage reviewStorage, | ||
| @Qualifier("filmDbStorage") FilmStorage filmStorage, | ||
| @Qualifier("userDbStorage") UserStorage userStorage) { | ||
| this.reviewStorage = reviewStorage; | ||
| this.filmStorage = filmStorage; | ||
| this.userStorage = userStorage; | ||
| } | ||
|
|
||
| private Review getReviewOrThrow(long id) { | ||
| return reviewStorage.findById(id) | ||
| .orElseThrow(() -> new NotFoundException("Отзыв с id=" + id + " не найден")); | ||
| } | ||
|
|
||
| private void checkUserExists(long id) { | ||
| userStorage.findById(id) | ||
| .orElseThrow(() -> new NotFoundException("Пользователь с id=" + id + " не найден")); | ||
| } | ||
|
|
||
| private void checkFilmExists(long id) { | ||
| filmStorage.findById(id) | ||
| .orElseThrow(() -> new NotFoundException("Фильм с id=" + id + " не найден")); | ||
| } | ||
|
|
||
| public Review add(Review review) { | ||
| checkUserExists(review.getUserId()); | ||
| checkFilmExists(review.getFilmId()); | ||
| Review created = reviewStorage.add(review); | ||
| log.debug("Пользователь id={} оставил отзыв id={} к фильму id={}", | ||
| created.getUserId(), created.getReviewId(), created.getFilmId()); | ||
| return created; | ||
| } | ||
|
|
||
| // Автор, фильм и рейтинг полезности не меняются — только текст и тип отзыва | ||
| public Review update(Review review) { | ||
| if (review.getReviewId() == null) { | ||
| throw new ValidationException("Не указан id отзыва"); | ||
| } | ||
| getReviewOrThrow(review.getReviewId()); | ||
| return reviewStorage.update(review); | ||
| } | ||
|
|
||
| public void delete(long id) { | ||
| getReviewOrThrow(id); | ||
| reviewStorage.delete(id); | ||
| log.debug("Отзыв id={} удалён", id); | ||
| } | ||
|
|
||
| public Review findById(long id) { | ||
| return getReviewOrThrow(id); | ||
| } | ||
|
|
||
| public Collection<Review> findMostUseful(Long filmId, int count) { | ||
| if (count <= 0) { | ||
| throw new ValidationException( | ||
| "Количество отзывов должно быть положительным числом, получено: " + count | ||
| ); | ||
| } | ||
| if (filmId != null) { | ||
| checkFilmExists(filmId); | ||
| } | ||
| return reviewStorage.findMostUseful(filmId, count); | ||
| } | ||
|
|
||
| public void addLike(long reviewId, long userId) { | ||
| addRating(reviewId, userId, true); | ||
| } | ||
|
|
||
| public void addDislike(long reviewId, long userId) { | ||
| addRating(reviewId, userId, false); | ||
| } | ||
|
|
||
| public void removeLike(long reviewId, long userId) { | ||
| removeRating(reviewId, userId, true); | ||
| } | ||
|
|
||
| public void removeDislike(long reviewId, long userId) { | ||
| removeRating(reviewId, userId, false); | ||
| } | ||
|
|
||
| // Повторная оценка заменяет предыдущую: лайк после дизлайка меняет рейтинг на +2 | ||
| private void addRating(long reviewId, long userId, boolean isUseful) { | ||
| getReviewOrThrow(reviewId); | ||
| checkUserExists(userId); | ||
| reviewStorage.addRating(reviewId, userId, isUseful); | ||
| log.debug("Пользователь id={} оценил отзыв id={}: полезный={}", userId, reviewId, isUseful); | ||
| } | ||
|
|
||
| private void removeRating(long reviewId, long userId, boolean isUseful) { | ||
| getReviewOrThrow(reviewId); | ||
| checkUserExists(userId); | ||
| reviewStorage.removeRating(reviewId, userId, isUseful); | ||
| log.debug("Пользователь id={} убрал оценку отзыва id={}: полезный={}", userId, reviewId, isUseful); | ||
| } | ||
| } | ||
24 changes: 24 additions & 0 deletions
24
src/main/java/ru/yandex/practicum/filmorate/storage/mapper/ReviewRowMapper.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| package ru.yandex.practicum.filmorate.storage.mapper; | ||
|
|
||
| import org.springframework.jdbc.core.RowMapper; | ||
| import org.springframework.stereotype.Component; | ||
| import ru.yandex.practicum.filmorate.model.Review; | ||
|
|
||
| import java.sql.ResultSet; | ||
| import java.sql.SQLException; | ||
|
|
||
| @Component | ||
| public class ReviewRowMapper implements RowMapper<Review> { | ||
|
|
||
| @Override | ||
| public Review mapRow(ResultSet rs, int rowNum) throws SQLException { | ||
| Review review = new Review(); | ||
| review.setReviewId(rs.getLong("id")); | ||
| review.setContent(rs.getString("content")); | ||
| review.setIsPositive(rs.getBoolean("is_positive")); | ||
| review.setUserId(rs.getLong("user_id")); | ||
| review.setFilmId(rs.getLong("film_id")); | ||
| review.setUseful(rs.getInt("useful")); | ||
| return review; | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Можно добавить запрет на самолайк
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
запрет на самолайк сломает автотесты Практикума: в тесте «Add like to review» автор лайкает собственный отзыв и ожидается 200. Поэтому оставляю как в спецификации, без ограничения.