From f228861b44c9375615a27a9251f51a9d43841795 Mon Sep 17 00:00:00 2001 From: Sergey Lyutov Date: Tue, 17 Feb 2026 12:41:03 +0300 Subject: [PATCH 01/47] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B2=20=D0=91=D0=94=20=D1=82=D0=B0?= =?UTF-8?q?=D0=B1=D0=BB=D0=B8=D1=86=D1=8B=20=D0=9E=D1=82=D0=B7=D1=8B=D0=B2?= =?UTF-8?q?=D0=BE=D0=B2(reviews)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/schema.sql | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index 0fb557a..8660231 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -75,3 +75,23 @@ CREATE INDEX IF NOT EXISTS idx_friends_friend_id ON friends (friend_id); CREATE INDEX IF NOT EXISTS idx_films_film_name ON films (film_name); +-- Добавление таблицы Отзывов +CREATE TABLE reviews ( + id bigint NOT NULL AUTO_INCREMENT PRIMARY KEY, + content varchar(2048) NOT NULL, + is_positive boolean NOT NULL, + user_id bigint NOT NULL, + film_id bigint NOT NULL, + useful bigint NOT NULL DEFAULT 0 +); + +-- Добавление внешних ключей +ALTER TABLE reviews ADD CONSTRAINT fk_reviews_user_id FOREIGN KEY (user_id) + REFERENCES users (id) ON DELETE CASCADE; + +ALTER TABLE reviews ADD CONSTRAINT fk_reviews_film_id FOREIGN KEY (film_id) + REFERENCES films (id) ON DELETE CASCADE; + +-- Индексы для быстрого поиска отзывов по фильму или автору +CREATE INDEX IF NOT EXISTS idx_reviews_film_id ON reviews (film_id); +CREATE INDEX IF NOT EXISTS idx_reviews_user_id ON reviews (user_id); From e7143249afe1b30435f22403c8e3c9d6606ac0a4 Mon Sep 17 00:00:00 2001 From: Sergey Lyutov Date: Tue, 17 Feb 2026 20:01:24 +0300 Subject: [PATCH 02/47] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B2=20=D0=91=D0=94=20=D1=82=D0=B0?= =?UTF-8?q?=D0=B1=D0=BB=D0=B8=D1=86=D1=8B=20=D0=9E=D1=82=D0=B7=D1=8B=D0=B2?= =?UTF-8?q?=D0=BE=D0=B2(reviews)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ReviewController.java | 69 +++++++++ .../filmorate/dal/ReviewStorage.java | 31 ++++ .../filmorate/dal/database/BaseDbStorage.java | 13 ++ .../dal/database/ReviewDbStorage.java | 107 +++++++++++++ .../dal/database/sql/ReviewQueries.java | 37 +++++ .../dal/rowmappers/ReviewRowMapper.java | 26 ++++ .../practicum/filmorate/dto/ReviewDto.java | 15 ++ .../request/create/ReviewCreateRequest.java | 20 +++ .../request/update/ReviewUpdateRequest.java | 16 ++ .../filmorate/mapper/ReviewMapper.java | 42 +++++ .../practicum/filmorate/model/Review.java | 28 ++++ .../filmorate/servise/ReviewService.java | 145 ++++++++++++++++++ src/main/resources/schema.sql | 19 ++- 13 files changed, 566 insertions(+), 2 deletions(-) create mode 100644 src/main/java/ru/yandex/practicum/filmorate/controller/ReviewController.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/dal/ReviewStorage.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/dal/database/ReviewDbStorage.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/dal/database/sql/ReviewQueries.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/dal/rowmappers/ReviewRowMapper.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/dto/ReviewDto.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/dto/request/create/ReviewCreateRequest.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/dto/request/update/ReviewUpdateRequest.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/mapper/ReviewMapper.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/model/Review.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/servise/ReviewService.java diff --git a/src/main/java/ru/yandex/practicum/filmorate/controller/ReviewController.java b/src/main/java/ru/yandex/practicum/filmorate/controller/ReviewController.java new file mode 100644 index 0000000..b92772a --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/ReviewController.java @@ -0,0 +1,69 @@ +package ru.yandex.practicum.filmorate.controller; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; +import ru.yandex.practicum.filmorate.dto.ReviewDto; +import ru.yandex.practicum.filmorate.dto.request.create.ReviewCreateRequest; +import ru.yandex.practicum.filmorate.dto.request.update.ReviewUpdateRequest; +import ru.yandex.practicum.filmorate.servise.ReviewService; + +import java.util.Collection; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/reviews") +public class ReviewController { + + private final ReviewService reviewService; + + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + public ReviewDto create(@RequestBody @Valid ReviewCreateRequest request) { + return reviewService.createReview(request); + } + + @PutMapping + public ReviewDto update(@RequestBody @Valid ReviewUpdateRequest request) { + return reviewService.updateReview(request); + } + + @DeleteMapping("/{id}") + public void delete(@PathVariable Long id) { + reviewService.deleteReview(id); + } + + @GetMapping("/{id}") + public ReviewDto findById(@PathVariable Long id) { + return reviewService.findReviewById(id); + } + + @GetMapping + public Collection findByFilmId( + @RequestParam(required = false) Long filmId, + @RequestParam(defaultValue = "10") int count + ) { + return reviewService.findReviewsByFilmId(filmId, count); + } + + @PutMapping("/{id}/like/{userId}") + public void addLike(@PathVariable Long id, @PathVariable Long userId) { + reviewService.addReviewLike(id, userId); + } + + @PutMapping("/{id}/dislike/{userId}") + public void addDislike(@PathVariable Long id, @PathVariable Long userId) { + reviewService.addReviewDislike(id, userId); + } + + @DeleteMapping("/{id}/like/{userId}") + public void removeLike(@PathVariable Long id, @PathVariable Long userId) { + reviewService.removeReviewLike(id, userId); + } + + @DeleteMapping("/{id}/dislike/{userId}") + public void removeDislike(@PathVariable Long id, @PathVariable Long userId) { + reviewService.removeReviewDislike(id, userId); + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/ReviewStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/ReviewStorage.java new file mode 100644 index 0000000..3a13cb1 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/ReviewStorage.java @@ -0,0 +1,31 @@ +package ru.yandex.practicum.filmorate.dal; + +import ru.yandex.practicum.filmorate.model.Review; + +import java.util.Collection; +import java.util.Optional; + +public interface ReviewStorage { + + Review createReview(Review review); + + Review updateReview(Review review); + + void deleteReview(long id); + + Optional findReviewById(long id); + + Collection findReviewsByFilmId(Long filmId, int count); + + void addReviewLike(long id, long userId); + + void addReviewDislike(long id, long userId); + + void removeReviewLike(long id, long userId); + + void removeReviewDislike(long id, long userId); + + boolean checkReviewIsNotPresent(long id); + + Optional getReviewLikeOrDislike(long reviewId, long userId); +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/database/BaseDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/database/BaseDbStorage.java index 05d792d..b0ebc38 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/database/BaseDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/database/BaseDbStorage.java @@ -66,4 +66,17 @@ protected void updateWithControl(String query, Object... params) { throw new InternalServerException("Не удалось обновить данные"); } } + + protected List findManyByQuery(String query, Object... params) { + return jdbc.query(query, rowMapper, params); + } + + protected Optional findOneByQuery(String query, Object... params) { + try { + T result = jdbc.queryForObject(query, rowMapper, params); + return Optional.ofNullable(result); + } catch (EmptyResultDataAccessException ignored) { + return Optional.empty(); + } + } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/database/ReviewDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/database/ReviewDbStorage.java new file mode 100644 index 0000000..f389b08 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/database/ReviewDbStorage.java @@ -0,0 +1,107 @@ +package ru.yandex.practicum.filmorate.dal.database; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; +import ru.yandex.practicum.filmorate.dal.ReviewStorage; +import ru.yandex.practicum.filmorate.model.Review; + +import java.util.Collection; +import java.util.Optional; + +import static ru.yandex.practicum.filmorate.dal.database.sql.ReviewQueries.*; + +@Repository +@Qualifier("reviewDbStorage") +public class ReviewDbStorage extends BaseDbStorage implements ReviewStorage { + + @Autowired + public ReviewDbStorage(JdbcTemplate jdbc, @Qualifier("reviewRowMapper") RowMapper mapper) { + super(jdbc, mapper); + } + + @Override + @Transactional + public Review createReview(Review review) { + long id = insertWithKeyHolder( + SQL_REVIEWS_INSERT, + review.getContent(), + review.getIsPositive(), + review.getUserId(), + review.getFilmId() + ); + review.setId(id); + return review; + } + + @Override + public Review updateReview(Review review) { + updateWithControl( + SQL_REVIEWS_UPDATE, + review.getContent(), + review.getIsPositive(), + review.getId() + ); + return findReviewById(review.getId()).get(); + } + + @Override + public void deleteReview(long id) { + updateWithControl(SQL_REVIEWS_DELETE, id); + } + + @Override + public Optional findReviewById(long id) { + return findOneByIdInTable(id, "reviews"); + } + + @Override + public Collection findReviewsByFilmId(Long filmId, int count) { + if (filmId == null) { + return findManyByQuery(SQL_REVIEWS_FIND_ALL, count); + } + return findManyByQuery(SQL_REVIEWS_FIND_BY_FILM_ID, filmId, count); + } + + @Override + @Transactional + public void addReviewLike(long id, long userId) { + updateWithControl(SQL_REVIEWS_ADD_LIKE, id, userId); + updateWithControl(SQL_REVIEWS_UPDATE_USEFUL, 1, id); + } + + @Override + @Transactional + public void addReviewDislike(long id, long userId) { + updateWithControl(SQL_REVIEWS_ADD_DISLIKE, id, userId); + updateWithControl(SQL_REVIEWS_UPDATE_USEFUL, -1, id); + } + + @Override + @Transactional + public void removeReviewLike(long id, long userId) { + updateWithControl(SQL_REVIEWS_DELETE_LIKE, id, userId); + updateWithControl(SQL_REVIEWS_UPDATE_USEFUL, -1, id); + } + + @Override + @Transactional + public void removeReviewDislike(long id, long userId) { + updateWithControl(SQL_REVIEWS_DELETE_DISLIKE, id, userId); + updateWithControl(SQL_REVIEWS_UPDATE_USEFUL, 1, id); + } + + @Override + public boolean checkReviewIsNotPresent(long id){ + return checkIdIsNotPresentInTable(id, "reviews"); + } + + public Optional getReviewLikeOrDislike(long reviewId, long userId) { + return jdbc.query(SQL_REVIEWS_GET_LIKE_OR_DISLIKE, (rs, rowNum) -> rs.getBoolean("is_like"), reviewId, userId) + .stream() + .findFirst(); + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/database/sql/ReviewQueries.java b/src/main/java/ru/yandex/practicum/filmorate/dal/database/sql/ReviewQueries.java new file mode 100644 index 0000000..20c180f --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/database/sql/ReviewQueries.java @@ -0,0 +1,37 @@ +package ru.yandex.practicum.filmorate.dal.database.sql; + +public class ReviewQueries { + + public static final String SQL_REVIEWS_INSERT = + "INSERT INTO reviews (content, is_positive, user_id, film_id) VALUES (?, ?, ?, ?)"; + + public static final String SQL_REVIEWS_UPDATE = + "UPDATE reviews SET content = ?, is_positive = ? WHERE id = ?"; + + public static final String SQL_REVIEWS_DELETE = + "DELETE FROM reviews WHERE id = ?"; + + public static final String SQL_REVIEWS_FIND_ALL = + "SELECT * FROM reviews ORDER BY useful DESC LIMIT ?"; + + public static final String SQL_REVIEWS_FIND_BY_FILM_ID = + "SELECT * FROM reviews WHERE film_id = ? ORDER BY useful DESC LIMIT ?"; + + public static final String SQL_REVIEWS_UPDATE_USEFUL = + "UPDATE reviews SET useful = useful + ? WHERE id = ?"; + + public static final String SQL_REVIEWS_ADD_LIKE = + "INSERT INTO review_likes (review_id, user_id, is_like) VALUES (?, ?, TRUE)"; + + public static final String SQL_REVIEWS_ADD_DISLIKE = + "INSERT INTO review_likes (review_id, user_id, is_like) VALUES (?, ?, FALSE)"; + + public static final String SQL_REVIEWS_DELETE_LIKE = + "DELETE FROM review_likes WHERE review_id = ? AND user_id = ? AND is_like = TRUE"; + + public static final String SQL_REVIEWS_DELETE_DISLIKE = + "DELETE FROM review_likes WHERE review_id = ? AND user_id = ? AND is_like = FALSE"; + + public static final String SQL_REVIEWS_GET_LIKE_OR_DISLIKE = + "SELECT is_like FROM review_likes WHERE review_id = ? AND user_id = ?"; +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/rowmappers/ReviewRowMapper.java b/src/main/java/ru/yandex/practicum/filmorate/dal/rowmappers/ReviewRowMapper.java new file mode 100644 index 0000000..a63d99c --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/rowmappers/ReviewRowMapper.java @@ -0,0 +1,26 @@ +package ru.yandex.practicum.filmorate.dal.rowmappers; + +import org.springframework.beans.factory.annotation.Qualifier; +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 +@Qualifier("reviewRowMapper") +public class ReviewRowMapper implements RowMapper { + + @Override + public Review mapRow(ResultSet resultSet, int rowNum) throws SQLException { + return Review.builder() + .id(resultSet.getLong("id")) + .content(resultSet.getString("content")) + .isPositive(resultSet.getBoolean("is_positive")) + .userId(resultSet.getLong("user_id")) + .filmId(resultSet.getLong("film_id")) + .useful(resultSet.getLong("useful")) + .build(); + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/dto/ReviewDto.java b/src/main/java/ru/yandex/practicum/filmorate/dto/ReviewDto.java new file mode 100644 index 0000000..f4b4a86 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/dto/ReviewDto.java @@ -0,0 +1,15 @@ +package ru.yandex.practicum.filmorate.dto; + +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class ReviewDto { + private Long reviewId; + private String content; + private Boolean isPositive; + private Long userId; + private Long filmId; + private Long useful; +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/dto/request/create/ReviewCreateRequest.java b/src/main/java/ru/yandex/practicum/filmorate/dto/request/create/ReviewCreateRequest.java new file mode 100644 index 0000000..b7ad14f --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/dto/request/create/ReviewCreateRequest.java @@ -0,0 +1,20 @@ +package ru.yandex.practicum.filmorate.dto.request.create; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Data +public class ReviewCreateRequest { + @NotBlank(message = "Контент отзыва не может быть пустым.") + private String content; + + @NotNull(message = "Тип отзыва должен быть указан.") + private Boolean isPositive; + + @NotNull(message = "userId должен быть указан.") + private Long userId; + + @NotNull(message = "filmId должен быть указан.") + private Long filmId; +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/dto/request/update/ReviewUpdateRequest.java b/src/main/java/ru/yandex/practicum/filmorate/dto/request/update/ReviewUpdateRequest.java new file mode 100644 index 0000000..8543192 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/dto/request/update/ReviewUpdateRequest.java @@ -0,0 +1,16 @@ +package ru.yandex.practicum.filmorate.dto.request.update; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Data +public class ReviewUpdateRequest { + @NotNull(message = "reviewId должен быть указан.") + private Long reviewId; + + @NotNull(message = "Контент отзыва не может быть пустым.") + private String content; + + @NotNull(message = "Тип отзыва должен быть указан.") + private Boolean isPositive; +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/mapper/ReviewMapper.java b/src/main/java/ru/yandex/practicum/filmorate/mapper/ReviewMapper.java new file mode 100644 index 0000000..ee8c163 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/mapper/ReviewMapper.java @@ -0,0 +1,42 @@ +package ru.yandex.practicum.filmorate.mapper; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import ru.yandex.practicum.filmorate.dto.ReviewDto; +import ru.yandex.practicum.filmorate.dto.request.create.ReviewCreateRequest; +import ru.yandex.practicum.filmorate.dto.request.update.ReviewUpdateRequest; +import ru.yandex.practicum.filmorate.model.Review; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class ReviewMapper { + + public static Review mapToReview(ReviewCreateRequest request) { + return Review.builder() + .content(request.getContent()) + .isPositive(request.getIsPositive()) + .userId(request.getUserId()) + .filmId(request.getFilmId()) + .build(); + } + + public static Review updateReviewFields(Review review, ReviewUpdateRequest request) { + if (request.getContent() != null) { + review.setContent(request.getContent()); + } + if (request.getIsPositive() != null) { + review.setIsPositive(request.getIsPositive()); + } + return review; + } + + public static ReviewDto mapToReviewDto(Review review) { + return ReviewDto.builder() + .reviewId(review.getId()) + .content(review.getContent()) + .isPositive(review.getIsPositive()) + .userId(review.getUserId()) + .filmId(review.getFilmId()) + .useful(review.getUseful()) + .build(); + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/Review.java b/src/main/java/ru/yandex/practicum/filmorate/model/Review.java new file mode 100644 index 0000000..bcdbbd9 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/model/Review.java @@ -0,0 +1,28 @@ +package ru.yandex.practicum.filmorate.model; + +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.extern.jackson.Jacksonized; + +import java.util.HashMap; +import java.util.Map; + +@Data +@Builder(toBuilder = true) +@Jacksonized +@EqualsAndHashCode(of = "id") +public class Review { + + private Long id; + private String content; + private Boolean isPositive; + private Long userId; + private Long filmId; + + @Builder.Default + private Long useful = 0L; + + @Builder.Default + private Map userLikes = new HashMap<>(); +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/servise/ReviewService.java b/src/main/java/ru/yandex/practicum/filmorate/servise/ReviewService.java new file mode 100644 index 0000000..3e6d16a --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/servise/ReviewService.java @@ -0,0 +1,145 @@ +package ru.yandex.practicum.filmorate.servise; + +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.dal.FilmStorage; +import ru.yandex.practicum.filmorate.dal.ReviewStorage; +import ru.yandex.practicum.filmorate.dal.UserStorage; +import ru.yandex.practicum.filmorate.dto.ReviewDto; +import ru.yandex.practicum.filmorate.dto.request.create.ReviewCreateRequest; +import ru.yandex.practicum.filmorate.dto.request.update.ReviewUpdateRequest; +import ru.yandex.practicum.filmorate.exception.NotFoundException; +import ru.yandex.practicum.filmorate.mapper.ReviewMapper; +import ru.yandex.practicum.filmorate.model.Review; + +import java.util.Collection; +import java.util.Optional; + +@Service +@Slf4j +public class ReviewService { + + private final ReviewStorage reviewStorage; + private final FilmStorage filmStorage; + private final UserStorage userStorage; + + @Autowired + public ReviewService( + @Qualifier("reviewDbStorage") ReviewStorage reviewStorage, + @Qualifier("filmDbStorage") FilmStorage filmStorage, + @Qualifier("userDbStorage") UserStorage userStorage + ) { + this.reviewStorage = reviewStorage; + this.filmStorage = filmStorage; + this.userStorage = userStorage; + } + + public ReviewDto createReview(ReviewCreateRequest request) { + log.info("Добавление нового отзыва пользователем id={} для фильма id={}", request.getUserId(), request.getFilmId()); + + checkUserExists(request.getUserId()); + checkFilmExists(request.getFilmId()); + + Review review = ReviewMapper.mapToReview(request); + review = reviewStorage.createReview(review); + + return ReviewMapper.mapToReviewDto(review); + } + + public ReviewDto updateReview(ReviewUpdateRequest request) { + log.info("Обновление данных об отзыве с id={}", request.getReviewId()); + + Review oldReview = reviewStorage.findReviewById(request.getReviewId()) + .orElseThrow(() -> new NotFoundException("Отзыв с id=" + request.getReviewId() + " не найден.")); + + Review newReview = ReviewMapper.updateReviewFields(oldReview, request); + newReview = reviewStorage.updateReview(newReview); + + return ReviewMapper.mapToReviewDto(newReview); + } + + public void deleteReview(Long id) { + log.info("Удаление отзыва с id={}", id); + checkReviewExists(id); + reviewStorage.deleteReview(id); + } + + public ReviewDto findReviewById(Long id) { + log.info("Получение отзыва с id={}", id); + return reviewStorage.findReviewById(id) + .map(ReviewMapper::mapToReviewDto) + .orElseThrow(() -> new NotFoundException("Отзыв с id=" + id + " не найден.")); + } + + public Collection findReviewsByFilmId(Long filmId, int count) { + log.info("Получение отзывов для фильма с id={} в количестве {}", filmId, count); + return reviewStorage.findReviewsByFilmId(filmId, count).stream() + .map(ReviewMapper::mapToReviewDto) + .toList(); + } + + public void addReviewLike(Long id, Long userId) { + log.info("Пользователь id={} ставит лайк отзыву id={}", userId, id); + checkReviewExists(id); + checkUserExists(userId); + + Optional likeStatus = reviewStorage.getReviewLikeOrDislike(id, userId); + + if (likeStatus.isEmpty()) { + reviewStorage.addReviewLike(id, userId); + } else if (!likeStatus.get()) { + reviewStorage.removeReviewDislike(id, userId); + reviewStorage.addReviewLike(id, userId); + } + + } + + public void addReviewDislike(Long id, Long userId) { + log.info("Пользователь id={} ставит дизлайк отзыву id={}", userId, id); + checkReviewExists(id); + checkUserExists(userId); + Optional likeStatus = reviewStorage.getReviewLikeOrDislike(id, userId); + + if (likeStatus.isEmpty()) { + reviewStorage.addReviewDislike(id, userId); + } else if (likeStatus.get()) { + reviewStorage.removeReviewLike(id, userId); + reviewStorage.addReviewDislike(id, userId); + } + + } + + public void removeReviewLike(Long id, Long userId) { + log.info("Пользователь id={} удаляет лайк у отзыва id={}", userId, id); + checkReviewExists(id); + checkUserExists(userId); + reviewStorage.removeReviewLike(id, userId); + } + + public void removeReviewDislike(Long id, Long userId) { + log.info("Пользователь id={} удаляет дизлайк у отзыва id={}", userId, id); + checkReviewExists(id); + checkUserExists(userId); + reviewStorage.removeReviewDislike(id, userId); + } + + private void checkFilmExists(Long filmId) { + if (filmStorage.checkFilmIsNotPresent(filmId)) { + throw new NotFoundException("Фильм с id=" + filmId + " не найден."); + } + } + + private void checkUserExists(Long userId) { + if (userStorage.checkUserIsNotPresent(userId)) { + throw new NotFoundException("Пользователь с id=" + userId + " не найден."); + } + } + + private void checkReviewExists(Long id) { + if (reviewStorage.checkReviewIsNotPresent(id)) { + throw new NotFoundException("Отзыв с id=" + id + " не найден."); + } + } +} diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index 8660231..0b92bdd 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -85,13 +85,28 @@ CREATE TABLE reviews ( useful bigint NOT NULL DEFAULT 0 ); --- Добавление внешних ключей + ALTER TABLE reviews ADD CONSTRAINT fk_reviews_user_id FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE; ALTER TABLE reviews ADD CONSTRAINT fk_reviews_film_id FOREIGN KEY (film_id) REFERENCES films (id) ON DELETE CASCADE; --- Индексы для быстрого поиска отзывов по фильму или автору + CREATE INDEX IF NOT EXISTS idx_reviews_film_id ON reviews (film_id); CREATE INDEX IF NOT EXISTS idx_reviews_user_id ON reviews (user_id); + +-- Добавление таблицы Лайков для Отзывов +CREATE TABLE review_likes ( + review_id bigint NOT NULL, + user_id bigint NOT NULL, + is_like boolean NOT NULL, + PRIMARY KEY (review_id, user_id) +); + +ALTER TABLE review_likes ADD CONSTRAINT fk_review_likes_review_id FOREIGN KEY (review_id) + REFERENCES reviews (id); + +ALTER TABLE review_likes ADD CONSTRAINT fk_review_likes_user_id FOREIGN KEY (user_id) + REFERENCES users (id); +CREATE INDEX IF NOT EXISTS idx_review_likes_review_id ON review_likes (review_id); \ No newline at end of file From ad22d4675c0c870d9dccae39a89a3ce6e1b3a4af Mon Sep 17 00:00:00 2001 From: Sergey Lyutov Date: Tue, 17 Feb 2026 21:26:57 +0300 Subject: [PATCH 03/47] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B2=20=D0=91=D0=94=20=D1=82=D0=B0?= =?UTF-8?q?=D0=B1=D0=BB=D0=B8=D1=86=D1=8B=20=D0=9E=D1=82=D0=B7=D1=8B=D0=B2?= =?UTF-8?q?=D0=BE=D0=B2(reviews)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../filmorate/dal/ReviewStorage.java | 3 +- .../dal/database/ReviewDbStorage.java | 29 +++++++-- .../dal/database/sql/ReviewQueries.java | 5 +- .../filmorate/servise/ReviewService.java | 4 +- src/main/resources/schema.sql | 62 +++++++++---------- 5 files changed, 64 insertions(+), 39 deletions(-) diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/ReviewStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/ReviewStorage.java index 3a13cb1..9ba884c 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/ReviewStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/ReviewStorage.java @@ -27,5 +27,6 @@ public interface ReviewStorage { boolean checkReviewIsNotPresent(long id); - Optional getReviewLikeOrDislike(long reviewId, long userId); + Optional getReactionsStatus(long reviewId, long userId); } + diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/database/ReviewDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/database/ReviewDbStorage.java index f389b08..f37cae4 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/database/ReviewDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/database/ReviewDbStorage.java @@ -10,6 +10,8 @@ import ru.yandex.practicum.filmorate.model.Review; import java.util.Collection; +import java.util.HashMap; +import java.util.Map; import java.util.Optional; import static ru.yandex.practicum.filmorate.dal.database.sql.ReviewQueries.*; @@ -60,10 +62,16 @@ public Optional findReviewById(long id) { @Override public Collection findReviewsByFilmId(Long filmId, int count) { + Collection reviews; if (filmId == null) { - return findManyByQuery(SQL_REVIEWS_FIND_ALL, count); + reviews = findManyByQuery(SQL_REVIEWS_FIND_ALL, count); + } else { + reviews = findManyByQuery(SQL_REVIEWS_FIND_BY_FILM_ID, filmId, count); } - return findManyByQuery(SQL_REVIEWS_FIND_BY_FILM_ID, filmId, count); + reviews.forEach(review ->{ + review.setUserLikes(findAllReactionsByReviewId(review.getId())); + }); + return reviews; } @Override @@ -99,9 +107,22 @@ public boolean checkReviewIsNotPresent(long id){ return checkIdIsNotPresentInTable(id, "reviews"); } - public Optional getReviewLikeOrDislike(long reviewId, long userId) { - return jdbc.query(SQL_REVIEWS_GET_LIKE_OR_DISLIKE, (rs, rowNum) -> rs.getBoolean("is_like"), reviewId, userId) + public Optional getReactionsStatus(long reviewId, long userId) { + return jdbc.query(SQL_REVIEWS_LIKE_STATUS, (rs, rowNum) -> rs.getBoolean("is_like"), reviewId, userId) .stream() .findFirst(); } + + private Map findAllReactionsByReviewId(long reviewId) { + return jdbc.query(SQL_REVIEWS_GET_LIKES_BY_REVIEW_ID, rs -> { + Map reactions = new HashMap<>(); + while (rs.next()) { + reactions.put( + rs.getLong("user_id"), + rs.getBoolean("is_like") + ); + } + return reactions; + }, reviewId); + } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/database/sql/ReviewQueries.java b/src/main/java/ru/yandex/practicum/filmorate/dal/database/sql/ReviewQueries.java index 20c180f..d8af760 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/database/sql/ReviewQueries.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/database/sql/ReviewQueries.java @@ -32,6 +32,9 @@ public class ReviewQueries { public static final String SQL_REVIEWS_DELETE_DISLIKE = "DELETE FROM review_likes WHERE review_id = ? AND user_id = ? AND is_like = FALSE"; - public static final String SQL_REVIEWS_GET_LIKE_OR_DISLIKE = + public static final String SQL_REVIEWS_LIKE_STATUS = "SELECT is_like FROM review_likes WHERE review_id = ? AND user_id = ?"; + + public static final String SQL_REVIEWS_GET_LIKES_BY_REVIEW_ID = + "SELECT user_id, is_like FROM review_likes WHERE review_id = ?"; } diff --git a/src/main/java/ru/yandex/practicum/filmorate/servise/ReviewService.java b/src/main/java/ru/yandex/practicum/filmorate/servise/ReviewService.java index 3e6d16a..068b350 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/servise/ReviewService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/servise/ReviewService.java @@ -85,7 +85,7 @@ public void addReviewLike(Long id, Long userId) { checkReviewExists(id); checkUserExists(userId); - Optional likeStatus = reviewStorage.getReviewLikeOrDislike(id, userId); + Optional likeStatus = reviewStorage.getReactionsStatus(id, userId); if (likeStatus.isEmpty()) { reviewStorage.addReviewLike(id, userId); @@ -100,7 +100,7 @@ public void addReviewDislike(Long id, Long userId) { log.info("Пользователь id={} ставит дизлайк отзыву id={}", userId, id); checkReviewExists(id); checkUserExists(userId); - Optional likeStatus = reviewStorage.getReviewLikeOrDislike(id, userId); + Optional likeStatus = reviewStorage.getReactionsStatus(id, userId); if (likeStatus.isEmpty()) { reviewStorage.addReviewDislike(id, userId); diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index 0b92bdd..6f022b8 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -1,70 +1,70 @@ DROP ALL OBJECTS; CREATE TABLE users ( - id bigint NOT NULL AUTO_INCREMENT PRIMARY KEY, - user_name varchar(50), - login varchar(50) NOT NULL, - email varchar(50) NOT NULL, - birthday date NOT NULL + id bigint NOT NULL AUTO_INCREMENT PRIMARY KEY, + user_name varchar(50), + login varchar(50) NOT NULL, + email varchar(50) NOT NULL, + birthday date NOT NULL ); CREATE TABLE friends ( - id bigint NOT NULL AUTO_INCREMENT PRIMARY KEY, - user_id bigint NOT NULL, - friend_id bigint NOT NULL + id bigint NOT NULL AUTO_INCREMENT PRIMARY KEY, + user_id bigint NOT NULL, + friend_id bigint NOT NULL ); CREATE TABLE films ( - id bigint NOT NULL AUTO_INCREMENT PRIMARY KEY, - film_name varchar(50) NOT NULL, - description varchar(200) NOT NULL, - release_date date NOT NULL, - duration int NOT NULL, - mpa_id int NOT NULL + id bigint NOT NULL AUTO_INCREMENT PRIMARY KEY, + film_name varchar(50) NOT NULL, + description varchar(200) NOT NULL, + release_date date NOT NULL, + duration int NOT NULL, + mpa_id int NOT NULL ); CREATE TABLE genres ( - id int NOT NULL AUTO_INCREMENT PRIMARY KEY, - genre_name varchar(20) NOT NULL + id int NOT NULL AUTO_INCREMENT PRIMARY KEY, + genre_name varchar(20) NOT NULL ); CREATE TABLE genres_of_films ( - id bigint NOT NULL AUTO_INCREMENT PRIMARY KEY, - genre_id int NOT NULL, - film_id bigint NOT NULL + id bigint NOT NULL AUTO_INCREMENT PRIMARY KEY, + genre_id int NOT NULL, + film_id bigint NOT NULL ); CREATE TABLE mpa ( - id int NOT NULL AUTO_INCREMENT PRIMARY KEY, - mpa_name varchar(5) NOT NULL + id int NOT NULL AUTO_INCREMENT PRIMARY KEY, + mpa_name varchar(5) NOT NULL ); CREATE TABLE likes ( - id bigint NOT NULL AUTO_INCREMENT PRIMARY KEY, - user_id bigint NOT NULL, - film_id bigint NOT NULL + id bigint NOT NULL AUTO_INCREMENT PRIMARY KEY, + user_id bigint NOT NULL, + film_id bigint NOT NULL ); ALTER TABLE friends ADD CONSTRAINT fk_friends_user_id FOREIGN KEY(user_id) -REFERENCES users (id); + REFERENCES users (id); ALTER TABLE friends ADD CONSTRAINT fk_friends_friend_id FOREIGN KEY(friend_id) -REFERENCES users (id); + REFERENCES users (id); ALTER TABLE films ADD CONSTRAINT fk_films_mpa_id FOREIGN KEY(mpa_id) -REFERENCES mpa (id); + REFERENCES mpa (id); ALTER TABLE genres_of_films ADD CONSTRAINT fk_genres_of_films_genre_id FOREIGN KEY(genre_id) -REFERENCES genres (id); + REFERENCES genres (id); ALTER TABLE genres_of_films ADD CONSTRAINT fk_genres_of_films_film_id FOREIGN KEY(film_id) -REFERENCES films (id); + REFERENCES films (id); ALTER TABLE likes ADD CONSTRAINT fk_likes_user_id FOREIGN KEY(user_id) -REFERENCES users (id); + REFERENCES users (id); ALTER TABLE likes ADD CONSTRAINT fk_likes_film_id FOREIGN KEY(film_id) -REFERENCES films (id); + REFERENCES films (id); CREATE INDEX IF NOT EXISTS idx_users_user_name ON users (user_name); CREATE UNIQUE INDEX IF NOT EXISTS idx_users_email ON users (email); From cd6b435d5b8e75e371b234c4e359df0d0bac6b43 Mon Sep 17 00:00:00 2001 From: Sergey Lyutov Date: Tue, 17 Feb 2026 21:43:32 +0300 Subject: [PATCH 04/47] add-reviews ver 1.0 --- .../practicum/filmorate/dal/database/BaseDbStorage.java | 8 -------- .../practicum/filmorate/dal/database/ReviewDbStorage.java | 7 ++++++- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/database/BaseDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/database/BaseDbStorage.java index b0ebc38..74e6f80 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/database/BaseDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/database/BaseDbStorage.java @@ -71,12 +71,4 @@ protected List findManyByQuery(String query, Object... params) { return jdbc.query(query, rowMapper, params); } - protected Optional findOneByQuery(String query, Object... params) { - try { - T result = jdbc.queryForObject(query, rowMapper, params); - return Optional.ofNullable(result); - } catch (EmptyResultDataAccessException ignored) { - return Optional.empty(); - } - } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/database/ReviewDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/database/ReviewDbStorage.java index f37cae4..63125ee 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/database/ReviewDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/database/ReviewDbStorage.java @@ -57,7 +57,12 @@ public void deleteReview(long id) { @Override public Optional findReviewById(long id) { - return findOneByIdInTable(id, "reviews"); + Optional review = findOneByIdInTable(id, "reviews"); + if (review.isPresent()) { + review.get().setUserLikes(findAllReactionsByReviewId(id)); + return review; + } + return Optional.empty(); } @Override From 80fe1bcd3d2075b43dd8ac56a31f06375cf6df5b Mon Sep 17 00:00:00 2001 From: Sergey Lyutov Date: Tue, 17 Feb 2026 22:34:26 +0300 Subject: [PATCH 05/47] add-reviews ver 1.0 --- .../dal/database/ReviewDbStorage.java | 34 +++++++++---------- .../dal/database/sql/ReviewQueries.java | 11 ++++-- 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/database/ReviewDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/database/ReviewDbStorage.java index 63125ee..fb2985d 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/database/ReviewDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/database/ReviewDbStorage.java @@ -56,10 +56,10 @@ public void deleteReview(long id) { } @Override - public Optional findReviewById(long id) { - Optional review = findOneByIdInTable(id, "reviews"); + public Optional findReviewById(long reviewId) { + Optional review = findOneByIdInTable(reviewId, "reviews"); if (review.isPresent()) { - review.get().setUserLikes(findAllReactionsByReviewId(id)); + review.get().setUserLikes(findAllReactionsByReviewId(reviewId)); return review; } return Optional.empty(); @@ -81,35 +81,35 @@ public Collection findReviewsByFilmId(Long filmId, int count) { @Override @Transactional - public void addReviewLike(long id, long userId) { - updateWithControl(SQL_REVIEWS_ADD_LIKE, id, userId); - updateWithControl(SQL_REVIEWS_UPDATE_USEFUL, 1, id); + public void addReviewLike(long reviewId, long userId) { + updateWithControl(SQL_REVIEWS_ADD_LIKE, reviewId, userId); + updateWithControl(SQL_REVIEWS_CALCULATE_USEFUL, reviewId, reviewId); } @Override @Transactional - public void addReviewDislike(long id, long userId) { - updateWithControl(SQL_REVIEWS_ADD_DISLIKE, id, userId); - updateWithControl(SQL_REVIEWS_UPDATE_USEFUL, -1, id); + public void addReviewDislike(long reviewId, long userId) { + updateWithControl(SQL_REVIEWS_ADD_DISLIKE, reviewId, userId); + updateWithControl(SQL_REVIEWS_CALCULATE_USEFUL, reviewId, reviewId); } @Override @Transactional - public void removeReviewLike(long id, long userId) { - updateWithControl(SQL_REVIEWS_DELETE_LIKE, id, userId); - updateWithControl(SQL_REVIEWS_UPDATE_USEFUL, -1, id); + public void removeReviewLike(long reviewId, long userId) { + updateWithControl(SQL_REVIEWS_DELETE_LIKE, reviewId, userId); + updateWithControl(SQL_REVIEWS_CALCULATE_USEFUL, reviewId, reviewId); } @Override @Transactional - public void removeReviewDislike(long id, long userId) { - updateWithControl(SQL_REVIEWS_DELETE_DISLIKE, id, userId); - updateWithControl(SQL_REVIEWS_UPDATE_USEFUL, 1, id); + public void removeReviewDislike(long reviewId, long userId) { + updateWithControl(SQL_REVIEWS_DELETE_DISLIKE, reviewId, userId); + updateWithControl(SQL_REVIEWS_CALCULATE_USEFUL, reviewId, reviewId); } @Override - public boolean checkReviewIsNotPresent(long id){ - return checkIdIsNotPresentInTable(id, "reviews"); + public boolean checkReviewIsNotPresent(long reviewId){ + return checkIdIsNotPresentInTable(reviewId, "reviews"); } public Optional getReactionsStatus(long reviewId, long userId) { diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/database/sql/ReviewQueries.java b/src/main/java/ru/yandex/practicum/filmorate/dal/database/sql/ReviewQueries.java index d8af760..3546403 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/database/sql/ReviewQueries.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/database/sql/ReviewQueries.java @@ -17,9 +17,6 @@ public class ReviewQueries { public static final String SQL_REVIEWS_FIND_BY_FILM_ID = "SELECT * FROM reviews WHERE film_id = ? ORDER BY useful DESC LIMIT ?"; - public static final String SQL_REVIEWS_UPDATE_USEFUL = - "UPDATE reviews SET useful = useful + ? WHERE id = ?"; - public static final String SQL_REVIEWS_ADD_LIKE = "INSERT INTO review_likes (review_id, user_id, is_like) VALUES (?, ?, TRUE)"; @@ -37,4 +34,12 @@ public class ReviewQueries { public static final String SQL_REVIEWS_GET_LIKES_BY_REVIEW_ID = "SELECT user_id, is_like FROM review_likes WHERE review_id = ?"; + + public static final String SQL_REVIEWS_CALCULATE_USEFUL = """ + UPDATE reviews SET useful = ( + SELECT COALESCE(SUM(CASE WHEN is_like = TRUE THEN 1 ELSE -1 END), 0) + FROM review_likes + WHERE review_id = ? + ) WHERE id = ? + """; } From 5880c1b208cad743a4101df82bd5a5498df390e6 Mon Sep 17 00:00:00 2001 From: Sergey Lyutov Date: Tue, 17 Feb 2026 22:47:56 +0300 Subject: [PATCH 06/47] add-reviews ver 1.0 --- .../practicum/filmorate/dal/database/ReviewDbStorage.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/database/ReviewDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/database/ReviewDbStorage.java index fb2985d..10372db 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/database/ReviewDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/database/ReviewDbStorage.java @@ -73,7 +73,7 @@ public Collection findReviewsByFilmId(Long filmId, int count) { } else { reviews = findManyByQuery(SQL_REVIEWS_FIND_BY_FILM_ID, filmId, count); } - reviews.forEach(review ->{ + reviews.forEach(review -> { review.setUserLikes(findAllReactionsByReviewId(review.getId())); }); return reviews; @@ -108,7 +108,7 @@ public void removeReviewDislike(long reviewId, long userId) { } @Override - public boolean checkReviewIsNotPresent(long reviewId){ + public boolean checkReviewIsNotPresent(long reviewId) { return checkIdIsNotPresentInTable(reviewId, "reviews"); } From f65b591fe81420b6122bb983a44adfcd2a7b8b16 Mon Sep 17 00:00:00 2001 From: Sergey Lyutov Date: Tue, 17 Feb 2026 23:22:06 +0300 Subject: [PATCH 07/47] add-reviews ver 1.1 --- .../filmorate/dto/request/create/ReviewCreateRequest.java | 2 ++ .../filmorate/dto/request/update/ReviewUpdateRequest.java | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/main/java/ru/yandex/practicum/filmorate/dto/request/create/ReviewCreateRequest.java b/src/main/java/ru/yandex/practicum/filmorate/dto/request/create/ReviewCreateRequest.java index b7ad14f..3ce1a00 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dto/request/create/ReviewCreateRequest.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dto/request/create/ReviewCreateRequest.java @@ -2,11 +2,13 @@ import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; import lombok.Data; @Data public class ReviewCreateRequest { @NotBlank(message = "Контент отзыва не может быть пустым.") + @Size(max = 2048, message = "Максимальная длина отзыва — 2048 символов.") private String content; @NotNull(message = "Тип отзыва должен быть указан.") diff --git a/src/main/java/ru/yandex/practicum/filmorate/dto/request/update/ReviewUpdateRequest.java b/src/main/java/ru/yandex/practicum/filmorate/dto/request/update/ReviewUpdateRequest.java index 8543192..8ad7586 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dto/request/update/ReviewUpdateRequest.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dto/request/update/ReviewUpdateRequest.java @@ -1,6 +1,7 @@ package ru.yandex.practicum.filmorate.dto.request.update; import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; import lombok.Data; @Data @@ -9,6 +10,7 @@ public class ReviewUpdateRequest { private Long reviewId; @NotNull(message = "Контент отзыва не может быть пустым.") + @Size(max = 2048, message = "Максимальная длина отзыва — 2048 символов.") private String content; @NotNull(message = "Тип отзыва должен быть указан.") From dd320ade52406bffeb1adcdf3c94d49ad0ba6efa Mon Sep 17 00:00:00 2001 From: Daria Simonenko Date: Wed, 18 Feb 2026 12:48:18 +0300 Subject: [PATCH 08/47] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D1=81=D1=83=D1=89=D0=BD=D0=BE=D1=81?= =?UTF-8?q?=D1=82=D0=B8=20=D1=81=D0=BE=D0=B1=D1=8B=D1=82=D0=B8=D1=8F=20+?= =?UTF-8?q?=20=D0=B4=D0=BE=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=BA=D0=B0=20Use?= =?UTF-8?q?rService=20=D0=B8=20FilmService=20=D0=BD=D0=B0=20=D1=80=D0=B0?= =?UTF-8?q?=D0=B1=D0=BE=D1=82=D1=83=20=D1=81=20=D1=8D=D1=82=D0=B8=D0=BC?= =?UTF-8?q?=D0=B8=20=D1=81=D0=BE=D0=B1=D1=8B=D1=82=D0=B8=D1=8F=D0=BC=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Conflicts: # src/main/java/ru/yandex/practicum/filmorate/dal/database/BaseDbStorage.java # src/main/resources/schema.sql --- .../filmorate/controller/UserController.java | 6 + .../practicum/filmorate/dal/EventStorage.java | 14 ++ .../filmorate/dal/database/BaseDbStorage.java | 1 - .../dal/database/EventDbStorage.java | 50 +++++++ .../dal/rowmappers/EventRowMapper.java | 28 ++++ .../practicum/filmorate/dto/EventDto.java | 19 +++ .../filmorate/mapper/EventMapper.java | 20 +++ .../practicum/filmorate/model/Event.java | 21 +++ .../practicum/filmorate/model/EventType.java | 7 + .../practicum/filmorate/model/Operation.java | 7 + .../filmorate/servise/EventService.java | 43 ++++++ .../filmorate/servise/FilmService.java | 20 ++- .../filmorate/servise/UserService.java | 13 +- src/main/resources/schema.sql | 136 ++++++++++-------- 14 files changed, 315 insertions(+), 70 deletions(-) create mode 100644 src/main/java/ru/yandex/practicum/filmorate/dal/EventStorage.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/dal/database/EventDbStorage.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/dal/rowmappers/EventRowMapper.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/dto/EventDto.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/mapper/EventMapper.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/model/Event.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/model/EventType.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/model/Operation.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/servise/EventService.java 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 0e1498a..7e404de 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java @@ -4,6 +4,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.*; +import ru.yandex.practicum.filmorate.dto.EventDto; import ru.yandex.practicum.filmorate.dto.request.create.UserCreateRequest; import ru.yandex.practicum.filmorate.dto.request.update.UserUpdateRequest; import ru.yandex.practicum.filmorate.dto.UserDto; @@ -59,4 +60,9 @@ public void addToFriends(@PathVariable long userId, @PathVariable long friendId) public void removeFromFriends(@PathVariable long userId, @PathVariable long friendId) { userService.changeFriends(FriendsAction.REMOVE, userId, friendId); } + + @GetMapping("/{id}/feed") + public Collection getUserEvents(@PathVariable(name = "id") long userId) { + return userService.getUserEvents(userId); + } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/EventStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/EventStorage.java new file mode 100644 index 0000000..f7c5d2c --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/EventStorage.java @@ -0,0 +1,14 @@ +package ru.yandex.practicum.filmorate.dal; + +import ru.yandex.practicum.filmorate.model.Event; + +import java.util.Collection; + +public interface EventStorage { + + /// Получение всех действий пользователя по его id + Collection getAllEventsByUserId(long userId); + + /// Добавление события + Event addEvent(Event event); +} \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/database/BaseDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/database/BaseDbStorage.java index 74e6f80..ee3e3c1 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/database/BaseDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/database/BaseDbStorage.java @@ -70,5 +70,4 @@ protected void updateWithControl(String query, Object... params) { protected List findManyByQuery(String query, Object... params) { return jdbc.query(query, rowMapper, params); } - } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/database/EventDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/database/EventDbStorage.java new file mode 100644 index 0000000..7a047d3 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/database/EventDbStorage.java @@ -0,0 +1,50 @@ +package ru.yandex.practicum.filmorate.dal.database; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Repository; +import ru.yandex.practicum.filmorate.dal.EventStorage; +import ru.yandex.practicum.filmorate.dal.rowmappers.EventRowMapper; +import ru.yandex.practicum.filmorate.exception.NotFoundException; +import ru.yandex.practicum.filmorate.model.Event; + +import java.util.Collection; + +@Repository +@Qualifier("eventDbStorage") +public class EventDbStorage extends BaseDbStorage implements EventStorage { + private static final String GET_USER_FEEDS = + "SELECT * FROM events " + + "WHERE user_id = ? " + + "OR entity_id = ? " + + "ORDER BY created_at DESC;"; + + private static final String ADD_USER_EVENT = + "INSERT INTO events (user_id, created_at, event_type, operation, entity_id) VALUES (?, ?, ?, ?, ?);"; + + @Autowired + public EventDbStorage(JdbcTemplate jdbc, EventRowMapper rowMapper) { + super(jdbc, rowMapper); + } + + @Override + public Collection getAllEventsByUserId(long userId) { + return findManyByQuery(GET_USER_FEEDS, userId, userId); + } + + public Event addEvent(Event event) { + if (event == null) { + throw new NotFoundException("Событие для добавления не найдено"); + } + + long eventId = insertWithKeyHolder(ADD_USER_EVENT, + event.getUserId(), + event.getTimestamp(), + event.getEventType().name(), + event.getOperation().name(), + event.getEntityId()); + event.setId(eventId); + return event; + } +} \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/rowmappers/EventRowMapper.java b/src/main/java/ru/yandex/practicum/filmorate/dal/rowmappers/EventRowMapper.java new file mode 100644 index 0000000..dc0057e --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/rowmappers/EventRowMapper.java @@ -0,0 +1,28 @@ +package ru.yandex.practicum.filmorate.dal.rowmappers; + +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Component; +import ru.yandex.practicum.filmorate.model.Event; +import ru.yandex.practicum.filmorate.model.EventType; +import ru.yandex.practicum.filmorate.model.Operation; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.time.Instant; + +@Component +@Qualifier("eventRowMapper") +public class EventRowMapper implements RowMapper { + @Override + public Event mapRow(ResultSet resultSet, int rowNum) throws SQLException { + return Event.builder() + .id(resultSet.getLong("id")) + .userId(resultSet.getLong("user_id")) + .timestamp(resultSet.getTimestamp("created_at").toInstant()) + .eventType(EventType.valueOf(resultSet.getString("event_type"))) + .operation(Operation.valueOf(resultSet.getString("operation"))) + .entityId(resultSet.getLong("entity_id")) + .build(); + } +} \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/dto/EventDto.java b/src/main/java/ru/yandex/practicum/filmorate/dto/EventDto.java new file mode 100644 index 0000000..25e5021 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/dto/EventDto.java @@ -0,0 +1,19 @@ +package ru.yandex.practicum.filmorate.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Builder; +import lombok.Data; +import ru.yandex.practicum.filmorate.model.EventType; +import ru.yandex.practicum.filmorate.model.Operation; + +@Data +@Builder +public class EventDto { + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + private Long id; + private Long userId; + private Long timestamp; + private EventType eventType; + private Operation operation; + private Long entityId; +} \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/mapper/EventMapper.java b/src/main/java/ru/yandex/practicum/filmorate/mapper/EventMapper.java new file mode 100644 index 0000000..9c91c18 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/mapper/EventMapper.java @@ -0,0 +1,20 @@ +package ru.yandex.practicum.filmorate.mapper; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import ru.yandex.practicum.filmorate.dto.EventDto; +import ru.yandex.practicum.filmorate.model.Event; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class EventMapper { + public static EventDto mapToEventDto(Event event) { + return EventDto.builder() + .id(event.getId()) + .userId(event.getUserId()) + .timestamp(event.getTimestamp().toEpochMilli()) + .eventType(event.getEventType()) + .operation(event.getOperation()) + .entityId(event.getEntityId()) + .build(); + } +} \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/Event.java b/src/main/java/ru/yandex/practicum/filmorate/model/Event.java new file mode 100644 index 0000000..8064030 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/model/Event.java @@ -0,0 +1,21 @@ +package ru.yandex.practicum.filmorate.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.Instant; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class Event { + private Long id; + private Long userId; + private Instant timestamp; + private EventType eventType; + private Operation operation; + private Long entityId; +} \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/EventType.java b/src/main/java/ru/yandex/practicum/filmorate/model/EventType.java new file mode 100644 index 0000000..f1e4056 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/model/EventType.java @@ -0,0 +1,7 @@ +package ru.yandex.practicum.filmorate.model; + +public enum EventType { + LIKE, + REVIEW, + FRIEND +} \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/Operation.java b/src/main/java/ru/yandex/practicum/filmorate/model/Operation.java new file mode 100644 index 0000000..bc5fff4 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/model/Operation.java @@ -0,0 +1,7 @@ +package ru.yandex.practicum.filmorate.model; + +public enum Operation { + REMOVE, + ADD, + UPDATE +} \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/servise/EventService.java b/src/main/java/ru/yandex/practicum/filmorate/servise/EventService.java new file mode 100644 index 0000000..480fd02 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/servise/EventService.java @@ -0,0 +1,43 @@ +package ru.yandex.practicum.filmorate.servise; + +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.dal.EventStorage; +import ru.yandex.practicum.filmorate.dto.EventDto; +import ru.yandex.practicum.filmorate.mapper.EventMapper; +import ru.yandex.practicum.filmorate.model.Event; +import ru.yandex.practicum.filmorate.model.EventType; +import ru.yandex.practicum.filmorate.model.Operation; + +import java.time.Instant; +import java.util.Collection; +import java.util.stream.Collectors; + +@Service +@Slf4j +public class EventService { + @Autowired + @Qualifier("eventDbStorage") + private EventStorage eventStorage; + + public Event addUserEvent(long userId, EventType eventType, Operation operation, long entityId) { + Event event = Event.builder() + .userId(userId) + .timestamp(Instant.now()) + .eventType(eventType) + .operation(operation) + .entityId(entityId) + .build(); + return eventStorage.addEvent(event); + } + + public Collection getUserEvents(long userId) { + log.info("Поиск всех событий пользователя."); + return eventStorage.getAllEventsByUserId(userId) + .stream() + .map(EventMapper::mapToEventDto) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/servise/FilmService.java b/src/main/java/ru/yandex/practicum/filmorate/servise/FilmService.java index 7656d9c..db58fb1 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/servise/FilmService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/servise/FilmService.java @@ -16,9 +16,7 @@ import ru.yandex.practicum.filmorate.exception.NotFoundException; import ru.yandex.practicum.filmorate.exception.ParameterNotValidException; import ru.yandex.practicum.filmorate.mapper.FilmMapper; -import ru.yandex.practicum.filmorate.model.Film; -import ru.yandex.practicum.filmorate.model.Genre; -import ru.yandex.practicum.filmorate.model.Mpa; +import ru.yandex.practicum.filmorate.model.*; import ru.yandex.practicum.filmorate.servise.util.LikeAction; import java.util.*; @@ -35,17 +33,21 @@ public class FilmService { private Map mpas; private Map genres; + private EventService eventService; + @Autowired public FilmService( @Qualifier("filmDbStorage") FilmStorage filmStorage, @Qualifier("userDbStorage") UserStorage userStorage, @Qualifier("mpaDbStorage") MpaStorage mpaStorage, - @Qualifier("genreDbStorage") GenreStorage genreStorage + @Qualifier("genreDbStorage") GenreStorage genreStorage, + EventService eventService ) { this.filmStorage = filmStorage; this.userStorage = userStorage; this.mpaStorage = mpaStorage; this.genreStorage = genreStorage; + this.eventService = eventService; } @EventListener(ApplicationReadyEvent.class) @@ -69,8 +71,14 @@ public void changeLike(LikeAction action, Long filmId, Long userId) { } switch (action) { - case SET -> filmStorage.setLike(filmId, userId); - case REMOVE -> filmStorage.removeLike(filmId, userId); + case SET -> { + filmStorage.setLike(filmId, userId); + eventService.addUserEvent(userId, EventType.LIKE, Operation.ADD, filmId); + } + case REMOVE -> { + filmStorage.removeLike(filmId, userId); + eventService.addUserEvent(userId, EventType.LIKE, Operation.REMOVE, filmId); + } } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/servise/UserService.java b/src/main/java/ru/yandex/practicum/filmorate/servise/UserService.java index 7c736e6..f7ede14 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/servise/UserService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/servise/UserService.java @@ -6,11 +6,14 @@ import org.springframework.dao.DuplicateKeyException; import org.springframework.stereotype.Service; import ru.yandex.practicum.filmorate.dal.UserStorage; +import ru.yandex.practicum.filmorate.dto.EventDto; import ru.yandex.practicum.filmorate.dto.request.create.UserCreateRequest; import ru.yandex.practicum.filmorate.dto.UserDto; import ru.yandex.practicum.filmorate.dto.request.update.UserUpdateRequest; import ru.yandex.practicum.filmorate.exception.*; import ru.yandex.practicum.filmorate.mapper.UserMapper; +import ru.yandex.practicum.filmorate.model.EventType; +import ru.yandex.practicum.filmorate.model.Operation; import ru.yandex.practicum.filmorate.model.User; import ru.yandex.practicum.filmorate.servise.util.FriendsAction; @@ -27,6 +30,9 @@ public class UserService { @Qualifier("userDbStorage") private UserStorage userStorage; + @Autowired + private EventService eventService; + public void changeFriends(FriendsAction action, long userId, long friendId) { switch (action) { case REQUEST -> log.info( @@ -55,11 +61,11 @@ public void changeFriends(FriendsAction action, long userId, long friendId) { } if (riendsOfFriend.contains(userId)) { - userStorage.addFriend(userId, friendId); log.info("Пользователи с id={} и id={} стали друзьями.", userId, friendId); } userStorage.addFriend(userId, friendId); + eventService.addUserEvent(userId, EventType.FRIEND, Operation.ADD, friendId); } case REMOVE -> { @@ -67,6 +73,7 @@ public void changeFriends(FriendsAction action, long userId, long friendId) { throw new NoContentException(); } userStorage.removeFriend(userId, friendId); + eventService.addUserEvent(userId, EventType.FRIEND, Operation.REMOVE, friendId); } } } @@ -132,6 +139,10 @@ public Collection getListOfMutualFriends(long userId, long friendId) { .toList(); } + public Collection getUserEvents(long userId) { + return eventService.getUserEvents(userId); + } + private void easyCheckUser(long userId) { if (userStorage.checkUserIsNotPresent(userId)) { throw new NotFoundException("Первый пользователь с id=" + userId + " не найден."); diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index 6f022b8..6e8c657 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -1,70 +1,110 @@ DROP ALL OBJECTS; CREATE TABLE users ( - id bigint NOT NULL AUTO_INCREMENT PRIMARY KEY, - user_name varchar(50), - login varchar(50) NOT NULL, - email varchar(50) NOT NULL, - birthday date NOT NULL + id bigint NOT NULL AUTO_INCREMENT PRIMARY KEY, + user_name varchar(50), + login varchar(50) NOT NULL, + email varchar(50) NOT NULL, + birthday date NOT NULL ); CREATE TABLE friends ( - id bigint NOT NULL AUTO_INCREMENT PRIMARY KEY, - user_id bigint NOT NULL, - friend_id bigint NOT NULL + id bigint NOT NULL AUTO_INCREMENT PRIMARY KEY, + user_id bigint NOT NULL, + friend_id bigint NOT NULL ); CREATE TABLE films ( - id bigint NOT NULL AUTO_INCREMENT PRIMARY KEY, - film_name varchar(50) NOT NULL, - description varchar(200) NOT NULL, - release_date date NOT NULL, - duration int NOT NULL, - mpa_id int NOT NULL + id bigint NOT NULL AUTO_INCREMENT PRIMARY KEY, + film_name varchar(50) NOT NULL, + description varchar(200) NOT NULL, + release_date date NOT NULL, + duration int NOT NULL, + mpa_id int NOT NULL ); CREATE TABLE genres ( - id int NOT NULL AUTO_INCREMENT PRIMARY KEY, - genre_name varchar(20) NOT NULL + id int NOT NULL AUTO_INCREMENT PRIMARY KEY, + genre_name varchar(20) NOT NULL ); CREATE TABLE genres_of_films ( - id bigint NOT NULL AUTO_INCREMENT PRIMARY KEY, - genre_id int NOT NULL, - film_id bigint NOT NULL + id bigint NOT NULL AUTO_INCREMENT PRIMARY KEY, + genre_id int NOT NULL, + film_id bigint NOT NULL ); CREATE TABLE mpa ( - id int NOT NULL AUTO_INCREMENT PRIMARY KEY, - mpa_name varchar(5) NOT NULL + id int NOT NULL AUTO_INCREMENT PRIMARY KEY, + mpa_name varchar(5) NOT NULL ); CREATE TABLE likes ( - id bigint NOT NULL AUTO_INCREMENT PRIMARY KEY, - user_id bigint NOT NULL, - film_id bigint NOT NULL + id bigint NOT NULL AUTO_INCREMENT PRIMARY KEY, + user_id bigint NOT NULL, + film_id bigint NOT NULL +); + +CREATE TABLE reviews ( + id bigint NOT NULL AUTO_INCREMENT PRIMARY KEY, + content varchar(2048) NOT NULL, + is_positive boolean NOT NULL, + user_id bigint NOT NULL, + film_id bigint NOT NULL, + useful bigint NOT NULL DEFAULT 0 +); + +CREATE TABLE review_likes ( + review_id bigint NOT NULL, + user_id bigint NOT NULL, + is_like boolean NOT NULL, + PRIMARY KEY (review_id, user_id) +); + +CREATE TABLE events ( + id bigint NOT NULL AUTO_INCREMENT PRIMARY KEY, + user_id bigint NOT NULL, + created_at TIMESTAMP NOT NULL, + event_type varchar(20), + operation varchar(20), + entity_id bigint NOT NULL ); ALTER TABLE friends ADD CONSTRAINT fk_friends_user_id FOREIGN KEY(user_id) - REFERENCES users (id); +REFERENCES users (id); ALTER TABLE friends ADD CONSTRAINT fk_friends_friend_id FOREIGN KEY(friend_id) - REFERENCES users (id); +REFERENCES users (id); ALTER TABLE films ADD CONSTRAINT fk_films_mpa_id FOREIGN KEY(mpa_id) - REFERENCES mpa (id); +REFERENCES mpa (id); ALTER TABLE genres_of_films ADD CONSTRAINT fk_genres_of_films_genre_id FOREIGN KEY(genre_id) - REFERENCES genres (id); +REFERENCES genres (id); ALTER TABLE genres_of_films ADD CONSTRAINT fk_genres_of_films_film_id FOREIGN KEY(film_id) - REFERENCES films (id); +REFERENCES films (id); ALTER TABLE likes ADD CONSTRAINT fk_likes_user_id FOREIGN KEY(user_id) - REFERENCES users (id); +REFERENCES users (id); ALTER TABLE likes ADD CONSTRAINT fk_likes_film_id FOREIGN KEY(film_id) - REFERENCES films (id); +REFERENCES films (id); + +ALTER TABLE reviews ADD CONSTRAINT fk_reviews_user_id FOREIGN KEY (user_id) +REFERENCES users (id) ON DELETE CASCADE; + +ALTER TABLE reviews ADD CONSTRAINT fk_reviews_film_id FOREIGN KEY (film_id) +REFERENCES films (id) ON DELETE CASCADE; + +ALTER TABLE review_likes ADD CONSTRAINT fk_review_likes_review_id FOREIGN KEY (review_id) +REFERENCES reviews (id); + +ALTER TABLE review_likes ADD CONSTRAINT fk_review_likes_user_id FOREIGN KEY (user_id) +REFERENCES users (id); + +ALTER TABLE events ADD CONSTRAINT fk_events_user_id FOREIGN KEY(user_id) +REFERENCES users (id); CREATE INDEX IF NOT EXISTS idx_users_user_name ON users (user_name); CREATE UNIQUE INDEX IF NOT EXISTS idx_users_email ON users (email); @@ -75,38 +115,10 @@ CREATE INDEX IF NOT EXISTS idx_friends_friend_id ON friends (friend_id); CREATE INDEX IF NOT EXISTS idx_films_film_name ON films (film_name); --- Добавление таблицы Отзывов -CREATE TABLE reviews ( - id bigint NOT NULL AUTO_INCREMENT PRIMARY KEY, - content varchar(2048) NOT NULL, - is_positive boolean NOT NULL, - user_id bigint NOT NULL, - film_id bigint NOT NULL, - useful bigint NOT NULL DEFAULT 0 -); - - -ALTER TABLE reviews ADD CONSTRAINT fk_reviews_user_id FOREIGN KEY (user_id) - REFERENCES users (id) ON DELETE CASCADE; - -ALTER TABLE reviews ADD CONSTRAINT fk_reviews_film_id FOREIGN KEY (film_id) - REFERENCES films (id) ON DELETE CASCADE; - - CREATE INDEX IF NOT EXISTS idx_reviews_film_id ON reviews (film_id); CREATE INDEX IF NOT EXISTS idx_reviews_user_id ON reviews (user_id); --- Добавление таблицы Лайков для Отзывов -CREATE TABLE review_likes ( - review_id bigint NOT NULL, - user_id bigint NOT NULL, - is_like boolean NOT NULL, - PRIMARY KEY (review_id, user_id) -); +CREATE INDEX IF NOT EXISTS idx_review_likes_review_id ON review_likes (review_id); -ALTER TABLE review_likes ADD CONSTRAINT fk_review_likes_review_id FOREIGN KEY (review_id) - REFERENCES reviews (id); - -ALTER TABLE review_likes ADD CONSTRAINT fk_review_likes_user_id FOREIGN KEY (user_id) - REFERENCES users (id); -CREATE INDEX IF NOT EXISTS idx_review_likes_review_id ON review_likes (review_id); \ No newline at end of file +CREATE INDEX IF NOT EXISTS idx_events_user_id ON events (user_id); +CREATE INDEX IF NOT EXISTS idx_events_created_at ON events (created_at); \ No newline at end of file From 916ec8ff55b306833bad841b31af50ebf47bce5c Mon Sep 17 00:00:00 2001 From: Darya346 Date: Wed, 18 Feb 2026 16:13:22 +0300 Subject: [PATCH 09/47] feat: Implement film recommendation system --- .../filmorate/controller/UserController.java | 5 ++ .../practicum/filmorate/dal/FilmStorage.java | 5 ++ .../filmorate/dal/database/FilmDbStorage.java | 16 ++++++ .../dal/inmemory/InMemoryFilmStorage.java | 8 +++ .../filmorate/servise/UserService.java | 50 +++++++++++++++++++ 5 files changed, 84 insertions(+) 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 0e1498a..9d9575b 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java @@ -9,6 +9,7 @@ import ru.yandex.practicum.filmorate.dto.UserDto; import ru.yandex.practicum.filmorate.servise.util.FriendsAction; import ru.yandex.practicum.filmorate.servise.UserService; +import ru.yandex.practicum.filmorate.dto.FilmDto; import java.util.Collection; @@ -59,4 +60,8 @@ public void addToFriends(@PathVariable long userId, @PathVariable long friendId) public void removeFromFriends(@PathVariable long userId, @PathVariable long friendId) { userService.changeFriends(FriendsAction.REMOVE, userId, friendId); } + @GetMapping("/{id}/recommendations") + public Collection getRecommendations(@PathVariable long id) { + return userService.getRecommendations(id); + } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/FilmStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/FilmStorage.java index 6927ef8..eab2b2f 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/FilmStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/FilmStorage.java @@ -4,6 +4,8 @@ import java.util.Collection; import java.util.Optional; +import java.util.Map; +import java.util.Set; public interface FilmStorage { @@ -30,4 +32,7 @@ public interface FilmStorage { /// Убрать лайк void removeLike(long filmId, long userId); + + // Метод для получения матрицы лайков всех пользователей + Map> getAllLikes(); } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/database/FilmDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/database/FilmDbStorage.java index cc53252..b9ae786 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/database/FilmDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/database/FilmDbStorage.java @@ -13,6 +13,8 @@ import java.sql.SQLException; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; +import java.util.HashMap; +import java.util.HashSet; import static ru.yandex.practicum.filmorate.dal.database.sql.FilmQueryes.*; @@ -141,4 +143,18 @@ private Set getGenreIdsByFilmId(long filmId) { return findColumnByQuery(SQL_FILMS_FIND_GENREIDS_BY_FILM_ID, Integer.class, filmId); } + @Override + public Map> getAllLikes() { + + String sql = "SELECT user_id, film_id FROM likes"; + Map> allLikes = new HashMap<>(); + + jdbc.query(sql, (rs) -> { + long userId = rs.getLong("user_id"); + long filmId = rs.getLong("film_id"); + allLikes.computeIfAbsent(userId, k -> new HashSet<>()).add(filmId); + }); + + return allLikes; + } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/inmemory/InMemoryFilmStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/inmemory/InMemoryFilmStorage.java index 80a1493..639dd50 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/inmemory/InMemoryFilmStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/inmemory/InMemoryFilmStorage.java @@ -11,6 +11,9 @@ import java.util.Collection; import java.util.Map; import java.util.Optional; +import java.util.HashMap; + +import java.util.Set; @Component @Qualifier("inMemoryFilmStorage") @@ -63,4 +66,9 @@ public void removeLike(long filmId, long userId) { public void setLike(long filmId, long userId) { throw new MethodNotImplementedException(); } + + @Override + public Map> getAllLikes() { + return new HashMap<>(); + } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/servise/UserService.java b/src/main/java/ru/yandex/practicum/filmorate/servise/UserService.java index 7c736e6..41322a8 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/servise/UserService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/servise/UserService.java @@ -13,11 +13,14 @@ import ru.yandex.practicum.filmorate.mapper.UserMapper; import ru.yandex.practicum.filmorate.model.User; import ru.yandex.practicum.filmorate.servise.util.FriendsAction; +import ru.yandex.practicum.filmorate.dal.FilmStorage; +import ru.yandex.practicum.filmorate.dto.FilmDto; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.stream.Collectors; +import java.util.*; @Service @Slf4j @@ -27,6 +30,13 @@ public class UserService { @Qualifier("userDbStorage") private UserStorage userStorage; + @Autowired + @Qualifier("filmDbStorage") + private FilmStorage filmStorage; + + @Autowired + private FilmService filmService; + public void changeFriends(FriendsAction action, long userId, long friendId) { switch (action) { case REQUEST -> log.info( @@ -171,4 +181,44 @@ private void logUserUpdate(UserUpdateRequest request, User user) { String updatedData = String.join(", ", updatedFields); log.info("Данные для обновления: [{}]", updatedData); } + // Рекомендации + public Collection getRecommendations(long userId) { + log.info("Получение рекомендаций для пользователя id={}.", userId); + easyCheckUser(userId); + + Map> allLikes = filmStorage.getAllLikes(); + Set targetUserLikes = allLikes.getOrDefault(userId, Collections.emptySet()); + + if (targetUserLikes.isEmpty()) { + return Collections.emptyList(); + } + + long similarUserId = -1; + int maxOverlap = 0; + + for (Map.Entry> entry : allLikes.entrySet()) { + long otherUserId = entry.getKey(); + if (otherUserId == userId) continue; + + Set otherUserLikes = entry.getValue(); + int overlap = (int) targetUserLikes.stream() + .filter(otherUserLikes::contains) + .count(); + + if (overlap > maxOverlap) { + maxOverlap = overlap; + similarUserId = otherUserId; + } + } + + if (similarUserId == -1) { + return Collections.emptyList(); + } + + Set similarUserLikes = allLikes.get(similarUserId); + return similarUserLikes.stream() + .filter(filmId -> !targetUserLikes.contains(filmId)) + .map(filmService::findById) + .collect(Collectors.toList()); + } } From 0794ac654a6384cdf0c93d247230ac4f0046c651 Mon Sep 17 00:00:00 2001 From: Daria Simonenko Date: Wed, 18 Feb 2026 16:20:08 +0300 Subject: [PATCH 10/47] =?UTF-8?q?=D0=94=D0=BE=D1=80=D0=B0=D0=B1=D0=BE?= =?UTF-8?q?=D1=82=D0=BA=D0=B0=20ReviewService=20=D0=B8=20=D1=80=D0=B5?= =?UTF-8?q?=D1=88=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BA=D0=BE=D0=BD=D1=84=D0=BB?= =?UTF-8?q?=D0=B8=D0=BA=D1=82=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../filmorate/dal/database/EventDbStorage.java | 5 ++--- .../practicum/filmorate/servise/FilmService.java | 3 +-- .../practicum/filmorate/servise/ReviewService.java | 11 ++++++++++- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/database/EventDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/database/EventDbStorage.java index 7a047d3..757b550 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/database/EventDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/database/EventDbStorage.java @@ -17,8 +17,7 @@ public class EventDbStorage extends BaseDbStorage implements EventStorage private static final String GET_USER_FEEDS = "SELECT * FROM events " + "WHERE user_id = ? " + - "OR entity_id = ? " + - "ORDER BY created_at DESC;"; + "ORDER BY created_at;"; private static final String ADD_USER_EVENT = "INSERT INTO events (user_id, created_at, event_type, operation, entity_id) VALUES (?, ?, ?, ?, ?);"; @@ -30,7 +29,7 @@ public EventDbStorage(JdbcTemplate jdbc, EventRowMapper rowMapper) { @Override public Collection getAllEventsByUserId(long userId) { - return findManyByQuery(GET_USER_FEEDS, userId, userId); + return findManyByQuery(GET_USER_FEEDS, userId); } public Event addEvent(Event event) { diff --git a/src/main/java/ru/yandex/practicum/filmorate/servise/FilmService.java b/src/main/java/ru/yandex/practicum/filmorate/servise/FilmService.java index db58fb1..5e6b0c8 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/servise/FilmService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/servise/FilmService.java @@ -32,8 +32,7 @@ public class FilmService { private Map mpas; private Map genres; - - private EventService eventService; + private final EventService eventService; @Autowired public FilmService( diff --git a/src/main/java/ru/yandex/practicum/filmorate/servise/ReviewService.java b/src/main/java/ru/yandex/practicum/filmorate/servise/ReviewService.java index 068b350..fb5ca0b 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/servise/ReviewService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/servise/ReviewService.java @@ -12,6 +12,8 @@ import ru.yandex.practicum.filmorate.dto.request.update.ReviewUpdateRequest; import ru.yandex.practicum.filmorate.exception.NotFoundException; import ru.yandex.practicum.filmorate.mapper.ReviewMapper; +import ru.yandex.practicum.filmorate.model.EventType; +import ru.yandex.practicum.filmorate.model.Operation; import ru.yandex.practicum.filmorate.model.Review; import java.util.Collection; @@ -24,16 +26,19 @@ public class ReviewService { private final ReviewStorage reviewStorage; private final FilmStorage filmStorage; private final UserStorage userStorage; + private final EventService eventService; @Autowired public ReviewService( @Qualifier("reviewDbStorage") ReviewStorage reviewStorage, @Qualifier("filmDbStorage") FilmStorage filmStorage, - @Qualifier("userDbStorage") UserStorage userStorage + @Qualifier("userDbStorage") UserStorage userStorage, + EventService eventService ) { this.reviewStorage = reviewStorage; this.filmStorage = filmStorage; this.userStorage = userStorage; + this.eventService = eventService; } public ReviewDto createReview(ReviewCreateRequest request) { @@ -44,6 +49,7 @@ public ReviewDto createReview(ReviewCreateRequest request) { Review review = ReviewMapper.mapToReview(request); review = reviewStorage.createReview(review); + eventService.addUserEvent(review.getUserId(), EventType.REVIEW, Operation.ADD, review.getId()); return ReviewMapper.mapToReviewDto(review); } @@ -56,6 +62,7 @@ public ReviewDto updateReview(ReviewUpdateRequest request) { Review newReview = ReviewMapper.updateReviewFields(oldReview, request); newReview = reviewStorage.updateReview(newReview); + eventService.addUserEvent(newReview.getUserId(), EventType.REVIEW, Operation.UPDATE, newReview.getId()); return ReviewMapper.mapToReviewDto(newReview); } @@ -63,7 +70,9 @@ public ReviewDto updateReview(ReviewUpdateRequest request) { public void deleteReview(Long id) { log.info("Удаление отзыва с id={}", id); checkReviewExists(id); + long userId = findReviewById(id).getUserId(); reviewStorage.deleteReview(id); + eventService.addUserEvent(userId, EventType.REVIEW, Operation.REMOVE, id); } public ReviewDto findReviewById(Long id) { From 0dbf16c1d98f262ebcfca500169f6b40cfedb3c8 Mon Sep 17 00:00:00 2001 From: Darya346 Date: Wed, 18 Feb 2026 16:25:14 +0300 Subject: [PATCH 11/47] trigger tests after changing base branch From 32a977999c988a18ffbdd54bf130c87cb1a66c4b Mon Sep 17 00:00:00 2001 From: Darya346 Date: Wed, 18 Feb 2026 16:28:00 +0300 Subject: [PATCH 12/47] fix: add empty line before getRecommendations for checkstyle --- .../ru/yandex/practicum/filmorate/controller/UserController.java | 1 + 1 file changed, 1 insertion(+) 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 9d9575b..3857261 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java @@ -60,6 +60,7 @@ public void addToFriends(@PathVariable long userId, @PathVariable long friendId) public void removeFromFriends(@PathVariable long userId, @PathVariable long friendId) { userService.changeFriends(FriendsAction.REMOVE, userId, friendId); } + @GetMapping("/{id}/recommendations") public Collection getRecommendations(@PathVariable long id) { return userService.getRecommendations(id); From ae14ec375de2bd785e110ab7e5766cb7d23e8280 Mon Sep 17 00:00:00 2001 From: Daria Simonenko Date: Wed, 18 Feb 2026 16:34:09 +0300 Subject: [PATCH 13/47] =?UTF-8?q?=D0=A0=D0=B5=D1=88=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BE=D0=BA=20=D1=82=D0=B5?= =?UTF-8?q?=D1=81=D1=82=D0=BE=D0=B2=20(=D1=83=D0=B1=D1=80=D0=B0=D0=BB?= =?UTF-8?q?=D0=B0=20=D0=BD=D0=B5=D0=B8=D1=81=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7?= =?UTF-8?q?=D1=83=D0=B5=D0=BC=D1=8B=D0=B9=20=D0=B8=D0=BC=D0=BF=D0=BE=D1=80?= =?UTF-8?q?=D1=82)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../practicum/filmorate/dal/rowmappers/EventRowMapper.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/rowmappers/EventRowMapper.java b/src/main/java/ru/yandex/practicum/filmorate/dal/rowmappers/EventRowMapper.java index dc0057e..a339a3b 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/rowmappers/EventRowMapper.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/rowmappers/EventRowMapper.java @@ -9,7 +9,6 @@ import java.sql.ResultSet; import java.sql.SQLException; -import java.time.Instant; @Component @Qualifier("eventRowMapper") From 0958a90eeeb74cc4d7b77614115e61651bb4b397 Mon Sep 17 00:00:00 2001 From: Darya346 Date: Wed, 18 Feb 2026 16:36:05 +0300 Subject: [PATCH 14/47] fix: force remove all trailing whitespaces --- .../yandex/practicum/filmorate/controller/UserController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 3857261..cb83854 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java @@ -60,7 +60,7 @@ public void addToFriends(@PathVariable long userId, @PathVariable long friendId) public void removeFromFriends(@PathVariable long userId, @PathVariable long friendId) { userService.changeFriends(FriendsAction.REMOVE, userId, friendId); } - + @GetMapping("/{id}/recommendations") public Collection getRecommendations(@PathVariable long id) { return userService.getRecommendations(id); From 1782a063c9978e8f3147e5d2554cd9fc4429d553 Mon Sep 17 00:00:00 2001 From: Darya346 Date: Wed, 18 Feb 2026 16:55:59 +0300 Subject: [PATCH 15/47] fix: remove trailing whitespace in UserService --- .../java/ru/yandex/practicum/filmorate/servise/UserService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/ru/yandex/practicum/filmorate/servise/UserService.java b/src/main/java/ru/yandex/practicum/filmorate/servise/UserService.java index 41322a8..83a74cb 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/servise/UserService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/servise/UserService.java @@ -181,6 +181,7 @@ private void logUserUpdate(UserUpdateRequest request, User user) { String updatedData = String.join(", ", updatedFields); log.info("Данные для обновления: [{}]", updatedData); } + // Рекомендации public Collection getRecommendations(long userId) { log.info("Получение рекомендаций для пользователя id={}.", userId); From 17781d9480afe50476abdb1d65d98b68d149912a Mon Sep 17 00:00:00 2001 From: Daria Simonenko Date: Thu, 19 Feb 2026 10:43:22 +0300 Subject: [PATCH 16/47] =?UTF-8?q?=D0=A0=D0=B5=D1=88=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=20=D0=BA=D0=BE=D0=BD=D1=84=D0=BB=D0=B8=D0=BA=D1=82=D0=BE?= =?UTF-8?q?=D0=B2=20=D0=BF=D1=80=D0=B8=20=D0=BC=D1=91=D1=80=D0=B6=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../practicum/filmorate/controller/UserController.java | 3 ++- .../ru/yandex/practicum/filmorate/servise/UserService.java | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) 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 f0f174b..68efccf 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java @@ -65,7 +65,8 @@ public void removeFromFriends(@PathVariable long userId, @PathVariable long frie @GetMapping("/{id}/feed") public Collection getUserEvents(@PathVariable(name = "id") long userId) { return userService.getUserEvents(userId); - + } + @GetMapping("/{id}/recommendations") public Collection getRecommendations(@PathVariable long id) { return userService.getRecommendations(id); diff --git a/src/main/java/ru/yandex/practicum/filmorate/servise/UserService.java b/src/main/java/ru/yandex/practicum/filmorate/servise/UserService.java index 95bbc0b..ce6f459 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/servise/UserService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/servise/UserService.java @@ -35,7 +35,7 @@ public class UserService { @Autowired private EventService eventService; - + @Qualifier("filmDbStorage") private FilmStorage filmStorage; @@ -78,7 +78,7 @@ public void changeFriends(FriendsAction action, long userId, long friendId) { } case REMOVE -> { - if (! friendsOfUser.contains(friendId)) { + if (!friendsOfUser.contains(friendId)) { throw new NoContentException(); } userStorage.removeFriend(userId, friendId); @@ -88,7 +88,7 @@ public void changeFriends(FriendsAction action, long userId, long friendId) { } public UserDto create(UserCreateRequest request) { - log.info("Добавление нового пользователя login={}.", request.getLogin()); + log.info("Добавление нового пользователя login={}.", request.getLogin()); User user = UserMapper.mapToUser(request); try { From 0d56fc7ac2afcff0aeb5562a62e64d4348824a37 Mon Sep 17 00:00:00 2001 From: Daria Simonenko Date: Thu, 19 Feb 2026 12:34:18 +0300 Subject: [PATCH 17/47] =?UTF-8?q?=D0=9C=D0=BE=D0=B4=D0=B5=D1=80=D0=BD?= =?UTF-8?q?=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D1=8F=20=D0=BC=D0=B5=D1=82=D0=BE?= =?UTF-8?q?=D0=B4=D0=B0,=20=D0=BA=D0=BE=D1=82=D0=BE=D1=80=D1=8B=D0=B9=20?= =?UTF-8?q?=D0=B2=D0=BE=D0=B7=D0=B2=D1=80=D0=B0=D1=89=D0=B0=D0=B5=D1=82=20?= =?UTF-8?q?=D0=BF=D0=BE=D0=BF=D1=83=D0=BB=D1=8F=D1=80=D0=BD=D1=8B=D0=B5=20?= =?UTF-8?q?=D1=84=D0=B8=D0=BB=D1=8C=D0=BC=D1=8B.=20=D0=94=D0=BE=D0=B1?= =?UTF-8?q?=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D1=8B=20=D1=84=D0=B8=D0=BB=D1=8C?= =?UTF-8?q?=D1=82=D1=80=D1=8B=20=D0=BF=D0=BE=20=D0=B3=D0=BE=D0=B4=D1=83=20?= =?UTF-8?q?=D0=B8=20=D0=B6=D0=B0=D0=BD=D1=80=D1=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../filmorate/controller/FilmController.java | 6 ++-- .../practicum/filmorate/dal/FilmStorage.java | 3 ++ .../filmorate/dal/database/FilmDbStorage.java | 19 ++++++++++++ .../dal/database/sql/FilmQueryes.java | 31 +++++++++++++++++++ .../dal/inmemory/InMemoryFilmStorage.java | 5 +++ .../filmorate/servise/FilmService.java | 12 +++++-- 6 files changed, 72 insertions(+), 4 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 90bb273..7b7fa99 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java @@ -30,8 +30,10 @@ public FilmDto findById(@PathVariable long id) { } @GetMapping("/popular") - public Collection getTop(@RequestParam (defaultValue = "10", required = false) Integer count) { - return filmService.getTopFilms(count); + public Collection getTop(@RequestParam(defaultValue = "10", required = false) Integer count, + @RequestParam(required = false) Integer genreId, + @RequestParam(required = false) String year) { + return filmService.getTopFilms(count, genreId, year); } @PostMapping diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/FilmStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/FilmStorage.java index eab2b2f..1454545 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/FilmStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/FilmStorage.java @@ -27,6 +27,9 @@ public interface FilmStorage { /// Получить топ фильмов Collection getTop(int count); + /// Получить топ фильмов по фильтрам + Collection getTopByFilters(Integer count, Integer genreId, String year); + /// Поставить лайк void setLike(long filmId, long userId); diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/database/FilmDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/database/FilmDbStorage.java index b9ae786..dec1b29 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/database/FilmDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/database/FilmDbStorage.java @@ -90,6 +90,25 @@ public Collection getTop(int top) { return findManyFilms(SQL_FILMS_FIND_TOP, top); } + @Override + public Collection getTopByFilters(Integer top, Integer genreId, String year) { + Collection films; + if (genreId != null && year != null && !year.isEmpty()) { + films = findManyByQuery(SQL_FILMS_FIND_TOP_BY_GENRE_AND_YEAR, Integer.parseInt(year), genreId, top); + } else if (year != null && !year.isEmpty()) { + films = findManyByQuery(SQL_FILMS_FIND_TOP_BY_YEAR, Integer.parseInt(year), top); + } else if (genreId != null) { + films = findManyByQuery(SQL_FILMS_FIND_TOP_BY_GENRES, genreId, top); + } else { + films = findManyByQuery(SQL_FILMS_FIND_TOP, top); + } + + return films.stream().peek(film -> { + Set genres = getGenreIdsByFilmId(film.getId()); + film.setGenreIds(new HashSet<>(genres)); + }).toList(); + } + @Override public boolean checkFilmIsNotPresent(Long filmId) { return checkIdIsNotPresentInTable(filmId, "films"); diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/database/sql/FilmQueryes.java b/src/main/java/ru/yandex/practicum/filmorate/dal/database/sql/FilmQueryes.java index dc43660..4bac477 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/database/sql/FilmQueryes.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/database/sql/FilmQueryes.java @@ -99,4 +99,35 @@ INSERT INTO films ( mpa_id = ? WHERE id = ? """; + + public static final String SQL_FILMS_FIND_TOP_BY_GENRES = """ + SELECT f.*, COUNT(fl.user_id) as likes_count + FROM films f + LEFT JOIN likes fl ON f.id = fl.film_id + LEFT JOIN genres_of_films gr ON f.id = gr.film_id + WHERE gr.genre_id = ? + GROUP BY f.id + ORDER BY likes_count DESC + LIMIT ? + """; + + public static final String SQL_FILMS_FIND_TOP_BY_YEAR = """ + SELECT f.*, COUNT(fl.user_id) as likes_count + FROM (SELECT * FROM films WHERE EXTRACT(YEAR FROM release_date) = ?) f + LEFT JOIN likes fl ON f.id = fl.film_id + GROUP BY f.id + ORDER BY likes_count DESC + LIMIT ? + """; + + public static final String SQL_FILMS_FIND_TOP_BY_GENRE_AND_YEAR = """ + SELECT f.*, COUNT(fl.user_id) as likes_count + FROM (SELECT * FROM films WHERE EXTRACT(YEAR FROM release_date) = ?) f + LEFT JOIN likes fl ON f.id = fl.film_id + LEFT JOIN genres_of_films gr ON f.id = gr.film_id + WHERE gr.genre_id = ? + GROUP BY f.id + ORDER BY likes_count DESC + LIMIT ? + """; } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/inmemory/InMemoryFilmStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/inmemory/InMemoryFilmStorage.java index 639dd50..ffdb502 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/inmemory/InMemoryFilmStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/inmemory/InMemoryFilmStorage.java @@ -57,6 +57,11 @@ public Collection getTop(int count) { throw new MethodNotImplementedException(); } + @Override + public Collection getTopByFilters(Integer count, Integer genreId, String year) { + throw new MethodNotImplementedException(); + } + @Override public void removeLike(long filmId, long userId) { throw new MethodNotImplementedException(); diff --git a/src/main/java/ru/yandex/practicum/filmorate/servise/FilmService.java b/src/main/java/ru/yandex/practicum/filmorate/servise/FilmService.java index 5e6b0c8..2e17241 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/servise/FilmService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/servise/FilmService.java @@ -81,7 +81,7 @@ public void changeLike(LikeAction action, Long filmId, Long userId) { } } - public Collection getTopFilms(Integer count) { + public Collection getTopFilms(Integer count, Integer genreId, String year) { log.info("Получение топ {} фильмов.", count); if (count <= 0) { throw new ParameterNotValidException( @@ -89,7 +89,15 @@ public Collection getTopFilms(Integer count) { "Топ фильмов должен быть положительным числом." ); } - return filmStorage.getTop(count).stream() + + Collection films; + if (genreId != null || year != null) { + films = filmStorage.getTopByFilters(count, genreId, year); + } else { + films = filmStorage.getTop(count); + } + + return films.stream() .map(film -> FilmMapper.mapToFilmDto( film, mpas.get(film.getMpaId()), From 5d5816ae746845d59d35d619bb114eb35b8ce316 Mon Sep 17 00:00:00 2001 From: AleksOBM Date: Tue, 17 Feb 2026 00:43:33 +0300 Subject: [PATCH 18/47] =?UTF-8?q?=D0=A1=D0=BE=D0=B7=D0=B4=D0=B0=D1=82?= =?UTF-8?q?=D1=8C=20=D0=BE=D1=81=D0=BD=D0=BE=D0=B2=D0=BD=D1=8B=D0=B5=20?= =?UTF-8?q?=D0=BA=D0=BB=D0=B0=D1=81=D1=81=D1=8B,=20=D1=80=D0=B0=D1=81?= =?UTF-8?q?=D1=81=D1=82=D0=B0=D0=B2=D0=B8=D1=82=D1=8C=20todo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/DirectorController.java | 43 +++++++++++++++ .../filmorate/dal/DirectorStorage.java | 25 +++++++++ .../dal/database/DirectorDbStorage.java | 53 +++++++++++++++++++ .../dal/rowmappers/DirectorRowMapper.java | 21 ++++++++ .../practicum/filmorate/dto/DirectorDto.java | 18 +++++++ .../request/create/DirectorCreateRequest.java | 14 +++++ .../request/update/DirectorUpdateRequest.java | 20 +++++++ .../practicum/filmorate/model/Director.java | 18 +++++++ .../filmorate/servise/DirectorService.java | 49 +++++++++++++++++ .../filmorate/servise/GenreService.java | 4 +- 10 files changed, 263 insertions(+), 2 deletions(-) create mode 100644 src/main/java/ru/yandex/practicum/filmorate/controller/DirectorController.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/dal/DirectorStorage.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/dal/database/DirectorDbStorage.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/dal/rowmappers/DirectorRowMapper.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/dto/DirectorDto.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/dto/request/create/DirectorCreateRequest.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/dto/request/update/DirectorUpdateRequest.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/model/Director.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/servise/DirectorService.java diff --git a/src/main/java/ru/yandex/practicum/filmorate/controller/DirectorController.java b/src/main/java/ru/yandex/practicum/filmorate/controller/DirectorController.java new file mode 100644 index 0000000..8000ddf --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/DirectorController.java @@ -0,0 +1,43 @@ +package ru.yandex.practicum.filmorate.controller; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; +import ru.yandex.practicum.filmorate.dto.DirectorDto; +import ru.yandex.practicum.filmorate.dto.request.create.DirectorCreateRequest; +import ru.yandex.practicum.filmorate.dto.request.update.DirectorUpdateRequest; +import ru.yandex.practicum.filmorate.servise.DirectorService; + +import java.util.Collection; + +@RestController +@RequestMapping("/directors") +@RequiredArgsConstructor +public class DirectorController { + private final DirectorService directorService; + + @GetMapping + public Collection getAllDirectors() { + return directorService.getAllDirectorDtos(); + } + + @GetMapping("/{directorId}") + public DirectorDto getDirectorById(@PathVariable Long directorId) { + return directorService.getDirectorDtoById(directorId); + } + + @PostMapping + public DirectorDto createDirector(@RequestBody @Valid DirectorCreateRequest directorRequest) { + return directorService.create(directorRequest); + } + + @PutMapping + public DirectorDto updateDirector(@RequestBody @Valid DirectorUpdateRequest directorRequest) { + return directorService.update(directorRequest); + } + + @DeleteMapping("/{directorId}") + public void deleteDirector(@PathVariable Long directorId) { + directorService.delete(directorId); + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/DirectorStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/DirectorStorage.java new file mode 100644 index 0000000..a1af27a --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/DirectorStorage.java @@ -0,0 +1,25 @@ +package ru.yandex.practicum.filmorate.dal; + +import ru.yandex.practicum.filmorate.model.Director; + +import java.util.Collection; +import java.util.Optional; + +public interface DirectorStorage { + + /// Получить режиссера по id + Optional findById(long id); + + /// Получить всех режиссеров + Collection findAll(); + + /// Добавить нового режиссера + Director createDirector(Director director); + + /// Обновить данные режиссера + Director updateDirector(Director director); + + /// Удалить режиссера по id + void deleteDirector(Director director); + +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/database/DirectorDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/database/DirectorDbStorage.java new file mode 100644 index 0000000..08f9152 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/database/DirectorDbStorage.java @@ -0,0 +1,53 @@ +package ru.yandex.practicum.filmorate.dal.database; + +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Repository; +import ru.yandex.practicum.filmorate.dal.DirectorStorage; +import ru.yandex.practicum.filmorate.exception.MethodNotImplementedException; +import ru.yandex.practicum.filmorate.model.Director; + +import java.util.Collection; +import java.util.Optional; + +@Repository +@Qualifier("directorDbStorage") +public class DirectorDbStorage extends BaseDbStorage implements DirectorStorage { + + public DirectorDbStorage( + JdbcTemplate jdbc, + @Qualifier("directorRowMapper") RowMapper rowMapper + ) { + super(jdbc, rowMapper); + } + + @Override + public Optional findById(long id) { + return findOneByIdInTable(id, "directors"); + } + + @Override + public Collection findAll() { + // todo + throw new MethodNotImplementedException(); + } + + @Override + public Director createDirector(Director director) { + // todo + throw new MethodNotImplementedException(); + } + + @Override + public Director updateDirector(Director director) { + // todo + throw new MethodNotImplementedException(); + } + + @Override + public void deleteDirector(Director director) { + // todo + throw new MethodNotImplementedException(); + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/rowmappers/DirectorRowMapper.java b/src/main/java/ru/yandex/practicum/filmorate/dal/rowmappers/DirectorRowMapper.java new file mode 100644 index 0000000..54d0ef1 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/rowmappers/DirectorRowMapper.java @@ -0,0 +1,21 @@ +package ru.yandex.practicum.filmorate.dal.rowmappers; + +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Component; +import ru.yandex.practicum.filmorate.exception.MethodNotImplementedException; +import ru.yandex.practicum.filmorate.model.Director; + +import java.sql.ResultSet; +import java.sql.SQLException; + +@Component +@Qualifier("directorRowMapper") +public class DirectorRowMapper implements RowMapper { + + @Override + public Director mapRow(ResultSet resultSet, int rowNum) throws SQLException { + // todo + throw new MethodNotImplementedException(); + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/dto/DirectorDto.java b/src/main/java/ru/yandex/practicum/filmorate/dto/DirectorDto.java new file mode 100644 index 0000000..0479c8b --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/dto/DirectorDto.java @@ -0,0 +1,18 @@ +package ru.yandex.practicum.filmorate.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Data; +import lombok.experimental.FieldDefaults; + +@Data +@Builder +@FieldDefaults(level = AccessLevel.PRIVATE) +public class DirectorDto { + + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + long id; + + String name; +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/dto/request/create/DirectorCreateRequest.java b/src/main/java/ru/yandex/practicum/filmorate/dto/request/create/DirectorCreateRequest.java new file mode 100644 index 0000000..b73286b --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/dto/request/create/DirectorCreateRequest.java @@ -0,0 +1,14 @@ +package ru.yandex.practicum.filmorate.dto.request.create; + +import jakarta.validation.constraints.Size; +import lombok.AccessLevel; +import lombok.Data; +import lombok.experimental.FieldDefaults; + +@Data +@FieldDefaults(level = AccessLevel.PRIVATE) +public class DirectorCreateRequest { + + @Size(min = 2, max = 70, message = "Длина имени режиссера может быть от 2 до 70 символов.") + String name; +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/dto/request/update/DirectorUpdateRequest.java b/src/main/java/ru/yandex/practicum/filmorate/dto/request/update/DirectorUpdateRequest.java new file mode 100644 index 0000000..862ffe7 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/dto/request/update/DirectorUpdateRequest.java @@ -0,0 +1,20 @@ +package ru.yandex.practicum.filmorate.dto.request.update; + +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.Size; +import lombok.AccessLevel; +import lombok.Data; +import lombok.experimental.FieldDefaults; + +@Data +@FieldDefaults(level = AccessLevel.PRIVATE) +public class DirectorUpdateRequest { + + @NotNull(message = "Id должен быть указан.") + @Positive(message = "Id должен быть положительным числом.") + Long id; + + @Size(min = 2, max = 70, message = "Длина имени режиссера может быть от 2 до 70 символов.") + String name; +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/Director.java b/src/main/java/ru/yandex/practicum/filmorate/model/Director.java new file mode 100644 index 0000000..501d383 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/model/Director.java @@ -0,0 +1,18 @@ +package ru.yandex.practicum.filmorate.model; + +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.FieldDefaults; +import lombok.extern.jackson.Jacksonized; + +@Data +@Builder(toBuilder = true) +@Jacksonized +@EqualsAndHashCode(of = "id") +@FieldDefaults(level = AccessLevel.PRIVATE) +public class Director { + Long id; + String name; +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/servise/DirectorService.java b/src/main/java/ru/yandex/practicum/filmorate/servise/DirectorService.java new file mode 100644 index 0000000..4e91701 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/servise/DirectorService.java @@ -0,0 +1,49 @@ +package ru.yandex.practicum.filmorate.servise; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import ru.yandex.practicum.filmorate.dal.DirectorStorage; +import ru.yandex.practicum.filmorate.dto.DirectorDto; +import ru.yandex.practicum.filmorate.dto.request.create.DirectorCreateRequest; +import ru.yandex.practicum.filmorate.dto.request.update.DirectorUpdateRequest; +import ru.yandex.practicum.filmorate.exception.MethodNotImplementedException; + +import java.util.Collection; + +@Slf4j +@Service +@RequiredArgsConstructor +public class DirectorService { + private final DirectorStorage directorStorage; + + public DirectorDto getDirectorDtoById(long directorId) { + log.info("Получение режиссера с id={}", directorId); + // todo + throw new MethodNotImplementedException(); + } + + public Collection getAllDirectorDtos() { + log.info("Получение всех режиссеров"); + // todo + throw new MethodNotImplementedException(); + } + + public DirectorDto create(DirectorCreateRequest request) { + log.info("Добавление нового режиссера name={}.", request.getName()); + // todo + throw new MethodNotImplementedException(); + } + + public DirectorDto update(DirectorUpdateRequest request) { + log.info("Обновление данных режиссера с id={}.", request.getId()); + // todo + throw new MethodNotImplementedException(); + } + + public void delete(Long directorId) { + log.info("Удаление режиссера с id={}", directorId); + // todo + throw new MethodNotImplementedException(); + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/servise/GenreService.java b/src/main/java/ru/yandex/practicum/filmorate/servise/GenreService.java index a141457..590a277 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/servise/GenreService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/servise/GenreService.java @@ -18,14 +18,14 @@ public class GenreService { public GenreDto getGenreDtoById(long genreId) { log.info("Получение жанра с id={}", genreId); return genreStorage.getGenreById(genreId) - .map(genre -> new GenreDto(genre.getId(), genre.getName())) + .map(genre -> new GenreDto(genre.getId(), genre.getName())) .orElseThrow(() -> new NotFoundException("Жанр с id=" + genreId + " не найден.")); } public Collection getAllGenreDtos() { log.info("Получение всех жанров"); return genreStorage.getAllGenres().stream() - .map(genre -> new GenreDto(genre.getId(), genre.getName())) + .map(genre -> new GenreDto(genre.getId(), genre.getName())) .toList(); } From 046673b57bc4a830c67b4c0b86f58907649ecd10 Mon Sep 17 00:00:00 2001 From: AleksOBM Date: Tue, 17 Feb 2026 01:10:31 +0300 Subject: [PATCH 19/47] =?UTF-8?q?=D0=9E=D1=82=D0=BA=D0=BB=D1=8E=D1=87?= =?UTF-8?q?=D0=B8=D1=82=D1=8C=20JPA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ru/yandex/practicum/filmorate/FilmorateApplication.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/ru/yandex/practicum/filmorate/FilmorateApplication.java b/src/main/java/ru/yandex/practicum/filmorate/FilmorateApplication.java index 3f2baca..fcd63be 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/FilmorateApplication.java +++ b/src/main/java/ru/yandex/practicum/filmorate/FilmorateApplication.java @@ -2,8 +2,9 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; -@SpringBootApplication +@SpringBootApplication(exclude = {HibernateJpaAutoConfiguration.class}) public class FilmorateApplication { public static void main(String[] args) { SpringApplication.run(FilmorateApplication.class, args); From b7d0e4066c9d9c6df299690e92b7f3c01b7b34f3 Mon Sep 17 00:00:00 2001 From: AleksOBM Date: Tue, 17 Feb 2026 15:28:35 +0300 Subject: [PATCH 20/47] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20=D1=82=D0=B0=D0=B1=D0=BB=D0=B8=D1=86=D1=8B=20?= =?UTF-8?q?=D0=B2=20=D0=91=D0=94,=20=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D1=82=D1=8C=20=D1=84=D1=83=D0=BD=D0=BA=D1=86?= =?UTF-8?q?=D0=B8=D0=B8=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F=20=D0=B8=20=D0=BF=D0=BE=D0=BB=D1=83=D1=87=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F=20=D1=80=D0=B5=D0=B6=D0=B8=D1=81=D1=81=D0=B5?= =?UTF-8?q?=D1=80=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/DirectorController.java | 4 +-- .../filmorate/dal/database/BaseDbStorage.java | 13 ++++++- .../dal/database/DirectorDbStorage.java | 11 +++--- .../dal/rowmappers/DirectorRowMapper.java | 6 ++-- .../practicum/filmorate/dto/DirectorDto.java | 2 +- .../request/update/DirectorUpdateRequest.java | 6 +++- .../filmorate/mapper/DirectorMapper.java | 32 +++++++++++++++++ .../practicum/filmorate/model/Director.java | 2 +- .../filmorate/servise/DirectorService.java | 35 ++++++++++++++----- src/main/resources/schema.sql | 18 ++++++++++ 10 files changed, 108 insertions(+), 21 deletions(-) create mode 100644 src/main/java/ru/yandex/practicum/filmorate/mapper/DirectorMapper.java diff --git a/src/main/java/ru/yandex/practicum/filmorate/controller/DirectorController.java b/src/main/java/ru/yandex/practicum/filmorate/controller/DirectorController.java index 8000ddf..3b3575c 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/DirectorController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/DirectorController.java @@ -22,7 +22,7 @@ public Collection getAllDirectors() { } @GetMapping("/{directorId}") - public DirectorDto getDirectorById(@PathVariable Long directorId) { + public DirectorDto getDirectorById(@PathVariable Integer directorId) { return directorService.getDirectorDtoById(directorId); } @@ -37,7 +37,7 @@ public DirectorDto updateDirector(@RequestBody @Valid DirectorUpdateRequest dire } @DeleteMapping("/{directorId}") - public void deleteDirector(@PathVariable Long directorId) { + public void deleteDirector(@PathVariable Integer directorId) { directorService.delete(directorId); } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/database/BaseDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/database/BaseDbStorage.java index ee3e3c1..ce88e02 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/database/BaseDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/database/BaseDbStorage.java @@ -1,7 +1,9 @@ package ru.yandex.practicum.filmorate.dal.database; import lombok.RequiredArgsConstructor; +import org.springframework.dao.DataRetrievalFailureException; import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.support.GeneratedKeyHolder; @@ -52,7 +54,16 @@ protected long insertWithKeyHolder(String query, Object... params) { return ps; }, keyHolder); - Long id = keyHolder.getKeyAs(Long.class); + Long id; + try{ + id = keyHolder.getKeyAs(Long.class); + } catch (DataRetrievalFailureException e) { + Integer id1 = keyHolder.getKeyAs(Integer.class); + if (id1 == null) { + throw new InternalServerException("Не удалось получить id элемента"); + } + id = (long) id1; + } if (id != null) { return id; } else { diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/database/DirectorDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/database/DirectorDbStorage.java index 08f9152..b499d28 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/database/DirectorDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/database/DirectorDbStorage.java @@ -29,14 +29,17 @@ public Optional findById(long id) { @Override public Collection findAll() { - // todo - throw new MethodNotImplementedException(); + return findAllInTable("directors"); } @Override public Director createDirector(Director director) { - // todo - throw new MethodNotImplementedException(); + int id = (int) insertWithKeyHolder( + "INSERT INTO directors (director_name) VALUES (?)", + director.getName() + ); + director.setId(id); + return director; } @Override diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/rowmappers/DirectorRowMapper.java b/src/main/java/ru/yandex/practicum/filmorate/dal/rowmappers/DirectorRowMapper.java index 54d0ef1..e9c0476 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/rowmappers/DirectorRowMapper.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/rowmappers/DirectorRowMapper.java @@ -15,7 +15,9 @@ public class DirectorRowMapper implements RowMapper { @Override public Director mapRow(ResultSet resultSet, int rowNum) throws SQLException { - // todo - throw new MethodNotImplementedException(); + return Director.builder() + .id(resultSet.getInt("id")) + .name(resultSet.getString("director_name")) + .build(); } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dto/DirectorDto.java b/src/main/java/ru/yandex/practicum/filmorate/dto/DirectorDto.java index 0479c8b..6f4483d 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dto/DirectorDto.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dto/DirectorDto.java @@ -12,7 +12,7 @@ public class DirectorDto { @JsonProperty(access = JsonProperty.Access.READ_ONLY) - long id; + int id; String name; } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dto/request/update/DirectorUpdateRequest.java b/src/main/java/ru/yandex/practicum/filmorate/dto/request/update/DirectorUpdateRequest.java index 862ffe7..9f560e1 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dto/request/update/DirectorUpdateRequest.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dto/request/update/DirectorUpdateRequest.java @@ -13,8 +13,12 @@ public class DirectorUpdateRequest { @NotNull(message = "Id должен быть указан.") @Positive(message = "Id должен быть положительным числом.") - Long id; + Integer id; @Size(min = 2, max = 70, message = "Длина имени режиссера может быть от 2 до 70 символов.") String name; + + public boolean hasNname() { + return name != null; + } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/mapper/DirectorMapper.java b/src/main/java/ru/yandex/practicum/filmorate/mapper/DirectorMapper.java new file mode 100644 index 0000000..2d874ef --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/mapper/DirectorMapper.java @@ -0,0 +1,32 @@ +package ru.yandex.practicum.filmorate.mapper; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import ru.yandex.practicum.filmorate.dto.DirectorDto; +import ru.yandex.practicum.filmorate.dto.request.create.DirectorCreateRequest; +import ru.yandex.practicum.filmorate.dto.request.update.DirectorUpdateRequest; +import ru.yandex.practicum.filmorate.model.Director; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class DirectorMapper { + public static Director mapToDirector(DirectorCreateRequest request) { + return Director.builder() + .name(request.getName()) + .build(); + } + + public static DirectorDto mapToDirectorDto(Director director) { + return DirectorDto.builder() + .id(director.getId()) + .name(director.getName()) + .build(); + } + + public static Director updateDirectorFields(Director director, DirectorUpdateRequest request) { + if (request.hasNname()) { + director.setName(request.getName()); + } + return director; + } + +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/Director.java b/src/main/java/ru/yandex/practicum/filmorate/model/Director.java index 501d383..a646372 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/model/Director.java +++ b/src/main/java/ru/yandex/practicum/filmorate/model/Director.java @@ -13,6 +13,6 @@ @EqualsAndHashCode(of = "id") @FieldDefaults(level = AccessLevel.PRIVATE) public class Director { - Long id; + Integer id; String name; } diff --git a/src/main/java/ru/yandex/practicum/filmorate/servise/DirectorService.java b/src/main/java/ru/yandex/practicum/filmorate/servise/DirectorService.java index 4e91701..cead54f 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/servise/DirectorService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/servise/DirectorService.java @@ -2,37 +2,48 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.dao.DuplicateKeyException; import org.springframework.stereotype.Service; import ru.yandex.practicum.filmorate.dal.DirectorStorage; import ru.yandex.practicum.filmorate.dto.DirectorDto; import ru.yandex.practicum.filmorate.dto.request.create.DirectorCreateRequest; import ru.yandex.practicum.filmorate.dto.request.update.DirectorUpdateRequest; +import ru.yandex.practicum.filmorate.exception.DuplicatedDataException; import ru.yandex.practicum.filmorate.exception.MethodNotImplementedException; +import ru.yandex.practicum.filmorate.exception.NotFoundException; +import ru.yandex.practicum.filmorate.mapper.DirectorMapper; +import ru.yandex.practicum.filmorate.model.Director; +import java.io.IOException; import java.util.Collection; -@Slf4j @Service @RequiredArgsConstructor +@Slf4j public class DirectorService { private final DirectorStorage directorStorage; - public DirectorDto getDirectorDtoById(long directorId) { + public DirectorDto getDirectorDtoById(int directorId) { log.info("Получение режиссера с id={}", directorId); - // todo - throw new MethodNotImplementedException(); + return DirectorMapper.mapToDirectorDto(findDirector(directorId)); } public Collection getAllDirectorDtos() { log.info("Получение всех режиссеров"); - // todo - throw new MethodNotImplementedException(); + return directorStorage.findAll().stream().map(DirectorMapper::mapToDirectorDto).toList(); } public DirectorDto create(DirectorCreateRequest request) { log.info("Добавление нового режиссера name={}.", request.getName()); - // todo - throw new MethodNotImplementedException(); + Director director = DirectorMapper.mapToDirector(request); + + try { + director = directorStorage.createDirector(director); + } catch (DuplicateKeyException e) { + throw new DuplicatedDataException("Этот режиссер " + director.getName() + " уже был добавлен ранее."); + } + + return DirectorMapper.mapToDirectorDto(director); } public DirectorDto update(DirectorUpdateRequest request) { @@ -41,9 +52,15 @@ public DirectorDto update(DirectorUpdateRequest request) { throw new MethodNotImplementedException(); } - public void delete(Long directorId) { + public void delete(int directorId) { log.info("Удаление режиссера с id={}", directorId); // todo throw new MethodNotImplementedException(); } + + private Director findDirector(int directorId) { + return directorStorage.findById(directorId).orElseThrow( + () -> new NotFoundException("Режиссер с id=" + directorId + " не найден.") + ); + } } diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index 6e8c657..acf9457 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -34,6 +34,17 @@ CREATE TABLE genres_of_films ( film_id bigint NOT NULL ); +CREATE TABLE directors ( + id int NOT NULL AUTO_INCREMENT PRIMARY KEY, + director_name varchar(70) NOT NULL +); + +CREATE TABLE directors_of_films ( + id bigint NOT NULL AUTO_INCREMENT PRIMARY KEY, + director_id int NOT NULL, + film_id bigint NOT NULL +); + CREATE TABLE mpa ( id int NOT NULL AUTO_INCREMENT PRIMARY KEY, mpa_name varchar(5) NOT NULL @@ -91,6 +102,12 @@ REFERENCES users (id); ALTER TABLE likes ADD CONSTRAINT fk_likes_film_id FOREIGN KEY(film_id) REFERENCES films (id); +ALTER TABLE directors_of_films ADD CONSTRAINT fk_directors_of_films_director_id FOREIGN KEY(director_id) +REFERENCES directors (id); + +ALTER TABLE directors_of_films ADD CONSTRAINT fk_directors_of_films_film_id FOREIGN KEY(film_id) +REFERENCES films (id); + ALTER TABLE reviews ADD CONSTRAINT fk_reviews_user_id FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE; @@ -117,6 +134,7 @@ CREATE INDEX IF NOT EXISTS idx_films_film_name ON films (film_name); CREATE INDEX IF NOT EXISTS idx_reviews_film_id ON reviews (film_id); CREATE INDEX IF NOT EXISTS idx_reviews_user_id ON reviews (user_id); +CREATE UNIQUE INDEX IF NOT EXISTS idx_directors_name ON directors (director_name); CREATE INDEX IF NOT EXISTS idx_review_likes_review_id ON review_likes (review_id); From f163c26774e7d39bdf8b89ff7cde9457dc89df0a Mon Sep 17 00:00:00 2001 From: AleksOBM Date: Wed, 18 Feb 2026 23:57:38 +0300 Subject: [PATCH 21/47] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20=D0=BB=D0=BE=D0=B3=D0=B8=D0=BA=D1=83=20=D0=BE?= =?UTF-8?q?=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B8?= =?UTF-8?q?=20=D1=83=D0=B4=D0=B0=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F=20=D1=80?= =?UTF-8?q?=D0=B5=D0=B6=D0=B8=D1=81=D1=81=D0=B5=D1=80=D0=BE=D0=B2,=20?= =?UTF-8?q?=D0=B8=20=D1=81=D0=BE=D1=80=D1=82=D0=B8=D1=80=D0=BE=D0=B2=D0=BA?= =?UTF-8?q?=D1=83=20=D1=84=D0=B8=D0=BB=D1=8C=D0=BC=D0=BE=D0=B2=20=D0=BF?= =?UTF-8?q?=D0=BE=20=D1=80=D0=B5=D0=B6=D0=B8=D1=81=D1=81=D0=B5=D1=80=D0=B0?= =?UTF-8?q?=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../filmorate/controller/ErrorHandler.java | 7 ++ .../filmorate/controller/FilmController.java | 8 ++ .../filmorate/dal/DirectorStorage.java | 7 +- .../practicum/filmorate/dal/FilmStorage.java | 5 +- .../filmorate/dal/database/BaseDbStorage.java | 99 +++++++++---------- .../dal/database/DirectorDbStorage.java | 22 +++-- .../filmorate/dal/database/FilmDbStorage.java | 34 ++++++- .../dal/database/sql/FilmQueryes.java | 60 ++++++++++- .../dal/inmemory/InMemoryFilmStorage.java | 5 + .../dal/rowmappers/DirectorRowMapper.java | 1 - .../practicum/filmorate/dto/DirectorDto.java | 6 +- .../practicum/filmorate/dto/FilmDto.java | 5 +- .../request/create/DirectorCreateRequest.java | 2 + .../dto/request/create/FilmCreateRequest.java | 11 ++- .../request/update/DirectorUpdateRequest.java | 2 + .../dto/request/update/FilmUpdateRequest.java | 8 ++ .../filmorate/mapper/DirectorMapper.java | 5 +- .../filmorate/mapper/FilmMapper.java | 18 +++- .../practicum/filmorate/model/Film.java | 11 ++- .../filmorate/servise/DirectorService.java | 23 +++-- .../filmorate/servise/FilmService.java | 92 ++++++++++++++--- .../servise/util/FilmSortingAction.java | 43 ++++++++ src/main/resources/schema.sql | 4 +- 23 files changed, 381 insertions(+), 97 deletions(-) create mode 100644 src/main/java/ru/yandex/practicum/filmorate/servise/util/FilmSortingAction.java diff --git a/src/main/java/ru/yandex/practicum/filmorate/controller/ErrorHandler.java b/src/main/java/ru/yandex/practicum/filmorate/controller/ErrorHandler.java index e4daae7..ce37374 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/ErrorHandler.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/ErrorHandler.java @@ -4,6 +4,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.validation.FieldError; import org.springframework.validation.method.MethodValidationException; import org.springframework.web.bind.MethodArgumentNotValidException; @@ -83,6 +84,12 @@ public ErrorResponse handlerMethodValidationException(final MethodValidationExce return new ErrorResponse(e.getAllErrors().getFirst().getDefaultMessage()); } + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ExceptionHandler + public ErrorResponse handleEmptyBody(HttpMessageNotReadableException ex) { + return new ErrorResponse("Тело запроса отсутствует или некорректно"); + } + @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity> handleMethodArgumentException(final MethodArgumentNotValidException e) { Map errors = new HashMap<>(); 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 7b7fa99..916e116 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java @@ -36,6 +36,14 @@ public Collection getTop(@RequestParam(defaultValue = "10", required = return filmService.getTopFilms(count, genreId, year); } + @GetMapping("/director/{directorId}") + public Collection findSortedFilmsOfDirector( + @PathVariable Integer directorId, + @RequestParam(required = false) String sortBy + ) { + return filmService.getSortedFilmsOfDirector(directorId, sortBy); + } + @PostMapping @ResponseStatus(HttpStatus.CREATED) public FilmDto create(@RequestBody @Valid FilmCreateRequest request) { diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/DirectorStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/DirectorStorage.java index a1af27a..4cbedc8 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/DirectorStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/DirectorStorage.java @@ -7,7 +7,7 @@ public interface DirectorStorage { - /// Получить режиссера по id + /// Получить режиссера по id Optional findById(long id); /// Получить всех режиссеров @@ -20,6 +20,9 @@ public interface DirectorStorage { Director updateDirector(Director director); /// Удалить режиссера по id - void deleteDirector(Director director); + void deleteDirector(Integer directorId); + + /// Проверить что директор отсутствует по id + boolean checkDirectorIsNotPresent(int directorId); } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/FilmStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/FilmStorage.java index 1454545..c0bbf12 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/FilmStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/FilmStorage.java @@ -36,6 +36,9 @@ public interface FilmStorage { /// Убрать лайк void removeLike(long filmId, long userId); - // Метод для получения матрицы лайков всех пользователей + /// Получить список фильмов по id режиссера + Collection getALLFilmsOfDirector(Integer directorId); + + /// Метод для получения матрицы лайков всех пользователей Map> getAllLikes(); } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/database/BaseDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/database/BaseDbStorage.java index ce88e02..34550af 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/database/BaseDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/database/BaseDbStorage.java @@ -3,7 +3,6 @@ import lombok.RequiredArgsConstructor; import org.springframework.dao.DataRetrievalFailureException; import org.springframework.dao.EmptyResultDataAccessException; -import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.support.GeneratedKeyHolder; @@ -15,61 +14,61 @@ @RequiredArgsConstructor public class BaseDbStorage { - protected final JdbcTemplate jdbc; - protected final RowMapper rowMapper; + protected final JdbcTemplate jdbc; + protected final RowMapper rowMapper; - protected Set findColumnByQuery(String query, Class objectClass, Object... params) { - try { - return new HashSet<>(jdbc.queryForList(query, objectClass, params)); - } catch (EmptyResultDataAccessException ignored) { - return HashSet.newHashSet(0); - } - } + protected Set findColumnByQuery(String query, Class objectClass, Object... params) { + try { + return new HashSet<>(jdbc.queryForList(query, objectClass, params)); + } catch (EmptyResultDataAccessException ignored) { + return HashSet.newHashSet(0); + } + } - protected Optional findOneByIdInTable(long id, String tableName) { - return jdbc.query("SELECT * FROM " + tableName + " WHERE id = ?", rowMapper, id) - .stream() - .findFirst(); - } + protected Optional findOneByIdInTable(long id, String tableName) { + return jdbc.query("SELECT * FROM " + tableName + " WHERE id = ?", rowMapper, id) + .stream() + .findFirst(); + } - protected Collection findAllInTable(String tableName) { - return jdbc.query("SELECT * FROM " + tableName + " ORDER BY id", rowMapper) - .stream() - .toList(); - } + protected Collection findAllInTable(String tableName) { + return jdbc.query("SELECT * FROM " + tableName + " ORDER BY id", rowMapper) + .stream() + .toList(); + } - protected boolean checkIdIsNotPresentInTable(long id, String tableName) { - String sql = "SELECT ID FROM " + tableName + " WHERE ID = ?"; - return jdbc.queryForList(sql, Long.class, id).isEmpty(); - } + protected boolean checkIdIsNotPresentInTable(long id, String tableName) { + String sql = "SELECT ID FROM " + tableName + " WHERE ID = ?"; + return jdbc.queryForList(sql, Long.class, id).isEmpty(); + } - protected long insertWithKeyHolder(String query, Object... params) { - GeneratedKeyHolder keyHolder = new GeneratedKeyHolder(); - jdbc.update(connection -> { - PreparedStatement ps = connection - .prepareStatement(query, Statement.RETURN_GENERATED_KEYS); - for (int idx = 0; idx < params.length; idx++) { - ps.setObject(idx + 1, params[idx]); - } - return ps; - }, keyHolder); + protected long insertWithKeyHolder(String query, Object... params) { + GeneratedKeyHolder keyHolder = new GeneratedKeyHolder(); + jdbc.update(connection -> { + PreparedStatement ps = connection + .prepareStatement(query, Statement.RETURN_GENERATED_KEYS); + for (int idx = 0; idx < params.length; idx++) { + ps.setObject(idx + 1, params[idx]); + } + return ps; + }, keyHolder); - Long id; - try{ - id = keyHolder.getKeyAs(Long.class); - } catch (DataRetrievalFailureException e) { - Integer id1 = keyHolder.getKeyAs(Integer.class); - if (id1 == null) { - throw new InternalServerException("Не удалось получить id элемента"); - } - id = (long) id1; - } - if (id != null) { - return id; - } else { - throw new InternalServerException("Не удалось сохранить данные"); - } - } + Long id; + try { + id = keyHolder.getKeyAs(Long.class); + } catch (DataRetrievalFailureException e) { + Integer id1 = keyHolder.getKeyAs(Integer.class); + if (id1 == null) { + throw new InternalServerException("Не удалось получить id элемента"); + } + id = (long) id1; + } + if (id != null) { + return id; + } else { + throw new InternalServerException("Не удалось сохранить данные"); + } + } protected void updateWithControl(String query, Object... params) { int rowsUpdated = jdbc.update(query, params); diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/database/DirectorDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/database/DirectorDbStorage.java index b499d28..440d58c 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/database/DirectorDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/database/DirectorDbStorage.java @@ -5,7 +5,6 @@ import org.springframework.jdbc.core.RowMapper; import org.springframework.stereotype.Repository; import ru.yandex.practicum.filmorate.dal.DirectorStorage; -import ru.yandex.practicum.filmorate.exception.MethodNotImplementedException; import ru.yandex.practicum.filmorate.model.Director; import java.util.Collection; @@ -44,13 +43,24 @@ public Director createDirector(Director director) { @Override public Director updateDirector(Director director) { - // todo - throw new MethodNotImplementedException(); + updateWithControl( + "UPDATE directors SET director_name= ? WHERE id = ?", + director.getName(), + director.getId() + ); + return director; } @Override - public void deleteDirector(Director director) { - // todo - throw new MethodNotImplementedException(); + public void deleteDirector(Integer directorId) { + updateWithControl( + "DELETE FROM directors WHERE id = ?", + directorId + ); } + + public boolean checkDirectorIsNotPresent(int directorId) { + return checkIdIsNotPresentInTable(directorId, "directors"); + } + } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/database/FilmDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/database/FilmDbStorage.java index dec1b29..6fda02e 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/database/FilmDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/database/FilmDbStorage.java @@ -44,10 +44,12 @@ public Film createFilm(Film film) { film.setId(id); insertGenreIds(film); + insertDirectorIds(film); return film; } @Override + @Transactional public Film updateFilm(Film film) { updateWithControl( SQL_FILMS_UPDATE, @@ -58,6 +60,9 @@ public Film updateFilm(Film film) { film.getMpaId(), film.getId() ); + + insertGenreIds(film); + insertDirectorIds(film); return film; } @@ -66,6 +71,7 @@ public Film updateFilm(Film film) { public Optional findById(long filmId) { Optional filmOptional = findOneByIdInTable(filmId, "films"); filmOptional.ifPresent(film -> film.setGenreIds(getGenreIdsByFilmId(filmId))); + filmOptional.ifPresent(film -> film.setDirectorIds(getDirectorIdsByFilmId(filmId))); return filmOptional; } @@ -85,6 +91,11 @@ public void removeLike(long filmId, long userId) { updateWithControl(SQL_FILMS_DELETE_LIKE, filmId, userId); } + @Override + public Collection getALLFilmsOfDirector(Integer directorId) { + return findManyFilms(SQL_FILMS_FIND_ALL_OF_DIRECTOR, directorId); + } + @Override public Collection getTop(int top) { return findManyFilms(SQL_FILMS_FIND_TOP, top); @@ -122,10 +133,22 @@ private void insertGenreIds(Film film) { String placeholders = String.join(",", Collections.nCopies(genreIds.size(), " (" + film.getId() + ", ?)") ); - String sql = "INSERT INTO GENRES_OF_FILMS (FILM_ID, GENRE_ID) VALUES" + placeholders; + String sql = SQL_FILMS_INSERT_GENREIDS + placeholders; updateWithControl(sql, genreIds.toArray()); } + private void insertDirectorIds(Film film) { + Set directorIds = film.getDirectorIds(); + if (directorIds.isEmpty()) { + return; + } + String placeholders = String.join(",", + Collections.nCopies(directorIds.size(), " (" + film.getId() + ", ?)") + ); + String sql = SQL_FILMS_INSERT_DIRECTORIDS + placeholders; + updateWithControl(sql, directorIds.toArray()); + } + private Collection findManyFilms(String query, Object... params) { return jdbc.query(query, rs -> { Map films = new LinkedHashMap<>(); @@ -152,6 +175,11 @@ private Collection findManyFilms(String query, Object... params) { if (!rs.wasNull() && film != null) { film.addGenreId(genreId); } + + int directorId = rs.getInt("director_id"); + if (!rs.wasNull() && film != null) { + film.addDirectorId(directorId); + } } return new ArrayList<>(films.values()); @@ -162,6 +190,10 @@ private Set getGenreIdsByFilmId(long filmId) { return findColumnByQuery(SQL_FILMS_FIND_GENREIDS_BY_FILM_ID, Integer.class, filmId); } + private Set getDirectorIdsByFilmId(long filmId) { + return findColumnByQuery(SQL_FILMS_FIND_DIRECTORIDS_BY_FILM_ID, Integer.class, filmId); + } + @Override public Map> getAllLikes() { diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/database/sql/FilmQueryes.java b/src/main/java/ru/yandex/practicum/filmorate/dal/database/sql/FilmQueryes.java index 4bac477..40d70e4 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/database/sql/FilmQueryes.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/database/sql/FilmQueryes.java @@ -10,6 +10,7 @@ public class FilmQueryes { mrg.DURATION, mrg.MPA_ID, gof.GENRE_ID, + dof.DIRECTOR_ID, user_id FROM ( SELECT f.ID, @@ -23,7 +24,43 @@ public class FilmQueryes { LEFT JOIN LIKES l ON f.ID = l.FILM_ID ORDER BY f.ID ) AS mrg - LEFT JOIN GENRES_OF_FILMS gof ON mrg.id = gof.FILM_ID; + LEFT JOIN GENRES_OF_FILMS gof ON mrg.id = gof.FILM_ID + LEFT JOIN DIRECTORS_OF_FILMS dof ON mrg.ID = dof.FILM_ID + """; + + public static final String SQL_FILMS_FIND_ALL_OF_DIRECTOR = """ + SELECT mrg.id, + mrg.FILM_NAME, + mrg.DESCRIPTION, + mrg.RELEASE_DATE, + mrg.DURATION, + mrg.MPA_ID, + gof.GENRE_ID, + dof.DIRECTOR_ID, + user_id + FROM ( + SELECT f.ID, + f.FILM_NAME, + DESCRIPTION, + RELEASE_DATE, + DURATION, + MPA_ID, + l.USER_ID + FROM FILMS f + LEFT JOIN LIKES l ON f.ID = l.FILM_ID + ORDER BY f.ID + ) AS mrg + LEFT JOIN GENRES_OF_FILMS gof ON mrg.id = gof.FILM_ID + LEFT JOIN DIRECTORS_OF_FILMS dof ON mrg.ID = dof.FILM_ID + WHERE mrg.ID IN ( + SELECT f1.ID + FROM FILMS f1 + WHERE f1.ID IN ( + SELECT dof2.FILM_ID + FROM DIRECTORS_OF_FILMS dof2 + WHERE dof2.DIRECTOR_ID = ? + ) + ) """; public static final String SQL_FILMS_FIND_TOP = """ @@ -34,6 +71,7 @@ public class FilmQueryes { mrg.DURATION, mrg.MPA_ID, gof.GENRE_ID, + dof.DIRECTOR_ID, user_id FROM ( SELECT f.ID, @@ -47,6 +85,7 @@ public class FilmQueryes { LEFT JOIN LIKES l ON f.ID = l.FILM_ID ) AS mrg LEFT JOIN GENRES_OF_FILMS gof ON mrg.id = gof.FILM_ID + LEFT JOIN DIRECTORS_OF_FILMS dof ON mrg.ID = dof.FILM_ID WHERE mrg.id IN ( SELECT rte.ID FROM ( @@ -58,12 +97,12 @@ WHERE mrg.id IN ( ) AS rte ORDER BY rate DESC, rte.ID LIMIT ? - ); + ) """; public static final String SQL_FILMS_SET_LIKE = """ INSERT INTO LIKES (FILM_ID, USER_ID) - VALUES (?, ?); + VALUES (?, ?) """; public static final String SQL_FILMS_DELETE_LIKE = """ @@ -79,6 +118,21 @@ INSERT INTO LIKES (FILM_ID, USER_ID) WHERE gof.FILM_ID = ? """; + public static final String SQL_FILMS_INSERT_GENREIDS = """ + MERGE INTO GENRES_OF_FILMS (FILM_ID, GENRE_ID) KEY (FILM_ID, GENRE_ID) VALUES + """; + + public static final String SQL_FILMS_FIND_DIRECTORIDS_BY_FILM_ID = """ + SELECT d.ID + FROM DIRECTORS d + JOIN DIRECTORS_OF_FILMS dof ON d.ID = dof.DIRECTOR_ID + WHERE dof.FILM_ID = ? + """; + + public static final String SQL_FILMS_INSERT_DIRECTORIDS = """ + MERGE INTO DIRECTORS_OF_FILMS (FILM_ID, DIRECTOR_ID) KEY (FILM_ID, DIRECTOR_ID) VALUES + """; + public static final String SQL_FILMS_INSERT = """ INSERT INTO films ( film_name, diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/inmemory/InMemoryFilmStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/inmemory/InMemoryFilmStorage.java index ffdb502..b1aa8d0 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/inmemory/InMemoryFilmStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/inmemory/InMemoryFilmStorage.java @@ -67,6 +67,11 @@ public void removeLike(long filmId, long userId) { throw new MethodNotImplementedException(); } + @Override + public Collection getALLFilmsOfDirector(Integer directorId) { + throw new MethodNotImplementedException(); + } + @Override public void setLike(long filmId, long userId) { throw new MethodNotImplementedException(); diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/rowmappers/DirectorRowMapper.java b/src/main/java/ru/yandex/practicum/filmorate/dal/rowmappers/DirectorRowMapper.java index e9c0476..e69af46 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/rowmappers/DirectorRowMapper.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/rowmappers/DirectorRowMapper.java @@ -3,7 +3,6 @@ import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.jdbc.core.RowMapper; import org.springframework.stereotype.Component; -import ru.yandex.practicum.filmorate.exception.MethodNotImplementedException; import ru.yandex.practicum.filmorate.model.Director; import java.sql.ResultSet; diff --git a/src/main/java/ru/yandex/practicum/filmorate/dto/DirectorDto.java b/src/main/java/ru/yandex/practicum/filmorate/dto/DirectorDto.java index 6f4483d..97c3e6f 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dto/DirectorDto.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dto/DirectorDto.java @@ -2,12 +2,14 @@ import com.fasterxml.jackson.annotation.JsonProperty; import lombok.AccessLevel; -import lombok.Builder; +import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; import lombok.experimental.FieldDefaults; @Data -@Builder +@NoArgsConstructor +@AllArgsConstructor @FieldDefaults(level = AccessLevel.PRIVATE) public class DirectorDto { diff --git a/src/main/java/ru/yandex/practicum/filmorate/dto/FilmDto.java b/src/main/java/ru/yandex/practicum/filmorate/dto/FilmDto.java index cf82f7b..ba35e8f 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dto/FilmDto.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dto/FilmDto.java @@ -10,9 +10,9 @@ import java.util.ArrayList; import java.util.Collection; +@JsonPropertyOrder @Data @Builder -@JsonPropertyOrder public class FilmDto { @JsonProperty(access = JsonProperty.Access.READ_ONLY) private long id; @@ -29,4 +29,7 @@ public class FilmDto { @Builder.Default private Collection genres = new ArrayList<>(); + + @Builder.Default + private Collection directors = new ArrayList<>(); } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dto/request/create/DirectorCreateRequest.java b/src/main/java/ru/yandex/practicum/filmorate/dto/request/create/DirectorCreateRequest.java index b73286b..da66a38 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dto/request/create/DirectorCreateRequest.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dto/request/create/DirectorCreateRequest.java @@ -1,5 +1,6 @@ package ru.yandex.practicum.filmorate.dto.request.create; +import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; import lombok.AccessLevel; import lombok.Data; @@ -10,5 +11,6 @@ public class DirectorCreateRequest { @Size(min = 2, max = 70, message = "Длина имени режиссера может быть от 2 до 70 символов.") + @NotBlank(message = "Имя режиссера не может быть пустым.") String name; } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dto/request/create/FilmCreateRequest.java b/src/main/java/ru/yandex/practicum/filmorate/dto/request/create/FilmCreateRequest.java index e117672..a2ad789 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dto/request/create/FilmCreateRequest.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dto/request/create/FilmCreateRequest.java @@ -6,7 +6,8 @@ import jakarta.validation.constraints.Size; import lombok.Data; import org.hibernate.validator.constraints.time.DurationMin; -import ru.yandex.practicum.filmorate.dto.adapters.*; +import ru.yandex.practicum.filmorate.dto.adapters.MinDate; +import ru.yandex.practicum.filmorate.model.Director; import ru.yandex.practicum.filmorate.model.Genre; import ru.yandex.practicum.filmorate.model.Mpa; @@ -18,16 +19,16 @@ @Data public class FilmCreateRequest { - @NotNull(message = "Название должно быть указано.") @Size(min = 2, max = 100, message = "Название должно содержать от 2 до 100 символов.") + @NotNull(message = "Название должно быть указано.") private String name; - @NotNull(message = "Описание должно быть указано.") @Size(max = 200, message = "Максимальная длина описания — 200 символов.") + @NotNull(message = "Описание должно быть указано.") private String description; - @NotNull(message = "Дата релиза должна быть указана.") @Past(message = "Дата релиза должна быть в прошлом.") + @NotNull(message = "Дата релиза должна быть указана.") @MinDate(minDate = "1895-12-28") private LocalDate releaseDate; @@ -41,4 +42,6 @@ public class FilmCreateRequest { private Set genres = new HashSet<>(); + private Set directors = new HashSet<>(); + } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dto/request/update/DirectorUpdateRequest.java b/src/main/java/ru/yandex/practicum/filmorate/dto/request/update/DirectorUpdateRequest.java index 9f560e1..6f930e9 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dto/request/update/DirectorUpdateRequest.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dto/request/update/DirectorUpdateRequest.java @@ -1,5 +1,6 @@ package ru.yandex.practicum.filmorate.dto.request.update; +import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Positive; import jakarta.validation.constraints.Size; @@ -16,6 +17,7 @@ public class DirectorUpdateRequest { Integer id; @Size(min = 2, max = 70, message = "Длина имени режиссера может быть от 2 до 70 символов.") + @NotBlank(message = "Имя режиссера не может быть пустым.") String name; public boolean hasNname() { diff --git a/src/main/java/ru/yandex/practicum/filmorate/dto/request/update/FilmUpdateRequest.java b/src/main/java/ru/yandex/practicum/filmorate/dto/request/update/FilmUpdateRequest.java index 3f5c862..33206b1 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dto/request/update/FilmUpdateRequest.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dto/request/update/FilmUpdateRequest.java @@ -6,6 +6,7 @@ import jakarta.validation.constraints.Size; import lombok.Data; import org.hibernate.validator.constraints.time.DurationMin; +import ru.yandex.practicum.filmorate.model.Director; import ru.yandex.practicum.filmorate.model.Genre; import ru.yandex.practicum.filmorate.model.Mpa; @@ -35,6 +36,9 @@ public class FilmUpdateRequest { @Size(min = 1, message = "Должен быть указан хотябы один жанр.") private Set genres; + @Size(min = 1, message = "Должен быть указан хотябы один режиссер.") + private Set directors; + private Mpa mpa; public boolean hasName() { @@ -57,6 +61,10 @@ public boolean hasGenres() { return genres != null; } + public boolean hasDirectors() { + return directors != null; + } + public boolean hasMpa() { return mpa != null; } diff --git a/src/main/java/ru/yandex/practicum/filmorate/mapper/DirectorMapper.java b/src/main/java/ru/yandex/practicum/filmorate/mapper/DirectorMapper.java index 2d874ef..e27dbac 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/mapper/DirectorMapper.java +++ b/src/main/java/ru/yandex/practicum/filmorate/mapper/DirectorMapper.java @@ -16,10 +16,7 @@ public static Director mapToDirector(DirectorCreateRequest request) { } public static DirectorDto mapToDirectorDto(Director director) { - return DirectorDto.builder() - .id(director.getId()) - .name(director.getName()) - .build(); + return new DirectorDto(director.getId(), director.getName()); } public static Director updateDirectorFields(Director director, DirectorUpdateRequest request) { diff --git a/src/main/java/ru/yandex/practicum/filmorate/mapper/FilmMapper.java b/src/main/java/ru/yandex/practicum/filmorate/mapper/FilmMapper.java index 963e7f5..f7553ce 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/mapper/FilmMapper.java +++ b/src/main/java/ru/yandex/practicum/filmorate/mapper/FilmMapper.java @@ -2,11 +2,13 @@ import lombok.AccessLevel; import lombok.NoArgsConstructor; +import ru.yandex.practicum.filmorate.dto.DirectorDto; import ru.yandex.practicum.filmorate.dto.FilmDto; import ru.yandex.practicum.filmorate.dto.GenreDto; import ru.yandex.practicum.filmorate.dto.MpaDto; import ru.yandex.practicum.filmorate.dto.request.create.FilmCreateRequest; import ru.yandex.practicum.filmorate.dto.request.update.FilmUpdateRequest; +import ru.yandex.practicum.filmorate.model.Director; import ru.yandex.practicum.filmorate.model.Film; import ru.yandex.practicum.filmorate.model.Genre; import ru.yandex.practicum.filmorate.model.Mpa; @@ -28,10 +30,13 @@ public static Film mapToFilm(FilmCreateRequest request) { .genreIds(request.getGenres().stream() .map(Genre::getId) .collect(Collectors.toSet())) + .directorIds(request.getDirectors().stream() + .map(Director::getId) + .collect(Collectors.toSet())) .build(); } - public static FilmDto mapToFilmDto(Film film, Mpa mpa, Set genres) { + public static FilmDto mapToFilmDto(Film film, Mpa mpa, Set genres, Set directors) { return FilmDto.builder() .id(film.getId()) .name(film.getName()) @@ -44,6 +49,11 @@ public static FilmDto mapToFilmDto(Film film, Mpa mpa, Set genres) { .sorted(Comparator.comparing(Genre::getId)) .map(genre -> new GenreDto(genre.getId(), genre.getName())) .toList()) + .directors(directors.stream() + .sorted(Comparator.comparing(Director::getId)) + .map(director -> new DirectorDto(director.getId(), director.getName())) + .toList() + ) .build(); } @@ -75,6 +85,12 @@ public static Film updateFilmFields(Film film, FilmUpdateRequest request) { .collect(Collectors.toSet())); } + if (request.hasDirectors()) { + film.setDirectorIds(request.getDirectors().stream() + .map(Director::getId) + .collect(Collectors.toSet())); + } + return film; } 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 a5d70aa..a16f4e6 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/model/Film.java +++ b/src/main/java/ru/yandex/practicum/filmorate/model/Film.java @@ -13,8 +13,8 @@ @Data @Builder(toBuilder = true) -@Jacksonized @EqualsAndHashCode(of = "id") +@Jacksonized public class Film { private Long id; @@ -31,10 +31,13 @@ public class Film { private Integer mpaId; @Builder.Default - private Set likes = new HashSet<>(); + private Set directorIds = new HashSet<>(); @Builder.Default + private Set likes = new HashSet<>(); + @JsonProperty(value = "rate") + @Builder.Default private int likesCount = 0; public int getLikesCount() { @@ -54,6 +57,10 @@ public void addGenreId(int genreId) { genreIds.add(genreId); } + public void addDirectorId(int directorId) { + directorIds.add(directorId); + } + public void addLike(long userId) { likes.add(userId); } diff --git a/src/main/java/ru/yandex/practicum/filmorate/servise/DirectorService.java b/src/main/java/ru/yandex/practicum/filmorate/servise/DirectorService.java index cead54f..2e1dc22 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/servise/DirectorService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/servise/DirectorService.java @@ -9,12 +9,10 @@ import ru.yandex.practicum.filmorate.dto.request.create.DirectorCreateRequest; import ru.yandex.practicum.filmorate.dto.request.update.DirectorUpdateRequest; import ru.yandex.practicum.filmorate.exception.DuplicatedDataException; -import ru.yandex.practicum.filmorate.exception.MethodNotImplementedException; import ru.yandex.practicum.filmorate.exception.NotFoundException; import ru.yandex.practicum.filmorate.mapper.DirectorMapper; import ru.yandex.practicum.filmorate.model.Director; -import java.io.IOException; import java.util.Collection; @Service @@ -48,14 +46,26 @@ public DirectorDto create(DirectorCreateRequest request) { public DirectorDto update(DirectorUpdateRequest request) { log.info("Обновление данных режиссера с id={}.", request.getId()); - // todo - throw new MethodNotImplementedException(); + Director oldDirector = findDirector(request.getId()); + Director newDirector = DirectorMapper.updateDirectorFields(oldDirector, request); + + try { + newDirector = directorStorage.updateDirector(newDirector); + } catch (DuplicateKeyException e) { + throw new DuplicatedDataException( + "Этот режиссер " + newDirector.getName() + " уже был добавлен ранее." + ); + } + + return DirectorMapper.mapToDirectorDto(newDirector); } public void delete(int directorId) { log.info("Удаление режиссера с id={}", directorId); - // todo - throw new MethodNotImplementedException(); + if (directorStorage.checkDirectorIsNotPresent(directorId)) { + throw new NotFoundException("Режиссер с id=" + directorId + " не найден."); + } + directorStorage.deleteDirector(directorId); } private Director findDirector(int directorId) { @@ -63,4 +73,5 @@ private Director findDirector(int directorId) { () -> new NotFoundException("Режиссер с id=" + directorId + " не найден.") ); } + } diff --git a/src/main/java/ru/yandex/practicum/filmorate/servise/FilmService.java b/src/main/java/ru/yandex/practicum/filmorate/servise/FilmService.java index 2e17241..cfc0da7 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/servise/FilmService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/servise/FilmService.java @@ -6,16 +6,14 @@ import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Service; -import ru.yandex.practicum.filmorate.dal.FilmStorage; -import ru.yandex.practicum.filmorate.dal.GenreStorage; -import ru.yandex.practicum.filmorate.dal.MpaStorage; -import ru.yandex.practicum.filmorate.dal.UserStorage; +import ru.yandex.practicum.filmorate.dal.*; import ru.yandex.practicum.filmorate.dto.FilmDto; import ru.yandex.practicum.filmorate.dto.request.create.FilmCreateRequest; import ru.yandex.practicum.filmorate.dto.request.update.FilmUpdateRequest; import ru.yandex.practicum.filmorate.exception.NotFoundException; import ru.yandex.practicum.filmorate.exception.ParameterNotValidException; import ru.yandex.practicum.filmorate.mapper.FilmMapper; +import ru.yandex.practicum.filmorate.servise.util.FilmSortingAction; import ru.yandex.practicum.filmorate.model.*; import ru.yandex.practicum.filmorate.servise.util.LikeAction; @@ -29,6 +27,7 @@ public class FilmService { private final UserStorage userStorage; private final MpaStorage mpaStorage; private final GenreStorage genreStorage; + private final DirectorStorage directorStorage; private Map mpas; private Map genres; @@ -40,12 +39,14 @@ public FilmService( @Qualifier("userDbStorage") UserStorage userStorage, @Qualifier("mpaDbStorage") MpaStorage mpaStorage, @Qualifier("genreDbStorage") GenreStorage genreStorage, + @Qualifier("directorDbStorage") DirectorStorage directorStorage, EventService eventService ) { this.filmStorage = filmStorage; this.userStorage = userStorage; this.mpaStorage = mpaStorage; this.genreStorage = genreStorage; + this.directorStorage = directorStorage; this.eventService = eventService; } @@ -55,6 +56,44 @@ public void initMpasAndGenres() { this.genres = genreStorage.getMapOfAllGenres(); } + public Collection getSortedFilmsOfDirector(Integer directorId, String sortBy) { + log.info("Получение фильмов по режиссеру"); + if (sortBy == null) { + return filmStorage.getALLFilmsOfDirector(directorId).stream() + .map(film -> FilmMapper.mapToFilmDto( + film, + mpas.get(film.getMpaId()), + getGenresByIds(film.getGenreIds()), + getDirectorsByIds(film.getDirectorIds()) + ) + ).toList(); + } + + log.trace("Проверка сортировочного запроса"); + FilmSortingAction sortingAction = FilmSortingAction.of(sortBy); + + log.trace("Применение сортировки"); + Comparator comparator = switch (sortingAction) { + case YEAR -> Comparator.comparing(Film::getReleaseDate); + case LIKES -> Comparator.comparing(Film::getLikesCount).reversed(); + case UNDEFINED -> throw new ParameterNotValidException( + sortingAction.getSortingName(), + "не подходит. Допустимые значения: " + + FilmSortingAction.getValidSortingList() + ); + }; + + return filmStorage.getALLFilmsOfDirector(directorId).stream() + .sorted(comparator) + .map(film -> FilmMapper.mapToFilmDto( + film, + mpas.get(film.getMpaId()), + getGenresByIds(film.getGenreIds()), + getDirectorsByIds(film.getDirectorIds()) + ) + ).toList(); + } + public void changeLike(LikeAction action, Long filmId, Long userId) { switch (action) { case SET -> log.info("Добавление лайка пользователя id={} к фильму id={}.", userId, filmId); @@ -101,7 +140,8 @@ public Collection getTopFilms(Integer count, Integer genreId, String ye .map(film -> FilmMapper.mapToFilmDto( film, mpas.get(film.getMpaId()), - getGenresByIds(film.getGenreIds()) + getGenresByIds(film.getGenreIds()), + getDirectorsByIds(film.getDirectorIds()) ) ).sorted(Comparator.comparing(FilmDto::getLikesCount).reversed()).toList(); } @@ -112,7 +152,8 @@ public Collection findAll() { .map(film -> FilmMapper.mapToFilmDto( film, mpas.get(film.getMpaId()), - getGenresByIds(film.getGenreIds()) + getGenresByIds(film.getGenreIds()), + getDirectorsByIds(film.getDirectorIds()) ) ).toList(); } @@ -123,7 +164,8 @@ public FilmDto findById(Long id) { .map(film -> FilmMapper.mapToFilmDto( film, mpas.get(film.getMpaId()), - getGenresByIds(film.getGenreIds()) + getGenresByIds(film.getGenreIds()), + getDirectorsByIds(film.getDirectorIds()) ) ) .orElseThrow(() -> new NotFoundException("Фильм с id=" + id + " не найден.") @@ -136,10 +178,11 @@ public FilmDto create(FilmCreateRequest request) { Film film = FilmMapper.mapToFilm(request); Mpa mpa = validateMpa(film); Set filmGenres = validateGenres(film); + Set filmDirectors = getDirectorsByIds(film.getDirectorIds()); film = filmStorage.createFilm(film); - return FilmMapper.mapToFilmDto(film, mpa, filmGenres); + return FilmMapper.mapToFilmDto(film, mpa, filmGenres, filmDirectors); } public FilmDto update(FilmUpdateRequest request) { @@ -153,9 +196,10 @@ public FilmDto update(FilmUpdateRequest request) { Film newFilm = FilmMapper.updateFilmFields(oldFilm, request); Mpa mpa = validateMpa(newFilm); Set filmGenres = validateGenres(newFilm); + Set filmDirectors = getDirectorsByIds(newFilm.getDirectorIds()); newFilm = filmStorage.updateFilm(newFilm); - return FilmMapper.mapToFilmDto(newFilm, mpa, filmGenres); + return FilmMapper.mapToFilmDto(newFilm, mpa, filmGenres, filmDirectors); } private Mpa validateMpa(Film film) { @@ -187,6 +231,29 @@ private Set getGenresByIds(Set genreIds) { return gnrs; } + private Set getDirectorsByIds(Set directorIds) { + Map allDirectorIds = new HashMap<>(); + directorStorage.findAll() + .forEach(director -> allDirectorIds.put(director.getId(), director)); + + log.trace("Проверка режиссеров"); + List badDirectorIds = directorIds.stream() + .filter(id -> !allDirectorIds.containsKey(id)) + .toList(); + if (!badDirectorIds.isEmpty()) { + throw new NotFoundException("Жанры с id " + badDirectorIds + " не найдены."); + } + + + HashSet dirs = new HashSet<>(); + for (Integer id : directorIds) { + if (allDirectorIds.containsKey(id)) { + dirs.add(allDirectorIds.get(id)); + } + } + return dirs; + } + private void logFilmUpdate(FilmUpdateRequest request, Film film) { List updatedFields = new ArrayList<>(); @@ -210,10 +277,13 @@ private void logFilmUpdate(FilmUpdateRequest request, Film film) { ) { updatedFields.add("жанры"); } + if (request.hasDirectors() && !request.getDirectors().stream() + .map(Director::getId).allMatch(film.getDirectorIds()::contains) + ) { + updatedFields.add("режиссеры"); + } String updatedData = String.join(", ", updatedFields); log.info("Данные для обновления: [{}]", updatedData); } - - } diff --git a/src/main/java/ru/yandex/practicum/filmorate/servise/util/FilmSortingAction.java b/src/main/java/ru/yandex/practicum/filmorate/servise/util/FilmSortingAction.java new file mode 100644 index 0000000..eff7375 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/servise/util/FilmSortingAction.java @@ -0,0 +1,43 @@ +package ru.yandex.practicum.filmorate.servise.util; + +import lombok.Getter; + +import java.util.Arrays; +import java.util.List; + +public enum FilmSortingAction { + YEAR, LIKES, UNDEFINED; + + @Getter + private String sortingName; + + public static FilmSortingAction of(String value) { + FilmSortingAction filterAction; + for (FilmSortingAction ffa : values()) { + if (ffa.name().equalsIgnoreCase(value)) { + return ffa; + } + } + + filterAction = FilmSortingAction.UNDEFINED; + filterAction.setSortingName(value); + return filterAction; + } + + private void setSortingName(String sortingName) { + this.sortingName = sortingName; + } + + public boolean isUndefined() { + return this == UNDEFINED; + } + + public static List getValidSortingList() { + return Arrays.stream(FilmSortingAction.values()) + .filter(f -> !f.equals(FilmSortingAction.UNDEFINED)) + .map(FilmSortingAction::name) + .map(String::toLowerCase) + .toList(); + } + +} diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index acf9457..234a9c0 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -102,10 +102,10 @@ REFERENCES users (id); ALTER TABLE likes ADD CONSTRAINT fk_likes_film_id FOREIGN KEY(film_id) REFERENCES films (id); -ALTER TABLE directors_of_films ADD CONSTRAINT fk_directors_of_films_director_id FOREIGN KEY(director_id) +ALTER TABLE DIRECTORS_OF_FILMS ADD CONSTRAINT fk_directors_of_films_director_id FOREIGN KEY(director_id) REFERENCES directors (id); -ALTER TABLE directors_of_films ADD CONSTRAINT fk_directors_of_films_film_id FOREIGN KEY(film_id) +ALTER TABLE DIRECTORS_OF_FILMS ADD CONSTRAINT fk_directors_of_films_film_id FOREIGN KEY(film_id) REFERENCES films (id); ALTER TABLE reviews ADD CONSTRAINT fk_reviews_user_id FOREIGN KEY (user_id) From 2bb2201261b47e25abde6a20bf5f1f7092616404 Mon Sep 17 00:00:00 2001 From: AleksOBM Date: Thu, 19 Feb 2026 00:20:04 +0300 Subject: [PATCH 22/47] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D1=82=D1=8C=20readme?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3488306..569650a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# java-filmorate (Java Filmorate) +# java-filmorate ### Описание (Description) The Filmorate project — это приложение для работы с базой данных фильмов. ### Основные возможности From a44c77a15a78cc4e20355670304ddc2fa902e4e2 Mon Sep 17 00:00:00 2001 From: AleksOBM Date: Fri, 20 Feb 2026 11:55:12 +0300 Subject: [PATCH 23/47] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20=09@Autowired=20=20=20=20=20=20=20=20=20=20=09@Q?= =?UTF-8?q?ualifier("filmDbStorage")=20=20=20=20=20=20=20=20=20=20=09priva?= =?UTF-8?q?te=20FilmStorage=20filmStorage;=20=D0=92=20UserService?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ru/yandex/practicum/filmorate/servise/UserService.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/ru/yandex/practicum/filmorate/servise/UserService.java b/src/main/java/ru/yandex/practicum/filmorate/servise/UserService.java index ce6f459..0040d60 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/servise/UserService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/servise/UserService.java @@ -34,11 +34,12 @@ public class UserService { private UserStorage userStorage; @Autowired - private EventService eventService; - @Qualifier("filmDbStorage") private FilmStorage filmStorage; + @Autowired + private EventService eventService; + @Autowired private FilmService filmService; From 18df2d1c37e72e37910f4d6165b61020f6dacd34 Mon Sep 17 00:00:00 2001 From: Sergey Lyutov Date: Thu, 19 Feb 2026 23:34:48 +0300 Subject: [PATCH 24/47] =?UTF-8?q?ver=201.0=20=D0=94=D0=BE=D0=B1=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D0=B5=D0=BD=D0=B0=20=D0=B2=D0=BE=D0=B7=D0=BC=D0=BE?= =?UTF-8?q?=D0=B6=D0=BD=D0=BE=D1=81=D1=82=D1=8C=20=D0=BF=D0=BE=D0=BB=D1=83?= =?UTF-8?q?=D1=87=D0=B0=D1=82=D1=8C=20=D0=BE=D0=B1=D1=89=D0=B8=D1=85=20?= =?UTF-8?q?=D1=81=20=D0=B4=D1=80=D1=83=D0=B3=D0=B8=D0=BC=20=D0=BF=D0=BE?= =?UTF-8?q?=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0=D1=82=D0=B5=D0=BB=D0=B5?= =?UTF-8?q?=D0=BC=20=D1=84=D0=B8=D0=BB=D1=8C=D0=BC=D0=BE=D0=B2=20=D1=81=20?= =?UTF-8?q?=D1=81=D0=BE=D1=80=D1=82=D0=B8=D1=80=D0=BE=D0=B2=D0=BA=D0=BE?= =?UTF-8?q?=D0=B9=20=D0=BF=D0=BE=20=D0=B8=D1=85=20=D0=BF=D0=BE=D0=BF=D1=83?= =?UTF-8?q?=D0=BB=D1=8F=D1=80=D0=BD=D0=BE=D1=81=D1=82=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Conflicts: # src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java --- README.md | 1 + .../filmorate/controller/FilmController.java | 6 ++++++ .../practicum/filmorate/dal/FilmStorage.java | 2 ++ .../filmorate/dal/database/FilmDbStorage.java | 5 +++++ .../dal/database/sql/FilmQueryes.java | 17 +++++++++++++++++ .../dal/inmemory/InMemoryFilmStorage.java | 5 +++++ .../filmorate/servise/FilmService.java | 18 ++++++++++++++++++ 7 files changed, 54 insertions(+) diff --git a/README.md b/README.md index 569650a..f47ddf9 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ The Filmorate project — это приложение для работы с б - Возможность пользователям ставить лайки фильмам - Возможность пользователям добавлять друг друга в друзья - Возможность получать рейтинг TOP_N фильмов по количеству лайков +- Возможность получать общих с другим пользователем фильмов с сортировкой по их популярности - Фильмы содержат возрастные ограничения по стандарту MPPA - Реализована система жанров для фильмов ### Требования (Environmental Requirements) 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 916e116..1d78ac4 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java @@ -36,6 +36,12 @@ public Collection getTop(@RequestParam(defaultValue = "10", required = return filmService.getTopFilms(count, genreId, year); } + @GetMapping("/common") + public Collection getCommonLikedFilms(@RequestParam(required = true) Long userId, + @RequestParam(required = true) Long friendId) { + return filmService.getCommonLikedFilms(userId, friendId); + } + @GetMapping("/director/{directorId}") public Collection findSortedFilmsOfDirector( @PathVariable Integer directorId, diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/FilmStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/FilmStorage.java index c0bbf12..271192d 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/FilmStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/FilmStorage.java @@ -41,4 +41,6 @@ public interface FilmStorage { /// Метод для получения матрицы лайков всех пользователей Map> getAllLikes(); + + Collection getCommonLikedFilms(long userId, long friendId); } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/database/FilmDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/database/FilmDbStorage.java index 6fda02e..1828427 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/database/FilmDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/database/FilmDbStorage.java @@ -208,4 +208,9 @@ public Map> getAllLikes() { return allLikes; } + + @Override + public Collection getCommonLikedFilms(long userId, long friendId) { + return findManyFilms(SQL_FILMS_FIND_COMMON_LIKED, userId, friendId); + } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/database/sql/FilmQueryes.java b/src/main/java/ru/yandex/practicum/filmorate/dal/database/sql/FilmQueryes.java index 40d70e4..a95b09d 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/database/sql/FilmQueryes.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/database/sql/FilmQueryes.java @@ -184,4 +184,21 @@ INSERT INTO films ( ORDER BY likes_count DESC LIMIT ? """; + + public static final String SQL_FILMS_FIND_COMMON_LIKED = """ + SELECT f.*, + l_all.user_id, + g.genre_id, + (SELECT COUNT(*) FROM likes WHERE film_id = f.id) as likes_count + FROM films f + LEFT JOIN likes l_all ON f.id = l_all.film_id + LEFT JOIN genres_of_films g ON f.id = g.film_id + WHERE f.id IN ( + SELECT l1.film_id + FROM likes l1 + JOIN likes l2 ON l1.film_id = l2.film_id + WHERE l1.user_id = ? AND l2.user_id = ? + ) + ORDER BY likes_count DESC; + """; } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/inmemory/InMemoryFilmStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/inmemory/InMemoryFilmStorage.java index b1aa8d0..5c8645b 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/inmemory/InMemoryFilmStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/inmemory/InMemoryFilmStorage.java @@ -81,4 +81,9 @@ public void setLike(long filmId, long userId) { public Map> getAllLikes() { return new HashMap<>(); } + + @Override + public Collection getCommonLikedFilms(long userId, long friendId) { + throw new MethodNotImplementedException(); + } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/servise/FilmService.java b/src/main/java/ru/yandex/practicum/filmorate/servise/FilmService.java index cfc0da7..b79e2e9 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/servise/FilmService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/servise/FilmService.java @@ -172,6 +172,24 @@ public FilmDto findById(Long id) { ); } + public Collection getCommonLikedFilms(Long userId, Long friendId) { + log.info("Получение общих с другом фильмов userId={}, friendId={}", userId, friendId); + + if (userStorage.checkUserIsNotPresent(userId)) { + throw new NotFoundException("Пользователь с id=" + userId + " не найден."); + } + if (userStorage.checkUserIsNotPresent(friendId)) { + throw new NotFoundException("Пользователь с id=" + friendId + " не найден."); + } + return filmStorage.getCommonLikedFilms(userId, friendId).stream() + .map(film -> FilmMapper.mapToFilmDto( + film, + mpas.get(film.getMpaId()), + getGenresByIds(film.getGenreIds()) + ) + ).toList(); + } + public FilmDto create(FilmCreateRequest request) { log.info("Добавление нового фильма"); From 5baa9045c623284edfab1753252433d7d1f03602 Mon Sep 17 00:00:00 2001 From: Sergey Lyutov Date: Fri, 20 Feb 2026 18:23:40 +0300 Subject: [PATCH 25/47] =?UTF-8?q?ver=201.1=20=D0=BE=D0=B1=D0=BD=D0=BE?= =?UTF-8?q?=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BF=D0=BE=D1=81=D0=BB?= =?UTF-8?q?=D0=B5=20=D0=B2=D0=BD=D0=B5=D1=81=D0=B5=D0=BD=D0=B8=D1=8F=20?= =?UTF-8?q?=D0=B8=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5=D0=BD=D0=B8=D0=B9=20=D0=B2?= =?UTF-8?q?=20film?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dal/database/sql/FilmQueryes.java | 37 +++++++++++-------- .../filmorate/servise/FilmService.java | 3 +- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/database/sql/FilmQueryes.java b/src/main/java/ru/yandex/practicum/filmorate/dal/database/sql/FilmQueryes.java index a95b09d..b2405d6 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/database/sql/FilmQueryes.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/database/sql/FilmQueryes.java @@ -186,19 +186,26 @@ INSERT INTO films ( """; public static final String SQL_FILMS_FIND_COMMON_LIKED = """ - SELECT f.*, - l_all.user_id, - g.genre_id, - (SELECT COUNT(*) FROM likes WHERE film_id = f.id) as likes_count - FROM films f - LEFT JOIN likes l_all ON f.id = l_all.film_id - LEFT JOIN genres_of_films g ON f.id = g.film_id - WHERE f.id IN ( - SELECT l1.film_id - FROM likes l1 - JOIN likes l2 ON l1.film_id = l2.film_id - WHERE l1.user_id = ? AND l2.user_id = ? - ) - ORDER BY likes_count DESC; - """; + SELECT f.id, + f.film_name, + f.description, + f.release_date, + f.duration, + f.mpa_id, + l_all.user_id, + g.genre_id, + d.director_id, + (SELECT COUNT(*) FROM likes WHERE film_id = f.id) as likes_count + FROM films f + LEFT JOIN likes l_all ON f.id = l_all.film_id + LEFT JOIN genres_of_films g ON f.id = g.film_id + LEFT JOIN directors_of_films d ON f.id = d.film_id + WHERE f.id IN ( + SELECT l1.film_id + FROM likes l1 + JOIN likes l2 ON l1.film_id = l2.film_id + WHERE l1.user_id = ? AND l2.user_id = ? + ) + ORDER BY likes_count DESC; + """; } diff --git a/src/main/java/ru/yandex/practicum/filmorate/servise/FilmService.java b/src/main/java/ru/yandex/practicum/filmorate/servise/FilmService.java index b79e2e9..8c38ccf 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/servise/FilmService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/servise/FilmService.java @@ -185,7 +185,8 @@ public Collection getCommonLikedFilms(Long userId, Long friendId) { .map(film -> FilmMapper.mapToFilmDto( film, mpas.get(film.getMpaId()), - getGenresByIds(film.getGenreIds()) + getGenresByIds(film.getGenreIds()), + getDirectorsByIds(film.getDirectorIds()) ) ).toList(); } From d824d6e33ad955a2f71b598696f5c6987481d720 Mon Sep 17 00:00:00 2001 From: AleksOBM Date: Fri, 20 Feb 2026 19:01:14 +0300 Subject: [PATCH 26/47] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D0=BE=D0=BD?= =?UTF-8?q?=D0=B0=D0=BB=D1=8C=D0=BD=D0=BE=D1=81=D1=82=D1=8C=20=D0=B4=D0=BB?= =?UTF-8?q?=D1=8F=20=D1=83=D0=B4=D0=B0=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F=20?= =?UTF-8?q?=D1=84=D0=B8=D0=BB=D1=8C=D0=BC=D0=B0=20=D0=B8=20=D0=BF=D0=BE?= =?UTF-8?q?=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0=D1=82=D0=B5=D0=BB=D1=8F=20?= =?UTF-8?q?=D0=BF=D0=BE=20=D0=B8=D0=B4=D0=B5=D0=BD=D1=82=D0=B8=D1=84=D0=B8?= =?UTF-8?q?=D0=BA=D0=B0=D1=82=D0=BE=D1=80=D1=83.=20=D0=9E=D0=B1=D0=BD?= =?UTF-8?q?=D0=BE=D0=B2=D0=B8=D1=82=D1=8C=20=D1=81=D1=85=D0=B5=D0=BC=D1=83?= =?UTF-8?q?=20=D0=91=D0=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FILMO_RATE_DATABASE_MAP.png | Bin 0 -> 144879 bytes QuickDBD-FILMO_RATE.png | Bin 71340 -> 0 bytes README.md | 2 +- .../filmorate/controller/FilmController.java | 5 ++++ .../filmorate/controller/UserController.java | 5 ++++ .../practicum/filmorate/dal/FilmStorage.java | 3 ++ .../practicum/filmorate/dal/UserStorage.java | 3 ++ .../filmorate/dal/database/FilmDbStorage.java | 5 ++++ .../filmorate/dal/database/UserDbStorage.java | 5 ++++ .../dal/inmemory/InMemoryFilmStorage.java | 5 ++++ .../dal/inmemory/InMemoryUserStorage.java | 5 ++++ .../filmorate/servise/FilmService.java | 8 ++++++ .../filmorate/servise/UserService.java | 8 ++++++ src/main/resources/schema.sql | 26 +++++++++--------- 14 files changed, 66 insertions(+), 14 deletions(-) create mode 100644 FILMO_RATE_DATABASE_MAP.png delete mode 100644 QuickDBD-FILMO_RATE.png diff --git a/FILMO_RATE_DATABASE_MAP.png b/FILMO_RATE_DATABASE_MAP.png new file mode 100644 index 0000000000000000000000000000000000000000..b50dbc3e607b1fa7c0b3a58cbe670ec33f0fba63 GIT binary patch literal 144879 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 literal 0 HcmV?d00001 diff --git a/QuickDBD-FILMO_RATE.png b/QuickDBD-FILMO_RATE.png deleted file mode 100644 index 34e788576dc84c44bd54d0501d615a58ce60c998..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 71340 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 CjuZI+ diff --git a/README.md b/README.md index f47ddf9..3b62c5a 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ The Filmorate project — это приложение для работы с б - logback-classic - logbook-spring-boot-starter ### Схема базы данных (Database map) -![DatabaseMap](QuickDBD-FILMO_RATE.png "Database map:") +![DatabaseMap](FILMO_RATE_DATABASE_MAP.png "Database map:") ### Примеры запросов к базе данных: 1. Get TOP-10 films: ```sql 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 1d78ac4..d5badc8 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java @@ -70,4 +70,9 @@ public void setLike(@PathVariable long filmId, @PathVariable long userId) { public void deleteLike(@PathVariable long filmId, @PathVariable long userId) { filmService.changeLike(LikeAction.REMOVE, filmId, userId); } + + @DeleteMapping("/{filmId}") + public void deleteFilm(@PathVariable long filmId) { + filmService.remove(filmId); + } } 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 68efccf..cdf8ae3 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java @@ -71,4 +71,9 @@ public Collection getUserEvents(@PathVariable(name = "id") long userId public Collection getRecommendations(@PathVariable long id) { return userService.getRecommendations(id); } + + @DeleteMapping("/{userId}") + public void deleteUser(@PathVariable long userId) { + userService.remove(userId); + } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/FilmStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/FilmStorage.java index 271192d..b5ee1b1 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/FilmStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/FilmStorage.java @@ -43,4 +43,7 @@ public interface FilmStorage { Map> getAllLikes(); Collection getCommonLikedFilms(long userId, long friendId); + + /// Удаление фильма по id + void removeFilm(long filmId); } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/UserStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/UserStorage.java index 368853b..65ff810 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/UserStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/UserStorage.java @@ -36,4 +36,7 @@ public interface UserStorage { /// Получить список id друзей Collection getFriendIds(long userId); + + /// Удаление пользователя по id + void removeUser(long userId); } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/database/FilmDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/database/FilmDbStorage.java index 1828427..30ba174 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/database/FilmDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/database/FilmDbStorage.java @@ -213,4 +213,9 @@ public Map> getAllLikes() { public Collection getCommonLikedFilms(long userId, long friendId) { return findManyFilms(SQL_FILMS_FIND_COMMON_LIKED, userId, friendId); } + + @Override + public void removeFilm(long filmId) { + updateWithControl("DELETE FROM films WHERE id = ?", filmId); + } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/database/UserDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/database/UserDbStorage.java index be9feb6..15a3bad 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/database/UserDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/database/UserDbStorage.java @@ -97,6 +97,11 @@ public Set getFriendIds(long userId) { return findColumnByQuery(sql, Long.class, userId); } + @Override + public void removeUser(long userId) { + updateWithControl("DELETE FROM users WHERE id = ?", userId); + } + @Override public boolean checkUserIsNotPresent(Long userId) { return checkIdIsNotPresentInTable(userId, "users"); diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/inmemory/InMemoryFilmStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/inmemory/InMemoryFilmStorage.java index 5c8645b..a20c49e 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/inmemory/InMemoryFilmStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/inmemory/InMemoryFilmStorage.java @@ -86,4 +86,9 @@ public Map> getAllLikes() { public Collection getCommonLikedFilms(long userId, long friendId) { throw new MethodNotImplementedException(); } + + @Override + public void removeFilm(long filmId) { + throw new MethodNotImplementedException(); + } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/inmemory/InMemoryUserStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/inmemory/InMemoryUserStorage.java index 3c2df8e..d88bb64 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/inmemory/InMemoryUserStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/inmemory/InMemoryUserStorage.java @@ -73,4 +73,9 @@ public Collection getFriendIds(long userId) { throw new MethodNotImplementedException(); } + @Override + public void removeUser(long userId) { + throw new MethodNotImplementedException(); + } + } diff --git a/src/main/java/ru/yandex/practicum/filmorate/servise/FilmService.java b/src/main/java/ru/yandex/practicum/filmorate/servise/FilmService.java index 8c38ccf..fdaf33e 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/servise/FilmService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/servise/FilmService.java @@ -221,6 +221,14 @@ public FilmDto update(FilmUpdateRequest request) { return FilmMapper.mapToFilmDto(newFilm, mpa, filmGenres, filmDirectors); } + public void remove(long filmId) { + log.info("Удаление фильма с id={}", filmId); + if (filmStorage.checkFilmIsNotPresent(filmId)) { + throw new NotFoundException("Фильм с id=" + filmId + " не найден."); + } + filmStorage.removeFilm(filmId); + } + private Mpa validateMpa(Film film) { log.trace("Проверка рейтинга MPAA"); int mpaId = film.getMpaId(); diff --git a/src/main/java/ru/yandex/practicum/filmorate/servise/UserService.java b/src/main/java/ru/yandex/practicum/filmorate/servise/UserService.java index 0040d60..76ec5d5 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/servise/UserService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/servise/UserService.java @@ -118,6 +118,14 @@ public UserDto update(UserUpdateRequest request) { return UserMapper.mapToUserDto(newUser); } + public void remove(long userId) { + log.info("Удаление пользователя с id={}.", userId); + if (userStorage.checkUserIsNotPresent(userId)) { + throw new NotFoundException("Пользователь с id=" + userId + " не найден."); + } + userStorage.removeUser(userId); + } + public UserDto findUserDtoById(long userId) { log.info("Поиск пользователя с id={}.", userId); return UserMapper.mapToUserDto(findUser(userId)); diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index 234a9c0..ac774a1 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -5,7 +5,7 @@ CREATE TABLE users ( user_name varchar(50), login varchar(50) NOT NULL, email varchar(50) NOT NULL, - birthday date NOT NULL + birthday date NOT NULL ); CREATE TABLE friends ( @@ -82,31 +82,31 @@ CREATE TABLE events ( ); ALTER TABLE friends ADD CONSTRAINT fk_friends_user_id FOREIGN KEY(user_id) -REFERENCES users (id); +REFERENCES users (id) ON DELETE CASCADE; ALTER TABLE friends ADD CONSTRAINT fk_friends_friend_id FOREIGN KEY(friend_id) -REFERENCES users (id); +REFERENCES users (id) ON DELETE CASCADE; ALTER TABLE films ADD CONSTRAINT fk_films_mpa_id FOREIGN KEY(mpa_id) -REFERENCES mpa (id); +REFERENCES mpa (id) ON DELETE SET NULL; ALTER TABLE genres_of_films ADD CONSTRAINT fk_genres_of_films_genre_id FOREIGN KEY(genre_id) -REFERENCES genres (id); +REFERENCES genres (id) ON DELETE CASCADE; ALTER TABLE genres_of_films ADD CONSTRAINT fk_genres_of_films_film_id FOREIGN KEY(film_id) -REFERENCES films (id); +REFERENCES films (id) ON DELETE CASCADE; ALTER TABLE likes ADD CONSTRAINT fk_likes_user_id FOREIGN KEY(user_id) -REFERENCES users (id); +REFERENCES users (id) ON DELETE CASCADE; ALTER TABLE likes ADD CONSTRAINT fk_likes_film_id FOREIGN KEY(film_id) -REFERENCES films (id); +REFERENCES films (id) ON DELETE CASCADE; ALTER TABLE DIRECTORS_OF_FILMS ADD CONSTRAINT fk_directors_of_films_director_id FOREIGN KEY(director_id) -REFERENCES directors (id); +REFERENCES directors (id) ON DELETE CASCADE; ALTER TABLE DIRECTORS_OF_FILMS ADD CONSTRAINT fk_directors_of_films_film_id FOREIGN KEY(film_id) -REFERENCES films (id); +REFERENCES films (id) ON DELETE CASCADE; ALTER TABLE reviews ADD CONSTRAINT fk_reviews_user_id FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE; @@ -115,13 +115,13 @@ ALTER TABLE reviews ADD CONSTRAINT fk_reviews_film_id FOREIGN KEY (film_id) REFERENCES films (id) ON DELETE CASCADE; ALTER TABLE review_likes ADD CONSTRAINT fk_review_likes_review_id FOREIGN KEY (review_id) -REFERENCES reviews (id); +REFERENCES reviews (id) ON DELETE CASCADE; ALTER TABLE review_likes ADD CONSTRAINT fk_review_likes_user_id FOREIGN KEY (user_id) -REFERENCES users (id); +REFERENCES users (id) ON DELETE CASCADE; ALTER TABLE events ADD CONSTRAINT fk_events_user_id FOREIGN KEY(user_id) -REFERENCES users (id); +REFERENCES users (id) ON DELETE CASCADE; CREATE INDEX IF NOT EXISTS idx_users_user_name ON users (user_name); CREATE UNIQUE INDEX IF NOT EXISTS idx_users_email ON users (email); From 90d5a7510d272b0d64a9806512a4f71ab67dc57f Mon Sep 17 00:00:00 2001 From: Darya346 Date: Fri, 20 Feb 2026 20:29:58 +0300 Subject: [PATCH 27/47] Add search --- .../filmorate/controller/FilmController.java | 8 +++++++ .../practicum/filmorate/dal/FilmStorage.java | 2 ++ .../filmorate/dal/database/FilmDbStorage.java | 17 +++++++++++++++ .../dal/database/sql/FilmQueryes.java | 21 +++++++++++++++++++ .../dal/inmemory/InMemoryFilmStorage.java | 5 +++++ .../filmorate/servise/FilmService.java | 12 +++++++++++ 6 files changed, 65 insertions(+) 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 1d78ac4..5b19158 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java @@ -70,4 +70,12 @@ public void setLike(@PathVariable long filmId, @PathVariable long userId) { public void deleteLike(@PathVariable long filmId, @PathVariable long userId) { filmService.changeLike(LikeAction.REMOVE, filmId, userId); } + + @GetMapping("/search") + public Collection search( + @RequestParam String query, + @RequestParam String by + ) { + return filmService.search(query, by); + } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/FilmStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/FilmStorage.java index 271192d..8cb64e3 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/FilmStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/FilmStorage.java @@ -43,4 +43,6 @@ public interface FilmStorage { Map> getAllLikes(); Collection getCommonLikedFilms(long userId, long friendId); + + Collection search(String query, String by); } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/database/FilmDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/database/FilmDbStorage.java index 1828427..2105fee 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/database/FilmDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/database/FilmDbStorage.java @@ -213,4 +213,21 @@ public Map> getAllLikes() { public Collection getCommonLikedFilms(long userId, long friendId) { return findManyFilms(SQL_FILMS_FIND_COMMON_LIKED, userId, friendId); } + + @Override + public Collection search(String query, String by) { + String searchPattern = "%" + query.toLowerCase() + "%"; + String condition; + + if (by.contains("director") && by.contains("title")) { + condition = "(LOWER(f.film_name) LIKE ? OR LOWER(d.director_name) LIKE ?)"; + return findManyFilms(String.format(SQL_FILMS_SEARCH, condition), searchPattern, searchPattern); + } else if (by.contains("director")) { + condition = "LOWER(d.director_name) LIKE ?"; + return findManyFilms(String.format(SQL_FILMS_SEARCH, condition), searchPattern); + } else { + condition = "LOWER(f.film_name) LIKE ?"; + return findManyFilms(String.format(SQL_FILMS_SEARCH, condition), searchPattern); + } + } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/database/sql/FilmQueryes.java b/src/main/java/ru/yandex/practicum/filmorate/dal/database/sql/FilmQueryes.java index b2405d6..e1bbc27 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/database/sql/FilmQueryes.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/database/sql/FilmQueryes.java @@ -208,4 +208,25 @@ WHERE f.id IN ( ) ORDER BY likes_count DESC; """; + + public static final String SQL_FILMS_SEARCH = """ + SELECT f.id, + f.film_name, + f.description, + f.release_date, + f.duration, + f.mpa_id, + m.mpa_name, + gof.genre_id, + dof.director_id, + l.user_id + FROM films f + LEFT JOIN mpa m ON f.mpa_id = m.id + LEFT JOIN genres_of_films gof ON f.id = gof.film_id + LEFT JOIN directors_of_films dof ON f.id = dof.film_id + LEFT JOIN directors d ON dof.director_id = d.id + LEFT JOIN likes l ON f.id = l.film_id + WHERE %s + ORDER BY (SELECT COUNT(*) FROM likes WHERE film_id = f.id) DESC, f.id ASC; + """; } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/inmemory/InMemoryFilmStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/inmemory/InMemoryFilmStorage.java index 5c8645b..80f8081 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/inmemory/InMemoryFilmStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/inmemory/InMemoryFilmStorage.java @@ -86,4 +86,9 @@ public Map> getAllLikes() { public Collection getCommonLikedFilms(long userId, long friendId) { throw new MethodNotImplementedException(); } + + @Override + public Collection search(String query, String by) { + throw new ru.yandex.practicum.filmorate.exception.MethodNotImplementedException(); + } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/servise/FilmService.java b/src/main/java/ru/yandex/practicum/filmorate/servise/FilmService.java index 8c38ccf..0364be4 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/servise/FilmService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/servise/FilmService.java @@ -305,4 +305,16 @@ private void logFilmUpdate(FilmUpdateRequest request, Film film) { String updatedData = String.join(", ", updatedFields); log.info("Данные для обновления: [{}]", updatedData); } + + public Collection search(String query, String by) { + log.info("Запрос на поиск фильмов: query='{}', by='{}'", query, by); + return filmStorage.search(query, by).stream() + .map(film -> FilmMapper.mapToFilmDto( + film, + mpas.get(film.getMpaId()), + getGenresByIds(film.getGenreIds()), + getDirectorsByIds(film.getDirectorIds()) + )) + .toList(); + } } From f9dc75021d80bacdfb1b8c2e0754a0c15a8900e3 Mon Sep 17 00:00:00 2001 From: Darya346 Date: Fri, 20 Feb 2026 20:33:48 +0300 Subject: [PATCH 28/47] trigger search tests From fac6ffbaf801ea70a62d4be86b050469cd900410 Mon Sep 17 00:00:00 2001 From: Darya346 Date: Fri, 20 Feb 2026 21:31:11 +0300 Subject: [PATCH 29/47] fix: resolve all checkstyle issues --- .../filmorate/controller/FilmController.java | 9 +- .../practicum/filmorate/dal/FilmStorage.java | 2 +- .../filmorate/dal/database/FilmDbStorage.java | 126 +++++++----------- .../dal/inmemory/InMemoryFilmStorage.java | 4 +- 4 files changed, 50 insertions(+), 91 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 e011df2..dba58e9 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java @@ -70,14 +70,11 @@ public void setLike(@PathVariable long filmId, @PathVariable long userId) { public void deleteLike(@PathVariable long filmId, @PathVariable long userId) { filmService.changeLike(LikeAction.REMOVE, filmId, userId); } - - add-search @GetMapping("/search") - public Collection search( - @RequestParam String query, - @RequestParam String by - ) { + public Collection search(@RequestParam String query, @RequestParam String by) { return filmService.search(query, by); + } + @DeleteMapping("/{filmId}") public void deleteFilm(@PathVariable long filmId) { diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/FilmStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/FilmStorage.java index 337dba5..dab18e0 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/FilmStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/FilmStorage.java @@ -44,7 +44,7 @@ public interface FilmStorage { Collection getCommonLikedFilms(long userId, long friendId); - add-search + Collection search(String query, String by); /// Удаление фильма по id diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/database/FilmDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/database/FilmDbStorage.java index c0e54c9..2575a2b 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/database/FilmDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/database/FilmDbStorage.java @@ -13,8 +13,6 @@ import java.sql.SQLException; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; -import java.util.HashMap; -import java.util.HashSet; import static ru.yandex.practicum.filmorate.dal.database.sql.FilmQueryes.*; @@ -23,10 +21,7 @@ public class FilmDbStorage extends BaseDbStorage implements FilmStorage { @Autowired - public FilmDbStorage( - JdbcTemplate jdbc, - @Qualifier("filmRowMapper") RowMapper mapper - ) { + public FilmDbStorage(JdbcTemplate jdbc, @Qualifier("filmRowMapper") RowMapper mapper) { super(jdbc, mapper); } @@ -41,7 +36,6 @@ public Film createFilm(Film film) { film.getDuration().toMinutes(), film.getMpaId() ); - film.setId(id); insertGenreIds(film); insertDirectorIds(film); @@ -60,7 +54,6 @@ public Film updateFilm(Film film) { film.getMpaId(), film.getId() ); - insertGenreIds(film); insertDirectorIds(film); return film; @@ -72,7 +65,6 @@ public Optional findById(long filmId) { Optional filmOptional = findOneByIdInTable(filmId, "films"); filmOptional.ifPresent(film -> film.setGenreIds(getGenreIdsByFilmId(filmId))); filmOptional.ifPresent(film -> film.setDirectorIds(getDirectorIdsByFilmId(filmId))); - return filmOptional; } @@ -105,7 +97,7 @@ public Collection getTop(int top) { public Collection getTopByFilters(Integer top, Integer genreId, String year) { Collection films; if (genreId != null && year != null && !year.isEmpty()) { - films = findManyByQuery(SQL_FILMS_FIND_TOP_BY_GENRE_AND_YEAR, Integer.parseInt(year), genreId, top); + films = findManyByQuery(SQL_FILMS_FIND_TOP_BY_GENRE_AND_YEAR, Integer.parseInt(year), genreId, top); } else if (year != null && !year.isEmpty()) { films = findManyByQuery(SQL_FILMS_FIND_TOP_BY_YEAR, Integer.parseInt(year), top); } else if (genreId != null) { @@ -113,11 +105,10 @@ public Collection getTopByFilters(Integer top, Integer genreId, String yea } else { films = findManyByQuery(SQL_FILMS_FIND_TOP, top); } - return films.stream().peek(film -> { Set genres = getGenreIdsByFilmId(film.getId()); film.setGenreIds(new HashSet<>(genres)); - }).toList(); + }).toList(); } @Override @@ -125,26 +116,51 @@ public boolean checkFilmIsNotPresent(Long filmId) { return checkIdIsNotPresentInTable(filmId, "films"); } + @Override + public Map> getAllLikes() { + String sql = "SELECT user_id, film_id FROM likes"; + Map> allLikes = new HashMap<>(); + jdbc.query(sql, (rs) -> { + long userId = rs.getLong("user_id"); + long filmId = rs.getLong("film_id"); + allLikes.computeIfAbsent(userId, k -> new HashSet<>()).add(filmId); + }); + return allLikes; + } + + @Override + public Collection getCommonLikedFilms(long userId, long friendId) { + return findManyFilms(SQL_FILMS_FIND_COMMON_LIKED, userId, friendId); + } + + @Override + public Collection search(String query, String by) { + String searchPattern = "%" + query.toLowerCase() + "%"; + String condition; + if (by.contains("director") && by.contains("title")) { + condition = "(LOWER(f.film_name) LIKE ? OR LOWER(d.director_name) LIKE ?)"; + return findManyFilms(String.format(SQL_FILMS_SEARCH, condition), searchPattern, searchPattern); + } else if (by.contains("director")) { + condition = "LOWER(d.director_name) LIKE ?"; + return findManyFilms(String.format(SQL_FILMS_SEARCH, condition), searchPattern); + } else { + condition = "LOWER(f.film_name) LIKE ?"; + return findManyFilms(String.format(SQL_FILMS_SEARCH, condition), searchPattern); + } + } + private void insertGenreIds(Film film) { Set genreIds = film.getGenreIds(); - if (genreIds.isEmpty()) { - return; - } - String placeholders = String.join(",", - Collections.nCopies(genreIds.size(), " (" + film.getId() + ", ?)") - ); + if (genreIds.isEmpty()) return; + String placeholders = String.join(",", Collections.nCopies(genreIds.size(), " (" + film.getId() + ", ?)")); String sql = SQL_FILMS_INSERT_GENREIDS + placeholders; updateWithControl(sql, genreIds.toArray()); } private void insertDirectorIds(Film film) { Set directorIds = film.getDirectorIds(); - if (directorIds.isEmpty()) { - return; - } - String placeholders = String.join(",", - Collections.nCopies(directorIds.size(), " (" + film.getId() + ", ?)") - ); + if (directorIds.isEmpty()) return; + String placeholders = String.join(",", Collections.nCopies(directorIds.size(), " (" + film.getId() + ", ?)")); String sql = SQL_FILMS_INSERT_DIRECTORIDS + placeholders; updateWithControl(sql, directorIds.toArray()); } @@ -156,32 +172,19 @@ private Collection findManyFilms(String query, Object... params) { while (rs.next()) { long filmId = rs.getLong("id"); Film film = films.computeIfAbsent(filmId, id -> { - try { return rowMapper.mapRow(rs, rowNum.getAndIncrement()); } catch (SQLException e) { - throw new InternalServerException( - "Не удалось получить все фильмы из базы.\n" + e.getMessage() - ); + throw new InternalServerException("Не удалось получить все фильмы из базы.\n" + e.getMessage()); } }); - long userId = rs.getLong("user_id"); - if (!rs.wasNull() && film != null) { - film.addLike(userId); - } - + if (!rs.wasNull() && film != null) film.addLike(userId); int genreId = rs.getInt("genre_id"); - if (!rs.wasNull() && film != null) { - film.addGenreId(genreId); - } - + if (!rs.wasNull() && film != null) film.addGenreId(genreId); int directorId = rs.getInt("director_id"); - if (!rs.wasNull() && film != null) { - film.addDirectorId(directorId); - } + if (!rs.wasNull() && film != null) film.addDirectorId(directorId); } - return new ArrayList<>(films.values()); }, params); } @@ -193,47 +196,8 @@ private Set getGenreIdsByFilmId(long filmId) { private Set getDirectorIdsByFilmId(long filmId) { return findColumnByQuery(SQL_FILMS_FIND_DIRECTORIDS_BY_FILM_ID, Integer.class, filmId); } - @Override - public Map> getAllLikes() { - - String sql = "SELECT user_id, film_id FROM likes"; - Map> allLikes = new HashMap<>(); - - jdbc.query(sql, (rs) -> { - long userId = rs.getLong("user_id"); - long filmId = rs.getLong("film_id"); - allLikes.computeIfAbsent(userId, k -> new HashSet<>()).add(filmId); - }); - - return allLikes; - } - - @Override - public Collection getCommonLikedFilms(long userId, long friendId) { - return findManyFilms(SQL_FILMS_FIND_COMMON_LIKED, userId, friendId); - } - - @Override - - add-search - public Collection search(String query, String by) { - String searchPattern = "%" + query.toLowerCase() + "%"; - String condition; - - if (by.contains("director") && by.contains("title")) { - condition = "(LOWER(f.film_name) LIKE ? OR LOWER(d.director_name) LIKE ?)"; - return findManyFilms(String.format(SQL_FILMS_SEARCH, condition), searchPattern, searchPattern); - } else if (by.contains("director")) { - condition = "LOWER(d.director_name) LIKE ?"; - return findManyFilms(String.format(SQL_FILMS_SEARCH, condition), searchPattern); - } else { - condition = "LOWER(f.film_name) LIKE ?"; - return findManyFilms(String.format(SQL_FILMS_SEARCH, condition), searchPattern); - } - public void removeFilm(long filmId) { updateWithControl("DELETE FROM films WHERE id = ?", filmId); - develop } -} +} \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/inmemory/InMemoryFilmStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/inmemory/InMemoryFilmStorage.java index 8a8d530..5c650c7 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/inmemory/InMemoryFilmStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/inmemory/InMemoryFilmStorage.java @@ -88,12 +88,10 @@ public Collection getCommonLikedFilms(long userId, long friendId) { } @Override - add-search public Collection search(String query, String by) { throw new ru.yandex.practicum.filmorate.exception.MethodNotImplementedException(); - + } public void removeFilm(long filmId) { throw new MethodNotImplementedException(); - develop } } From ecaa10aa00c1e50a2b5c5a2bec5823026b58becf Mon Sep 17 00:00:00 2001 From: Darya346 Date: Fri, 20 Feb 2026 21:36:40 +0300 Subject: [PATCH 30/47] fix: fix checkstyle --- .../practicum/filmorate/controller/FilmController.java | 7 +++---- 1 file changed, 3 insertions(+), 4 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 dba58e9..848cd15 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java @@ -38,7 +38,7 @@ public Collection getTop(@RequestParam(defaultValue = "10", required = @GetMapping("/common") public Collection getCommonLikedFilms(@RequestParam(required = true) Long userId, - @RequestParam(required = true) Long friendId) { + @RequestParam(required = true) Long friendId) { return filmService.getCommonLikedFilms(userId, friendId); } @@ -70,15 +70,14 @@ public void setLike(@PathVariable long filmId, @PathVariable long userId) { public void deleteLike(@PathVariable long filmId, @PathVariable long userId) { filmService.changeLike(LikeAction.REMOVE, filmId, userId); } + @GetMapping("/search") public Collection search(@RequestParam String query, @RequestParam String by) { return filmService.search(query, by); } - @DeleteMapping("/{filmId}") public void deleteFilm(@PathVariable long filmId) { filmService.remove(filmId); - } -} +} \ No newline at end of file From 7602f0215f0b44cfe4a8f26f65d367c26bebcf0d Mon Sep 17 00:00:00 2001 From: Darya346 Date: Fri, 20 Feb 2026 21:42:44 +0300 Subject: [PATCH 31/47] fix: resolve checkstyle empty line issues --- .../filmorate/dal/database/FilmDbStorage.java | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/database/FilmDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/database/FilmDbStorage.java index 2575a2b..602782b 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/database/FilmDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/database/FilmDbStorage.java @@ -149,9 +149,16 @@ public Collection search(String query, String by) { } } + @Override + public void removeFilm(long filmId) { + updateWithControl("DELETE FROM films WHERE id = ?", filmId); + } + private void insertGenreIds(Film film) { Set genreIds = film.getGenreIds(); - if (genreIds.isEmpty()) return; + if (genreIds.isEmpty()) { + return; + } String placeholders = String.join(",", Collections.nCopies(genreIds.size(), " (" + film.getId() + ", ?)")); String sql = SQL_FILMS_INSERT_GENREIDS + placeholders; updateWithControl(sql, genreIds.toArray()); @@ -159,7 +166,9 @@ private void insertGenreIds(Film film) { private void insertDirectorIds(Film film) { Set directorIds = film.getDirectorIds(); - if (directorIds.isEmpty()) return; + if (directorIds.isEmpty()) { + return; + } String placeholders = String.join(",", Collections.nCopies(directorIds.size(), " (" + film.getId() + ", ?)")); String sql = SQL_FILMS_INSERT_DIRECTORIDS + placeholders; updateWithControl(sql, directorIds.toArray()); @@ -179,11 +188,17 @@ private Collection findManyFilms(String query, Object... params) { } }); long userId = rs.getLong("user_id"); - if (!rs.wasNull() && film != null) film.addLike(userId); + if (!rs.wasNull() && film != null) { + film.addLike(userId); + } int genreId = rs.getInt("genre_id"); - if (!rs.wasNull() && film != null) film.addGenreId(genreId); + if (!rs.wasNull() && film != null) { + film.addGenreId(genreId); + } int directorId = rs.getInt("director_id"); - if (!rs.wasNull() && film != null) film.addDirectorId(directorId); + if (!rs.wasNull() && film != null) { + film.addDirectorId(directorId); + } } return new ArrayList<>(films.values()); }, params); @@ -196,8 +211,4 @@ private Set getGenreIdsByFilmId(long filmId) { private Set getDirectorIdsByFilmId(long filmId) { return findColumnByQuery(SQL_FILMS_FIND_DIRECTORIDS_BY_FILM_ID, Integer.class, filmId); } - @Override - public void removeFilm(long filmId) { - updateWithControl("DELETE FROM films WHERE id = ?", filmId); - } } \ No newline at end of file From a20a4de4b8ee8426200170f0b39a47e7bcac34a9 Mon Sep 17 00:00:00 2001 From: Darya346 Date: Fri, 20 Feb 2026 21:45:24 +0300 Subject: [PATCH 32/47] fix: resolve checkstyle --- .../practicum/filmorate/dal/inmemory/InMemoryFilmStorage.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/inmemory/InMemoryFilmStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/inmemory/InMemoryFilmStorage.java index 5c650c7..68e984f 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/inmemory/InMemoryFilmStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/inmemory/InMemoryFilmStorage.java @@ -91,6 +91,8 @@ public Collection getCommonLikedFilms(long userId, long friendId) { public Collection search(String query, String by) { throw new ru.yandex.practicum.filmorate.exception.MethodNotImplementedException(); } + + @Override public void removeFilm(long filmId) { throw new MethodNotImplementedException(); } From a9e27e56173b4519c40e65aa770fa7e03d6adff8 Mon Sep 17 00:00:00 2001 From: Darya346 Date: Fri, 20 Feb 2026 21:47:13 +0300 Subject: [PATCH 33/47] checkstyle --- .../dal/inmemory/InMemoryFilmStorage.java | 43 ++++++------------- 1 file changed, 13 insertions(+), 30 deletions(-) diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/inmemory/InMemoryFilmStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/inmemory/InMemoryFilmStorage.java index 68e984f..c72242b 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/inmemory/InMemoryFilmStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/inmemory/InMemoryFilmStorage.java @@ -1,55 +1,38 @@ package ru.yandex.practicum.filmorate.dal.inmemory; -import lombok.RequiredArgsConstructor; -import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; import ru.yandex.practicum.filmorate.dal.FilmStorage; import ru.yandex.practicum.filmorate.exception.MethodNotImplementedException; import ru.yandex.practicum.filmorate.model.Film; -import ru.yandex.practicum.filmorate.servise.util.IdentifyService; -import java.util.Collection; -import java.util.Map; -import java.util.Optional; -import java.util.HashMap; - -import java.util.Set; +import java.util.*; @Component -@Qualifier("inMemoryFilmStorage") -@RequiredArgsConstructor public class InMemoryFilmStorage implements FilmStorage { - private final Map films; - private final IdentifyService identifyService; - @Override public Optional findById(long id) { - return films.values().stream() - .filter(f -> f.getId().equals(id)).findFirst(); + throw new MethodNotImplementedException(); } @Override public Collection findAll() { - return films.values(); + throw new MethodNotImplementedException(); } @Override public Film createFilm(Film film) { - film.setId(identifyService.getNextId(films)); - films.put(film.getId(), film); - return film; + throw new MethodNotImplementedException(); } @Override public Film updateFilm(Film film) { - films.put(film.getId(), film); - return film; + throw new MethodNotImplementedException(); } @Override public boolean checkFilmIsNotPresent(Long filmId) { - return films.containsKey(filmId); + throw new MethodNotImplementedException(); } @Override @@ -63,23 +46,23 @@ public Collection getTopByFilters(Integer count, Integer genreId, String y } @Override - public void removeLike(long filmId, long userId) { + public void setLike(long filmId, long userId) { throw new MethodNotImplementedException(); } @Override - public Collection getALLFilmsOfDirector(Integer directorId) { + public void removeLike(long filmId, long userId) { throw new MethodNotImplementedException(); } @Override - public void setLike(long filmId, long userId) { + public Collection getALLFilmsOfDirector(Integer directorId) { throw new MethodNotImplementedException(); } @Override public Map> getAllLikes() { - return new HashMap<>(); + throw new MethodNotImplementedException(); } @Override @@ -89,11 +72,11 @@ public Collection getCommonLikedFilms(long userId, long friendId) { @Override public Collection search(String query, String by) { - throw new ru.yandex.practicum.filmorate.exception.MethodNotImplementedException(); + throw new MethodNotImplementedException(); } - + @Override public void removeFilm(long filmId) { throw new MethodNotImplementedException(); } -} +} \ No newline at end of file From 22faeb15b1dd122f10793b6bdf7280dd6e772e16 Mon Sep 17 00:00:00 2001 From: Daria Simonenko Date: Fri, 20 Feb 2026 23:40:48 +0300 Subject: [PATCH 34/47] =?UTF-8?q?=D0=92=20=D1=81=D1=83=D1=89=D0=BD=D0=BE?= =?UTF-8?q?=D1=81=D1=82=D0=B8=20Event=20=D0=BF=D0=BE=D0=BB=D0=B5=20id=20?= =?UTF-8?q?=D0=B7=D0=B0=D0=BC=D0=B5=D0=BD=D0=B8=D0=BB=D0=B0=20=D0=BD=D0=B0?= =?UTF-8?q?=20eventId?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yandex/practicum/filmorate/dal/database/EventDbStorage.java | 2 +- .../practicum/filmorate/dal/rowmappers/EventRowMapper.java | 2 +- src/main/java/ru/yandex/practicum/filmorate/dto/EventDto.java | 2 +- .../java/ru/yandex/practicum/filmorate/mapper/EventMapper.java | 2 +- src/main/java/ru/yandex/practicum/filmorate/model/Event.java | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/database/EventDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/database/EventDbStorage.java index 757b550..ca56330 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/database/EventDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/database/EventDbStorage.java @@ -43,7 +43,7 @@ public Event addEvent(Event event) { event.getEventType().name(), event.getOperation().name(), event.getEntityId()); - event.setId(eventId); + event.setEventId(eventId); return event; } } \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/rowmappers/EventRowMapper.java b/src/main/java/ru/yandex/practicum/filmorate/dal/rowmappers/EventRowMapper.java index a339a3b..d22c689 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/rowmappers/EventRowMapper.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/rowmappers/EventRowMapper.java @@ -16,7 +16,7 @@ public class EventRowMapper implements RowMapper { @Override public Event mapRow(ResultSet resultSet, int rowNum) throws SQLException { return Event.builder() - .id(resultSet.getLong("id")) + .eventId(resultSet.getLong("id")) .userId(resultSet.getLong("user_id")) .timestamp(resultSet.getTimestamp("created_at").toInstant()) .eventType(EventType.valueOf(resultSet.getString("event_type"))) diff --git a/src/main/java/ru/yandex/practicum/filmorate/dto/EventDto.java b/src/main/java/ru/yandex/practicum/filmorate/dto/EventDto.java index 25e5021..f6af3d1 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dto/EventDto.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dto/EventDto.java @@ -10,7 +10,7 @@ @Builder public class EventDto { @JsonProperty(access = JsonProperty.Access.READ_ONLY) - private Long id; + private Long eventId; private Long userId; private Long timestamp; private EventType eventType; diff --git a/src/main/java/ru/yandex/practicum/filmorate/mapper/EventMapper.java b/src/main/java/ru/yandex/practicum/filmorate/mapper/EventMapper.java index 9c91c18..b1458ea 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/mapper/EventMapper.java +++ b/src/main/java/ru/yandex/practicum/filmorate/mapper/EventMapper.java @@ -9,7 +9,7 @@ public class EventMapper { public static EventDto mapToEventDto(Event event) { return EventDto.builder() - .id(event.getId()) + .eventId(event.getEventId()) .userId(event.getUserId()) .timestamp(event.getTimestamp().toEpochMilli()) .eventType(event.getEventType()) diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/Event.java b/src/main/java/ru/yandex/practicum/filmorate/model/Event.java index 8064030..0e6b176 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/model/Event.java +++ b/src/main/java/ru/yandex/practicum/filmorate/model/Event.java @@ -12,7 +12,7 @@ @NoArgsConstructor @AllArgsConstructor public class Event { - private Long id; + private Long eventId; private Long userId; private Instant timestamp; private EventType eventType; From 0b9dda8ecae87e307773af8fce58f41c2220dbfb Mon Sep 17 00:00:00 2001 From: Daria Simonenko Date: Sat, 21 Feb 2026 00:08:15 +0300 Subject: [PATCH 35/47] =?UTF-8?q?=D0=92=20=D0=BC=D0=B5=D1=82=D0=BE=D0=B4?= =?UTF-8?q?=20getTopByFilters=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=20=D0=B4=D0=B8=D1=80=D0=B5=D0=BA=D1=82=D0=BE=D1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yandex/practicum/filmorate/dal/database/FilmDbStorage.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/database/FilmDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/database/FilmDbStorage.java index 602782b..b7a55e0 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/database/FilmDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/database/FilmDbStorage.java @@ -108,6 +108,8 @@ public Collection getTopByFilters(Integer top, Integer genreId, String yea return films.stream().peek(film -> { Set genres = getGenreIdsByFilmId(film.getId()); film.setGenreIds(new HashSet<>(genres)); + Set directors = getDirectorIdsByFilmId(film.getId()); + film.setDirectorIds(new HashSet<>(directors)); }).toList(); } From a0361a1fc1335e269f610d51da19e2f180b48398 Mon Sep 17 00:00:00 2001 From: AleksOBM Date: Sat, 21 Feb 2026 12:14:45 +0300 Subject: [PATCH 36/47] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D1=82=D1=8C=20=D0=B2=D0=B0=D0=BB=D0=B8=D0=B4=D0=B0=D1=86?= =?UTF-8?q?=D0=B8=D1=8E=20=D0=B8=D0=BC=D0=B5=D0=BD=D0=B8=20=D0=BF=D0=BE?= =?UTF-8?q?=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0=D1=82=D0=B5=D0=BB=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../filmorate/dto/request/create/UserCreateRequest.java | 2 +- .../filmorate/dto/request/update/UserUpdateRequest.java | 2 +- .../yandex/practicum/filmorate/servise/UserService.java | 8 ++++++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/main/java/ru/yandex/practicum/filmorate/dto/request/create/UserCreateRequest.java b/src/main/java/ru/yandex/practicum/filmorate/dto/request/create/UserCreateRequest.java index d8e2a74..1edda74 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dto/request/create/UserCreateRequest.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dto/request/create/UserCreateRequest.java @@ -8,7 +8,7 @@ @Data public class UserCreateRequest { - @Size(min = 2, max = 50, message = "Длина имени пользователя может быть от 2 до 50 символов.") + @Size(max = 50, message = "Длина имени пользователя не может быть больше 50 символов.") private String name; @NotNull(message = "Логин должен быть указан.") diff --git a/src/main/java/ru/yandex/practicum/filmorate/dto/request/update/UserUpdateRequest.java b/src/main/java/ru/yandex/practicum/filmorate/dto/request/update/UserUpdateRequest.java index 6276e1e..5a211d2 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dto/request/update/UserUpdateRequest.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dto/request/update/UserUpdateRequest.java @@ -12,7 +12,7 @@ public class UserUpdateRequest { @Positive(message = "Id должен быть положительным числом.") private Long id; - @Size(min = 2, max = 50, message = "Длина имени пользователя может быть от 2 до 50 символов.") + @Size(max = 50, message = "Длина имени пользователя не может быть больше 50 символов.") private String name; @Pattern(regexp = "^[A-Za-z0-9_]+$", message = "Логин может состоять только из этих символов [A-Za-z0-9_].") diff --git a/src/main/java/ru/yandex/practicum/filmorate/servise/UserService.java b/src/main/java/ru/yandex/practicum/filmorate/servise/UserService.java index 76ec5d5..c7d7d20 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/servise/UserService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/servise/UserService.java @@ -90,6 +90,10 @@ public void changeFriends(FriendsAction action, long userId, long friendId) { public UserDto create(UserCreateRequest request) { log.info("Добавление нового пользователя login={}.", request.getLogin()); + if (request.getName().isBlank()) { + request.setName(request.getLogin()); + } + User user = UserMapper.mapToUser(request); try { @@ -103,6 +107,10 @@ public UserDto create(UserCreateRequest request) { public UserDto update(UserUpdateRequest request) { log.info("Обновление данных о пользователе с id={}.", request.getId()); + if (request.getName().isBlank()) { + request.setName(request.getLogin()); + } + User oldUser = findUser(request.getId()); logUserUpdate(request, oldUser); User newUser = UserMapper.updateUserFields(oldUser, request); From b61c976c3a1560846c2351fbf6e75f07164919c8 Mon Sep 17 00:00:00 2001 From: AleksOBM Date: Sat, 21 Feb 2026 13:40:47 +0300 Subject: [PATCH 37/47] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D1=82=D1=8C=20=D1=82=D0=B5=D0=BA=D1=81=D1=82=20=D0=BE?= =?UTF-8?q?=D1=88=D0=B8=D0=B1=D0=BA=D0=B8=20=D0=BF=D1=80=D0=B8=20=D0=BE?= =?UTF-8?q?=D1=82=D1=81=D1=83=D1=82=D1=81=D1=82=D0=B2=D0=B8=D0=B8=20=D1=80?= =?UTF-8?q?=D0=B5=D0=B6=D0=B8=D1=81=D1=81=D0=B5=D1=80=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ru/yandex/practicum/filmorate/servise/FilmService.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/ru/yandex/practicum/filmorate/servise/FilmService.java b/src/main/java/ru/yandex/practicum/filmorate/servise/FilmService.java index 7f3c486..c4e91ec 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/servise/FilmService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/servise/FilmService.java @@ -268,10 +268,9 @@ private Set getDirectorsByIds(Set directorIds) { .filter(id -> !allDirectorIds.containsKey(id)) .toList(); if (!badDirectorIds.isEmpty()) { - throw new NotFoundException("Жанры с id " + badDirectorIds + " не найдены."); + throw new NotFoundException("Режиссеры с id " + badDirectorIds + " не найдены."); } - HashSet dirs = new HashSet<>(); for (Integer id : directorIds) { if (allDirectorIds.containsKey(id)) { From fad5d92d354cb75e683d0f9775de41605985670d Mon Sep 17 00:00:00 2001 From: AleksOBM Date: Sat, 21 Feb 2026 15:19:37 +0300 Subject: [PATCH 38/47] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D1=82=D1=8C=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA=D1=83=20?= =?UTF-8?q?=D0=BE=D1=82=D1=81=D0=BA=D1=82=D1=81=D1=82=D0=B2=D0=B8=D1=8F=20?= =?UTF-8?q?=D0=B7=D0=B0=D0=BF=D0=B8=D1=81=D0=B8=20=D0=B2=20=D0=B1=D0=B0?= =?UTF-8?q?=D0=B7=D1=83=20=D0=BF=D1=80=D0=B8=20=D0=BE=D0=B1=D0=BD=D0=BE?= =?UTF-8?q?=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B8=20=D0=B6=D0=B0=D0=BD=D1=80?= =?UTF-8?q?=D0=BE=D0=B2=20=D0=B8=20=D1=80=D0=B5=D0=B6=D0=B8=D1=81=D1=81?= =?UTF-8?q?=D0=B5=D1=80=D0=BE=D0=B2.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../filmorate/dal/database/FilmDbStorage.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/database/FilmDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/database/FilmDbStorage.java index b7a55e0..6e83ce2 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/database/FilmDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/database/FilmDbStorage.java @@ -159,9 +159,13 @@ public void removeFilm(long filmId) { private void insertGenreIds(Film film) { Set genreIds = film.getGenreIds(); if (genreIds.isEmpty()) { + jdbc.update("DELETE FROM genres_of_films WHERE film_id = ?", film.getId()); return; } - String placeholders = String.join(",", Collections.nCopies(genreIds.size(), " (" + film.getId() + ", ?)")); + jdbc.update("DELETE FROM genres_of_films WHERE film_id = ?", film.getId()); + String placeholders = String.join( + ",", Collections.nCopies(genreIds.size(), " (" + film.getId() + ", ?)") + ); String sql = SQL_FILMS_INSERT_GENREIDS + placeholders; updateWithControl(sql, genreIds.toArray()); } @@ -169,9 +173,13 @@ private void insertGenreIds(Film film) { private void insertDirectorIds(Film film) { Set directorIds = film.getDirectorIds(); if (directorIds.isEmpty()) { + jdbc.update("DELETE FROM directors_of_films WHERE film_id = ?", film.getId()); return; } - String placeholders = String.join(",", Collections.nCopies(directorIds.size(), " (" + film.getId() + ", ?)")); + jdbc.update("DELETE FROM directors_of_films WHERE film_id = ?", film.getId()); + String placeholders = String.join( + ",", Collections.nCopies(directorIds.size(), " (" + film.getId() + ", ?)") + ); String sql = SQL_FILMS_INSERT_DIRECTORIDS + placeholders; updateWithControl(sql, directorIds.toArray()); } From ff81dccfc04c10d39a8b35ef5fa940724b7c165e Mon Sep 17 00:00:00 2001 From: AleksOBM Date: Sat, 21 Feb 2026 18:11:17 +0300 Subject: [PATCH 39/47] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D1=82=D1=8C=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA=D1=83=20?= =?UTF-8?q?=D0=BF=D1=80=D0=B8=20=D0=BA=D0=BE=D1=82=D0=BE=D1=80=D0=BE=D0=B9?= =?UTF-8?q?=20=D1=81=D1=82=D0=B0=D1=80=D1=8B=D0=B9=20=D1=80=D0=B5=D0=B6?= =?UTF-8?q?=D0=B8=D1=81=D1=81=D0=B5=D1=80=20=D0=B8=D0=BB=D0=B8=20=D0=B6?= =?UTF-8?q?=D0=B0=D0=BD=D1=80=20=D0=BE=D1=81=D1=82=D0=B0=D0=B5=D1=82=D1=81?= =?UTF-8?q?=D1=8F,=20=D0=B5=D1=81=D0=BB=D0=B8=20=D0=BF=D1=80=D0=B8=20?= =?UTF-8?q?=D0=BE=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B8=20?= =?UTF-8?q?=D0=BD=D0=B5=20=D0=BF=D0=B5=D1=80=D0=B5=D0=B4=D0=B0=D0=B2=D0=B0?= =?UTF-8?q?=D1=82=D1=8C=20=D0=B5=D0=B3=D0=BE=20=D0=B2=20=D0=B7=D0=B0=D0=BF?= =?UTF-8?q?=D1=80=D0=BE=D1=81=D0=B5.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../filmorate/controller/FilmController.java | 1 + .../dto/request/update/FilmUpdateRequest.java | 7 +++-- .../filmorate/mapper/FilmMapper.java | 4 --- .../filmorate/servise/FilmService.java | 27 ++++++++++++++++--- 4 files changed, 27 insertions(+), 12 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 848cd15..3f6fb49 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java @@ -1,6 +1,7 @@ package ru.yandex.practicum.filmorate.controller; import jakarta.validation.Valid; +import jakarta.validation.constraints.Positive; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.*; diff --git a/src/main/java/ru/yandex/practicum/filmorate/dto/request/update/FilmUpdateRequest.java b/src/main/java/ru/yandex/practicum/filmorate/dto/request/update/FilmUpdateRequest.java index 33206b1..8fc4748 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dto/request/update/FilmUpdateRequest.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dto/request/update/FilmUpdateRequest.java @@ -12,6 +12,7 @@ import java.time.Duration; import java.time.LocalDate; +import java.util.HashSet; import java.util.Set; @Data @@ -33,11 +34,9 @@ public class FilmUpdateRequest { @DurationMin(minutes = 1, message = "Продолжительность фильма должна быть положительным числом.") private Duration duration; - @Size(min = 1, message = "Должен быть указан хотябы один жанр.") - private Set genres; + private Set genres = new HashSet<>(); - @Size(min = 1, message = "Должен быть указан хотябы один режиссер.") - private Set directors; + private Set directors = new HashSet<>(); private Mpa mpa; diff --git a/src/main/java/ru/yandex/practicum/filmorate/mapper/FilmMapper.java b/src/main/java/ru/yandex/practicum/filmorate/mapper/FilmMapper.java index f7553ce..65e64a0 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/mapper/FilmMapper.java +++ b/src/main/java/ru/yandex/practicum/filmorate/mapper/FilmMapper.java @@ -79,17 +79,13 @@ public static Film updateFilmFields(Film film, FilmUpdateRequest request) { film.setMpaId(request.getMpa().getId()); } - if (request.hasGenres()) { film.setGenreIds(request.getGenres().stream() .map(Genre::getId) .collect(Collectors.toSet())); - } - if (request.hasDirectors()) { film.setDirectorIds(request.getDirectors().stream() .map(Director::getId) .collect(Collectors.toSet())); - } return film; } diff --git a/src/main/java/ru/yandex/practicum/filmorate/servise/FilmService.java b/src/main/java/ru/yandex/practicum/filmorate/servise/FilmService.java index c4e91ec..28a822e 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/servise/FilmService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/servise/FilmService.java @@ -13,8 +13,8 @@ import ru.yandex.practicum.filmorate.exception.NotFoundException; import ru.yandex.practicum.filmorate.exception.ParameterNotValidException; import ru.yandex.practicum.filmorate.mapper.FilmMapper; -import ru.yandex.practicum.filmorate.servise.util.FilmSortingAction; import ru.yandex.practicum.filmorate.model.*; +import ru.yandex.practicum.filmorate.servise.util.FilmSortingAction; import ru.yandex.practicum.filmorate.servise.util.LikeAction; import java.util.*; @@ -58,8 +58,14 @@ public void initMpasAndGenres() { public Collection getSortedFilmsOfDirector(Integer directorId, String sortBy) { log.info("Получение фильмов по режиссеру"); + if (directorId <= 0) { + throw new ParameterNotValidException(directorId.toString(), "должен быть положительным числом."); + } + + Collection films; + if (sortBy == null) { - return filmStorage.getALLFilmsOfDirector(directorId).stream() + return getSortedFilmDtosOfDirector(directorId).stream() .map(film -> FilmMapper.mapToFilmDto( film, mpas.get(film.getMpaId()), @@ -83,8 +89,10 @@ public Collection getSortedFilmsOfDirector(Integer directorId, String s ); }; - return filmStorage.getALLFilmsOfDirector(directorId).stream() - .sorted(comparator) + films = getSortedFilmDtosOfDirector(directorId).stream() + .sorted(comparator).toList(); + + return films.stream() .map(film -> FilmMapper.mapToFilmDto( film, mpas.get(film.getMpaId()), @@ -94,6 +102,17 @@ public Collection getSortedFilmsOfDirector(Integer directorId, String s ).toList(); } + private Collection getSortedFilmDtosOfDirector(Integer directorId) { + Collection films = filmStorage.getALLFilmsOfDirector(directorId); + + if (films.isEmpty()) { + throw new NotFoundException("Фильмы с этим режиссером" + + " найдены, попробуйте изменить режиссера или параметры сортировки"); + } + + return films; + } + public void changeLike(LikeAction action, Long filmId, Long userId) { switch (action) { case SET -> log.info("Добавление лайка пользователя id={} к фильму id={}.", userId, filmId); From 83d64a1000c4fa354080fe8aa94532a4eb25fb2d Mon Sep 17 00:00:00 2001 From: AleksOBM Date: Sat, 21 Feb 2026 18:13:25 +0300 Subject: [PATCH 40/47] =?UTF-8?q?=D0=A3=D0=B1=D1=80=D0=B0=D1=82=D1=8C=20un?= =?UTF-8?q?used=20import?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ru/yandex/practicum/filmorate/controller/FilmController.java | 1 - 1 file changed, 1 deletion(-) 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 3f6fb49..848cd15 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java @@ -1,7 +1,6 @@ package ru.yandex.practicum.filmorate.controller; import jakarta.validation.Valid; -import jakarta.validation.constraints.Positive; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.*; From 5f221782431f06b798da2cf927ee93f811e79363 Mon Sep 17 00:00:00 2001 From: AleksOBM Date: Sat, 21 Feb 2026 18:27:06 +0300 Subject: [PATCH 41/47] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20=D0=BF=D1=80=D0=BE=D0=B2=D0=B5=D1=80=D0=BA=D1=83?= =?UTF-8?q?=20=D0=B8=D0=BC=D0=B5=D0=BD=D0=B8=20=D0=BF=D0=BE=D0=BB=D1=8C?= =?UTF-8?q?=D0=B7=D0=BE=D0=B2=D0=B0=D1=82=D0=B5=D0=BB=D1=8F=20=D0=BF=D1=80?= =?UTF-8?q?=D0=B8=20=D0=BF=D0=BE=D0=BB=D1=83=D1=87=D0=B5=D0=BD=D0=B8=D0=B8?= =?UTF-8?q?=20=D1=81=D0=BE=D0=B1=D1=8B=D1=82=D0=B8=D1=8F=20=D0=BF=D0=BE?= =?UTF-8?q?=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0=D1=82=D0=B5=D0=BB=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ru/yandex/practicum/filmorate/servise/UserService.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/ru/yandex/practicum/filmorate/servise/UserService.java b/src/main/java/ru/yandex/practicum/filmorate/servise/UserService.java index c7d7d20..b195e6e 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/servise/UserService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/servise/UserService.java @@ -128,9 +128,7 @@ public UserDto update(UserUpdateRequest request) { public void remove(long userId) { log.info("Удаление пользователя с id={}.", userId); - if (userStorage.checkUserIsNotPresent(userId)) { - throw new NotFoundException("Пользователь с id=" + userId + " не найден."); - } + easyCheckUser(userId); userStorage.removeUser(userId); } @@ -166,6 +164,7 @@ public Collection getListOfMutualFriends(long userId, long friendId) { } public Collection getUserEvents(long userId) { + easyCheckUser(userId); return eventService.getUserEvents(userId); } From b53306b806412dc5ca15ec7626d2e3625e2d1acb Mon Sep 17 00:00:00 2001 From: AleksOBM Date: Sat, 21 Feb 2026 22:02:54 +0300 Subject: [PATCH 42/47] =?UTF-8?q?=D0=A0=D0=B5=D1=84=D0=B0=D0=BA=D1=82?= =?UTF-8?q?=D0=BE=D1=80=D0=B8=D0=BD=D0=B3=20=D0=B8=20=D0=BF=D0=BE=D0=B4?= =?UTF-8?q?=D0=B3=D0=BE=D1=82=D0=BE=D0=B2=D0=BA=D0=B0=20=D0=BA=20=D0=BF?= =?UTF-8?q?=D0=B5=D1=80=D0=B2=D0=BE=D0=BC=D1=83=20=D1=80=D0=B5=D0=B2=D1=8C?= =?UTF-8?q?=D1=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 9 +- .../filmorate/FilmorateApplication.java | 6 +- .../controller/DirectorController.java | 4 +- .../filmorate/controller/ErrorHandler.java | 2 +- .../filmorate/controller/FilmController.java | 112 +++++----- .../filmorate/controller/GenreController.java | 4 +- .../filmorate/controller/HomeController.java | 66 +++--- .../filmorate/controller/MpaController.java | 6 +- .../controller/ReviewController.java | 86 ++++---- .../filmorate/controller/UserController.java | 28 +-- .../practicum/filmorate/dal/EventStorage.java | 8 +- .../practicum/filmorate/dal/FilmStorage.java | 5 +- .../practicum/filmorate/dal/GenreStorage.java | 4 + .../practicum/filmorate/dal/MpaStorage.java | 4 + .../filmorate/dal/ReviewStorage.java | 22 +- .../filmorate/dal/database/BaseDbStorage.java | 18 +- .../dal/database/EventDbStorage.java | 64 +++--- .../dal/database/GenreDbStorage.java | 9 +- .../dal/database/ReviewDbStorage.java | 202 +++++++++--------- .../dal/database/sql/FilmQueryes.java | 82 +++---- .../dal/database/sql/ReviewQueries.java | 58 ++--- .../dal/inmemory/InMemoryFilmStorage.java | 5 +- .../dal/inmemory/InMemoryUserStorage.java | 2 +- .../dal/rowmappers/EventRowMapper.java | 22 +- .../dal/rowmappers/ReviewRowMapper.java | 22 +- .../dal/rowmappers/UserRowMapper.java | 22 +- .../practicum/filmorate/dto/EventDto.java | 14 +- .../practicum/filmorate/dto/MpaDto.java | 1 - .../practicum/filmorate/dto/ReviewDto.java | 12 +- .../practicum/filmorate/dto/UserDto.java | 16 +- .../filmorate/dto/adapters/MinDate.java | 7 +- .../dto/adapters/MinDateValidator.java | 5 +- .../request/create/ReviewCreateRequest.java | 16 +- .../dto/request/create/UserCreateRequest.java | 19 +- .../request/update/ReviewUpdateRequest.java | 12 +- .../dto/request/update/UserUpdateRequest.java | 44 ++-- .../filmorate/mapper/EventMapper.java | 20 +- .../filmorate/mapper/FilmMapper.java | 12 +- .../filmorate/mapper/ReviewMapper.java | 54 ++--- .../filmorate/mapper/UserMapper.java | 82 +++---- .../practicum/filmorate/model/Director.java | 2 +- .../filmorate/model/ErrorResponse.java | 2 - .../practicum/filmorate/model/Event.java | 12 +- .../practicum/filmorate/model/EventType.java | 6 +- .../practicum/filmorate/model/Genre.java | 1 - .../practicum/filmorate/model/Operation.java | 6 +- .../practicum/filmorate/model/Review.java | 20 +- .../practicum/filmorate/model/User.java | 29 ++- .../{servise => service}/DirectorService.java | 2 +- .../filmorate/service/EventService.java | 44 ++++ .../{servise => service}/FilmService.java | 6 +- .../{servise => service}/GenreService.java | 2 +- .../MpaService.java} | 10 +- .../filmorate/service/ReviewService.java | 154 +++++++++++++ .../{servise => service}/UserService.java | 16 +- .../util/FilmSortingAction.java | 2 +- .../util/FriendsAction.java | 2 +- .../util/IdentifyService.java | 6 +- .../filmorate/service/util/LikeAction.java | 5 + .../filmorate/servise/EventService.java | 43 ---- .../filmorate/servise/ReviewService.java | 154 ------------- .../filmorate/servise/util/LikeAction.java | 5 - 62 files changed, 868 insertions(+), 847 deletions(-) rename src/main/java/ru/yandex/practicum/filmorate/{servise => service}/DirectorService.java (98%) create mode 100644 src/main/java/ru/yandex/practicum/filmorate/service/EventService.java rename src/main/java/ru/yandex/practicum/filmorate/{servise => service}/FilmService.java (98%) rename src/main/java/ru/yandex/practicum/filmorate/{servise => service}/GenreService.java (95%) rename src/main/java/ru/yandex/practicum/filmorate/{servise/MpaServise.java => service/MpaService.java} (81%) create mode 100644 src/main/java/ru/yandex/practicum/filmorate/service/ReviewService.java rename src/main/java/ru/yandex/practicum/filmorate/{servise => service}/UserService.java (97%) rename src/main/java/ru/yandex/practicum/filmorate/{servise => service}/util/FilmSortingAction.java (94%) rename src/main/java/ru/yandex/practicum/filmorate/{servise => service}/util/FriendsAction.java (55%) rename src/main/java/ru/yandex/practicum/filmorate/{servise => service}/util/IdentifyService.java (77%) create mode 100644 src/main/java/ru/yandex/practicum/filmorate/service/util/LikeAction.java delete mode 100644 src/main/java/ru/yandex/practicum/filmorate/servise/EventService.java delete mode 100644 src/main/java/ru/yandex/practicum/filmorate/servise/ReviewService.java delete mode 100644 src/main/java/ru/yandex/practicum/filmorate/servise/util/LikeAction.java diff --git a/README.md b/README.md index 3b62c5a..2629083 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # java-filmorate -### Описание (Description) -The Filmorate project — это приложение для работы с базой данных фильмов. +The Filmorate project — это приложение для работы с базой данных фильмов. +А также площадка для общения и взаимодействия пользователей. + ### Основные возможности - Хранение данных о фильмах - Хранение данных о пользователях @@ -8,8 +9,12 @@ The Filmorate project — это приложение для работы с б - Возможность пользователям добавлять друг друга в друзья - Возможность получать рейтинг TOP_N фильмов по количеству лайков - Возможность получать общих с другим пользователем фильмов с сортировкой по их популярности +- Возможность заводить друзей +- Возможность оставлять отзывы о фильмах - Фильмы содержат возрастные ограничения по стандарту MPPA +- Реализован поиск по названию и описанию - Реализована система жанров для фильмов +- Есть лента событий ### Требования (Environmental Requirements) Для запуска проекта требуется: - Java версии 21 Приложение работает на порту 8080 diff --git a/src/main/java/ru/yandex/practicum/filmorate/FilmorateApplication.java b/src/main/java/ru/yandex/practicum/filmorate/FilmorateApplication.java index fcd63be..32a2e5f 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/FilmorateApplication.java +++ b/src/main/java/ru/yandex/practicum/filmorate/FilmorateApplication.java @@ -6,7 +6,7 @@ @SpringBootApplication(exclude = {HibernateJpaAutoConfiguration.class}) public class FilmorateApplication { - public static void main(String[] args) { - SpringApplication.run(FilmorateApplication.class, args); - } + public static void main(String[] args) { + SpringApplication.run(FilmorateApplication.class, args); + } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/controller/DirectorController.java b/src/main/java/ru/yandex/practicum/filmorate/controller/DirectorController.java index 3b3575c..8e7ea6b 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/DirectorController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/DirectorController.java @@ -6,12 +6,12 @@ import ru.yandex.practicum.filmorate.dto.DirectorDto; import ru.yandex.practicum.filmorate.dto.request.create.DirectorCreateRequest; import ru.yandex.practicum.filmorate.dto.request.update.DirectorUpdateRequest; -import ru.yandex.practicum.filmorate.servise.DirectorService; +import ru.yandex.practicum.filmorate.service.DirectorService; import java.util.Collection; -@RestController @RequestMapping("/directors") +@RestController @RequiredArgsConstructor public class DirectorController { private final DirectorService directorService; diff --git a/src/main/java/ru/yandex/practicum/filmorate/controller/ErrorHandler.java b/src/main/java/ru/yandex/practicum/filmorate/controller/ErrorHandler.java index ce37374..0a33078 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/ErrorHandler.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/ErrorHandler.java @@ -11,8 +11,8 @@ 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.model.ErrorResponse; import ru.yandex.practicum.filmorate.exception.*; +import ru.yandex.practicum.filmorate.model.ErrorResponse; import java.util.HashMap; import java.util.Map; 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 848cd15..2362097 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java @@ -4,80 +4,80 @@ import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.*; -import ru.yandex.practicum.filmorate.dto.request.create.FilmCreateRequest; import ru.yandex.practicum.filmorate.dto.FilmDto; +import ru.yandex.practicum.filmorate.dto.request.create.FilmCreateRequest; import ru.yandex.practicum.filmorate.dto.request.update.FilmUpdateRequest; -import ru.yandex.practicum.filmorate.servise.FilmService; -import ru.yandex.practicum.filmorate.servise.util.LikeAction; +import ru.yandex.practicum.filmorate.service.FilmService; +import ru.yandex.practicum.filmorate.service.util.LikeAction; import java.util.Collection; +@RequestMapping("/films") @RestController @RequiredArgsConstructor -@RequestMapping("/films") public class FilmController { - private final FilmService filmService; + private final FilmService filmService; - @GetMapping - public Collection findAll() { - return filmService.findAll(); - } + @GetMapping + public Collection findAll() { + return filmService.findAll(); + } - @GetMapping("/{id}") - public FilmDto findById(@PathVariable long id) { - return filmService.findById(id); - } + @GetMapping("/{id}") + public FilmDto findById(@PathVariable long id) { + return filmService.findById(id); + } - @GetMapping("/popular") - public Collection getTop(@RequestParam(defaultValue = "10", required = false) Integer count, - @RequestParam(required = false) Integer genreId, - @RequestParam(required = false) String year) { - return filmService.getTopFilms(count, genreId, year); - } + @GetMapping("/popular") + public Collection getTop(@RequestParam(defaultValue = "10", required = false) Integer count, + @RequestParam(required = false) Integer genreId, + @RequestParam(required = false) String year) { + return filmService.getTopFilms(count, genreId, year); + } - @GetMapping("/common") - public Collection getCommonLikedFilms(@RequestParam(required = true) Long userId, - @RequestParam(required = true) Long friendId) { - return filmService.getCommonLikedFilms(userId, friendId); - } + @GetMapping("/common") + public Collection getCommonLikedFilms(@RequestParam(required = true) Long userId, + @RequestParam(required = true) Long friendId) { + return filmService.getCommonLikedFilms(userId, friendId); + } - @GetMapping("/director/{directorId}") - public Collection findSortedFilmsOfDirector( - @PathVariable Integer directorId, - @RequestParam(required = false) String sortBy - ) { - return filmService.getSortedFilmsOfDirector(directorId, sortBy); - } + @GetMapping("/director/{directorId}") + public Collection findSortedFilmsOfDirector( + @PathVariable Integer directorId, + @RequestParam(required = false) String sortBy + ) { + return filmService.getSortedFilmsOfDirector(directorId, sortBy); + } - @PostMapping - @ResponseStatus(HttpStatus.CREATED) - public FilmDto create(@RequestBody @Valid FilmCreateRequest request) { - return filmService.create(request); - } + @GetMapping("/search") + public Collection search(@RequestParam String query, @RequestParam String by) { + return filmService.search(query, by); + } - @PutMapping - public FilmDto update(@RequestBody FilmUpdateRequest request) { - return filmService.update(request); - } + @ResponseStatus(HttpStatus.CREATED) + @PostMapping + public FilmDto create(@RequestBody @Valid FilmCreateRequest request) { + return filmService.create(request); + } - @PutMapping("/{filmId}/like/{userId}") - public void setLike(@PathVariable long filmId, @PathVariable long userId) { - filmService.changeLike(LikeAction.SET, filmId, userId); - } + @PutMapping + public FilmDto update(@RequestBody FilmUpdateRequest request) { + return filmService.update(request); + } - @DeleteMapping("/{filmId}/like/{userId}") - public void deleteLike(@PathVariable long filmId, @PathVariable long userId) { - filmService.changeLike(LikeAction.REMOVE, filmId, userId); - } + @PutMapping("/{filmId}/like/{userId}") + public void setLike(@PathVariable long filmId, @PathVariable long userId) { + filmService.changeLike(LikeAction.SET, filmId, userId); + } - @GetMapping("/search") - public Collection search(@RequestParam String query, @RequestParam String by) { - return filmService.search(query, by); - } + @DeleteMapping("/{filmId}/like/{userId}") + public void deleteLike(@PathVariable long filmId, @PathVariable long userId) { + filmService.changeLike(LikeAction.REMOVE, filmId, userId); + } - @DeleteMapping("/{filmId}") - public void deleteFilm(@PathVariable long filmId) { - filmService.remove(filmId); - } + @DeleteMapping("/{filmId}") + public void deleteFilm(@PathVariable long filmId) { + filmService.remove(filmId); + } } \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/controller/GenreController.java b/src/main/java/ru/yandex/practicum/filmorate/controller/GenreController.java index aa7a4eb..6c8bf7c 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/GenreController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/GenreController.java @@ -6,12 +6,12 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import ru.yandex.practicum.filmorate.dto.GenreDto; -import ru.yandex.practicum.filmorate.servise.GenreService; +import ru.yandex.practicum.filmorate.service.GenreService; import java.util.Collection; -@RestController @RequestMapping("/genres") +@RestController @RequiredArgsConstructor public class GenreController { private final GenreService genreService; diff --git a/src/main/java/ru/yandex/practicum/filmorate/controller/HomeController.java b/src/main/java/ru/yandex/practicum/filmorate/controller/HomeController.java index 8af1dcf..4e16f33 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/HomeController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/HomeController.java @@ -4,43 +4,43 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -@RestController @RequestMapping("") +@RestController public class HomeController { - @GetMapping - public String homePage() { - return """ -


-
-                    ███████╗██╗██╗     ███╗   ███╗ ██████╗     ██████╗  █████╗ ████████╗███████╗
-                    ██╔════╝██║██║     ████╗ ████║██╔═══██╗    ██╔══██╗██╔══██╗╚══██╔══╝██╔════╝
-                    █████╗  ██║██║     ██╔████╔██║██║   ██║    ██████╔╝███████║   ██║   █████╗
-                    ██╔══╝  ██║██║     ██║╚██╔╝██║██║   ██║    ██╔══██╗██╔══██║   ██║   ██╔══╝
-                    ██║     ██║███████╗██║ ╚═╝ ██║╚██████╔╝    ██║  ██║██║  ██║   ██║   ███████╗
-                    ╚═╝     ╚═╝╚══════╝╚═╝     ╚═╝ ╚═════╝     ╚═╝  ╚═╝╚═╝  ╚═╝   ╚═╝   ╚══════╝
-                
- """; - } + @GetMapping + public String homePage() { + return """ +


+
+				    ███████╗██╗██╗     ███╗   ███╗ ██████╗     ██████╗  █████╗ ████████╗███████╗
+				    ██╔════╝██║██║     ████╗ ████║██╔═══██╗    ██╔══██╗██╔══██╗╚══██╔══╝██╔════╝
+				    █████╗  ██║██║     ██╔████╔██║██║   ██║    ██████╔╝███████║   ██║   █████╗
+				    ██╔══╝  ██║██║     ██║╚██╔╝██║██║   ██║    ██╔══██╗██╔══██║   ██║   ██╔══╝
+				    ██║     ██║███████╗██║ ╚═╝ ██║╚██████╔╝    ██║  ██║██║  ██║   ██║   ███████╗
+				    ╚═╝     ╚═╝╚══════╝╚═╝     ╚═╝ ╚═════╝     ╚═╝  ╚═╝╚═╝  ╚═╝   ╚═╝   ╚══════╝
+				
+ """; + } - @GetMapping("favicon.ico") - public String faviconIco() { - return """ -


- """; - } + @GetMapping("favicon.ico") + public String faviconIco() { + return """ +


+ """; + } - @GetMapping(".well-known/appspecific/com.chrome.devtools.json") - public String devtools() { - return """ -


- """; - } + @GetMapping(".well-known/appspecific/com.chrome.devtools.json") + public String devtools() { + return """ +


+ """; + } - @GetMapping("sw.js") - public String swjs() { - return """ -


- """; - } + @GetMapping("sw.js") + public String swjs() { + return """ +


+ """; + } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/controller/MpaController.java b/src/main/java/ru/yandex/practicum/filmorate/controller/MpaController.java index 19eb0f0..8e4bd37 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/MpaController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/MpaController.java @@ -6,15 +6,15 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import ru.yandex.practicum.filmorate.dto.MpaDto; -import ru.yandex.practicum.filmorate.servise.MpaServise; +import ru.yandex.practicum.filmorate.service.MpaService; import java.util.Collection; -@RestController @RequestMapping("/mpa") +@RestController @RequiredArgsConstructor public class MpaController { - private final MpaServise mpaService; + private final MpaService mpaService; @GetMapping("/{mpaId}") public MpaDto getMpaById(@PathVariable long mpaId) { diff --git a/src/main/java/ru/yandex/practicum/filmorate/controller/ReviewController.java b/src/main/java/ru/yandex/practicum/filmorate/controller/ReviewController.java index b92772a..e14a105 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/ReviewController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/ReviewController.java @@ -7,63 +7,63 @@ import ru.yandex.practicum.filmorate.dto.ReviewDto; import ru.yandex.practicum.filmorate.dto.request.create.ReviewCreateRequest; import ru.yandex.practicum.filmorate.dto.request.update.ReviewUpdateRequest; -import ru.yandex.practicum.filmorate.servise.ReviewService; +import ru.yandex.practicum.filmorate.service.ReviewService; import java.util.Collection; +@RequestMapping("/reviews") @RestController @RequiredArgsConstructor -@RequestMapping("/reviews") public class ReviewController { - private final ReviewService reviewService; + private final ReviewService reviewService; - @PostMapping - @ResponseStatus(HttpStatus.CREATED) - public ReviewDto create(@RequestBody @Valid ReviewCreateRequest request) { - return reviewService.createReview(request); - } + @GetMapping("/{id}") + public ReviewDto findById(@PathVariable Long id) { + return reviewService.findReviewById(id); + } - @PutMapping - public ReviewDto update(@RequestBody @Valid ReviewUpdateRequest request) { - return reviewService.updateReview(request); - } + @GetMapping + public Collection findByFilmId( + @RequestParam(required = false) Long filmId, + @RequestParam(defaultValue = "10") int count + ) { + return reviewService.findReviewsByFilmId(filmId, count); + } - @DeleteMapping("/{id}") - public void delete(@PathVariable Long id) { - reviewService.deleteReview(id); - } + @ResponseStatus(HttpStatus.CREATED) + @PostMapping + public ReviewDto create(@RequestBody @Valid ReviewCreateRequest request) { + return reviewService.createReview(request); + } - @GetMapping("/{id}") - public ReviewDto findById(@PathVariable Long id) { - return reviewService.findReviewById(id); - } + @PutMapping + public ReviewDto update(@RequestBody @Valid ReviewUpdateRequest request) { + return reviewService.updateReview(request); + } - @GetMapping - public Collection findByFilmId( - @RequestParam(required = false) Long filmId, - @RequestParam(defaultValue = "10") int count - ) { - return reviewService.findReviewsByFilmId(filmId, count); - } + @PutMapping("/{id}/like/{userId}") + public void addLike(@PathVariable Long id, @PathVariable Long userId) { + reviewService.addReviewLike(id, userId); + } - @PutMapping("/{id}/like/{userId}") - public void addLike(@PathVariable Long id, @PathVariable Long userId) { - reviewService.addReviewLike(id, userId); - } + @PutMapping("/{id}/dislike/{userId}") + public void addDislike(@PathVariable Long id, @PathVariable Long userId) { + reviewService.addReviewDislike(id, userId); + } - @PutMapping("/{id}/dislike/{userId}") - public void addDislike(@PathVariable Long id, @PathVariable Long userId) { - reviewService.addReviewDislike(id, userId); - } + @DeleteMapping("/{id}") + public void delete(@PathVariable Long id) { + reviewService.deleteReview(id); + } - @DeleteMapping("/{id}/like/{userId}") - public void removeLike(@PathVariable Long id, @PathVariable Long userId) { - reviewService.removeReviewLike(id, userId); - } + @DeleteMapping("/{id}/like/{userId}") + public void removeLike(@PathVariable Long id, @PathVariable Long userId) { + reviewService.removeReviewLike(id, userId); + } - @DeleteMapping("/{id}/dislike/{userId}") - public void removeDislike(@PathVariable Long id, @PathVariable Long userId) { - reviewService.removeReviewDislike(id, userId); - } + @DeleteMapping("/{id}/dislike/{userId}") + public void removeDislike(@PathVariable Long id, @PathVariable Long userId) { + reviewService.removeReviewDislike(id, userId); + } } 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 cdf8ae3..bf7326c 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java @@ -5,12 +5,12 @@ import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.*; import ru.yandex.practicum.filmorate.dto.EventDto; +import ru.yandex.practicum.filmorate.dto.FilmDto; +import ru.yandex.practicum.filmorate.dto.UserDto; import ru.yandex.practicum.filmorate.dto.request.create.UserCreateRequest; import ru.yandex.practicum.filmorate.dto.request.update.UserUpdateRequest; -import ru.yandex.practicum.filmorate.dto.UserDto; -import ru.yandex.practicum.filmorate.servise.util.FriendsAction; -import ru.yandex.practicum.filmorate.servise.UserService; -import ru.yandex.practicum.filmorate.dto.FilmDto; +import ru.yandex.practicum.filmorate.service.UserService; +import ru.yandex.practicum.filmorate.service.util.FriendsAction; import java.util.Collection; @@ -41,6 +41,16 @@ public Collection getMutualFriends(@PathVariable(name = "id") long user return userService.getListOfMutualFriends(userId, otherId); } + @GetMapping("/{id}/feed") + public Collection getUserEvents(@PathVariable(name = "id") long userId) { + return userService.getUserEvents(userId); + } + + @GetMapping("/{id}/recommendations") + public Collection getRecommendations(@PathVariable long id) { + return userService.getRecommendations(id); + } + @PostMapping @ResponseStatus(HttpStatus.CREATED) public UserDto create(@RequestBody @Valid UserCreateRequest userRequest) { @@ -62,16 +72,6 @@ public void removeFromFriends(@PathVariable long userId, @PathVariable long frie userService.changeFriends(FriendsAction.REMOVE, userId, friendId); } - @GetMapping("/{id}/feed") - public Collection getUserEvents(@PathVariable(name = "id") long userId) { - return userService.getUserEvents(userId); - } - - @GetMapping("/{id}/recommendations") - public Collection getRecommendations(@PathVariable long id) { - return userService.getRecommendations(id); - } - @DeleteMapping("/{userId}") public void deleteUser(@PathVariable long userId) { userService.remove(userId); diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/EventStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/EventStorage.java index f7c5d2c..e772f09 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/EventStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/EventStorage.java @@ -6,9 +6,9 @@ public interface EventStorage { - /// Получение всех действий пользователя по его id - Collection getAllEventsByUserId(long userId); + /// Получение всех действий пользователя по его id + Collection getAllEventsByUserId(long userId); - /// Добавление события - Event addEvent(Event event); + /// Добавление события + Event addEvent(Event event); } \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/FilmStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/FilmStorage.java index dab18e0..ab96136 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/FilmStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/FilmStorage.java @@ -3,8 +3,8 @@ import ru.yandex.practicum.filmorate.model.Film; import java.util.Collection; -import java.util.Optional; import java.util.Map; +import java.util.Optional; import java.util.Set; public interface FilmStorage { @@ -42,9 +42,10 @@ public interface FilmStorage { /// Метод для получения матрицы лайков всех пользователей Map> getAllLikes(); + /// Получить список филиьов, которые понравились обоим пользователям Collection getCommonLikedFilms(long userId, long friendId); - + /// Поиск фильмов по названию и описанию Collection search(String query, String by); /// Удаление фильма по id diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/GenreStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/GenreStorage.java index 447b7e4..d54ad49 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/GenreStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/GenreStorage.java @@ -7,9 +7,13 @@ import java.util.Optional; public interface GenreStorage { + + /// Получить жанр по id Optional getGenreById(long genreId); + /// Получить список всех жанров Collection getAllGenres(); + /// Получить матрици всех жанров с их id Map getMapOfAllGenres(); } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/MpaStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/MpaStorage.java index c7a14e4..872f3e0 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/MpaStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/MpaStorage.java @@ -7,9 +7,13 @@ import java.util.Optional; public interface MpaStorage { + + /// Получить МППА рейтинг по id Optional getMpaById(long mpaId); + /// Получить список всех МППА рейтингов Collection getAllMpa(); + /// Получить матрицу МППА рейтингов и их id Map getMapOfAllMpa(); } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/ReviewStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/ReviewStorage.java index 9ba884c..6db0364 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/ReviewStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/ReviewStorage.java @@ -7,26 +7,26 @@ public interface ReviewStorage { - Review createReview(Review review); + Review createReview(Review review); - Review updateReview(Review review); + Review updateReview(Review review); - void deleteReview(long id); + void deleteReview(long id); - Optional findReviewById(long id); + Optional findReviewById(long id); - Collection findReviewsByFilmId(Long filmId, int count); + Collection findReviewsByFilmId(Long filmId, int count); - void addReviewLike(long id, long userId); + void addReviewLike(long id, long userId); - void addReviewDislike(long id, long userId); + void addReviewDislike(long id, long userId); - void removeReviewLike(long id, long userId); + void removeReviewLike(long id, long userId); - void removeReviewDislike(long id, long userId); + void removeReviewDislike(long id, long userId); - boolean checkReviewIsNotPresent(long id); + boolean checkReviewIsNotPresent(long id); - Optional getReactionsStatus(long reviewId, long userId); + Optional getReactionsStatus(long reviewId, long userId); } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/database/BaseDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/database/BaseDbStorage.java index 34550af..209d5cb 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/database/BaseDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/database/BaseDbStorage.java @@ -70,14 +70,14 @@ protected long insertWithKeyHolder(String query, Object... params) { } } - protected void updateWithControl(String query, Object... params) { - int rowsUpdated = jdbc.update(query, params); - if (rowsUpdated == 0) { - throw new InternalServerException("Не удалось обновить данные"); - } - } + protected void updateWithControl(String query, Object... params) { + int rowsUpdated = jdbc.update(query, params); + if (rowsUpdated == 0) { + throw new InternalServerException("Не удалось обновить данные"); + } + } - protected List findManyByQuery(String query, Object... params) { - return jdbc.query(query, rowMapper, params); - } + protected List findManyByQuery(String query, Object... params) { + return jdbc.query(query, rowMapper, params); + } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/database/EventDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/database/EventDbStorage.java index ca56330..c6a01a8 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/database/EventDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/database/EventDbStorage.java @@ -14,36 +14,36 @@ @Repository @Qualifier("eventDbStorage") public class EventDbStorage extends BaseDbStorage implements EventStorage { - private static final String GET_USER_FEEDS = - "SELECT * FROM events " + - "WHERE user_id = ? " + - "ORDER BY created_at;"; - - private static final String ADD_USER_EVENT = - "INSERT INTO events (user_id, created_at, event_type, operation, entity_id) VALUES (?, ?, ?, ?, ?);"; - - @Autowired - public EventDbStorage(JdbcTemplate jdbc, EventRowMapper rowMapper) { - super(jdbc, rowMapper); - } - - @Override - public Collection getAllEventsByUserId(long userId) { - return findManyByQuery(GET_USER_FEEDS, userId); - } - - public Event addEvent(Event event) { - if (event == null) { - throw new NotFoundException("Событие для добавления не найдено"); - } - - long eventId = insertWithKeyHolder(ADD_USER_EVENT, - event.getUserId(), - event.getTimestamp(), - event.getEventType().name(), - event.getOperation().name(), - event.getEntityId()); - event.setEventId(eventId); - return event; - } + private static final String GET_USER_FEEDS = + "SELECT * FROM events " + + "WHERE user_id = ? " + + "ORDER BY created_at;"; + + private static final String ADD_USER_EVENT = + "INSERT INTO events (user_id, created_at, event_type, operation, entity_id) VALUES (?, ?, ?, ?, ?);"; + + @Autowired + public EventDbStorage(JdbcTemplate jdbc, EventRowMapper rowMapper) { + super(jdbc, rowMapper); + } + + @Override + public Collection getAllEventsByUserId(long userId) { + return findManyByQuery(GET_USER_FEEDS, userId); + } + + public Event addEvent(Event event) { + if (event == null) { + throw new NotFoundException("Событие для добавления не найдено"); + } + + long eventId = insertWithKeyHolder(ADD_USER_EVENT, + event.getUserId(), + event.getTimestamp(), + event.getEventType().name(), + event.getOperation().name(), + event.getEntityId()); + event.setEventId(eventId); + return event; + } } \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/database/GenreDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/database/GenreDbStorage.java index 0bda6a2..53838b8 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/database/GenreDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/database/GenreDbStorage.java @@ -8,7 +8,10 @@ import ru.yandex.practicum.filmorate.dal.GenreStorage; import ru.yandex.practicum.filmorate.model.Genre; -import java.util.*; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; @Repository @Qualifier("genreDbStorage") @@ -32,9 +35,9 @@ public Collection getAllGenres() { @Override public Map getMapOfAllGenres() { Map allGenres = new HashMap<>(); - findAllInTable("genres") + findAllInTable("genres") .forEach(genre -> allGenres.put(genre.getId(), genre)); - return allGenres; + return allGenres; } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/database/ReviewDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/database/ReviewDbStorage.java index 10372db..cd09399 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/database/ReviewDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/database/ReviewDbStorage.java @@ -20,114 +20,114 @@ @Qualifier("reviewDbStorage") public class ReviewDbStorage extends BaseDbStorage implements ReviewStorage { - @Autowired - public ReviewDbStorage(JdbcTemplate jdbc, @Qualifier("reviewRowMapper") RowMapper mapper) { - super(jdbc, mapper); - } + @Autowired + public ReviewDbStorage(JdbcTemplate jdbc, @Qualifier("reviewRowMapper") RowMapper mapper) { + super(jdbc, mapper); + } - @Override - @Transactional + @Transactional + @Override public Review createReview(Review review) { - long id = insertWithKeyHolder( - SQL_REVIEWS_INSERT, - review.getContent(), - review.getIsPositive(), - review.getUserId(), - review.getFilmId() - ); - review.setId(id); - return review; - } - - @Override - public Review updateReview(Review review) { - updateWithControl( - SQL_REVIEWS_UPDATE, - review.getContent(), - review.getIsPositive(), - review.getId() - ); - return findReviewById(review.getId()).get(); - } - - @Override - public void deleteReview(long id) { - updateWithControl(SQL_REVIEWS_DELETE, id); - } - - @Override - public Optional findReviewById(long reviewId) { - Optional review = findOneByIdInTable(reviewId, "reviews"); - if (review.isPresent()) { - review.get().setUserLikes(findAllReactionsByReviewId(reviewId)); - return review; - } - return Optional.empty(); - } - - @Override - public Collection findReviewsByFilmId(Long filmId, int count) { - Collection reviews; - if (filmId == null) { - reviews = findManyByQuery(SQL_REVIEWS_FIND_ALL, count); - } else { - reviews = findManyByQuery(SQL_REVIEWS_FIND_BY_FILM_ID, filmId, count); - } - reviews.forEach(review -> { - review.setUserLikes(findAllReactionsByReviewId(review.getId())); - }); - return reviews; - } - - @Override - @Transactional + long id = insertWithKeyHolder( + SQL_REVIEWS_INSERT, + review.getContent(), + review.getIsPositive(), + review.getUserId(), + review.getFilmId() + ); + review.setId(id); + return review; + } + + @Override + public Review updateReview(Review review) { + updateWithControl( + SQL_REVIEWS_UPDATE, + review.getContent(), + review.getIsPositive(), + review.getId() + ); + return findReviewById(review.getId()).get(); + } + + @Override + public void deleteReview(long id) { + updateWithControl(SQL_REVIEWS_DELETE, id); + } + + @Override + public Optional findReviewById(long reviewId) { + Optional review = findOneByIdInTable(reviewId, "reviews"); + if (review.isPresent()) { + review.get().setUserLikes(findAllReactionsByReviewId(reviewId)); + return review; + } + return Optional.empty(); + } + + @Override + public Collection findReviewsByFilmId(Long filmId, int count) { + Collection reviews; + if (filmId == null) { + reviews = findManyByQuery(SQL_REVIEWS_FIND_ALL, count); + } else { + reviews = findManyByQuery(SQL_REVIEWS_FIND_BY_FILM_ID, filmId, count); + } + reviews.forEach(review -> { + review.setUserLikes(findAllReactionsByReviewId(review.getId())); + }); + return reviews; + } + + @Transactional + @Override public void addReviewLike(long reviewId, long userId) { - updateWithControl(SQL_REVIEWS_ADD_LIKE, reviewId, userId); - updateWithControl(SQL_REVIEWS_CALCULATE_USEFUL, reviewId, reviewId); - } + updateWithControl(SQL_REVIEWS_ADD_LIKE, reviewId, userId); + updateWithControl(SQL_REVIEWS_CALCULATE_USEFUL, reviewId, reviewId); + } - @Override - @Transactional + @Transactional + @Override public void addReviewDislike(long reviewId, long userId) { - updateWithControl(SQL_REVIEWS_ADD_DISLIKE, reviewId, userId); - updateWithControl(SQL_REVIEWS_CALCULATE_USEFUL, reviewId, reviewId); - } + updateWithControl(SQL_REVIEWS_ADD_DISLIKE, reviewId, userId); + updateWithControl(SQL_REVIEWS_CALCULATE_USEFUL, reviewId, reviewId); + } - @Override - @Transactional + @Transactional + @Override public void removeReviewLike(long reviewId, long userId) { - updateWithControl(SQL_REVIEWS_DELETE_LIKE, reviewId, userId); - updateWithControl(SQL_REVIEWS_CALCULATE_USEFUL, reviewId, reviewId); - } + updateWithControl(SQL_REVIEWS_DELETE_LIKE, reviewId, userId); + updateWithControl(SQL_REVIEWS_CALCULATE_USEFUL, reviewId, reviewId); + } - @Override - @Transactional + @Transactional + @Override public void removeReviewDislike(long reviewId, long userId) { - updateWithControl(SQL_REVIEWS_DELETE_DISLIKE, reviewId, userId); - updateWithControl(SQL_REVIEWS_CALCULATE_USEFUL, reviewId, reviewId); - } - - @Override - public boolean checkReviewIsNotPresent(long reviewId) { - return checkIdIsNotPresentInTable(reviewId, "reviews"); - } - - public Optional getReactionsStatus(long reviewId, long userId) { - return jdbc.query(SQL_REVIEWS_LIKE_STATUS, (rs, rowNum) -> rs.getBoolean("is_like"), reviewId, userId) - .stream() - .findFirst(); - } - - private Map findAllReactionsByReviewId(long reviewId) { - return jdbc.query(SQL_REVIEWS_GET_LIKES_BY_REVIEW_ID, rs -> { - Map reactions = new HashMap<>(); - while (rs.next()) { - reactions.put( - rs.getLong("user_id"), - rs.getBoolean("is_like") - ); - } - return reactions; - }, reviewId); - } + updateWithControl(SQL_REVIEWS_DELETE_DISLIKE, reviewId, userId); + updateWithControl(SQL_REVIEWS_CALCULATE_USEFUL, reviewId, reviewId); + } + + @Override + public boolean checkReviewIsNotPresent(long reviewId) { + return checkIdIsNotPresentInTable(reviewId, "reviews"); + } + + public Optional getReactionsStatus(long reviewId, long userId) { + return jdbc.query(SQL_REVIEWS_LIKE_STATUS, (rs, rowNum) -> rs.getBoolean("is_like"), reviewId, userId) + .stream() + .findFirst(); + } + + private Map findAllReactionsByReviewId(long reviewId) { + return jdbc.query(SQL_REVIEWS_GET_LIKES_BY_REVIEW_ID, rs -> { + Map reactions = new HashMap<>(); + while (rs.next()) { + reactions.put( + rs.getLong("user_id"), + rs.getBoolean("is_like") + ); + } + return reactions; + }, reviewId); + } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/database/sql/FilmQueryes.java b/src/main/java/ru/yandex/practicum/filmorate/dal/database/sql/FilmQueryes.java index e1bbc27..6b21bf1 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/database/sql/FilmQueryes.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/database/sql/FilmQueryes.java @@ -186,47 +186,47 @@ INSERT INTO films ( """; public static final String SQL_FILMS_FIND_COMMON_LIKED = """ - SELECT f.id, - f.film_name, - f.description, - f.release_date, - f.duration, - f.mpa_id, - l_all.user_id, - g.genre_id, - d.director_id, - (SELECT COUNT(*) FROM likes WHERE film_id = f.id) as likes_count - FROM films f - LEFT JOIN likes l_all ON f.id = l_all.film_id - LEFT JOIN genres_of_films g ON f.id = g.film_id - LEFT JOIN directors_of_films d ON f.id = d.film_id - WHERE f.id IN ( - SELECT l1.film_id - FROM likes l1 - JOIN likes l2 ON l1.film_id = l2.film_id - WHERE l1.user_id = ? AND l2.user_id = ? - ) - ORDER BY likes_count DESC; - """; + SELECT f.id, + f.film_name, + f.description, + f.release_date, + f.duration, + f.mpa_id, + l_all.user_id, + g.genre_id, + d.director_id, + (SELECT COUNT(*) FROM likes WHERE film_id = f.id) as likes_count + FROM films f + LEFT JOIN likes l_all ON f.id = l_all.film_id + LEFT JOIN genres_of_films g ON f.id = g.film_id + LEFT JOIN directors_of_films d ON f.id = d.film_id + WHERE f.id IN ( + SELECT l1.film_id + FROM likes l1 + JOIN likes l2 ON l1.film_id = l2.film_id + WHERE l1.user_id = ? AND l2.user_id = ? + ) + ORDER BY likes_count DESC; + """; public static final String SQL_FILMS_SEARCH = """ - SELECT f.id, - f.film_name, - f.description, - f.release_date, - f.duration, - f.mpa_id, - m.mpa_name, - gof.genre_id, - dof.director_id, - l.user_id - FROM films f - LEFT JOIN mpa m ON f.mpa_id = m.id - LEFT JOIN genres_of_films gof ON f.id = gof.film_id - LEFT JOIN directors_of_films dof ON f.id = dof.film_id - LEFT JOIN directors d ON dof.director_id = d.id - LEFT JOIN likes l ON f.id = l.film_id - WHERE %s - ORDER BY (SELECT COUNT(*) FROM likes WHERE film_id = f.id) DESC, f.id ASC; - """; + SELECT f.id, + f.film_name, + f.description, + f.release_date, + f.duration, + f.mpa_id, + m.mpa_name, + gof.genre_id, + dof.director_id, + l.user_id + FROM films f + LEFT JOIN mpa m ON f.mpa_id = m.id + LEFT JOIN genres_of_films gof ON f.id = gof.film_id + LEFT JOIN directors_of_films dof ON f.id = dof.film_id + LEFT JOIN directors d ON dof.director_id = d.id + LEFT JOIN likes l ON f.id = l.film_id + WHERE %s + ORDER BY (SELECT COUNT(*) FROM likes WHERE film_id = f.id) DESC, f.id ASC; + """; } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/database/sql/ReviewQueries.java b/src/main/java/ru/yandex/practicum/filmorate/dal/database/sql/ReviewQueries.java index 3546403..96b9ed5 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/database/sql/ReviewQueries.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/database/sql/ReviewQueries.java @@ -2,44 +2,44 @@ public class ReviewQueries { - public static final String SQL_REVIEWS_INSERT = - "INSERT INTO reviews (content, is_positive, user_id, film_id) VALUES (?, ?, ?, ?)"; + public static final String SQL_REVIEWS_INSERT = + "INSERT INTO reviews (content, is_positive, user_id, film_id) VALUES (?, ?, ?, ?)"; - public static final String SQL_REVIEWS_UPDATE = - "UPDATE reviews SET content = ?, is_positive = ? WHERE id = ?"; + public static final String SQL_REVIEWS_UPDATE = + "UPDATE reviews SET content = ?, is_positive = ? WHERE id = ?"; - public static final String SQL_REVIEWS_DELETE = - "DELETE FROM reviews WHERE id = ?"; + public static final String SQL_REVIEWS_DELETE = + "DELETE FROM reviews WHERE id = ?"; - public static final String SQL_REVIEWS_FIND_ALL = - "SELECT * FROM reviews ORDER BY useful DESC LIMIT ?"; + public static final String SQL_REVIEWS_FIND_ALL = + "SELECT * FROM reviews ORDER BY useful DESC LIMIT ?"; - public static final String SQL_REVIEWS_FIND_BY_FILM_ID = - "SELECT * FROM reviews WHERE film_id = ? ORDER BY useful DESC LIMIT ?"; + public static final String SQL_REVIEWS_FIND_BY_FILM_ID = + "SELECT * FROM reviews WHERE film_id = ? ORDER BY useful DESC LIMIT ?"; - public static final String SQL_REVIEWS_ADD_LIKE = - "INSERT INTO review_likes (review_id, user_id, is_like) VALUES (?, ?, TRUE)"; + public static final String SQL_REVIEWS_ADD_LIKE = + "INSERT INTO review_likes (review_id, user_id, is_like) VALUES (?, ?, TRUE)"; - public static final String SQL_REVIEWS_ADD_DISLIKE = - "INSERT INTO review_likes (review_id, user_id, is_like) VALUES (?, ?, FALSE)"; + public static final String SQL_REVIEWS_ADD_DISLIKE = + "INSERT INTO review_likes (review_id, user_id, is_like) VALUES (?, ?, FALSE)"; - public static final String SQL_REVIEWS_DELETE_LIKE = - "DELETE FROM review_likes WHERE review_id = ? AND user_id = ? AND is_like = TRUE"; + public static final String SQL_REVIEWS_DELETE_LIKE = + "DELETE FROM review_likes WHERE review_id = ? AND user_id = ? AND is_like = TRUE"; - public static final String SQL_REVIEWS_DELETE_DISLIKE = - "DELETE FROM review_likes WHERE review_id = ? AND user_id = ? AND is_like = FALSE"; + public static final String SQL_REVIEWS_DELETE_DISLIKE = + "DELETE FROM review_likes WHERE review_id = ? AND user_id = ? AND is_like = FALSE"; - public static final String SQL_REVIEWS_LIKE_STATUS = - "SELECT is_like FROM review_likes WHERE review_id = ? AND user_id = ?"; + public static final String SQL_REVIEWS_LIKE_STATUS = + "SELECT is_like FROM review_likes WHERE review_id = ? AND user_id = ?"; - public static final String SQL_REVIEWS_GET_LIKES_BY_REVIEW_ID = - "SELECT user_id, is_like FROM review_likes WHERE review_id = ?"; + public static final String SQL_REVIEWS_GET_LIKES_BY_REVIEW_ID = + "SELECT user_id, is_like FROM review_likes WHERE review_id = ?"; - public static final String SQL_REVIEWS_CALCULATE_USEFUL = """ - UPDATE reviews SET useful = ( - SELECT COALESCE(SUM(CASE WHEN is_like = TRUE THEN 1 ELSE -1 END), 0) - FROM review_likes - WHERE review_id = ? - ) WHERE id = ? - """; + public static final String SQL_REVIEWS_CALCULATE_USEFUL = """ + UPDATE reviews SET useful = ( + SELECT COALESCE(SUM(CASE WHEN is_like = TRUE THEN 1 ELSE -1 END), 0) + FROM review_likes + WHERE review_id = ? + ) WHERE id = ? + """; } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/inmemory/InMemoryFilmStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/inmemory/InMemoryFilmStorage.java index c72242b..39e60e9 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/inmemory/InMemoryFilmStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/inmemory/InMemoryFilmStorage.java @@ -5,7 +5,10 @@ import ru.yandex.practicum.filmorate.exception.MethodNotImplementedException; import ru.yandex.practicum.filmorate.model.Film; -import java.util.*; +import java.util.Collection; +import java.util.Map; +import java.util.Optional; +import java.util.Set; @Component public class InMemoryFilmStorage implements FilmStorage { diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/inmemory/InMemoryUserStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/inmemory/InMemoryUserStorage.java index d88bb64..620be32 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/inmemory/InMemoryUserStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/inmemory/InMemoryUserStorage.java @@ -6,7 +6,7 @@ import ru.yandex.practicum.filmorate.dal.UserStorage; import ru.yandex.practicum.filmorate.exception.MethodNotImplementedException; import ru.yandex.practicum.filmorate.model.User; -import ru.yandex.practicum.filmorate.servise.util.IdentifyService; +import ru.yandex.practicum.filmorate.service.util.IdentifyService; import java.util.Collection; import java.util.Map; diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/rowmappers/EventRowMapper.java b/src/main/java/ru/yandex/practicum/filmorate/dal/rowmappers/EventRowMapper.java index d22c689..679de25 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/rowmappers/EventRowMapper.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/rowmappers/EventRowMapper.java @@ -13,15 +13,15 @@ @Component @Qualifier("eventRowMapper") public class EventRowMapper implements RowMapper { - @Override - public Event mapRow(ResultSet resultSet, int rowNum) throws SQLException { - return Event.builder() - .eventId(resultSet.getLong("id")) - .userId(resultSet.getLong("user_id")) - .timestamp(resultSet.getTimestamp("created_at").toInstant()) - .eventType(EventType.valueOf(resultSet.getString("event_type"))) - .operation(Operation.valueOf(resultSet.getString("operation"))) - .entityId(resultSet.getLong("entity_id")) - .build(); - } + @Override + public Event mapRow(ResultSet resultSet, int rowNum) throws SQLException { + return Event.builder() + .eventId(resultSet.getLong("id")) + .userId(resultSet.getLong("user_id")) + .timestamp(resultSet.getTimestamp("created_at").toInstant()) + .eventType(EventType.valueOf(resultSet.getString("event_type"))) + .operation(Operation.valueOf(resultSet.getString("operation"))) + .entityId(resultSet.getLong("entity_id")) + .build(); + } } \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/rowmappers/ReviewRowMapper.java b/src/main/java/ru/yandex/practicum/filmorate/dal/rowmappers/ReviewRowMapper.java index a63d99c..d7bb879 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/rowmappers/ReviewRowMapper.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/rowmappers/ReviewRowMapper.java @@ -12,15 +12,15 @@ @Qualifier("reviewRowMapper") public class ReviewRowMapper implements RowMapper { - @Override - public Review mapRow(ResultSet resultSet, int rowNum) throws SQLException { - return Review.builder() - .id(resultSet.getLong("id")) - .content(resultSet.getString("content")) - .isPositive(resultSet.getBoolean("is_positive")) - .userId(resultSet.getLong("user_id")) - .filmId(resultSet.getLong("film_id")) - .useful(resultSet.getLong("useful")) - .build(); - } + @Override + public Review mapRow(ResultSet resultSet, int rowNum) throws SQLException { + return Review.builder() + .id(resultSet.getLong("id")) + .content(resultSet.getString("content")) + .isPositive(resultSet.getBoolean("is_positive")) + .userId(resultSet.getLong("user_id")) + .filmId(resultSet.getLong("film_id")) + .useful(resultSet.getLong("useful")) + .build(); + } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/rowmappers/UserRowMapper.java b/src/main/java/ru/yandex/practicum/filmorate/dal/rowmappers/UserRowMapper.java index 3734215..a6e6748 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/rowmappers/UserRowMapper.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/rowmappers/UserRowMapper.java @@ -12,15 +12,15 @@ @Component @Qualifier("userRowMapper") public class UserRowMapper implements RowMapper { - @Override - public User mapRow(ResultSet resultSet, int rowNum) throws SQLException { - return User.builder() - .id(resultSet.getLong("id")) - .name(resultSet.getString("user_name")) - .login(resultSet.getString("login")) - .email(resultSet.getString("email")) - .birthday(resultSet.getDate("birthday").toLocalDate()) - .friendsIds(new HashSet<>()) - .build(); - } + @Override + public User mapRow(ResultSet resultSet, int rowNum) throws SQLException { + return User.builder() + .id(resultSet.getLong("id")) + .name(resultSet.getString("user_name")) + .login(resultSet.getString("login")) + .email(resultSet.getString("email")) + .birthday(resultSet.getDate("birthday").toLocalDate()) + .friendsIds(new HashSet<>()) + .build(); + } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dto/EventDto.java b/src/main/java/ru/yandex/practicum/filmorate/dto/EventDto.java index f6af3d1..6f66634 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dto/EventDto.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dto/EventDto.java @@ -9,11 +9,11 @@ @Data @Builder public class EventDto { - @JsonProperty(access = JsonProperty.Access.READ_ONLY) - private Long eventId; - private Long userId; - private Long timestamp; - private EventType eventType; - private Operation operation; - private Long entityId; + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + private Long eventId; + private Long userId; + private Long timestamp; + private EventType eventType; + private Operation operation; + private Long entityId; } \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/dto/MpaDto.java b/src/main/java/ru/yandex/practicum/filmorate/dto/MpaDto.java index b8475d7..ce1d998 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dto/MpaDto.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dto/MpaDto.java @@ -8,5 +8,4 @@ public class MpaDto extends Mpa { public MpaDto(int id, String name) { super(id, name); } - } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dto/ReviewDto.java b/src/main/java/ru/yandex/practicum/filmorate/dto/ReviewDto.java index f4b4a86..c9013d4 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dto/ReviewDto.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dto/ReviewDto.java @@ -6,10 +6,10 @@ @Data @Builder public class ReviewDto { - private Long reviewId; - private String content; - private Boolean isPositive; - private Long userId; - private Long filmId; - private Long useful; + private Long reviewId; + private String content; + private Boolean isPositive; + private Long userId; + private Long filmId; + private Long useful; } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dto/UserDto.java b/src/main/java/ru/yandex/practicum/filmorate/dto/UserDto.java index cb67197..8bbc7e9 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dto/UserDto.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dto/UserDto.java @@ -11,13 +11,13 @@ @Data @Builder public class UserDto { - @JsonProperty(access = JsonProperty.Access.READ_ONLY) - private long id; - private String name; - private String login; - private String email; - private LocalDate birthday; + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + private long id; + private String name; + private String login; + private String email; + private LocalDate birthday; - @Builder.Default - private Set friends = new HashSet<>(); + @Builder.Default + private Set friends = new HashSet<>(); } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dto/adapters/MinDate.java b/src/main/java/ru/yandex/practicum/filmorate/dto/adapters/MinDate.java index e62f6f5..16b4fba 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dto/adapters/MinDate.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dto/adapters/MinDate.java @@ -2,31 +2,35 @@ import jakarta.validation.Constraint; import jakarta.validation.Payload; + import java.lang.annotation.*; /** * Аннотация MinDate используется для проверки того, что дата не может быть раньше указанной минимальной даты. * Она применяется к полям и параметрам и валидируется с помощью MinDateValidator. */ -@Documented @Constraint(validatedBy = MinDateValidator.class) @Target({ElementType.FIELD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) +@Documented public @interface MinDate { /** * Сообщение, которое будет отображаться в случае, если проверка не пройдена. + * * @return сообщение по умолчанию: "Дата не может быть раньше {minDate}" */ String message() default "Дата не может быть раньше {minDate}"; /** * Группы, к которым относится валидация. + * * @return массив классов групп по умолчанию */ Class[] groups() default {}; /** * Пэйлоады, связанные с валидацией. + * * @return массив классов пейлоадов по умолчанию */ Class[] payload() default {}; @@ -34,6 +38,7 @@ /** * Указывает минимальную дату, с которой будет сравниваться проверяемая дата. * Формат даты должен соответствовать шаблону "yyyy-MM-dd". + * * @return минимальная дата для проверки */ String minDate(); diff --git a/src/main/java/ru/yandex/practicum/filmorate/dto/adapters/MinDateValidator.java b/src/main/java/ru/yandex/practicum/filmorate/dto/adapters/MinDateValidator.java index 08daa27..63f6cb7 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dto/adapters/MinDateValidator.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dto/adapters/MinDateValidator.java @@ -3,6 +3,7 @@ import jakarta.validation.ConstraintValidator; import jakarta.validation.ConstraintValidatorContext; + import java.time.LocalDate; import java.time.format.DateTimeFormatter; @@ -18,6 +19,7 @@ public class MinDateValidator implements ConstraintValidator /** * Инициализирует валидатор минимальным значением даты, заданным в аннотации. + * * @param constraintAnnotation аннотация @MinDate */ @Override @@ -28,7 +30,8 @@ public void initialize(MinDate constraintAnnotation) { /** * Проверяет, что указанная дата не меньше минимальной даты. * Если проверяемая дата равна null, считается, что проверка прошла успешно. - * @param value проверяемая дата + * + * @param value проверяемая дата * @param context контекст валидации * @return true, если дата не меньше минимальной или равна null, иначе false */ diff --git a/src/main/java/ru/yandex/practicum/filmorate/dto/request/create/ReviewCreateRequest.java b/src/main/java/ru/yandex/practicum/filmorate/dto/request/create/ReviewCreateRequest.java index 3ce1a00..23fbd31 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dto/request/create/ReviewCreateRequest.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dto/request/create/ReviewCreateRequest.java @@ -7,16 +7,16 @@ @Data public class ReviewCreateRequest { - @NotBlank(message = "Контент отзыва не может быть пустым.") - @Size(max = 2048, message = "Максимальная длина отзыва — 2048 символов.") + @NotBlank(message = "Контент отзыва не может быть пустым.") + @Size(max = 2048, message = "Максимальная длина отзыва — 2048 символов.") private String content; - @NotNull(message = "Тип отзыва должен быть указан.") - private Boolean isPositive; + @NotNull(message = "Тип отзыва должен быть указан.") + private Boolean isPositive; - @NotNull(message = "userId должен быть указан.") - private Long userId; + @NotNull(message = "userId должен быть указан.") + private Long userId; - @NotNull(message = "filmId должен быть указан.") - private Long filmId; + @NotNull(message = "filmId должен быть указан.") + private Long filmId; } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dto/request/create/UserCreateRequest.java b/src/main/java/ru/yandex/practicum/filmorate/dto/request/create/UserCreateRequest.java index 1edda74..b3b39f9 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dto/request/create/UserCreateRequest.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dto/request/create/UserCreateRequest.java @@ -8,20 +8,19 @@ @Data public class UserCreateRequest { - @Size(max = 50, message = "Длина имени пользователя не может быть больше 50 символов.") - private String name; + @Size(max = 50, message = "Длина имени пользователя не может быть больше 50 символов.") + private String name; - @NotNull(message = "Логин должен быть указан.") - @Pattern(regexp = "^[A-Za-z0-9_]+$", message = "Логин может состоять только из этих символов [A-Za-z0-9_].") - @Size(min = 3, max = 30, message = "Длина логина может быть от 3 до 20 символов.") + @NotNull(message = "Логин должен быть указан.") + @Pattern(regexp = "^[A-Za-z0-9_]+$", message = "Логин может состоять только из этих символов [A-Za-z0-9_].") + @Size(min = 3, max = 30, message = "Длина логина может быть от 3 до 20 символов.") private String login; - @NotNull(message = "Электронная почта должна быть указана.") - @Email(message = "Не распознан формат адреса электронной почты.") + @NotNull(message = "Электронная почта должна быть указана.") + @Email(message = "Не распознан формат адреса электронной почты.") private String email; - @NotNull(message = "Дата рождения должна быть указана.") - @PastOrPresent(message = "Дата рождения не может быть в будущем.") + @NotNull(message = "Дата рождения должна быть указана.") + @PastOrPresent(message = "Дата рождения не может быть в будущем.") private LocalDate birthday; - } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dto/request/update/ReviewUpdateRequest.java b/src/main/java/ru/yandex/practicum/filmorate/dto/request/update/ReviewUpdateRequest.java index 8ad7586..6b8dcc3 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dto/request/update/ReviewUpdateRequest.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dto/request/update/ReviewUpdateRequest.java @@ -6,13 +6,13 @@ @Data public class ReviewUpdateRequest { - @NotNull(message = "reviewId должен быть указан.") - private Long reviewId; + @NotNull(message = "reviewId должен быть указан.") + private Long reviewId; - @NotNull(message = "Контент отзыва не может быть пустым.") - @Size(max = 2048, message = "Максимальная длина отзыва — 2048 символов.") + @NotNull(message = "Контент отзыва не может быть пустым.") + @Size(max = 2048, message = "Максимальная длина отзыва — 2048 символов.") private String content; - @NotNull(message = "Тип отзыва должен быть указан.") - private Boolean isPositive; + @NotNull(message = "Тип отзыва должен быть указан.") + private Boolean isPositive; } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dto/request/update/UserUpdateRequest.java b/src/main/java/ru/yandex/practicum/filmorate/dto/request/update/UserUpdateRequest.java index 5a211d2..9740b9b 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dto/request/update/UserUpdateRequest.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dto/request/update/UserUpdateRequest.java @@ -8,36 +8,36 @@ @Data public class UserUpdateRequest { - @NotNull(message = "Id должен быть указан.") - @Positive(message = "Id должен быть положительным числом.") + @Positive(message = "Id должен быть положительным числом.") + @NotNull(message = "Id должен быть указан.") private Long id; - @Size(max = 50, message = "Длина имени пользователя не может быть больше 50 символов.") - private String name; + @Size(max = 50, message = "Длина имени пользователя не может быть больше 50 символов.") + private String name; - @Pattern(regexp = "^[A-Za-z0-9_]+$", message = "Логин может состоять только из этих символов [A-Za-z0-9_].") - @Size(min = 3, max = 30, message = "Длина логина может быть от 3 до 20 символов.") + @Pattern(regexp = "^[A-Za-z0-9_]+$", message = "Логин может состоять только из этих символов [A-Za-z0-9_].") + @Size(min = 3, max = 30, message = "Длина логина может быть от 3 до 20 символов.") private String login; - @Email(message = "Не распознан формат адреса электронной почты.") - private String email; + @Email(message = "Не распознан формат адреса электронной почты.") + private String email; - @PastOrPresent(message = "Дата рождения не может быть в будущем.") - private LocalDate birthday; + @PastOrPresent(message = "Дата рождения не может быть в будущем.") + private LocalDate birthday; - public boolean hasNname() { - return name != null; - } + public boolean hasNname() { + return name != null; + } - public boolean hasLogin() { - return login != null; - } + public boolean hasLogin() { + return login != null; + } - public boolean hasEmail() { - return email != null; - } + public boolean hasEmail() { + return email != null; + } - public boolean hasBirthday() { - return birthday != null; - } + public boolean hasBirthday() { + return birthday != null; + } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/mapper/EventMapper.java b/src/main/java/ru/yandex/practicum/filmorate/mapper/EventMapper.java index b1458ea..943e78d 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/mapper/EventMapper.java +++ b/src/main/java/ru/yandex/practicum/filmorate/mapper/EventMapper.java @@ -7,14 +7,14 @@ @NoArgsConstructor(access = AccessLevel.PRIVATE) public class EventMapper { - public static EventDto mapToEventDto(Event event) { - return EventDto.builder() - .eventId(event.getEventId()) - .userId(event.getUserId()) - .timestamp(event.getTimestamp().toEpochMilli()) - .eventType(event.getEventType()) - .operation(event.getOperation()) - .entityId(event.getEntityId()) - .build(); - } + public static EventDto mapToEventDto(Event event) { + return EventDto.builder() + .eventId(event.getEventId()) + .userId(event.getUserId()) + .timestamp(event.getTimestamp().toEpochMilli()) + .eventType(event.getEventType()) + .operation(event.getOperation()) + .entityId(event.getEntityId()) + .build(); + } } \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/mapper/FilmMapper.java b/src/main/java/ru/yandex/practicum/filmorate/mapper/FilmMapper.java index 65e64a0..056a560 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/mapper/FilmMapper.java +++ b/src/main/java/ru/yandex/practicum/filmorate/mapper/FilmMapper.java @@ -79,13 +79,13 @@ public static Film updateFilmFields(Film film, FilmUpdateRequest request) { film.setMpaId(request.getMpa().getId()); } - film.setGenreIds(request.getGenres().stream() - .map(Genre::getId) - .collect(Collectors.toSet())); + film.setGenreIds(request.getGenres().stream() + .map(Genre::getId) + .collect(Collectors.toSet())); - film.setDirectorIds(request.getDirectors().stream() - .map(Director::getId) - .collect(Collectors.toSet())); + film.setDirectorIds(request.getDirectors().stream() + .map(Director::getId) + .collect(Collectors.toSet())); return film; } diff --git a/src/main/java/ru/yandex/practicum/filmorate/mapper/ReviewMapper.java b/src/main/java/ru/yandex/practicum/filmorate/mapper/ReviewMapper.java index ee8c163..547c3ef 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/mapper/ReviewMapper.java +++ b/src/main/java/ru/yandex/practicum/filmorate/mapper/ReviewMapper.java @@ -10,33 +10,33 @@ @NoArgsConstructor(access = AccessLevel.PRIVATE) public class ReviewMapper { - public static Review mapToReview(ReviewCreateRequest request) { - return Review.builder() - .content(request.getContent()) - .isPositive(request.getIsPositive()) - .userId(request.getUserId()) - .filmId(request.getFilmId()) - .build(); - } + public static Review mapToReview(ReviewCreateRequest request) { + return Review.builder() + .content(request.getContent()) + .isPositive(request.getIsPositive()) + .userId(request.getUserId()) + .filmId(request.getFilmId()) + .build(); + } - public static Review updateReviewFields(Review review, ReviewUpdateRequest request) { - if (request.getContent() != null) { - review.setContent(request.getContent()); - } - if (request.getIsPositive() != null) { - review.setIsPositive(request.getIsPositive()); - } - return review; - } + public static Review updateReviewFields(Review review, ReviewUpdateRequest request) { + if (request.getContent() != null) { + review.setContent(request.getContent()); + } + if (request.getIsPositive() != null) { + review.setIsPositive(request.getIsPositive()); + } + return review; + } - public static ReviewDto mapToReviewDto(Review review) { - return ReviewDto.builder() - .reviewId(review.getId()) - .content(review.getContent()) - .isPositive(review.getIsPositive()) - .userId(review.getUserId()) - .filmId(review.getFilmId()) - .useful(review.getUseful()) - .build(); - } + public static ReviewDto mapToReviewDto(Review review) { + return ReviewDto.builder() + .reviewId(review.getId()) + .content(review.getContent()) + .isPositive(review.getIsPositive()) + .userId(review.getUserId()) + .filmId(review.getFilmId()) + .useful(review.getUseful()) + .build(); + } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/mapper/UserMapper.java b/src/main/java/ru/yandex/practicum/filmorate/mapper/UserMapper.java index 4bdfb96..78b40a6 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/mapper/UserMapper.java +++ b/src/main/java/ru/yandex/practicum/filmorate/mapper/UserMapper.java @@ -2,51 +2,51 @@ import lombok.AccessLevel; import lombok.NoArgsConstructor; +import ru.yandex.practicum.filmorate.dto.UserDto; import ru.yandex.practicum.filmorate.dto.request.create.UserCreateRequest; import ru.yandex.practicum.filmorate.dto.request.update.UserUpdateRequest; -import ru.yandex.practicum.filmorate.dto.UserDto; import ru.yandex.practicum.filmorate.model.User; @NoArgsConstructor(access = AccessLevel.PRIVATE) public final class UserMapper { - public static User mapToUser(UserCreateRequest request) { - return User.builder() - .name(request.getName() == null ? request.getLogin() : request.getName()) - .login(request.getLogin()) - .email(request.getEmail()) - .birthday(request.getBirthday()) - .build(); - } - - public static UserDto mapToUserDto(User user) { - return UserDto.builder() - .id(user.getId()) - .name(user.getName()) - .login(user.getLogin()) - .email(user.getEmail()) - .birthday(user.getBirthday()) - .friends(user.getFriendsIds()) - .build(); - } - - public static User updateUserFields(User user, UserUpdateRequest request) { - - if (request.hasNname()) { - user.setName(request.getName()); - } - - if (request.hasLogin()) { - user.setLogin(request.getLogin()); - } - - if (request.hasEmail()) { - user.setEmail(request.getEmail()); - } - - if (request.hasBirthday()) { - user.setBirthday(request.getBirthday()); - } - - return user; - } + public static User mapToUser(UserCreateRequest request) { + return User.builder() + .name(request.getName() == null ? request.getLogin() : request.getName()) + .login(request.getLogin()) + .email(request.getEmail()) + .birthday(request.getBirthday()) + .build(); + } + + public static UserDto mapToUserDto(User user) { + return UserDto.builder() + .id(user.getId()) + .name(user.getName()) + .login(user.getLogin()) + .email(user.getEmail()) + .birthday(user.getBirthday()) + .friends(user.getFriendsIds()) + .build(); + } + + public static User updateUserFields(User user, UserUpdateRequest request) { + + if (request.hasNname()) { + user.setName(request.getName()); + } + + if (request.hasLogin()) { + user.setLogin(request.getLogin()); + } + + if (request.hasEmail()) { + user.setEmail(request.getEmail()); + } + + if (request.hasBirthday()) { + user.setBirthday(request.getBirthday()); + } + + return user; + } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/Director.java b/src/main/java/ru/yandex/practicum/filmorate/model/Director.java index a646372..b3b8659 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/model/Director.java +++ b/src/main/java/ru/yandex/practicum/filmorate/model/Director.java @@ -9,9 +9,9 @@ @Data @Builder(toBuilder = true) -@Jacksonized @EqualsAndHashCode(of = "id") @FieldDefaults(level = AccessLevel.PRIVATE) +@Jacksonized public class Director { Integer id; String name; diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/ErrorResponse.java b/src/main/java/ru/yandex/practicum/filmorate/model/ErrorResponse.java index 4145733..a6b0acb 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/model/ErrorResponse.java +++ b/src/main/java/ru/yandex/practicum/filmorate/model/ErrorResponse.java @@ -3,12 +3,10 @@ import lombok.Getter; public class ErrorResponse { - @Getter private final String error; public ErrorResponse(String error) { this.error = error; } - } diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/Event.java b/src/main/java/ru/yandex/practicum/filmorate/model/Event.java index 0e6b176..a033a30 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/model/Event.java +++ b/src/main/java/ru/yandex/practicum/filmorate/model/Event.java @@ -12,10 +12,10 @@ @NoArgsConstructor @AllArgsConstructor public class Event { - private Long eventId; - private Long userId; - private Instant timestamp; - private EventType eventType; - private Operation operation; - private Long entityId; + private Long eventId; + private Long userId; + private Instant timestamp; + private EventType eventType; + private Operation operation; + private Long entityId; } \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/EventType.java b/src/main/java/ru/yandex/practicum/filmorate/model/EventType.java index f1e4056..e6cfb0b 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/model/EventType.java +++ b/src/main/java/ru/yandex/practicum/filmorate/model/EventType.java @@ -1,7 +1,7 @@ package ru.yandex.practicum.filmorate.model; public enum EventType { - LIKE, - REVIEW, - FRIEND + LIKE, + REVIEW, + FRIEND } \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/Genre.java b/src/main/java/ru/yandex/practicum/filmorate/model/Genre.java index 2c953ef..3f89b27 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/model/Genre.java +++ b/src/main/java/ru/yandex/practicum/filmorate/model/Genre.java @@ -10,5 +10,4 @@ public class Genre { private int id; private String name; - } diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/Operation.java b/src/main/java/ru/yandex/practicum/filmorate/model/Operation.java index bc5fff4..c0bc9bc 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/model/Operation.java +++ b/src/main/java/ru/yandex/practicum/filmorate/model/Operation.java @@ -1,7 +1,7 @@ package ru.yandex.practicum.filmorate.model; public enum Operation { - REMOVE, - ADD, - UPDATE + REMOVE, + ADD, + UPDATE } \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/Review.java b/src/main/java/ru/yandex/practicum/filmorate/model/Review.java index bcdbbd9..b32bff9 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/model/Review.java +++ b/src/main/java/ru/yandex/practicum/filmorate/model/Review.java @@ -10,19 +10,19 @@ @Data @Builder(toBuilder = true) -@Jacksonized @EqualsAndHashCode(of = "id") +@Jacksonized public class Review { - private Long id; - private String content; - private Boolean isPositive; - private Long userId; - private Long filmId; + private Long id; + private String content; + private Boolean isPositive; + private Long userId; + private Long filmId; - @Builder.Default - private Long useful = 0L; + @Builder.Default + private Long useful = 0L; - @Builder.Default - private Map userLikes = new HashMap<>(); + @Builder.Default + private Map userLikes = new HashMap<>(); } 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 6ac051b..414acce 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/model/User.java +++ b/src/main/java/ru/yandex/practicum/filmorate/model/User.java @@ -11,25 +11,24 @@ @Data @Builder(toBuilder = true) -@Jacksonized @EqualsAndHashCode(of = "id") +@Jacksonized public class User { - private Long id; - private String name; - private String login; - private String email; - private LocalDate birthday; - - @Builder.Default - private Set friendsIds = new HashSet<>(); + private Long id; + private String name; + private String login; + private String email; + private LocalDate birthday; - public void addFriendId(Long id) { - friendsIds.add(id); - } + @Builder.Default + private Set friendsIds = new HashSet<>(); - public void removeFriends(Long id) { - friendsIds.remove(id); - } + public void addFriendId(Long id) { + friendsIds.add(id); + } + public void removeFriends(Long id) { + friendsIds.remove(id); + } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/servise/DirectorService.java b/src/main/java/ru/yandex/practicum/filmorate/service/DirectorService.java similarity index 98% rename from src/main/java/ru/yandex/practicum/filmorate/servise/DirectorService.java rename to src/main/java/ru/yandex/practicum/filmorate/service/DirectorService.java index 2e1dc22..c2404b8 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/servise/DirectorService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/DirectorService.java @@ -1,4 +1,4 @@ -package ru.yandex.practicum.filmorate.servise; +package ru.yandex.practicum.filmorate.service; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/EventService.java b/src/main/java/ru/yandex/practicum/filmorate/service/EventService.java new file mode 100644 index 0000000..b50e86b --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/service/EventService.java @@ -0,0 +1,44 @@ +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.dal.EventStorage; +import ru.yandex.practicum.filmorate.dto.EventDto; +import ru.yandex.practicum.filmorate.mapper.EventMapper; +import ru.yandex.practicum.filmorate.model.Event; +import ru.yandex.practicum.filmorate.model.EventType; +import ru.yandex.practicum.filmorate.model.Operation; + +import java.time.Instant; +import java.util.Collection; +import java.util.stream.Collectors; + +@Service +@Slf4j +public class EventService { + + @Qualifier("eventDbStorage") + @Autowired + private EventStorage eventStorage; + + public Event addUserEvent(long userId, EventType eventType, Operation operation, long entityId) { + Event event = Event.builder() + .userId(userId) + .timestamp(Instant.now()) + .eventType(eventType) + .operation(operation) + .entityId(entityId) + .build(); + return eventStorage.addEvent(event); + } + + public Collection getUserEvents(long userId) { + log.info("Поиск всех событий пользователя."); + return eventStorage.getAllEventsByUserId(userId) + .stream() + .map(EventMapper::mapToEventDto) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/servise/FilmService.java b/src/main/java/ru/yandex/practicum/filmorate/service/FilmService.java similarity index 98% rename from src/main/java/ru/yandex/practicum/filmorate/servise/FilmService.java rename to src/main/java/ru/yandex/practicum/filmorate/service/FilmService.java index 28a822e..47e9483 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/servise/FilmService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/FilmService.java @@ -1,4 +1,4 @@ -package ru.yandex.practicum.filmorate.servise; +package ru.yandex.practicum.filmorate.service; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -14,8 +14,8 @@ import ru.yandex.practicum.filmorate.exception.ParameterNotValidException; import ru.yandex.practicum.filmorate.mapper.FilmMapper; import ru.yandex.practicum.filmorate.model.*; -import ru.yandex.practicum.filmorate.servise.util.FilmSortingAction; -import ru.yandex.practicum.filmorate.servise.util.LikeAction; +import ru.yandex.practicum.filmorate.service.util.FilmSortingAction; +import ru.yandex.practicum.filmorate.service.util.LikeAction; import java.util.*; diff --git a/src/main/java/ru/yandex/practicum/filmorate/servise/GenreService.java b/src/main/java/ru/yandex/practicum/filmorate/service/GenreService.java similarity index 95% rename from src/main/java/ru/yandex/practicum/filmorate/servise/GenreService.java rename to src/main/java/ru/yandex/practicum/filmorate/service/GenreService.java index 590a277..20c2973 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/servise/GenreService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/GenreService.java @@ -1,4 +1,4 @@ -package ru.yandex.practicum.filmorate.servise; +package ru.yandex.practicum.filmorate.service; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; diff --git a/src/main/java/ru/yandex/practicum/filmorate/servise/MpaServise.java b/src/main/java/ru/yandex/practicum/filmorate/service/MpaService.java similarity index 81% rename from src/main/java/ru/yandex/practicum/filmorate/servise/MpaServise.java rename to src/main/java/ru/yandex/practicum/filmorate/service/MpaService.java index 00b231d..60b7605 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/servise/MpaServise.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/MpaService.java @@ -1,4 +1,4 @@ -package ru.yandex.practicum.filmorate.servise; +package ru.yandex.practicum.filmorate.service; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -9,24 +9,24 @@ import java.util.Collection; -@Slf4j @Service @RequiredArgsConstructor -public class MpaServise { +@Slf4j +public class MpaService { private final MpaStorage mpaStorage; public MpaDto getMpaDtoById(long mpaId) { log.info("Получение рейтинга MPAA с id={}", mpaId); return mpaStorage.getMpaById(mpaId) - .map(mpa -> new MpaDto(mpa.getId(), mpa.getName())) + .map(mpa -> new MpaDto(mpa.getId(), mpa.getName())) .orElseThrow(() -> new NotFoundException("Рейтинг MPAA с id=" + mpaId + " не найден.")); } public Collection getAllMpaDtos() { log.info("Получение всех рейтингов MPAA"); return mpaStorage.getAllMpa().stream() - .map(mpa -> new MpaDto(mpa.getId(), mpa.getName())) + .map(mpa -> new MpaDto(mpa.getId(), mpa.getName())) .toList(); } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/ReviewService.java b/src/main/java/ru/yandex/practicum/filmorate/service/ReviewService.java new file mode 100644 index 0000000..04d3194 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/service/ReviewService.java @@ -0,0 +1,154 @@ +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.dal.FilmStorage; +import ru.yandex.practicum.filmorate.dal.ReviewStorage; +import ru.yandex.practicum.filmorate.dal.UserStorage; +import ru.yandex.practicum.filmorate.dto.ReviewDto; +import ru.yandex.practicum.filmorate.dto.request.create.ReviewCreateRequest; +import ru.yandex.practicum.filmorate.dto.request.update.ReviewUpdateRequest; +import ru.yandex.practicum.filmorate.exception.NotFoundException; +import ru.yandex.practicum.filmorate.mapper.ReviewMapper; +import ru.yandex.practicum.filmorate.model.EventType; +import ru.yandex.practicum.filmorate.model.Operation; +import ru.yandex.practicum.filmorate.model.Review; + +import java.util.Collection; +import java.util.Optional; + +@Service +@Slf4j +public class ReviewService { + + private final ReviewStorage reviewStorage; + private final FilmStorage filmStorage; + private final UserStorage userStorage; + private final EventService eventService; + + @Autowired + public ReviewService( + @Qualifier("reviewDbStorage") ReviewStorage reviewStorage, + @Qualifier("filmDbStorage") FilmStorage filmStorage, + @Qualifier("userDbStorage") UserStorage userStorage, + EventService eventService + ) { + this.reviewStorage = reviewStorage; + this.filmStorage = filmStorage; + this.userStorage = userStorage; + this.eventService = eventService; + } + + public ReviewDto createReview(ReviewCreateRequest request) { + log.info("Добавление нового отзыва пользователем id={} для фильма id={}", request.getUserId(), request.getFilmId()); + + checkUserExists(request.getUserId()); + checkFilmExists(request.getFilmId()); + + Review review = ReviewMapper.mapToReview(request); + review = reviewStorage.createReview(review); + eventService.addUserEvent(review.getUserId(), EventType.REVIEW, Operation.ADD, review.getId()); + + return ReviewMapper.mapToReviewDto(review); + } + + public ReviewDto updateReview(ReviewUpdateRequest request) { + log.info("Обновление данных об отзыве с id={}", request.getReviewId()); + + Review oldReview = reviewStorage.findReviewById(request.getReviewId()) + .orElseThrow(() -> new NotFoundException("Отзыв с id=" + request.getReviewId() + " не найден.")); + + Review newReview = ReviewMapper.updateReviewFields(oldReview, request); + newReview = reviewStorage.updateReview(newReview); + eventService.addUserEvent(newReview.getUserId(), EventType.REVIEW, Operation.UPDATE, newReview.getId()); + + return ReviewMapper.mapToReviewDto(newReview); + } + + public void deleteReview(Long id) { + log.info("Удаление отзыва с id={}", id); + checkReviewExists(id); + long userId = findReviewById(id).getUserId(); + reviewStorage.deleteReview(id); + eventService.addUserEvent(userId, EventType.REVIEW, Operation.REMOVE, id); + } + + public ReviewDto findReviewById(Long id) { + log.info("Получение отзыва с id={}", id); + return reviewStorage.findReviewById(id) + .map(ReviewMapper::mapToReviewDto) + .orElseThrow(() -> new NotFoundException("Отзыв с id=" + id + " не найден.")); + } + + public Collection findReviewsByFilmId(Long filmId, int count) { + log.info("Получение отзывов для фильма с id={} в количестве {}", filmId, count); + return reviewStorage.findReviewsByFilmId(filmId, count).stream() + .map(ReviewMapper::mapToReviewDto) + .toList(); + } + + public void addReviewLike(Long id, Long userId) { + log.info("Пользователь id={} ставит лайк отзыву id={}", userId, id); + checkReviewExists(id); + checkUserExists(userId); + + Optional likeStatus = reviewStorage.getReactionsStatus(id, userId); + + if (likeStatus.isEmpty()) { + reviewStorage.addReviewLike(id, userId); + } else if (!likeStatus.get()) { + reviewStorage.removeReviewDislike(id, userId); + reviewStorage.addReviewLike(id, userId); + } + + } + + public void addReviewDislike(Long id, Long userId) { + log.info("Пользователь id={} ставит дизлайк отзыву id={}", userId, id); + checkReviewExists(id); + checkUserExists(userId); + Optional likeStatus = reviewStorage.getReactionsStatus(id, userId); + + if (likeStatus.isEmpty()) { + reviewStorage.addReviewDislike(id, userId); + } else if (likeStatus.get()) { + reviewStorage.removeReviewLike(id, userId); + reviewStorage.addReviewDislike(id, userId); + } + + } + + public void removeReviewLike(Long id, Long userId) { + log.info("Пользователь id={} удаляет лайк у отзыва id={}", userId, id); + checkReviewExists(id); + checkUserExists(userId); + reviewStorage.removeReviewLike(id, userId); + } + + public void removeReviewDislike(Long id, Long userId) { + log.info("Пользователь id={} удаляет дизлайк у отзыва id={}", userId, id); + checkReviewExists(id); + checkUserExists(userId); + reviewStorage.removeReviewDislike(id, userId); + } + + private void checkFilmExists(Long filmId) { + if (filmStorage.checkFilmIsNotPresent(filmId)) { + throw new NotFoundException("Фильм с id=" + filmId + " не найден."); + } + } + + private void checkUserExists(Long userId) { + if (userStorage.checkUserIsNotPresent(userId)) { + throw new NotFoundException("Пользователь с id=" + userId + " не найден."); + } + } + + private void checkReviewExists(Long id) { + if (reviewStorage.checkReviewIsNotPresent(id)) { + throw new NotFoundException("Отзыв с id=" + id + " не найден."); + } + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/servise/UserService.java b/src/main/java/ru/yandex/practicum/filmorate/service/UserService.java similarity index 97% rename from src/main/java/ru/yandex/practicum/filmorate/servise/UserService.java rename to src/main/java/ru/yandex/practicum/filmorate/service/UserService.java index b195e6e..6799afe 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/servise/UserService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/UserService.java @@ -1,29 +1,26 @@ -package ru.yandex.practicum.filmorate.servise; +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.dao.DuplicateKeyException; import org.springframework.stereotype.Service; +import ru.yandex.practicum.filmorate.dal.FilmStorage; import ru.yandex.practicum.filmorate.dal.UserStorage; import ru.yandex.practicum.filmorate.dto.EventDto; -import ru.yandex.practicum.filmorate.dto.request.create.UserCreateRequest; +import ru.yandex.practicum.filmorate.dto.FilmDto; import ru.yandex.practicum.filmorate.dto.UserDto; +import ru.yandex.practicum.filmorate.dto.request.create.UserCreateRequest; import ru.yandex.practicum.filmorate.dto.request.update.UserUpdateRequest; import ru.yandex.practicum.filmorate.exception.*; import ru.yandex.practicum.filmorate.mapper.UserMapper; import ru.yandex.practicum.filmorate.model.EventType; import ru.yandex.practicum.filmorate.model.Operation; import ru.yandex.practicum.filmorate.model.User; -import ru.yandex.practicum.filmorate.servise.util.FriendsAction; -import ru.yandex.practicum.filmorate.dal.FilmStorage; -import ru.yandex.practicum.filmorate.dto.FilmDto; +import ru.yandex.practicum.filmorate.service.util.FriendsAction; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.stream.Collectors; import java.util.*; +import java.util.stream.Collectors; @Service @Slf4j @@ -164,6 +161,7 @@ public Collection getListOfMutualFriends(long userId, long friendId) { } public Collection getUserEvents(long userId) { + log.info("Получение событий пользователя с id={}.", userId); easyCheckUser(userId); return eventService.getUserEvents(userId); } diff --git a/src/main/java/ru/yandex/practicum/filmorate/servise/util/FilmSortingAction.java b/src/main/java/ru/yandex/practicum/filmorate/service/util/FilmSortingAction.java similarity index 94% rename from src/main/java/ru/yandex/practicum/filmorate/servise/util/FilmSortingAction.java rename to src/main/java/ru/yandex/practicum/filmorate/service/util/FilmSortingAction.java index eff7375..4803a27 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/servise/util/FilmSortingAction.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/util/FilmSortingAction.java @@ -1,4 +1,4 @@ -package ru.yandex.practicum.filmorate.servise.util; +package ru.yandex.practicum.filmorate.service.util; import lombok.Getter; diff --git a/src/main/java/ru/yandex/practicum/filmorate/servise/util/FriendsAction.java b/src/main/java/ru/yandex/practicum/filmorate/service/util/FriendsAction.java similarity index 55% rename from src/main/java/ru/yandex/practicum/filmorate/servise/util/FriendsAction.java rename to src/main/java/ru/yandex/practicum/filmorate/service/util/FriendsAction.java index ef1ffc5..36e31c3 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/servise/util/FriendsAction.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/util/FriendsAction.java @@ -1,4 +1,4 @@ -package ru.yandex.practicum.filmorate.servise.util; +package ru.yandex.practicum.filmorate.service.util; public enum FriendsAction { REQUEST, // GET diff --git a/src/main/java/ru/yandex/practicum/filmorate/servise/util/IdentifyService.java b/src/main/java/ru/yandex/practicum/filmorate/service/util/IdentifyService.java similarity index 77% rename from src/main/java/ru/yandex/practicum/filmorate/servise/util/IdentifyService.java rename to src/main/java/ru/yandex/practicum/filmorate/service/util/IdentifyService.java index e6b34a5..6b85448 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/servise/util/IdentifyService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/util/IdentifyService.java @@ -1,14 +1,14 @@ -package ru.yandex.practicum.filmorate.servise.util; +package ru.yandex.practicum.filmorate.service.util; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import java.util.Map; -@Slf4j @Service +@Slf4j public class IdentifyService { - public long getNextId(Map map) { + public long getNextId(Map map) { log.trace("Генерация нового id"); int currentMaxId = map.keySet() .stream() diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/util/LikeAction.java b/src/main/java/ru/yandex/practicum/filmorate/service/util/LikeAction.java new file mode 100644 index 0000000..dd910e2 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/service/util/LikeAction.java @@ -0,0 +1,5 @@ +package ru.yandex.practicum.filmorate.service.util; + +public enum LikeAction { + SET, REMOVE +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/servise/EventService.java b/src/main/java/ru/yandex/practicum/filmorate/servise/EventService.java deleted file mode 100644 index 480fd02..0000000 --- a/src/main/java/ru/yandex/practicum/filmorate/servise/EventService.java +++ /dev/null @@ -1,43 +0,0 @@ -package ru.yandex.practicum.filmorate.servise; - -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.dal.EventStorage; -import ru.yandex.practicum.filmorate.dto.EventDto; -import ru.yandex.practicum.filmorate.mapper.EventMapper; -import ru.yandex.practicum.filmorate.model.Event; -import ru.yandex.practicum.filmorate.model.EventType; -import ru.yandex.practicum.filmorate.model.Operation; - -import java.time.Instant; -import java.util.Collection; -import java.util.stream.Collectors; - -@Service -@Slf4j -public class EventService { - @Autowired - @Qualifier("eventDbStorage") - private EventStorage eventStorage; - - public Event addUserEvent(long userId, EventType eventType, Operation operation, long entityId) { - Event event = Event.builder() - .userId(userId) - .timestamp(Instant.now()) - .eventType(eventType) - .operation(operation) - .entityId(entityId) - .build(); - return eventStorage.addEvent(event); - } - - public Collection getUserEvents(long userId) { - log.info("Поиск всех событий пользователя."); - return eventStorage.getAllEventsByUserId(userId) - .stream() - .map(EventMapper::mapToEventDto) - .collect(Collectors.toList()); - } -} diff --git a/src/main/java/ru/yandex/practicum/filmorate/servise/ReviewService.java b/src/main/java/ru/yandex/practicum/filmorate/servise/ReviewService.java deleted file mode 100644 index fb5ca0b..0000000 --- a/src/main/java/ru/yandex/practicum/filmorate/servise/ReviewService.java +++ /dev/null @@ -1,154 +0,0 @@ -package ru.yandex.practicum.filmorate.servise; - -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.dal.FilmStorage; -import ru.yandex.practicum.filmorate.dal.ReviewStorage; -import ru.yandex.practicum.filmorate.dal.UserStorage; -import ru.yandex.practicum.filmorate.dto.ReviewDto; -import ru.yandex.practicum.filmorate.dto.request.create.ReviewCreateRequest; -import ru.yandex.practicum.filmorate.dto.request.update.ReviewUpdateRequest; -import ru.yandex.practicum.filmorate.exception.NotFoundException; -import ru.yandex.practicum.filmorate.mapper.ReviewMapper; -import ru.yandex.practicum.filmorate.model.EventType; -import ru.yandex.practicum.filmorate.model.Operation; -import ru.yandex.practicum.filmorate.model.Review; - -import java.util.Collection; -import java.util.Optional; - -@Service -@Slf4j -public class ReviewService { - - private final ReviewStorage reviewStorage; - private final FilmStorage filmStorage; - private final UserStorage userStorage; - private final EventService eventService; - - @Autowired - public ReviewService( - @Qualifier("reviewDbStorage") ReviewStorage reviewStorage, - @Qualifier("filmDbStorage") FilmStorage filmStorage, - @Qualifier("userDbStorage") UserStorage userStorage, - EventService eventService - ) { - this.reviewStorage = reviewStorage; - this.filmStorage = filmStorage; - this.userStorage = userStorage; - this.eventService = eventService; - } - - public ReviewDto createReview(ReviewCreateRequest request) { - log.info("Добавление нового отзыва пользователем id={} для фильма id={}", request.getUserId(), request.getFilmId()); - - checkUserExists(request.getUserId()); - checkFilmExists(request.getFilmId()); - - Review review = ReviewMapper.mapToReview(request); - review = reviewStorage.createReview(review); - eventService.addUserEvent(review.getUserId(), EventType.REVIEW, Operation.ADD, review.getId()); - - return ReviewMapper.mapToReviewDto(review); - } - - public ReviewDto updateReview(ReviewUpdateRequest request) { - log.info("Обновление данных об отзыве с id={}", request.getReviewId()); - - Review oldReview = reviewStorage.findReviewById(request.getReviewId()) - .orElseThrow(() -> new NotFoundException("Отзыв с id=" + request.getReviewId() + " не найден.")); - - Review newReview = ReviewMapper.updateReviewFields(oldReview, request); - newReview = reviewStorage.updateReview(newReview); - eventService.addUserEvent(newReview.getUserId(), EventType.REVIEW, Operation.UPDATE, newReview.getId()); - - return ReviewMapper.mapToReviewDto(newReview); - } - - public void deleteReview(Long id) { - log.info("Удаление отзыва с id={}", id); - checkReviewExists(id); - long userId = findReviewById(id).getUserId(); - reviewStorage.deleteReview(id); - eventService.addUserEvent(userId, EventType.REVIEW, Operation.REMOVE, id); - } - - public ReviewDto findReviewById(Long id) { - log.info("Получение отзыва с id={}", id); - return reviewStorage.findReviewById(id) - .map(ReviewMapper::mapToReviewDto) - .orElseThrow(() -> new NotFoundException("Отзыв с id=" + id + " не найден.")); - } - - public Collection findReviewsByFilmId(Long filmId, int count) { - log.info("Получение отзывов для фильма с id={} в количестве {}", filmId, count); - return reviewStorage.findReviewsByFilmId(filmId, count).stream() - .map(ReviewMapper::mapToReviewDto) - .toList(); - } - - public void addReviewLike(Long id, Long userId) { - log.info("Пользователь id={} ставит лайк отзыву id={}", userId, id); - checkReviewExists(id); - checkUserExists(userId); - - Optional likeStatus = reviewStorage.getReactionsStatus(id, userId); - - if (likeStatus.isEmpty()) { - reviewStorage.addReviewLike(id, userId); - } else if (!likeStatus.get()) { - reviewStorage.removeReviewDislike(id, userId); - reviewStorage.addReviewLike(id, userId); - } - - } - - public void addReviewDislike(Long id, Long userId) { - log.info("Пользователь id={} ставит дизлайк отзыву id={}", userId, id); - checkReviewExists(id); - checkUserExists(userId); - Optional likeStatus = reviewStorage.getReactionsStatus(id, userId); - - if (likeStatus.isEmpty()) { - reviewStorage.addReviewDislike(id, userId); - } else if (likeStatus.get()) { - reviewStorage.removeReviewLike(id, userId); - reviewStorage.addReviewDislike(id, userId); - } - - } - - public void removeReviewLike(Long id, Long userId) { - log.info("Пользователь id={} удаляет лайк у отзыва id={}", userId, id); - checkReviewExists(id); - checkUserExists(userId); - reviewStorage.removeReviewLike(id, userId); - } - - public void removeReviewDislike(Long id, Long userId) { - log.info("Пользователь id={} удаляет дизлайк у отзыва id={}", userId, id); - checkReviewExists(id); - checkUserExists(userId); - reviewStorage.removeReviewDislike(id, userId); - } - - private void checkFilmExists(Long filmId) { - if (filmStorage.checkFilmIsNotPresent(filmId)) { - throw new NotFoundException("Фильм с id=" + filmId + " не найден."); - } - } - - private void checkUserExists(Long userId) { - if (userStorage.checkUserIsNotPresent(userId)) { - throw new NotFoundException("Пользователь с id=" + userId + " не найден."); - } - } - - private void checkReviewExists(Long id) { - if (reviewStorage.checkReviewIsNotPresent(id)) { - throw new NotFoundException("Отзыв с id=" + id + " не найден."); - } - } -} diff --git a/src/main/java/ru/yandex/practicum/filmorate/servise/util/LikeAction.java b/src/main/java/ru/yandex/practicum/filmorate/servise/util/LikeAction.java deleted file mode 100644 index 1a4e372..0000000 --- a/src/main/java/ru/yandex/practicum/filmorate/servise/util/LikeAction.java +++ /dev/null @@ -1,5 +0,0 @@ -package ru.yandex.practicum.filmorate.servise.util; - -public enum LikeAction { - SET, REMOVE -} From 47657a0d5d744dce9ca20df5200220fc29f77e88 Mon Sep 17 00:00:00 2001 From: AleksOBM Date: Sat, 21 Feb 2026 22:13:15 +0300 Subject: [PATCH 43/47] =?UTF-8?q?=D0=9E=D1=82=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BA=D0=B0=20=D0=BD=D0=B0=20=D0=BF=D0=B5=D1=80=D0=B2=D0=BE?= =?UTF-8?q?=D0=B5=20=D1=80=D0=B5=D0=B2=D1=8C=D1=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2629083..bfd8b49 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ The Filmorate project — это приложение для работы с б А также площадка для общения и взаимодействия пользователей. ### Основные возможности -- Хранение данных о фильмах +- Хранение данных о фильмах - Хранение данных о пользователях - Возможность пользователям ставить лайки фильмам - Возможность пользователям добавлять друг друга в друзья @@ -15,6 +15,7 @@ The Filmorate project — это приложение для работы с б - Реализован поиск по названию и описанию - Реализована система жанров для фильмов - Есть лента событий + ### Требования (Environmental Requirements) Для запуска проекта требуется: - Java версии 21 Приложение работает на порту 8080 From c11817b9bb10b082e3408499bd85339d66f2d0c9 Mon Sep 17 00:00:00 2001 From: AleksOBM Date: Sun, 22 Feb 2026 23:14:13 +0300 Subject: [PATCH 44/47] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D1=82=D1=8C=20=D0=BC=D0=B5=D0=BB=D0=BA=D0=B8=D0=B5=20?= =?UTF-8?q?=D0=B1=D0=B0=D0=B3=D0=B8=20=D0=B8=20=D0=B4=D0=BE=D1=80=D0=B0?= =?UTF-8?q?=D0=B1=D0=BE=D1=82=D0=B0=D1=82=D1=8C=20readme?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ExamplesOfRequests.png | Bin 141945 -> 60826 bytes README.md | 2 +- .../filmorate/service/FilmService.java | 13 ++++++++++++- .../filmorate/service/ReviewService.java | 16 ++++++++-------- 4 files changed, 21 insertions(+), 10 deletions(-) diff --git a/ExamplesOfRequests.png b/ExamplesOfRequests.png index 7f30c7d15b50ac05007f527f2de2d3d0d75be6d6..ae488551b08dba23949225d3b87dff16606282e6 100644 GIT binary patch literal 60826 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 z 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%0AP^@5j9orkNqN@*Ce&2u- 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 z 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 diff --git a/README.md b/README.md index bfd8b49..7a8723d 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ The Filmorate project — это приложение для работы с б - Возможность заводить друзей - Возможность оставлять отзывы о фильмах - Фильмы содержат возрастные ограничения по стандарту MPPA -- Реализован поиск по названию и описанию +- Реализован поиск по названию и режиссерам - Реализована система жанров для фильмов - Есть лента событий diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/FilmService.java b/src/main/java/ru/yandex/practicum/filmorate/service/FilmService.java index 47e9483..894a280 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/service/FilmService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/FilmService.java @@ -12,6 +12,7 @@ import ru.yandex.practicum.filmorate.dto.request.update.FilmUpdateRequest; import ru.yandex.practicum.filmorate.exception.NotFoundException; import ru.yandex.practicum.filmorate.exception.ParameterNotValidException; +import ru.yandex.practicum.filmorate.exception.ValidationException; import ru.yandex.practicum.filmorate.mapper.FilmMapper; import ru.yandex.practicum.filmorate.model.*; import ru.yandex.practicum.filmorate.service.util.FilmSortingAction; @@ -193,7 +194,9 @@ public FilmDto findById(Long id) { public Collection getCommonLikedFilms(Long userId, Long friendId) { log.info("Получение общих с другом фильмов userId={}, friendId={}", userId, friendId); - + if (Objects.equals(userId, friendId)) { + throw new ValidationException("У пользователей должны быть разные id."); + } if (userStorage.checkUserIsNotPresent(userId)) { throw new NotFoundException("Пользователь с id=" + userId + " не найден."); } @@ -334,6 +337,14 @@ private void logFilmUpdate(FilmUpdateRequest request, Film film) { public Collection search(String query, String by) { log.info("Запрос на поиск фильмов: query='{}', by='{}'", query, by); + if (query == null || query.isBlank()) { + throw new ParameterNotValidException("query", "Поисковая строка не может быть пустой."); + } + if (by == null || by.isBlank()) { + throw new ParameterNotValidException("by", + "Параметр поля для поиска должен содержать 'title' или 'director'"); + } + return filmStorage.search(query, by).stream() .map(film -> FilmMapper.mapToFilmDto( film, diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/ReviewService.java b/src/main/java/ru/yandex/practicum/filmorate/service/ReviewService.java index 04d3194..54b1094 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/service/ReviewService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/ReviewService.java @@ -18,6 +18,7 @@ import java.util.Collection; import java.util.Optional; +import java.util.stream.Stream; @Service @Slf4j @@ -56,10 +57,7 @@ public ReviewDto createReview(ReviewCreateRequest request) { public ReviewDto updateReview(ReviewUpdateRequest request) { log.info("Обновление данных об отзыве с id={}", request.getReviewId()); - - Review oldReview = reviewStorage.findReviewById(request.getReviewId()) - .orElseThrow(() -> new NotFoundException("Отзыв с id=" + request.getReviewId() + " не найден.")); - + Review oldReview = findReview(request.getReviewId()); Review newReview = ReviewMapper.updateReviewFields(oldReview, request); newReview = reviewStorage.updateReview(newReview); eventService.addUserEvent(newReview.getUserId(), EventType.REVIEW, Operation.UPDATE, newReview.getId()); @@ -77,9 +75,7 @@ public void deleteReview(Long id) { public ReviewDto findReviewById(Long id) { log.info("Получение отзыва с id={}", id); - return reviewStorage.findReviewById(id) - .map(ReviewMapper::mapToReviewDto) - .orElseThrow(() -> new NotFoundException("Отзыв с id=" + id + " не найден.")); + return ReviewMapper.mapToReviewDto(findReview(id)); } public Collection findReviewsByFilmId(Long filmId, int count) { @@ -102,7 +98,6 @@ public void addReviewLike(Long id, Long userId) { reviewStorage.removeReviewDislike(id, userId); reviewStorage.addReviewLike(id, userId); } - } public void addReviewDislike(Long id, Long userId) { @@ -134,6 +129,11 @@ public void removeReviewDislike(Long id, Long userId) { reviewStorage.removeReviewDislike(id, userId); } + private Review findReview(long reviewId) { + return reviewStorage.findReviewById(reviewId) + .orElseThrow(() -> new NotFoundException("Отзыв с id=" + reviewId + " не найден.")); + } + private void checkFilmExists(Long filmId) { if (filmStorage.checkFilmIsNotPresent(filmId)) { throw new NotFoundException("Фильм с id=" + filmId + " не найден."); From c4491a55eebaccaa3d158523513f4f449e02470e Mon Sep 17 00:00:00 2001 From: Darya346 Date: Mon, 23 Feb 2026 00:14:59 +0300 Subject: [PATCH 45/47] =?UTF-8?q?fix:=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D0=B5=D0=BD=D1=8B=20=D0=B7=D0=B0=D0=BC=D0=B5=D1=87?= =?UTF-8?q?=D0=B0=D0=BD=D0=B8=D1=8F=20=D0=B2=20getRecommendations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../practicum/filmorate/dal/FilmStorage.java | 1 + .../filmorate/dal/database/FilmDbStorage.java | 59 ++++++++++--------- .../dal/inmemory/InMemoryFilmStorage.java | 5 ++ .../filmorate/service/FilmService.java | 14 +++++ .../filmorate/service/UserService.java | 33 +++++++---- 5 files changed, 74 insertions(+), 38 deletions(-) diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/FilmStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/FilmStorage.java index ab96136..1fe9ca9 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/FilmStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/FilmStorage.java @@ -51,4 +51,5 @@ public interface FilmStorage { /// Удаление фильма по id void removeFilm(long filmId); + Collection getFilmsByIds(Collection ids); } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/database/FilmDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/database/FilmDbStorage.java index 6e83ce2..eac5594 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/database/FilmDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/database/FilmDbStorage.java @@ -39,7 +39,7 @@ public Film createFilm(Film film) { film.setId(id); insertGenreIds(film); insertDirectorIds(film); - return film; + return findById(id).get(); } @Override @@ -56,7 +56,7 @@ public Film updateFilm(Film film) { ); insertGenreIds(film); insertDirectorIds(film); - return film; + return findById(film.getId()).get(); } @Override @@ -157,29 +157,19 @@ public void removeFilm(long filmId) { } private void insertGenreIds(Film film) { - Set genreIds = film.getGenreIds(); - if (genreIds.isEmpty()) { - jdbc.update("DELETE FROM genres_of_films WHERE film_id = ?", film.getId()); - return; - } jdbc.update("DELETE FROM genres_of_films WHERE film_id = ?", film.getId()); - String placeholders = String.join( - ",", Collections.nCopies(genreIds.size(), " (" + film.getId() + ", ?)") - ); + Set genreIds = film.getGenreIds(); + if (genreIds == null || genreIds.isEmpty()) return; + String placeholders = String.join(",", Collections.nCopies(genreIds.size(), " (" + film.getId() + ", ?)")); String sql = SQL_FILMS_INSERT_GENREIDS + placeholders; updateWithControl(sql, genreIds.toArray()); } private void insertDirectorIds(Film film) { - Set directorIds = film.getDirectorIds(); - if (directorIds.isEmpty()) { - jdbc.update("DELETE FROM directors_of_films WHERE film_id = ?", film.getId()); - return; - } jdbc.update("DELETE FROM directors_of_films WHERE film_id = ?", film.getId()); - String placeholders = String.join( - ",", Collections.nCopies(directorIds.size(), " (" + film.getId() + ", ?)") - ); + Set directorIds = film.getDirectorIds(); + if (directorIds == null || directorIds.isEmpty()) return; + String placeholders = String.join(",", Collections.nCopies(directorIds.size(), " (" + film.getId() + ", ?)")); String sql = SQL_FILMS_INSERT_DIRECTORIDS + placeholders; updateWithControl(sql, directorIds.toArray()); } @@ -198,17 +188,11 @@ private Collection findManyFilms(String query, Object... params) { } }); long userId = rs.getLong("user_id"); - if (!rs.wasNull() && film != null) { - film.addLike(userId); - } + if (!rs.wasNull() && film != null) film.addLike(userId); int genreId = rs.getInt("genre_id"); - if (!rs.wasNull() && film != null) { - film.addGenreId(genreId); - } + if (!rs.wasNull() && film != null) film.addGenreId(genreId); int directorId = rs.getInt("director_id"); - if (!rs.wasNull() && film != null) { - film.addDirectorId(directorId); - } + if (!rs.wasNull() && film != null) film.addDirectorId(directorId); } return new ArrayList<>(films.values()); }, params); @@ -221,4 +205,25 @@ private Set getGenreIdsByFilmId(long filmId) { private Set getDirectorIdsByFilmId(long filmId) { return findColumnByQuery(SQL_FILMS_FIND_DIRECTORIDS_BY_FILM_ID, Integer.class, filmId); } + + @Override + public Collection getFilmsByIds(Collection ids) { + if (ids == null || ids.isEmpty()) { + return Collections.emptyList(); + } + + String inSql = String.join(",", Collections.nCopies(ids.size(), "?")); + + String sql = String.format(""" + SELECT f.id, f.film_name, f.description, f.release_date, f.duration, f.mpa_id, + l.user_id, gof.genre_id, dof.director_id + FROM films f + LEFT JOIN likes l ON f.id = l.film_id + LEFT JOIN genres_of_films gof ON f.id = gof.film_id + LEFT JOIN directors_of_films dof ON f.id = dof.film_id + WHERE f.id IN (%s) + """, inSql); + + return findManyFilms(sql, ids.toArray()); + } } \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/inmemory/InMemoryFilmStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/inmemory/InMemoryFilmStorage.java index 39e60e9..bdbb0ed 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/inmemory/InMemoryFilmStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/inmemory/InMemoryFilmStorage.java @@ -82,4 +82,9 @@ public Collection search(String query, String by) { public void removeFilm(long filmId) { throw new MethodNotImplementedException(); } + + @Override + public Collection getFilmsByIds(Collection ids) { + throw new MethodNotImplementedException(); + } } \ 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 index 894a280..569e0c5 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/service/FilmService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/FilmService.java @@ -354,4 +354,18 @@ public Collection search(String query, String by) { )) .toList(); } + + public Collection getFilmsByIds(Collection ids) { + if (ids == null || ids.isEmpty()) { + return Collections.emptyList(); + } + return filmStorage.getFilmsByIds(ids).stream() + .map(film -> FilmMapper.mapToFilmDto( + film, + mpas.get(film.getMpaId()), + getGenresByIds(film.getGenreIds()), + getDirectorsByIds(film.getDirectorIds()) + )) + .toList(); + } } 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 6799afe..621a247 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/service/UserService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/UserService.java @@ -206,7 +206,6 @@ private void logUserUpdate(UserUpdateRequest request, User user) { log.info("Данные для обновления: [{}]", updatedData); } - // Рекомендации public Collection getRecommendations(long userId) { log.info("Получение рекомендаций для пользователя id={}.", userId); easyCheckUser(userId); @@ -218,8 +217,7 @@ public Collection getRecommendations(long userId) { return Collections.emptyList(); } - long similarUserId = -1; - int maxOverlap = 0; + Map similarityMap = new HashMap<>(); for (Map.Entry> entry : allLikes.entrySet()) { long otherUserId = entry.getKey(); @@ -230,20 +228,33 @@ public Collection getRecommendations(long userId) { .filter(otherUserLikes::contains) .count(); - if (overlap > maxOverlap) { - maxOverlap = overlap; - similarUserId = otherUserId; + if (overlap > 0) { + similarityMap.put(otherUserId, overlap); } } - if (similarUserId == -1) { + if (similarityMap.isEmpty()) { return Collections.emptyList(); } - Set similarUserLikes = allLikes.get(similarUserId); - return similarUserLikes.stream() - .filter(filmId -> !targetUserLikes.contains(filmId)) - .map(filmService::findById) + List similarUserIds = similarityMap.entrySet().stream() + .sorted(Map.Entry.comparingByValue().reversed()) + .limit(15) + .map(Map.Entry::getKey) .collect(Collectors.toList()); + + Set recommendedFilmIds = new HashSet<>(); + for (Long simId : similarUserIds) { + Set likedBySimilarUser = allLikes.get(simId); + likedBySimilarUser.stream() + .filter(filmId -> !targetUserLikes.contains(filmId)) + .forEach(recommendedFilmIds::add); + } + + if (recommendedFilmIds.isEmpty()) { + return Collections.emptyList(); + } + + return filmService.getFilmsByIds(recommendedFilmIds); } } From 33681bbade95f0332e8ae8f56500fdce58f480cf Mon Sep 17 00:00:00 2001 From: Darya346 Date: Mon, 23 Feb 2026 00:49:48 +0300 Subject: [PATCH 46/47] fix: remove unused import in ReviewService --- .../ru/yandex/practicum/filmorate/service/ReviewService.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/ReviewService.java b/src/main/java/ru/yandex/practicum/filmorate/service/ReviewService.java index 54b1094..2dd323c 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/service/ReviewService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/ReviewService.java @@ -18,7 +18,6 @@ import java.util.Collection; import java.util.Optional; -import java.util.stream.Stream; @Service @Slf4j From 402c5ea61f8b7f3089826bcca30f2ad5a0fbeacf Mon Sep 17 00:00:00 2001 From: AleksOBM Date: Mon, 23 Feb 2026 14:00:43 +0300 Subject: [PATCH 47/47] =?UTF-8?q?=D0=9E=D1=82=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D1=82=D1=8C=20=D0=BD=D0=B0=20=D0=B2=D1=82=D0=BE=D1=80?= =?UTF-8?q?=D0=BE=D0=B5=20=D1=80=D0=B5=D0=B2=D1=8C=D1=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/ru/yandex/practicum/filmorate/service/UserService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 621a247..aa2485f 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/service/UserService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/UserService.java @@ -241,7 +241,7 @@ public Collection getRecommendations(long userId) { .sorted(Map.Entry.comparingByValue().reversed()) .limit(15) .map(Map.Entry::getKey) - .collect(Collectors.toList()); + .toList(); Set recommendedFilmIds = new HashSet<>(); for (Long simId : similarUserIds) {