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 @@ -8,6 +8,7 @@
import ru.yandex.practicum.filmorate.service.FilmService;

import java.util.Collection;
import java.util.List;

@RestController
@RequestMapping("/films")
Expand Down Expand Up @@ -84,4 +85,11 @@ public Collection<Film> getFilmsByDirector(@PathVariable long directorId,
log.info("GET /films/director/{}?sortBy={}", directorId, sortBy);
return filmService.findByDirectorSorted(directorId, sortBy);
}

@GetMapping("/search")
public Collection<Film> searchFilms(@RequestParam String query,
@RequestParam List<String> by) {
log.info("GET /films/search?query={}&by={}", query, by);
return filmService.searchFilms(query, by);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import java.time.LocalDate;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

@Service
Expand Down Expand Up @@ -195,4 +196,23 @@ public Collection<Film> findByDirectorSorted(long directorId, String sortBy) {
directorService.getDirectorById(directorId); // проверка существования режиссёра — иначе 404
return filmStorage.findFilmByDirector(directorId, sortBy);
}

private void validateSearchParameters(String query, List<String> by) {
if (query == null || query.isBlank()) {
throw new ValidationException("Параметр 'query' не может быть пустым");
}
if (by == null || by.isEmpty()) {
throw new ValidationException("Параметр 'by' не может быть пустым");
}
for (String field : by) {
if (field == null || !field.equalsIgnoreCase("title") && !field.equalsIgnoreCase("director")) {
throw new ValidationException("Недопустимое значение в параметре 'by': " + field);
}
}
}

public Collection<Film> searchFilms(String query, List<String> by) {
validateSearchParameters(query, by);
return filmStorage.searchFilms(query, by);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,14 @@ public class FilmDbStorage implements FilmStorage {
"WHERE fd.director_id = ? " +
"GROUP BY f.id, f.name, f.description, f.release_date, " +
"f.duration, f.mpa_id, m.name ";
private static final String FIND_FILMS_BY_SEARCH_QUERY =
"SELECT f.id, f.name, f.description, f.release_date, f.duration, " +
"f.mpa_id, m.name AS mpa_name " +
"FROM films f " +
"LEFT JOIN mpa m ON f.mpa_id = m.id " +
"LEFT JOIN film_director fd ON f.id = fd.film_id " +
"LEFT JOIN directors d ON fd.director_id = d.id " +
"LEFT JOIN film_likes fl ON f.id = fl.film_id ";

private final JdbcTemplate jdbc;
private final FilmRowMapper mapper;
Expand Down Expand Up @@ -238,6 +246,40 @@ public List<Film> findFilmByDirector(long directorId, String sortBy) {
return films;
}

@Override
public List<Film> searchFilms(String query, List<String> by) {
StringBuilder sql = new StringBuilder(FIND_FILMS_BY_SEARCH_QUERY);
List<String> conditions = new ArrayList<>();
List<Object> params = new ArrayList<>();

if (by.contains("director")) {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Здесь by.contains(...) учитывает регистр, а в validateSearchParameters проверка через equalsIgnoreCase. Из-за этого by=Title проходит валидацию, но тут не находит ни одного условия - и в ответ 400 «Необходимо указать поиск». Не блокирует, но правило лучше сделать одним в обоих местах.

conditions.add("LOWER(d.name) LIKE ?");
params.add("%" + query.toLowerCase() + "%");
}

if (by.contains("title")) {
conditions.add("LOWER(f.name) LIKE ?");
params.add("%" + query.toLowerCase() + "%");
}

if (conditions.isEmpty()) {
throw new ValidationException("Необходимо указать поиск: director или title");
}

sql.append("WHERE ")
.append(String.join(" OR ", conditions))
.append(" ");

sql.append("GROUP BY f.id, f.name, f.description, f.release_date, ")
.append("f.duration, f.mpa_id, m.name ")
.append("ORDER BY COUNT(DISTINCT fl.user_id) DESC, f.id");

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Классно, что COUNT(DISTINCT ...): из-за JOIN с режиссёрами у фильма с двумя режиссёрами каждый лайк иначе посчитался бы дважды 👍

List<Film> films = jdbc.query(sql.toString(), mapper, params.toArray());

loadDirectors(films);
loadGenresForFilms(films);
return films;
}

//Собирает запрос: чтение + WHERE по условиям + сортировка по числу лайков, без LIMIT
private String buildPopularQuery(List<String> conditions) {
StringBuilder sql = new StringBuilder(FIND_POPULAR_BASE_QUERY);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,6 @@ public interface FilmStorage {
boolean removeLike(long filmId, long userId);

List<Film> findFilmByDirector(long directorId, String sortBy);

List<Film> searchFilms(String query, List<String> by);
}
Original file line number Diff line number Diff line change
Expand Up @@ -348,4 +348,45 @@ void shouldReturnAllFilmsByDirectorSortedByYear() {
assertEquals(film3.getId(), allFilms.get(1).getId());
assertEquals(film2.getId(), allFilms.get(2).getId());
}

@Test
void searchFilmByTitle() {
Film film = newFilm("Интерстеллар");
film.setReleaseDate(LocalDate.of(2000, 1, 1));
film = filmStorage.add(film);
List<Film> foundFilms = filmStorage.searchFilms("инт", List.of("title"));
assertEquals(1, foundFilms.size());
assertEquals(film.getId(), foundFilms.get(0).getId());
assertEquals("Интерстеллар", foundFilms.get(0).getName());
}

@Test
void searchFilmByDirector() {
Director director = createDirector("Кристофер Нолан");
directorStorage.addDirector(director);
Film film = newFilm("Начало");
film.getDirectors().add(director);
film = filmStorage.add(film);
List<Film> foundFilms = filmStorage.searchFilms("нол", List.of("director"));
assertEquals(1, foundFilms.size());
assertEquals(film.getId(), foundFilms.get(0).getId());
assertEquals("Начало", foundFilms.get(0).getName());
}

@Test
void searchFilmByTitleAndDirector() {
Director director = createDirector("Кристофер Нолан");
directorStorage.addDirector(director);
Film film = newFilm("Начало");
film.getDirectors().add(director);
film = filmStorage.add(film);
Long filmId = film.getId();
Film film1 = newFilm("Нолан и его фильмы");
film1 = filmStorage.add(film1);
Long film1Id = film1.getId();
List<Film> foundFilms = filmStorage.searchFilms("нол", List.of("title", "director"));
assertEquals(2, foundFilms.size());
assertTrue(foundFilms.stream().anyMatch(f -> f.getId().equals(filmId)));
assertTrue(foundFilms.stream().anyMatch(f -> f.getId().equals(film1Id)));
}
}
Loading