diff --git a/img/schema-database.png b/img/schema-database.png index 473408b..5854b0d 100644 Binary files a/img/schema-database.png and b/img/schema-database.png differ diff --git a/pom.xml b/pom.xml index 6ad9433..065620c 100644 --- a/pom.xml +++ b/pom.xml @@ -21,7 +21,6 @@ org.springframework.boot spring-boot-starter-web - org.projectlombok lombok @@ -41,6 +40,14 @@ logbook-spring-boot-starter 3.7.2 + + com.h2database + h2 + + + org.springframework.boot + spring-boot-starter-data-jdbc + 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 ed7be62..6c7700c 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java @@ -49,4 +49,9 @@ public List getPopularFilms(@Positive @RequestParam(defaultValue = "10") i return filmService.getPopularFilms(count); } + @GetMapping("/{id}") + public Film getFilm(@PathVariable Long id) { + return filmService.getFilmById(id); + } + } diff --git a/src/main/java/ru/yandex/practicum/filmorate/controller/GenreController.java b/src/main/java/ru/yandex/practicum/filmorate/controller/GenreController.java new file mode 100644 index 0000000..7dbd3f2 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/GenreController.java @@ -0,0 +1,28 @@ +package ru.yandex.practicum.filmorate.controller; + +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import ru.yandex.practicum.filmorate.model.Genre; +import ru.yandex.practicum.filmorate.service.GenreService; + +import java.util.Collection; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/genres") +public class GenreController { + private final GenreService genreService; + + @GetMapping("{id}") + public Genre getGenre(@PathVariable Long id) { + return genreService.getGenreById(id); + } + + @GetMapping + public Collection getGenres() { + return genreService.getGenres(); + } +} \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/controller/RatingController.java b/src/main/java/ru/yandex/practicum/filmorate/controller/RatingController.java new file mode 100644 index 0000000..86449c6 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/RatingController.java @@ -0,0 +1,28 @@ +package ru.yandex.practicum.filmorate.controller; + +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import ru.yandex.practicum.filmorate.model.Rating; +import ru.yandex.practicum.filmorate.service.RatingService; + +import java.util.Collection; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/mpa") +public class RatingController { + private final RatingService ratingService; + + @GetMapping("{id}") + public Rating getRating(@PathVariable Long id) { + return ratingService.getRatingById(id); + } + + @GetMapping + public Collection getRating() { + return ratingService.getRatings(); + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java b/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java index 189c38e..4e7e84f 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java @@ -22,7 +22,7 @@ public Collection getUsers() { } @PostMapping - public User createFilm(@Valid @RequestBody User user) { + public User createUser(@Valid @RequestBody User user) { return userService.createUser(user); } diff --git a/src/main/java/ru/yandex/practicum/filmorate/exception/ErrorResponse.java b/src/main/java/ru/yandex/practicum/filmorate/exception/ErrorResponse.java index e04a4ac..c5fcbfd 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/exception/ErrorResponse.java +++ b/src/main/java/ru/yandex/practicum/filmorate/exception/ErrorResponse.java @@ -11,5 +11,4 @@ public ErrorResponse(String error, String description) { this.error = error; this.description = description; } - } \ 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 f5cf2c0..88f5669 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/model/Film.java +++ b/src/main/java/ru/yandex/practicum/filmorate/model/Film.java @@ -1,7 +1,9 @@ package ru.yandex.practicum.filmorate.model; +import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import jakarta.validation.Valid; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; import ru.yandex.practicum.filmorate.json.DeserializerDuration; @@ -13,6 +15,7 @@ import java.time.Duration; import java.time.LocalDate; import java.util.HashSet; +import java.util.List; import java.util.Set; @Data @@ -30,5 +33,9 @@ public class Film { @JsonDeserialize(using = DeserializerDuration.class) private Duration duration; private final Set likes = new HashSet<>(); - + @JsonProperty("mpa") + @Valid + private Rating rating; + @Valid + private List genres; } diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/Genre.java b/src/main/java/ru/yandex/practicum/filmorate/model/Genre.java new file mode 100644 index 0000000..bab0def --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/model/Genre.java @@ -0,0 +1,17 @@ +package ru.yandex.practicum.filmorate.model; + +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Setter +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class Genre { + @NotNull + private Long id; + private String name; +} \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/Rating.java b/src/main/java/ru/yandex/practicum/filmorate/model/Rating.java new file mode 100644 index 0000000..2ce9d76 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/model/Rating.java @@ -0,0 +1,19 @@ +package ru.yandex.practicum.filmorate.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Setter +@Getter +@JsonIgnoreProperties(ignoreUnknown = true) +@NoArgsConstructor +@AllArgsConstructor +public class Rating { + @NotNull + private Long id; + private String name; +} \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/User.java b/src/main/java/ru/yandex/practicum/filmorate/model/User.java index 4e90863..716ae6c 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/model/User.java +++ b/src/main/java/ru/yandex/practicum/filmorate/model/User.java @@ -24,5 +24,4 @@ public class User { @PastOrPresent private LocalDate birthday; private final Set friends = new HashSet<>(); - -} +} \ No newline at end of file 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 8d54f38..f698d25 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/service/FilmService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/FilmService.java @@ -2,12 +2,18 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import ru.yandex.practicum.filmorate.exception.ExceptionNotFound; import ru.yandex.practicum.filmorate.model.Film; +import ru.yandex.practicum.filmorate.model.Genre; +import ru.yandex.practicum.filmorate.model.Rating; +import ru.yandex.practicum.filmorate.storage.dal.dao.FilmGenreDbStorage; import ru.yandex.practicum.filmorate.storage.film.FilmStorage; import java.util.Collection; import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; @RequiredArgsConstructor @Service @@ -15,18 +21,34 @@ public class FilmService { private final FilmStorage filmStorage; private final UserService userService; + private final GenreService genreService; + private final RatingService ratingService; + private final FilmGenreDbStorage filmGenreDbStorage; public Collection getFilms() { return filmStorage.getFilms(); } + @Transactional public Film createFilm(Film film) { - return filmStorage.createFilm(film); + validateFilm(film); + Film createdFilm = filmStorage.createFilm(film); + if (film.getGenres() != null && !film.getGenres().isEmpty()) { + filmGenreDbStorage.saveFilmGenres(createdFilm.getId(), film.getGenres()); + } + return createdFilm; } + @Transactional public Film updateFilm(Film film) { - return filmStorage.updateFilm(film) + validateFilm(film); + Film updatedFilm = filmStorage.updateFilm(film) .orElseThrow(() -> new ExceptionNotFound("Фильм с идентификатором '%d' не найден".formatted(film.getId()))); + filmGenreDbStorage.deleteFilmGenresForFilmId(updatedFilm.getId()); + if (film.getGenres() != null && !film.getGenres().isEmpty()) { + filmGenreDbStorage.saveFilmGenres(updatedFilm.getId(), film.getGenres()); + } + return updatedFilm; } public void addLike(Long filmId, Long userId) { @@ -50,4 +72,28 @@ public Film getFilmById(Long id) { .orElseThrow(() -> new ExceptionNotFound("Фильм с идентификатором '%d' не найден".formatted(id))); } + private void validateFilm(Film film) { + if (film.getGenres() != null) { + Set allGenreId = genreService.getGenres() + .stream() + .map(Genre::getId) + .collect(Collectors.toSet()); + Set allGenreIdFilm = film.getGenres() + .stream() + .map(Genre::getId) + .collect(Collectors.toSet()); + if (!allGenreId.containsAll(allGenreIdFilm)) { + throw new ExceptionNotFound("Один или несколько жанров не существует."); + } + } + if (film.getRating() != null && film.getRating().getId() != null) { + Set ratingId = ratingService.getRatings() + .stream() + .map(Rating::getId) + .collect(Collectors.toSet()); + if (!ratingId.contains(film.getRating().getId())) { + throw new ExceptionNotFound("Рейтинг с идентификатором %d не найден".formatted(film.getRating().getId())); + } + } + } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/GenreService.java b/src/main/java/ru/yandex/practicum/filmorate/service/GenreService.java new file mode 100644 index 0000000..a088cd1 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/service/GenreService.java @@ -0,0 +1,24 @@ +package ru.yandex.practicum.filmorate.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import ru.yandex.practicum.filmorate.exception.ExceptionNotFound; +import ru.yandex.practicum.filmorate.model.Genre; +import ru.yandex.practicum.filmorate.storage.dal.dao.GenreDbStorage; + +import java.util.Collection; + +@RequiredArgsConstructor +@Service +public class GenreService { + private final GenreDbStorage genreDbStorage; + + public Genre getGenreById(Long id) { + return genreDbStorage.getGenreById(id) + .orElseThrow(() -> new ExceptionNotFound("Жанр с идентификатором '%d' не найден".formatted(id))); + } + + public Collection getGenres() { + return genreDbStorage.getGenres(); + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/RatingService.java b/src/main/java/ru/yandex/practicum/filmorate/service/RatingService.java new file mode 100644 index 0000000..6ac4ef5 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/service/RatingService.java @@ -0,0 +1,24 @@ +package ru.yandex.practicum.filmorate.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import ru.yandex.practicum.filmorate.exception.ExceptionNotFound; +import ru.yandex.practicum.filmorate.model.Rating; +import ru.yandex.practicum.filmorate.storage.dal.dao.RatingDbStorage; + +import java.util.Collection; + +@RequiredArgsConstructor +@Service +public class RatingService { + private final RatingDbStorage ratingDbStorage; + + public Rating getRatingById(Long id) { + return ratingDbStorage.getRatingById(id) + .orElseThrow(() -> new ExceptionNotFound("Рейтинг с идентификатором '%d' не найден".formatted(id))); + } + + public Collection getRatings() { + return ratingDbStorage.getRatings(); + } +} 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 3107cda..3eb57aa 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/service/UserService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/UserService.java @@ -13,7 +13,6 @@ @RequiredArgsConstructor @Service public class UserService { - private final UserStorage userStorage; public Collection getUsers() { diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/dal/dao/FilmDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/dal/dao/FilmDbStorage.java new file mode 100644 index 0000000..f6f20bf --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/dal/dao/FilmDbStorage.java @@ -0,0 +1,143 @@ +package ru.yandex.practicum.filmorate.storage.dal.dao; + +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Primary; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.ResultSetExtractor; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.support.GeneratedKeyHolder; +import org.springframework.jdbc.support.KeyHolder; +import org.springframework.stereotype.Repository; +import ru.yandex.practicum.filmorate.model.Film; +import ru.yandex.practicum.filmorate.storage.film.FilmStorage; + +import java.sql.PreparedStatement; +import java.sql.Statement; +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +@Primary +@Repository +@RequiredArgsConstructor +public class FilmDbStorage implements FilmStorage { + private final JdbcTemplate jdbcTemplate; + private final RowMapper filmRowMapper; + private final ResultSetExtractor filmExtractor; + private final ResultSetExtractor> filmsExtractor; + + @Override + public Collection getFilms() { + String query = """ + SELECT + f.film_id AS film_id, + f.name AS film_name, + f.description AS description, + f.release_date AS release_date, + f.duration AS duration, + f.rating_id AS rating_id, + r.name AS rating_name, + g.genre_id AS genre_id, + g.name AS genre_name + FROM films f + JOIN ratings r ON f.rating_id = r.rating_id + LEFT JOIN film_genres fg ON f.film_id = fg.film_id + LEFT JOIN genres g ON fg.genre_id = g.genre_id + ORDER BY f.film_id + """; + return jdbcTemplate.query(query, filmsExtractor); + } + + @Override + public Film createFilm(Film film) { + KeyHolder keyHolder = new GeneratedKeyHolder(); + String query = "INSERT INTO films (name, description, release_date, duration, rating_id) VALUES (?, ?, ?, ?, ?)"; + jdbcTemplate.update(connection -> { + PreparedStatement ps = connection.prepareStatement(query, Statement.RETURN_GENERATED_KEYS); + ps.setString(1, film.getName()); + ps.setString(2, film.getDescription()); + ps.setDate(3, java.sql.Date.valueOf(film.getReleaseDate())); + ps.setLong(4, film.getDuration().toMinutes()); + ps.setLong(5, film.getRating().getId()); + return ps; + }, keyHolder); + Long id = keyHolder.getKeyAs(Long.class); + if (id == null) { + throw new IllegalStateException("Не удалось сгенерировать идентификатор фильма"); + } + film.setId(id); + return film; + } + + @Override + public Optional updateFilm(Film film) { + String query = """ + UPDATE films + SET name = ?, description = ?, release_date = ?, duration = ?, rating_id = ? + WHERE film_id = ?; + """; + int countRows = jdbcTemplate.update(query, + film.getName(), + film.getDescription(), + film.getReleaseDate(), + film.getDuration(), + film.getRating().getId(), + film.getId() + ); + if (countRows > 0) { + return Optional.of(film); + } + return Optional.empty(); + } + + @Override + public Optional getFilmById(Long id) { + String query = """ + SELECT + f.film_id AS film_id, + f.name AS film_name, + f.description AS description, + f.release_date AS release_date, + f.duration AS duration, + f.rating_id AS rating_id, + r.name AS rating_name, + g.genre_id AS genre_id, + g.name AS genre_name + FROM films f + JOIN ratings r ON f.rating_id = r.rating_id + LEFT JOIN film_genres fg ON f.film_id = fg.film_id + LEFT JOIN genres g ON fg.genre_id = g.genre_id + WHERE f.film_id = ? + """; + return Optional.ofNullable(jdbcTemplate.query(query, filmExtractor, id)); + } + + @Override + public void addLike(Long filmId, Long userId) { + String query = "INSERT INTO film_likes(user_id, film_id) VALUES (?, ?)"; + jdbcTemplate.update(query, userId, filmId); + } + + @Override + public void removeLike(Long filmId, Long userId) { + String query = "DELETE FROM film_likes WHERE user_id = ? AND film_id = ?"; + jdbcTemplate.update(query, userId, filmId); + } + + @Override + public List getPopularFilms(int count) { + String query = """ + SELECT f.film_id, f.name, f.description, f.release_date, f.duration, f.rating_id + FROM films f + LEFT JOIN ( + SELECT film_id, COUNT(user_id) AS count_like + FROM film_likes + GROUP BY film_id + ) fl ON f.film_id = fl.film_id + ORDER BY fl.count_like DESC + LIMIT ? + """; + return jdbcTemplate.query(query, filmRowMapper, count); + } + +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/dal/dao/FilmGenreDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/dal/dao/FilmGenreDbStorage.java new file mode 100644 index 0000000..127d349 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/dal/dao/FilmGenreDbStorage.java @@ -0,0 +1,33 @@ +package ru.yandex.practicum.filmorate.storage.dal.dao; + +import lombok.RequiredArgsConstructor; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Repository; +import ru.yandex.practicum.filmorate.model.Genre; + +import java.util.List; + +@Repository +@RequiredArgsConstructor +public class FilmGenreDbStorage { + private final JdbcTemplate jdbcTemplate; + + public void saveFilmGenres(Long filmId, List genres) { + String query = """ + INSERT INTO film_genres(film_id, genre_id) + VALUES (?, ?) + """; + List params = genres + .stream() + .map(Genre::getId) + .distinct() + .map(genreId -> new Object[]{filmId, genreId}) + .toList(); + jdbcTemplate.batchUpdate(query, params); + } + + public void deleteFilmGenresForFilmId(Long filmId) { + String query = "DELETE FROM film_genres WHERE film_id = ?"; + jdbcTemplate.update(query, filmId); + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/dal/dao/GenreDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/dal/dao/GenreDbStorage.java new file mode 100644 index 0000000..29c6728 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/dal/dao/GenreDbStorage.java @@ -0,0 +1,40 @@ +package ru.yandex.practicum.filmorate.storage.dal.dao; + +import lombok.RequiredArgsConstructor; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Repository; +import org.springframework.jdbc.core.JdbcTemplate; +import ru.yandex.practicum.filmorate.model.Genre; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +@Repository +@RequiredArgsConstructor +public class GenreDbStorage { + private final JdbcTemplate jdbcTemplate; + private final RowMapper genreRowMapper; + + public Optional getGenreById(Long id) { + String query = """ + SELECT genre_id, name + FROM genres + WHERE genre_id = ? + """; + List genres = jdbcTemplate.query(query, genreRowMapper, id); + if (genres.isEmpty()) { + return Optional.empty(); + } + return Optional.of(genres.getFirst()); + } + + public Collection getGenres() { + String query = """ + SELECT genre_id, name + FROM genres + ORDER BY genre_id + """; + return jdbcTemplate.query(query, genreRowMapper); + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/dal/dao/RatingDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/dal/dao/RatingDbStorage.java new file mode 100644 index 0000000..d5a8507 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/dal/dao/RatingDbStorage.java @@ -0,0 +1,40 @@ +package ru.yandex.practicum.filmorate.storage.dal.dao; + +import lombok.RequiredArgsConstructor; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Repository; +import ru.yandex.practicum.filmorate.model.Rating; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +@Repository +@RequiredArgsConstructor +public class RatingDbStorage { + private final JdbcTemplate jdbcTemplate; + private final RowMapper ratingRowMapper; + + public Optional getRatingById(Long id) { + String query = """ + SELECT rating_id, name + FROM ratings + WHERE rating_id = ? + """; + List ratings = jdbcTemplate.query(query, ratingRowMapper, id); + if (ratings.isEmpty()) { + return Optional.empty(); + } + return Optional.of(ratings.getFirst()); + } + + public Collection getRatings() { + String query = """ + SELECT rating_id, name + FROM ratings + ORDER BY rating_id + """; + return jdbcTemplate.query(query, ratingRowMapper); + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/dal/dao/UserDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/dal/dao/UserDbStorage.java new file mode 100644 index 0000000..2dad8bf --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/dal/dao/UserDbStorage.java @@ -0,0 +1,127 @@ +package ru.yandex.practicum.filmorate.storage.dal.dao; + +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Primary; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.support.GeneratedKeyHolder; +import org.springframework.jdbc.support.KeyHolder; +import org.springframework.stereotype.Repository; +import ru.yandex.practicum.filmorate.model.User; +import ru.yandex.practicum.filmorate.storage.user.UserStorage; + +import java.sql.PreparedStatement; +import java.sql.Statement; +import java.util.*; +import java.util.stream.Collectors; + +@Primary +@Repository +@RequiredArgsConstructor +public class UserDbStorage implements UserStorage { + private final JdbcTemplate jdbcTemplate; + private final RowMapper userRowMapper; + + @Override + public Collection getUsers() { + String query = """ + SELECT user_id, email, login, name, birthday + FROM users + """; + return jdbcTemplate.query(query, userRowMapper); + } + + @Override + public User createUser(User user) { + KeyHolder keyHolder = new GeneratedKeyHolder(); + String query = "INSERT INTO users (email, login, name, birthday) VALUES (?, ?, ?, ?)"; + jdbcTemplate.update(connection -> { + PreparedStatement ps = connection.prepareStatement(query, Statement.RETURN_GENERATED_KEYS); + ps.setString(1, user.getEmail()); + ps.setString(2, user.getLogin()); + ps.setString(3, user.getName()); + ps.setObject(4, user.getBirthday()); + return ps; + }, keyHolder); + //log.trace("keyHolder - "+keyHolder.getKeyAs(Long.class)); + Long id = keyHolder.getKeyAs(Long.class); + if (id == null) { + throw new IllegalStateException("Не удалось сгенерировать идентификатор пользователя"); + } + user.setId(id); + return user; + } + + @Override + public Optional updateUser(User user) { + String query = """ + UPDATE users + SET email = ?, login = ?, name = ?, birthday = ? + WHERE user_id = ? + """; + int countRows = jdbcTemplate.update(query, + user.getEmail(), + user.getLogin(), + user.getName(), + user.getBirthday(), + user.getId() + ); + if (countRows > 0) { + return Optional.of(user); + } + return Optional.empty(); + } + + @Override + public Optional getUserById(Long id) { + String query = """ + SELECT user_id, email, login, name, birthday + FROM users + WHERE user_id = ? + """; + List users = jdbcTemplate.query(query, userRowMapper, id); + if (users.isEmpty()) { + return Optional.empty(); + } + return Optional.of(users.getFirst()); + } + + @Override + public void addFriend(Long userId, Long friendId) { + String query = "INSERT INTO friendships(user_id, user_id_friend) VALUES (?, ?)"; + jdbcTemplate.update(query, userId, friendId); + } + + @Override + public void removeFriend(Long userId, Long friendId) { + String query = "DELETE FROM friendships WHERE user_id = ? AND user_id_friend = ?"; + jdbcTemplate.update(query, userId, friendId); + } + + @Override + public Set> getCommonFriends(Long userIdOne, Long userIdTwo) { + String query = """ + SELECT u.user_id, u.email, u.login, u.name, u.birthday + FROM friendships f1 + JOIN friendships f2 ON f1.user_id_friend = f2.user_id_friend + JOIN users u ON u.user_id = f1.user_id_friend + WHERE f1.user_id = ? AND f2.user_id = ? + """; + return jdbcTemplate.query(query, userRowMapper, userIdOne, userIdTwo) + .stream() + .map(Optional::ofNullable) + .collect(Collectors.toSet()); + } + + @Override + public Set getFriends(Long userId) { + String query = """ + SELECT u.user_id, u.email, u.login, u.name, u.birthday + FROM friendships f + JOIN users u ON u.user_id = f.user_id_friend + WHERE f.user_id = ? + """; + return new HashSet<>(jdbcTemplate.query(query, userRowMapper, userId)); + } + +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/dal/extractors/FilmExtractor.java b/src/main/java/ru/yandex/practicum/filmorate/storage/dal/extractors/FilmExtractor.java new file mode 100644 index 0000000..209a962 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/dal/extractors/FilmExtractor.java @@ -0,0 +1,31 @@ +package ru.yandex.practicum.filmorate.storage.dal.extractors; + +import org.springframework.jdbc.core.ResultSetExtractor; +import org.springframework.stereotype.Component; +import ru.yandex.practicum.filmorate.model.Film; +import ru.yandex.practicum.filmorate.model.Genre; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; + +@Component +public class FilmExtractor implements ResultSetExtractor { + + @Override + public Film extractData(ResultSet resultSet) throws SQLException { + Film film = null; + while (resultSet.next()) { + if (film == null) { + film = FilmMapsUtil.mapFilm(resultSet); + film.setRating(FilmMapsUtil.mapRating(resultSet)); + film.setGenres(new ArrayList<>()); + } + Genre genre = FilmMapsUtil.mapGenre(resultSet); + if (genre != null) { + film.getGenres().add(genre); + } + } + return film; + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/dal/extractors/FilmMapsUtil.java b/src/main/java/ru/yandex/practicum/filmorate/storage/dal/extractors/FilmMapsUtil.java new file mode 100644 index 0000000..2fca1c4 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/dal/extractors/FilmMapsUtil.java @@ -0,0 +1,39 @@ +package ru.yandex.practicum.filmorate.storage.dal.extractors; + +import ru.yandex.practicum.filmorate.model.Film; +import ru.yandex.practicum.filmorate.model.Genre; +import ru.yandex.practicum.filmorate.model.Rating; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.time.Duration; + +public class FilmMapsUtil { + public static Film mapFilm(ResultSet resultSet) throws SQLException { + Film film = new Film(); + film.setId(resultSet.getLong("film_id")); + film.setName(resultSet.getString("film_name")); + film.setDescription(resultSet.getString("description")); + film.setReleaseDate(resultSet.getDate("release_date").toLocalDate()); + film.setDuration(Duration.ofMinutes(resultSet.getInt("duration"))); + return film; + } + + public static Rating mapRating(ResultSet resultSet) throws SQLException { + Rating rating = new Rating(); + rating.setId(resultSet.getLong("rating_id")); + rating.setName(resultSet.getString("rating_name")); + return rating; + } + + public static Genre mapGenre(ResultSet resultSet) throws SQLException { + long genreId = resultSet.getLong("genre_id"); + if (genreId <= 0) { + return null; + } + Genre genre = new Genre(); + genre.setId(genreId); + genre.setName(resultSet.getString("genre_name")); + return genre; + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/dal/extractors/FilmsExtractor.java b/src/main/java/ru/yandex/practicum/filmorate/storage/dal/extractors/FilmsExtractor.java new file mode 100644 index 0000000..d11c8c9 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/dal/extractors/FilmsExtractor.java @@ -0,0 +1,34 @@ +package ru.yandex.practicum.filmorate.storage.dal.extractors; + +import org.springframework.jdbc.core.ResultSetExtractor; +import org.springframework.stereotype.Component; +import ru.yandex.practicum.filmorate.model.Film; +import ru.yandex.practicum.filmorate.model.Genre; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.*; + +@Component +public class FilmsExtractor implements ResultSetExtractor> { + + @Override + public List extractData(ResultSet resultSet) throws SQLException { + Map films = new HashMap<>(); + while (resultSet.next()) { + long filmId = resultSet.getLong("film_id"); + Film film = films.get(filmId); + if (film == null) { + film = FilmMapsUtil.mapFilm(resultSet); + film.setRating(FilmMapsUtil.mapRating(resultSet)); + film.setGenres(new ArrayList<>()); + films.put(filmId, film); + } + Genre genre = FilmMapsUtil.mapGenre(resultSet); + if (genre != null) { + film.getGenres().add(genre); + } + } + return new ArrayList<>(films.values()); + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/dal/mappers/FilmRowMapper.java b/src/main/java/ru/yandex/practicum/filmorate/storage/dal/mappers/FilmRowMapper.java new file mode 100644 index 0000000..26d270c --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/dal/mappers/FilmRowMapper.java @@ -0,0 +1,31 @@ +package ru.yandex.practicum.filmorate.storage.dal.mappers; + +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Component; +import ru.yandex.practicum.filmorate.model.Film; +import ru.yandex.practicum.filmorate.model.Rating; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.time.Duration; + +@Component +public class FilmRowMapper implements RowMapper { + + @Override + public Film mapRow(ResultSet resultSet, int rowNum) throws SQLException { + Film film = new Film(); + film.setId(resultSet.getLong("film_id")); + film.setName(resultSet.getString("name")); + film.setDescription(resultSet.getString("description")); + film.setReleaseDate(resultSet.getDate("release_date").toLocalDate()); + film.setDuration(Duration.ofMinutes(resultSet.getInt("duration"))); + long ratingId = resultSet.getLong("rating_id"); + if (!resultSet.wasNull()) { + Rating rating = new Rating(); + rating.setId(ratingId); + film.setRating(rating); + } + return film; + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/dal/mappers/GenreRowMapper.java b/src/main/java/ru/yandex/practicum/filmorate/storage/dal/mappers/GenreRowMapper.java new file mode 100644 index 0000000..36ff83e --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/dal/mappers/GenreRowMapper.java @@ -0,0 +1,20 @@ +package ru.yandex.practicum.filmorate.storage.dal.mappers; + +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Component; +import ru.yandex.practicum.filmorate.model.Genre; + +import java.sql.ResultSet; +import java.sql.SQLException; + +@Component +public class GenreRowMapper implements RowMapper { + + @Override + public Genre mapRow(ResultSet resultSet, int rowNum) throws SQLException { + Genre genre = new Genre(); + genre.setId(resultSet.getLong("genre_id")); + genre.setName(resultSet.getString("name")); + return genre; + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/dal/mappers/RatingRowMapper.java b/src/main/java/ru/yandex/practicum/filmorate/storage/dal/mappers/RatingRowMapper.java new file mode 100644 index 0000000..16b7b19 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/dal/mappers/RatingRowMapper.java @@ -0,0 +1,20 @@ +package ru.yandex.practicum.filmorate.storage.dal.mappers; + +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Component; +import ru.yandex.practicum.filmorate.model.Rating; + +import java.sql.ResultSet; +import java.sql.SQLException; + +@Component +public class RatingRowMapper implements RowMapper { + + @Override + public Rating mapRow(ResultSet resultSet, int rowNum) throws SQLException { + Rating rating = new Rating(); + rating.setId(resultSet.getLong("rating_id")); + rating.setName(resultSet.getString("name")); + return rating; + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/dal/mappers/UserRowMapper.java b/src/main/java/ru/yandex/practicum/filmorate/storage/dal/mappers/UserRowMapper.java new file mode 100644 index 0000000..6a24fed --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/dal/mappers/UserRowMapper.java @@ -0,0 +1,26 @@ +package ru.yandex.practicum.filmorate.storage.dal.mappers; + +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Component; +import ru.yandex.practicum.filmorate.model.User; + +import java.sql.Date; +import java.sql.ResultSet; +import java.sql.SQLException; + +@Component +public class UserRowMapper implements RowMapper { + @Override + public User mapRow(ResultSet resultSet, int rowNum) throws SQLException { + User user = new User(); + user.setId(resultSet.getLong("user_id")); + user.setEmail(resultSet.getString("email")); + user.setLogin(resultSet.getString("login")); + user.setName(resultSet.getString("name")); + Date birthday = resultSet.getDate("birthday"); + if (birthday != null) { + user.setBirthday(birthday.toLocalDate()); + } + return user; + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index cc392b5..d365592 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1,6 @@ -logging.level.org.zalando.logbook=TRACE \ No newline at end of file +logging.level.org.zalando.logbook=TRACE +spring.sql.init.mode=always +spring.datasource.url=jdbc:h2:file:./db/filmorate +spring.datasource.driverClassName=org.h2.Driver +spring.datasource.username=sa +spring.datasource.password=password \ No newline at end of file diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql new file mode 100644 index 0000000..6772c1e --- /dev/null +++ b/src/main/resources/data.sql @@ -0,0 +1,11 @@ +MERGE INTO ratings (name) KEY (name) VALUES ('G'); +MERGE INTO ratings (name) KEY (name) VALUES ('PG'); +MERGE INTO ratings (name) KEY (name) VALUES ('PG-13'); +MERGE INTO ratings (name) KEY (name) VALUES ('R'); +MERGE INTO ratings (name) KEY (name) VALUES ('NC-17'); +MERGE INTO genres (genre_id, name) KEY (genre_id) VALUES (1, 'Комедия'); +MERGE INTO genres (genre_id, name) KEY (genre_id) VALUES (2, 'Драма'); +MERGE INTO genres (genre_id, name) KEY (genre_id) VALUES (3, 'Мультфильм'); +MERGE INTO genres (genre_id, name) KEY (genre_id) VALUES (4, 'Триллер'); +MERGE INTO genres (genre_id, name) KEY (genre_id) VALUES (5, 'Документальный'); +MERGE INTO genres (genre_id, name) KEY (genre_id) VALUES (6, 'Боевик') \ No newline at end of file diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql new file mode 100644 index 0000000..b24e000 --- /dev/null +++ b/src/main/resources/schema.sql @@ -0,0 +1,42 @@ +CREATE TABLE IF NOT EXISTS users( +user_id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, +email varchar NOT NULL, +login varchar NOT NULL, +name varchar, +birthday date); + +CREATE TABLE IF NOT EXISTS ratings( +rating_id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, +name varchar UNIQUE NOT NULL); + +CREATE TABLE IF NOT EXISTS films( +film_id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, +name varchar NOT NULL, +description varchar(200), +release_date date, +duration bigint, +rating_id bigint, +FOREIGN KEY (rating_id) REFERENCES ratings (rating_id)); + +CREATE TABLE IF NOT EXISTS friendships( +user_id bigint NOT NULL, +user_id_friend bigint NOT NULL, +FOREIGN KEY (user_id) REFERENCES users (user_id), +FOREIGN KEY (user_id_friend) REFERENCES users (user_id), +CONSTRAINT not_equal_to_himself CHECK (user_id <> user_id_friend)); + +CREATE TABLE IF NOT EXISTS film_likes( +user_id bigint NOT NULL, +film_id bigint NOT NULL, +FOREIGN KEY (user_id) REFERENCES users (user_id), +FOREIGN KEY (film_id) REFERENCES films (film_id)); + +CREATE TABLE IF NOT EXISTS genres( +genre_id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, +name varchar UNIQUE NOT NULL); + +CREATE TABLE IF NOT EXISTS film_genres( +film_id bigint NOT NULL, +genre_id bigint NOT NULL, +FOREIGN KEY (genre_id) REFERENCES genres (genre_id), +FOREIGN KEY (film_id) REFERENCES films (film_id)); \ No newline at end of file diff --git a/src/test/java/ru/yandex/practicum/filmorate/dao/FilmDbStorageTest.java b/src/test/java/ru/yandex/practicum/filmorate/dao/FilmDbStorageTest.java new file mode 100644 index 0000000..a1c6cea --- /dev/null +++ b/src/test/java/ru/yandex/practicum/filmorate/dao/FilmDbStorageTest.java @@ -0,0 +1,163 @@ +package ru.yandex.practicum.filmorate.dao; + +import lombok.RequiredArgsConstructor; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.jdbc.core.JdbcTemplate; +import ru.yandex.practicum.filmorate.model.Film; +import ru.yandex.practicum.filmorate.model.Rating; +import ru.yandex.practicum.filmorate.model.User; +import ru.yandex.practicum.filmorate.storage.dal.dao.FilmDbStorage; +import ru.yandex.practicum.filmorate.storage.dal.dao.UserDbStorage; + +import java.time.Duration; +import java.time.LocalDate; +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; + +@JdbcTest +@AutoConfigureTestDatabase +@RequiredArgsConstructor(onConstructor_ = @Autowired) +@ComponentScan(basePackages = {"ru.yandex.practicum.filmorate.storage"}) +class FilmDbStorageTest { + private final FilmDbStorage filmDbStorage; + private final UserDbStorage userDbStorage; + private final JdbcTemplate jdbcTemplate; + + private Film createFilm(String name, String description) { + Film film = new Film(); + film.setName(name); + film.setDescription(description); + film.setReleaseDate(LocalDate.of(1990, 2, 5)); + film.setDuration(Duration.ofMinutes(120)); + film.setRating(new Rating(5L, "NC-17")); + film.setGenres(List.of()); + return film; + } + + private User createUser(String login, String name) { + User user = new User(); + user.setEmail("email@mail.ru"); + user.setLogin(login); + user.setName(name); + user.setBirthday(LocalDate.of(1990, 2, 5)); + return user; + } + + @Test + void createFilm() { + Film film = createFilm("Название фильма №1", "Описание фильма №1"); + Film savedFilm = filmDbStorage.createFilm(film); + Optional loadedFilm = filmDbStorage.getFilmById(savedFilm.getId()); + + assertThat(savedFilm.getId()).isNotNull(); + assertThat(loadedFilm).isPresent(); + assertThat(loadedFilm.get().getName()).isEqualTo("Название фильма №1"); + assertThat(loadedFilm.get().getDescription()).isEqualTo("Описание фильма №1"); + } + + @Test + void findFilm() { + Film film2 = createFilm("Название фильма №2", "Описание фильма №2"); + filmDbStorage.createFilm(film2); + Optional findFilmYes = filmDbStorage.getFilmById(film2.getId()); + assertThat(findFilmYes).isPresent(); + Optional findFilmNot = filmDbStorage.getFilmById(999L); + assertThat(findFilmNot).isEmpty(); + + Film film3 = createFilm("Название фильма №3", "Описание фильма №3"); + Film film4 = createFilm("Название фильма №4", "Описание фильма №4"); + filmDbStorage.createFilm(film3); + filmDbStorage.createFilm(film4); + + Collection films = filmDbStorage.getFilms(); + assertThat(films).hasSize(3); + } + + @Test + void updateFilm() { + Film film5 = createFilm("Название фильма №5", "Описание фильма №5"); + filmDbStorage.createFilm(film5); + film5.setName("Название фильма №5 новое"); + film5.setDescription("Описание фильма №5 новое"); + Optional updatedFilm = filmDbStorage.updateFilm(film5); + + assertThat(updatedFilm).isPresent(); + assertThat(updatedFilm.get().getName()).isEqualTo("Название фильма №5 новое"); + assertThat(updatedFilm.get().getDescription()).isEqualTo("Описание фильма №5 новое"); + + Optional loadedFilm = filmDbStorage.getFilmById(film5.getId()); + assertThat(loadedFilm).isPresent(); + assertThat(loadedFilm.get().getName()).isEqualTo(updatedFilm.get().getName()); + assertThat(loadedFilm.get().getDescription()).isEqualTo(updatedFilm.get().getDescription()); + + Film film6 = createFilm("Название фильма №6", "Описание фильма №6"); + film6.setId(7L); + + Optional updateFilm = filmDbStorage.updateFilm(film6); + assertThat(updateFilm).isEmpty(); + } + + @Test + void addAndDeleteLike() { + Film film7 = createFilm("Название фильма №7", "Описание фильма №7"); + filmDbStorage.createFilm(film7); + Film film8 = createFilm("Название фильма №8", "Описание фильма №8"); + filmDbStorage.createFilm(film8); + User user1 = createUser("Логин №1", "Имя №1"); + userDbStorage.createUser(user1); + User user2 = createUser("Логин №2", "Имя №2"); + userDbStorage.createUser(user2); + filmDbStorage.addLike(film7.getId(), user1.getId()); + filmDbStorage.addLike(film8.getId(), user1.getId()); + filmDbStorage.addLike(film8.getId(), user2.getId()); + + int countOneLike = jdbcTemplate.queryForObject( + "SELECT COUNT(*) FROM film_likes WHERE user_id = ? AND film_id = ?", + Integer.class, user1.getId(), film7.getId() + ); + assertThat(countOneLike).isEqualTo(1); + int countAllLikeOneUser = jdbcTemplate.queryForObject( + "SELECT COUNT(*) FROM film_likes WHERE user_id = ?", + Integer.class, user1.getId() + ); + assertThat(countAllLikeOneUser).isEqualTo(2); + int countAllLikeAllUser = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM film_likes", Integer.class); + assertThat(countAllLikeAllUser).isEqualTo(3); + + filmDbStorage.removeLike(film7.getId(), user1.getId()); + + Integer countDeleteLike = jdbcTemplate.queryForObject( + "SELECT COUNT(*) FROM film_likes WHERE user_id = ? AND film_id = ?", + Integer.class, film7.getId(), user1.getId() + ); + assertThat(countDeleteLike).isZero(); + } + + @Test + void findTopFilmsByLikes() { + User user3 = createUser("Логин №3", "Имя №3"); + userDbStorage.createUser(user3); + User user4 = createUser("Логин №4", "Имя №4"); + userDbStorage.createUser(user4); + Film film9 = createFilm("Название фильма №9", "Описание фильма №9"); + filmDbStorage.createFilm(film9); + Film film10 = createFilm("Название фильма №10", "Описание фильма №10"); + filmDbStorage.createFilm(film10); + jdbcTemplate.update("INSERT INTO film_likes (user_id, film_id) VALUES (?, ?)", user3.getId(), film9.getId()); + jdbcTemplate.update("INSERT INTO film_likes (user_id, film_id) VALUES (?, ?)", user4.getId(), film9.getId()); + jdbcTemplate.update("INSERT INTO film_likes (user_id, film_id) VALUES (?, ?)", user3.getId(), film10.getId()); + + List resultListPopularFilms = filmDbStorage.getPopularFilms(10); + + assertThat(resultListPopularFilms).hasSize(2); + assertThat(resultListPopularFilms.get(0).getId()).isEqualTo(1L); + assertThat(resultListPopularFilms.get(1).getId()).isEqualTo(2L); + } +} \ No newline at end of file diff --git a/src/test/java/ru/yandex/practicum/filmorate/dao/GenreDbStorageTest.java b/src/test/java/ru/yandex/practicum/filmorate/dao/GenreDbStorageTest.java new file mode 100644 index 0000000..d2e7447 --- /dev/null +++ b/src/test/java/ru/yandex/practicum/filmorate/dao/GenreDbStorageTest.java @@ -0,0 +1,41 @@ +package ru.yandex.practicum.filmorate.dao; + +import lombok.RequiredArgsConstructor; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest; +import org.springframework.context.annotation.ComponentScan; +import ru.yandex.practicum.filmorate.model.Genre; +import ru.yandex.practicum.filmorate.storage.dal.dao.GenreDbStorage; + +import java.util.Collection; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; + +@JdbcTest +@AutoConfigureTestDatabase +@RequiredArgsConstructor(onConstructor_ = @Autowired) +@ComponentScan(basePackages = {"ru.yandex.practicum.filmorate.storage"}) +public class GenreDbStorageTest { + private final GenreDbStorage genreDbStorage; + + @Test + void getGenreById() { + Optional genre = genreDbStorage.getGenreById(1L); + assertThat(genre).isPresent(); + assertThat(genre.get().getName()).isEqualTo("Комедия"); + + Optional genreNot = genreDbStorage.getGenreById(100L); + assertThat(genreNot).isEmpty(); + } + + @Test + void getGenres() { + Collection genres = genreDbStorage.getGenres(); + assertThat(genres).hasSize(6); + assertThat(genres).extracting(Genre::getName) + .containsExactlyInAnyOrder("Комедия", "Драма", "Мультфильм", "Триллер", "Документальный", "Боевик"); + } +} diff --git a/src/test/java/ru/yandex/practicum/filmorate/dao/RatingDbStorageTest.java b/src/test/java/ru/yandex/practicum/filmorate/dao/RatingDbStorageTest.java new file mode 100644 index 0000000..37046b9 --- /dev/null +++ b/src/test/java/ru/yandex/practicum/filmorate/dao/RatingDbStorageTest.java @@ -0,0 +1,42 @@ +package ru.yandex.practicum.filmorate.dao; + +import lombok.RequiredArgsConstructor; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest; +import org.springframework.context.annotation.ComponentScan; +import ru.yandex.practicum.filmorate.model.Rating; +import ru.yandex.practicum.filmorate.storage.dal.dao.RatingDbStorage; + +import java.util.Collection; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; + +@JdbcTest +@AutoConfigureTestDatabase +@RequiredArgsConstructor(onConstructor_ = @Autowired) +@ComponentScan(basePackages = {"ru.yandex.practicum.filmorate.storage"}) +public class RatingDbStorageTest { + private final RatingDbStorage ratingDbStorage; + + @Test + void getRatingById() { + Optional rating = ratingDbStorage.getRatingById(1L); + assertThat(rating).isPresent(); + assertThat(rating.get().getName()).isEqualTo("G"); + + Optional ratingNot = ratingDbStorage.getRatingById(100L); + assertThat(ratingNot).isEmpty(); + } + + @Test + void getRatings() { + Collection ratings = ratingDbStorage.getRatings(); + assertThat(ratings).hasSize(5); + assertThat(ratings).extracting(Rating::getName) + .containsExactlyInAnyOrder("G", "PG", "PG-13", "R", "NC-17"); + } + +} diff --git a/src/test/java/ru/yandex/practicum/filmorate/dao/UserDbStorageTest.java b/src/test/java/ru/yandex/practicum/filmorate/dao/UserDbStorageTest.java new file mode 100644 index 0000000..1445ba6 --- /dev/null +++ b/src/test/java/ru/yandex/practicum/filmorate/dao/UserDbStorageTest.java @@ -0,0 +1,123 @@ +package ru.yandex.practicum.filmorate.dao; + +import lombok.RequiredArgsConstructor; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest; +import org.springframework.context.annotation.ComponentScan; +import ru.yandex.practicum.filmorate.model.User; +import ru.yandex.practicum.filmorate.storage.dal.dao.UserDbStorage; + +import java.time.LocalDate; +import java.util.Collection; +import java.util.Optional; +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; + +@JdbcTest +@AutoConfigureTestDatabase +@RequiredArgsConstructor(onConstructor_ = @Autowired) +@ComponentScan(basePackages = {"ru.yandex.practicum.filmorate.storage"}) +class UserDbStorageTest { + private final UserDbStorage userDbStorage; + + private User createUser(String login, String name) { + User user = new User(); + user.setEmail("email@mail.ru"); + user.setLogin(login); + user.setName(name); + user.setBirthday(LocalDate.of(1990, 2, 5)); + return user; + } + + @Test + void addAndFindUser() { + User user1 = createUser("Логин №1", "Имя №1"); + User savedUser = userDbStorage.createUser(user1); + Optional getUserfromDb = userDbStorage.getUserById(savedUser.getId()); + + assertThat(savedUser.getId()).isNotNull(); + assertThat(getUserfromDb).isPresent(); + assertThat(getUserfromDb.get().getLogin()).isEqualTo("Логин №1"); + assertThat(getUserfromDb.get().getName()).isEqualTo("Имя №1"); + + Optional user = userDbStorage.getUserById(100L); + assertThat(user).isEmpty(); + } + + @Test + void findAllUsers() { + User user2 = createUser("Логин №2", "Имя №2"); + userDbStorage.createUser(user2); + User user3 = createUser("Логин №3", "Имя №3"); + userDbStorage.createUser(user3); + User user4 = createUser("Логин №4", "Имя №4"); + userDbStorage.createUser(user4); + + Collection users = userDbStorage.getUsers(); + assertThat(users).hasSize(3); + } + + @Test + void updateUser() { + User user5 = createUser("Логин №5", "Имя №5"); + userDbStorage.createUser(user5); + user5.setLogin("Логин №5 новый"); + user5.setName("Имя №5 новое"); + Optional updatedUser = userDbStorage.updateUser(user5); + Optional loadedUser = userDbStorage.getUserById(user5.getId()); + + assertThat(updatedUser).isPresent(); + assertThat(updatedUser.get().getLogin()).isEqualTo("Логин №5 новый"); + assertThat(updatedUser.get().getName()).isEqualTo("Имя №5 новое"); + assertThat(loadedUser).isPresent(); + assertThat(loadedUser.get().getLogin()).isEqualTo("Логин №5 новый"); + assertThat(loadedUser.get().getName()).isEqualTo("Имя №5 новое"); + + User user6 = createUser("Логин №6", "Имя №6"); + userDbStorage.createUser(user6); + user6.setId(200L); + Optional result = userDbStorage.updateUser(user6); + assertThat(result).isEmpty(); + } + + @Test + void addAndDeleteFriendship() { + User user7 = createUser("Логин №7", "Имя №7"); + userDbStorage.createUser(user7); + User user8 = createUser("Логин №8", "Имя №8"); + userDbStorage.createUser(user8); + User user9 = createUser("Логин №9", "Имя №9"); + userDbStorage.createUser(user9); + userDbStorage.addFriend(user7.getId(), user8.getId()); + userDbStorage.addFriend(user9.getId(), user8.getId()); + userDbStorage.addFriend(user7.getId(), user9.getId()); + + Set friendsOfOne = userDbStorage.getFriends(user7.getId()); + assertThat(friendsOfOne.size()).isEqualTo(2L); + + userDbStorage.removeFriend(user7.getId(), user8.getId()); + + Set friendsOfOneNewSet = userDbStorage.getFriends(user7.getId()); + assertThat(friendsOfOneNewSet.size()).isEqualTo(1L); + } + + @Test + void findCommonFriends() { + User user10 = createUser("Логин №10", "Имя №10"); + userDbStorage.createUser(user10); + User user11 = createUser("Логин №11", "Имя №11"); + userDbStorage.createUser(user11); + User user12 = createUser("Логин №12", "Имя №12"); + userDbStorage.createUser(user12); + userDbStorage.addFriend(user10.getId(), user11.getId()); + userDbStorage.addFriend(user12.getId(), user11.getId()); + userDbStorage.addFriend(user10.getId(), user12.getId()); + + Set> commonFriends = userDbStorage.getCommonFriends(user10.getId(), user12.getId()); + assertThat(commonFriends.size()).isEqualTo(1L); + } + +}