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 961959c..db89073 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java @@ -65,4 +65,10 @@ public Collection getPopular( log.info("GET /films/popular?count={}&genreId={}&year={}", count, genreId, year); return filmService.getPopular(count, genreId, year); } + + @GetMapping("/common") + public Collection getCommon(@RequestParam long userId, @RequestParam long friendId) { + log.info("GET /films/common?userId={}&friendId={}", userId, friendId); + return filmService.getCommon(userId, friendId); + } } \ 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 beee424..94089cb 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/service/FilmService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/FilmService.java @@ -97,6 +97,13 @@ public Collection getPopular(int count, Integer genreId, Integer year) { return filmStorage.findPopular(count, genreId, year); } + // Общие фильмы двух пользователей — те, что лайкнули оба, по убыванию популярности + public Collection getCommon(long userId, long friendId) { + getUserOrThrow(userId); // оба пользователя должны существовать — иначе 404 + getUserOrThrow(friendId); + return filmStorage.findCommon(userId, friendId); + } + // Валидация даты релиза — не может быть раньше дня рождения кино private void validateReleaseDate(Film film) { if (film.getReleaseDate().isBefore(CINEMA_BIRTHDAY)) { diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/film/FilmDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/film/FilmDbStorage.java index 36e2435..5280bef 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/storage/film/FilmDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/film/FilmDbStorage.java @@ -37,17 +37,21 @@ public class FilmDbStorage implements FilmStorage { // Базовый запрос для популярных фильмов: WHERE-условия добавляются динамически private static final String FIND_POPULAR_BASE_QUERY = SELECT_FILMS + "LEFT JOIN film_likes fl ON f.id = fl.film_id "; - // Хвост запроса популярных фильмов: группировка, сортировка и лимит + // Хвост запроса популярных фильмов: группировка и сортировка по числу лайков private static final String FIND_POPULAR_TAIL_QUERY = " " + "GROUP BY f.id, f.name, f.description, f.release_date, f.duration, f.mpa_id, m.name " - + "ORDER BY COUNT(fl.user_id) DESC, f.id " - + "LIMIT ?"; + + "ORDER BY COUNT(fl.user_id) DESC, f.id"; + // Лимит отдельно: в общих фильмах, поиске и фильмах режиссёра его нет + private static final String LIMIT_CLAUSE = " LIMIT ?"; // Условия фильтрации популярных фильмов: по жанру и по году выпуска private static final String FIND_POPULAR_GENRE_FILTER = "f.id IN (SELECT film_id FROM film_genres WHERE genre_id = ?)"; private static final String FIND_POPULAR_YEAR_FILTER = "YEAR(f.release_date) = ?"; + private static final String LIKED_BY_USER_FILTER = + "f.id IN (SELECT film_id FROM film_likes WHERE user_id = ?)"; + private static final String INSERT_QUERY = "INSERT INTO films (name, description, release_date, duration, mpa_id) VALUES (?, ?, ?, ?, ?)"; private static final String UPDATE_QUERY = @@ -149,7 +153,17 @@ public List findPopular(int count, Integer genreId, Integer year) { } params.add(count); // LIMIT всегда последний - List films = jdbc.query(buildPopularQuery(conditions), mapper, params.toArray()); + String sql = buildPopularQuery(conditions) + LIMIT_CLAUSE; + List films = jdbc.query(sql, mapper, params.toArray()); + loadGenresForFilms(films); + return films; + } + + @Override + public List findCommon(long userId, long friendId) { + // Одно и то же условие дважды, с разными id: фильм лайкнули оба + String sql = buildPopularQuery(List.of(LIKED_BY_USER_FILTER, LIKED_BY_USER_FILTER)); + List films = jdbc.query(sql, mapper, userId, friendId); loadGenresForFilms(films); return films; } @@ -165,7 +179,7 @@ public boolean removeLike(long filmId, long userId) { return jdbc.update(REMOVE_LIKE_QUERY, filmId, userId) > 0; } - // Собирает запрос популярных фильмов: базовое чтение + WHERE по условиям + группировка с лимитом + // Собирает запрос: чтение + WHERE по условиям + сортировка по числу лайков, без LIMIT private String buildPopularQuery(List conditions) { StringBuilder sql = new StringBuilder(FIND_POPULAR_BASE_QUERY); if (!conditions.isEmpty()) { diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/film/FilmStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/film/FilmStorage.java index d1d178c..3e5ecd3 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/storage/film/FilmStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/film/FilmStorage.java @@ -20,6 +20,8 @@ public interface FilmStorage { List findPopular(int count, Integer genreId, Integer year); + List findCommon(long userId, long friendId); + void addLike(long filmId, long userId); boolean removeLike(long filmId, long userId); diff --git a/src/test/java/ru/yandex/practicum/filmorate/storage/film/FilmDbStorageTest.java b/src/test/java/ru/yandex/practicum/filmorate/storage/film/FilmDbStorageTest.java index b9ad5e6..67cb17b 100644 --- a/src/test/java/ru/yandex/practicum/filmorate/storage/film/FilmDbStorageTest.java +++ b/src/test/java/ru/yandex/practicum/filmorate/storage/film/FilmDbStorageTest.java @@ -223,4 +223,37 @@ void shouldReturnAllPopularWhenFiltersAreNull() { assertEquals(1, filmStorage.findPopular(10, null, null).size()); } + + @Test + void shouldFindCommonFilmsSortedByPopularity() { + Film first = createFilm("Первый"); + Film second = createFilm("Второй"); + Film onlyAnna = createFilm("Только Анны"); + User anna = createUser("anna"); + User boris = createUser("boris"); + User vera = createUser("vera"); + // Оба лайкнули первый и второй; у второго есть ещё лайк Веры — он популярнее + filmStorage.addLike(first.getId(), anna.getId()); + filmStorage.addLike(first.getId(), boris.getId()); + filmStorage.addLike(second.getId(), anna.getId()); + filmStorage.addLike(second.getId(), boris.getId()); + filmStorage.addLike(second.getId(), vera.getId()); + // Этот лайкнула только Анна — в общие не попадает + filmStorage.addLike(onlyAnna.getId(), anna.getId()); + + List common = filmStorage.findCommon(anna.getId(), boris.getId()); + + assertEquals(List.of(second.getId(), first.getId()), + common.stream().map(Film::getId).toList()); + } + + @Test + void shouldReturnEmptyCommonFilmsWhenNoSharedLikes() { + Film film = createFilm("Один"); + User anna = createUser("anna"); + User boris = createUser("boris"); + filmStorage.addLike(film.getId(), anna.getId()); + + assertEquals(0, filmStorage.findCommon(anna.getId(), boris.getId()).size()); + } } \ No newline at end of file