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