diff --git a/pom.xml b/pom.xml index ba41373..3cd0ac8 100644 --- a/pom.xml +++ b/pom.xml @@ -21,6 +21,19 @@ org.springframework.boot spring-boot-starter-web + + org.springframework.boot + spring-boot-starter-jdbc + + + org.springframework.boot + spring-boot-starter-data-jpa + + + com.h2database + h2 + runtime + org.zalando logbook-spring-boot-starter diff --git a/src/main/java/ru/yandex/practicum/filmorate/FilmorateApplication.java b/src/main/java/ru/yandex/practicum/filmorate/FilmorateApplication.java index dca451b..79171d8 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/FilmorateApplication.java +++ b/src/main/java/ru/yandex/practicum/filmorate/FilmorateApplication.java @@ -5,6 +5,7 @@ @SpringBootApplication public class FilmorateApplication { + public static void main(String[] args) { SpringApplication.run(FilmorateApplication.class, args); } 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 b66662b..6a2ca9d 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java @@ -8,8 +8,6 @@ import ru.yandex.practicum.filmorate.service.FilmService; import java.util.Collection; -import java.util.Map; -import java.util.Set; @Slf4j @RestController @@ -24,6 +22,11 @@ public Collection getAll() { return filmService.getAll(); } + @GetMapping("/{id}") + public Film getFilmById(@PathVariable int id) { + return filmService.getFilmById(id); + } + @GetMapping("/popular") public Collection getFilmsByLike(@RequestParam(defaultValue = "10") int sizeFilms) { return filmService.getFilmsByLike(sizeFilms); @@ -40,7 +43,7 @@ public Film update(@Valid @RequestBody Film filmUpdate) { } @PutMapping("/{filmId}/like/{userId}") - public Map> addLike(@PathVariable int filmId, @PathVariable int userId) { + public boolean addLike(@PathVariable int filmId, @PathVariable int userId) { return filmService.addLike(filmId, userId); } @@ -49,5 +52,4 @@ public void removeLike(@PathVariable int filmId, @PathVariable int userId) { filmService.removeLike(filmId, userId); } - } 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..03bc124 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/GenreController.java @@ -0,0 +1,30 @@ +package ru.yandex.practicum.filmorate.controller; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +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; + +@Slf4j +@RestController +@RequiredArgsConstructor +public class GenreController { + + private final GenreService genreService; + + @GetMapping("/genres/{id}") + public Genre getById(@PathVariable Integer id) { + return genreService.getById(id); + } + + @GetMapping("/genres") + public Collection getAll() { + return genreService.getAll(); + } + +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/controller/MpaRatingController.java b/src/main/java/ru/yandex/practicum/filmorate/controller/MpaRatingController.java new file mode 100644 index 0000000..cf21496 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/MpaRatingController.java @@ -0,0 +1,31 @@ +package ru.yandex.practicum.filmorate.controller; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +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.MpaRating; +import ru.yandex.practicum.filmorate.service.MpaRatingService; + +import java.util.Collection; + +@Slf4j +@RestController +@RequestMapping("/mpa") +@RequiredArgsConstructor +public class MpaRatingController { + private final MpaRatingService mpaRatingService; + + @GetMapping("/{id}") + public MpaRating getById(@PathVariable Integer id) { + return mpaRatingService.getById(id); + } + + @GetMapping + public Collection getAll() { + return mpaRatingService.getAll(); + } + +} 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 7111058..c45f494 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java @@ -54,5 +54,4 @@ public void removeFriend(@PathVariable int userId, @PathVariable int friendsId) userService.removeFriend(userId, friendsId); } - } diff --git a/src/main/java/ru/yandex/practicum/filmorate/mappers/FilmRowMapper.java b/src/main/java/ru/yandex/practicum/filmorate/mappers/FilmRowMapper.java new file mode 100644 index 0000000..8a56fe8 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/mappers/FilmRowMapper.java @@ -0,0 +1,49 @@ +package ru.yandex.practicum.filmorate.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.Genre; +import ru.yandex.practicum.filmorate.storage.genre.GenreDbStorage; +import ru.yandex.practicum.filmorate.storage.mpa.MpaRatingDbStorage; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.List; + +@Component +public class FilmRowMapper implements RowMapper { + private final GenreDbStorage genreDbStorage; + private final MpaRatingDbStorage mpa; + + public FilmRowMapper(GenreDbStorage genreDbStorage, MpaRatingDbStorage mpa) { + this.genreDbStorage = genreDbStorage; + this.mpa = mpa; + } + + @Override + public Film mapRow(ResultSet resultSet, int rowNum) throws SQLException { + Timestamp releaseDate = resultSet.getTimestamp("release_date"); + String genreIds = resultSet.getString("genre_ids"); + List genres = new ArrayList<>(); + if (!genreIds.isEmpty()) { + String[] genreIdsArray = genreIds.split(",\\s*"); + for (String id : genreIdsArray) { + genres.add(genreDbStorage.getById(Integer.parseInt(id))); + } + } + + return Film.builder() + .id(resultSet.getInt("film_id")) + .name(resultSet.getString("film_name")) + .description(resultSet.getString("description")) + .releaseDate(releaseDate.toLocalDateTime().toLocalDate()) + .duration(resultSet.getInt("duration_minutes")) + .likesCount(resultSet.getInt("likes_count")) + .mpa(mpa.geById(resultSet.getInt("mpa_id"))) + .genres(genres) + .build(); + } +} 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 478d9e3..dc00fa7 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/model/Film.java +++ b/src/main/java/ru/yandex/practicum/filmorate/model/Film.java @@ -8,6 +8,7 @@ import lombok.Data; import java.time.LocalDate; +import java.util.List; @Builder @Data @@ -23,6 +24,7 @@ public class Film { @Positive @NotNull private Integer duration; - private int likes; - + private int likesCount; + private List genres; + private MpaRating mpa; } diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/Friendship.java b/src/main/java/ru/yandex/practicum/filmorate/model/Friendship.java new file mode 100644 index 0000000..e107872 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/model/Friendship.java @@ -0,0 +1,16 @@ +package ru.yandex.practicum.filmorate.model; + +import jakarta.validation.constraints.NotNull; +import lombok.Builder; +import lombok.Data; + +@Builder +@Data +public class Friendship { + @NotNull + private Integer firstUserId; + @NotNull + private Integer secondUserId; + @NotNull + private Boolean status; +} 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..b691aec --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/model/Genre.java @@ -0,0 +1,15 @@ +package ru.yandex.practicum.filmorate.model; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Builder; +import lombok.Data; + +@Builder +@Data +public class Genre { + private Integer id; + @NotNull + @NotBlank + private String name; +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/MpaRating.java b/src/main/java/ru/yandex/practicum/filmorate/model/MpaRating.java new file mode 100644 index 0000000..7bf14da --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/model/MpaRating.java @@ -0,0 +1,15 @@ +package ru.yandex.practicum.filmorate.model; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Builder; +import lombok.Data; + +@Builder +@Data +public class MpaRating { + private Integer id; + @NotNull + @NotBlank + private String name; +} 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 7622f2d..10266a5 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/model/User.java +++ b/src/main/java/ru/yandex/practicum/filmorate/model/User.java @@ -8,11 +8,11 @@ import lombok.Data; import java.time.LocalDate; +import java.util.Set; @Builder @Data public class User { - private Integer id; @NotNull @NotBlank @@ -25,5 +25,5 @@ public class User { @Past @NotNull private LocalDate birthday; - + private Set friends; } 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 2c49ed0..c29b4f6 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/service/FilmService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/FilmService.java @@ -1,42 +1,47 @@ package ru.yandex.practicum.filmorate.service; -import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; import ru.yandex.practicum.filmorate.model.Film; -import ru.yandex.practicum.filmorate.storage.film.InMemoryFilmStorage; +import ru.yandex.practicum.filmorate.storage.film.FilmStorage; import java.util.Collection; -import java.util.Map; -import java.util.Set; -@RequiredArgsConstructor @Service public class FilmService { - private final InMemoryFilmStorage inMemoryFilmStorage; + private final FilmStorage filmStorage; + + public FilmService(@Qualifier("filmDbStorage") FilmStorage filmStorage) { + this.filmStorage = filmStorage; + } public Collection getAll() { - return inMemoryFilmStorage.getAll(); + return filmStorage.getAll(); + } + + public Film getFilmById(int id) { + return filmStorage.getFilmById(id); } public Film create(Film film) { - return inMemoryFilmStorage.create(film); + return filmStorage.create(film); } public Film update(Film filmUpdate) { - return inMemoryFilmStorage.update(filmUpdate); + return filmStorage.update(filmUpdate); } - public Map> addLike(int filmId, int userId) { - return inMemoryFilmStorage.addLike(filmId, userId); + public boolean addLike(int filmId, int userId) { + return filmStorage.addLike(filmId, userId); } public void removeLike(int filmId, int userId) { - inMemoryFilmStorage.removeLike(filmId, userId); + filmStorage.removeLike(filmId, userId); } public Collection getFilmsByLike(Integer sizeFilms) { - return inMemoryFilmStorage.getFilmsByLike(sizeFilms); + return filmStorage.getFilmsByLike(sizeFilms); } } 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..55d5dbf --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/service/GenreService.java @@ -0,0 +1,23 @@ +package ru.yandex.practicum.filmorate.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import ru.yandex.practicum.filmorate.model.Genre; +import ru.yandex.practicum.filmorate.storage.genre.GenreDbStorage; + +import java.util.Collection; + +@Service +@RequiredArgsConstructor +public class GenreService { + private final GenreDbStorage genreDbStorage; + + public Genre getById(Integer id) { + return genreDbStorage.getById(id); + } + + public Collection getAll() { + return genreDbStorage.getAll(); + } + +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/MpaRatingService.java b/src/main/java/ru/yandex/practicum/filmorate/service/MpaRatingService.java new file mode 100644 index 0000000..efb731c --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/service/MpaRatingService.java @@ -0,0 +1,23 @@ +package ru.yandex.practicum.filmorate.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import ru.yandex.practicum.filmorate.model.MpaRating; +import ru.yandex.practicum.filmorate.storage.mpa.MpaRatingDbStorage; + +import java.util.Collection; + +@Service +@RequiredArgsConstructor +public class MpaRatingService { + private final MpaRatingDbStorage mpaRatingDbStorage; + + public MpaRating getById(Integer id) { + return mpaRatingDbStorage.geById(id); + } + + public Collection getAll() { + return mpaRatingDbStorage.getAll(); + } + +} 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 b6a5d6c..e668d29 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/service/UserService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/UserService.java @@ -1,47 +1,50 @@ package ru.yandex.practicum.filmorate.service; -import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; import ru.yandex.practicum.filmorate.model.User; -import ru.yandex.practicum.filmorate.storage.user.InMemoryUserStorage; +import ru.yandex.practicum.filmorate.storage.user.UserStorage; import java.util.Collection; import java.util.Map; import java.util.Set; -@RequiredArgsConstructor + @Service public class UserService { - private final InMemoryUserStorage inMemoryUserStorage; + public UserService(@Qualifier("userDbStorage") UserStorage userStorage) { + this.userStorage = userStorage; + } + + private final UserStorage userStorage; public Collection getAll() { - return inMemoryUserStorage.getAll(); + return userStorage.getAll(); } public User create(User user) { - return inMemoryUserStorage.create(user); + return userStorage.create(user); } public User update(User newUser) { - return inMemoryUserStorage.update(newUser); + return userStorage.update(newUser); } public Map> addToFriend(int userId, int friendsId) { - return inMemoryUserStorage.addToFriend(userId, friendsId); + return userStorage.addToFriend(userId, friendsId); } public void removeFriend(int userId, int friendsId) { - inMemoryUserStorage.removeFriend(userId, friendsId); + userStorage.removeFriend(userId, friendsId); } public Collection getFriendsUser(int userId) { - return inMemoryUserStorage.getFriendsUser(userId); + return userStorage.getFriendsUser(userId); } public Collection getMutualFriends(int userId, int friendsId) { - return inMemoryUserStorage.getMutualFriends(userId, friendsId); + return userStorage.getMutualFriends(userId, friendsId); } - } diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/film/FilmDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/film/FilmDbStorage.java new file mode 100644 index 0000000..7bc4487 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/film/FilmDbStorage.java @@ -0,0 +1,260 @@ +package ru.yandex.practicum.filmorate.storage.film; + +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.support.GeneratedKeyHolder; +import org.springframework.jdbc.support.KeyHolder; +import org.springframework.stereotype.Repository; +import ru.yandex.practicum.filmorate.exception.NotFoundException; +import ru.yandex.practicum.filmorate.exception.ValidationException; +import ru.yandex.practicum.filmorate.mappers.FilmRowMapper; +import ru.yandex.practicum.filmorate.model.Film; +import ru.yandex.practicum.filmorate.model.Genre; + +import java.sql.Date; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.time.LocalDate; +import java.util.Collection; +import java.util.List; +import java.util.Objects; + +@Repository +public class FilmDbStorage implements FilmStorage { + protected final JdbcTemplate jdbc; + private final FilmRowMapper rowMapper; + + public FilmDbStorage(JdbcTemplate jdbc, FilmRowMapper rowMapper) { + this.jdbc = jdbc; + this.rowMapper = rowMapper; + } + + private final LocalDate birthdayFilm = LocalDate.of(1895, 12, 28); + + private static final String GET_GENERAL = """ + SELECT + f.id AS film_id, + f.name AS film_name, + f.description, + f.release_date, + f.duration_minutes, + """; + + private static final String GET_BY_ID = GET_GENERAL + """ + f.likes_count, + f.rating AS mpa_id, + COUNT(fl.user_id) AS likes_count, + COALESCE(STRING_AGG(fg.genre_id, ', '), '') AS genre_ids + FROM + film f + LEFT JOIN + film_like fl ON f.id = fl.film_id + LEFT JOIN + film_genre fg ON f.id = fg.film_id + WHERE f.id = ? + GROUP BY + f.id, f.name, f.description, f.release_date, f.duration_minutes, f.likes_count, f.rating; + """; + + private static final String GET_ALL = GET_GENERAL + """ + f.likes_count, + f.rating AS mpa_id, + COUNT(fl.user_id) AS likes_count, + COALESCE(STRING_AGG(fg.genre_id, ', '), '') AS genre_ids + FROM + film f + LEFT JOIN + film_like fl ON f.id = fl.film_id + LEFT JOIN + film_genre fg ON f.id = fg.film_id + GROUP BY + f.id, f.name, f.description, f.release_date, f.duration_minutes, f.likes_count, f.rating + ORDER BY + likes_count DESC; + """; + + private static final String GET_FILM_BY_LIKES = GET_GENERAL + """ + f.rating AS mpa_id, + COUNT(fl.user_id) AS likes_count, + COALESCE(STRING_AGG(fg.genre_id, ', '), '') AS genre_ids + FROM film f + LEFT JOIN film_like fl ON f.id = fl.film_id + LEFT JOIN film_genre fg ON f.id = fg.film_id + GROUP BY f.id, f.name, f.description, f.release_date, f.duration_minutes, f.likes_count, f.rating + ORDER BY likes_count DESC + LIMIT ?; + """; + + private static final String INSERT_FILMS = "INSERT INTO film (name, description, release_date, " + + "duration_minutes, rating) VALUES (?, ?, ?, ?, ?)"; + private static final String INSERT_FILM_GENRE = "INSERT INTO film_genre (film_id, genre_id) VALUES (?, ?)"; + private static final String INSERT_LIKE = "INSERT INTO film_like (user_id, film_id) VALUES (?, ?)"; + private static final String UPDATE_FILM = """ + UPDATE film + SET name = ?, description = ?, release_date = ?, duration_minutes = ?, rating = ? + WHERE id = ? + """; + + private static final String CHECK_DUPLICATE_FILM_GENRE = """ + SELECT EXISTS ( + SELECT 1 + FROM film_genre + WHERE film_id = ? AND genre_id = ? + ) AS record_exists; + """; + + private static final String DELETE_LIKE = "DELETE FROM film_like WHERE user_id = ? AND film_id = ?"; + private static final String DELETE_FILM_GENRE = "DELETE FROM film_genre WHERE film_id = ?"; + + private static final String GET_MAX_ID_MPA = """ + SELECT EXISTS ( + SELECT 1 + FROM mpa + WHERE id = ? + ) AS id_exists; + """; + private static final String GET_MAX_ID_GENRE = """ + SELECT EXISTS ( + SELECT 1 + FROM genres + WHERE id = ? + ) AS id_exists; + """; + + @Override + public Collection getAll() { + return jdbc.query(GET_ALL, rowMapper); + } + + @Override + public Film getFilmById(int id) { + return jdbc.queryForObject(GET_BY_ID, rowMapper, id); + } + + @Override + public Film create(Film film) { + validateReleaseDate(film); + validateIdMpa(film.getMpa().getId()); + KeyHolder keyHolder = new GeneratedKeyHolder(); + + jdbc.update(connection -> { + PreparedStatement stmt = connection.prepareStatement(INSERT_FILMS, new String[]{"id"}); + stmt.setString(1, film.getName()); + stmt.setString(2, film.getDescription()); + stmt.setDate(3, Date.valueOf(film.getReleaseDate())); + stmt.setInt(4, film.getDuration()); + stmt.setInt(5, film.getMpa().getId()); + return stmt; + }, keyHolder); + + Integer id = Objects.requireNonNull(keyHolder.getKey()).intValue(); + film.setId(id); + + if (film.getGenres() != null) { + createGenre(film.getGenres(), id); + } + return getFilmById(id); + } + + @Override + public Film update(Film filmUpdate) { + int id = filmUpdate.getId(); + + jdbc.update(UPDATE_FILM, + filmUpdate.getName(), + filmUpdate.getDescription(), + filmUpdate.getReleaseDate(), + filmUpdate.getDuration(), + filmUpdate.getMpa().getId(), + id + ); + + if (filmUpdate.getGenres() != null) { + deleteGenre(id); + createGenre(filmUpdate.getGenres(), id); + } + return getFilmById(id); + } + + @Override + public boolean addLike(int filmId, int userId) { + KeyHolder keyHolder = new GeneratedKeyHolder(); + + return jdbc.update(connection -> { + PreparedStatement stmt = connection.prepareStatement(INSERT_LIKE); + stmt.setInt(1, userId); + stmt.setInt(2, filmId); + return stmt; + }, keyHolder) > 0; + } + + @Override + public void removeLike(Integer filmId, Integer userId) { + jdbc.update(DELETE_LIKE, userId, filmId); + } + + @Override + public Collection getFilmsByLike(Integer sizeFilms) { + return jdbc.query(GET_FILM_BY_LIKES, rowMapper, sizeFilms); + } + + private Boolean heckIdMapper(ResultSet resultSet, int rowNum) throws SQLException { + return resultSet.getBoolean("id_exists"); + } + + private Boolean checkDuplicateGenre(ResultSet resultSet, int rowNum) throws SQLException { + return resultSet.getBoolean("record_exists"); + } + + private void createGenre(List genre, Integer idFilm) { + for (Genre genres : genre) { + validateIdGenre(genres.getId()); + if (!validateDuplicateGenre(idFilm, genres.getId())) { + KeyHolder keyHolder = new GeneratedKeyHolder(); + + jdbc.update(connection -> { + PreparedStatement stmt2 = connection.prepareStatement(INSERT_FILM_GENRE); + stmt2.setInt(1, idFilm); + stmt2.setInt(2, genres.getId()); + return stmt2; + }, keyHolder); + } + } + } + + private void validateReleaseDate(Film film) { + if (film.getReleaseDate().isBefore(birthdayFilm)) { + String msg = "дата релиза — не раньше 28 декабря 1895 года"; + throw new ValidationException(msg); + } + } + + private void validateIdMpa(Integer id) { + Boolean result = jdbc.queryForObject(GET_MAX_ID_MPA, this::heckIdMapper, id); + if (result == null) { + throw new NotFoundException("Ваш id = " + id + " в таблице mpa не найден"); + } + if (!result) { + throw new NotFoundException("Ваш id = " + id + " в таблице mpa не найден"); + } + } + + private void validateIdGenre(Integer id) { + Boolean result = jdbc.queryForObject(GET_MAX_ID_GENRE, this::heckIdMapper, id); + if (result == null) { + throw new NotFoundException("Ваш id = " + id + " в таблице genres не найден"); + } + if (!result) { + throw new NotFoundException("Ваш id = " + id + " в таблице genres не найден"); + } + } + + private Boolean validateDuplicateGenre(Integer filmId, Integer genreId) { + return jdbc.queryForObject(CHECK_DUPLICATE_FILM_GENRE, this::checkDuplicateGenre, filmId, genreId); + } + + private void deleteGenre(Integer filmId) { + jdbc.update(DELETE_FILM_GENRE, filmId); + } + +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/film/FilmStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/film/FilmStorage.java index 9bca9ca..576426b 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/storage/film/FilmStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/film/FilmStorage.java @@ -3,8 +3,6 @@ import ru.yandex.practicum.filmorate.model.Film; import java.util.Collection; -import java.util.Map; -import java.util.Set; public interface FilmStorage { @@ -16,7 +14,7 @@ public interface FilmStorage { Film update(Film filmUpdate); - Map> addLike(int filmId, int userId); + boolean addLike(int filmId, int userId); void removeLike(Integer filmId, Integer userId); diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/film/InMemoryFilmStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/film/InMemoryFilmStorage.java index d02895f..cad5024 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/storage/film/InMemoryFilmStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/film/InMemoryFilmStorage.java @@ -29,7 +29,6 @@ public int getNextId() { return id++; } - @Override public Collection getAll() { log.info("GET, all films"); @@ -81,7 +80,7 @@ public Film update(Film filmUpdate) { } @Override - public Map> addLike(int filmId, int userId) { + public boolean addLike(int filmId, int userId) { inMemoryUserStorage.validateUserId(userId); validateFilmId(filmId); @@ -99,7 +98,7 @@ public Map> addLike(int filmId, int userId) { filmsLikes.put(filmId, likes); } - return Map.of(films.get(filmId), filmsLikes.get(filmId)); + return filmsLikes.get(filmId).contains(userId); } @Override @@ -121,7 +120,7 @@ public void removeLike(Integer filmId, Integer userId) { public Collection getFilmsByLike(Integer sizeFilms) { return films.values() .stream() - .sorted(Comparator.comparing(Film::getLikes).reversed()) + .sorted(Comparator.comparing(Film::getLikesCount).reversed()) .limit(sizeFilms) .collect(Collectors.toList()); } @@ -139,15 +138,15 @@ private boolean checkFilmsLikes(int id) { private void addLikeFilm(int filmId) { Film film = films.get(filmId); - film.setLikes(film.getLikes() + 1); + film.setLikesCount(film.getLikesCount() + 1); } private void removeLikeFilm(int filmId) { Film film = films.get(filmId); - int like = film.getLikes(); + int like = film.getLikesCount(); if (like != 0) { - film.setLikes(like - 1); + film.setLikesCount(like - 1); } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/genre/GenreDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/genre/GenreDbStorage.java new file mode 100644 index 0000000..89484a7 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/genre/GenreDbStorage.java @@ -0,0 +1,48 @@ +package ru.yandex.practicum.filmorate.storage.genre; + +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Repository; +import ru.yandex.practicum.filmorate.exception.NotFoundException; +import ru.yandex.practicum.filmorate.model.Genre; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Collection; +import java.util.List; + +@Repository +public class GenreDbStorage { + protected final JdbcTemplate jdbc; + + public GenreDbStorage(JdbcTemplate jdbc) { + this.jdbc = jdbc; + } + + private static final String GET_BY_ID = "SELECT * FROM genres WHERE id = ?;"; + private static final String CHECK_ID = "SELECT id FROM genres WHERE id = ?;"; + private static final String GET_ALL = "SELECT * FROM genres ORDER BY id ;"; + + public Genre getById(Integer id) { + validateId(id); + return jdbc.queryForObject(GET_BY_ID, this::getGenreMapper, id); + } + + public Collection getAll() { + return jdbc.query(GET_ALL, this::getGenreMapper); + } + + private Genre getGenreMapper(ResultSet resultSet, int rowNum) throws SQLException { + return Genre.builder() + .id(resultSet.getInt("id")) + .name(resultSet.getString("title")) + .build(); + } + + private void validateId(Integer id) { + List ids = jdbc.queryForList(CHECK_ID, Integer.class, id); + if (ids.isEmpty()) { + throw new NotFoundException("Жанр с id = " + id + " не найден"); + } + } + +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/mpa/MpaRatingDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/mpa/MpaRatingDbStorage.java new file mode 100644 index 0000000..6196bee --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/mpa/MpaRatingDbStorage.java @@ -0,0 +1,50 @@ +package ru.yandex.practicum.filmorate.storage.mpa; + +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Repository; +import ru.yandex.practicum.filmorate.exception.NotFoundException; +import ru.yandex.practicum.filmorate.model.MpaRating; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Collection; +import java.util.List; + +@Repository +public class MpaRatingDbStorage { + + protected final JdbcTemplate jdbc; + + public MpaRatingDbStorage(JdbcTemplate jdbc) { + this.jdbc = jdbc; + } + + private static final String GET_BY_ID = "SELECT * FROM mpa WHERE id = ?;"; + private static final String CHECK_ID = "SELECT id FROM mpa WHERE id = ?;"; + private static final String GET_ALL = "SELECT * FROM mpa ORDER BY id ;"; + + public MpaRating geById(Integer id) { + validateMpaId(id); + return jdbc.queryForObject(GET_BY_ID, this::getMpaRatingMapper, id); + } + + public Collection getAll() { + return jdbc.query(GET_ALL, this::getMpaRatingMapper); + } + + + private MpaRating getMpaRatingMapper(ResultSet resultSet, int rowNum) throws SQLException { + return MpaRating.builder() + .id(resultSet.getInt("id")) + .name(resultSet.getString("title")) + .build(); + } + + private void validateMpaId(Integer id) { + List ids = jdbc.queryForList(CHECK_ID, Integer.class, id); + if (ids.isEmpty()) { + throw new NotFoundException("id = " + id + " не найден"); + } + } + +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/user/InMemoryUserStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/user/InMemoryUserStorage.java index a91fc77..b3cb487 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/storage/user/InMemoryUserStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/user/InMemoryUserStorage.java @@ -141,7 +141,7 @@ public Collection getMutualFriends(int userId, int friendsId) { return mutualFriends; } - @Override + public void validateUserId(int id) { if (!users.containsKey(id)) { throw new NotFoundException("Пользователь с id: " + id + " не найден"); diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/user/UserDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/user/UserDbStorage.java new file mode 100644 index 0000000..cb21827 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/user/UserDbStorage.java @@ -0,0 +1,256 @@ +package ru.yandex.practicum.filmorate.storage.user; + +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.support.GeneratedKeyHolder; +import org.springframework.jdbc.support.KeyHolder; +import org.springframework.stereotype.Repository; +import ru.yandex.practicum.filmorate.exception.NotFoundException; +import ru.yandex.practicum.filmorate.exception.ValidationException; +import ru.yandex.practicum.filmorate.model.Friendship; +import ru.yandex.practicum.filmorate.model.User; + +import java.sql.Date; +import java.sql.*; +import java.util.*; +import java.util.stream.Collectors; + +@Repository +public class UserDbStorage implements UserStorage { + protected final JdbcTemplate jdbc; + + public UserDbStorage(JdbcTemplate jdbc) { + this.jdbc = jdbc; + } + + private static final String GET_EMAIL = "SELECT email FROM users WHERE email = ?;"; + private static final String GET_ID = "SELECT id FROM users WHERE id = ?;"; + private static final String INSERT_USERS = "INSERT INTO users (email, login, name, " + + "birthday) VALUES (?, ?, ?, ?)"; + private static final String INSERT_FRIENDSHIP = """ + INSERT INTO friendship (first_user_id, second_user_id, status) + VALUES (?, ?, ?) + """; + + private static final String UPDATE_STATUS_FRIENDSHIP = """ + UPDATE friendship + SET status ? + WHERE first_user_id = ?, second_user_id = ? + """; + + private static final String GET_FRIENDSHIP_BY_IDS = """ + SELECT * + FROM friendship + WHERE first_user_id = ? AND second_user_id = ?; + """; + + private static final String GET_FRIENDS_BY_ID = """ + SELECT STRING_AGG(second_user_id, ',') AS second_user_id + FROM friendship + WHERE first_user_id = ? AND status = true; + """; + + private static final String GET_MUTUAL_FRIENDS = """ + SELECT second_user_id AS friend_id + FROM friendship + WHERE first_user_id = ? + AND status = true + INTERSECT + SELECT second_user_id AS friend_id + FROM friendship + WHERE first_user_id = ? + AND status = true; + """; + + private static final String GET_BY_ID = "SELECT * FROM users WHERE id = ?;"; + private static final String GET_ALL = "SELECT * FROM users;"; + + private static final String UPDATE_USERS = """ + UPDATE users + SET email = ?, login = ?, name = ?, birthday = ? + WHERE id = ? + """; + private static final String DELETE_FRIENDSHIP = """ + DELETE FROM friendship + WHERE first_user_id = ? AND second_user_id = ? + """; + + @Override + public Collection getAll() { + return jdbc.query(GET_ALL, this::getUserMapper); + } + + @Override + public User getUserById(int id) { + return jdbc.queryForObject(GET_BY_ID, this::getUserMapper, id); + } + + @Override + public User create(User user) { + cloneSearchEmail(user.getEmail()); + checkOrAddUserName(user); + KeyHolder keyHolder = new GeneratedKeyHolder(); + + jdbc.update(connection -> { + PreparedStatement stmt = connection.prepareStatement(INSERT_USERS, new String[]{"id"}); + stmt.setString(1, user.getEmail()); + stmt.setString(2, user.getLogin()); + stmt.setString(3, user.getName()); + stmt.setDate(4, Date.valueOf(user.getBirthday())); + return stmt; + }, keyHolder); + + int id = Objects.requireNonNull(keyHolder.getKey()).intValue(); + user.setId(id); + + if ((user.getFriends() != null)) { + Set ids = user.getFriends(); + for (Integer idFriend : ids) { + addToFriend(id, idFriend); + } + } + + return jdbc.queryForObject(GET_BY_ID, this::getUserMapper, id); + } + + @Override + public User update(User updateUser) { + Integer id = updateUser.getId(); + validateUserId(id); + + jdbc.update(UPDATE_USERS, + updateUser.getEmail(), + updateUser.getLogin(), + updateUser.getName(), + updateUser.getBirthday(), + id + ); + + return jdbc.queryForObject(GET_BY_ID, this::getUserMapper, id); + } + + @Override + public Map> addToFriend(int userId, int friendsId) { + validateUserId(userId); + validateUserId(friendsId); + List friendships = jdbc.query(GET_FRIENDSHIP_BY_IDS, this::getFriendshipMapper, userId, friendsId); + KeyHolder keyHolder = new GeneratedKeyHolder(); + + if (friendships.isEmpty()) { + jdbc.update(connection -> { + PreparedStatement stmt = connection.prepareStatement(INSERT_FRIENDSHIP); + stmt.setInt(1, userId); + stmt.setInt(2, friendsId); + stmt.setBoolean(3, true); + return stmt; + }, keyHolder); + + jdbc.update(connection -> { + PreparedStatement stmt = connection.prepareStatement(INSERT_FRIENDSHIP); + stmt.setInt(1, friendsId); + stmt.setInt(2, userId); + stmt.setBoolean(3, false); + return stmt; + }, keyHolder); + } else { + Friendship friendship = friendships.getFirst(); + if (!friendship.getStatus()) { + jdbc.update(UPDATE_STATUS_FRIENDSHIP, + true, + friendship.getFirstUserId(), + friendship.getSecondUserId() + ); + } + } + + String friends = jdbc.queryForObject(GET_FRIENDS_BY_ID, String.class, userId); + Set friendIds = Arrays.stream(friends.split(",")) + .map(Integer::parseInt) + .collect(Collectors.toSet()); + User user = jdbc.queryForObject(GET_BY_ID, this::getUserMapper, userId); + return Map.of(user, friendIds); + + } + + @Override + public void removeFriend(int userId, int friendsId) { + validateUserId(userId); + validateUserId(friendsId); + + jdbc.update(DELETE_FRIENDSHIP, userId, friendsId); + } + + @Override + public Collection getFriendsUser(int userId) { + validateUserId(userId); + String friends = jdbc.queryForObject(GET_FRIENDS_BY_ID, String.class, userId); + List users = new ArrayList<>(); + + if (friends != null) { + String[] ids = friends.split(",\\s*"); + for (String id : ids) { + User user = jdbc.queryForObject(GET_BY_ID, this::getUserMapper, Integer.parseInt(id)); + users.add(user); + } + } + return users; + } + + @Override + public Collection getMutualFriends(int userId, int friendsId) { + validateUserId(userId); + validateUserId(friendsId); + + List friends = jdbc.queryForList(GET_MUTUAL_FRIENDS, Integer.class, userId, friendsId); + List users = new ArrayList<>(); + if (!friends.isEmpty()) { + for (Integer id : friends) { + User user = jdbc.queryForObject(GET_BY_ID, this::getUserMapper, id); + users.add(user); + } + } + return users; + } + + private User getUserMapper(ResultSet resultSet, int rowNum) throws SQLException { + Timestamp birthday = resultSet.getTimestamp("birthday"); + + return User.builder() + .id(resultSet.getInt("id")) + .email(resultSet.getString("email")) + .login(resultSet.getString("login")) + .name(resultSet.getString("name")) + .birthday(birthday.toLocalDateTime().toLocalDate()) + .build(); + } + + private Friendship getFriendshipMapper(ResultSet resultSet, int rowNum) throws SQLException { + + return Friendship.builder() + .firstUserId(resultSet.getInt("first_user_id")) + .secondUserId(resultSet.getInt("second_user_id")) + .status(resultSet.getBoolean("status")) + .build(); + } + + private void cloneSearchEmail(String newEmail) { + List emails = jdbc.queryForList(GET_EMAIL, String.class, newEmail); + if (!emails.isEmpty()) { + throw new ValidationException("Этот Email уже используется"); + } + } + + private void validateUserId(int id) { + List ids = jdbc.queryForList(GET_ID, Integer.class, id); + + if (ids.isEmpty()) { + throw new NotFoundException("Пользователь с id = " + id + " не найден"); + } + } + + private void checkOrAddUserName(User user) { + if (user.getName() == null) { + user.setName(user.getLogin()); + } + } + +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/user/UserStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/user/UserStorage.java index cc4c3e9..bd19de7 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/storage/user/UserStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/user/UserStorage.java @@ -24,5 +24,4 @@ public interface UserStorage { Collection getMutualFriends(int userId, int friendsId); - void validateUserId(int id); } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 147f265..30433ed 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1,11 @@ logging.level.org.zalando.logbook= TRACE +logging.level.org.springframework.jdbc=DEBUG + +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 + + + diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql new file mode 100644 index 0000000..385b622 --- /dev/null +++ b/src/main/resources/data.sql @@ -0,0 +1,16 @@ +MERGE INTO genres (id, title) KEY (title) +VALUES + (1, 'Комедия'), + (2, 'Драма'), + (3, 'Мультфильм'), + (4, 'Триллер'), + (5, 'Документальный'), + (6, 'Боевик'); + + MERGE INTO mpa (id, title) KEY (title) + VALUES + (1, 'G'), + (2, 'PG'), + (3, 'PG-13'), + (4, 'R'), + (5, 'NC-17'); \ 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..98b1e49 --- /dev/null +++ b/src/main/resources/schema.sql @@ -0,0 +1,62 @@ + +CREATE TABLE IF NOT EXISTS mpa ( + id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + title VARCHAR(100) NOT NULL UNIQUE +); + +CREATE TABLE IF NOT EXISTS film ( + id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + name VARCHAR(60) NOT NULL, + description VARCHAR(200), + release_date DATE NOT NULL, + duration_minutes INTEGER, + likes_count INTEGER DEFAULT 0, + rating INTEGER, + FOREIGN KEY (rating) REFERENCES mpa(id) +); + +CREATE TABLE IF NOT EXISTS users ( + id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + email VARCHAR(100) NOT NULL UNIQUE, + login VARCHAR(100) NOT NULL UNIQUE, + name VARCHAR(100), + birthday DATE NOT NULL +); + +CREATE TABLE IF NOT EXISTS genres ( + id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + title VARCHAR(100) NOT NULL UNIQUE +); + +CREATE TABLE IF NOT EXISTS film_genre ( + id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + film_id INTEGER NOT NULL, + genre_id INTEGER NOT NULL, + FOREIGN KEY (film_id) REFERENCES film(id) ON DELETE CASCADE, + FOREIGN KEY (genre_id) REFERENCES genres(id) ON DELETE CASCADE, + UNIQUE (film_id, genre_id) +); + + +CREATE TABLE IF NOT EXISTS film_like ( + id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + user_id INTEGER NOT NULL, + film_id INTEGER NOT NULL, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, + FOREIGN KEY (film_id) REFERENCES film(id) ON DELETE CASCADE, + UNIQUE (user_id, film_id) +); + + +CREATE TABLE IF NOT EXISTS friendship ( + id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + first_user_id INTEGER NOT NULL, + second_user_id INTEGER NOT NULL, + status boolean NOT NULL, + FOREIGN KEY (first_user_id) REFERENCES users(id) ON DELETE CASCADE, + FOREIGN KEY (second_user_id) REFERENCES users(id) ON DELETE CASCADE, + UNIQUE (first_user_id, second_user_id), + CHECK (first_user_id <> second_user_id) +); + + diff --git a/src/test/java/ru/yandex/practicum/filmorate/FilmorateApplicationTests.java b/src/test/java/ru/yandex/practicum/filmorate/FilmorateApplicationTests.java index 660412e..dc5cfdf 100644 --- a/src/test/java/ru/yandex/practicum/filmorate/FilmorateApplicationTests.java +++ b/src/test/java/ru/yandex/practicum/filmorate/FilmorateApplicationTests.java @@ -6,8 +6,8 @@ @SpringBootTest class FilmorateApplicationTests { - @Test - void contextLoads() { - } + @Test + void contextLoads() { + } } diff --git a/src/test/java/ru/yandex/practicum/filmorate/storage/film/FilmDbStorageTest.java b/src/test/java/ru/yandex/practicum/filmorate/storage/film/FilmDbStorageTest.java new file mode 100644 index 0000000..cdfd23e --- /dev/null +++ b/src/test/java/ru/yandex/practicum/filmorate/storage/film/FilmDbStorageTest.java @@ -0,0 +1,164 @@ +package ru.yandex.practicum.filmorate.storage.film; + +import lombok.RequiredArgsConstructor; +import org.assertj.core.api.Assertions; +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.Import; +import ru.yandex.practicum.filmorate.mappers.FilmRowMapper; +import ru.yandex.practicum.filmorate.model.Film; +import ru.yandex.practicum.filmorate.model.Genre; +import ru.yandex.practicum.filmorate.model.MpaRating; +import ru.yandex.practicum.filmorate.model.User; +import ru.yandex.practicum.filmorate.storage.genre.GenreDbStorage; +import ru.yandex.practicum.filmorate.storage.mpa.MpaRatingDbStorage; +import ru.yandex.practicum.filmorate.storage.user.UserDbStorage; + +import java.time.LocalDate; +import java.util.Collection; +import java.util.List; + +@JdbcTest +@AutoConfigureTestDatabase +@RequiredArgsConstructor(onConstructor_ = @Autowired) +@Import({FilmDbStorage.class, FilmRowMapper.class, GenreDbStorage.class, MpaRatingDbStorage.class, UserDbStorage.class}) +class FilmDbStorageTest { + + private final FilmDbStorage filmDbStorage; + private final UserDbStorage userDbStorage; + + Film film1 = Film.builder() + .name("iron man") + .description("Super description") + .releaseDate(LocalDate.of(1895, 12, 28)) + .duration(1) + .genres(List.of(Genre.builder().id(1).build())) + .mpa(MpaRating.builder().id(2).build()) + .build(); + + Film film2 = Film.builder() + .name("spider man") + .description("Super man") + .releaseDate(LocalDate.of(1896, 12, 29)) + .duration(1) + .genres(List.of(Genre.builder().id(3).build())) + .mpa(MpaRating.builder().id(4).build()) + .build(); + + User user1 = User.builder() + .email("yandex@mail.ru") + .login("turbo") + .birthday(LocalDate.of(2024, 12, 12)) + .build(); + + User user2 = User.builder() + .email("Arrange@mail.ru") + .login("Away") + .birthday(LocalDate.of(2024, 11, 11)) + .build(); + + @Test + void getAll() { +// Arrange + Film createdFilm1 = filmDbStorage.create(film1); + Film createdFilm2 = filmDbStorage.create(film2); + +// Act + Collection getAllFilms = filmDbStorage.getAll(); + +// Assert + Assertions.assertThat(getAllFilms.size()).isEqualTo(2); + Assertions.assertThat(getAllFilms).isEqualTo(List.of(createdFilm1, createdFilm2)); + } + + @Test + void getFilmById() { +// Arrange + Film createdFilm = filmDbStorage.create(film1); + +// Act + Film film = filmDbStorage.getFilmById(createdFilm.getId()); + +// Assert + Assertions.assertThat(createdFilm).isEqualTo(film); + } + + @Test + void create() { +// Act + Film createdFilm = filmDbStorage.create(film1); + +// Assert + Assertions.assertThat(createdFilm.getName()).isEqualTo("iron man"); + Assertions.assertThat(createdFilm.getDescription()).isEqualTo("Super description"); + Assertions.assertThat(createdFilm.getGenres().size()).isEqualTo(1); + Assertions.assertThat(createdFilm.getMpa().getId()).isEqualTo(2); + } + + @Test + void update() { +// Arrange + Film createdFilm = filmDbStorage.create(film1); + createdFilm.setName("The Pirates of Somalia"); + createdFilm.setDescription("tell their story"); + +// Act + Film updatedFilm = filmDbStorage.update(createdFilm); + +// Assert + Assertions.assertThat(createdFilm.getId()).isEqualTo(updatedFilm.getId()); + Assertions.assertThat(updatedFilm.getName()).isEqualTo("The Pirates of Somalia"); + Assertions.assertThat(updatedFilm.getDescription()).isEqualTo("tell their story"); + } + + @Test + void addLike() { +// Arrange + Film createdFilm = filmDbStorage.create(film1); + User createdUser = userDbStorage.create(user1); + +// Act + Boolean addLike = filmDbStorage.addLike(createdFilm.getId(), createdUser.getId()); + +// Assert + Assertions.assertThat(addLike).isEqualTo(true); + } + + @Test + void removeLike() { +// Arrange + Integer createdFilmId = filmDbStorage.create(film1).getId(); + Integer createdUserId = userDbStorage.create(user1).getId(); + Boolean addLike = filmDbStorage.addLike(createdFilmId, createdUserId); + Assertions.assertThat(addLike).isEqualTo(true); + +// Act + filmDbStorage.removeLike(createdFilmId, createdUserId); + Film film = filmDbStorage.getFilmById(createdFilmId); + +// Assert + Assertions.assertThat(film.getLikesCount()).isEqualTo(0); + } + + @Test + void getFilmsByLike() { +// Arrange + Integer createdFilmId1 = filmDbStorage.create(film1).getId(); + Integer createdFilmId2 = filmDbStorage.create(film2).getId(); + Integer createdUserId1 = userDbStorage.create(user1).getId(); + Integer createdUserId2 = userDbStorage.create(user2).getId(); + + Assertions.assertThat(filmDbStorage.addLike(createdFilmId1, createdUserId1)).isEqualTo(true); + Assertions.assertThat(filmDbStorage.addLike(createdFilmId1, createdUserId2)).isEqualTo(true); + +// Act + List filmsByLike = (List) filmDbStorage.getFilmsByLike(10); + +// Assert + Assertions.assertThat(filmsByLike.size()).isEqualTo(2); + Assertions.assertThat(filmsByLike.getFirst().getLikesCount()).isEqualTo(2); + Assertions.assertThat(filmsByLike.getLast().getLikesCount()).isEqualTo(0); + } +} \ No newline at end of file diff --git a/src/test/java/ru/yandex/practicum/filmorate/storage/genre/GenreDbStorageTest.java b/src/test/java/ru/yandex/practicum/filmorate/storage/genre/GenreDbStorageTest.java new file mode 100644 index 0000000..ea10a95 --- /dev/null +++ b/src/test/java/ru/yandex/practicum/filmorate/storage/genre/GenreDbStorageTest.java @@ -0,0 +1,49 @@ +package ru.yandex.practicum.filmorate.storage.genre; + +import lombok.RequiredArgsConstructor; +import org.assertj.core.api.Assertions; +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.Import; +import ru.yandex.practicum.filmorate.mappers.FilmRowMapper; +import ru.yandex.practicum.filmorate.model.Genre; +import ru.yandex.practicum.filmorate.storage.film.FilmDbStorage; +import ru.yandex.practicum.filmorate.storage.mpa.MpaRatingDbStorage; +import ru.yandex.practicum.filmorate.storage.user.UserDbStorage; + +import java.util.List; + +@JdbcTest +@AutoConfigureTestDatabase +@RequiredArgsConstructor(onConstructor_ = @Autowired) +@Import({FilmDbStorage.class, FilmRowMapper.class, GenreDbStorage.class, + MpaRatingDbStorage.class, UserDbStorage.class, GenreDbStorage.class}) +class GenreDbStorageTest { + + private final GenreDbStorage genreDbStorage; + + @Test + void getById() { +// Act + Genre genre = genreDbStorage.getById(1); + +// Assert + Assertions.assertThat(genre.getName()).isEqualTo("Комедия"); + } + + @Test + void getAll() { +// Act + List genres = (List) genreDbStorage.getAll(); + +// Assert + Assertions.assertThat(genres.get(0).getName()).isEqualTo("Комедия"); + Assertions.assertThat(genres.get(1).getName()).isEqualTo("Драма"); + Assertions.assertThat(genres.get(2).getName()).isEqualTo("Мультфильм"); + Assertions.assertThat(genres.get(3).getName()).isEqualTo("Триллер"); + Assertions.assertThat(genres.get(4).getName()).isEqualTo("Документальный"); + Assertions.assertThat(genres.get(5).getName()).isEqualTo("Боевик"); + } +} \ No newline at end of file diff --git a/src/test/java/ru/yandex/practicum/filmorate/storage/mpa/MpaRatingDbStorageTest.java b/src/test/java/ru/yandex/practicum/filmorate/storage/mpa/MpaRatingDbStorageTest.java new file mode 100644 index 0000000..0669637 --- /dev/null +++ b/src/test/java/ru/yandex/practicum/filmorate/storage/mpa/MpaRatingDbStorageTest.java @@ -0,0 +1,44 @@ +package ru.yandex.practicum.filmorate.storage.mpa; + +import lombok.RequiredArgsConstructor; +import org.assertj.core.api.Assertions; +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.Import; +import ru.yandex.practicum.filmorate.mappers.FilmRowMapper; +import ru.yandex.practicum.filmorate.model.MpaRating; +import ru.yandex.practicum.filmorate.storage.film.FilmDbStorage; +import ru.yandex.practicum.filmorate.storage.genre.GenreDbStorage; +import ru.yandex.practicum.filmorate.storage.user.UserDbStorage; + +import java.util.List; + +@JdbcTest +@AutoConfigureTestDatabase +@RequiredArgsConstructor(onConstructor_ = @Autowired) +@Import({FilmDbStorage.class, FilmRowMapper.class, GenreDbStorage.class, + MpaRatingDbStorage.class, UserDbStorage.class, MpaRatingDbStorage.class}) +class MpaRatingDbStorageTest { + + private final MpaRatingDbStorage mpaRatingDbStorage; + + @Test + void geById() { + MpaRating mpa = mpaRatingDbStorage.geById(1); + + Assertions.assertThat(mpa.getName()).isEqualTo("G"); + } + + @Test + void getAll() { + List ratings = (List) mpaRatingDbStorage.getAll(); + + Assertions.assertThat(ratings.get(0).getName()).isEqualTo("G"); + Assertions.assertThat(ratings.get(1).getName()).isEqualTo("PG"); + Assertions.assertThat(ratings.get(2).getName()).isEqualTo("PG-13"); + Assertions.assertThat(ratings.get(3).getName()).isEqualTo("R"); + Assertions.assertThat(ratings.get(4).getName()).isEqualTo("NC-17"); + } +} \ No newline at end of file diff --git a/src/test/java/ru/yandex/practicum/filmorate/storage/user/UserDbStorageTest.java b/src/test/java/ru/yandex/practicum/filmorate/storage/user/UserDbStorageTest.java new file mode 100644 index 0000000..19cdb8f --- /dev/null +++ b/src/test/java/ru/yandex/practicum/filmorate/storage/user/UserDbStorageTest.java @@ -0,0 +1,165 @@ +package ru.yandex.practicum.filmorate.storage.user; + +import lombok.RequiredArgsConstructor; +import org.assertj.core.api.Assertions; +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.Import; +import ru.yandex.practicum.filmorate.mappers.FilmRowMapper; +import ru.yandex.practicum.filmorate.model.User; +import ru.yandex.practicum.filmorate.storage.film.FilmDbStorage; +import ru.yandex.practicum.filmorate.storage.genre.GenreDbStorage; +import ru.yandex.practicum.filmorate.storage.mpa.MpaRatingDbStorage; + +import java.time.LocalDate; +import java.util.List; + +@JdbcTest +@AutoConfigureTestDatabase +@RequiredArgsConstructor(onConstructor_ = @Autowired) +@Import({FilmDbStorage.class, FilmRowMapper.class, GenreDbStorage.class, MpaRatingDbStorage.class, UserDbStorage.class}) +class UserDbStorageTest { + + private final UserDbStorage userDbStorage; + + User user1 = User.builder() + .email("yandex@mail.ru") + .login("turbo") + .birthday(LocalDate.of(2024, 12, 12)) + .build(); + + User user2 = User.builder() + .email("Arrange@mail.ru") + .login("Away") + .birthday(LocalDate.of(2024, 11, 11)) + .build(); + + User user3 = User.builder() + .email("bobr@mail.ru") + .login("bobr") + .birthday(LocalDate.of(2024, 11, 11)) + .build(); + + @Test + void getAll() { +// Arrange + User createdUser1 = userDbStorage.create(user1); + User createdUser2 = userDbStorage.create(user2); + +// Act + List allUsers = (List) userDbStorage.getAll(); + +// Assert + Assertions.assertThat(allUsers).isEqualTo(List.of(createdUser1, createdUser2)); + } + + @Test + void getUserById() { +// Arrange + User createdUser = userDbStorage.create(user1); + +// Act + User getUser = userDbStorage.getUserById(createdUser.getId()); + +// Assert + Assertions.assertThat(createdUser).isEqualTo(getUser); + } + + @Test + void create() { +// Act + User createdUser = userDbStorage.create(user1); + +// Assert + Assertions.assertThat(createdUser).isEqualTo(user1); + + } + + @Test + void update() { +// Arrange + User createdUser = userDbStorage.create(user1); + createdUser.setLogin("Titan"); + createdUser.setEmail("titan@mail.ru"); + +// Act + userDbStorage.update(createdUser); + User getUser = userDbStorage.getUserById(createdUser.getId()); + +// Assert + Assertions.assertThat(getUser.getLogin()).isEqualTo("Titan"); + Assertions.assertThat(getUser.getEmail()).isEqualTo("titan@mail.ru"); + } + + @Test + void addToFriend() { +// Arrange + Integer createdUserId1 = userDbStorage.create(user1).getId(); + User createdUser2 = userDbStorage.create(user2); + userDbStorage.addToFriend(createdUserId1, createdUser2.getId()); + +// Act + List firends = (List) userDbStorage.getFriendsUser(createdUserId1); + +// Assert + Assertions.assertThat(firends.size()).isEqualTo(1); + Assertions.assertThat(firends.getFirst()).isEqualTo(createdUser2); + } + + @Test + void removeFriend() { +// Arrange + Integer createdUserId1 = userDbStorage.create(user1).getId(); + User createdUser2 = userDbStorage.create(user2); + userDbStorage.addToFriend(createdUserId1, createdUser2.getId()); + List firends = (List) userDbStorage.getFriendsUser(createdUserId1); + Assertions.assertThat(firends.size()).isEqualTo(1); + Assertions.assertThat(firends.getFirst()).isEqualTo(createdUser2); + +// Act + userDbStorage.removeFriend(createdUserId1, createdUser2.getId()); + List firendsUpdate = (List) userDbStorage.getFriendsUser(createdUserId1); + +// Assert + Assertions.assertThat(firendsUpdate.size()).isEqualTo(0); + } + + @Test + void getFriendsUser() { + // Arrange + Integer createdUserId1 = userDbStorage.create(user1).getId(); + User createdUser2 = userDbStorage.create(user2); + userDbStorage.addToFriend(createdUserId1, createdUser2.getId()); + +// Act + List firends = (List) userDbStorage.getFriendsUser(createdUserId1); + +// Assert + Assertions.assertThat(firends.size()).isEqualTo(1); + Assertions.assertThat(firends.getFirst()).isEqualTo(createdUser2); + } + + @Test + void getMutualFriends() { +// Arrange + Integer userId1 = userDbStorage.create(user1).getId(); + Integer userId2 = userDbStorage.create(user2).getId(); + Integer userId3 = userDbStorage.create(user3).getId(); + + userDbStorage.addToFriend(userId1, userId2); + userDbStorage.addToFriend(userId1, userId3); + userDbStorage.addToFriend(userId2, userId3); + +// Act + List mutualFriends = (List) userDbStorage.getMutualFriends(userId1, userId2); + +// Assert + Assertions.assertThat(mutualFriends.size()).isEqualTo(1); + Assertions.assertThat(mutualFriends.getFirst()).isEqualTo(user3); + + } + + +} \ No newline at end of file