diff --git a/ChatGPT Image.png b/ChatGPT Image.png new file mode 100644 index 0000000..b4819ea Binary files /dev/null and b/ChatGPT Image.png differ diff --git a/README.md b/README.md index 7a8723d..2f081e5 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,19 @@ # java-filmorate -The Filmorate project — это приложение для работы с базой данных фильмов. + +![Static Badge](https://img.shields.io/badge/Java-21-green) +![Static Badge](https://img.shields.io/badge/Spring_Boot-3.5.9-green) +![Static Badge](https://img.shields.io/badge/JDBCTemplate-8A2BE2) +![Static Badge](https://img.shields.io/badge/Lombok-red) +![Static Badge](https://img.shields.io/badge/H2_database-blue) +![Static Badge](https://img.shields.io/badge/DockerFile-blue) +![Static Badge](https://img.shields.io/badge/JUnit-5-orange) +![Static Badge](https://img.shields.io/badge/Maven-orange) + +**The Filmorate project** — это бэкенд приложения для работы с базой данных фильмов. А также площадка для общения и взаимодействия пользователей. +![preview](ChatGPT%20Image.png) + ### Основные возможности - Хранение данных о фильмах - Хранение данных о пользователях @@ -45,15 +57,7 @@ The Filmorate project — это приложение для работы с б - и подобные ### Примеры пользовательских запросов (Request Examples) ![ExamplesOfRequests](ExamplesOfRequests.png) -### Архитектура и структура проекта (Architecture and Project Structure) -Проект использует Spring Boot версии 3.5.9 и включает в себя следующие основные зависимости: -- spring-boot-starter-web -- spring-boot-starter-test -- spring-boot-starter-validation -- spring-boot-starter-jdbc -- h2database - lombok -- logback-classic -- logbook-spring-boot-starter + ### Схема базы данных (Database map) ![DatabaseMap](FILMO_RATE_DATABASE_MAP.png "Database map:") ### Примеры запросов к базе данных: diff --git a/pom.xml b/pom.xml index 417da8b..943d219 100644 --- a/pom.xml +++ b/pom.xml @@ -20,8 +20,6 @@ 21 2.2.220 3.9.0 - - 3.3.1 3.11.0 3.3.1 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 2362097..206f30d 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java @@ -7,6 +7,7 @@ import ru.yandex.practicum.filmorate.dto.FilmDto; import ru.yandex.practicum.filmorate.dto.request.create.FilmCreateRequest; import ru.yandex.practicum.filmorate.dto.request.update.FilmUpdateRequest; +import ru.yandex.practicum.filmorate.model.Assessment; import ru.yandex.practicum.filmorate.service.FilmService; import ru.yandex.practicum.filmorate.service.util.LikeAction; @@ -37,8 +38,8 @@ public Collection getTop(@RequestParam(defaultValue = "10", required = } @GetMapping("/common") - public Collection getCommonLikedFilms(@RequestParam(required = true) Long userId, - @RequestParam(required = true) Long friendId) { + public Collection getCommonLikedFilms(@RequestParam Long userId, + @RequestParam Long friendId) { return filmService.getCommonLikedFilms(userId, friendId); } @@ -66,14 +67,18 @@ public FilmDto update(@RequestBody FilmUpdateRequest request) { return filmService.update(request); } - @PutMapping("/{filmId}/like/{userId}") - public void setLike(@PathVariable long filmId, @PathVariable long userId) { - filmService.changeLike(LikeAction.SET, filmId, userId); + @PutMapping({"/{filmId}/like/{userId}/{assessment}", "/{filmId}/like/{userId}"}) + public void setLike( + @PathVariable long filmId, + @PathVariable long userId, + @PathVariable(required = false) Integer assessment + ) { + filmService.changeLike(LikeAction.SET, filmId, userId, Assessment.of(assessment)); } @DeleteMapping("/{filmId}/like/{userId}") public void deleteLike(@PathVariable long filmId, @PathVariable long userId) { - filmService.changeLike(LikeAction.REMOVE, filmId, userId); + filmService.changeLike(LikeAction.REMOVE, filmId, userId, null); } @DeleteMapping("/{filmId}") diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/FilmStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/FilmStorage.java index 1fe9ca9..83dde16 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/FilmStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/FilmStorage.java @@ -1,9 +1,10 @@ package ru.yandex.practicum.filmorate.dal; +import ru.yandex.practicum.filmorate.model.Assessment; import ru.yandex.practicum.filmorate.model.Film; +import ru.yandex.practicum.filmorate.model.Like; import java.util.Collection; -import java.util.Map; import java.util.Optional; import java.util.Set; @@ -30,8 +31,8 @@ public interface FilmStorage { /// Получить топ фильмов по фильтрам Collection getTopByFilters(Integer count, Integer genreId, String year); - /// Поставить лайк - void setLike(long filmId, long userId); + /// Поставить лайк с оценкой (по умолчанию оценка 10) + void addLike(long filmId, long userId, Assessment assessment); /// Убрать лайк void removeLike(long filmId, long userId); @@ -39,8 +40,8 @@ public interface FilmStorage { /// Получить список фильмов по id режиссера Collection getALLFilmsOfDirector(Integer directorId); - /// Метод для получения матрицы лайков всех пользователей - Map> getAllLikes(); + /// Получить все лайки + Set getAllLikes(); /// Получить список филиьов, которые понравились обоим пользователям Collection getCommonLikedFilms(long userId, long friendId); @@ -51,5 +52,6 @@ public interface FilmStorage { /// Удаление фильма по id void removeFilm(long filmId); + /// Получить коллекцию фильмов по id фильмов Collection getFilmsByIds(Collection ids); } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/database/FilmDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/database/FilmDbStorage.java index eac5594..57b42e1 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/database/FilmDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/database/FilmDbStorage.java @@ -7,12 +7,17 @@ import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; import ru.yandex.practicum.filmorate.dal.FilmStorage; +import ru.yandex.practicum.filmorate.dal.rowmappers.LikeRowMapper; import ru.yandex.practicum.filmorate.exception.InternalServerException; +import ru.yandex.practicum.filmorate.model.Assessment; import ru.yandex.practicum.filmorate.model.Film; +import ru.yandex.practicum.filmorate.model.Like; import java.sql.SQLException; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Collectors; import static ru.yandex.practicum.filmorate.dal.database.sql.FilmQueryes.*; @@ -34,12 +39,13 @@ public Film createFilm(Film film) { film.getDescription(), film.getReleaseDate(), film.getDuration().toMinutes(), - film.getMpaId() + film.getMpaId(), + 0.0f ); film.setId(id); insertGenreIds(film); insertDirectorIds(film); - return findById(id).get(); + return film; } @Override @@ -56,15 +62,19 @@ public Film updateFilm(Film film) { ); insertGenreIds(film); insertDirectorIds(film); - return findById(film.getId()).get(); + return film; } @Override @Transactional public Optional findById(long filmId) { Optional filmOptional = findOneByIdInTable(filmId, "films"); - filmOptional.ifPresent(film -> film.setGenreIds(getGenreIdsByFilmId(filmId))); - filmOptional.ifPresent(film -> film.setDirectorIds(getDirectorIdsByFilmId(filmId))); + filmOptional.ifPresent(film -> { + film.setGenreIds(getGenreIdsByFilmId(filmId)); + film.setDirectorIds(getDirectorIdsByFilmId(filmId)); + film.setRate(calculateRating(getLikesByFilmId(filmId))); + }); + return filmOptional; } @@ -73,9 +83,18 @@ public Collection findAll() { return findManyFilms(SQL_FILMS_FIND_ALL); } + @Transactional @Override - public void setLike(long filmId, long userId) { - updateWithControl(SQL_FILMS_SET_LIKE, filmId, userId); + public void addLike(long filmId, long userId, Assessment assessment) { + jdbc.update( + SQL_FILMS_SET_LIKE, + filmId, + userId, + assessment.getValue() + ); + + float rate = calculateRating(getLikesByFilmId(filmId)); + jdbc.update("UPDATE films SET rate = ? WHERE id = ?", rate, filmId); } @Override @@ -95,16 +114,15 @@ public Collection getTop(int top) { @Override public Collection getTopByFilters(Integer top, Integer genreId, String year) { - Collection films; - if (genreId != null && year != null && !year.isEmpty()) { + Collection films = List.of(); + if (genreId != null && year != null && !year.isBlank()) { films = findManyByQuery(SQL_FILMS_FIND_TOP_BY_GENRE_AND_YEAR, Integer.parseInt(year), genreId, top); - } else if (year != null && !year.isEmpty()) { + } else if (year != null && !year.isBlank()) { films = findManyByQuery(SQL_FILMS_FIND_TOP_BY_YEAR, Integer.parseInt(year), top); } else if (genreId != null) { films = findManyByQuery(SQL_FILMS_FIND_TOP_BY_GENRES, genreId, top); - } else { - films = findManyByQuery(SQL_FILMS_FIND_TOP, top); } + return films.stream().peek(film -> { Set genres = getGenreIdsByFilmId(film.getId()); film.setGenreIds(new HashSet<>(genres)); @@ -118,18 +136,6 @@ public boolean checkFilmIsNotPresent(Long filmId) { return checkIdIsNotPresentInTable(filmId, "films"); } - @Override - public Map> getAllLikes() { - String sql = "SELECT user_id, film_id FROM likes"; - Map> allLikes = new HashMap<>(); - jdbc.query(sql, (rs) -> { - long userId = rs.getLong("user_id"); - long filmId = rs.getLong("film_id"); - allLikes.computeIfAbsent(userId, k -> new HashSet<>()).add(filmId); - }); - return allLikes; - } - @Override public Collection getCommonLikedFilms(long userId, long friendId) { return findManyFilms(SQL_FILMS_FIND_COMMON_LIKED, userId, friendId); @@ -156,6 +162,41 @@ public void removeFilm(long filmId) { updateWithControl("DELETE FROM films WHERE id = ?", filmId); } + @Override + public Set getAllLikes() { + String sql = "SELECT user_id, film_id, assessment FROM likes"; + Set allLikes = new HashSet<>(); + jdbc.query(sql, (rs) -> { + long filmId = rs.getLong("film_id"); + long userId = rs.getLong("user_id"); + int assessment = rs.getInt("assessment"); + Like like = Like.builder() + .filmId(filmId) + .userId(userId) + .assessment(Assessment.of(assessment)) + .build(); + allLikes.add(like); + }); + return allLikes; + } + + @Override + public Collection getFilmsByIds(Collection ids) { + if (ids == null || ids.isEmpty()) { + return Collections.emptyList(); + } + + String inSql = String.join(",", Collections.nCopies(ids.size(), "?")); + String sql = String.format(SQL_FILMS_GET_BY_IDS, inSql); + + return findManyFilms(sql, ids.toArray()); + } + + private Set getLikesByFilmId(long filmId) { + return jdbc.query(SQL_FILMS_FIND_LIKES_BY_FILM_ID, new LikeRowMapper(), filmId).stream() + .collect(Collectors.toUnmodifiableSet()); + } + private void insertGenreIds(Film film) { jdbc.update("DELETE FROM genres_of_films WHERE film_id = ?", film.getId()); Set genreIds = film.getGenreIds(); @@ -187,8 +228,6 @@ private Collection findManyFilms(String query, Object... params) { throw new InternalServerException("Не удалось получить все фильмы из базы.\n" + e.getMessage()); } }); - long userId = rs.getLong("user_id"); - if (!rs.wasNull() && film != null) film.addLike(userId); int genreId = rs.getInt("genre_id"); if (!rs.wasNull() && film != null) film.addGenreId(genreId); int directorId = rs.getInt("director_id"); @@ -206,24 +245,23 @@ private Set getDirectorIdsByFilmId(long filmId) { return findColumnByQuery(SQL_FILMS_FIND_DIRECTORIDS_BY_FILM_ID, Integer.class, filmId); } - @Override - public Collection getFilmsByIds(Collection ids) { - if (ids == null || ids.isEmpty()) { - return Collections.emptyList(); + private float calculateRating(Set likes) { + if (likes.isEmpty()) { + return 0f; + } else if (likes.size() == 1) { + return likes.iterator().next().getAssessment().getValue(); } + AtomicLong count = new AtomicLong(); + AtomicLong sum = new AtomicLong(); + likes.stream() + .map(Like::getAssessment) + .filter(assessment -> !assessment.equals(Assessment.UNDEFINED)) + .map(Assessment::getValue).forEach(value -> { + sum.addAndGet(value); + count.getAndIncrement(); + }); - String inSql = String.join(",", Collections.nCopies(ids.size(), "?")); - - String sql = String.format(""" - SELECT f.id, f.film_name, f.description, f.release_date, f.duration, f.mpa_id, - l.user_id, gof.genre_id, dof.director_id - FROM films f - LEFT JOIN likes l ON f.id = l.film_id - LEFT JOIN genres_of_films gof ON f.id = gof.film_id - LEFT JOIN directors_of_films dof ON f.id = dof.film_id - WHERE f.id IN (%s) - """, inSql); - - return findManyFilms(sql, ids.toArray()); + float average = (float) sum.get() / count.get(); + return Math.round(average * 100) / 100.0f; } } \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/database/sql/FilmQueryes.java b/src/main/java/ru/yandex/practicum/filmorate/dal/database/sql/FilmQueryes.java index 6b21bf1..9a04a0e 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/database/sql/FilmQueryes.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/database/sql/FilmQueryes.java @@ -2,6 +2,22 @@ public class FilmQueryes { + public static final String SQL_FILMS_GET_BY_IDS = """ + SELECT f.id, + f.film_name, + f.description, + f.release_date, + f.duration, + f.mpa_id, + f.rate, + gof.genre_id, + dof.director_id + FROM films f + LEFT JOIN genres_of_films gof ON f.id = gof.film_id + LEFT JOIN directors_of_films dof ON f.id = dof.film_id + WHERE f.id IN (%s) + """; + public static final String SQL_FILMS_FIND_ALL = """ SELECT mrg.id, mrg.FILM_NAME, @@ -9,23 +25,12 @@ public class FilmQueryes { mrg.RELEASE_DATE, mrg.DURATION, mrg.MPA_ID, - gof.GENRE_ID, - dof.DIRECTOR_ID, - user_id - FROM ( - SELECT f.ID, - f.FILM_NAME, - DESCRIPTION, - RELEASE_DATE, - DURATION, - MPA_ID, - l.USER_ID - FROM FILMS f - LEFT JOIN LIKES l ON f.ID = l.FILM_ID - ORDER BY f.ID - ) AS mrg + mrg.RATE, + dof.DIRECTOR_ID, + gof.GENRE_ID + FROM FILMS mrg LEFT JOIN GENRES_OF_FILMS gof ON mrg.id = gof.FILM_ID - LEFT JOIN DIRECTORS_OF_FILMS dof ON mrg.ID = dof.FILM_ID + LEFT JOIN DIRECTORS_OF_FILMS dof ON mrg.ID = dof.FILM_ID; """; public static final String SQL_FILMS_FIND_ALL_OF_DIRECTOR = """ @@ -35,6 +40,7 @@ public class FilmQueryes { mrg.RELEASE_DATE, mrg.DURATION, mrg.MPA_ID, + mrg.RATE, gof.GENRE_ID, dof.DIRECTOR_ID, user_id @@ -45,6 +51,7 @@ public class FilmQueryes { RELEASE_DATE, DURATION, MPA_ID, + f.RATE, l.USER_ID FROM FILMS f LEFT JOIN LIKES l ON f.ID = l.FILM_ID @@ -64,45 +71,28 @@ WHERE f1.ID IN ( """; public static final String SQL_FILMS_FIND_TOP = """ - SELECT mrg.id, + SELECT mrg.ID, mrg.FILM_NAME, mrg.DESCRIPTION, mrg.RELEASE_DATE, mrg.DURATION, mrg.MPA_ID, - gof.GENRE_ID, - dof.DIRECTOR_ID, - user_id + mrg.RATE, + dof.DIRECTOR_ID, + gof.GENRE_ID FROM ( - SELECT f.ID, - f.FILM_NAME, - DESCRIPTION, - RELEASE_DATE, - DURATION, - MPA_ID, - l.USER_ID - FROM FILMS f - LEFT JOIN LIKES l ON f.ID = l.FILM_ID + SELECT * + FROM FILMS flm + ORDER BY RATE DESC + LIMIT ? ) AS mrg LEFT JOIN GENRES_OF_FILMS gof ON mrg.id = gof.FILM_ID - LEFT JOIN DIRECTORS_OF_FILMS dof ON mrg.ID = dof.FILM_ID - WHERE mrg.id IN ( - SELECT rte.ID - FROM ( - SELECT flm.*, - COUNT(lks.ID) AS rate - FROM FILMS flm - LEFT JOIN LIKES lks ON flm.ID = lks.FILM_ID - GROUP BY flm.ID - ) AS rte - ORDER BY rate DESC, rte.ID - LIMIT ? - ) + LEFT JOIN DIRECTORS_OF_FILMS dof ON mrg.ID = dof.FILM_ID; """; public static final String SQL_FILMS_SET_LIKE = """ - INSERT INTO LIKES (FILM_ID, USER_ID) - VALUES (?, ?) + INSERT INTO LIKES (FILM_ID, USER_ID, ASSESSMENT) + VALUES (?, ?, ?) """; public static final String SQL_FILMS_DELETE_LIKE = """ @@ -129,6 +119,12 @@ MERGE INTO GENRES_OF_FILMS (FILM_ID, GENRE_ID) KEY (FILM_ID, GENRE_ID) VALUES WHERE dof.FILM_ID = ? """; + public static final String SQL_FILMS_FIND_LIKES_BY_FILM_ID = """ + SELECT * + FROM LIKES + WHERE FILM_ID = ? + """; + public static final String SQL_FILMS_INSERT_DIRECTORIDS = """ MERGE INTO DIRECTORS_OF_FILMS (FILM_ID, DIRECTOR_ID) KEY (FILM_ID, DIRECTOR_ID) VALUES """; @@ -139,9 +135,10 @@ INSERT INTO films ( description, release_date, duration, - mpa_id + mpa_id, + rate ) - VALUES (?, ?, ?, ?, ?) + VALUES (?, ?, ?, ?, ?, ?) """; public static final String SQL_FILMS_UPDATE = """ @@ -155,34 +152,92 @@ INSERT INTO films ( """; public static final String SQL_FILMS_FIND_TOP_BY_GENRES = """ - SELECT f.*, COUNT(fl.user_id) as likes_count - FROM films f - LEFT JOIN likes fl ON f.id = fl.film_id - LEFT JOIN genres_of_films gr ON f.id = gr.film_id - WHERE gr.genre_id = ? - GROUP BY f.id - ORDER BY likes_count DESC - LIMIT ? + SELECT mrg.ID, + mrg.FILM_NAME, + mrg.DESCRIPTION, + mrg.RELEASE_DATE, + mrg.DURATION, + mrg.MPA_ID, + mrg.RATE, + dof.DIRECTOR_ID, + gof.GENRE_ID + FROM ( + SELECT * + FROM ( + SELECT * + FROM films flm + WHERE flm.id IN ( + SELECT f.id + FROM films f + LEFT JOIN GENRES_OF_FILMS g ON f.ID = g.FILM_ID + WHERE g.GENRE_ID = ? + ) + ) + ORDER BY RATE DESC + LIMIT ? + ) AS mrg + LEFT JOIN GENRES_OF_FILMS gof ON mrg.id = gof.FILM_ID + LEFT JOIN DIRECTORS_OF_FILMS dof ON mrg.id = dof.FILM_ID; """; public static final String SQL_FILMS_FIND_TOP_BY_YEAR = """ - SELECT f.*, COUNT(fl.user_id) as likes_count - FROM (SELECT * FROM films WHERE EXTRACT(YEAR FROM release_date) = ?) f - LEFT JOIN likes fl ON f.id = fl.film_id - GROUP BY f.id - ORDER BY likes_count DESC - LIMIT ? + SELECT mrg.ID, + mrg.FILM_NAME, + mrg.DESCRIPTION, + mrg.RELEASE_DATE, + mrg.DURATION, + mrg.MPA_ID, + mrg.RATE, + dof.DIRECTOR_ID, + gof.GENRE_ID + FROM ( + SELECT * + FROM ( + SELECT * + FROM films + WHERE EXTRACT( + YEAR + FROM release_date + ) = ? + ) + ORDER BY RATE DESC + LIMIT ? + ) AS mrg + LEFT JOIN GENRES_OF_FILMS gof ON mrg.id = gof.FILM_ID + LEFT JOIN DIRECTORS_OF_FILMS dof ON mrg.id = dof.FILM_ID; """; public static final String SQL_FILMS_FIND_TOP_BY_GENRE_AND_YEAR = """ - SELECT f.*, COUNT(fl.user_id) as likes_count - FROM (SELECT * FROM films WHERE EXTRACT(YEAR FROM release_date) = ?) f - LEFT JOIN likes fl ON f.id = fl.film_id - LEFT JOIN genres_of_films gr ON f.id = gr.film_id - WHERE gr.genre_id = ? - GROUP BY f.id - ORDER BY likes_count DESC - LIMIT ? + SELECT mrg.ID, + mrg.FILM_NAME, + mrg.DESCRIPTION, + mrg.RELEASE_DATE, + mrg.DURATION, + mrg.MPA_ID, + mrg.RATE, + dof.DIRECTOR_ID, + gof.GENRE_ID + FROM ( + SELECT * + FROM ( + SELECT * + FROM films flm + WHERE EXTRACT( + YEAR + FROM flm.release_date + ) = ? + AND flm.id IN ( + SELECT f.id + FROM films f + LEFT JOIN GENRES_OF_FILMS g ON f.ID = g.FILM_ID + WHERE g.GENRE_ID = ? + ) + ) + ORDER BY RATE DESC + LIMIT ? + ) AS mrg + LEFT JOIN GENRES_OF_FILMS gof ON mrg.id = gof.FILM_ID + LEFT JOIN DIRECTORS_OF_FILMS dof ON mrg.id = dof.FILM_ID; """; public static final String SQL_FILMS_FIND_COMMON_LIKED = """ @@ -192,6 +247,7 @@ INSERT INTO films ( f.release_date, f.duration, f.mpa_id, + f.rate, l_all.user_id, g.genre_id, d.director_id, @@ -216,16 +272,15 @@ WHERE f.id IN ( f.release_date, f.duration, f.mpa_id, + f.rate, m.mpa_name, gof.genre_id, - dof.director_id, - l.user_id + dof.director_id FROM films f LEFT JOIN mpa m ON f.mpa_id = m.id LEFT JOIN genres_of_films gof ON f.id = gof.film_id LEFT JOIN directors_of_films dof ON f.id = dof.film_id LEFT JOIN directors d ON dof.director_id = d.id - LEFT JOIN likes l ON f.id = l.film_id WHERE %s ORDER BY (SELECT COUNT(*) FROM likes WHERE film_id = f.id) DESC, f.id ASC; """; diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/inmemory/InMemoryFilmStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dal/inmemory/InMemoryFilmStorage.java index bdbb0ed..df1784f 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/inmemory/InMemoryFilmStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/inmemory/InMemoryFilmStorage.java @@ -3,10 +3,11 @@ import org.springframework.stereotype.Component; import ru.yandex.practicum.filmorate.dal.FilmStorage; import ru.yandex.practicum.filmorate.exception.MethodNotImplementedException; +import ru.yandex.practicum.filmorate.model.Assessment; import ru.yandex.practicum.filmorate.model.Film; +import ru.yandex.practicum.filmorate.model.Like; import java.util.Collection; -import java.util.Map; import java.util.Optional; import java.util.Set; @@ -49,7 +50,7 @@ public Collection getTopByFilters(Integer count, Integer genreId, String y } @Override - public void setLike(long filmId, long userId) { + public void addLike(long filmId, long userId, Assessment assessment) { throw new MethodNotImplementedException(); } @@ -64,7 +65,7 @@ public Collection getALLFilmsOfDirector(Integer directorId) { } @Override - public Map> getAllLikes() { + public Set getAllLikes() { throw new MethodNotImplementedException(); } @@ -87,4 +88,5 @@ public void removeFilm(long filmId) { public Collection getFilmsByIds(Collection ids) { throw new MethodNotImplementedException(); } + } \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/rowmappers/EventRowMapper.java b/src/main/java/ru/yandex/practicum/filmorate/dal/rowmappers/EventRowMapper.java index 679de25..18165e8 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/rowmappers/EventRowMapper.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/rowmappers/EventRowMapper.java @@ -4,8 +4,8 @@ import org.springframework.jdbc.core.RowMapper; import org.springframework.stereotype.Component; import ru.yandex.practicum.filmorate.model.Event; -import ru.yandex.practicum.filmorate.model.EventType; -import ru.yandex.practicum.filmorate.model.Operation; +import ru.yandex.practicum.filmorate.service.util.EventType; +import ru.yandex.practicum.filmorate.service.util.Operation; import java.sql.ResultSet; import java.sql.SQLException; diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/rowmappers/FilmRowMapper.java b/src/main/java/ru/yandex/practicum/filmorate/dal/rowmappers/FilmRowMapper.java index 13224bb..bbc2881 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/rowmappers/FilmRowMapper.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/rowmappers/FilmRowMapper.java @@ -21,7 +21,7 @@ public Film mapRow(ResultSet resultSet, int rowNum) throws SQLException { .releaseDate(resultSet.getDate("release_date").toLocalDate()) .duration(Duration.ofMinutes(resultSet.getInt("duration"))) .mpaId(resultSet.getInt("mpa_id")) + .rate(resultSet.getFloat("rate")) .build(); } - } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/rowmappers/LikeRowMapper.java b/src/main/java/ru/yandex/practicum/filmorate/dal/rowmappers/LikeRowMapper.java new file mode 100644 index 0000000..9559054 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/rowmappers/LikeRowMapper.java @@ -0,0 +1,20 @@ +package ru.yandex.practicum.filmorate.dal.rowmappers; + +import org.springframework.jdbc.core.RowMapper; +import ru.yandex.practicum.filmorate.model.Assessment; +import ru.yandex.practicum.filmorate.model.Like; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class LikeRowMapper implements RowMapper { + @Override + public Like mapRow(ResultSet resultSet, int rowNum) throws SQLException { + return Like.builder() + .id(resultSet.getLong("id")) + .filmId(resultSet.getLong("film_id")) + .userId(resultSet.getLong("user_id")) + .assessment(Assessment.of(resultSet.getInt("assessment"))) + .build(); + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/dto/EventDto.java b/src/main/java/ru/yandex/practicum/filmorate/dto/EventDto.java index 6f66634..11f005c 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dto/EventDto.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dto/EventDto.java @@ -3,8 +3,8 @@ import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Builder; import lombok.Data; -import ru.yandex.practicum.filmorate.model.EventType; -import ru.yandex.practicum.filmorate.model.Operation; +import ru.yandex.practicum.filmorate.service.util.EventType; +import ru.yandex.practicum.filmorate.service.util.Operation; @Data @Builder diff --git a/src/main/java/ru/yandex/practicum/filmorate/dto/FilmDto.java b/src/main/java/ru/yandex/practicum/filmorate/dto/FilmDto.java index ba35e8f..82583f2 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dto/FilmDto.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dto/FilmDto.java @@ -20,9 +20,7 @@ public class FilmDto { private String description; private LocalDate releaseDate; private Duration duration; - - @JsonProperty("rate") - int likesCount; + float rate; @Builder.Default private MpaDto mpa = new MpaDto(); diff --git a/src/main/java/ru/yandex/practicum/filmorate/mapper/FilmMapper.java b/src/main/java/ru/yandex/practicum/filmorate/mapper/FilmMapper.java index 056a560..cc6c2f9 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/mapper/FilmMapper.java +++ b/src/main/java/ru/yandex/practicum/filmorate/mapper/FilmMapper.java @@ -43,7 +43,6 @@ public static FilmDto mapToFilmDto(Film film, Mpa mpa, Set genres, Set genres, Set new DirectorDto(director.getId(), director.getName())) .toList() ) + .rate(film.getRate()) .build(); } @@ -87,6 +87,8 @@ public static Film updateFilmFields(Film film, FilmUpdateRequest request) { .map(Director::getId) .collect(Collectors.toSet())); + film.getRate(); + return film; } diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/Assessment.java b/src/main/java/ru/yandex/practicum/filmorate/model/Assessment.java new file mode 100644 index 0000000..419b9a9 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/model/Assessment.java @@ -0,0 +1,35 @@ +package ru.yandex.practicum.filmorate.model; + +import lombok.Getter; + +@Getter +public enum Assessment { + UNDEFINED, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN; + + private int value; + + Assessment() { + this.value = ordinal(); + } + + public static Assessment of(Integer value) { + if (value == null) { + value = 10; + } + + Assessment result = Assessment.UNDEFINED; + if (value < 1 || value > 10) { + result.setValue(value); + return result; + } + + result = values()[value]; + result.setValue(result.ordinal()); + return result; + } + + private void setValue(int value) { + this.value = value; + } +} + diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/Event.java b/src/main/java/ru/yandex/practicum/filmorate/model/Event.java index a033a30..c3c5265 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/model/Event.java +++ b/src/main/java/ru/yandex/practicum/filmorate/model/Event.java @@ -4,6 +4,8 @@ import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +import ru.yandex.practicum.filmorate.service.util.EventType; +import ru.yandex.practicum.filmorate.service.util.Operation; import java.time.Instant; diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/EventType.java b/src/main/java/ru/yandex/practicum/filmorate/model/EventType.java deleted file mode 100644 index e6cfb0b..0000000 --- a/src/main/java/ru/yandex/practicum/filmorate/model/EventType.java +++ /dev/null @@ -1,7 +0,0 @@ -package ru.yandex.practicum.filmorate.model; - -public enum EventType { - LIKE, - REVIEW, - FRIEND -} \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/Film.java b/src/main/java/ru/yandex/practicum/filmorate/model/Film.java index a16f4e6..429d01d 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/model/Film.java +++ b/src/main/java/ru/yandex/practicum/filmorate/model/Film.java @@ -1,6 +1,5 @@ package ru.yandex.practicum.filmorate.model; -import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Builder; import lombok.Data; import lombok.EqualsAndHashCode; @@ -25,33 +24,16 @@ public class Film { @Builder.Default private Duration duration = Duration.ZERO; - @Builder.Default - private Set genreIds = new HashSet<>(); - private Integer mpaId; @Builder.Default - private Set directorIds = new HashSet<>(); + private Float rate = 0.0f; @Builder.Default - private Set likes = new HashSet<>(); + private Set genreIds = new HashSet<>(); - @JsonProperty(value = "rate") @Builder.Default - private int likesCount = 0; - - public int getLikesCount() { - likesCount = likes.size(); - return likesCount; - } - - public void setLike(User user) { - likes.add(user.getId()); - } - - public void removeLike(User user) { - likes.remove(user.getId()); - } + private Set directorIds = new HashSet<>(); public void addGenreId(int genreId) { genreIds.add(genreId); @@ -60,8 +42,4 @@ public void addGenreId(int genreId) { public void addDirectorId(int directorId) { directorIds.add(directorId); } - - public void addLike(long userId) { - likes.add(userId); - } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/Like.java b/src/main/java/ru/yandex/practicum/filmorate/model/Like.java new file mode 100644 index 0000000..4b17652 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/model/Like.java @@ -0,0 +1,13 @@ +package ru.yandex.practicum.filmorate.model; + +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class Like { + Long id; + long filmId; + long userId; + Assessment assessment; +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/Operation.java b/src/main/java/ru/yandex/practicum/filmorate/model/Operation.java deleted file mode 100644 index c0bc9bc..0000000 --- a/src/main/java/ru/yandex/practicum/filmorate/model/Operation.java +++ /dev/null @@ -1,7 +0,0 @@ -package ru.yandex.practicum.filmorate.model; - -public enum Operation { - REMOVE, - ADD, - UPDATE -} \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/EventService.java b/src/main/java/ru/yandex/practicum/filmorate/service/EventService.java index b50e86b..0156ac7 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/service/EventService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/EventService.java @@ -8,8 +8,8 @@ import ru.yandex.practicum.filmorate.dto.EventDto; import ru.yandex.practicum.filmorate.mapper.EventMapper; import ru.yandex.practicum.filmorate.model.Event; -import ru.yandex.practicum.filmorate.model.EventType; -import ru.yandex.practicum.filmorate.model.Operation; +import ru.yandex.practicum.filmorate.service.util.EventType; +import ru.yandex.practicum.filmorate.service.util.Operation; import java.time.Instant; import java.util.Collection; 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 569e0c5..f198fac 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/service/FilmService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/FilmService.java @@ -15,8 +15,10 @@ import ru.yandex.practicum.filmorate.exception.ValidationException; import ru.yandex.practicum.filmorate.mapper.FilmMapper; import ru.yandex.practicum.filmorate.model.*; +import ru.yandex.practicum.filmorate.service.util.EventType; import ru.yandex.practicum.filmorate.service.util.FilmSortingAction; import ru.yandex.practicum.filmorate.service.util.LikeAction; +import ru.yandex.practicum.filmorate.service.util.Operation; import java.util.*; @@ -82,7 +84,7 @@ public Collection getSortedFilmsOfDirector(Integer directorId, String s log.trace("Применение сортировки"); Comparator comparator = switch (sortingAction) { case YEAR -> Comparator.comparing(Film::getReleaseDate); - case LIKES -> Comparator.comparing(Film::getLikesCount).reversed(); + case RATE -> Comparator.comparing(Film::getRate).reversed(); case UNDEFINED -> throw new ParameterNotValidException( sortingAction.getSortingName(), "не подходит. Допустимые значения: " + @@ -114,23 +116,22 @@ private Collection getSortedFilmDtosOfDirector(Integer directorId) { return films; } - public void changeLike(LikeAction action, Long filmId, Long userId) { + public void changeLike(LikeAction action, Long filmId, Long userId, Assessment assessment) { switch (action) { case SET -> log.info("Добавление лайка пользователя id={} к фильму id={}.", userId, filmId); case REMOVE -> log.info("Удаление лайка пользователя id={} у фильма id={}.", userId, filmId); } - if (filmStorage.checkFilmIsNotPresent(filmId)) { - throw new NotFoundException("Фильм с id=" + filmId + " не найден."); + if (assessment != null && assessment.equals(Assessment.UNDEFINED)) { + throw new ParameterNotValidException("assessment", "может быть только от 1 до 10, либо отсутствовать."); } - if (userStorage.checkUserIsNotPresent(userId)) { - throw new NotFoundException("Пользователь с id=" + userId + " не найден."); - } + easyCheckFilm(filmId); + easyCheckUser(userId); switch (action) { case SET -> { - filmStorage.setLike(filmId, userId); + filmStorage.addLike(filmId, userId, assessment); eventService.addUserEvent(userId, EventType.LIKE, Operation.ADD, filmId); } case REMOVE -> { @@ -150,7 +151,7 @@ public Collection getTopFilms(Integer count, Integer genreId, String ye } Collection films; - if (genreId != null || year != null) { + if (genreId != null || (year != null && !year.isBlank())) { films = filmStorage.getTopByFilters(count, genreId, year); } else { films = filmStorage.getTop(count); @@ -163,7 +164,7 @@ public Collection getTopFilms(Integer count, Integer genreId, String ye getGenresByIds(film.getGenreIds()), getDirectorsByIds(film.getDirectorIds()) ) - ).sorted(Comparator.comparing(FilmDto::getLikesCount).reversed()).toList(); + ).sorted(Comparator.comparing(FilmDto::getRate).reversed()).toList(); } public Collection findAll() { @@ -368,4 +369,16 @@ public Collection getFilmsByIds(Collection ids) { )) .toList(); } + + private void easyCheckFilm(long filmId) { + if (filmStorage.checkFilmIsNotPresent(filmId)) { + throw new NotFoundException("Фильм с id=" + filmId + " не найден."); + } + } + + private void easyCheckUser(long userId) { + if (userStorage.checkUserIsNotPresent(userId)) { + throw new NotFoundException("Пользователь с id=" + userId + " не найден."); + } + } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/ReviewService.java b/src/main/java/ru/yandex/practicum/filmorate/service/ReviewService.java index 2dd323c..201f02a 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/service/ReviewService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/ReviewService.java @@ -12,8 +12,8 @@ import ru.yandex.practicum.filmorate.dto.request.update.ReviewUpdateRequest; import ru.yandex.practicum.filmorate.exception.NotFoundException; import ru.yandex.practicum.filmorate.mapper.ReviewMapper; -import ru.yandex.practicum.filmorate.model.EventType; -import ru.yandex.practicum.filmorate.model.Operation; +import ru.yandex.practicum.filmorate.service.util.EventType; +import ru.yandex.practicum.filmorate.service.util.Operation; import ru.yandex.practicum.filmorate.model.Review; import java.util.Collection; diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/UserService.java b/src/main/java/ru/yandex/practicum/filmorate/service/UserService.java index aa2485f..f6e77f1 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/service/UserService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/UserService.java @@ -14,8 +14,9 @@ import ru.yandex.practicum.filmorate.dto.request.update.UserUpdateRequest; import ru.yandex.practicum.filmorate.exception.*; import ru.yandex.practicum.filmorate.mapper.UserMapper; -import ru.yandex.practicum.filmorate.model.EventType; -import ru.yandex.practicum.filmorate.model.Operation; +import ru.yandex.practicum.filmorate.model.Like; +import ru.yandex.practicum.filmorate.service.util.EventType; +import ru.yandex.practicum.filmorate.service.util.Operation; import ru.yandex.practicum.filmorate.model.User; import ru.yandex.practicum.filmorate.service.util.FriendsAction; @@ -210,8 +211,14 @@ public Collection getRecommendations(long userId) { log.info("Получение рекомендаций для пользователя id={}.", userId); easyCheckUser(userId); - Map> allLikes = filmStorage.getAllLikes(); - Set targetUserLikes = allLikes.getOrDefault(userId, Collections.emptySet()); + Set likes = filmStorage.getAllLikes(); + + Map> userTofilmsLikes = new HashMap<>(); + likes.forEach(like -> + userTofilmsLikes.computeIfAbsent(like.getUserId(), k -> new HashSet<>()).add(like.getFilmId()) + ); + + Set targetUserLikes = userTofilmsLikes.getOrDefault(userId, Collections.emptySet()); if (targetUserLikes.isEmpty()) { return Collections.emptyList(); @@ -219,7 +226,7 @@ public Collection getRecommendations(long userId) { Map similarityMap = new HashMap<>(); - for (Map.Entry> entry : allLikes.entrySet()) { + for (Map.Entry> entry : userTofilmsLikes.entrySet()) { long otherUserId = entry.getKey(); if (otherUserId == userId) continue; @@ -245,7 +252,7 @@ public Collection getRecommendations(long userId) { Set recommendedFilmIds = new HashSet<>(); for (Long simId : similarUserIds) { - Set likedBySimilarUser = allLikes.get(simId); + Set likedBySimilarUser = userTofilmsLikes.get(simId); likedBySimilarUser.stream() .filter(filmId -> !targetUserLikes.contains(filmId)) .forEach(recommendedFilmIds::add); @@ -255,6 +262,8 @@ public Collection getRecommendations(long userId) { return Collections.emptyList(); } - return filmService.getFilmsByIds(recommendedFilmIds); + return filmService.getFilmsByIds(recommendedFilmIds).stream() + .filter(filmDto -> filmDto.getRate() > 5.0f) + .toList(); } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/util/EventType.java b/src/main/java/ru/yandex/practicum/filmorate/service/util/EventType.java new file mode 100644 index 0000000..78f7013 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/service/util/EventType.java @@ -0,0 +1,7 @@ +package ru.yandex.practicum.filmorate.service.util; + +public enum EventType { + LIKE, + REVIEW, + FRIEND +} \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/util/FilmSortingAction.java b/src/main/java/ru/yandex/practicum/filmorate/service/util/FilmSortingAction.java index 4803a27..0157cbb 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/service/util/FilmSortingAction.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/util/FilmSortingAction.java @@ -6,7 +6,7 @@ import java.util.List; public enum FilmSortingAction { - YEAR, LIKES, UNDEFINED; + UNDEFINED, YEAR, RATE; @Getter private String sortingName; diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/util/LikeAction.java b/src/main/java/ru/yandex/practicum/filmorate/service/util/LikeAction.java index dd910e2..76fd67e 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/service/util/LikeAction.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/util/LikeAction.java @@ -1,5 +1,5 @@ package ru.yandex.practicum.filmorate.service.util; public enum LikeAction { - SET, REMOVE + SET, RATE, REMOVE } diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/util/Operation.java b/src/main/java/ru/yandex/practicum/filmorate/service/util/Operation.java new file mode 100644 index 0000000..30df4e0 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/service/util/Operation.java @@ -0,0 +1,7 @@ +package ru.yandex.practicum.filmorate.service.util; + +public enum Operation { + REMOVE, + ADD, + UPDATE +} \ No newline at end of file diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index ac774a1..81afd31 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -20,7 +20,8 @@ CREATE TABLE films ( description varchar(200) NOT NULL, release_date date NOT NULL, duration int NOT NULL, - mpa_id int NOT NULL + mpa_id int NOT NULL, + rate real NOT NULL ); CREATE TABLE genres ( @@ -52,8 +53,9 @@ CREATE TABLE mpa ( CREATE TABLE likes ( id bigint NOT NULL AUTO_INCREMENT PRIMARY KEY, + film_id bigint NOT NULL, user_id bigint NOT NULL, - film_id bigint NOT NULL + assessment int NOT NULL ); CREATE TABLE reviews ( @@ -132,6 +134,8 @@ CREATE INDEX IF NOT EXISTS idx_friends_friend_id ON friends (friend_id); CREATE INDEX IF NOT EXISTS idx_films_film_name ON films (film_name); +CREATE UNIQUE INDEX IF NOT EXISTS idx_likes_film_id_user_id ON likes (film_id, user_id); + CREATE INDEX IF NOT EXISTS idx_reviews_film_id ON reviews (film_id); CREATE INDEX IF NOT EXISTS idx_reviews_user_id ON reviews (user_id); CREATE UNIQUE INDEX IF NOT EXISTS idx_directors_name ON directors (director_name); diff --git a/src/test/java/ru/yandex/practicum/filmorate/FilmoRateApplicationTests.java b/src/test/java/ru/yandex/practicum/filmorate/FilmoRateApplicationTests.java index 46e71d6..38ab019 100644 --- a/src/test/java/ru/yandex/practicum/filmorate/FilmoRateApplicationTests.java +++ b/src/test/java/ru/yandex/practicum/filmorate/FilmoRateApplicationTests.java @@ -1,5 +1,6 @@ package ru.yandex.practicum.filmorate; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -7,21 +8,12 @@ import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest; import org.springframework.context.annotation.Import; -import ru.yandex.practicum.filmorate.dal.database.FilmDbStorage; -import ru.yandex.practicum.filmorate.dal.database.GenreDbStorage; -import ru.yandex.practicum.filmorate.dal.database.MpaDbStorage; -import ru.yandex.practicum.filmorate.dal.database.UserDbStorage; -import ru.yandex.practicum.filmorate.dal.rowmappers.FilmRowMapper; -import ru.yandex.practicum.filmorate.dal.rowmappers.GenreRowMapper; -import ru.yandex.practicum.filmorate.dal.rowmappers.MpaRowMapper; -import ru.yandex.practicum.filmorate.dal.rowmappers.UserRowMapper; +import ru.yandex.practicum.filmorate.dal.database.*; +import ru.yandex.practicum.filmorate.dal.rowmappers.*; import ru.yandex.practicum.filmorate.dto.request.update.FilmUpdateRequest; import ru.yandex.practicum.filmorate.mapper.FilmMapper; import ru.yandex.practicum.filmorate.mapper.UserMapper; -import ru.yandex.practicum.filmorate.model.Film; -import ru.yandex.practicum.filmorate.model.Genre; -import ru.yandex.practicum.filmorate.model.Mpa; -import ru.yandex.practicum.filmorate.model.User; +import ru.yandex.practicum.filmorate.model.*; import java.time.Duration; import java.time.LocalDate; @@ -34,6 +26,7 @@ FilmDbStorage.class, FilmRowMapper.class, GenreDbStorage.class, GenreRowMapper.class, MpaDbStorage.class, MpaRowMapper.class, + DirectorDbStorage.class, DirectorRowMapper.class, FilmMapper.class, UserMapper.class }) @JdbcTest @@ -44,18 +37,21 @@ class FilmoRateApplicationTests { private final FilmDbStorage filmStorage; private final GenreDbStorage genreStorage; private final MpaDbStorage mpaStorage; + private final DirectorDbStorage directorStorage; @Autowired FilmoRateApplicationTests( UserDbStorage userStorage, FilmDbStorage filmStorage, GenreDbStorage genreStorage, - MpaDbStorage mpaStorage + MpaDbStorage mpaStorage, + DirectorDbStorage directorStorage ) { this.userStorage = userStorage; this.filmStorage = filmStorage; this.genreStorage = genreStorage; this.mpaStorage = mpaStorage; + this.directorStorage = directorStorage; } private String generateRandomText(int length, String addAfterStr, Character... addSynbols) { @@ -118,6 +114,14 @@ private Film addRandomFilm() { .build()); } + private Director addRandomDirector() { + return directorStorage.createDirector( + Director.builder() + .name(generateRandomText(20, "", ' ')) + .build() + ); + } + @Nested @DisplayName("Тест пользователей.") class Users { @@ -199,6 +203,23 @@ public void friends() { assertThat(userStorage.getFriendIds(id2)).isEqualTo(Set.of(id1, id3)); } + @Test + void recomendations() { + User user1 = addRandomUser(); + User user2 = addRandomUser(); + User user3 = addRandomUser(); + + Film film1 = addRandomFilm(); + Film film2 = addRandomFilm(); + Film film3 = addRandomFilm(); + + filmStorage.addLike(film1.getId(), user1.getId(), Assessment.SEVEN); + filmStorage.addLike(film2.getId(), user2.getId(), Assessment.EIGHT); + filmStorage.addLike(film3.getId(), user3.getId(), Assessment.NINE); + + + } + } @Nested @@ -206,7 +227,7 @@ public void friends() { class Films { @Test - public void testFindUserById() { + public void testFindFilmById() { long id = addFifstFilm().getId(); Optional filmOptional = filmStorage.findById(id); @@ -226,10 +247,7 @@ public void testFindUserById() { ); assertThat(film).hasFieldOrPropertyWithValue("mpaId", 1); assertThat(film).hasFieldOrPropertyWithValue("genreIds", Set.of(1, 2)); - assertThat(film).hasFieldOrPropertyWithValue( - "likes", HashSet.newHashSet(0) - ); - assertThat(film).hasFieldOrPropertyWithValue("likesCount", 0); + assertThat(film).hasFieldOrPropertyWithValue("rate", 0.0f); } ); } @@ -270,43 +288,26 @@ public void updateFilm() { } @Test - public void likes() { + void likes() { + Film film1 = addRandomFilm(); + User user1 = addRandomUser(); User user2 = addRandomUser(); User user3 = addRandomUser(); - addRandomFilm(); - addRandomFilm(); - - Film film1 = addRandomFilm(); - Film film2 = addRandomFilm(); - Film film3 = addRandomFilm(); - - filmStorage.setLike(film2.getId(), user1.getId()); - filmStorage.setLike(film2.getId(), user2.getId()); - filmStorage.setLike(film2.getId(), user3.getId()); - film2.setLikes(Set.of(user1.getId(), user2.getId(), user3.getId())); + filmStorage.addLike(film1.getId(), user1.getId(), Assessment.ONE); + filmStorage.addLike(film1.getId(), user2.getId(), Assessment.of(3)); + filmStorage.addLike(film1.getId(), user3.getId(), Assessment.of(null)); - filmStorage.setLike(film3.getId(), user1.getId()); - filmStorage.setLike(film3.getId(), user2.getId()); - film3.setLikes(Set.of(user1.getId(), user2.getId())); + film1 = filmStorage.findById(film1.getId()).orElse(null); + Assertions.assertNotNull(film1); + assertThat(film1.getRate()).isEqualTo(4.67f); - filmStorage.setLike(film1.getId(), user2.getId()); - film1.setLikes(Set.of(user2.getId())); + filmStorage.removeLike(film1.getId(), user1.getId()); - Collection top = filmStorage.getTop(4); - assertThat(top) - .contains(film1, film2, film3) - .hasSize(4); - - filmStorage.removeLike(film1.getId(), user2.getId()); - film1.setLikes(HashSet.newHashSet(0)); - - Collection newTop = filmStorage.getTop(4); - assertThat(newTop) - .contains(film2, film3) - .doesNotContain(film1) - .hasSize(4); + film1 = filmStorage.findById(film1.getId()).orElse(null); + Assertions.assertNotNull(film1); + assertThat(film1.getRate()).isEqualTo(6.5f); } @Test @@ -324,7 +325,6 @@ void genres() { Collection genreIds = genreStorage.getAllGenres().stream().map(Genre::getId).toList(); assertThat(genreIds).containsAll(film1.getGenreIds()); - } @Test @@ -345,6 +345,38 @@ void mpa() { } + @Test + void top() { + + User user1 = addRandomUser(); + + Film film1 = addRandomFilm(); + Film film2 = addRandomFilm(); + Film film3 = addRandomFilm(); + + Director director = addRandomDirector(); + film1.setDirectorIds(Set.of(director.getId())); + film2.setDirectorIds(Set.of(director.getId())); + film3.setDirectorIds(Set.of(director.getId())); + + film1 = filmStorage.updateFilm(film1); + film2 = filmStorage.updateFilm(film2); + film3 = filmStorage.updateFilm(film3); + + filmStorage.addLike(film1.getId(), user1.getId(), Assessment.ONE); + filmStorage.addLike(film2.getId(), user1.getId(), Assessment.NINE); + filmStorage.addLike(film3.getId(), user1.getId(), Assessment.SIX); + + assertThat(filmStorage.findById(film1.getId()).orElseThrow().getRate()).isEqualTo(1); + assertThat(filmStorage.findById(film2.getId()).orElseThrow().getRate()).isEqualTo(9); + assertThat(filmStorage.findById(film3.getId()).orElseThrow().getRate()).isEqualTo(6); + + Collection films = filmStorage.getTop(2); + + assertThat(films).containsExactly(film2, film3); + + } + } } \ No newline at end of file