From e74a576bc27547f2e0e0e9ab63972fc0d12f875b Mon Sep 17 00:00:00 2001 From: Shaihattarov Ranis Date: Sat, 16 Jul 2022 17:47:45 +0300 Subject: [PATCH 1/5] =?UTF-8?q?=D0=98=D1=82=D0=BE=D0=B3=D0=BE=D0=B2=D0=B0?= =?UTF-8?q?=D1=8F=20=D0=B2=D0=B5=D1=80=D1=81=D0=B8=D1=8F=20=D0=BA=2011=20?= =?UTF-8?q?=D1=81=D0=BF=D1=80=D0=B8=D0=BD=D1=82=D1=83=20#1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 + img.png | Bin 0 -> 101421 bytes pom.xml | 9 + .../filmorate/controller/FilmController.java | 2 +- .../filmorate/controller/UserController.java | 2 +- .../practicum/filmorate/model/Film.java | 35 +++- .../filmorate/model/FriendshipStatus.java | 6 + .../practicum/filmorate/model/Genre.java | 10 ++ .../practicum/filmorate/model/MpaRating.java | 8 + .../filmorate/model/MpaRatingEnum.java | 9 + .../practicum/filmorate/model/User.java | 24 ++- .../filmorate/model/mapper/FilmRowMapper.java | 22 +++ .../filmorate/model/mapper/UserRowMapper.java | 22 +++ .../filmorate/service/FilmService.java | 41 +++-- .../filmorate/service/UserService.java | 69 +++----- .../filmorate/sortage/FilmDbStorage.java | 114 ++++++++++++ .../filmorate/sortage/FilmStorage.java | 16 +- .../sortage/InMemoryFilmStorage.java | 55 ------ .../sortage/InMemoryUserStorage.java | 44 ----- .../filmorate/sortage/UserDbStorage.java | 162 ++++++++++++++++++ .../filmorate/sortage/UserStorage.java | 14 +- src/main/resources/application.properties | 5 + src/main/resources/schema | 88 ++++++++++ .../controllerTest/FilmControllerTest.java | 2 +- .../filmorate/service/FilmServiceTest.java | 22 +++ .../filmorate/service/UserServiceTest.java | 7 + 26 files changed, 610 insertions(+), 180 deletions(-) create mode 100644 img.png create mode 100644 src/main/java/ru/yandex/practicum/filmorate/model/FriendshipStatus.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/model/Genre.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/model/MpaRating.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/model/MpaRatingEnum.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/model/mapper/FilmRowMapper.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/model/mapper/UserRowMapper.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/sortage/FilmDbStorage.java delete mode 100644 src/main/java/ru/yandex/practicum/filmorate/sortage/InMemoryFilmStorage.java delete mode 100644 src/main/java/ru/yandex/practicum/filmorate/sortage/InMemoryUserStorage.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/sortage/UserDbStorage.java create mode 100644 src/main/resources/schema create mode 100644 src/test/java/ru/yandex/practicum/filmorate/service/FilmServiceTest.java create mode 100644 src/test/java/ru/yandex/practicum/filmorate/service/UserServiceTest.java diff --git a/README.md b/README.md index 2cf454a..5d153e4 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,4 @@ # java-filmorate Template repository for Filmorate project. + +![](img.png) \ No newline at end of file diff --git a/img.png b/img.png new file mode 100644 index 0000000000000000000000000000000000000000..9f7fdec78f4324beeace8b5f27c7c2b38416d5e6 GIT binary patch literal 101421 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 zI%2junit test + + org.springframework.boot + spring-boot-starter-data-jdbc + + + com.h2database + h2 + runtime + 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 8fc3d90..d25fa63 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java @@ -56,7 +56,7 @@ public void deleteLike(@PathVariable Integer id, public Collection getPopularFilms( @RequestParam(defaultValue = "10",required = false) Integer count ) { - return filmService.getPopularFilms(filmService.findAll(), count); + return filmService.getPopularFilms(count); } @GetMapping("{id}") 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 59ba687..24d16d3 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java @@ -75,7 +75,7 @@ public List commonFriends(@PathVariable Integer id, @DeleteMapping public @Valid void delete(@Valid @RequestBody User user) throws ValidationException { - userService.deleteUser(user); + userService.deleteUser(user.getUserId()); } } 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 12a9553..031f68d 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/model/Film.java +++ b/src/main/java/ru/yandex/practicum/filmorate/model/Film.java @@ -1,18 +1,25 @@ package ru.yandex.practicum.filmorate.model; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; import lombok.Data; -import org.springframework.validation.annotation.Validated; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; -import java.time.Duration; import java.time.LocalDate; import java.util.HashSet; +import java.util.List; import java.util.Set; import javax.validation.constraints.*; @Data +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode public class Film { - private int id; + @JsonProperty("id") + private int filmId; @NotBlank(message = "Имя не может быть пустым!") private String name; @NotBlank(message = "Описание не может быть пустым!") @@ -21,14 +28,28 @@ public class Film { @Past(message = "Некорректная дата релиза") private LocalDate releaseDate; private long duration; - private Set likes; + //private Set likes; + private int rate; + private long likes; + private Genre genre; + @NotNull + private MpaRating mpa; - public Film(int id, String name, String description, LocalDate releaseDate, long duration) { - this.id = id; + public Film(int filmId, String name, String description, LocalDate releaseDate, long duration, int rate, MpaRating mpa) { + this.filmId = filmId; + this.name = name; + this.description = description; + this.releaseDate = releaseDate; + this.duration = duration; + this.rate = rate; + this.mpa = mpa; + } + + public Film(int filmId, String name, String description, LocalDate releaseDate, long duration, int rate) { + this.filmId = filmId; this.name = name; this.description = description; this.releaseDate = releaseDate; this.duration = duration; - this.likes = new HashSet<>(); } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/FriendshipStatus.java b/src/main/java/ru/yandex/practicum/filmorate/model/FriendshipStatus.java new file mode 100644 index 0000000..c1ed4ab --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/model/FriendshipStatus.java @@ -0,0 +1,6 @@ +package ru.yandex.practicum.filmorate.model; + +public enum FriendshipStatus { + CONFIRMED, + UNCONFIRMED +} 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..396e2ca --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/model/Genre.java @@ -0,0 +1,10 @@ +package ru.yandex.practicum.filmorate.model; + +public enum Genre { + COMEDY, + DRAMA, + CARTOON, + THRILLER, + DOCUMENTARY, + ACTION + } 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..c2f108e --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/model/MpaRating.java @@ -0,0 +1,8 @@ +package ru.yandex.practicum.filmorate.model; + +import lombok.Data; + +@Data +public class MpaRating { + private int id; +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/MpaRatingEnum.java b/src/main/java/ru/yandex/practicum/filmorate/model/MpaRatingEnum.java new file mode 100644 index 0000000..4852b0b --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/model/MpaRatingEnum.java @@ -0,0 +1,9 @@ +package ru.yandex.practicum.filmorate.model; + +public enum MpaRatingEnum { + G, + PG, + PG13, + R, + NC17 +} 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 44938df..a0a6a5d 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/model/User.java +++ b/src/main/java/ru/yandex/practicum/filmorate/model/User.java @@ -1,8 +1,12 @@ package ru.yandex.practicum.filmorate.model; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonRootName; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.AllArgsConstructor; import lombok.Data; import lombok.EqualsAndHashCode; -import org.springframework.validation.annotation.Validated; +import lombok.NoArgsConstructor; import javax.validation.constraints.*; import java.time.LocalDate; @@ -11,8 +15,11 @@ @Data @EqualsAndHashCode +@AllArgsConstructor +@NoArgsConstructor public class User { - private int id; + @JsonProperty("id") + private int userId; @NotBlank @Email(message = "Некоррктный email!") private String email; @@ -23,14 +30,23 @@ public class User { @Past(message = "Дата рождения не может быть в будущем!") private LocalDate birthday; private Set friends; + private FriendshipStatus friendshipStatus; - public User(int id, String email, String login, String name, LocalDate birthday) { - this.id = id; + public User(int userId, String email, String login, String name, LocalDate birthday, FriendshipStatus friendshipStatus) { + this.userId = userId; this.email = email; this.login = login; this.name = name; this.birthday = birthday; this.friends = new HashSet<>(); + this.friendshipStatus = friendshipStatus; } + public User(int userId, String email, String login, String name, LocalDate birthday) { + this.userId = userId; + this.email = email; + this.login = login; + this.name = name; + this.birthday = birthday; + } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/mapper/FilmRowMapper.java b/src/main/java/ru/yandex/practicum/filmorate/model/mapper/FilmRowMapper.java new file mode 100644 index 0000000..227971e --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/model/mapper/FilmRowMapper.java @@ -0,0 +1,22 @@ +package ru.yandex.practicum.filmorate.model.mapper; + +import org.springframework.jdbc.core.RowMapper; +import ru.yandex.practicum.filmorate.model.Film; +import ru.yandex.practicum.filmorate.model.User; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class FilmRowMapper implements RowMapper { + + @Override + public Film mapRow(ResultSet rs, int rowNum) throws SQLException { + Film film = new Film(rs.getInt("FILM_ID"), + rs.getString("NAME"), + rs.getString("DESCRIPTION"), + rs.getDate("RELEASE_DATE").toLocalDate(), + rs.getLong("DURATION"), + rs.getInt("RATE")); + return film; + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/mapper/UserRowMapper.java b/src/main/java/ru/yandex/practicum/filmorate/model/mapper/UserRowMapper.java new file mode 100644 index 0000000..08129be --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/model/mapper/UserRowMapper.java @@ -0,0 +1,22 @@ +package ru.yandex.practicum.filmorate.model.mapper; + +import org.springframework.jdbc.core.RowMapper; +import ru.yandex.practicum.filmorate.model.User; + + +import javax.swing.tree.TreePath; +import java.sql.ResultSet; +import java.sql.SQLException; + +public class UserRowMapper implements RowMapper { + + @Override + public User mapRow(ResultSet rs, int rowNum) throws SQLException { + User user = new User(rs.getInt("USER_ID"), + rs.getString("EMAIL"), + rs.getString("LOGIN"), + rs.getString("NAME"), + rs.getDate("BIRTHDAY").toLocalDate()); + return user; + } +} 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 27b410d..fa2f455 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/service/FilmService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/FilmService.java @@ -4,10 +4,12 @@ import org.springframework.stereotype.Service; import ru.yandex.practicum.filmorate.exception.FilmIdNotValidation; import ru.yandex.practicum.filmorate.exception.FilmNotExistsException; +import ru.yandex.practicum.filmorate.exception.UserIdNotValidation; import ru.yandex.practicum.filmorate.model.Film; import ru.yandex.practicum.filmorate.model.User; import ru.yandex.practicum.filmorate.sortage.FilmStorage; import ru.yandex.practicum.filmorate.exception.ValidationException; +import ru.yandex.practicum.filmorate.sortage.UserStorage; import java.time.LocalDate; import java.util.Collection; @@ -32,7 +34,7 @@ public Film create(Film film) throws ValidationException { if (film.getReleaseDate().isBefore(minReleaseDate) || film.getDuration() < 0) { throw new FilmNotExistsException("Некорректные данные! Дата релиза должна быть позднее 28.12.1985 года!"); } - if (film.getId() < 0) { + if (film.getFilmId() < 0) { throw new FilmIdNotValidation("Id не может быть отрицательный!"); } return filmStorage.create(film); @@ -42,39 +44,46 @@ public Film put(Film film) throws ValidationException { if (film.getReleaseDate().isBefore(minReleaseDate) || film.getDuration() < 0) { throw new FilmNotExistsException("" + "Некорректные данные!"); } - if (film.getId() < 0) { + if (film.getFilmId() < 0) { throw new FilmIdNotValidation("Id не может быть отрицательный!"); } return filmStorage.put(film); } public Film findById(Integer id) throws ValidationException { - if (!filmStorage.findAllId().contains(id)) { - throw new FilmIdNotValidation("Некорректные данные! Проверьте логин или email"); - } + // if (!filmStorage.findAllId().contains(id)) { + // throw new FilmIdNotValidation("Некорректные данные! Проверьте логин или email"); + // } return filmStorage.findFilm(id); } public void addLike(Film film, User user) { - Set filmLikes = film.getLikes(); - filmLikes.add(user.getId()); - film.setLikes(filmLikes); + if(user.getUserId() < 0) { + throw new UserIdNotValidation("dsad"); + } + if(film.getFilmId() < 0) { + throw new FilmIdNotValidation("dsas"); + } + + filmStorage.addLike(user.getUserId(), film.getFilmId()); } public void deleteLike(Film film, User user) { - Set filmLikes = film.getLikes(); - filmLikes.remove(user.getId()); - film.setLikes(filmLikes); + if(user.getUserId() < 0) { + throw new UserIdNotValidation("dsad"); + } + if(film.getFilmId() < 0) { + throw new FilmIdNotValidation("dsas"); + } + filmStorage.deleteLike(user.getUserId(), film.getFilmId()); } - public Collection getPopularFilms(Collection listFilms, Integer count) { - return listFilms.stream() - .sorted((o1, o2) -> o2.getLikes().size() - o1.getLikes().size()) - .limit(count).collect(Collectors.toList()); + public Collection getPopularFilms(Integer count) { + return filmStorage.getPopularFilms(count); } public void DeleteFilm(Film film) { - filmStorage.deleteFilm(film); + filmStorage.deleteFilm(film.getFilmId()); } 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 b87793e..c9b2c68 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/service/UserService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/UserService.java @@ -2,6 +2,8 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.data.relational.core.sql.In; import org.springframework.stereotype.Service; import ru.yandex.practicum.filmorate.exception.UserIdNotValidation; import ru.yandex.practicum.filmorate.model.User; @@ -18,8 +20,9 @@ public class UserService { UserStorage userStorage; + @Autowired - public UserService (UserStorage userStorage) { + public UserService (@Qualifier("userDbStorage") UserStorage userStorage) { this.userStorage = userStorage; } @@ -31,7 +34,7 @@ public User put(User user) throws ValidationException { if(user.getLogin().contains(" ")) { throw new ValidationException("Некорректные данные! Логин содержит пробелы"); } - if(user.getId() <= 0) { + if(user.getUserId() <= 0) { throw new UserIdNotValidation("Id не может быть отрицательный!"); } if (user.getName().isEmpty() || user.getName() == null) { @@ -44,7 +47,7 @@ public User create(User user) throws ValidationException { if (user.getLogin().contains(" ") ) { throw new ValidationException("Некорректные данные! Проверьте логин или email"); } - if(user.getId() < 0) { + if(user.getUserId() < 0) { throw new UserIdNotValidation("Id не может быть отрицательный!"); } if (user.getName().isEmpty() || user.getName() == null) { @@ -54,61 +57,43 @@ public User create(User user) throws ValidationException { } public User findById(Integer id) throws UserIdNotValidation { - if (!userStorage.findAllId().contains(id)) { - throw new UserIdNotValidation("Некорректные данные! Такого id не существует"); - } + //if (!userStorage.findAllId().contains(id)) { + // throw new UserIdNotValidation("Некорректные данные! Такого id не существует"); + // } return userStorage.findUser(id); } - public void addFriends(User user, User friends) { - - Set userList = user.getFriends(); - userList.add(friends.getId()); - user.setFriends(userList); - - Set friendsList = friends.getFriends(); - friendsList.add(user.getId()); - friends.setFriends(friendsList); + public void addFriends(User user, User friends) throws ValidationException { + if(!(findAll().contains(user) && findAll().contains(friends))) { + throw new ValidationException("dsadas"); + } + userStorage.addFriend(user.getUserId(), friends.getUserId()); } public void deleteFriends(User user, User friends) { - - Set userList = user.getFriends(); - userList.remove(friends.getId()); - user.setFriends(userList); - - Set friendsList = friends.getFriends(); - friendsList.remove(user.getId()); - friends.setFriends(friendsList); + userStorage.deleteFriends(user.getUserId(), friends.getUserId()); } public List findAllFriends(User user) { - List friends = new ArrayList<>(); - List friendsId = new ArrayList<>(user.getFriends()); - for (Integer integer : friendsId) { - friends.add(userStorage.findUser(integer)); + if (user == null) { + throw new UserIdNotValidation("se"); } - return friends; + return userStorage.findAllFriends(user.getUserId()); + } public List commonFriendsList(User user1, User user2) { - Set setFriendsUser1 = user1.getFriends(); - Set setFriendsUser2 = user2.getFriends(); - - List commonFriends = new ArrayList<>(); - - for (Integer l : setFriendsUser1) { - for (Integer s : setFriendsUser2) { - if (l.equals(s)) { - commonFriends.add(userStorage.findUser(l)); - } - } + if(user1.getUserId() < 0) { + throw new UserIdNotValidation("Id не может быть отрицательный!"); + } + if(user2.getUserId() < 0) { + throw new UserIdNotValidation("Id не может быть отрицательный!"); } - return commonFriends; + return userStorage.commonFriends(user1.getUserId(), user2.getUserId()); } - public void deleteUser(User user) { - userStorage.deleteUser(user); + public void deleteUser(Integer id) { + userStorage.deleteUser(id); } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/sortage/FilmDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/sortage/FilmDbStorage.java new file mode 100644 index 0000000..499a1eb --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/sortage/FilmDbStorage.java @@ -0,0 +1,114 @@ +package ru.yandex.practicum.filmorate.sortage; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.support.GeneratedKeyHolder; +import org.springframework.jdbc.support.KeyHolder; +import org.springframework.stereotype.Component; +import ru.yandex.practicum.filmorate.model.Film; +import ru.yandex.practicum.filmorate.model.User; +import ru.yandex.practicum.filmorate.model.mapper.FilmRowMapper; +import ru.yandex.practicum.filmorate.model.mapper.UserRowMapper; + +import java.sql.Date; +import java.sql.PreparedStatement; +import java.sql.Types; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; + + +@Slf4j +@Component +public class FilmDbStorage implements FilmStorage{ + + private final JdbcTemplate jdbcTemplate; + + @Autowired + public FilmDbStorage(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + + @Override + public Collection findAll() { + //log.debug("Текущее количество фильмов: {}", films.size()); + String sqlQuery = "select * from FILMS"; + return jdbcTemplate.query(sqlQuery, new FilmRowMapper()); + } + + @Override + public Film create(Film film) { + String sqlQuery = "insert into FILMS (NAME, DESCRIPTION, RELEASE_DATE, DURATION) values (?,?,?,?)"; + + KeyHolder keyHolder = new GeneratedKeyHolder(); + jdbcTemplate.update(connection -> { + PreparedStatement stmt = connection.prepareStatement(sqlQuery, new String[]{"FILM_ID"}); + stmt.setString(1, film.getName()); + stmt.setString(2, film.getDescription()); + stmt.setLong(4, film.getDuration()); + final LocalDate releaseDate = film.getReleaseDate(); + if (releaseDate == null) { + stmt.setNull(3, Types.DATE); + } else { + stmt.setDate(3, Date.valueOf(releaseDate)); + } + return stmt; + }, keyHolder); + film.setFilmId(keyHolder.getKey().intValue()); + return film; + } + + @Override + public Film put(Film film) { + String sqlQuery = "update FILMS set " + + "NAME = ?, DESCRIPTION = ?, RELEASE_DATE = ?, DURATION = ? " + + "where FILM_ID = ?"; + jdbcTemplate.update(sqlQuery + , film.getName() + , film.getDescription() + , film.getReleaseDate() + , film.getDuration() + , film.getFilmId()); + return film; + } + + @Override + public Film findFilm(Integer id) { + final String sqlQuery = "select * from FILMS where FILM_ID = ?"; + final List findFilms = jdbcTemplate.query(sqlQuery, new FilmRowMapper(), id); + return findFilms.get(0); + } + + @Override + public void deleteFilm(Integer id) { + String sqlQuery = "DELETE FROM FILMS WHERE film_id = ?"; + jdbcTemplate.update(sqlQuery, id); + } + + @Override + public void addLike(Integer userId, Integer filmId) { + final String sqlQuery = "INSERT INTO LIKE_FILMS (USER_ID, FILM_ID) values(?, ?)"; + jdbcTemplate.update(sqlQuery, + userId, filmId); + } + + @Override + public void deleteLike(Integer userId, Integer filmId) { + final String sqlQuery = "DELETE FROM LIKE_FILMS where USER_ID = ? AND FILM_ID = ?"; + jdbcTemplate.update(sqlQuery, + userId, filmId); + } + + @Override + public Collection getPopularFilms(Integer count) { + String sqlQuery = "select FILM_ID, count(USER_ID) as LIKES from LIKE_FILMS " + + "GROUP BY FILM_ID ORDER BY LIKES DESC LIMIT ?"; + return jdbcTemplate.query(sqlQuery, new FilmRowMapper(), count); + } + + +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/sortage/FilmStorage.java b/src/main/java/ru/yandex/practicum/filmorate/sortage/FilmStorage.java index 885ba1d..e0f2160 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/sortage/FilmStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/sortage/FilmStorage.java @@ -1,10 +1,15 @@ package ru.yandex.practicum.filmorate.sortage; +import org.springframework.data.relational.core.sql.In; import ru.yandex.practicum.filmorate.model.Film; import ru.yandex.practicum.filmorate.exception.ValidationException; +import ru.yandex.practicum.filmorate.model.User; +import javax.naming.InsufficientResourcesException; import java.util.Collection; import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; public interface FilmStorage { @@ -14,10 +19,17 @@ public interface FilmStorage { Film put(Film film) throws ValidationException; - List findAllId(); + //List findAllId(); Film findFilm(Integer id); - void deleteFilm(Film film); + void deleteFilm(Integer id); + + void addLike(Integer userId, Integer filmId); + + void deleteLike(Integer userId, Integer filmId); + + Collection getPopularFilms(Integer count); + } diff --git a/src/main/java/ru/yandex/practicum/filmorate/sortage/InMemoryFilmStorage.java b/src/main/java/ru/yandex/practicum/filmorate/sortage/InMemoryFilmStorage.java deleted file mode 100644 index 4d9d8b4..0000000 --- a/src/main/java/ru/yandex/practicum/filmorate/sortage/InMemoryFilmStorage.java +++ /dev/null @@ -1,55 +0,0 @@ -package ru.yandex.practicum.filmorate.sortage; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; -import ru.yandex.practicum.filmorate.model.Film; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; - - -@Slf4j -@Component -public class InMemoryFilmStorage implements FilmStorage{ - private final HashMap films = new HashMap<>(); - - - @Override - public Collection findAll() { - log.debug("Текущее количество фильмов: {}", films.size()); - return films.values(); - } - - @Override - public Film create(Film film) { - log.debug("Добавлен фильм: {}", film.getName()); - if (film.getId() == 0) { - film.setId(films.size() + 1); - films.put(film.getId(), film); - } else { - films.put(film.getId(), film); - } - return film; - } - - @Override - public Film put(Film film) { - films.put(film.getId(),film); - log.debug("Изменен фильм: {}", film.getName()); - return film; - } - - @Override - public List findAllId() { - return new ArrayList<>(films.keySet()); - } - - @Override - public Film findFilm(Integer id) { - return films.get(id); - } - - -} diff --git a/src/main/java/ru/yandex/practicum/filmorate/sortage/InMemoryUserStorage.java b/src/main/java/ru/yandex/practicum/filmorate/sortage/InMemoryUserStorage.java deleted file mode 100644 index 966bf35..0000000 --- a/src/main/java/ru/yandex/practicum/filmorate/sortage/InMemoryUserStorage.java +++ /dev/null @@ -1,44 +0,0 @@ -package ru.yandex.practicum.filmorate.sortage; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; -import ru.yandex.practicum.filmorate.model.User; -import ru.yandex.practicum.filmorate.exception.ValidationException; - -import java.util.*; - -@Slf4j -@Component -public class InMemoryUserStorage implements UserStorage{ - private final HashMap users = new HashMap<>(); - - public Collection findAll() { - log.debug("Текущее количество юзеров: {}", users.size()); - return users.values(); - } - - public User put(User user) { - log.debug("Изменен пользователь с логином: {}", user.getLogin()); - users.put(user.getId(),user); - return user; - } - - public User create(User user) throws ValidationException { - log.debug("Добавлен пользователь с логином: {}", user.getLogin()); - if(user.getId() == 0) { - user.setId(users.size()+1); - } - users.put(user.getId(), user); - return user; - } - - public List findAllId() { - return new ArrayList<>(users.keySet()); - } - - public User findUser(Integer id) { - return users.get(id); - } - - -} diff --git a/src/main/java/ru/yandex/practicum/filmorate/sortage/UserDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/sortage/UserDbStorage.java new file mode 100644 index 0000000..6a81a80 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/sortage/UserDbStorage.java @@ -0,0 +1,162 @@ +package ru.yandex.practicum.filmorate.sortage; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.relational.core.sql.In; +import org.springframework.jdbc.core.BeanPropertyRowMapper; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.support.GeneratedKeyHolder; +import org.springframework.jdbc.support.KeyHolder; +import org.springframework.jdbc.support.rowset.SqlRowSet; +import org.springframework.stereotype.Component; +import ru.yandex.practicum.filmorate.exception.UserIdNotValidation; +import ru.yandex.practicum.filmorate.exception.ValidationException; +import ru.yandex.practicum.filmorate.model.User; +import ru.yandex.practicum.filmorate.model.mapper.UserRowMapper; + +import java.sql.*; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.stream.Collectors; + +@Slf4j +@Component +public class UserDbStorage implements UserStorage { + //private final HashMap users = new HashMap<>(); + private final JdbcTemplate jdbcTemplate; + + @Autowired + public UserDbStorage(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + public Collection findAll() { + //log.debug("Текущее количество юзеров: {}", users.size()); + String sqlQuery = "select * from USERS"; + return jdbcTemplate.query(sqlQuery, new UserRowMapper()); + } + + + public User put(User user) { + log.debug("Изменен пользователь с логином: {}", user.getLogin()); + String sqlQuery = "update USERS set " + + "EMAIL = ?, LOGIN = ?, NAME = ?, BIRTHDAY = ? " + + "where USER_ID = ?"; + jdbcTemplate.update(sqlQuery + , user.getEmail() + , user.getLogin() + , user.getName() + , user.getBirthday() + , user.getUserId()); + return user; + } + + + /*public List findAllId() { + + String sqlQuery = "SELECT USER_ID FROM users"; + return new ArrayList<>(jdbcTemplate.query(sqlQuery, new UserRowMapper())); + }*/ + + public User findUser(Integer id) { + final String sqlQuery = "select * from USERS where USER_ID = ?"; + final List findUsers = jdbcTemplate.query(sqlQuery, new UserRowMapper(), id); + if(findUsers.isEmpty()){ + throw new UserIdNotValidation(" sdfd"); + } + return findUsers.get(0); + } + @Override + public void deleteUser(Integer id) { + String sqlQuery = ("DELETE FROM USERS WHERE user_id = ?"); + jdbcTemplate.update(sqlQuery, id); + } + + @Override + public User create(User user) { + String sqlQuery = "insert into USERS (EMAIL, LOGIN, NAME, BIRTHDAY) values (?,?,?,?)"; + + KeyHolder keyHolder = new GeneratedKeyHolder(); + jdbcTemplate.update(connection -> { + PreparedStatement stmt = connection.prepareStatement(sqlQuery, new String[]{"USER_ID"}); + stmt.setString(1, user.getEmail()); + stmt.setString(2, user.getLogin()); + stmt.setString(3, user.getName()); + final LocalDate birthday = user.getBirthday(); + if (birthday == null) { + stmt.setNull(4, Types.DATE); + } else { + stmt.setDate(4, Date.valueOf(birthday)); + } + return stmt; + }, keyHolder); + user.setUserId(keyHolder.getKey().intValue()); + return user; + } + + + public boolean containsUser(Integer id) { + if (findUser(id) != null) { + return true; + } else { + log.info("Пользователь с идентификатором {} не найден.", id); + throw new UserIdNotValidation(String.format("Пользователь с id %d не существует.", id)); + } + } + + public void addFriend(Integer userId, Integer friendId) { + String sqlQuery = "INSERT INTO USER_FRIEND (user_id, friend_id)" + + "VALUES (?, ?)"; + jdbcTemplate.update(sqlQuery, + userId, + friendId); + } + + public void deleteFriends(Integer userId, Integer friendId) { + String sqlQuery = "DELETE FROM USER_FRIEND WHERE USER_ID = ? AND FRIEND_ID = ?"; + jdbcTemplate.update(sqlQuery, + userId, + friendId); + } + + @Override + public List findAllFriends(Integer userId) { + SqlRowSet sqlRowSet = jdbcTemplate.queryForRowSet("SELECT friend_id FROM USER_FRIEND " + + "WHERE USER_ID = ?", userId); + List friends = new ArrayList<>(); + while (sqlRowSet.next()) { + User friend = findUser(sqlRowSet.getInt("friend_id")); + friends.add(friend); + } + return friends; + } + + @Override + public List commonFriends(Integer userId, Integer secondUserId) { + SqlRowSet sqlRowSetUser = jdbcTemplate.queryForRowSet("SELECT friend_id FROM USER_FRIEND " + + "WHERE USER_ID = ?", userId); + + SqlRowSet sqlRowSetSecondUser = jdbcTemplate.queryForRowSet("SELECT friend_id FROM USER_FRIEND " + + "WHERE USER_ID = ?", secondUserId); + + List friendsUser = new ArrayList<>(); + List friendsSecondUser = new ArrayList<>(); + + while (sqlRowSetUser.next()) { + friendsUser.add(sqlRowSetUser.getInt("friend_id")); + } + + while (sqlRowSetSecondUser.next()) { + friendsSecondUser.add(sqlRowSetSecondUser.getInt("friend_id")); + } + + friendsUser.retainAll(friendsSecondUser); + + return friendsUser.stream().map(this::findUser).collect(Collectors.toList()); + } + + +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/sortage/UserStorage.java b/src/main/java/ru/yandex/practicum/filmorate/sortage/UserStorage.java index c3af870..b9311b2 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/sortage/UserStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/sortage/UserStorage.java @@ -1,5 +1,6 @@ package ru.yandex.practicum.filmorate.sortage; +import org.springframework.data.relational.core.sql.In; import ru.yandex.practicum.filmorate.model.Film; import ru.yandex.practicum.filmorate.model.User; import ru.yandex.practicum.filmorate.exception.ValidationException; @@ -10,14 +11,13 @@ public interface UserStorage { Collection findAll(); - User put(User user) throws ValidationException; - User create(User user) throws ValidationException; - - List findAllId(); - + //List findAllId(); User findUser(Integer friendsId); - - void deleteUser(User user); + void deleteUser(Integer id); + void addFriend(Integer userId, Integer friendId); + void deleteFriends(Integer userId, Integer friendId); + List findAllFriends(Integer userId); + List commonFriends(Integer userId, Integer secondUserId); } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index c4df02b..5ec9f04 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1,6 @@ server.port=8088 +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/schema b/src/main/resources/schema new file mode 100644 index 0000000..6bd69a1 --- /dev/null +++ b/src/main/resources/schema @@ -0,0 +1,88 @@ +DROP TABLE IF EXISTS USERS; +DROP TABLE IF EXISTS USER_FRIEND; +DROP TABLE IF EXISTS FILMS; +DROP TABLE IF EXISTS LIKE_FILMS; +DROP TABLE IF EXISTS GENRE_FILM; + + +-- Creating tables +CREATE TABLE if not exists USERS +( + user_id int auto_increment unique, + email varchar, + login varchar, + name varchar, + birthday date, + primary key (user_id) +); + +CREATE TABLE if not exists FRIENDSHIP_STATUS +( + friendship_status_id int, + status varchar, + primary key (friendship_status_id) +); + +CREATE TABLE if not exists USER_FRIEND +( + user_id int references users (user_id), + friend_id int references users (user_id), + friendship_status_id int references FRIENDSHIP_STATUS (friendship_status_id) +); + + + +CREATE TABLE if not exists MPA_RATING +( + mpa_id int auto_increment unique, + mpa_rating varchar, + description varchar, + primary key (mpa_id) +); + + +CREATE TABLE if not exists FILMS +( + film_id int auto_increment unique, + name varchar, + description varchar, + release_date date, + duration int, + rate int, + mpa_id int references mpa_rating (mpa_id), + primary key (film_id) +); + +CREATE TABLE if not exists LIKE_FILMS +( + user_id int references users (user_id), + film_id int references films (film_id) +); + +CREATE TABLE if not exists GENRE +( + genre_id int auto_increment unique, + genre_name varchar, + primary key (genre_id) +); + +CREATE TABLE if not exists GENRE_FILM +( + film_id int references films (film_id), + genre_id int references genre (genre_id) +); + +MERGE INTO MPA_RATING (MPA_ID, MPA_RATING, DESCRIPTION) + values (1, 'G', 'У фильма нет возрастных ограничений'); +MERGE INTO MPA_RATING (MPA_ID, MPA_RATING, DESCRIPTION) + values (2, 'PG', 'Детям рекомендуется смотреть фильм с родителями'); +MERGE INTO MPA_RATING (MPA_ID, MPA_RATING, DESCRIPTION) + values (3, 'PG-13', 'Детям до 13 лет просмотр не желателен'); +MERGE INTO MPA_RATING (MPA_ID, MPA_RATING, DESCRIPTION) + values (4, 'R', 'Лицам до 17 лет просматривать фильм можно только в присутствии взрослого'); +MERGE INTO MPA_RATING (MPA_ID, MPA_RATING, DESCRIPTION) + values (5, 'NC-17', 'Лицам до 18 лет просмотр запрещён'); + + + + diff --git a/src/test/java/ru/yandex/practicum/filmorate/controllerTest/FilmControllerTest.java b/src/test/java/ru/yandex/practicum/filmorate/controllerTest/FilmControllerTest.java index b083245..4a37e88 100644 --- a/src/test/java/ru/yandex/practicum/filmorate/controllerTest/FilmControllerTest.java +++ b/src/test/java/ru/yandex/practicum/filmorate/controllerTest/FilmControllerTest.java @@ -28,7 +28,7 @@ public class FilmControllerTest { void beforeEach() { duration1 = 100; releaseDate = LocalDate.of(1900, 1, 1); - film1 = new Film(1,"name", "logi n sdad", releaseDate, duration1); + film1 = new Film(1,"name", "logi n sdad", releaseDate, duration1, 4); } @Test diff --git a/src/test/java/ru/yandex/practicum/filmorate/service/FilmServiceTest.java b/src/test/java/ru/yandex/practicum/filmorate/service/FilmServiceTest.java new file mode 100644 index 0000000..7a3c76e --- /dev/null +++ b/src/test/java/ru/yandex/practicum/filmorate/service/FilmServiceTest.java @@ -0,0 +1,22 @@ +package ru.yandex.practicum.filmorate.service; + +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.context.SpringBootTest; +import ru.yandex.practicum.filmorate.model.Film; +import ru.yandex.practicum.filmorate.sortage.FilmDbStorage; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest +@AutoConfigureTestDatabase +@RequiredArgsConstructor(onConstructor_ = @Autowired) +class FilmServiceTest { + private final FilmService filmService; + private final FilmDbStorage filmDbStorage; + + // Допишу тесты в след итерацию, есть сомнения в основной части проекта +} \ No newline at end of file diff --git a/src/test/java/ru/yandex/practicum/filmorate/service/UserServiceTest.java b/src/test/java/ru/yandex/practicum/filmorate/service/UserServiceTest.java new file mode 100644 index 0000000..945bf83 --- /dev/null +++ b/src/test/java/ru/yandex/practicum/filmorate/service/UserServiceTest.java @@ -0,0 +1,7 @@ +package ru.yandex.practicum.filmorate.service; + +import static org.junit.jupiter.api.Assertions.*; + +class UserServiceTest { + +} \ No newline at end of file From ad9eb28bb7c9bec03995827767f99708512b1931 Mon Sep 17 00:00:00 2001 From: Shaihattarov Ranis Date: Sun, 17 Jul 2022 21:36:27 +0300 Subject: [PATCH 2/5] =?UTF-8?q?=D0=98=D1=82=D0=BE=D0=B3=D0=BE=D0=B2=D0=B0?= =?UTF-8?q?=D1=8F=20=D0=B2=D0=B5=D1=80=D1=81=D0=B8=D1=8F=20=D0=BA=2011=20?= =?UTF-8?q?=D1=81=D0=BF=D1=80=D0=B8=D0=BD=D1=82=D1=83=20#2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../filmorate/controller/FilmController.java | 5 +- .../filmorate/service/FilmService.java | 27 +++--- .../filmorate/service/UserService.java | 20 ++--- .../filmorate/sortage/FilmDbStorage.java | 27 +----- .../filmorate/sortage/FilmStorage.java | 7 +- .../filmorate/sortage/LikeFilmDbStorage.java | 41 +++++++++ .../filmorate/sortage/UserDbStorage.java | 71 +-------------- .../sortage/UserFriendDbStorage.java | 76 ++++++++++++++++ .../filmorate/sortage/UserStorage.java | 4 - .../java/resources/application.properties | 6 ++ src/test/java/resources/schema | 15 ++++ .../filmorate/service/FilmServiceTest.java | 22 ----- .../filmorate/service/UserServiceTest.java | 7 -- .../filmorate/sortage/FilmDbStorageTest.java | 87 +++++++++++++++++++ .../sortage/LikeFilmDbStorageTest.java | 44 ++++++++++ .../filmorate/sortage/UserDbStorageTest.java | 78 +++++++++++++++++ .../sortage/UserFriendDbStorageTest.java | 40 +++++++++ 17 files changed, 419 insertions(+), 158 deletions(-) create mode 100644 src/main/java/ru/yandex/practicum/filmorate/sortage/LikeFilmDbStorage.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/sortage/UserFriendDbStorage.java create mode 100644 src/test/java/resources/application.properties create mode 100644 src/test/java/resources/schema delete mode 100644 src/test/java/ru/yandex/practicum/filmorate/service/FilmServiceTest.java delete mode 100644 src/test/java/ru/yandex/practicum/filmorate/service/UserServiceTest.java create mode 100644 src/test/java/ru/yandex/practicum/filmorate/sortage/FilmDbStorageTest.java create mode 100644 src/test/java/ru/yandex/practicum/filmorate/sortage/LikeFilmDbStorageTest.java create mode 100644 src/test/java/ru/yandex/practicum/filmorate/sortage/UserDbStorageTest.java create mode 100644 src/test/java/ru/yandex/practicum/filmorate/sortage/UserFriendDbStorageTest.java 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 d25fa63..22eed6d 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java @@ -11,6 +11,7 @@ import javax.validation.Valid; import java.util.Collection; +import java.util.List; @Slf4j @RestController @@ -26,7 +27,7 @@ public FilmController(FilmService filmService, UserService userService) { } @GetMapping - public Collection findAll() { + public List findAll() { return filmService.findAll(); } @@ -53,7 +54,7 @@ public void deleteLike(@PathVariable Integer id, } @GetMapping("/popular") - public Collection getPopularFilms( + public List getPopularFilms( @RequestParam(defaultValue = "10",required = false) Integer count ) { return filmService.getPopularFilms(count); 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 fa2f455..4ffbfd9 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/service/FilmService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/FilmService.java @@ -9,24 +9,28 @@ import ru.yandex.practicum.filmorate.model.User; import ru.yandex.practicum.filmorate.sortage.FilmStorage; import ru.yandex.practicum.filmorate.exception.ValidationException; +import ru.yandex.practicum.filmorate.sortage.LikeFilmDbStorage; import ru.yandex.practicum.filmorate.sortage.UserStorage; import java.time.LocalDate; import java.util.Collection; +import java.util.List; import java.util.Set; import java.util.stream.Collectors; @Service public class FilmService { FilmStorage filmStorage; + LikeFilmDbStorage likeFilmDbStorage; LocalDate minReleaseDate = LocalDate.of(1895, 12, 28); @Autowired - public FilmService (FilmStorage filmStorage) { + public FilmService (FilmStorage filmStorage, LikeFilmDbStorage likeFilmDbStorage) { this.filmStorage = filmStorage; + this.likeFilmDbStorage = likeFilmDbStorage; } - public Collection findAll() { + public List findAll() { return filmStorage.findAll(); } @@ -51,35 +55,32 @@ public Film put(Film film) throws ValidationException { } public Film findById(Integer id) throws ValidationException { - // if (!filmStorage.findAllId().contains(id)) { - // throw new FilmIdNotValidation("Некорректные данные! Проверьте логин или email"); - // } return filmStorage.findFilm(id); } public void addLike(Film film, User user) { if(user.getUserId() < 0) { - throw new UserIdNotValidation("dsad"); + throw new UserIdNotValidation("Id не может быть отрицательный!"); } if(film.getFilmId() < 0) { - throw new FilmIdNotValidation("dsas"); + throw new FilmIdNotValidation("Id не может быть отрицательный!"); } - filmStorage.addLike(user.getUserId(), film.getFilmId()); + likeFilmDbStorage.addLike(user.getUserId(), film.getFilmId()); } public void deleteLike(Film film, User user) { if(user.getUserId() < 0) { - throw new UserIdNotValidation("dsad"); + throw new UserIdNotValidation("Id не может быть отрицательный!"); } if(film.getFilmId() < 0) { - throw new FilmIdNotValidation("dsas"); + throw new FilmIdNotValidation("Id не может быть отрицательный!"); } - filmStorage.deleteLike(user.getUserId(), film.getFilmId()); + likeFilmDbStorage.deleteLike(user.getUserId(), film.getFilmId()); } - public Collection getPopularFilms(Integer count) { - return filmStorage.getPopularFilms(count); + public List getPopularFilms(Integer count) { + return likeFilmDbStorage.getPopularFilms(count); } public void DeleteFilm(Film film) { 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 c9b2c68..54de8ca 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/service/UserService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/UserService.java @@ -7,6 +7,7 @@ import org.springframework.stereotype.Service; import ru.yandex.practicum.filmorate.exception.UserIdNotValidation; import ru.yandex.practicum.filmorate.model.User; +import ru.yandex.practicum.filmorate.sortage.UserFriendDbStorage; import ru.yandex.practicum.filmorate.sortage.UserStorage; import ru.yandex.practicum.filmorate.exception.ValidationException; @@ -19,11 +20,13 @@ @Service public class UserService { UserStorage userStorage; + UserFriendDbStorage userFriendDbStorage; @Autowired - public UserService (@Qualifier("userDbStorage") UserStorage userStorage) { + public UserService (UserStorage userStorage, UserFriendDbStorage userFriendDbStorage) { this.userStorage = userStorage; + this.userFriendDbStorage = userFriendDbStorage; } public Collection findAll() { @@ -57,28 +60,25 @@ public User create(User user) throws ValidationException { } public User findById(Integer id) throws UserIdNotValidation { - //if (!userStorage.findAllId().contains(id)) { - // throw new UserIdNotValidation("Некорректные данные! Такого id не существует"); - // } return userStorage.findUser(id); } public void addFriends(User user, User friends) throws ValidationException { if(!(findAll().contains(user) && findAll().contains(friends))) { - throw new ValidationException("dsadas"); + throw new ValidationException("Такого пользователя не существует"); } - userStorage.addFriend(user.getUserId(), friends.getUserId()); + userFriendDbStorage.addFriend(user.getUserId(), friends.getUserId()); } public void deleteFriends(User user, User friends) { - userStorage.deleteFriends(user.getUserId(), friends.getUserId()); + userFriendDbStorage.deleteFriends(user.getUserId(), friends.getUserId()); } public List findAllFriends(User user) { if (user == null) { - throw new UserIdNotValidation("se"); + throw new UserIdNotValidation("Такого пользователя не существует"); } - return userStorage.findAllFriends(user.getUserId()); + return userFriendDbStorage.findAllFriends(user.getUserId()); } @@ -89,7 +89,7 @@ public List commonFriendsList(User user1, User user2) { if(user2.getUserId() < 0) { throw new UserIdNotValidation("Id не может быть отрицательный!"); } - return userStorage.commonFriends(user1.getUserId(), user2.getUserId()); + return userFriendDbStorage.commonFriends(user1.getUserId(), user2.getUserId()); } public void deleteUser(Integer id) { diff --git a/src/main/java/ru/yandex/practicum/filmorate/sortage/FilmDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/sortage/FilmDbStorage.java index 499a1eb..5743285 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/sortage/FilmDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/sortage/FilmDbStorage.java @@ -6,6 +6,7 @@ import org.springframework.jdbc.support.GeneratedKeyHolder; import org.springframework.jdbc.support.KeyHolder; import org.springframework.stereotype.Component; +import org.springframework.stereotype.Service; import ru.yandex.practicum.filmorate.model.Film; import ru.yandex.practicum.filmorate.model.User; import ru.yandex.practicum.filmorate.model.mapper.FilmRowMapper; @@ -22,7 +23,7 @@ @Slf4j -@Component +@Service public class FilmDbStorage implements FilmStorage{ private final JdbcTemplate jdbcTemplate; @@ -34,8 +35,7 @@ public FilmDbStorage(JdbcTemplate jdbcTemplate) { @Override - public Collection findAll() { - //log.debug("Текущее количество фильмов: {}", films.size()); + public List findAll() { String sqlQuery = "select * from FILMS"; return jdbcTemplate.query(sqlQuery, new FilmRowMapper()); } @@ -89,26 +89,5 @@ public void deleteFilm(Integer id) { jdbcTemplate.update(sqlQuery, id); } - @Override - public void addLike(Integer userId, Integer filmId) { - final String sqlQuery = "INSERT INTO LIKE_FILMS (USER_ID, FILM_ID) values(?, ?)"; - jdbcTemplate.update(sqlQuery, - userId, filmId); - } - - @Override - public void deleteLike(Integer userId, Integer filmId) { - final String sqlQuery = "DELETE FROM LIKE_FILMS where USER_ID = ? AND FILM_ID = ?"; - jdbcTemplate.update(sqlQuery, - userId, filmId); - } - - @Override - public Collection getPopularFilms(Integer count) { - String sqlQuery = "select FILM_ID, count(USER_ID) as LIKES from LIKE_FILMS " + - "GROUP BY FILM_ID ORDER BY LIKES DESC LIMIT ?"; - return jdbcTemplate.query(sqlQuery, new FilmRowMapper(), count); - } - } diff --git a/src/main/java/ru/yandex/practicum/filmorate/sortage/FilmStorage.java b/src/main/java/ru/yandex/practicum/filmorate/sortage/FilmStorage.java index e0f2160..7974386 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/sortage/FilmStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/sortage/FilmStorage.java @@ -13,7 +13,7 @@ public interface FilmStorage { - Collection findAll(); + List findAll(); Film create(Film film) throws ValidationException; @@ -25,11 +25,6 @@ public interface FilmStorage { void deleteFilm(Integer id); - void addLike(Integer userId, Integer filmId); - - void deleteLike(Integer userId, Integer filmId); - - Collection getPopularFilms(Integer count); } diff --git a/src/main/java/ru/yandex/practicum/filmorate/sortage/LikeFilmDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/sortage/LikeFilmDbStorage.java new file mode 100644 index 0000000..b255a77 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/sortage/LikeFilmDbStorage.java @@ -0,0 +1,41 @@ +package ru.yandex.practicum.filmorate.sortage; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Component; +import ru.yandex.practicum.filmorate.model.Film; +import ru.yandex.practicum.filmorate.model.mapper.FilmRowMapper; + +import java.util.Collection; +import java.util.List; + +@Component +public class LikeFilmDbStorage { + private final JdbcTemplate jdbcTemplate; + + @Autowired + public LikeFilmDbStorage(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + + public void addLike(Integer userId, Integer filmId) { + final String sqlQuery = "INSERT INTO LIKE_FILMS (USER_ID, FILM_ID) values(?, ?)"; + jdbcTemplate.update(sqlQuery, + userId, filmId); + } + + + public void deleteLike(Integer userId, Integer filmId) { + final String sqlQuery = "DELETE FROM LIKE_FILMS where USER_ID = ? AND FILM_ID = ?"; + jdbcTemplate.update(sqlQuery, + userId, filmId); + } + + + public List getPopularFilms(Integer count) { + String sqlQuery = "select FILM_ID, count(USER_ID) as LIKES from LIKE_FILMS " + + "GROUP BY FILM_ID ORDER BY LIKES DESC LIMIT ?"; + return jdbcTemplate.query(sqlQuery, new FilmRowMapper(), count); + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/sortage/UserDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/sortage/UserDbStorage.java index 6a81a80..1d39820 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/sortage/UserDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/sortage/UserDbStorage.java @@ -25,7 +25,7 @@ @Slf4j @Component public class UserDbStorage implements UserStorage { - //private final HashMap users = new HashMap<>(); + private final JdbcTemplate jdbcTemplate; @Autowired @@ -54,13 +54,6 @@ public User put(User user) { return user; } - - /*public List findAllId() { - - String sqlQuery = "SELECT USER_ID FROM users"; - return new ArrayList<>(jdbcTemplate.query(sqlQuery, new UserRowMapper())); - }*/ - public User findUser(Integer id) { final String sqlQuery = "select * from USERS where USER_ID = ?"; final List findUsers = jdbcTemplate.query(sqlQuery, new UserRowMapper(), id); @@ -97,66 +90,4 @@ public User create(User user) { return user; } - - public boolean containsUser(Integer id) { - if (findUser(id) != null) { - return true; - } else { - log.info("Пользователь с идентификатором {} не найден.", id); - throw new UserIdNotValidation(String.format("Пользователь с id %d не существует.", id)); - } - } - - public void addFriend(Integer userId, Integer friendId) { - String sqlQuery = "INSERT INTO USER_FRIEND (user_id, friend_id)" + - "VALUES (?, ?)"; - jdbcTemplate.update(sqlQuery, - userId, - friendId); - } - - public void deleteFriends(Integer userId, Integer friendId) { - String sqlQuery = "DELETE FROM USER_FRIEND WHERE USER_ID = ? AND FRIEND_ID = ?"; - jdbcTemplate.update(sqlQuery, - userId, - friendId); - } - - @Override - public List findAllFriends(Integer userId) { - SqlRowSet sqlRowSet = jdbcTemplate.queryForRowSet("SELECT friend_id FROM USER_FRIEND " + - "WHERE USER_ID = ?", userId); - List friends = new ArrayList<>(); - while (sqlRowSet.next()) { - User friend = findUser(sqlRowSet.getInt("friend_id")); - friends.add(friend); - } - return friends; - } - - @Override - public List commonFriends(Integer userId, Integer secondUserId) { - SqlRowSet sqlRowSetUser = jdbcTemplate.queryForRowSet("SELECT friend_id FROM USER_FRIEND " + - "WHERE USER_ID = ?", userId); - - SqlRowSet sqlRowSetSecondUser = jdbcTemplate.queryForRowSet("SELECT friend_id FROM USER_FRIEND " + - "WHERE USER_ID = ?", secondUserId); - - List friendsUser = new ArrayList<>(); - List friendsSecondUser = new ArrayList<>(); - - while (sqlRowSetUser.next()) { - friendsUser.add(sqlRowSetUser.getInt("friend_id")); - } - - while (sqlRowSetSecondUser.next()) { - friendsSecondUser.add(sqlRowSetSecondUser.getInt("friend_id")); - } - - friendsUser.retainAll(friendsSecondUser); - - return friendsUser.stream().map(this::findUser).collect(Collectors.toList()); - } - - } diff --git a/src/main/java/ru/yandex/practicum/filmorate/sortage/UserFriendDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/sortage/UserFriendDbStorage.java new file mode 100644 index 0000000..4723214 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/sortage/UserFriendDbStorage.java @@ -0,0 +1,76 @@ +package ru.yandex.practicum.filmorate.sortage; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.support.rowset.SqlRowSet; +import org.springframework.stereotype.Component; +import ru.yandex.practicum.filmorate.model.User; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +@Component +public class UserFriendDbStorage { + private final JdbcTemplate jdbcTemplate; + private UserDbStorage userDbStorage; + + @Autowired + public UserFriendDbStorage(JdbcTemplate jdbcTemplate, UserDbStorage userDbStorage) { + this.jdbcTemplate = jdbcTemplate; + this.userDbStorage = userDbStorage; + } + + public void addFriend(Integer userId, Integer friendId) { + String sqlQuery = "INSERT INTO USER_FRIEND (user_id, friend_id)" + + "VALUES (?, ?)"; + jdbcTemplate.update(sqlQuery, + userId, + friendId); + } + + public void deleteFriends(Integer userId, Integer friendId) { + String sqlQuery = "DELETE FROM USER_FRIEND WHERE USER_ID = ? AND FRIEND_ID = ?"; + jdbcTemplate.update(sqlQuery, + userId, + friendId); + } + + + public List findAllFriends(Integer userId) { + SqlRowSet sqlRowSet = jdbcTemplate.queryForRowSet("SELECT friend_id FROM USER_FRIEND " + + "WHERE USER_ID = ?", userId); + List friends = new ArrayList<>(); + while (sqlRowSet.next()) { + User friend = userDbStorage.findUser(sqlRowSet.getInt("friend_id")); + friends.add(friend); + } + return friends; + } + + + public List commonFriends(Integer userId, Integer secondUserId) { + SqlRowSet sqlRowSetUser = jdbcTemplate.queryForRowSet("SELECT friend_id FROM USER_FRIEND " + + "WHERE USER_ID = ?", userId); + + SqlRowSet sqlRowSetSecondUser = jdbcTemplate.queryForRowSet("SELECT friend_id FROM USER_FRIEND " + + "WHERE USER_ID = ?", secondUserId); + + List friendsUser = new ArrayList<>(); + List friendsSecondUser = new ArrayList<>(); + + while (sqlRowSetUser.next()) { + friendsUser.add(sqlRowSetUser.getInt("friend_id")); + } + + while (sqlRowSetSecondUser.next()) { + friendsSecondUser.add(sqlRowSetSecondUser.getInt("friend_id")); + } + + friendsUser.retainAll(friendsSecondUser); + + return friendsUser.stream().map(s -> userDbStorage.findUser(s)).collect(Collectors.toList()); + } + + +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/sortage/UserStorage.java b/src/main/java/ru/yandex/practicum/filmorate/sortage/UserStorage.java index b9311b2..0c3fcc6 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/sortage/UserStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/sortage/UserStorage.java @@ -16,8 +16,4 @@ public interface UserStorage { //List findAllId(); User findUser(Integer friendsId); void deleteUser(Integer id); - void addFriend(Integer userId, Integer friendId); - void deleteFriends(Integer userId, Integer friendId); - List findAllFriends(Integer userId); - List commonFriends(Integer userId, Integer secondUserId); } diff --git a/src/test/java/resources/application.properties b/src/test/java/resources/application.properties new file mode 100644 index 0000000..5ec9f04 --- /dev/null +++ b/src/test/java/resources/application.properties @@ -0,0 +1,6 @@ +server.port=8088 +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/test/java/resources/schema b/src/test/java/resources/schema new file mode 100644 index 0000000..98593af --- /dev/null +++ b/src/test/java/resources/schema @@ -0,0 +1,15 @@ + +INSERT INTO FILMS (NAME, DESCRIPTION, RELEASE_DATE, DURATION, RATE, MPA_ID) +VALUES ('Film', 'Description', '2000-01-01', 100, 4, 1); + +INSERT INTO USERS (EMAIL, LOGIN, NAME, BIRTHDAY) +VALUES ('test@test.ru', 'test', 'test', '2000-01-01'); + +INSERT INTO USERS (EMAIL, LOGIN, NAME, BIRTHDAY) +VALUES ('test1@test.ru', 'test1', 'test1', '1999-01-01'); + +INSERT INTO LIKE_FILMS (USER_ID, FILM_ID) +VALUES (2, 1); + + + diff --git a/src/test/java/ru/yandex/practicum/filmorate/service/FilmServiceTest.java b/src/test/java/ru/yandex/practicum/filmorate/service/FilmServiceTest.java deleted file mode 100644 index 7a3c76e..0000000 --- a/src/test/java/ru/yandex/practicum/filmorate/service/FilmServiceTest.java +++ /dev/null @@ -1,22 +0,0 @@ -package ru.yandex.practicum.filmorate.service; - -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.context.SpringBootTest; -import ru.yandex.practicum.filmorate.model.Film; -import ru.yandex.practicum.filmorate.sortage.FilmDbStorage; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -import static org.junit.jupiter.api.Assertions.*; - -@SpringBootTest -@AutoConfigureTestDatabase -@RequiredArgsConstructor(onConstructor_ = @Autowired) -class FilmServiceTest { - private final FilmService filmService; - private final FilmDbStorage filmDbStorage; - - // Допишу тесты в след итерацию, есть сомнения в основной части проекта -} \ No newline at end of file diff --git a/src/test/java/ru/yandex/practicum/filmorate/service/UserServiceTest.java b/src/test/java/ru/yandex/practicum/filmorate/service/UserServiceTest.java deleted file mode 100644 index 945bf83..0000000 --- a/src/test/java/ru/yandex/practicum/filmorate/service/UserServiceTest.java +++ /dev/null @@ -1,7 +0,0 @@ -package ru.yandex.practicum.filmorate.service; - -import static org.junit.jupiter.api.Assertions.*; - -class UserServiceTest { - -} \ No newline at end of file diff --git a/src/test/java/ru/yandex/practicum/filmorate/sortage/FilmDbStorageTest.java b/src/test/java/ru/yandex/practicum/filmorate/sortage/FilmDbStorageTest.java new file mode 100644 index 0000000..d2b82a6 --- /dev/null +++ b/src/test/java/ru/yandex/practicum/filmorate/sortage/FilmDbStorageTest.java @@ -0,0 +1,87 @@ +package ru.yandex.practicum.filmorate.sortage; + +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.context.SpringBootTest; +import ru.yandex.practicum.filmorate.model.Film; +import ru.yandex.practicum.filmorate.model.MpaRating; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest +@AutoConfigureTestDatabase +@RequiredArgsConstructor(onConstructor_ = @Autowired) +class FilmDbStorageTest { + + private final FilmDbStorage filmDbStorage; + @Test + void findAll() { + List films = filmDbStorage.findAll(); + + assertThat(films.size()).isEqualTo(2); + } + + @Test + void create() { + Film film = new Film(); + + film.setName("Фильм1"); + film.setDescription("Описание фильма 1"); + film.setReleaseDate(LocalDate.of(2000, 01, 01)); + film.setDuration(100); + film.setRate(10); + + MpaRating mpaRating = new MpaRating(); + mpaRating.setId(1); + + film.setMpa(mpaRating); + + filmDbStorage.create(film); + + assertThat(film).hasFieldOrPropertyWithValue("film_id", 2L); + } + + @Test + void put() { + Film film = new Film(); + + film.setFilmId(2); + film.setName("Фильм измененный 1"); + film.setDescription("Описание фильма измененного 1"); + film.setReleaseDate(LocalDate.of(2000, 01, 01)); + film.setDuration(100); + film.setRate(10); + + MpaRating mpaRating = new MpaRating(); + mpaRating.setId(2); + + film.setMpa(mpaRating); + + filmDbStorage.put(film); + + assertThat(film).hasFieldOrPropertyWithValue("description", "Описание фильма измененного 1"); + } + + @Test + void findFilm() { + Film film = filmDbStorage.findFilm(1); + + assertThat(film).hasFieldOrPropertyWithValue("film_id", 1); + } + + @Test + void deleteFilm() { + filmDbStorage.deleteFilm(2); + + List films = filmDbStorage.findAll(); + + assertThat(films.size()).isEqualTo(1); + } +} \ No newline at end of file diff --git a/src/test/java/ru/yandex/practicum/filmorate/sortage/LikeFilmDbStorageTest.java b/src/test/java/ru/yandex/practicum/filmorate/sortage/LikeFilmDbStorageTest.java new file mode 100644 index 0000000..ad56629 --- /dev/null +++ b/src/test/java/ru/yandex/practicum/filmorate/sortage/LikeFilmDbStorageTest.java @@ -0,0 +1,44 @@ +package ru.yandex.practicum.filmorate.sortage; + +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.context.SpringBootTest; +import ru.yandex.practicum.filmorate.model.Film; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.junit.jupiter.api.Assertions.*; + + +@SpringBootTest +@AutoConfigureTestDatabase +@RequiredArgsConstructor(onConstructor_ = @Autowired) +class LikeFilmDbStorageTest { + + private final LikeFilmDbStorage likeFilmDbStorage; + private final FilmDbStorage filmDbStorage; + + @Test + void addLike() { + likeFilmDbStorage.addLike(1,1); + Film film = filmDbStorage.findFilm(1); + assertThat(film.getLikes()).isEqualTo(1); + } + + @Test + void deleteLike() { + likeFilmDbStorage.deleteLike(2, 1); + + Film film = filmDbStorage.findFilm(1); + + assertThat(film.getLikes()).isEqualTo(0); + } + + @Test + void getPopularFilms() { + Film film = likeFilmDbStorage.getPopularFilms(1).get(0); + + assertThat(film.getLikes()).isEqualTo(1); + } +} \ No newline at end of file diff --git a/src/test/java/ru/yandex/practicum/filmorate/sortage/UserDbStorageTest.java b/src/test/java/ru/yandex/practicum/filmorate/sortage/UserDbStorageTest.java new file mode 100644 index 0000000..da48ca1 --- /dev/null +++ b/src/test/java/ru/yandex/practicum/filmorate/sortage/UserDbStorageTest.java @@ -0,0 +1,78 @@ +package ru.yandex.practicum.filmorate.sortage; + +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.context.SpringBootTest; +import ru.yandex.practicum.filmorate.model.User; + +import java.time.LocalDate; +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest +@AutoConfigureTestDatabase +@RequiredArgsConstructor(onConstructor_ = @Autowired) +class UserDbStorageTest { + + private final UserDbStorage userStorage; + + @Test + void findAll() { + Optional> optionalUsers = Optional.of(List.copyOf(userStorage.findAll())); + + assertThat(optionalUsers.get().size()).isEqualTo(3); + + } + + @Test + void create() { + User user = new User(); + user.setUserId(3); + user.setName("test1"); + user.setEmail("test1@test1.ru"); + user.setLogin("test1"); + user.setBirthday(LocalDate.of(1999, 01, 01)); + + userStorage.create(user); + + assertThat(user).hasFieldOrPropertyWithValue("userId",3); + } + + @Test + void put() { + User user = new User(); + user.setUserId(2); + user.setName("test1Changed"); + user.setEmail("test1Changed@test1.ru"); + user.setLogin("test1Changed"); + user.setBirthday(LocalDate.of(1998, 01, 01)); + userStorage.put(user); + + + assertThat(user).hasFieldOrPropertyWithValue("name", "test1Changed"); + } + + @Test + void findUser() { + User user = userStorage.findUser(1); + + assertThat(user).hasFieldOrPropertyWithValue("user_id", 1); + + } + + @Test + void deleteUser() { + userStorage.deleteUser(3); + + User user = userStorage.findUser(3); + + assertThat(user).isNull(); + } + + +} \ No newline at end of file diff --git a/src/test/java/ru/yandex/practicum/filmorate/sortage/UserFriendDbStorageTest.java b/src/test/java/ru/yandex/practicum/filmorate/sortage/UserFriendDbStorageTest.java new file mode 100644 index 0000000..6a946dd --- /dev/null +++ b/src/test/java/ru/yandex/practicum/filmorate/sortage/UserFriendDbStorageTest.java @@ -0,0 +1,40 @@ +package ru.yandex.practicum.filmorate.sortage; + +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.context.SpringBootTest; +import org.springframework.data.relational.core.sql.In; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest +@AutoConfigureTestDatabase +@RequiredArgsConstructor(onConstructor_ = @Autowired) +class UserFriendDbStorageTest { + + private final UserFriendDbStorage userFriendDbStorage; + + @Test + public void addFriend() { + userFriendDbStorage.addFriend(1, 2); + Integer fiendsId = userFriendDbStorage.findAllFriends(1).get(0).getUserId(); + assertThat(fiendsId).isEqualTo(2); + } + + @Test + public void findAllFriends() { + userFriendDbStorage.addFriend(1, 2); + Integer friendsList = userFriendDbStorage.findAllFriends(1).size(); + assertThat(friendsList).isEqualTo(1); + } + + @Test + public void deleteFriends() { + userFriendDbStorage.deleteFriends(1, 2); + Integer friend = userFriendDbStorage.findAllFriends(1).size(); + assertThat(friend).isEqualTo(0); + } +} \ No newline at end of file From d93eadd5f9897052b0dc4491f5e536ad9380ead9 Mon Sep 17 00:00:00 2001 From: robert Date: Thu, 21 Jul 2022 01:33:15 +0300 Subject: [PATCH 3/5] =?UTF-8?q?=D0=98=D1=82=D0=BE=D0=B3=D0=BE=D0=B2=D1=8B?= =?UTF-8?q?=D0=B9=20=D0=BF=D1=80=D0=BE=D0=B5=D0=BA=D1=82=2011=20#3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../practicum/filmorate/model/LikeFilms.java | 15 +++ .../model/mapper/LikeFilmsRowMapper.java | 17 +++ .../filmorate/service/FilmService.java | 16 ++- .../filmorate/sortage/LikeFilmDbStorage.java | 16 +-- .../filmorate/sortage/UserDbStorage.java | 9 +- src/main/resources/{schema => schema.sql} | 0 src/test/java/resources/schema | 15 --- src/test/java/resources/schema.sql | 103 ++++++++++++++++++ .../controllerTest/FilmControllerTest.java | 6 +- .../filmorate/sortage/FilmDbStorageTest.java | 6 +- .../sortage/LikeFilmDbStorageTest.java | 11 +- .../filmorate/sortage/UserDbStorageTest.java | 10 +- .../sortage/UserFriendDbStorageTest.java | 8 +- 13 files changed, 174 insertions(+), 58 deletions(-) create mode 100644 src/main/java/ru/yandex/practicum/filmorate/model/LikeFilms.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/model/mapper/LikeFilmsRowMapper.java rename src/main/resources/{schema => schema.sql} (100%) delete mode 100644 src/test/java/resources/schema create mode 100644 src/test/java/resources/schema.sql diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/LikeFilms.java b/src/main/java/ru/yandex/practicum/filmorate/model/LikeFilms.java new file mode 100644 index 0000000..93f4580 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/model/LikeFilms.java @@ -0,0 +1,15 @@ +package ru.yandex.practicum.filmorate.model; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode +public class LikeFilms { + + private Integer filmId; +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/mapper/LikeFilmsRowMapper.java b/src/main/java/ru/yandex/practicum/filmorate/model/mapper/LikeFilmsRowMapper.java new file mode 100644 index 0000000..f44c76a --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/model/mapper/LikeFilmsRowMapper.java @@ -0,0 +1,17 @@ +package ru.yandex.practicum.filmorate.model.mapper; + +import org.springframework.jdbc.core.RowMapper; +import ru.yandex.practicum.filmorate.model.Film; +import ru.yandex.practicum.filmorate.model.LikeFilms; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class LikeFilmsRowMapper implements RowMapper { + + @Override + public LikeFilms mapRow(ResultSet rs, int rowNum) throws SQLException { + LikeFilms likeFilms = new LikeFilms(rs.getInt("FILM_ID")); + return likeFilms; + } +} 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 4ffbfd9..7eeb0e3 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/service/FilmService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/FilmService.java @@ -6,17 +6,15 @@ import ru.yandex.practicum.filmorate.exception.FilmNotExistsException; import ru.yandex.practicum.filmorate.exception.UserIdNotValidation; import ru.yandex.practicum.filmorate.model.Film; +import ru.yandex.practicum.filmorate.model.LikeFilms; import ru.yandex.practicum.filmorate.model.User; import ru.yandex.practicum.filmorate.sortage.FilmStorage; import ru.yandex.practicum.filmorate.exception.ValidationException; import ru.yandex.practicum.filmorate.sortage.LikeFilmDbStorage; -import ru.yandex.practicum.filmorate.sortage.UserStorage; import java.time.LocalDate; -import java.util.Collection; +import java.util.ArrayList; import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; @Service public class FilmService { @@ -54,7 +52,7 @@ public Film put(Film film) throws ValidationException { return filmStorage.put(film); } - public Film findById(Integer id) throws ValidationException { + public Film findById(Integer id) { return filmStorage.findFilm(id); } @@ -80,7 +78,13 @@ public void deleteLike(Film film, User user) { } public List getPopularFilms(Integer count) { - return likeFilmDbStorage.getPopularFilms(count); + List popularFilms = likeFilmDbStorage.getPopularFilms(count); + List likeFilmIds = new ArrayList<>(); + popularFilms.forEach(f -> { + Film film = findById(f.getFilmId()); + likeFilmIds.add(film); + }); + return likeFilmIds; } public void DeleteFilm(Film film) { diff --git a/src/main/java/ru/yandex/practicum/filmorate/sortage/LikeFilmDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/sortage/LikeFilmDbStorage.java index b255a77..041ad94 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/sortage/LikeFilmDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/sortage/LikeFilmDbStorage.java @@ -3,10 +3,9 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Component; -import ru.yandex.practicum.filmorate.model.Film; -import ru.yandex.practicum.filmorate.model.mapper.FilmRowMapper; +import ru.yandex.practicum.filmorate.model.LikeFilms; +import ru.yandex.practicum.filmorate.model.mapper.LikeFilmsRowMapper; -import java.util.Collection; import java.util.List; @Component @@ -19,10 +18,10 @@ public LikeFilmDbStorage(JdbcTemplate jdbcTemplate) { } - public void addLike(Integer userId, Integer filmId) { + public boolean addLike(Integer userId, Integer filmId) { final String sqlQuery = "INSERT INTO LIKE_FILMS (USER_ID, FILM_ID) values(?, ?)"; - jdbcTemplate.update(sqlQuery, - userId, filmId); + int updatedRows = jdbcTemplate.update(sqlQuery, userId, filmId); + return updatedRows > 0; } @@ -33,9 +32,10 @@ public void deleteLike(Integer userId, Integer filmId) { } - public List getPopularFilms(Integer count) { + public List getPopularFilms(Integer count) { String sqlQuery = "select FILM_ID, count(USER_ID) as LIKES from LIKE_FILMS " + "GROUP BY FILM_ID ORDER BY LIKES DESC LIMIT ?"; - return jdbcTemplate.query(sqlQuery, new FilmRowMapper(), count); + List likeFilms = jdbcTemplate.query(sqlQuery, new LikeFilmsRowMapper(), count); + return likeFilms; } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/sortage/UserDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/sortage/UserDbStorage.java index 1d39820..3f22718 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/sortage/UserDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/sortage/UserDbStorage.java @@ -2,25 +2,18 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.relational.core.sql.In; -import org.springframework.jdbc.core.BeanPropertyRowMapper; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.support.GeneratedKeyHolder; import org.springframework.jdbc.support.KeyHolder; -import org.springframework.jdbc.support.rowset.SqlRowSet; import org.springframework.stereotype.Component; import ru.yandex.practicum.filmorate.exception.UserIdNotValidation; -import ru.yandex.practicum.filmorate.exception.ValidationException; import ru.yandex.practicum.filmorate.model.User; import ru.yandex.practicum.filmorate.model.mapper.UserRowMapper; import java.sql.*; import java.time.LocalDate; -import java.util.ArrayList; import java.util.Collection; -import java.util.HashMap; import java.util.List; -import java.util.stream.Collectors; @Slf4j @Component @@ -58,7 +51,7 @@ public User findUser(Integer id) { final String sqlQuery = "select * from USERS where USER_ID = ?"; final List findUsers = jdbcTemplate.query(sqlQuery, new UserRowMapper(), id); if(findUsers.isEmpty()){ - throw new UserIdNotValidation(" sdfd"); + throw new UserIdNotValidation("Некорректный ID"); } return findUsers.get(0); } diff --git a/src/main/resources/schema b/src/main/resources/schema.sql similarity index 100% rename from src/main/resources/schema rename to src/main/resources/schema.sql diff --git a/src/test/java/resources/schema b/src/test/java/resources/schema deleted file mode 100644 index 98593af..0000000 --- a/src/test/java/resources/schema +++ /dev/null @@ -1,15 +0,0 @@ - -INSERT INTO FILMS (NAME, DESCRIPTION, RELEASE_DATE, DURATION, RATE, MPA_ID) -VALUES ('Film', 'Description', '2000-01-01', 100, 4, 1); - -INSERT INTO USERS (EMAIL, LOGIN, NAME, BIRTHDAY) -VALUES ('test@test.ru', 'test', 'test', '2000-01-01'); - -INSERT INTO USERS (EMAIL, LOGIN, NAME, BIRTHDAY) -VALUES ('test1@test.ru', 'test1', 'test1', '1999-01-01'); - -INSERT INTO LIKE_FILMS (USER_ID, FILM_ID) -VALUES (2, 1); - - - diff --git a/src/test/java/resources/schema.sql b/src/test/java/resources/schema.sql new file mode 100644 index 0000000..2a54d18 --- /dev/null +++ b/src/test/java/resources/schema.sql @@ -0,0 +1,103 @@ +DROP TABLE IF EXISTS LIKE_FILMS; +DROP TABLE IF EXISTS GENRE_FILM; +DROP TABLE IF EXISTS USER_FRIEND; +DROP TABLE IF EXISTS USERS; +DROP TABLE IF EXISTS FILMS; + + + +-- Creating tables +CREATE TABLE if not exists USERS +( + user_id int auto_increment unique, + email varchar, + login varchar, + name varchar, + birthday date, + primary key (user_id) +); + +CREATE TABLE if not exists FRIENDSHIP_STATUS +( + friendship_status_id int, + status varchar, + primary key (friendship_status_id) +); + +CREATE TABLE if not exists USER_FRIEND +( + user_id int references users (user_id), + friend_id int references users (user_id), + friendship_status_id int references FRIENDSHIP_STATUS (friendship_status_id) +); + + + +CREATE TABLE if not exists MPA_RATING +( + mpa_id int auto_increment unique, + mpa_rating varchar, + description varchar, + primary key (mpa_id) +); + + +CREATE TABLE if not exists FILMS +( + film_id int auto_increment unique, + name varchar, + description varchar, + release_date date, + duration int, + rate int, + mpa_id int references mpa_rating (mpa_id), + primary key (film_id) +); + +CREATE TABLE if not exists LIKE_FILMS +( + user_id int references users (user_id), + film_id int references films (film_id) +); + +CREATE TABLE if not exists GENRE +( + genre_id int auto_increment unique, + genre_name varchar, + primary key (genre_id) +); + +CREATE TABLE if not exists GENRE_FILM +( + film_id int references films (film_id), + genre_id int references genre (genre_id) +); + +MERGE INTO MPA_RATING (MPA_ID, MPA_RATING, DESCRIPTION) + values (1, 'G', 'У фильма нет возрастных ограничений'); +MERGE INTO MPA_RATING (MPA_ID, MPA_RATING, DESCRIPTION) + values (2, 'PG', 'Детям рекомендуется смотреть фильм с родителями'); +MERGE INTO MPA_RATING (MPA_ID, MPA_RATING, DESCRIPTION) + values (3, 'PG-13', 'Детям до 13 лет просмотр не желателен'); +MERGE INTO MPA_RATING (MPA_ID, MPA_RATING, DESCRIPTION) + values (4, 'R', 'Лицам до 17 лет просматривать фильм можно только в присутствии взрослого'); +MERGE INTO MPA_RATING (MPA_ID, MPA_RATING, DESCRIPTION) + values (5, 'NC-17', 'Лицам до 18 лет просмотр запрещён'); + + + + +INSERT INTO FILMS (NAME, DESCRIPTION, RELEASE_DATE, DURATION, RATE, MPA_ID) +VALUES ('Film', 'Description', '2000-01-01', 100, 4, 1); + +INSERT INTO USERS (EMAIL, LOGIN, NAME, BIRTHDAY) +VALUES ('test@test.ru', 'test', 'test', '2000-01-01'); + +INSERT INTO USERS (EMAIL, LOGIN, NAME, BIRTHDAY) +VALUES ('test1@test.ru', 'test1', 'test1', '1999-01-01'); + +INSERT INTO LIKE_FILMS (USER_ID, FILM_ID) +VALUES (2, 1); + + + diff --git a/src/test/java/ru/yandex/practicum/filmorate/controllerTest/FilmControllerTest.java b/src/test/java/ru/yandex/practicum/filmorate/controllerTest/FilmControllerTest.java index 4a37e88..4499b32 100644 --- a/src/test/java/ru/yandex/practicum/filmorate/controllerTest/FilmControllerTest.java +++ b/src/test/java/ru/yandex/practicum/filmorate/controllerTest/FilmControllerTest.java @@ -5,8 +5,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import ru.yandex.practicum.filmorate.controller.FilmController; +import ru.yandex.practicum.filmorate.exception.FilmNotExistsException; import ru.yandex.practicum.filmorate.model.Film; -import ru.yandex.practicum.filmorate.exception.ValidationException; import java.time.LocalDate; @@ -27,13 +27,13 @@ public class FilmControllerTest { @BeforeEach void beforeEach() { duration1 = 100; - releaseDate = LocalDate.of(1900, 1, 1); + releaseDate = LocalDate.of(1825, 1, 1); film1 = new Film(1,"name", "logi n sdad", releaseDate, duration1, 4); } @Test void createDateReleaseMore1985Years() { - ValidationException ex = assertThrows(ValidationException.class, + FilmNotExistsException ex = assertThrows(FilmNotExistsException.class, () -> filmController.create(film1)); assertEquals("Некорректные данные! Дата релиза должна быть позднее 28.12.1985 года!", ex.getMessage()); } diff --git a/src/test/java/ru/yandex/practicum/filmorate/sortage/FilmDbStorageTest.java b/src/test/java/ru/yandex/practicum/filmorate/sortage/FilmDbStorageTest.java index d2b82a6..3386d43 100644 --- a/src/test/java/ru/yandex/practicum/filmorate/sortage/FilmDbStorageTest.java +++ b/src/test/java/ru/yandex/practicum/filmorate/sortage/FilmDbStorageTest.java @@ -25,7 +25,7 @@ class FilmDbStorageTest { void findAll() { List films = filmDbStorage.findAll(); - assertThat(films.size()).isEqualTo(2); + assertFalse(films.isEmpty()); } @Test @@ -45,7 +45,7 @@ void create() { filmDbStorage.create(film); - assertThat(film).hasFieldOrPropertyWithValue("film_id", 2L); + assertThat(film).hasFieldOrPropertyWithValue("filmId", 2); } @Test @@ -73,7 +73,7 @@ void put() { void findFilm() { Film film = filmDbStorage.findFilm(1); - assertThat(film).hasFieldOrPropertyWithValue("film_id", 1); + assertThat(film).hasFieldOrPropertyWithValue("filmId", 1); } @Test diff --git a/src/test/java/ru/yandex/practicum/filmorate/sortage/LikeFilmDbStorageTest.java b/src/test/java/ru/yandex/practicum/filmorate/sortage/LikeFilmDbStorageTest.java index ad56629..c7db6c2 100644 --- a/src/test/java/ru/yandex/practicum/filmorate/sortage/LikeFilmDbStorageTest.java +++ b/src/test/java/ru/yandex/practicum/filmorate/sortage/LikeFilmDbStorageTest.java @@ -6,6 +6,9 @@ import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.context.SpringBootTest; import ru.yandex.practicum.filmorate.model.Film; +import ru.yandex.practicum.filmorate.model.LikeFilms; + +import java.util.List; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.junit.jupiter.api.Assertions.*; @@ -21,9 +24,7 @@ class LikeFilmDbStorageTest { @Test void addLike() { - likeFilmDbStorage.addLike(1,1); - Film film = filmDbStorage.findFilm(1); - assertThat(film.getLikes()).isEqualTo(1); + assertTrue(likeFilmDbStorage.addLike(1,1)); } @Test @@ -37,8 +38,8 @@ void deleteLike() { @Test void getPopularFilms() { - Film film = likeFilmDbStorage.getPopularFilms(1).get(0); + List popularFilms = likeFilmDbStorage.getPopularFilms(1); - assertThat(film.getLikes()).isEqualTo(1); + assertFalse(popularFilms.isEmpty()); } } \ No newline at end of file diff --git a/src/test/java/ru/yandex/practicum/filmorate/sortage/UserDbStorageTest.java b/src/test/java/ru/yandex/practicum/filmorate/sortage/UserDbStorageTest.java index da48ca1..790b030 100644 --- a/src/test/java/ru/yandex/practicum/filmorate/sortage/UserDbStorageTest.java +++ b/src/test/java/ru/yandex/practicum/filmorate/sortage/UserDbStorageTest.java @@ -5,6 +5,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.context.SpringBootTest; +import ru.yandex.practicum.filmorate.exception.UserIdNotValidation; import ru.yandex.practicum.filmorate.model.User; import java.time.LocalDate; @@ -61,18 +62,13 @@ void put() { void findUser() { User user = userStorage.findUser(1); - assertThat(user).hasFieldOrPropertyWithValue("user_id", 1); + assertThat(user).hasFieldOrPropertyWithValue("userId", 1); } @Test void deleteUser() { userStorage.deleteUser(3); - - User user = userStorage.findUser(3); - - assertThat(user).isNull(); + assertThrows(UserIdNotValidation.class ,() -> userStorage.findUser(3)); } - - } \ No newline at end of file diff --git a/src/test/java/ru/yandex/practicum/filmorate/sortage/UserFriendDbStorageTest.java b/src/test/java/ru/yandex/practicum/filmorate/sortage/UserFriendDbStorageTest.java index 6a946dd..d50d05f 100644 --- a/src/test/java/ru/yandex/practicum/filmorate/sortage/UserFriendDbStorageTest.java +++ b/src/test/java/ru/yandex/practicum/filmorate/sortage/UserFriendDbStorageTest.java @@ -5,7 +5,9 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.data.relational.core.sql.In; +import ru.yandex.practicum.filmorate.model.User; + +import java.util.List; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.junit.jupiter.api.Assertions.*; @@ -27,8 +29,8 @@ public void addFriend() { @Test public void findAllFriends() { userFriendDbStorage.addFriend(1, 2); - Integer friendsList = userFriendDbStorage.findAllFriends(1).size(); - assertThat(friendsList).isEqualTo(1); + List allFriends = userFriendDbStorage.findAllFriends(1); + assertFalse(allFriends.isEmpty()); } @Test From 97d472a6c25b34916b701c1fcf86b3e6929407b4 Mon Sep 17 00:00:00 2001 From: Shaihattarov Ranis Date: Thu, 28 Jul 2022 02:21:36 +0300 Subject: [PATCH 4/5] =?UTF-8?q?=D0=98=D1=82=D0=BE=D0=B3=D0=BE=D0=B2=D0=B0?= =?UTF-8?q?=D1=8F=20=D0=B2=D0=B5=D1=80=D1=81=D0=B8=D1=8F=20=D0=BA=2011=20?= =?UTF-8?q?=D1=81=D0=BF=D1=80=D0=B8=D0=BD=D1=82=D1=83=20#3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../filmorate/controller/FilmController.java | 8 +- .../filmorate/controller/GenreController.java | 32 +++++++ .../filmorate/controller/MpaController.java | 33 +++++++ .../filmorate/controller/UserController.java | 3 +- .../practicum/filmorate/model/Film.java | 6 +- .../practicum/filmorate/model/Genre.java | 19 ++-- .../practicum/filmorate/model/GenreEnum.java | 30 ++++++ .../practicum/filmorate/model/LikeFilms.java | 15 +++ .../practicum/filmorate/model/MpaRating.java | 8 ++ .../filmorate/model/MpaRatingEnum.java | 28 +++++- .../model/mapper/LikeFilmsRowMapper.java | 16 ++++ .../filmorate/service/FilmService.java | 20 ++-- .../filmorate/service/GenreService.java | 27 ++++++ .../filmorate/service/MpaService.java | 29 ++++++ .../filmorate/service/UserService.java | 8 +- .../filmorate/sortage/FilmDbStorage.java | 93 ++++++++++++++++--- .../filmorate/sortage/GenreDbStorage.java | 38 ++++++++ .../filmorate/sortage/LikeFilmDbStorage.java | 39 +++++--- .../filmorate/sortage/MpaDbStorage.java | 39 ++++++++ .../filmorate/sortage/UserDbStorage.java | 13 +-- src/main/resources/schema | 20 +++- .../controllerTest/FilmControllerTest.java | 1 - .../filmorate/sortage/FilmDbStorageTest.java | 3 - 23 files changed, 448 insertions(+), 80 deletions(-) create mode 100644 src/main/java/ru/yandex/practicum/filmorate/controller/GenreController.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/controller/MpaController.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/model/GenreEnum.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/model/LikeFilms.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/model/mapper/LikeFilmsRowMapper.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/service/GenreService.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/service/MpaService.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/sortage/GenreDbStorage.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/sortage/MpaDbStorage.java 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 22eed6d..dd94f86 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java @@ -3,14 +3,12 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; +import ru.yandex.practicum.filmorate.exception.ValidationException; import ru.yandex.practicum.filmorate.model.Film; import ru.yandex.practicum.filmorate.service.FilmService; import ru.yandex.practicum.filmorate.service.UserService; -import ru.yandex.practicum.filmorate.sortage.FilmStorage; -import ru.yandex.practicum.filmorate.exception.ValidationException; import javax.validation.Valid; -import java.util.Collection; import java.util.List; @Slf4j @@ -43,8 +41,8 @@ public List findAll() { @PutMapping("{id}/like/{userId}") public void addLike(@PathVariable Integer id, - @PathVariable Integer userId) throws ValidationException { - filmService.addLike(filmService.findById(id), userService.findById(userId)); + @PathVariable Integer userId) { + filmService.addLike(id, userId); } @DeleteMapping("{id}/like/{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..6bda364 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/GenreController.java @@ -0,0 +1,32 @@ +package ru.yandex.practicum.filmorate.controller; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +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 javax.validation.constraints.NotNull; +import java.util.List; + +@Slf4j +@RestController +@RequestMapping("/genres") +public class GenreController { + + @Autowired + GenreService genreService; + + @GetMapping("{id}") + public Genre findById(@NotNull @PathVariable Integer id){ + return genreService.findById(id); + } + + @GetMapping + public List findAll() { + return genreService.findAll(); + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/controller/MpaController.java b/src/main/java/ru/yandex/practicum/filmorate/controller/MpaController.java new file mode 100644 index 0000000..7a9cff5 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/MpaController.java @@ -0,0 +1,33 @@ +package ru.yandex.practicum.filmorate.controller; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +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.MpaService; + +import javax.validation.constraints.NotNull; +import java.util.List; + +@Slf4j +@RestController +@RequestMapping("/mpa") +public class MpaController { + + @Autowired + MpaService mpaService; + + @GetMapping("{id}") + public MpaRating findById(@NotNull @PathVariable Integer id) { + return mpaService.findById(id); + } + + @GetMapping() + public List findAll() { + return mpaService.findAll(); + } + +} 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 24d16d3..dbb886a 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java @@ -4,9 +4,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import ru.yandex.practicum.filmorate.model.User; -import ru.yandex.practicum.filmorate.service.UserService; import ru.yandex.practicum.filmorate.exception.ValidationException; -import ru.yandex.practicum.filmorate.sortage.UserStorage; +import ru.yandex.practicum.filmorate.service.UserService; import javax.validation.Valid; import java.util.Collection; 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 031f68d..f302192 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/model/Film.java +++ b/src/main/java/ru/yandex/practicum/filmorate/model/Film.java @@ -7,9 +7,7 @@ import lombok.NoArgsConstructor; import java.time.LocalDate; -import java.util.HashSet; import java.util.List; -import java.util.Set; import javax.validation.constraints.*; @@ -31,8 +29,10 @@ public class Film { //private Set likes; private int rate; private long likes; - private Genre genre; + @JsonProperty("genres") + private List genre; @NotNull + @JsonProperty("mpa") private MpaRating mpa; public Film(int filmId, String name, String description, LocalDate releaseDate, long duration, int rate, MpaRating mpa) { diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/Genre.java b/src/main/java/ru/yandex/practicum/filmorate/model/Genre.java index 396e2ca..4211d7b 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/model/Genre.java +++ b/src/main/java/ru/yandex/practicum/filmorate/model/Genre.java @@ -1,10 +1,13 @@ package ru.yandex.practicum.filmorate.model; -public enum Genre { - COMEDY, - DRAMA, - CARTOON, - THRILLER, - DOCUMENTARY, - ACTION - } +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +@Data +public class Genre { + @JsonProperty("id") + private Integer id; + + @JsonProperty("name") + private String name; +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/GenreEnum.java b/src/main/java/ru/yandex/practicum/filmorate/model/GenreEnum.java new file mode 100644 index 0000000..8f53192 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/model/GenreEnum.java @@ -0,0 +1,30 @@ +package ru.yandex.practicum.filmorate.model; + +public enum GenreEnum { + COMEDY(1, "Комедия"), + DRAMA(2,"Драма"), + CARTOON(3,"Мультфильм"), + THRILLER(4,"Триллер"), + DOCUMENTARY(5,"Документальный"), + ACTION(6,"Экшн"); + + private int id; + + private String name; + + GenreEnum(int aId, String aName) { + id = aId; + name = aName; + } + + public static String getNameById(int id) { + for (GenreEnum o : GenreEnum.values()) { + if (o.id == id) { + return o.name; + } + } + return ""; + } +} + + diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/LikeFilms.java b/src/main/java/ru/yandex/practicum/filmorate/model/LikeFilms.java new file mode 100644 index 0000000..93f4580 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/model/LikeFilms.java @@ -0,0 +1,15 @@ +package ru.yandex.practicum.filmorate.model; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode +public class LikeFilms { + + private Integer filmId; +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/MpaRating.java b/src/main/java/ru/yandex/practicum/filmorate/model/MpaRating.java index c2f108e..8830575 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/model/MpaRating.java +++ b/src/main/java/ru/yandex/practicum/filmorate/model/MpaRating.java @@ -1,8 +1,16 @@ package ru.yandex.practicum.filmorate.model; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; @Data public class MpaRating { + @JsonProperty("id") private int id; + + @JsonProperty("name") + @JsonIgnore + private String name; + } diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/MpaRatingEnum.java b/src/main/java/ru/yandex/practicum/filmorate/model/MpaRatingEnum.java index 4852b0b..845a480 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/model/MpaRatingEnum.java +++ b/src/main/java/ru/yandex/practicum/filmorate/model/MpaRatingEnum.java @@ -1,9 +1,27 @@ package ru.yandex.practicum.filmorate.model; public enum MpaRatingEnum { - G, - PG, - PG13, - R, - NC17 + G(1, "G"), + PG(2, "PG"), + PG13(3, "PG-13"), + R(4, "R"), + NC17(5, "NC-17"); + + private int id; + + private String name; + + MpaRatingEnum(int id, String name) { + this.id = id; + this.name = name; + } + + public static String getNameById(int id) { + for (MpaRatingEnum e : MpaRatingEnum.values()) { + if (e.id == id) { + return e.name; + } + } + return null; + } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/mapper/LikeFilmsRowMapper.java b/src/main/java/ru/yandex/practicum/filmorate/model/mapper/LikeFilmsRowMapper.java new file mode 100644 index 0000000..2a0cfa7 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/model/mapper/LikeFilmsRowMapper.java @@ -0,0 +1,16 @@ +package ru.yandex.practicum.filmorate.model.mapper; + +import org.springframework.jdbc.core.RowMapper; +import ru.yandex.practicum.filmorate.model.LikeFilms; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class LikeFilmsRowMapper implements RowMapper { + + @Override + public LikeFilms mapRow(ResultSet rs, int rowNum) throws SQLException { + LikeFilms likeFilms = new LikeFilms(rs.getInt("FILM_ID")); + return likeFilms; + } +} 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 4ffbfd9..2741120 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/service/FilmService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/FilmService.java @@ -5,18 +5,15 @@ import ru.yandex.practicum.filmorate.exception.FilmIdNotValidation; import ru.yandex.practicum.filmorate.exception.FilmNotExistsException; import ru.yandex.practicum.filmorate.exception.UserIdNotValidation; +import ru.yandex.practicum.filmorate.exception.ValidationException; import ru.yandex.practicum.filmorate.model.Film; import ru.yandex.practicum.filmorate.model.User; import ru.yandex.practicum.filmorate.sortage.FilmStorage; -import ru.yandex.practicum.filmorate.exception.ValidationException; import ru.yandex.practicum.filmorate.sortage.LikeFilmDbStorage; -import ru.yandex.practicum.filmorate.sortage.UserStorage; + import java.time.LocalDate; -import java.util.Collection; import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; @Service public class FilmService { @@ -54,19 +51,22 @@ public Film put(Film film) throws ValidationException { return filmStorage.put(film); } - public Film findById(Integer id) throws ValidationException { + public Film findById(Integer id) { + if (id < 0) { + throw new FilmIdNotValidation("Id не может быть отрицательный!"); + } return filmStorage.findFilm(id); } - public void addLike(Film film, User user) { - if(user.getUserId() < 0) { + public void addLike(int filmId, int userId) { + if(userId < 0) { throw new UserIdNotValidation("Id не может быть отрицательный!"); } - if(film.getFilmId() < 0) { + if(filmId < 0) { throw new FilmIdNotValidation("Id не может быть отрицательный!"); } - likeFilmDbStorage.addLike(user.getUserId(), film.getFilmId()); + likeFilmDbStorage.addLike(userId, filmId); } public void deleteLike(Film film, User user) { 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..14f2517 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/service/GenreService.java @@ -0,0 +1,27 @@ +package ru.yandex.practicum.filmorate.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import ru.yandex.practicum.filmorate.exception.FilmIdNotValidation; +import ru.yandex.practicum.filmorate.model.Genre; +import ru.yandex.practicum.filmorate.sortage.GenreDbStorage; + +import java.util.List; + +@Service +public class GenreService { + + @Autowired + GenreDbStorage genreDbStorage; + + public Genre findById(Integer id) { + if (id < 0) { + throw new FilmIdNotValidation("Id не может быть отрицательный!"); + } + return genreDbStorage.findById(id); + } + + public List findAll() { + return genreDbStorage.findAll(); + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/MpaService.java b/src/main/java/ru/yandex/practicum/filmorate/service/MpaService.java new file mode 100644 index 0000000..82ec953 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/service/MpaService.java @@ -0,0 +1,29 @@ +package ru.yandex.practicum.filmorate.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import ru.yandex.practicum.filmorate.exception.FilmIdNotValidation; +import ru.yandex.practicum.filmorate.model.MpaRating; +import ru.yandex.practicum.filmorate.sortage.MpaDbStorage; + +import java.util.List; + +@Service +public class MpaService { + + @Autowired + MpaDbStorage mpaDbStorage; + + public MpaRating findById(Integer id) { + if (id < 0) { + throw new FilmIdNotValidation("Id не может быть отрицательный!"); + } + return mpaDbStorage.findById(id); + } + + public List findAll() { + List mpaRatingList = mpaDbStorage.findAll(); + return mpaRatingList; + } + +} 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 54de8ca..da75679 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/service/UserService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/UserService.java @@ -2,19 +2,15 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.data.relational.core.sql.In; import org.springframework.stereotype.Service; import ru.yandex.practicum.filmorate.exception.UserIdNotValidation; +import ru.yandex.practicum.filmorate.exception.ValidationException; import ru.yandex.practicum.filmorate.model.User; import ru.yandex.practicum.filmorate.sortage.UserFriendDbStorage; import ru.yandex.practicum.filmorate.sortage.UserStorage; -import ru.yandex.practicum.filmorate.exception.ValidationException; -import java.util.ArrayList; import java.util.Collection; import java.util.List; -import java.util.Set; @Slf4j @Service @@ -24,7 +20,7 @@ public class UserService { @Autowired - public UserService (UserStorage userStorage, UserFriendDbStorage userFriendDbStorage) { + public UserService(UserStorage userStorage, UserFriendDbStorage userFriendDbStorage) { this.userStorage = userStorage; this.userFriendDbStorage = userFriendDbStorage; } diff --git a/src/main/java/ru/yandex/practicum/filmorate/sortage/FilmDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/sortage/FilmDbStorage.java index 5743285..1407cfc 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/sortage/FilmDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/sortage/FilmDbStorage.java @@ -3,23 +3,19 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; 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.Component; import org.springframework.stereotype.Service; -import ru.yandex.practicum.filmorate.model.Film; -import ru.yandex.practicum.filmorate.model.User; +import ru.yandex.practicum.filmorate.model.*; import ru.yandex.practicum.filmorate.model.mapper.FilmRowMapper; -import ru.yandex.practicum.filmorate.model.mapper.UserRowMapper; -import java.sql.Date; -import java.sql.PreparedStatement; -import java.sql.Types; +import java.sql.*; import java.time.LocalDate; import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; import java.util.List; +import java.util.NoSuchElementException; +import java.util.stream.Collectors; @Slf4j @@ -59,6 +55,15 @@ public Film create(Film film) { return stmt; }, keyHolder); film.setFilmId(keyHolder.getKey().intValue()); + List genre = film.getGenre(); + if (genre != null) { + genre.forEach(g -> jdbcTemplate.update("INSERT INTO GENRE_FILM values (?,?)", film.getFilmId(), g.getId())); + } + MpaRating mpa = film.getMpa(); + if (mpa != null) { + jdbcTemplate.update("INSERT INTO MPA_FILMS values (?,?)",mpa.getId(), film.getFilmId()); + } + return film; } @@ -73,14 +78,80 @@ public Film put(Film film) { , film.getReleaseDate() , film.getDuration() , film.getFilmId()); - return film; + if (film.getMpa() != null) { + jdbcTemplate.update("update mpa_films set mpa_id = ? where film_id = ?", + film.getMpa().getId(), film.getFilmId()); + } + + if (film.getGenre() != null && !film.getGenre().isEmpty()) { + film.getGenre().forEach(g -> { + List integers = jdbcTemplate.query("select film_id from genre_film where film_id = ? and genre_id = ?", new RowMapper() { + @Override + public Integer mapRow(ResultSet rs, int rowNum) throws SQLException { + return rs.getInt("FILM_ID"); + } + }, film.getFilmId(), g.getId()); + if (integers.isEmpty()) { +// jdbcTemplate.update("delete from genre_film where film_id = ?", film.getFilmId()); + jdbcTemplate.update("insert into genre_film values(?,?)",film.getFilmId(), g.getId()); + } else { + jdbcTemplate.update("update genre_film set genre_id = ? where film_id = ? and genre_id = ?", + g.getId(), film.getFilmId(), g.getId()); + }}); + List genreIds = film.getGenre().stream().map(Genre::getId).collect(Collectors.toList()); + List forDelInts = getGenre(genreIds); + forDelInts.forEach(i -> { + jdbcTemplate.update("delete from genre_film where film_id = ? and genre_id = ?", film.getFilmId(), i); + }); + + + } else if (film.getGenre() != null && film.getGenre().isEmpty()) { + jdbcTemplate.update("delete from genre_film where film_id = ?", film.getFilmId()); + } + + return findFilm(film.getFilmId()); + } + + private List getGenre(List genreIds) { + List genres = new ArrayList<>(List.of(1, 2, 3, 4, 5, 6)); + genres.removeAll(genreIds); + return genres; } @Override public Film findFilm(Integer id) { final String sqlQuery = "select * from FILMS where FILM_ID = ?"; final List findFilms = jdbcTemplate.query(sqlQuery, new FilmRowMapper(), id); - return findFilms.get(0); + if (findFilms.isEmpty()) { + throw new NoSuchElementException(); + } + Film film = findFilms.get(0); + List query = jdbcTemplate.query("select MPA_ID from MPA_FILMS WHERE FILM_ID = ?", + (rs, rowNum) -> rs.getInt("MPA_ID"), film.getFilmId()); + + MpaRating mpa = new MpaRating(); + film.setMpa(mpa); + + if (query.isEmpty()) { + return film; + } + + int mpaId = query.get(0); + mpa.setId(mpaId); + mpa.setName(MpaRatingEnum.getNameById(mpaId)); + + List genreList = new ArrayList<>(); + film.setGenre(genreList); + List genreId = jdbcTemplate.query("select genre_id from genre_film where film_id = ?", + (rs, rowNum) -> rs.getInt("GENRE_ID"), + film.getFilmId()); + genreId.forEach(g -> { + Genre genre = new Genre(); + genre.setId(g); + genre.setName(GenreEnum.getNameById(g)); + genreList.add(genre); + }); + return film; } @Override diff --git a/src/main/java/ru/yandex/practicum/filmorate/sortage/GenreDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/sortage/GenreDbStorage.java new file mode 100644 index 0000000..1585a78 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/sortage/GenreDbStorage.java @@ -0,0 +1,38 @@ +package ru.yandex.practicum.filmorate.sortage; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Service; +import ru.yandex.practicum.filmorate.model.Genre; +import ru.yandex.practicum.filmorate.model.GenreEnum; + +import java.util.List; + +@Slf4j +@Service +public class GenreDbStorage { + @Autowired + JdbcTemplate jdbcTemplate; + + public Genre findById(int id) { + List genres = jdbcTemplate.query("select * from genre where genre_id = ?", (rs, rowNum) -> { + Genre genre = new Genre(); + genre.setId(rs.getInt("GENRE_ID")); + genre.setName(GenreEnum.getNameById(genre.getId())); + return genre; + }, id); + return genres.size() != 1 ? new Genre() : genres.get(0); + } + + public List findAll() { + List genres = jdbcTemplate.query("select genre_id from genre", + (rs, rowNum) -> { + Genre genre = new Genre(); + genre.setId(rs.getInt("GENRE_ID")); + genre.setName(GenreEnum.getNameById(genre.getId())); + return genre; + }); + return genres; + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/sortage/LikeFilmDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/sortage/LikeFilmDbStorage.java index b255a77..5e076a1 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/sortage/LikeFilmDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/sortage/LikeFilmDbStorage.java @@ -2,27 +2,24 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.stereotype.Component; +import org.springframework.stereotype.Service; import ru.yandex.practicum.filmorate.model.Film; +import ru.yandex.practicum.filmorate.model.MpaRating; +import ru.yandex.practicum.filmorate.model.MpaRatingEnum; import ru.yandex.practicum.filmorate.model.mapper.FilmRowMapper; -import java.util.Collection; import java.util.List; -@Component +@Service public class LikeFilmDbStorage { - private final JdbcTemplate jdbcTemplate; - @Autowired - public LikeFilmDbStorage(JdbcTemplate jdbcTemplate) { - this.jdbcTemplate = jdbcTemplate; - } + private JdbcTemplate jdbcTemplate; - public void addLike(Integer userId, Integer filmId) { - final String sqlQuery = "INSERT INTO LIKE_FILMS (USER_ID, FILM_ID) values(?, ?)"; - jdbcTemplate.update(sqlQuery, - userId, filmId); + + public boolean addLike(Integer userId, Integer filmId) { + int updatedRows = jdbcTemplate.update("INSERT INTO LIKE_FILMS values(?, ?)", userId, filmId); + return updatedRows > 0; } @@ -34,8 +31,20 @@ public void deleteLike(Integer userId, Integer filmId) { public List getPopularFilms(Integer count) { - String sqlQuery = "select FILM_ID, count(USER_ID) as LIKES from LIKE_FILMS " + - "GROUP BY FILM_ID ORDER BY LIKES DESC LIMIT ?"; - return jdbcTemplate.query(sqlQuery, new FilmRowMapper(), count); + List films = jdbcTemplate.query("SELECT * FROM FILMS AS f " + + "LEFT JOIN LIKE_FILMS AS fl ON f.film_id = fl.film_id GROUP BY f.name ORDER BY COUNT(user_id) DESC LIMIT ?", + new FilmRowMapper(), count); + films.forEach(f -> { + List query = jdbcTemplate.query("select MPA_ID from MPA_FILMS WHERE FILM_ID = ?", + (rs, rowNum) -> rs.getInt("MPA_ID"), f.getFilmId()); + + MpaRating mpa = new MpaRating(); + f.setMpa(mpa); + + int mpaId = query.get(0); + mpa.setId(mpaId); + mpa.setName(MpaRatingEnum.getNameById(mpaId)); + }); + return films; } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/sortage/MpaDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/sortage/MpaDbStorage.java new file mode 100644 index 0000000..6d25d15 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/sortage/MpaDbStorage.java @@ -0,0 +1,39 @@ +package ru.yandex.practicum.filmorate.sortage; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Service; +import ru.yandex.practicum.filmorate.model.MpaRating; + +import java.util.List; + +@Slf4j +@Service +public class MpaDbStorage { + + @Autowired + JdbcTemplate jdbcTemplate; + + public MpaRating findById(int id) { + List mpaList = jdbcTemplate.query("select mpa_id, mpa_rating from mpa_rating where mpa_id = ?", + (rs, rowNum) -> { + MpaRating mpa = new MpaRating(); + mpa.setId(rs.getInt("MPA_ID")); + mpa.setName(rs.getString("MPA_RATING")); + return mpa; + }, id); + return mpaList.size() != 1 ? new MpaRating() : mpaList.get(0); + } + + public List findAll() { + List mpaRatingList = jdbcTemplate.query("select mpa_id, mpa_rating from mpa_rating", + (rs, rowNum) -> { + MpaRating mpa = new MpaRating(); + mpa.setId(rs.getInt("MPA_ID")); + mpa.setName(rs.getString("MPA_RATING")); + return mpa; + }); + return mpaRatingList; + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/sortage/UserDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/sortage/UserDbStorage.java index 1d39820..31a39d2 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/sortage/UserDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/sortage/UserDbStorage.java @@ -2,25 +2,20 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.relational.core.sql.In; -import org.springframework.jdbc.core.BeanPropertyRowMapper; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.support.GeneratedKeyHolder; import org.springframework.jdbc.support.KeyHolder; -import org.springframework.jdbc.support.rowset.SqlRowSet; import org.springframework.stereotype.Component; import ru.yandex.practicum.filmorate.exception.UserIdNotValidation; -import ru.yandex.practicum.filmorate.exception.ValidationException; import ru.yandex.practicum.filmorate.model.User; import ru.yandex.practicum.filmorate.model.mapper.UserRowMapper; -import java.sql.*; +import java.sql.Date; +import java.sql.PreparedStatement; +import java.sql.Types; import java.time.LocalDate; -import java.util.ArrayList; import java.util.Collection; -import java.util.HashMap; import java.util.List; -import java.util.stream.Collectors; @Slf4j @Component @@ -58,7 +53,7 @@ public User findUser(Integer id) { final String sqlQuery = "select * from USERS where USER_ID = ?"; final List findUsers = jdbcTemplate.query(sqlQuery, new UserRowMapper(), id); if(findUsers.isEmpty()){ - throw new UserIdNotValidation(" sdfd"); + throw new UserIdNotValidation("Некорректный ID"); } return findUsers.get(0); } diff --git a/src/main/resources/schema b/src/main/resources/schema index 6bd69a1..79cc135 100644 --- a/src/main/resources/schema +++ b/src/main/resources/schema @@ -59,6 +59,12 @@ CREATE TABLE if not exists LIKE_FILMS film_id int references films (film_id) ); +CREATE TABLE if not exists MPA_FILMS +( + mpa_id int references MPA_RATING (mpa_id), + film_id int references films (film_id) +); + CREATE TABLE if not exists GENRE ( genre_id int auto_increment unique, @@ -83,6 +89,16 @@ MERGE INTO MPA_RATING (MPA_ID, MPA_RATING, DESCRIPTION) MERGE INTO MPA_RATING (MPA_ID, MPA_RATING, DESCRIPTION) values (5, 'NC-17', 'Лицам до 18 лет просмотр запрещён'); - - +MERGE INTO GENRE + values (1, 'COMEDY'); +MERGE INTO GENRE + values (2, 'DRAMA'); +MERGE INTO GENRE + values (3, 'CARTOON'); +MERGE INTO GENRE + values (4, 'THRILLER'); +MERGE INTO GENRE + values (5, 'DOCUMENTARY'); +MERGE INTO GENRE + values (6, 'ACTION'); diff --git a/src/test/java/ru/yandex/practicum/filmorate/controllerTest/FilmControllerTest.java b/src/test/java/ru/yandex/practicum/filmorate/controllerTest/FilmControllerTest.java index 4a37e88..000b093 100644 --- a/src/test/java/ru/yandex/practicum/filmorate/controllerTest/FilmControllerTest.java +++ b/src/test/java/ru/yandex/practicum/filmorate/controllerTest/FilmControllerTest.java @@ -4,7 +4,6 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import ru.yandex.practicum.filmorate.controller.FilmController; import ru.yandex.practicum.filmorate.model.Film; import ru.yandex.practicum.filmorate.exception.ValidationException; diff --git a/src/test/java/ru/yandex/practicum/filmorate/sortage/FilmDbStorageTest.java b/src/test/java/ru/yandex/practicum/filmorate/sortage/FilmDbStorageTest.java index d2b82a6..a361e70 100644 --- a/src/test/java/ru/yandex/practicum/filmorate/sortage/FilmDbStorageTest.java +++ b/src/test/java/ru/yandex/practicum/filmorate/sortage/FilmDbStorageTest.java @@ -6,14 +6,11 @@ import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.context.SpringBootTest; import ru.yandex.practicum.filmorate.model.Film; -import ru.yandex.practicum.filmorate.model.MpaRating; import java.time.LocalDate; -import java.util.ArrayList; import java.util.List; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -import static org.junit.jupiter.api.Assertions.*; @SpringBootTest @AutoConfigureTestDatabase From a3af540a5c54e9b6fd5041c605e3509a1553a508 Mon Sep 17 00:00:00 2001 From: Shaihattarov Ranis Date: Thu, 28 Jul 2022 02:27:08 +0300 Subject: [PATCH 5/5] =?UTF-8?q?=D0=98=D1=82=D0=BE=D0=B3=D0=BE=D0=B2=D0=B0?= =?UTF-8?q?=D1=8F=20=D0=B2=D0=B5=D1=80=D1=81=D0=B8=D1=8F=20=D0=BA=2011=20?= =?UTF-8?q?=D1=81=D0=BF=D1=80=D0=B8=D0=BD=D1=82=D1=83=20#3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yandex/practicum/filmorate/sortage/LikeFilmDbStorage.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/ru/yandex/practicum/filmorate/sortage/LikeFilmDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/sortage/LikeFilmDbStorage.java index 2604f69..66bed68 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/sortage/LikeFilmDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/sortage/LikeFilmDbStorage.java @@ -4,6 +4,8 @@ import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Component; import ru.yandex.practicum.filmorate.model.Film; +import ru.yandex.practicum.filmorate.model.MpaRating; +import ru.yandex.practicum.filmorate.model.MpaRatingEnum; import ru.yandex.practicum.filmorate.model.mapper.FilmRowMapper; import java.util.Collection;