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 1359e8a..a86e957 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.NotBlank; import jakarta.validation.constraints.Positive; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -13,6 +14,7 @@ import ru.yandex.practicum.filmorate.service.film.FilmService; import java.util.Collection; +import java.util.List; @RestController @RequestMapping("/films") @@ -69,6 +71,22 @@ public void deleteLike(@PathVariable long id, filmService.removeLike(id, userId); } + @GetMapping("/search") + public Collection searchFilms(@RequestParam @NotBlank String query, + @RequestParam(defaultValue = "title") String by) { + log.trace("Search films requested with query: {}, by: {}", query, by); + + List parsedInput = SearchFilmsBy.parseStr(by); + + for (SearchFilmsBy searchFilmsBy : parsedInput) { + if (searchFilmsBy == null) { + throw new IllegalArgumentException("Unknown search params %s".formatted(by)); + } + } + + return filmService.searchFilms(query, parsedInput); + } + @GetMapping("/common") public Collection findCommonFilms(@RequestParam(name = "userId") @Positive long userId, @RequestParam(name = "friendId") @Positive long friendId) { diff --git a/src/main/java/ru/yandex/practicum/filmorate/controller/SearchFilmsBy.java b/src/main/java/ru/yandex/practicum/filmorate/controller/SearchFilmsBy.java new file mode 100644 index 0000000..2e598f8 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/SearchFilmsBy.java @@ -0,0 +1,39 @@ +package ru.yandex.practicum.filmorate.controller; + +import lombok.Getter; + +import java.util.ArrayList; +import java.util.List; + +@Getter +public enum SearchFilmsBy { + TITLE("title"), + DIRECTOR("director"); + + private final String value; + + SearchFilmsBy(String value) { + this.value = value; + } + + public static List parseStr(String value) { + String[] splittedStr = value.trim().toLowerCase().split(","); + List parsedList = new ArrayList<>(); + + for (String str : splittedStr) { + SearchFilmsBy searchFilmsBy = SearchFilmsBy.fromString(str); + parsedList.add(searchFilmsBy); + } + + return parsedList; + } + + public static SearchFilmsBy fromString(String value) { + return switch (value) { + case "title" -> SearchFilmsBy.TITLE; + case "director" -> SearchFilmsBy.DIRECTOR; + default -> null; + }; + } + +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/repository/film/FilmRepository.java b/src/main/java/ru/yandex/practicum/filmorate/repository/film/FilmRepository.java index 46326de..159e292 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/repository/film/FilmRepository.java +++ b/src/main/java/ru/yandex/practicum/filmorate/repository/film/FilmRepository.java @@ -1,9 +1,11 @@ package ru.yandex.practicum.filmorate.repository.film; import ru.yandex.practicum.filmorate.controller.FilmsSortBy; +import ru.yandex.practicum.filmorate.controller.SearchFilmsBy; import ru.yandex.practicum.filmorate.model.Film; import java.util.Collection; +import java.util.List; import java.util.Optional; public interface FilmRepository { @@ -24,4 +26,6 @@ public interface FilmRepository { Collection findFilmsOfDirector(long directorId, FilmsSortBy sortFilmsBy); Collection findFilmRecommendations(long userId, long similarUserId); + + Collection searchFilms(String query, List searchBy); } diff --git a/src/main/java/ru/yandex/practicum/filmorate/repository/film/JdbcFilmRepository.java b/src/main/java/ru/yandex/practicum/filmorate/repository/film/JdbcFilmRepository.java index a150f85..31adb6a 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/repository/film/JdbcFilmRepository.java +++ b/src/main/java/ru/yandex/practicum/filmorate/repository/film/JdbcFilmRepository.java @@ -8,10 +8,12 @@ import org.springframework.jdbc.support.GeneratedKeyHolder; import org.springframework.stereotype.Repository; import ru.yandex.practicum.filmorate.controller.FilmsSortBy; +import ru.yandex.practicum.filmorate.controller.SearchFilmsBy; import ru.yandex.practicum.filmorate.model.Director; import ru.yandex.practicum.filmorate.model.Film; import ru.yandex.practicum.filmorate.model.Genre; +import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Optional; @@ -185,6 +187,37 @@ AND f.film_id NOT IN ( return jdbc.query(sqlRecommendations, params, filmRowMapper); } + @Override + public Collection searchFilms(String query, List searchBy) { + String searchParamName = "query"; + String searchFilmsSql = buildSearchSql(searchBy, searchParamName); + String searchPattern = "%%%s%%".formatted(query.toLowerCase()); + + MapSqlParameterSource params = new MapSqlParameterSource() + .addValue(searchParamName, searchPattern); + + return jdbc.query(searchFilmsSql, params, filmRowMapper); + } + + private String buildSearchSql(List searchFilmsBy, String searchParamName) { + List conditions = new ArrayList<>(); + + for (SearchFilmsBy searchBy : searchFilmsBy) { + switch (searchBy) { + case TITLE -> conditions.add("LOWER(f.name) LIKE :%s".formatted(searchParamName)); + case DIRECTOR -> conditions.add("LOWER(d.name) LIKE :%s".formatted(searchParamName)); + } + } + + String whereClause = String.join(" OR ", conditions); + + return BASE_SELECT_SQL.concat(""" + WHERE (%s) + GROUP BY f.film_id + ORDER BY (SELECT COUNT(*) FROM likes l WHERE l.film_id = f.film_id) DESC, f.film_id""".formatted( + whereClause)); + } + private void saveGenres(Collection genres, long filmId) { String insertGenresSql = """ INSERT INTO film_genres (film_id, genre_id) diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/film/FilmService.java b/src/main/java/ru/yandex/practicum/filmorate/service/film/FilmService.java index d2ad983..c175560 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/service/film/FilmService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/film/FilmService.java @@ -1,11 +1,13 @@ package ru.yandex.practicum.filmorate.service.film; import ru.yandex.practicum.filmorate.controller.FilmsSortBy; +import ru.yandex.practicum.filmorate.controller.SearchFilmsBy; import ru.yandex.practicum.filmorate.dto.film.FilmDto; import ru.yandex.practicum.filmorate.dto.film.NewFilmRequest; import ru.yandex.practicum.filmorate.dto.film.UpdateFilmRequest; import java.util.Collection; +import java.util.List; public interface FilmService { Collection findAll(); @@ -27,4 +29,6 @@ public interface FilmService { Collection findCommonFilms(long userId, long friendId); Collection findFilmsOfDirector(long directorId, FilmsSortBy sortFilmsBy); + + Collection searchFilms(String query, List searchFilmsBy); } diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/film/FilmServiceImpl.java b/src/main/java/ru/yandex/practicum/filmorate/service/film/FilmServiceImpl.java index c21a947..a1f17a0 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/service/film/FilmServiceImpl.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/film/FilmServiceImpl.java @@ -6,6 +6,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import ru.yandex.practicum.filmorate.controller.FilmsSortBy; +import ru.yandex.practicum.filmorate.controller.SearchFilmsBy; import ru.yandex.practicum.filmorate.dto.director.DirectorDto; import ru.yandex.practicum.filmorate.dto.film.FilmDto; import ru.yandex.practicum.filmorate.dto.film.NewFilmRequest; @@ -13,11 +14,7 @@ import ru.yandex.practicum.filmorate.dto.genre.GenreDto; import ru.yandex.practicum.filmorate.exception.NotFoundException; import ru.yandex.practicum.filmorate.mapper.FilmMapper; -import ru.yandex.practicum.filmorate.model.Director; -import ru.yandex.practicum.filmorate.model.EventType; -import ru.yandex.practicum.filmorate.model.Film; -import ru.yandex.practicum.filmorate.model.Genre; -import ru.yandex.practicum.filmorate.model.Operation; +import ru.yandex.practicum.filmorate.model.*; import ru.yandex.practicum.filmorate.repository.director.DirectorRepository; import ru.yandex.practicum.filmorate.repository.film.FilmRepository; import ru.yandex.practicum.filmorate.repository.genre.GenreRepository; @@ -183,6 +180,14 @@ public Collection findFilmsOfDirector(long directorId, FilmsSortBy sort .toList(); } + @Override + public Collection searchFilms(String query, List searchFilmsBy) { + return filmRepository.searchFilms(query, searchFilmsBy) + .stream() + .map(FilmMapper::toFilmDto) + .toList(); + } + private void throwIfDirectorNotFound(long directorId) { directorRepository.findById(directorId) .orElseThrow(NotFoundException.supplier("Director with id %d not found", directorId));