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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,10 @@ public Collection<Film> getPopular(
log.info("GET /films/popular?count={}&genreId={}&year={}", count, genreId, year);
return filmService.getPopular(count, genreId, year);
}

@GetMapping("/common")
public Collection<Film> getCommon(@RequestParam long userId, @RequestParam long friendId) {
log.info("GET /films/common?userId={}&friendId={}", userId, friendId);
return filmService.getCommon(userId, friendId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,13 @@ public Collection<Film> getPopular(int count, Integer genreId, Integer year) {
return filmStorage.findPopular(count, genreId, year);
}

// Общие фильмы двух пользователей — те, что лайкнули оба, по убыванию популярности
public Collection<Film> 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)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down Expand Up @@ -149,7 +153,17 @@ public List<Film> findPopular(int count, Integer genreId, Integer year) {
}
params.add(count); // LIMIT всегда последний

List<Film> films = jdbc.query(buildPopularQuery(conditions), mapper, params.toArray());
String sql = buildPopularQuery(conditions) + LIMIT_CLAUSE;
List<Film> films = jdbc.query(sql, mapper, params.toArray());
loadGenresForFilms(films);
return films;
}

@Override
public List<Film> findCommon(long userId, long friendId) {
// Одно и то же условие дважды, с разными id: фильм лайкнули оба
String sql = buildPopularQuery(List.of(LIKED_BY_USER_FILTER, LIKED_BY_USER_FILTER));
List<Film> films = jdbc.query(sql, mapper, userId, friendId);
loadGenresForFilms(films);
return films;
}
Expand All @@ -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<String> conditions) {
StringBuilder sql = new StringBuilder(FIND_POPULAR_BASE_QUERY);
if (!conditions.isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ public interface FilmStorage {

List<Film> findPopular(int count, Integer genreId, Integer year);

List<Film> findCommon(long userId, long friendId);

void addLike(long filmId, long userId);

boolean removeLike(long filmId, long userId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Film> 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());
}
}
Loading