diff --git a/README.md b/README.md index 2cf454a..d19e6a6 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,25 @@ -# java-filmorate -Template repository for Filmorate project. +# Filmorate +![](https://img.shields.io/badge/Language-Java-orange) + +![](https://img.shields.io/badge/Framework-Spring_boot-green) + +![](https://img.shields.io/badge/Build_automation_tool-Maven-blue) + +![](https://img.shields.io/badge/Database-H2Database-blue) + +#### Приложение имеет: + +1. Full Rest API для работы с пользователями, фильмами, жанрами и режисёрами фильмов. +2. Для хранения данных задействована H2DB. +3. Входные данные проходят валидацию. +4. Реализовано логирование на Slf4j. +5. Приложение имеет стандартные функциональности соцсети: Лента событий, добавление отзывов о фильме, добавление друзей и т.д. +6. Дополнительные характеристики фильма: рейтинг на основе кол-ва лайков от пользователей, возрастные рекомендации для просмотра, жанр фильма. +7. У фильмов реализовано свойство - режиссёр фильма с функциональностью: + - вывод всех фильмов режиссёра, отсортированных по количеству лайков. + - вывод всех фильмов режиссёра, отсортированных по годам. +8. Основные свойства пользователя: e-mail, логин, имя, день рождения. +9. Дополнительные связи пользователя: друзья, отзывы и лайки фильмам. +10. Есть возможность получения списка фильмов по определнным фильтрам и заданной сортировке + + diff --git a/src/main/java/ru/yandex/practicum/filmorate/controller/DirectorController.java b/src/main/java/ru/yandex/practicum/filmorate/controller/DirectorController.java new file mode 100644 index 0000000..fc7c8a2 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/DirectorController.java @@ -0,0 +1,57 @@ +package ru.yandex.practicum.filmorate.controller; + +import lombok.extern.slf4j.Slf4j; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import ru.yandex.practicum.filmorate.model.Director; +import ru.yandex.practicum.filmorate.service.DirectorService; + +import java.util.List; + +@RestController +@RequiredArgsConstructor +@RequestMapping ("directors") +@Slf4j +public class DirectorController { + private final DirectorService directorService; + + @GetMapping + public ResponseEntity> getAll () { + log.info("Получен запрос на список директоров"); + List directorList = directorService.getAll(); + return new ResponseEntity<>(directorList, HttpStatus.OK); + } + + @GetMapping ("{id}") + public ResponseEntity getById (@PathVariable Integer id) { + log.info("Получен запрос на директора по id " + id); + Director director = directorService.getById(id); + return new ResponseEntity<>(director, HttpStatus.OK); + } + + + + @PostMapping + public ResponseEntity create (@RequestBody Director director) { + log.info("Получен запрос на создание директора"); + Director createdDirector = directorService.create(director); + return new ResponseEntity<>(createdDirector, HttpStatus.OK); + } + + + @PutMapping + public ResponseEntity update (@RequestBody Director director) { + log.info("Получен запрос на обновление директора с id " + director.getId()); + Director updatedDirector = directorService.update(director); + return new ResponseEntity<>(updatedDirector, HttpStatus.OK); + } + + @DeleteMapping ("{id}") + public ResponseEntity delete (@PathVariable Integer id) { + log.info("Получен запрос на удаление директора с id " + id); + Director director = directorService.delete(id); + return new ResponseEntity<>(director, HttpStatus.OK); + } +} 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 765d7fa..9fe6912 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java @@ -8,7 +8,9 @@ import ru.yandex.practicum.filmorate.service.FilmService; import javax.validation.Valid; -import java.util.*; +import javax.validation.constraints.NotBlank; +import java.util.Collection; +import java.util.List; @Slf4j @RestController @@ -18,6 +20,11 @@ public class FilmController { private final FilmService filmService; + @GetMapping("/common") + public List getCommonFilms(@RequestParam("userId") int userId, @RequestParam("friendId") int friendId) { + return filmService.getCommonFilms(userId, friendId); + } + @GetMapping public Collection getAllFilms() { return filmService.getAllFilms(); @@ -25,41 +32,59 @@ public Collection getAllFilms() { @GetMapping("/{id}") public Film getFilm(@PathVariable int id) { + log.info("Get Film {}", id); return filmService.getFilmById(id); } @PostMapping @ResponseStatus(HttpStatus.CREATED) public Film createFilm(@Valid @RequestBody Film film) { - log.info("Creating Film " + film); + log.info("Creating Film {}", film.getName()); return filmService.createFilm(film); } @PutMapping public Film updateFilm(@Valid @RequestBody Film film) { - log.info("Updating Film " + film); + log.info("Updating Film {}", film.getName()); return filmService.updateFilm(film); } @DeleteMapping("/{id}") public void deleteFilm(@PathVariable int id) { - log.info("Deleting Film " + id); + log.info("Deleting Film {}", id); filmService.deleteFilmById(id); } @GetMapping("/popular") public List getPopularFilms(@RequestParam(defaultValue = "10") int count) { + log.info("Получен запрос на список популярных фильмов"); return filmService.getPopularFilms(count); } @PutMapping("/{id}/like/{userId}") public Film likeFilm(@PathVariable int id, @PathVariable int userId) { + log.info("Получен запрос на добавление лайка фильму id " + id + " юзером " + userId); return filmService.likeFilm(id, userId); } @DeleteMapping("/{id}/like/{userId}") public Film deleteLikeFromFilm(@PathVariable int id, @PathVariable int userId) { + log.info("Получен запрос на удаление лайка фильму id " + id + " юзером " + userId); return filmService.deleteLikeFromFilm(id, userId); } + + @GetMapping ("director/{directorId}") + public List getFilmsByDirectorId (@PathVariable Integer directorId, + @RequestParam (value = "sortBy")String param) { + log.info("Получен запрос на получение"); + return filmService.getFilmsSortedByLikesOrYear(directorId, param); + } + + @GetMapping("/search") + public List searchFilms(@RequestParam @NotBlank String query, + @RequestParam @NotBlank List by) { + log.info("Получен запрос на поиск фильмов"); + return filmService.searchFilms(query, by); + } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/controller/GenreController.java b/src/main/java/ru/yandex/practicum/filmorate/controller/GenreController.java index a232dd9..92e071a 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/GenreController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/GenreController.java @@ -1,32 +1,32 @@ 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.Genre; -import ru.yandex.practicum.filmorate.service.FilmService; +import ru.yandex.practicum.filmorate.service.GenreService; import java.util.List; @Slf4j @RestController +@RequiredArgsConstructor @RequestMapping("/genres") public class GenreController { - private final FilmService filmService; - - public GenreController(FilmService filmService) { - this.filmService = filmService; - } + private final GenreService genreService; @GetMapping public List getAllGenres(){ - return filmService.getAllGenres(); + log.info("Получен запрос на получение списка жанров"); + return genreService.getAllGenres(); } @GetMapping("/{id}") public Genre getGenreById(@PathVariable("id") int id){ - return filmService.getGenreById(id); + log.info("Получен запрос на получение жанра по id " + id); + return genreService.getGenreById(id); } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/controller/MpaController.java b/src/main/java/ru/yandex/practicum/filmorate/controller/MpaController.java index 42ce986..f544368 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/MpaController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/MpaController.java @@ -1,33 +1,33 @@ 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.Mpa; -import ru.yandex.practicum.filmorate.service.FilmService; +import ru.yandex.practicum.filmorate.service.MpaService; import java.util.List; @Slf4j @RestController +@RequiredArgsConstructor @RequestMapping("/mpa") public class MpaController { - private final FilmService filmService; - - public MpaController(FilmService filmService) { - this.filmService = filmService; - } + private final MpaService mpaService; @GetMapping public List getAllMpa(){ - return filmService.getAllMpa(); + log.info("Получен запрос на получение списка Mpa"); + return mpaService.getAllMpa(); } @GetMapping("/{id}") public Mpa getMpaById(@PathVariable("id") int id){ - return filmService.getMpaById(id); + log.info("Получен запрос на получение Mpa по id " + id); + return mpaService.getMpaById(id); } } \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/controller/ReviewController.java b/src/main/java/ru/yandex/practicum/filmorate/controller/ReviewController.java new file mode 100644 index 0000000..fd8d3ab --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/ReviewController.java @@ -0,0 +1,92 @@ +package ru.yandex.practicum.filmorate.controller; + +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; +import ru.yandex.practicum.filmorate.model.Review; +import ru.yandex.practicum.filmorate.service.review.ReviewService; + +import javax.validation.Valid; +import java.util.List; + +@RestController +@Slf4j +@AllArgsConstructor +@RequestMapping("/reviews") +public class ReviewController { + private final ReviewService service; + + @GetMapping("/{id}") + public Review getReviewById(@PathVariable("id") Long reviewId) { + Review review = service.getById(reviewId); + log.info("Обзор с ID #{} пользователя с ID #{} передан", review.getReviewId(), review.getUserId()); + return review; + + } + + @PostMapping + public Review postReview(@RequestBody @Valid Review review) { + review = service.add(review); + log.info("Отзыв с ID #{} добавлен.", review.getReviewId()); + return review; + } + + @PutMapping + public Review putReview(@RequestBody @Valid Review review) { + review = service.update(review); + log.info("Отзыв с ID #{} обновлен.", review.getReviewId()); + return review; + } + + @DeleteMapping("/{id}") + public void deleteReview(@PathVariable("id") long reviewId) { + service.delete(reviewId); + log.info("Отзыв с ID #{} удален.", reviewId); + } + + @GetMapping + public List getReviewsByFilmId(@RequestParam(required = false) Long filmId, + @RequestParam(defaultValue = "10") Integer count) { + List reviews; + if (filmId == null) { + reviews = service.getAllReviews(); + log.info("Передан список всех отзывов в размере {}", reviews.size()); + return reviews; + } + reviews = service.getAllReviewsByFilmId(filmId, count); + log.info("Список отзывов в размере {} фильма с ID {}", count, filmId); + return reviews; + } + + @PutMapping("/{id}/like/{userId}") + public Review addLike(@PathVariable("id") Long reviewId, @PathVariable Long userId) { + Review review = service.addLike(reviewId, userId); + log.info("Лайк отзыв с ID #{} пользователя c ID #{} добавлен. Количество лайков отзыва {}", + reviewId, userId, review.getUseful()); + return review; + } + + @PutMapping("/{id}/dislike/{userId}") + public Review addDislike(@PathVariable("id") Long reviewId, @PathVariable Long userId) { + Review review = service.addDislike(reviewId, userId); + log.info("Дизлайк отзыва с ID #{} пользователя c ID #{} добавлен. Количество лайков отзыва {}", + reviewId, userId, review.getUseful()); + return review; + } + + @DeleteMapping("/{id}/like/{userId}") + public Review deleteLike(@PathVariable("id") Long reviewId, @PathVariable Long userId) { + Review review = service.deleteLike(reviewId, userId); + log.info("Лайк отзыва с ID #{} пользователя c ID #{} удален. Количество лайков отзыва {}", + reviewId, userId, review.getUseful()); + return review; + } + + @DeleteMapping("/{id}/dislike/{userId}") + public Review deleteDislike(@PathVariable("id") Long reviewId, @PathVariable Long userId) { + Review review = service.deleteDislike(reviewId, userId); + log.info("Дизлайк отзыва с ID #{} пользователя c ID #{} удален. Количество лайков отзыва {}", + reviewId, userId, review.getUseful()); + return review; + } +} \ No newline at end of file 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 895a51c..239fd5a 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java @@ -3,11 +3,14 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.*; +import ru.yandex.practicum.filmorate.model.Event; +import ru.yandex.practicum.filmorate.model.Film; import ru.yandex.practicum.filmorate.model.User; import ru.yandex.practicum.filmorate.service.UserService; import javax.validation.Valid; -import java.util.*; +import java.util.Collection; +import java.util.List; @Slf4j @RestController @@ -49,7 +52,6 @@ public void deleteUserById(@PathVariable int id) { @PutMapping("/{id}/friends/{friendId}") public void addFriend(@PathVariable("id") Integer id, @PathVariable("friendId") Integer friendId) { - log.info("Friends list" + userService.getById(id) + " added " + userService.getById(friendId)); userService.addFriend(id, friendId); } @@ -62,13 +64,27 @@ public User deleteFriend(@PathVariable int id, @PathVariable int friendId) { @GetMapping("/{id}/friends") public List getFriends(@PathVariable int id) { + log.info("Получен запрос на получение список друзей юзера с id " + id); return userService.getFriends(id); } @GetMapping("/{id}/friends/common/{otherId}") public List getCommonFriends(@PathVariable int id, @PathVariable int otherId) { + log.info("Получен запрос на получение списка общих друзей пользователя id " + id); return userService.getCommonFriends(id, otherId); } + + @GetMapping("/{id}/recommendations") + public List getRecommendedFilms(@PathVariable int id) { + log.info("Получен запрос на получение"); + return userService.getRecommendedFilms(id); + } + + @GetMapping("/{id}/feed") + public List getEvents(@PathVariable int id) { + log.info("Получен запрос на получение списка событий пользователя с id " + id); + return userService.getEvents(id); + } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dao/DirectorDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dao/DirectorDbStorage.java new file mode 100644 index 0000000..468eb2f --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/DirectorDbStorage.java @@ -0,0 +1,142 @@ +package ru.yandex.practicum.filmorate.dao; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +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.dao.mappers.DirectorMapper; +import ru.yandex.practicum.filmorate.dao.mappers.FilmMapper; +import ru.yandex.practicum.filmorate.exceptions.NotFoundObjectException; +import ru.yandex.practicum.filmorate.exceptions.ValidationException; +import ru.yandex.practicum.filmorate.model.Director; +import ru.yandex.practicum.filmorate.model.Film; +import ru.yandex.practicum.filmorate.storage.DirectorStorage; + +import java.sql.PreparedStatement; +import java.sql.Statement; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +@Component +@RequiredArgsConstructor +@Slf4j +public class DirectorDbStorage implements DirectorStorage { + private final JdbcTemplate jdbcTemplate; + private final FilmMapper filmMapper; + + @Override + public List findAll() { + String statement = "SELECT * FROM directors"; + List directors = jdbcTemplate.query(statement, new DirectorMapper()); + directors.forEach(director -> director.setFilms(new HashSet<>(findFilmsByDirectorId(director.getId())))); + return directors; + } + + @Override + public Director create(Director director) { + if(director.getName() == null || director.getName().isBlank()) { + log.warn("Имя директора отсутствует"); + throw new ValidationException("Name is required"); + } + String statement = "INSERT INTO directors (directorName) VALUES (?)"; + KeyHolder keyHolder = new GeneratedKeyHolder(); + jdbcTemplate.update(connection -> { + PreparedStatement ps = connection + .prepareStatement(statement, Statement.RETURN_GENERATED_KEYS); + ps.setString(1, director.getName()); + return ps; + }, keyHolder); + if (keyHolder.getKey() != null) { + if (director.getFilms() != null) { + updateFilmsForDirector(keyHolder.getKey().intValue(), director.getFilms()); + } + return findById(keyHolder.getKey().intValue()); + } + return null; + } + + @Override + public Director update(Director director) { + if (director.getId() == null) { + log.warn("Обьект директора = null"); + throw new NotFoundObjectException("Director id is null"); + } + if (! isExists(director.getId())) { + throw new NotFoundObjectException("Director with " + director.getId() + " not found"); + } + if(director.getName() == null || director.getName().isBlank()) { + throw new ValidationException("Name is required"); + } + String statement = "UPDATE directors SET directorName = ? WHERE directorId = ?"; + jdbcTemplate.update(statement, director.getName(), director.getId()); + if (director.getFilms() != null) { + updateFilmsForDirector(director.getId(), director.getFilms()); + } + + return findById(director.getId()); + } + + @Override + public Director findById(Integer id) { + if (! isExists(id)) { + throw new NotFoundObjectException("Director with " + id + " not found"); + } + String statement = "SELECT * FROM directors WHERE directorID = ?"; + Director director = jdbcTemplate.queryForObject(statement, new DirectorMapper(), id); + if (director == null) { + throw new NotFoundObjectException("Director with " + id + " not found"); + } + director.setFilms(new HashSet<>(findFilmsByDirectorId(id))); + return director; + } + + @Override + public Director delete(Integer id) { + Director director = findById(id); + String statement = "DELETE FROM directors WHERE directorID = ?"; + jdbcTemplate.update(statement, id); + return director; + } + + public List findFilmsByDirectorId (Integer directorId) { + if (! isExists(directorId)) { + throw new NotFoundObjectException("Director with " + directorId + " not found"); + } + String statement = "SELECT directorId,films.* FROM directorFilm " + + "LEFT JOIN films ON directorFilm.filmId = films.filmId WHERE directorId = ?"; + List films = jdbcTemplate.query(statement, filmMapper, directorId); + if (films != null) { + films.forEach(film -> film.setDirectors(new HashSet<>(findDirectorsByFilmId(film.getId())))); + } + return films; + } + + private void updateFilmsForDirector (Integer directorId, Set films) { + String deleteStatement = "DELETE FROM directorFilm WHERE directorId = ?"; + int commaAndSpace = 2; + jdbcTemplate.update(deleteStatement, directorId); + StringBuilder updateStatment = new StringBuilder("INSERT INTO directorFilm (directorId, filmId) VALUES "); + films.forEach(film -> updateStatment.append(String.format("(%d, %d), ", directorId, film.getId()))); + updateStatment.setLength(updateStatment.length() - commaAndSpace); + jdbcTemplate.update(updateStatment.toString()); + } + + private boolean isExists(Integer id) { + String s = "SELECT COUNT(*) FROM directors WHERE directorId=?"; + Long obj = jdbcTemplate.queryForObject(s, Long.class, id); + if (obj != null) { + return obj != 0; + } else { + return false; + } + } + + private List findDirectorsByFilmId (Integer filmId) { + String statement = "SELECT df.filmId, d.directorId, d.directorName FROM directorFilm AS dF " + + "LEFT JOIN directors AS d ON df.directorId = d.directorId WHERE df.filmId = ?"; + return jdbcTemplate.query(statement, new DirectorMapper(), filmId); + } +} \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/dao/EventDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dao/EventDbStorage.java new file mode 100644 index 0000000..7aaacb1 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/EventDbStorage.java @@ -0,0 +1,42 @@ +package ru.yandex.practicum.filmorate.dao; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Component; +import ru.yandex.practicum.filmorate.dao.mappers.EventMapper; +import ru.yandex.practicum.filmorate.exceptions.NotFoundObjectException; +import ru.yandex.practicum.filmorate.model.Event; +import ru.yandex.practicum.filmorate.model.enums.EventTypes; +import ru.yandex.practicum.filmorate.model.enums.OperationTypes; + +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; + +@Component +@RequiredArgsConstructor +@Slf4j +public class EventDbStorage { + private final JdbcTemplate jdbcTemplate; + + public void saveEvent(long userId, EventTypes eventType, OperationTypes operation, long entityId) { + Timestamp timestamp = Timestamp.valueOf(LocalDateTime.now()); + jdbcTemplate.update("INSERT INTO events(TIMESTAMP, USERID, EVENTTYPE, OPERATION, ENTITYID) VALUES (?,?,?,?,?)", + timestamp.getTime() , userId, eventType.name(), operation.name(), entityId); + log.info("Событие для пользователя " + userId + " сохранено"); + } + + public List getEvent(int id) { + if (!jdbcTemplate.queryForRowSet("SELECT USERID FROM USERS WHERE USERID =?", id).next()) { + log.warn("Пользователь " + id + " не найден"); + throw new NotFoundObjectException("User with id " + id + " not found"); + } + log.info("Список событий для пользователя " + id + " отправлен"); + return jdbcTemplate.queryForStream("SELECT * " + + "FROM EVENTS " + + "WHERE USERID = ?;", (rs, rowNum) -> + new EventMapper().mapRow(rs, rowNum), id).collect(Collectors.toList()); + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/dao/FilmDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dao/FilmDbStorage.java index e61dcb2..1fba54e 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dao/FilmDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/FilmDbStorage.java @@ -1,87 +1,134 @@ package ru.yandex.practicum.filmorate.dao; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; 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.exceptions.GenreNotFoundException; -import ru.yandex.practicum.filmorate.exceptions.MpaNotFoundException; +import ru.yandex.practicum.filmorate.dao.mappers.DirectorMapper; +import ru.yandex.practicum.filmorate.dao.mappers.FilmMapper; import ru.yandex.practicum.filmorate.exceptions.NotFoundObjectException; import ru.yandex.practicum.filmorate.exceptions.ValidationException; +import ru.yandex.practicum.filmorate.model.Director; import ru.yandex.practicum.filmorate.model.Film; import ru.yandex.practicum.filmorate.model.Genre; -import ru.yandex.practicum.filmorate.model.Mpa; import ru.yandex.practicum.filmorate.storage.FilmStorage; -import ru.yandex.practicum.filmorate.validators.FilmValidator; -import java.sql.ResultSet; -import java.sql.SQLException; +import java.sql.PreparedStatement; import java.time.LocalDate; -import java.util.ArrayList; -import java.util.List; +import java.util.*; @Slf4j @Component("filmDBStorage") +@RequiredArgsConstructor public class FilmDbStorage implements FilmStorage { - private static int filmId = 0; private final JdbcTemplate jdbcTemplate; - private static final LocalDate FILMSTARTDATE = LocalDate.of(1895, 12, 28); + private static final LocalDate FILM_START_DATE = LocalDate.of(1895, 12, 28); + private final FilmMapper filmMapper; - - public FilmDbStorage(JdbcTemplate jdbcTemplate) { - this.jdbcTemplate = jdbcTemplate; - } - - private int generateFilmId() { - return ++filmId; + @Override + public List getAllFilms() { + List films = jdbcTemplate.query("SELECT * FROM films", filmMapper); + if (films != null) { + films.forEach(film -> film.setDirectors(new HashSet<>(getDirectorByFilmId(film.getId())))); + } + return films; } @Override - public List getAllFilms() { - return jdbcTemplate.query("SELECT * FROM films", new FilmMapper(jdbcTemplate, this)); + public List getCommonFilms(int userId, int friendId) { + String sql = "SELECT DISTINCT(FILMID), NAME, DESCRIPTION, RELEASE_DATE, DURATION, MPAID " + + "FROM FILMS as f JOIN " + + "(SELECT A.FILMID as FI, A.USERID as AU, B.USERID as BU " + + "FROM LIKESLIST A, LIKESLIST B " + + "WHERE A.FILMID = B.FILMID AND A.USERID <> B.USERID) as common " + + "ON f.FILMID = common.FI " + + "WHERE (AU = " + userId + " AND BU = " + friendId + ")"; + return jdbcTemplate.query(sql, filmMapper); } @Override public Film getFilmById(int id) { String sql = "SELECT * FROM films WHERE filmId="+id; if (checkFilmInDb(id)){ - return jdbcTemplate.query(sql, this::makeFilm); - }else{ + Film film = jdbcTemplate.query(sql, filmMapper).get(0); + if (film != null) { + film.setDirectors(new HashSet<>(getDirectorByFilmId(id))); + } + return film; + } else { return null; } } @Override public Film createFilm(Film film) { - if (film.getReleaseDate().isBefore(FILMSTARTDATE)) { + if (film.getReleaseDate().isBefore(FILM_START_DATE)) { log.info("Не пройдена валидация даты выпуска фильма. Так рано фильмы не снимали!"); throw new ValidationException("Так рано фильмы не снимали!"); - }else{ - film.setId(generateFilmId()); - jdbcTemplate.update("INSERT INTO films(filmId, name, description, release_date, duration, mpaId) " + - "VALUES(?,?,?,?,?,?)", - film.getId(), - film.getName(), - film.getDescription(), - film.getReleaseDate(), - film.getDuration(), - film.getMpa().getId()); - if(film.getGenres().size()>0){ - for (Genre genre : film.getGenres()){ - jdbcTemplate.update("INSERT INTO film_genre(filmId, genreId) VALUES(?,?)", - film.getId(), - genre.getId()); - } - } - } - return film; + String sql = "INSERT INTO FILMS (MPAID, NAME, DESCRIPTION, RELEASE_DATE, DURATION) VALUES (?,?,?,?,?)"; + KeyHolder keyHolder = new GeneratedKeyHolder(); + + jdbcTemplate.update(connection -> { + PreparedStatement ps = connection.prepareStatement(sql, new String[]{"filmId"}); + ps.setInt(1, film.getMpa().getId()); + ps.setString(2, film.getName()); + ps.setString(3, film.getDescription()); + ps.setDate(4, java.sql.Date.valueOf(film.getReleaseDate())); + ps.setInt(5, film.getDuration()); + return ps; + }, keyHolder); + int generatedId = Objects.requireNonNull(keyHolder.getKey()).intValue(); + film.setId(generatedId); + setGenres(film); + setDirectors(film); + return getFilmById(generatedId); + } + + private void setGenres(Film film) { + Set genres = film.getGenres(); + int filmId = film.getId(); + int commaAndSpace = 2; + if (genres.size() > 0) { + StringBuilder sqlGenre = new StringBuilder("INSERT INTO film_genre(filmId, genreId) VALUES "); + genres.forEach(genre -> sqlGenre.append(String.format("(%d, %d), ", filmId, genre.getId()))); + sqlGenre.setLength(sqlGenre.length() - commaAndSpace); + jdbcTemplate.update(sqlGenre.toString()); + } + } + + private void setDirectors(Film film) { + int commaAndSpace = 2; + int filmId = film.getId(); + jdbcTemplate.update("DELETE FROM directorFilm WHERE filmId = ?", film.getId()); + Set directors = film.getDirectors(); + if (directors != null && directors.size() > 0) { + StringBuilder sqlDirectors = new StringBuilder("INSERT INTO directorFilm (filmId, directorId) VALUES "); + directors.forEach(director -> sqlDirectors.append(String.format("(%d, %d), ", filmId, director.getId()))); + sqlDirectors.setLength(sqlDirectors.length() - commaAndSpace); + jdbcTemplate.update(sqlDirectors.toString()); + } + } + + private void setLikes(Film film) { + int commaAndSpace = 2; + int filmId = film.getId(); + Set likes = film.getLikes(); + if (likes.size() > 0) { + StringBuilder sql = new StringBuilder("INSERT INTO likesList VALUES "); + likes.forEach(userId -> sql.append(String.format("(%d, %d)", filmId, userId))); + sql.setLength(sql.length() - commaAndSpace); + jdbcTemplate.update(sql.toString()); + } } @Override public Film updateFilm(Film film) { - if(checkFilmInDb(film.getId())){ + if(checkFilmInDb(film.getId())) { jdbcTemplate.update("UPDATE films SET name=?, description=?, release_date=?, duration=?, " + "mpaId=? WHERE filmId=?", film.getName(), @@ -92,27 +139,25 @@ public Film updateFilm(Film film) { film.getId()); jdbcTemplate.update("DELETE FROM likesList WHERE filmId=?", film.getId()); jdbcTemplate.update("DELETE FROM film_genre WHERE filmId=?", film.getId()); - for(Integer userId : film.getLikes()){ - jdbcTemplate.update("INSERT INTO likesList VALUES(?,?)", film.getId(), userId); - } - for (Genre genre : film.getGenres()){ - jdbcTemplate.update("INSERT INTO film_genre(filmId, genreId) " + - "VALUES(?,?)", film.getId(), genre.getId()); - } + setLikes(film); + setGenres(film); + setDirectors(film); } - return film; + return getFilmById(film.getId()); } @Override public void deleteFilmById(int filmId) { - checkFilmInDb(filmId); - + if(checkFilmInDb(filmId)) { + jdbcTemplate.update("DELETE FROM films where filmId = ?", filmId); + } else { + throw new NotFoundObjectException("Фильм с filmId " + filmId + " не был удален."); + } } @Override public Film likeFilm(int filmId, int userId) { checkFilmInDb(filmId); - checkUserInDb(userId); jdbcTemplate.update("INSERT INTO likesList VALUES (?,?)", filmId, userId); return getFilmById(filmId); } @@ -120,7 +165,6 @@ public Film likeFilm(int filmId, int userId) { @Override public Film deleteLikeFromFilm(int filmId, int userId) { checkFilmInDb(filmId); - checkUserInDb(userId); jdbcTemplate.update("DELETE FROM likesList WHERE filmId=? AND userId=?", filmId, userId); return getFilmById(filmId); } @@ -129,139 +173,57 @@ public Film deleteLikeFromFilm(int filmId, int userId) { public List getPopularFilms(int count) { String sql = "SELECT f.*, count(fl.userId) AS likes FROM films AS f LEFT JOIN likesList AS fl " + "ON f.filmId=fl.filmId GROUP BY f.filmId ORDER BY likes DESC LIMIT "+count; - return jdbcTemplate.query(sql, new FilmMapper(jdbcTemplate, this)); - } - - @Override - public List getAllGenres() { - List genres = new ArrayList<>(); - SqlRowSet allGenres = jdbcTemplate.queryForRowSet("SELECT * FROM genre"); - while(allGenres.next()){ - Genre genre = new Genre(allGenres.getInt("genreId"), allGenres.getString("name")); - genres.add(genre); + List films = jdbcTemplate.query(sql, filmMapper); + if (films != null) { + films.forEach(film -> film.setDirectors(new HashSet<>(getDirectorByFilmId(film.getId())))); } - return genres; + return films; } - @Override - public Genre getGenreById(int id) { - checkGenreInDb(id); - SqlRowSet genreRows = jdbcTemplate.queryForRowSet("SELECT * FROM genre WHERE genreId = ?", id); - if(genreRows.next()){ - Genre genre = new Genre(genreRows.getInt("genreId"), - genreRows.getString("name")); - log.info("Жанр с id={}, это {}.", genre.getId(), genre.getName()); - return genre; - }else{ - log.info("Жанра с таким id нет!"); - return null; - } - } - - @Override - public List getAllMpa() { - List mpas = new ArrayList<>(); - SqlRowSet allMpas = jdbcTemplate.queryForRowSet("SELECT * FROM mpa"); - while(allMpas.next()){ - Mpa mpa = new Mpa(allMpas.getInt("mpaId"), allMpas.getString("name")); - mpas.add(mpa); - } - return mpas; - } - - public Mpa getMpaById(int id){ - checkMpaInDb(id); - SqlRowSet mpaRows = jdbcTemplate.queryForRowSet("SELECT * FROM mpa WHERE mpaId = ?", id); - if(mpaRows.next()){ - Mpa mpa = new Mpa(mpaRows.getInt("mpaId"), mpaRows.getString("name")); - log.info("Рейтинг с id={}, это {}.", mpa.getId(), mpa.getName()); - return mpa; - }else{ - log.info("Рейтинга с таким id нет!"); - return null; - } - } - - private boolean checkMpaInDb(int id){ - String sql = "SELECT mpaId FROM mpa"; - SqlRowSet getMpaFromDb = jdbcTemplate.queryForRowSet(sql); - List ids = new ArrayList<>(); - while (getMpaFromDb.next()){ - ids.add(getMpaFromDb.getInt("mpaId")); - } - if(ids.contains(id)){ + private boolean checkFilmInDb(Integer id) { + String sql = "SELECT filmId FROM films where filmId =?"; + SqlRowSet getFilmFromDb = jdbcTemplate.queryForRowSet(sql, id); + if (getFilmFromDb.next()) { return true; - }else{ - throw new MpaNotFoundException("Рейтинга с таким id нет в базе!"); + } else { + throw new NotFoundObjectException("Фильма с таким id нет в базе!"); } } - private boolean checkFilmInDb(Integer id){ - String sql = "SELECT filmId FROM films"; - SqlRowSet getFilmFromDb = jdbcTemplate.queryForRowSet(sql); - List ids = new ArrayList<>(); - while (getFilmFromDb.next()){ - ids.add(getFilmFromDb.getInt("filmId")); - } - if(ids.contains(id)){ - return true; - }else{ - throw new NotFoundObjectException("Фильма с таким id нет в базе!"); - } + private List getDirectorByFilmId (Integer filmId) { + String statement = "SELECT df.filmId, d.directorId, d.directorName " + + "FROM directorFilm AS df LEFT JOIN directors AS d " + + "ON df.directorId = d.directorId WHERE df.filmId = ?"; + return jdbcTemplate.query(statement, new DirectorMapper(), filmId); } - private Film makeFilm(ResultSet rs) throws SQLException { - if (rs.next()){ - Film film = new Film(rs.getString("name"), - rs.getString("description"), - rs.getDate("release_date").toLocalDate(), - rs.getInt("duration")); - film.setId(rs.getInt("filmId")); - film.setMpa(getMpaById(rs.getInt("mpaId"))); - SqlRowSet getFilmGenres = jdbcTemplate.queryForRowSet("SELECT genreId FROM film_genre WHERE filmId=?", film.getId()); - while(getFilmGenres.next()){ - Genre genre = getGenreById(getFilmGenres.getInt("genreId")); - film.addGenre(genre); + public List searchFilms(String query1, List by) { + List searchResultFilms = new ArrayList<>(); + if (by.size() == 1) { + if (by.get(0).equalsIgnoreCase("title")) { + searchResultFilms.addAll(Objects.requireNonNull(jdbcTemplate.query( + "SELECT * FROM films WHERE lower(name) LIKE ?", filmMapper, "%" + query1.toLowerCase() + "%"))); } - SqlRowSet getFilmLikes = jdbcTemplate.queryForRowSet("SELECT userId FROM likesList WHERE filmId = ?", - film.getId()); - while(getFilmLikes.next()){ - film.addLIke(getFilmLikes.getInt("userId")); + if (by.get(0).equalsIgnoreCase("director")) { + searchByDirector(query1,searchResultFilms); } - return film; - }else{ - return null; } - - } - - private boolean checkGenreInDb(Integer id){ - String sql = "SELECT genreId FROM genre"; - SqlRowSet getGenreFromDb = jdbcTemplate.queryForRowSet(sql); - List ids = new ArrayList<>(); - while (getGenreFromDb.next()){ - ids.add(getGenreFromDb.getInt("genreId")); - } - if(ids.contains(id)){ - return true; - }else{ - throw new GenreNotFoundException("Жанра с таким id нет в базе!"); + if (by.size() == 2) { + searchResultFilms.addAll(Objects.requireNonNull(jdbcTemplate.query( + "SELECT * FROM films WHERE lower(name) LIKE ?", filmMapper, "%" + query1.toLowerCase() + "%"))); + searchByDirector(query1,searchResultFilms); } + return searchResultFilms; } - private boolean checkUserInDb(Integer id){ - String sql = "SELECT userId FROM users"; - SqlRowSet getUsersFromDb = jdbcTemplate.queryForRowSet(sql); - List ids = new ArrayList<>(); - while (getUsersFromDb.next()){ - ids.add(getUsersFromDb.getInt("userId")); - } - if(ids.contains(id)){ - return true; - }else{ - throw new NotFoundObjectException("Пользователя с таким id нет в базе!"); + private void searchByDirector(String query1, List searchResultFilms) { + SqlRowSet searchByDirector = jdbcTemplate.queryForRowSet( + "SELECT df.* FROM directorFilm as df " + + "INNER JOIN directors as d ON df.directorId = d.directorId " + + "WHERE lower(d.directorName) LIKE lower(CONCAT('%',?,'%'))", query1); + while (searchByDirector.next()) { + Integer filmId1 = searchByDirector.getInt("filmId"); + searchResultFilms.add(getFilmById(filmId1)); } - } - -} +} \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/dao/GenreDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dao/GenreDbStorage.java new file mode 100644 index 0000000..e332abc --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/GenreDbStorage.java @@ -0,0 +1,84 @@ +package ru.yandex.practicum.filmorate.dao; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.support.rowset.SqlRowSet; +import org.springframework.stereotype.Component; +import ru.yandex.practicum.filmorate.exceptions.GenreNotFoundException; +import ru.yandex.practicum.filmorate.model.Genre; +import ru.yandex.practicum.filmorate.storage.GenreStorage; + +import java.util.*; + +@Slf4j +@Component +@RequiredArgsConstructor +public class GenreDbStorage implements GenreStorage { + + private final JdbcTemplate jdbcTemplate; + + @Override + public List getAllGenres() { + List genres = new ArrayList<>(); + SqlRowSet allGenres = jdbcTemplate.queryForRowSet("SELECT * FROM genre"); + while (allGenres.next()) { + Genre genre = new Genre(allGenres.getInt("genreId"), allGenres.getString("name")); + genres.add(genre); + } + return genres; + } + + @Override + public Genre getGenreById(int id) { + SqlRowSet genreRows = jdbcTemplate.queryForRowSet("SELECT * FROM genre WHERE genreId = ?", id); + if (genreRows.next()) { + return new Genre(genreRows.getInt("genreId"), + genreRows.getString("name")); + } else { + throw new GenreNotFoundException("Жанра с таким id нет в базе!"); + } + } + + @Override + public Set getGenresByFilmId(int filmId) { + Set filmGenres = new HashSet<>(); + SqlRowSet rs = jdbcTemplate.queryForRowSet("SELECT * FROM FILM_GENRE as fg " + + "LEFT JOIN GENRE as g " + + "ON fg.GENREID = g.GENREID " + + "WHERE filmId = ?", filmId); + while (rs.next()) { + filmGenres.add(new Genre(rs.getInt("genreId"), rs.getString("name"))); + } + return filmGenres; + } + + @Override + public Map getGenresMap() { + Map genreIdNamesMap = new HashMap<>(); + List genres = getAllGenres(); + for (Genre genre : genres) { + genreIdNamesMap.put(genre.getId(), genre); + } + return genreIdNamesMap; + } + + public Map> getAllGenresOfAllFilms() { + Map> genresOfAllFilms = new HashMap<>(); + String sql = "SELECT filmId, genreId FROM FILM_GENRE"; + SqlRowSet rs = jdbcTemplate.queryForRowSet(sql); + Map allGenres = getGenresMap(); + while (rs.next()) { + Integer filmId = rs.getInt("filmId"); + Integer genreId = rs.getInt("genreId"); + if (genresOfAllFilms.containsKey(filmId)) { + genresOfAllFilms.get(filmId).add(allGenres.get(genreId)); + continue; + } + genresOfAllFilms.put(filmId, new HashSet<>() {{ + add(allGenres.get(genreId)); + }}); + } + return genresOfAllFilms; + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/dao/MpaDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dao/MpaDbStorage.java new file mode 100644 index 0000000..927a79c --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/MpaDbStorage.java @@ -0,0 +1,70 @@ +package ru.yandex.practicum.filmorate.dao; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.support.rowset.SqlRowSet; +import org.springframework.stereotype.Component; +import ru.yandex.practicum.filmorate.exceptions.MpaNotFoundException; +import ru.yandex.practicum.filmorate.model.Mpa; +import ru.yandex.practicum.filmorate.storage.MpaStorage; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Slf4j +@Component +@RequiredArgsConstructor +public class MpaDbStorage implements MpaStorage { + + private final JdbcTemplate jdbcTemplate; + + @Override + public List getAllMpa() { + List mpas = new ArrayList<>(); + SqlRowSet allMpas = jdbcTemplate.queryForRowSet("SELECT * FROM mpa"); + while (allMpas.next()) { + Mpa mpa = new Mpa(allMpas.getInt("mpaId"), allMpas.getString("name")); + mpas.add(mpa); + } + return mpas; + } + + @Override + public Mpa getMpaById(int id) { + checkMpaInDb(id); + SqlRowSet mpaRows = jdbcTemplate.queryForRowSet("SELECT * FROM mpa WHERE mpaId = ?", id); + if (mpaRows.next()) { + return new Mpa(mpaRows.getInt("mpaId"), mpaRows.getString("name")); + } else { + return null; + } + } + + @Override + public Map getMpasMap() { + Map mpaIdNamesMap = new HashMap<>(); + List mpas = getAllMpa(); + for (Mpa mpa : mpas) { + mpaIdNamesMap.put(mpa.getId(), mpa); + } + return mpaIdNamesMap; + } + + private boolean checkMpaInDb(int id) { + String sql = "SELECT mpaId FROM mpa"; + SqlRowSet getMpaFromDb = jdbcTemplate.queryForRowSet(sql); + List ids = new ArrayList<>(); + while (getMpaFromDb.next()){ + ids.add(getMpaFromDb.getInt("mpaId")); + } + if (ids.contains(id)) { + return true; + } else { + throw new MpaNotFoundException("Рейтинга с таким id нет в базе!"); + } + } + +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/dao/UserDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dao/UserDbStorage.java index faa63ee..1aa5202 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dao/UserDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/UserDbStorage.java @@ -1,39 +1,40 @@ package ru.yandex.practicum.filmorate.dao; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Qualifier; 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.dao.mappers.FilmMapper; +import ru.yandex.practicum.filmorate.dao.mappers.UserMapper; import ru.yandex.practicum.filmorate.exceptions.NotFoundObjectException; +import ru.yandex.practicum.filmorate.model.Film; import ru.yandex.practicum.filmorate.model.User; -import ru.yandex.practicum.filmorate.service.UserService; import ru.yandex.practicum.filmorate.storage.UserStorage; +import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; +import java.util.Objects; @Slf4j @Component("userDBStorage") +@RequiredArgsConstructor public class UserDbStorage implements UserStorage { - private static int userId = 0; private final JdbcTemplate jdbcTemplate; + private final FilmMapper filmMapper; + private final UserMapper userMapper; - public UserDbStorage(JdbcTemplate jdbcTemplate) { - this.jdbcTemplate = jdbcTemplate; - } - - private int generateUserId() { - return ++userId; - } @Override public List getAllUsers() { String sql = "SELECT * FROM users"; - return jdbcTemplate.query(sql, new UserMapper(jdbcTemplate, this)); + return jdbcTemplate.query(sql, userMapper); } @Override @@ -48,17 +49,21 @@ public User getUserById(int id) { @Override public User createUser(User user) { - user.setId(generateUserId()); - if ((user.getName()==null)||(user.getName().isBlank())) { - log.info("Поле \"Имя\" пустое, ему будет присвоено значение поля \"Логин\""); - user.setName(user.getLogin()); - } - jdbcTemplate.update("INSERT INTO users(userId, email, login, name, birthdate) VALUES (?,?,?,?,?)", - user.getId(), - user.getEmail(), - user.getLogin(), - user.getName(), - user.getBirthday()); + String sql = "INSERT INTO users (NAME, EMAIL, LOGIN, BIRTHDATE) VALUES (?, ?, ?, ?)"; + KeyHolder keyHolder = new GeneratedKeyHolder(); + + + jdbcTemplate.update(connection -> { + PreparedStatement ps = connection.prepareStatement(sql, + new String[]{"userid"}); + ps.setString(1, user.getName()); + ps.setString(2, user.getEmail()); + ps.setString(3, user.getLogin()); + ps.setDate(4, java.sql.Date.valueOf(user.getBirthday())); + return ps; + }, keyHolder); + int generatedId = Objects.requireNonNull(keyHolder.getKey()).intValue(); + user.setId(generatedId); return user; } @@ -76,8 +81,12 @@ public User updateUser(User user) { } @Override - public User deleteUserById(int userId) { - return null; + public void deleteUserById(int userId) { + if(checkUserInDb(userId)) { + jdbcTemplate.update("DELETE FROM users where userId = ?", userId); + } else { + throw new NotFoundObjectException("Пользователь с userId " + userId + " не был удален."); + } } @Override @@ -96,7 +105,7 @@ public void addFriend(int userId, int friendId) { jdbcTemplate.update("UPDATE friendship SET friendshipStatusId=? WHERE userId=? AND friendId=?", jdbcTemplate.queryForObject("SELECT friendshipStatusId FROM friendshipStatus WHERE description='apply'", Integer.class), friendId, userId); - }else{ + } else { jdbcTemplate.update("INSERT INTO friendship VALUES (?,?,?)", userId, friendId, jdbcTemplate.queryForObject("SELECT friendshipStatusId FROM friendshipStatus WHERE description='not apply'", Integer.class)); @@ -110,9 +119,11 @@ public User deleteFriend(int userId, int friendId) { List friends = new ArrayList<>(); SqlRowSet checkFriends = jdbcTemplate.queryForRowSet("SELECT friendId FROM friendship " + "WHERE userId=?", userId); + while(checkFriends.next()){ friends.add(checkFriends.getInt("friendId")); } + if(friends.contains(userId)){ jdbcTemplate.update("UPDATE friendship SET friendshipStatusId=? WHERE userId=? AND friendId=?", jdbcTemplate.queryForObject("SELECT friendshipStatusId FROM friendshipStatus WHERE description='not apply'", @@ -123,7 +134,7 @@ public User deleteFriend(int userId, int friendId) { @Override public List getFriends(int id) { - if(checkUserInDb(id)){ + if(checkUserInDb(id)) { List userFriends = new ArrayList<>(); SqlRowSet getFriends = jdbcTemplate.queryForRowSet("SELECT friendId FROM friendship WHERE userId=?", id); @@ -131,15 +142,14 @@ public List getFriends(int id) { userFriends.add(getUserById(getFriends.getInt("friendId"))); } return userFriends; - }else{ + } else { return null; } } - @Override public List getCommonFriends(int userId, int otherId) { - if(checkUserInDb(userId)){ + if(checkUserInDb(userId)) { List userFriends = new ArrayList<>(); SqlRowSet getFriends = jdbcTemplate.queryForRowSet("SELECT friendId FROM friendship WHERE userId=?", userId); @@ -147,23 +157,35 @@ public List getCommonFriends(int userId, int otherId) { userFriends.add(getUserById(getFriends.getInt("friendId"))); } return userFriends; - }else{ + } else { return null; } } - private boolean checkUserInDb(Integer id){ - String sql = "SELECT userId FROM users"; - SqlRowSet getUsersFromDb = jdbcTemplate.queryForRowSet(sql); - List ids = new ArrayList<>(); - while (getUsersFromDb.next()){ - ids.add(getUsersFromDb.getInt("userId")); - } - if(ids.contains(id)){ - return true; - }else{ - throw new NotFoundObjectException("Пользователя с таким id нет в базе!"); + @Override + public List getRecommendedFilms(int id) { + String sql = "SELECT FILMID, NAME, DESCRIPTION, RELEASE_DATE, DURATION, MPAID FROM FILMS as f JOIN " + + "(SELECT FILMID as recommended FROM LIKESLIST " + + "WHERE USERID = (SELECT USERID FROM LIKESLIST " + + " WHERE FILMID IN (SELECT FILMID as userfilmlist from LIKESLIST where USERID = " + id +") " + + " AND USERID <> " + id + + " GROUP BY USERID " + + " ORDER BY count(USERID) DESC " + + " LIMIT 1) " + + "AND FILMID NOT IN " + + " (SELECT FILMID as userfilmlist from LIKESLIST where USERID = " + id +")) as Lr " + + "ON f.FILMID = recommended"; + + return jdbcTemplate.query(sql, filmMapper); + } + + public boolean checkUserInDb(Integer id) { + String sql = "SELECT userId FROM users where USERID =?"; + SqlRowSet getUsersFromDb = jdbcTemplate.queryForRowSet(sql, id); + if (!getUsersFromDb.next()) { + throw new NotFoundObjectException("Пользователя с id" + id + " нет в базе!"); } + return true; } private User makeUser(ResultSet rs) throws SQLException { @@ -183,4 +205,4 @@ private User makeUser(ResultSet rs) throws SQLException { return null; } } -} +} \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/DirectorMapper.java b/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/DirectorMapper.java new file mode 100644 index 0000000..b163cdc --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/DirectorMapper.java @@ -0,0 +1,18 @@ +package ru.yandex.practicum.filmorate.dao.mappers; + +import org.springframework.jdbc.core.RowMapper; +import ru.yandex.practicum.filmorate.model.Director; + +import java.sql.ResultSet; +import java.sql.SQLException; + + +public class DirectorMapper implements RowMapper { + @Override + public Director mapRow(ResultSet rs, int rowNum) throws SQLException { + Director director = new Director(); + director.setId(rs.getInt("directorId")); + director.setName(rs.getString("directorName")); + return director; + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/EventMapper.java b/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/EventMapper.java new file mode 100644 index 0000000..d71a770 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/EventMapper.java @@ -0,0 +1,22 @@ +package ru.yandex.practicum.filmorate.dao.mappers; + +import org.springframework.jdbc.core.RowMapper; +import ru.yandex.practicum.filmorate.model.Event; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class EventMapper implements RowMapper { + @Override + public Event mapRow(ResultSet rs, int rowNum) throws SQLException { + return Event.builder(). + timestamp(rs.getLong("timestamp")). + userId(rs.getInt("userId")). + eventType(rs.getString("eventType")). + operation(rs.getString("operation")). + eventId(rs.getInt("eventId")). + entityId(rs.getLong("entityId")). + build(); + } +} + diff --git a/src/main/java/ru/yandex/practicum/filmorate/dao/FilmMapper.java b/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/FilmMapper.java similarity index 57% rename from src/main/java/ru/yandex/practicum/filmorate/dao/FilmMapper.java rename to src/main/java/ru/yandex/practicum/filmorate/dao/mappers/FilmMapper.java index dea5041..09efe49 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dao/FilmMapper.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/FilmMapper.java @@ -1,47 +1,44 @@ -package ru.yandex.practicum.filmorate.dao; +package ru.yandex.practicum.filmorate.dao.mappers; -import org.springframework.beans.factory.annotation.Qualifier; +import lombok.RequiredArgsConstructor; import org.springframework.dao.DataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.ResultSetExtractor; import org.springframework.jdbc.support.rowset.SqlRowSet; +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.FilmStorage; +import ru.yandex.practicum.filmorate.model.Mpa; +import ru.yandex.practicum.filmorate.service.MpaService; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; +import java.util.Map; +@Component +@RequiredArgsConstructor public class FilmMapper implements ResultSetExtractor> { private final JdbcTemplate jdbcTemplate; - private final FilmStorage filmStorage; + private final MpaService mpaService; - public FilmMapper(JdbcTemplate jdbcTemplate, @Qualifier("filmDBStorage") FilmStorage filmStorage) { - this.jdbcTemplate = jdbcTemplate; - this.filmStorage = filmStorage; - } public List extractData(ResultSet rs) throws SQLException, DataAccessException { List films = new ArrayList<>(); + Map mpas = mpaService.getMpasMap(); while (rs.next()) { Film film = new Film(rs.getString("name"), rs.getString("description"), rs.getDate("release_date").toLocalDate(), rs.getInt("duration")); film.setId(rs.getInt("filmId")); - film.setMpa(filmStorage.getMpaById(rs.getInt("mpaId"))); - SqlRowSet getFilmGenres = jdbcTemplate.queryForRowSet("SELECT genreId FROM film_genre WHERE filmId=?", film.getId()); - while(getFilmGenres.next()){ - Genre genre = filmStorage.getGenreById(getFilmGenres.getInt("genreId")); - film.addGenre(genre); - } + film.setMpa(mpas.get(rs.getInt("mpaId"))); + SqlRowSet getFilmLikes = jdbcTemplate.queryForRowSet("SELECT userId FROM likesList WHERE filmId = ?", film.getId()); while(getFilmLikes.next()){ - film.addLIke(getFilmLikes.getInt("userId")); + film.addLike(getFilmLikes.getInt("userId")); } films.add(film); } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dao/UserMapper.java b/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/UserMapper.java similarity index 75% rename from src/main/java/ru/yandex/practicum/filmorate/dao/UserMapper.java rename to src/main/java/ru/yandex/practicum/filmorate/dao/mappers/UserMapper.java index 0802f86..e44ede1 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dao/UserMapper.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/UserMapper.java @@ -1,27 +1,23 @@ -package ru.yandex.practicum.filmorate.dao; +package ru.yandex.practicum.filmorate.dao.mappers; -import org.springframework.beans.factory.annotation.Qualifier; +import lombok.RequiredArgsConstructor; import org.springframework.dao.DataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.ResultSetExtractor; import org.springframework.jdbc.support.rowset.SqlRowSet; +import org.springframework.stereotype.Component; import ru.yandex.practicum.filmorate.model.User; -import ru.yandex.practicum.filmorate.storage.UserStorage; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; +@Component +@RequiredArgsConstructor public class UserMapper implements ResultSetExtractor> { private final JdbcTemplate jdbcTemplate; - private final UserStorage userStorage; - - public UserMapper(JdbcTemplate jdbcTemplate, @Qualifier("userDBStorage") UserStorage userStorage) { - this.jdbcTemplate = jdbcTemplate; - this.userStorage = userStorage; - } public List extractData(ResultSet rs) throws SQLException, DataAccessException { List users = new ArrayList<>(); diff --git a/src/main/java/ru/yandex/practicum/filmorate/dao/review/ReviewDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dao/review/ReviewDbStorage.java new file mode 100644 index 0000000..73c69a5 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/review/ReviewDbStorage.java @@ -0,0 +1,127 @@ +package ru.yandex.practicum.filmorate.dao.review; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource; +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.jdbc.core.namedparam.SqlParameterSource; +import org.springframework.jdbc.support.GeneratedKeyHolder; +import org.springframework.jdbc.support.KeyHolder; +import org.springframework.stereotype.Repository; +import ru.yandex.practicum.filmorate.exceptions.NotFoundObjectException; +import ru.yandex.practicum.filmorate.model.Review; +import ru.yandex.practicum.filmorate.storage.review.ReviewStorage; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; +import java.util.stream.Collectors; + +@Repository("reviewDbStorage") +@RequiredArgsConstructor +public class ReviewDbStorage implements ReviewStorage { + private final NamedParameterJdbcTemplate namedParameterJdbcTemplate; + private final JdbcTemplate jdbcTemplate; + + @Override + public Review add(Review review) { + negativeUserOrFilmCheck(review); + String sql = "INSERT INTO review (content, isPositive, user_id, film_id, useful) " + + "VALUES (:content, :isPositive, :userId, :filmId, :useful)"; + SqlParameterSource parameterSource = getParameterSource(review); + KeyHolder keyHolder = new GeneratedKeyHolder(); + namedParameterJdbcTemplate.update(sql, parameterSource, keyHolder); + review.setReviewId((long) keyHolder.getKey()); + return review; + } + + @Override + public Review update(Review review) { + checkReviewById(review.getReviewId()); + String sql = "UPDATE review SET content = :content, isPositive = :isPositive WHERE review_id = :id"; + SqlParameterSource parameterSource = new MapSqlParameterSource() + .addValue("id", review.getReviewId()) + .addValue("content", review.getContent()) + .addValue("isPositive", review.getIsPositive()); + namedParameterJdbcTemplate.update(sql, parameterSource); + return getById(review.getReviewId()); + } + + @Override + public Review updateUseful(Review review) { + String sql = "UPDATE review SET useful = :useful WHERE review_id = :id"; + SqlParameterSource parameterSource = new MapSqlParameterSource() + .addValue("useful", review.getUseful()) + .addValue("id", review.getReviewId()); + namedParameterJdbcTemplate.update(sql, parameterSource); + return getById(review.getReviewId()); + } + + @Override + public void delete(long reviewId) { + checkReviewById(reviewId); + String sql = "DELETE FROM review WHERE review_id = :id"; + SqlParameterSource parameterSource = new MapSqlParameterSource().addValue("id", reviewId); + namedParameterJdbcTemplate.update(sql, parameterSource); + } + + @Override + public Review getById(long reviewId) { + if (reviewId <= 0) throw new NotFoundObjectException("Такого отзыва не существует."); + String sql = "SELECT * FROM review WHERE review_id = ?"; + return jdbcTemplate.queryForObject(sql, this::getRowMapper, reviewId); + } + + @Override + public List getAllReviewsByFilmId(long filmId, int limit) { + if (filmId <= 0) throw new NotFoundObjectException("Такого фильма не существует."); + String sql = "SELECT * FROM review WHERE film_id = :filmId ORDER BY useful DESC LIMIT :limit"; + SqlParameterSource parameterSource = new MapSqlParameterSource() + .addValue("filmId", filmId) + .addValue("limit", limit); + return namedParameterJdbcTemplate.queryForStream(sql, parameterSource, this::getRowMapper) + .collect(Collectors.toList()); + } + + @Override + public List getAllReviews() { + String sql = "SELECT * FROM review ORDER BY useful DESC"; + SqlParameterSource parameterSource = new BeanPropertySqlParameterSource(Review.class); + return namedParameterJdbcTemplate.queryForStream(sql, parameterSource, this::getRowMapper) + .collect(Collectors.toList()); + } + + private Review getRowMapper(ResultSet rs, int rowNum) throws SQLException { + return Review.builder() + .reviewId(rs.getLong("review_id")) + .content(rs.getString("content")) + .filmId(rs.getLong("film_id")) + .userId(rs.getLong("user_id")) + .isPositive(rs.getBoolean("isPositive")) + .useful(rs.getInt("useful")) + .build(); + } + + private MapSqlParameterSource getParameterSource(Review review) { + return new MapSqlParameterSource() + .addValue("id", review.getReviewId()) + .addValue("content", review.getContent()) + .addValue("isPositive", review.getIsPositive()) + .addValue("userId", review.getUserId()) + .addValue("filmId", review.getFilmId()) + .addValue("useful", review.getUseful()); + } + + private void checkReviewById(long reviewId) { + String sql = "SELECT review_id FROM review WHERE review_id = :id"; + SqlParameterSource parameterSource = new MapSqlParameterSource().addValue("id", reviewId); + namedParameterJdbcTemplate.queryForObject(sql, parameterSource, (rs, rowNum) -> rs.getLong("review_id")); + } + + private void negativeUserOrFilmCheck(Review review) throws NotFoundObjectException { + if (review.getFilmId() < 0 || review.getUserId() < 0) + throw new NotFoundObjectException("Такого пользовователя или фильма не существует."); + } +} \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/dao/review/ReviewLikesDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dao/review/ReviewLikesDbStorage.java new file mode 100644 index 0000000..3d93fdb --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/review/ReviewLikesDbStorage.java @@ -0,0 +1,102 @@ +package ru.yandex.practicum.filmorate.dao.review; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.jdbc.core.namedparam.SqlParameterSource; +import org.springframework.stereotype.Repository; +import ru.yandex.practicum.filmorate.model.Review; +import ru.yandex.practicum.filmorate.storage.review.ReviewLikesStorage; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Repository("reviewLikesDbStorage") +@RequiredArgsConstructor +@Slf4j +public class ReviewLikesDbStorage implements ReviewLikesStorage { + private final NamedParameterJdbcTemplate namedParameterJdbcTemplate; + private final JdbcTemplate jdbcTemplate; + + @Data + @AllArgsConstructor + private class ReviewLikesRow { + private long userId; + private boolean isLike; + } + + @Override + public void add(Review review) { + Map likes = review.getLikes(); + if (likes.size() > 0) { + String sql = getInsertSql(likes, review); + jdbcTemplate.update(sql); + log.info("Лайк для отзыва " + review.getReviewId() + " добавлен"); + } + } + + @Override + public void update(Review review) { + Map reviewLikes = review.getLikes(); + + if (reviewLikes.size() > 0) { + String sql = getInsertSql(reviewLikes, review); + delete(review.getReviewId()); + jdbcTemplate.update(sql); + log.info("Лайк для отзыва " + review.getReviewId() + " обновлен"); + } + } + + @Override + public void delete(long reviewId) { + String sql = "DELETE FROM review_likes WHERE review_id = :id"; + SqlParameterSource parameterSource = new MapSqlParameterSource().addValue("id", reviewId); + namedParameterJdbcTemplate.update(sql, parameterSource); + log.info("Лайк для отзыва " + reviewId + " удален"); + } + + @Override + public Map getReviewLikesById(long reviewId) { + String sql = "SELECT user_id, isLike FROM review_likes WHERE review_id = ?"; + List likesRow = jdbcTemplate.queryForStream(sql, (rs, rowNum) -> + new ReviewLikesRow(rs.getLong("user_id"), rs.getBoolean("isLike")), reviewId) + .collect(Collectors.toList()); + Map reviewLikes = new HashMap<>(); + if (likesRow.size() > 0) { + likesRow.forEach(reviewLikesRow -> + reviewLikes.put(reviewLikesRow.getUserId(), reviewLikesRow.isLike())); + } + log.info("Список лайков для отзыва " + reviewId + " отправлен"); + return reviewLikes; + } + + @Override + public Map getAllReviewLikes() { + String sql = "SELECT user_id, isLike FROM review_likes"; + List likesRow = jdbcTemplate.queryForStream(sql, (rs, rowNum) -> + new ReviewLikesRow(rs.getLong("user_id"), rs.getBoolean("isLike"))) + .collect(Collectors.toList()); + Map reviewLikes = new HashMap<>(); + if (likesRow.size() > 0) { + likesRow.forEach(reviewLikesRow -> + reviewLikes.put(reviewLikesRow.getUserId(), reviewLikesRow.isLike())); + } + log.info("Список всех лайков отправлен"); + return reviewLikes; + } + + private String getInsertSql(Map reviewLikes, Review review) { + int commaAndSpace = 2; + long reviewId = review.getReviewId(); + StringBuilder sql = new StringBuilder("INSERT INTO review_likes (user_id, review_id, isLike) VALUES "); + reviewLikes.forEach((userId, isLike) -> sql.append(String.format("(%d, %d, %b), ", userId, reviewId, isLike))); + sql.setLength(sql.length() - commaAndSpace); + return sql.toString(); + } +} \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/exceptions/ErrorHandler.java b/src/main/java/ru/yandex/practicum/filmorate/exceptions/ErrorHandler.java index 5efc77c..74a71f1 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/exceptions/ErrorHandler.java +++ b/src/main/java/ru/yandex/practicum/filmorate/exceptions/ErrorHandler.java @@ -1,41 +1,51 @@ package ru.yandex.practicum.filmorate.exceptions; +import lombok.extern.slf4j.Slf4j; +import org.springframework.dao.DataAccessException; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice; import ru.yandex.practicum.filmorate.model.ErrorResponse; +import javax.validation.ValidationException; + @RestControllerAdvice +@Slf4j public class ErrorHandler { @ExceptionHandler @ResponseStatus(HttpStatus.BAD_REQUEST) public ErrorResponse handleValidationException(final ValidationException e) { + log.error(e.getMessage()); return new ErrorResponse(e.getMessage()); } - @ExceptionHandler + @ExceptionHandler({NotFoundObjectException.class, DataAccessException.class}) @ResponseStatus(HttpStatus.NOT_FOUND) - public ErrorResponse handleNotFoundUserException(final NotFoundObjectException e) { + public ErrorResponse handleNotFoundUserException(final RuntimeException e) { + log.error(e.getMessage()); return new ErrorResponse(e.getMessage()); } @ExceptionHandler @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public ErrorResponse handleThrowable(final Throwable e) { + log.error(e.getMessage()); return new ErrorResponse(e.getMessage()); } @ExceptionHandler @ResponseStatus(HttpStatus.NOT_FOUND) public ErrorResponse handleMpaNotFoundException(final MpaNotFoundException e){ + log.error(e.getMessage()); return new ErrorResponse(String.format("Возникла ошибка поиска рейтинга: \"%s\".", e.getMessage())); } @ExceptionHandler @ResponseStatus(HttpStatus.NOT_FOUND) public ErrorResponse handleGenreNotFoundException(final GenreNotFoundException e){ + log.error(e.getMessage()); return new ErrorResponse(String.format("Возникла ошибка поиска жанра: \"%s\".", e.getMessage())); } diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/Director.java b/src/main/java/ru/yandex/practicum/filmorate/model/Director.java new file mode 100644 index 0000000..23953ec --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/model/Director.java @@ -0,0 +1,12 @@ +package ru.yandex.practicum.filmorate.model; + +import lombok.Data; + +import java.util.Set; + +@Data +public class Director { + private Integer id; + private String name; + private Set films; +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/Event.java b/src/main/java/ru/yandex/practicum/filmorate/model/Event.java new file mode 100644 index 0000000..d97a067 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/model/Event.java @@ -0,0 +1,28 @@ +package ru.yandex.practicum.filmorate.model; + +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +import java.sql.Timestamp; + +@Getter +@Setter +@Builder +public class Event { + private long timestamp; + private int userId; + private String eventType; + private String operation; + private int eventId; + private long entityId; + + public Event(long timestamp, int userId, String eventType, String operation, int eventId, long entityId) { + this.timestamp = timestamp; + this.userId = userId; + this.eventType = eventType; + this.operation = operation; + this.eventId = eventId; + this.entityId = entityId; + } +} \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/Film.java b/src/main/java/ru/yandex/practicum/filmorate/model/Film.java index e2fa9ea..958d5f7 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/model/Film.java +++ b/src/main/java/ru/yandex/practicum/filmorate/model/Film.java @@ -3,9 +3,11 @@ import lombok.Data; import ru.yandex.practicum.filmorate.exceptions.NotFoundObjectException; -import javax.validation.constraints.*; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Positive; +import javax.validation.constraints.Size; import java.time.LocalDate; -import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Set; @@ -25,6 +27,7 @@ public class Film { private Set likes = new LinkedHashSet<>(); private Set genres = new LinkedHashSet<>(); private Mpa mpa; + private Set directors= new LinkedHashSet<>(); public Film(String name, String description, LocalDate releaseDate, int duration) { this.name = name; @@ -33,16 +36,16 @@ public Film(String name, String description, LocalDate releaseDate, int duration this.duration = duration; } - public void addLIke(Integer userId){ + public void addLike(Integer userId){ likes.add(userId); } - public void removeLike(Integer userId){ + public void removeLike(Integer userId) { if (likes.contains(userId)){ likes.remove(userId); - }else{ - throw new NotFoundObjectException("Лайк от пользователя "+userId+" этому фильму и так не был поставлен, " + - "удалять нечего"); + } else { + throw new NotFoundObjectException("Лайк от пользователя " + userId + + " этому фильму и так не был поставлен, удалять нечего"); } } @@ -50,4 +53,7 @@ public void addGenre(Genre genre){ genres.add(genre); } -} + public Integer getLikesCount(){ + return likes.size(); + } +} \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/Mpa.java b/src/main/java/ru/yandex/practicum/filmorate/model/Mpa.java index bdb7ddd..fc27cdd 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/model/Mpa.java +++ b/src/main/java/ru/yandex/practicum/filmorate/model/Mpa.java @@ -7,7 +7,6 @@ public class Mpa { private Integer id; private String name; - public Mpa(Integer id, String name) { this.id = id; this.name = name; diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/Review.java b/src/main/java/ru/yandex/practicum/filmorate/model/Review.java new file mode 100644 index 0000000..8501d65 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/model/Review.java @@ -0,0 +1,24 @@ +package ru.yandex.practicum.filmorate.model; + +import lombok.Builder; +import lombok.Data; + +import javax.validation.constraints.NotNull; +import java.util.HashMap; +import java.util.Map; + +@Data +@Builder(toBuilder = true) +public class Review { + private long reviewId; + @NotNull(message = "Пустое описание рецензии") + private String content; + @NotNull + private Boolean isPositive; + @NotNull + private Long userId; + @NotNull + private Long filmId; + private int useful; + private final Map likes = new HashMap<>(); // User ID - true/false for like/dislike +} \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/User.java b/src/main/java/ru/yandex/practicum/filmorate/model/User.java index 16a3457..c3f19dc 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/model/User.java +++ b/src/main/java/ru/yandex/practicum/filmorate/model/User.java @@ -1,10 +1,10 @@ package ru.yandex.practicum.filmorate.model; import lombok.Data; -import ru.yandex.practicum.filmorate.exceptions.NotFoundObjectException; import javax.validation.constraints.*; import java.time.LocalDate; +import java.util.HashSet; import java.util.Set; import java.util.TreeSet; @@ -34,5 +34,4 @@ public User(String email, String login, String name, LocalDate birthday) { public void addFriendId(int userId){ friendIds.add(userId); } - } diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/enums/EventTypes.java b/src/main/java/ru/yandex/practicum/filmorate/model/enums/EventTypes.java new file mode 100644 index 0000000..2a39dad --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/model/enums/EventTypes.java @@ -0,0 +1,7 @@ +package ru.yandex.practicum.filmorate.model.enums; + +public enum EventTypes { + LIKE, + REVIEW, + FRIEND +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/enums/OperationTypes.java b/src/main/java/ru/yandex/practicum/filmorate/model/enums/OperationTypes.java new file mode 100644 index 0000000..44d5839 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/model/enums/OperationTypes.java @@ -0,0 +1,7 @@ +package ru.yandex.practicum.filmorate.model.enums; + +public enum OperationTypes { + REMOVE, + ADD, + UPDATE +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/DirectorService.java b/src/main/java/ru/yandex/practicum/filmorate/service/DirectorService.java new file mode 100644 index 0000000..324abf4 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/service/DirectorService.java @@ -0,0 +1,15 @@ +package ru.yandex.practicum.filmorate.service; + +import ru.yandex.practicum.filmorate.model.Director; +import ru.yandex.practicum.filmorate.model.Film; + +import java.util.List; + +public interface DirectorService { + Director create (Director director); + Director update (Director director); + Director getById (Integer id); + Director delete (Integer id); + List getAll (); + List findFilmsByDirectorId (Integer directorId); +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/DirectorServiceManager.java b/src/main/java/ru/yandex/practicum/filmorate/service/DirectorServiceManager.java new file mode 100644 index 0000000..6ab48a5 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/service/DirectorServiceManager.java @@ -0,0 +1,45 @@ +package ru.yandex.practicum.filmorate.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import ru.yandex.practicum.filmorate.model.Director; +import ru.yandex.practicum.filmorate.model.Film; +import ru.yandex.practicum.filmorate.storage.DirectorStorage; + +import java.util.List; + +@Component +@RequiredArgsConstructor +public class DirectorServiceManager implements DirectorService { + private final DirectorStorage storage; + + @Override + public Director create(Director director) { + return storage.create(director); + } + + @Override + public Director update(Director director) { + return storage.update(director); + } + + @Override + public Director getById(Integer id) { + return storage.findById(id); + } + + @Override + public Director delete(Integer id) { + return storage.delete(id); + } + + @Override + public List getAll() { + return storage.findAll(); + } + + @Override + public List findFilmsByDirectorId(Integer directorId) { + return storage.findFilmsByDirectorId(directorId); + } +} 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 b7494c7..cc361f3 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/service/FilmService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/FilmService.java @@ -1,10 +1,7 @@ package ru.yandex.practicum.filmorate.service; import ru.yandex.practicum.filmorate.model.Film; -import ru.yandex.practicum.filmorate.model.Genre; -import ru.yandex.practicum.filmorate.model.Mpa; -import java.util.Collection; import java.util.List; public interface FilmService { @@ -17,9 +14,7 @@ public interface FilmService { Film likeFilm(int filmId, int userId); Film deleteLikeFromFilm(int filmId, int userId); List getPopularFilms(int count); - List getAllGenres(); - Genre getGenreById(int id); - List getAllMpa(); - Mpa getMpaById(int id); - + List searchFilms(String query, List by); + List getCommonFilms(int userId, int friendId); + List getFilmsSortedByLikesOrYear(Integer directorId, String param); } diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/FilmServiceManager.java b/src/main/java/ru/yandex/practicum/filmorate/service/FilmServiceManager.java index 9b62265..47be078 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/service/FilmServiceManager.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/FilmServiceManager.java @@ -1,51 +1,71 @@ package ru.yandex.practicum.filmorate.service; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; -import ru.yandex.practicum.filmorate.exceptions.NotFoundObjectException; +import ru.yandex.practicum.filmorate.dao.EventDbStorage; import ru.yandex.practicum.filmorate.model.Film; import ru.yandex.practicum.filmorate.model.Genre; -import ru.yandex.practicum.filmorate.model.Mpa; -import ru.yandex.practicum.filmorate.model.User; +import ru.yandex.practicum.filmorate.model.enums.EventTypes; +import ru.yandex.practicum.filmorate.model.enums.OperationTypes; import ru.yandex.practicum.filmorate.storage.FilmStorage; -import ru.yandex.practicum.filmorate.storage.UserStorage; import java.util.*; import java.util.stream.Collectors; @Service +@RequiredArgsConstructor public class FilmServiceManager implements FilmService { private final FilmStorage filmStorage; - private final UserStorage userStorage; + private final UserService userService; + private final EventDbStorage eventDbStorage; + private final GenreService genreService; + private final DirectorService directorService; - @Autowired - public FilmServiceManager(@Qualifier("filmDBStorage") FilmStorage filmStorage, UserStorage userStorage) { - this.filmStorage = filmStorage; - this.userStorage = userStorage; + @Override + public List getCommonFilms(int userId, int friendId) { + List resultList = filmStorage.getCommonFilms(userId, friendId); + resultList.sort(Comparator.comparingInt(Film::getLikesCount).reversed()); + return setGenresOfFilmList(resultList); } @Override public List getAllFilms() { - return filmStorage.getAllFilms(); + List films = filmStorage.getAllFilms(); + return setGenresOfFilmList(films); + } + + private List setGenresOfFilmList(List films) { + Map> allGenresOfAllFilms = genreService.getAllGenresOfAllFilms(); + for (Film film : films) { + film.setGenres(allGenresOfAllFilms.get(film.getId())); + if (film.getGenres() == null) { + film.setGenres(new HashSet<>()); + } + } + return films; } @Override - public Film getFilmById(int id) { - return filmStorage.getFilmById(id); + public Film getFilmById(int filmId) { + Film film = filmStorage.getFilmById(filmId); + film.setGenres(genreService.getGenresByFilmId(filmId)); + + return film; } @Override public Film createFilm(Film film) { - return filmStorage.createFilm(film); + filmStorage.createFilm(film); + film.setGenres(genreService.getGenresByFilmId(film.getId())); + return film; } @Override public Film updateFilm(Film film) { - return filmStorage.updateFilm(film); + filmStorage.updateFilm(film); + film.setGenres(genreService.getGenresByFilmId(film.getId())); + return film; } @Override @@ -55,37 +75,46 @@ public void deleteFilmById(int id) { @Override public Film likeFilm(int filmId, int userId) { - return filmStorage.likeFilm(filmId, userId); + userService.checkUserInDb(userId); + Film film = filmStorage.likeFilm(filmId, userId); + eventDbStorage.saveEvent(userId, EventTypes.LIKE, OperationTypes.ADD, filmId); + return film; } @Override public Film deleteLikeFromFilm(int filmId, int userId) { - return filmStorage.deleteLikeFromFilm(filmId, userId); + userService.checkUserInDb(userId); + Film film = filmStorage.deleteLikeFromFilm(filmId, userId); + film.setGenres(genreService.getGenresByFilmId(filmId)); + eventDbStorage.saveEvent(userId, EventTypes.LIKE, OperationTypes.REMOVE, filmId); + return film; } @Override public List getPopularFilms(int count) { - return filmStorage.getPopularFilms(count); + return setGenresOfFilmList(filmStorage.getPopularFilms(count)); } @Override - public List getAllGenres() { - return filmStorage.getAllGenres(); + public List searchFilms(String query, List by) { + List resultList = filmStorage.searchFilms(query, by); + resultList.sort(Comparator.comparingInt(Film::getLikesCount).reversed()); + return setGenresOfFilmList(resultList); } @Override - public Genre getGenreById(int id) { - return filmStorage.getGenreById(id); + public List getFilmsSortedByLikesOrYear(Integer directorId, String param) { + List filmsByDirectorId = directorService.findFilmsByDirectorId(directorId); + setGenresOfFilmList(filmsByDirectorId); + + if ("year".equalsIgnoreCase(param)) { + return filmsByDirectorId + .stream().sorted(Comparator.comparing(Film::getReleaseDate)).collect(Collectors.toList()); + } + else if ("likes".equalsIgnoreCase(param)) { + return filmsByDirectorId.stream().sorted(Comparator.comparing(film -> film.getLikes().size())).collect(Collectors.toList()); + } + + return filmsByDirectorId; } - - @Override - public List getAllMpa() { - return filmStorage.getAllMpa(); - } - - @Override - public Mpa getMpaById(int id) { - return filmStorage.getMpaById(id); - } - } \ No newline at end of file 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..754a6b3 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/service/GenreService.java @@ -0,0 +1,19 @@ +package ru.yandex.practicum.filmorate.service; + +import ru.yandex.practicum.filmorate.model.Genre; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +public interface GenreService { + List getAllGenres(); + + Genre getGenreById(int id); + + Map getGenresMap(); + + Set getGenresByFilmId(int filmId); + Map> getAllGenresOfAllFilms(); + +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/GenreServiceManager.java b/src/main/java/ru/yandex/practicum/filmorate/service/GenreServiceManager.java new file mode 100644 index 0000000..e6e6db0 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/service/GenreServiceManager.java @@ -0,0 +1,42 @@ +package ru.yandex.practicum.filmorate.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import ru.yandex.practicum.filmorate.model.Genre; +import ru.yandex.practicum.filmorate.storage.GenreStorage; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +@Component +@RequiredArgsConstructor +public class GenreServiceManager implements GenreService { + + private final GenreStorage genreStorage; + + @Override + public List getAllGenres() { + return genreStorage.getAllGenres(); + } + + @Override + public Genre getGenreById(int id) { + return genreStorage.getGenreById(id); + } + + @Override + public Map getGenresMap() { + return genreStorage.getGenresMap(); + } + + @Override + public Set getGenresByFilmId(int filmId) { + return genreStorage.getGenresByFilmId(filmId); + } + + @Override + public Map> getAllGenresOfAllFilms() { + return genreStorage.getAllGenresOfAllFilms(); + } +} 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..90a6b5a --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/service/MpaService.java @@ -0,0 +1,14 @@ +package ru.yandex.practicum.filmorate.service; + +import ru.yandex.practicum.filmorate.model.Mpa; + +import java.util.List; +import java.util.Map; + +public interface MpaService { + List getAllMpa(); + + Mpa getMpaById(int id); + + Map getMpasMap(); +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/MpaServiceManager.java b/src/main/java/ru/yandex/practicum/filmorate/service/MpaServiceManager.java new file mode 100644 index 0000000..ee33972 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/service/MpaServiceManager.java @@ -0,0 +1,30 @@ +package ru.yandex.practicum.filmorate.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import ru.yandex.practicum.filmorate.model.Mpa; +import ru.yandex.practicum.filmorate.storage.MpaStorage; + +import java.util.List; +import java.util.Map; + +@Component +@RequiredArgsConstructor +public class MpaServiceManager implements MpaService { + private final MpaStorage mpaStorage; + + @Override + public List getAllMpa() { + return mpaStorage.getAllMpa(); + } + + @Override + public Mpa getMpaById(int id) { + return mpaStorage.getMpaById(id); + } + + @Override + public Map getMpasMap() { + return mpaStorage.getMpasMap(); + } +} 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 0eab755..1619992 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/service/UserService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/UserService.java @@ -1,19 +1,23 @@ package ru.yandex.practicum.filmorate.service; +import ru.yandex.practicum.filmorate.model.Event; +import ru.yandex.practicum.filmorate.model.Film; import ru.yandex.practicum.filmorate.model.User; import java.util.Collection; import java.util.List; public interface UserService { - Collection getAll(); User getById(int id); User createUser(User user); User updateUser(User user); - User deleteUserById(int id); + void deleteUserById(int id); void addFriend(int userId, int friendId); User deleteFriend(int userId, int friendId); List getFriends(int id); List getCommonFriends(int userId, int otherId); -} + List getRecommendedFilms(int id); + List getEvents(int id); + boolean checkUserInDb(Integer userId); +} \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/UserServiceManager.java b/src/main/java/ru/yandex/practicum/filmorate/service/UserServiceManager.java index dfe6948..dc112e4 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/service/UserServiceManager.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/UserServiceManager.java @@ -1,24 +1,28 @@ package ru.yandex.practicum.filmorate.service; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import ru.yandex.practicum.filmorate.dao.EventDbStorage; import ru.yandex.practicum.filmorate.exceptions.ValidationException; +import ru.yandex.practicum.filmorate.model.Event; +import ru.yandex.practicum.filmorate.model.Film; +import ru.yandex.practicum.filmorate.model.Genre; import ru.yandex.practicum.filmorate.model.User; +import ru.yandex.practicum.filmorate.model.enums.EventTypes; +import ru.yandex.practicum.filmorate.model.enums.OperationTypes; import ru.yandex.practicum.filmorate.storage.UserStorage; -import java.util.ArrayList; -import java.util.List; +import java.util.*; @Service +@Slf4j +@RequiredArgsConstructor public class UserServiceManager implements UserService{ private final UserStorage userStorage; - - @Autowired - public UserServiceManager(@Qualifier("userDBStorage") UserStorage userStorage) { - this.userStorage = userStorage; - } + private final EventDbStorage eventDbStorage; + private final GenreService genreService; @Override public List getAll() { @@ -32,6 +36,10 @@ public User getById(int id) { @Override public User createUser(User user) { + if ((user.getName()==null)||(user.getName().isBlank())) { + log.info("Поле \"Имя\" пустое, ему будет присвоено значение поля \"Логин\""); + user.setName(user.getLogin()); + } return userStorage.createUser(user); } @@ -41,18 +49,21 @@ public User updateUser(User user) { } @Override - public User deleteUserById(int id) { - return userStorage.deleteUserById(id); + public void deleteUserById(int id) { + userStorage.deleteUserById(id); } @Override public void addFriend(int userId, int friendId) { userStorage.addFriend(userId, friendId); + eventDbStorage.saveEvent(userId, EventTypes.FRIEND, OperationTypes.ADD, friendId); } @Override public User deleteFriend(int userId, int friendId) throws ValidationException { - return userStorage.deleteFriend(userId, friendId); + User user = userStorage.deleteFriend(userId, friendId); + eventDbStorage.saveEvent(userId, EventTypes.FRIEND, OperationTypes.REMOVE, friendId); + return user; } @Override @@ -71,7 +82,28 @@ public List getCommonFriends(int userId, int otherId) { } } return commonFriends; + } + + @Override + public List getRecommendedFilms(int id) { + List recommendedFilms = userStorage.getRecommendedFilms(id); + Map> allGenres = genreService.getAllGenresOfAllFilms(); + for (Film film : recommendedFilms) { + film.setGenres(allGenres.get(film.getId())); + if (film.getGenres() == null) { + film.setGenres(new HashSet<>()); + } + } + return recommendedFilms; + } + @Override + public List getEvents(int id) { + return eventDbStorage.getEvent(id); + } + @Override + public boolean checkUserInDb(Integer userId) { + return userStorage.checkUserInDb(userId); } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/review/ReviewService.java b/src/main/java/ru/yandex/practicum/filmorate/service/review/ReviewService.java new file mode 100644 index 0000000..39e1b08 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/service/review/ReviewService.java @@ -0,0 +1,18 @@ +package ru.yandex.practicum.filmorate.service.review; + +import ru.yandex.practicum.filmorate.model.Review; + +import java.util.List; + +public interface ReviewService { + Review add(Review review); + Review update(Review review); + void delete(long reviewId); + Review getById(long reviewId); + List getAllReviewsByFilmId(long filmId, int limit); + List getAllReviews(); + Review addLike(long reviewId, long userId); + Review deleteLike(long reviewId, long userId); + Review addDislike(long reviewId, long userId); + Review deleteDislike(long reviewId, long userId); +} \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/review/ReviewServiceImpl.java b/src/main/java/ru/yandex/practicum/filmorate/service/review/ReviewServiceImpl.java new file mode 100644 index 0000000..c4982a7 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/service/review/ReviewServiceImpl.java @@ -0,0 +1,150 @@ +package ru.yandex.practicum.filmorate.service.review; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import ru.yandex.practicum.filmorate.dao.EventDbStorage; +import ru.yandex.practicum.filmorate.model.Review; +import ru.yandex.practicum.filmorate.model.enums.EventTypes; +import ru.yandex.practicum.filmorate.model.enums.OperationTypes; +import ru.yandex.practicum.filmorate.storage.review.ReviewLikesStorage; +import ru.yandex.practicum.filmorate.storage.review.ReviewStorage; + +import javax.validation.ValidationException; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +@Service +@RequiredArgsConstructor +public class ReviewServiceImpl implements ReviewService { + private final ReviewStorage reviewStorage; + private final ReviewLikesStorage reviewLikesStorage; + private final EventDbStorage eventDbStorage; + + @Override + public Review add(Review review) { + review = reviewStorage.add(review); + reviewLikesStorage.add(review); + eventDbStorage.saveEvent(review.getUserId(), EventTypes.REVIEW, + OperationTypes.ADD, review.getReviewId()); + return review; + } + + @Override + public Review update(Review review) { + review = reviewStorage.update(review); + eventDbStorage.saveEvent(review.getUserId(), EventTypes.REVIEW, + OperationTypes.UPDATE, review.getReviewId()); + return review; + } + + @Override + public void delete(long reviewId) { + reviewStorage.delete(reviewId); + reviewLikesStorage.delete(reviewId); + eventDbStorage.saveEvent(Math.toIntExact(reviewId), EventTypes.REVIEW, + OperationTypes.REMOVE, Math.toIntExact(reviewId)); + } + + @Override + public Review getById(long reviewId) { + Review review = reviewStorage.getById(reviewId); + review.getLikes().putAll(reviewLikesStorage.getReviewLikesById(review.getReviewId())); + return review; + } + + @Override + public List getAllReviewsByFilmId(long filmId, int limit) { + List reviews = reviewStorage.getAllReviewsByFilmId(filmId, limit); + return getReviewWithLikes(reviews); + } + + @Override + public List getAllReviews() { + List reviews = reviewStorage.getAllReviews(); + return getReviewWithLikes(reviews); + } + + @Override + public Review addLike(long reviewId, long userId) { + Review review = reviewStorage.getById(reviewId); + Map reviewLikes = review.getLikes(); + + if (reviewLikes.containsKey(userId) && reviewLikes.get(userId).equals(true)) { + throw new ValidationException("Этот пользователь уже поставил лайк."); + } + reviewLikes.put(userId, true); + updateReviewLikes(review, reviewLikes); + return review; + } + + @Override + public Review deleteLike(long reviewId, long userId) { + Review review = reviewStorage.getById(reviewId); + Map reviewLikes = review.getLikes(); + + if (!reviewLikes.containsKey(userId) && reviewLikes.get(userId).equals(false)) { + throw new ValidationException("Этот пользователь не ставил лайк."); + } + reviewLikes.remove(userId); + updateReviewLikes(review, reviewLikes); + return review; + } + + @Override + public Review addDislike(long reviewId, long userId) { + Review review = reviewStorage.getById(reviewId); + Map reviewLikes = review.getLikes(); + + if (reviewLikes.containsKey(userId) && reviewLikes.get(userId).equals(false)) { + throw new ValidationException("Этот пользователь уже поставил дизлайк."); + } + reviewLikes.put(userId, false); + updateReviewLikes(review, reviewLikes); + return review; + } + + @Override + public Review deleteDislike(long reviewId, long userId) { + Review review = reviewStorage.getById(reviewId); + Map reviewLikes = review.getLikes(); + + if (!reviewLikes.containsKey(userId) && reviewLikes.get(userId).equals(true)) { + throw new ValidationException("Этот пользователь уже поставил дизлайк."); + } + reviewLikes.remove(userId); + updateReviewLikes(review, reviewLikes); + return review; + } + + private void setNewUseful(Review review) { + int newUseful; + Map reviewLikes = review.getLikes(); + AtomicInteger likes = new AtomicInteger(); + reviewLikes.values().forEach(like -> { + if (like) likes.getAndIncrement(); + if (!like) likes.decrementAndGet(); + }); + newUseful = likes.intValue(); + review.setUseful(newUseful); + } + + private void updateReviewLikes(Review review, Map reviewLikes) { + review.getLikes().putAll(reviewLikes); + setNewUseful(review); + review = reviewStorage.updateUseful(review); + reviewLikesStorage.update(review); + } + + private List getReviewWithLikes(List reviews) { + if (reviews.size() > 0) { + Map reviewsLikes = reviewLikesStorage.getAllReviewLikes(); + if (reviewsLikes.size() > 0) { + reviews.forEach(review -> reviewsLikes.forEach((userId, like) -> { + if (review.getUserId().equals(userId)) review.getLikes().put(userId, like); + })); + } + } + return reviews; + } +} \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/DirectorStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/DirectorStorage.java new file mode 100644 index 0000000..c067acf --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/DirectorStorage.java @@ -0,0 +1,15 @@ +package ru.yandex.practicum.filmorate.storage; + +import ru.yandex.practicum.filmorate.model.Director; +import ru.yandex.practicum.filmorate.model.Film; + +import java.util.List; + +public interface DirectorStorage { + List findAll (); + Director create (Director director); + Director update (Director director); + Director findById (Integer id); + Director delete (Integer id); + List findFilmsByDirectorId (Integer directorId); +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/FilmStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/FilmStorage.java index fbd1da0..f52f2a9 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/storage/FilmStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/FilmStorage.java @@ -1,14 +1,10 @@ package ru.yandex.practicum.filmorate.storage; import ru.yandex.practicum.filmorate.model.Film; -import ru.yandex.practicum.filmorate.model.Genre; -import ru.yandex.practicum.filmorate.model.Mpa; -import java.util.Collection; import java.util.List; public interface FilmStorage { - List getAllFilms(); Film getFilmById(int filmId); Film createFilm(Film film); @@ -17,9 +13,6 @@ public interface FilmStorage { Film likeFilm(int filmId, int userId); Film deleteLikeFromFilm(int filmId, int userId); List getPopularFilms(int count); - List getAllGenres(); - Genre getGenreById(int id); - List getAllMpa(); - Mpa getMpaById(int id); - + List getCommonFilms(int userId, int friendId); + List searchFilms(String query, List by); } diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/GenreStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/GenreStorage.java new file mode 100644 index 0000000..1c8bca8 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/GenreStorage.java @@ -0,0 +1,18 @@ +package ru.yandex.practicum.filmorate.storage; + +import ru.yandex.practicum.filmorate.model.Genre; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +public interface GenreStorage { + List getAllGenres(); + + Genre getGenreById(int id); + + Set getGenresByFilmId(int filmId); + + Map getGenresMap(); + Map> getAllGenresOfAllFilms(); +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryFilmStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryFilmStorage.java deleted file mode 100644 index 262695d..0000000 --- a/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryFilmStorage.java +++ /dev/null @@ -1,103 +0,0 @@ -package ru.yandex.practicum.filmorate.storage; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; -import ru.yandex.practicum.filmorate.exceptions.NotFoundObjectException; -import ru.yandex.practicum.filmorate.exceptions.ValidationException; -import ru.yandex.practicum.filmorate.model.Film; -import ru.yandex.practicum.filmorate.model.Genre; -import ru.yandex.practicum.filmorate.model.Mpa; -import ru.yandex.practicum.filmorate.validators.FilmValidator; - -import java.time.LocalDate; -import java.util.*; - - -@Slf4j -public class InMemoryFilmStorage implements FilmStorage { - - private final Map films = new HashMap<>(); - private int filmId = 0; - - private int generateFilmId() { - return ++filmId; - } - - @Override - public List getAllFilms() { - return new ArrayList<>(films.values()); - } - - @Override - public Film getFilmById(int filmId) { - if (films.containsKey(filmId)) { - return films.get(filmId); - } else { - throw new NotFoundObjectException("Film with this " + filmId + " ID not found."); - } - } - - @Override - public Film createFilm(Film film) { - if (FilmValidator.valid(film)) { - film.setId(generateFilmId()); - films.put(film.getId(), film); - log.info("Film" + film.getId() + " created"); - } else { - throw new ValidationException("Film with this " + film.getId() + " ID already created"); - } - return film; - } - - @Override - public Film updateFilm(Film film) { - if (!films.containsKey(film.getId())) { - throw new NotFoundObjectException("Not found this film"); - } else { - films.remove(film.getId()); - films.put(film.getId(), film); - } - return film; - } - - @Override - public void deleteFilmById(int filmId) { - films.remove(filmId); - } - - @Override - public Film likeFilm(int filmId, int userId) { - return null; - } - - @Override - public Film deleteLikeFromFilm(int filmId, int userId) { - return null; - } - - @Override - public List getPopularFilms(int count) { - return null; - } - - @Override - public List getAllGenres() { - return null; - } - - @Override - public Genre getGenreById(int id) { - return null; - } - - @Override - public List getAllMpa() { - return null; - } - - @Override - public Mpa getMpaById(int id) { - return null; - } - -} diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryUserStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryUserStorage.java deleted file mode 100644 index 56eec0d..0000000 --- a/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryUserStorage.java +++ /dev/null @@ -1,79 +0,0 @@ -package ru.yandex.practicum.filmorate.storage; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; -import ru.yandex.practicum.filmorate.exceptions.NotFoundObjectException; -import ru.yandex.practicum.filmorate.model.User; -import ru.yandex.practicum.filmorate.validators.UserValidator; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; - -@Slf4j -public class InMemoryUserStorage implements UserStorage{ - - private final HashMap users = new HashMap<>(); - private int userId = 0; - - private int generateUserId() { - return ++userId; - } - - @Override - public List getAllUsers() { - return new ArrayList<>(users.values()); - } - - @Override - public User getUserById(int userId) { - return users.get(userId); - } - - @Override - public User createUser(User user) { - UserValidator.checkName(user); - user.setId(generateUserId()); - users.put(user.getId(), user); - return user; - } - - @Override - public User updateUser(User user) { - if (users.containsKey(user.getId())) { - users.remove(user.getId()); - users.put(user.getId(), user); - } else { - throw new NotFoundObjectException("Невозможно обновить данные. Такого пользователя не существует."); - } - return user; - } - - @Override - public User deleteUserById(int userId) { - User user = users.get(userId); - users.remove(userId); - return user; - } - - @Override - public void addFriend(int userId, int friendId) { - - } - - @Override - public User deleteFriend(int userId, int friendId) { - return null; - } - - @Override - public List getFriends(int id) { - return null; - } - - @Override - public List getCommonFriends(int userId, int otherId) { - return null; - } - -} diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/MpaStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/MpaStorage.java new file mode 100644 index 0000000..a642d7e --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/MpaStorage.java @@ -0,0 +1,14 @@ +package ru.yandex.practicum.filmorate.storage; + +import ru.yandex.practicum.filmorate.model.Mpa; + +import java.util.List; +import java.util.Map; + +public interface MpaStorage { + List getAllMpa(); + + Mpa getMpaById(int id); + + Map getMpasMap(); +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/UserStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/UserStorage.java index 8f19b0a..e61ea04 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/storage/UserStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/UserStorage.java @@ -1,5 +1,6 @@ package ru.yandex.practicum.filmorate.storage; +import ru.yandex.practicum.filmorate.model.Film; import ru.yandex.practicum.filmorate.model.User; import java.util.List; @@ -10,9 +11,11 @@ public interface UserStorage { User getUserById(int userId); User createUser(User user); User updateUser(User user); - User deleteUserById(int userId); + void deleteUserById(int userId); void addFriend(int userId, int friendId); User deleteFriend(int userId, int friendId); List getFriends(int id); List getCommonFriends(int userId, int otherId); + List getRecommendedFilms(int id); + boolean checkUserInDb(Integer userId); } diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/review/ReviewLikesStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/review/ReviewLikesStorage.java new file mode 100644 index 0000000..1e2781c --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/review/ReviewLikesStorage.java @@ -0,0 +1,13 @@ +package ru.yandex.practicum.filmorate.storage.review; + +import ru.yandex.practicum.filmorate.model.Review; + +import java.util.Map; + +public interface ReviewLikesStorage { + void add(Review review); + void update(Review review); + void delete(long reviewId); + Map getReviewLikesById(long reviewId); + Map getAllReviewLikes(); +} \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/review/ReviewStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/review/ReviewStorage.java new file mode 100644 index 0000000..4f29dac --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/review/ReviewStorage.java @@ -0,0 +1,15 @@ +package ru.yandex.practicum.filmorate.storage.review; + +import ru.yandex.practicum.filmorate.model.Review; + +import java.util.List; + +public interface ReviewStorage { + Review add(Review review); + Review update(Review review); + Review updateUseful(Review review); + void delete(long reviewId); + Review getById(long reviewId); + List getAllReviewsByFilmId(long filmId, int limit); + List getAllReviews(); +} \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/validators/FilmValidator.java b/src/main/java/ru/yandex/practicum/filmorate/validators/FilmValidator.java deleted file mode 100644 index 0376cd1..0000000 --- a/src/main/java/ru/yandex/practicum/filmorate/validators/FilmValidator.java +++ /dev/null @@ -1,26 +0,0 @@ -package ru.yandex.practicum.filmorate.validators; - -import ru.yandex.practicum.filmorate.exceptions.ValidationException; -import ru.yandex.practicum.filmorate.model.Film; - -import java.time.LocalDate; - -public class FilmValidator { - - private static boolean checkYear(Film film) { - LocalDate checkDate = LocalDate.of(1895, 12, 28); - if (film.getReleaseDate() != null - && film.getReleaseDate().isBefore(checkDate)) - return true; - throw new ValidationException("Invalid film's release date"); - } - - private static boolean checkLike(Film film) { - - return true; - } - - public static boolean valid(Film film) { - return (checkYear(film)); - } -} diff --git a/src/main/java/ru/yandex/practicum/filmorate/validators/UserValidator.java b/src/main/java/ru/yandex/practicum/filmorate/validators/UserValidator.java deleted file mode 100644 index f8cbe6d..0000000 --- a/src/main/java/ru/yandex/practicum/filmorate/validators/UserValidator.java +++ /dev/null @@ -1,15 +0,0 @@ -package ru.yandex.practicum.filmorate.validators; - -import ru.yandex.practicum.filmorate.model.User; - -public class UserValidator { - - public static User checkName(User user) { - if (user.getName() == null) { - user.setName(user.getLogin()); - } else if (user.getName().isEmpty() || user.getName().isBlank()) { - user.setName(user.getLogin()); - } - return user; - } -} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 25509ee..ed9b7ec 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,8 +1,9 @@ logging.level.ru.yandex.practicum=debug -logging.level.org.zalando.logbook=TRACE +logging.level.org.zalando.logbook=TRACE spring.sql.init.mode=always -spring.datasource.url=jdbc:h2:file:./db/filmorate +spring.datasource.url=jdbc:h2:file:./db/filmorate;AUTO_SERVER=TRUE spring.datasource.driverClassName=org.h2.Driver spring.datasource.username=sa -spring.datasource.password=password \ No newline at end of file +spring.datasource.password=password +spring.datasource.hikari.maximum-pool-size=500 \ No newline at end of file diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql index 1de346b..da588cb 100644 --- a/src/main/resources/data.sql +++ b/src/main/resources/data.sql @@ -1,11 +1,14 @@ -DELETE FROM films; -DELETE FROM users; -DELETE FROM film_genre; -DELETE FROM genre; -DELETE FROM likesList; -DELETE FROM friendship; -DELETE FROM mpa; -DELETE FROM friendshipStatus; +-- DELETE FROM directorFilm; +-- DELETE FROM directors; +-- DELETE FROM films; +-- DELETE FROM users; +-- DELETE FROM film_genre; +-- DELETE FROM genre; +-- DELETE FROM likesList; +-- DELETE FROM friendship; +-- DELETE FROM mpa; +-- DELETE FROM friendshipStatus; +-- DELETE FROM events; INSERT INTO genre (genreId, name) VALUES (1, 'Комедия'); INSERT INTO genre (genreId, name) VALUES (2, 'Драма'); INSERT INTO genre (genreId, name) VALUES (3, 'Мультфильм'); diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index 40e5d50..e90b90c 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -1,5 +1,26 @@ +DROP ALL OBJECTS DELETE FILES; + +CREATE TABLE IF NOT EXISTS genre ( + genreId int PRIMARY KEY , + name varchar +); + +CREATE TABLE IF NOT EXISTS mpa ( + mpaId int, + name varchar +); + +CREATE TABLE IF NOT EXISTS friendshipStatus ( + friendshipStatusId int PRIMARY KEY , + description varchar +); + +CREATE TABLE IF NOT EXISTS directors ( + directorId int NOT NULL AUTO_INCREMENT PRIMARY KEY, + directorName varchar +); CREATE TABLE IF NOT EXISTS films ( - filmId INTEGER PRIMARY KEY, + filmId INTEGER PRIMARY KEY AUTO_INCREMENT , name varchar(200), description varchar, release_date date, @@ -8,7 +29,7 @@ CREATE TABLE IF NOT EXISTS films ( ); CREATE TABLE IF NOT EXISTS users ( - userId INTEGER PRIMARY KEY, + userId INTEGER PRIMARY KEY AUTO_INCREMENT , email varchar, login varchar, name varchar, @@ -16,32 +37,52 @@ CREATE TABLE IF NOT EXISTS users ( ); CREATE TABLE IF NOT EXISTS film_genre ( - filmId int, - genreId int -); - -CREATE TABLE IF NOT EXISTS genre ( - genreId int, - name varchar + filmId int REFERENCES FILMS(filmId) ON DELETE CASCADE , + genreId int REFERENCES GENRE(genreId) ON DELETE CASCADE , + PRIMARY KEY (filmId, genreId) ); CREATE TABLE IF NOT EXISTS likesList ( - filmId int, - userId int + filmId int REFERENCES FILMS(filmId) ON DELETE CASCADE , + userId int REFERENCES USERS(userId) ON DELETE CASCADE ); CREATE TABLE IF NOT EXISTS friendship ( - userId int, - friendId int, - friendshipStatusId int + userId int REFERENCES USERS(userId) ON DELETE CASCADE , + friendId int REFERENCES USERS(userId) ON DELETE CASCADE , + friendshipStatusId int REFERENCES FRIENDSHIPSTATUS(friendshipStatusId), + PRIMARY KEY (userId, friendId) ); -CREATE TABLE IF NOT EXISTS mpa ( - mpaId int, - name varchar +CREATE TABLE IF NOT EXISTS review ( + review_id LONG GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + content varchar NOT NULL CHECK (content <> ' '), + isPositive boolean NOT NULL, + user_id LONG REFERENCES users (userId), + film_id LONG REFERENCES films (filmId), + useful int ); -CREATE TABLE IF NOT EXISTS friendshipStatus ( - friendshipStatusId int, - description varchar +CREATE TABLE IF NOT EXISTS review_likes ( + user_id LONG REFERENCES users (userId) ON DELETE CASCADE, + review_id LONG REFERENCES review (review_id) ON DELETE CASCADE, + isLike BOOLEAN NOT NULL, + PRIMARY KEY (user_id, review_id) +); + +CREATE TABLE IF NOT EXISTS directorFilm ( + directorFilmId int NOT NULL AUTO_INCREMENT PRIMARY KEY, + filmId int REFERENCES FILMS(filmId) ON DELETE CASCADE , + directorId int REFERENCES DIRECTORS(DIRECTORID) ON DELETE CASCADE, + CONSTRAINT filmDirectorFilm FOREIGN KEY (filmId) REFERENCES films (filmId), + CONSTRAINT directorFilmDirector FOREIGN KEY (directorId) REFERENCES directors (directorId) +); + +CREATE TABLE IF NOT EXISTS events ( + timestamp long, + userId int REFERENCES USERS(userId) ON DELETE CASCADE , + eventType varchar(6), + operation varchar(6), + eventId int GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + entityId long ); \ No newline at end of file