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 @@ -59,8 +59,10 @@ public Film removeLike(@PathVariable long id, @PathVariable long userId) {

@GetMapping("/popular")
public Collection<Film> getPopular(
@RequestParam(defaultValue = "10") int count) {
log.info("GET /films/popular?count={}", count);
return filmService.getPopular(count);
@RequestParam(defaultValue = "10") int count,
@RequestParam(required = false) Integer genreId,
@RequestParam(required = false) Integer year) {
log.info("GET /films/popular?count={}&genreId={}&year={}", count, genreId, year);
return filmService.getPopular(count, genreId, year);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,14 +77,24 @@ public Film removeLike(long filmId, long userId) {
return film;
}

public Collection<Film> getPopular(int count) {
public Collection<Film> getPopular(int count, Integer genreId, Integer year) {
// Валидация: count должен быть положительным числом
if (count <= 0) {
throw new ValidationException(
"Количество фильмов должно быть положительным числом, получено: " + count
);
}
return filmStorage.findPopular(count);
// Жанр, если указан, должен существовать — иначе 404
if (genreId != null) {
genreService.findById(genreId);
}
// Валидация: год, если указан, должен быть положительным
if (year != null && year <= 0) {
throw new ValidationException(
"Год должен быть положительным числом, получено: " + year
);
}
return filmStorage.findPopular(count, genreId, year);
}

// Валидация даты релиза — не может быть раньше дня рождения кино
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,21 @@ public class FilmDbStorage implements FilmStorage {
+ "LEFT JOIN mpa m ON f.mpa_id = m.id ";
private static final String FIND_ALL_QUERY = SELECT_FILMS + "ORDER BY f.id";
private static final String FIND_BY_ID_QUERY = SELECT_FILMS + "WHERE f.id = ?";
private static final String FIND_POPULAR_QUERY = SELECT_FILMS
+ "LEFT JOIN film_likes fl ON f.id = fl.film_id "

// Базовый запрос для популярных фильмов: 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 ?";

// Условия фильтрации популярных фильмов: по жанру и по году выпуска
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 INSERT_QUERY =
"INSERT INTO films (name, description, release_date, duration, mpa_id) VALUES (?, ?, ?, ?, ?)";
private static final String UPDATE_QUERY =
Expand Down Expand Up @@ -124,8 +134,22 @@ public Optional<Film> findById(long id) {
}

@Override
public List<Film> findPopular(int count) {
List<Film> films = jdbc.query(FIND_POPULAR_QUERY, mapper, count);
public List<Film> findPopular(int count, Integer genreId, Integer year) {
List<String> conditions = new ArrayList<>();
List<Object> params = new ArrayList<>();

if (genreId != null) {
// Подзапрос вместо JOIN: иначе фильм с несколькими жанрами задвоит строки
conditions.add(FIND_POPULAR_GENRE_FILTER);
params.add(genreId);
}
if (year != null) {
conditions.add(FIND_POPULAR_YEAR_FILTER);
params.add(year);
}
params.add(count); // LIMIT всегда последний

List<Film> films = jdbc.query(buildPopularQuery(conditions), mapper, params.toArray());
loadGenresForFilms(films);
return films;
}
Expand All @@ -141,6 +165,15 @@ public boolean removeLike(long filmId, long userId) {
return jdbc.update(REMOVE_LIKE_QUERY, filmId, userId) > 0;
}

// Собирает запрос популярных фильмов: базовое чтение + WHERE по условиям + группировка с лимитом
private String buildPopularQuery(List<String> conditions) {
StringBuilder sql = new StringBuilder(FIND_POPULAR_BASE_QUERY);
if (!conditions.isEmpty()) {
sql.append("WHERE ").append(String.join(" AND ", conditions)).append(" ");
}
return sql.append(FIND_POPULAR_TAIL_QUERY).toString();
}

// Рейтинга может не быть — тогда в колонку mpa_id уходит NULL
private Integer getMpaId(Film film) {
return film.getMpa() == null ? null : film.getMpa().getId();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public interface FilmStorage {

Optional<Film> findById(long id);

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

void addLike(long filmId, long userId);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,10 +158,69 @@ void shouldFindPopularByLikes() {
filmStorage.addLike(second.getId(), boris.getId());
filmStorage.addLike(first.getId(), anna.getId());

List<Film> popular = filmStorage.findPopular(10);
List<Film> popular = filmStorage.findPopular(10, null, null);

assertEquals(List.of(second.getId(), first.getId(), third.getId()),
popular.stream().map(Film::getId).toList());
assertEquals(2, filmStorage.findPopular(2).size());
assertEquals(2, filmStorage.findPopular(2, null, null).size());
}

@Test
void shouldFilterPopularByGenre() {
Film comedy = createFilm("Комедия");
comedy.getGenres().add(genre(1));
filmStorage.update(comedy);
Film drama = createFilm("Драма");
drama.getGenres().add(genre(2));
filmStorage.update(drama);
User anna = createUser("anna");
filmStorage.addLike(comedy.getId(), anna.getId());
filmStorage.addLike(drama.getId(), anna.getId());

List<Film> popular = filmStorage.findPopular(10, 1, null);

assertEquals(List.of(comedy.getId()), popular.stream().map(Film::getId).toList());
}

@Test
void shouldFilterPopularByYear() {
Film old = createFilm("Старый");
Film fresh = newFilm("Новый");
fresh.setReleaseDate(LocalDate.of(2020, 1, 1));
fresh = filmStorage.add(fresh);
User anna = createUser("anna");
filmStorage.addLike(old.getId(), anna.getId());
filmStorage.addLike(fresh.getId(), anna.getId());

List<Film> popular = filmStorage.findPopular(10, null, 2000);

assertEquals(List.of(old.getId()), popular.stream().map(Film::getId).toList());
}

@Test
void shouldFilterPopularByGenreAndYear() {
Film comedy2000 = newFilm("Комедия 2000");
comedy2000.getGenres().add(genre(1));
comedy2000 = filmStorage.add(comedy2000);
Film comedy2020 = newFilm("Комедия 2020");
comedy2020.setReleaseDate(LocalDate.of(2020, 1, 1));
comedy2020.getGenres().add(genre(1));
comedy2020 = filmStorage.add(comedy2020);
User anna = createUser("anna");
filmStorage.addLike(comedy2000.getId(), anna.getId());
filmStorage.addLike(comedy2020.getId(), anna.getId());

List<Film> popular = filmStorage.findPopular(10, 1, 2000);

assertEquals(List.of(comedy2000.getId()), popular.stream().map(Film::getId).toList());
}

@Test
void shouldReturnAllPopularWhenFiltersAreNull() {
Film film = createFilm("Один");
User anna = createUser("anna");
filmStorage.addLike(film.getId(), anna.getId());

assertEquals(1, filmStorage.findPopular(10, null, null).size());
}
}
Loading