From dc001ed2410dd5f61c994996a8d768e9483f1e4f Mon Sep 17 00:00:00 2001 From: Denis Panasyuk Date: Wed, 26 Nov 2025 22:12:50 +0300 Subject: [PATCH 01/12] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=BE=20=D1=81=D0=BE=D0=B7=D0=B4=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=20=D0=B2=D1=81=D1=82=D1=80=D0=BE=D0=B5=D0=BD=D0=BD=D0=BE?= =?UTF-8?q?=D0=B9=20=D0=91=D0=94=20=D0=B8=20=D1=80=D0=B0=D0=B7=D0=BC=D0=B5?= =?UTF-8?q?=D1=87=D0=B5=D0=BD=D0=B0=20=D0=B0=D1=80=D1=85=D0=B8=D1=82=D0=B5?= =?UTF-8?q?=D0=BA=D1=82=D1=83=D1=80=D0=B0=20=D0=BF=D1=80=D0=BE=D0=B5=D0=BA?= =?UTF-8?q?=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 12 +++++ .../filmorate/controller/GenreController.java | 4 ++ .../controller/RatingController.java | 4 ++ .../practicum/filmorate/dao/BaseDao.java | 4 ++ .../filmorate/dao/FilmDbStorage.java | 4 ++ .../filmorate/dao/GenreDbStorage.java | 4 ++ .../filmorate/dao/RatingDbStorage.java | 4 ++ .../filmorate/dao/UserDbStorage.java | 4 ++ .../filmorate/dao/mappers/FilmRawMapper.java | 4 ++ .../filmorate/dao/mappers/GenreRawMapper.java | 4 ++ .../dao/mappers/RatingRawMapper.java | 4 ++ .../filmorate/dao/mappers/UserRawMapper.java | 4 ++ .../ErrorHandler.java | 4 +- .../practicum/filmorate/model/Genre.java | 4 ++ .../practicum/filmorate/model/Rating.java | 4 ++ src/main/resources/application.properties | 7 ++- src/main/resources/data.sql | 20 +++++++ src/main/resources/schema.sql | 53 +++++++++++++++++++ 18 files changed, 144 insertions(+), 4 deletions(-) create mode 100644 src/main/java/ru/yandex/practicum/filmorate/controller/GenreController.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/controller/RatingController.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/dao/BaseDao.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/dao/FilmDbStorage.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/dao/GenreDbStorage.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/dao/RatingDbStorage.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/dao/UserDbStorage.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/dao/mappers/FilmRawMapper.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/dao/mappers/GenreRawMapper.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/dao/mappers/RatingRawMapper.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/dao/mappers/UserRawMapper.java rename src/main/java/ru/yandex/practicum/filmorate/{controller => exception}/ErrorHandler.java (83%) create mode 100644 src/main/java/ru/yandex/practicum/filmorate/model/Genre.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/model/Rating.java create mode 100644 src/main/resources/data.sql create mode 100644 src/main/resources/schema.sql diff --git a/pom.xml b/pom.xml index 7cd7b4c..4e4a811 100644 --- a/pom.xml +++ b/pom.xml @@ -49,6 +49,18 @@ logbook-spring-boot-starter 3.7.2 + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + com.h2database + h2 + 2.1.210 + runtime + diff --git a/src/main/java/ru/yandex/practicum/filmorate/controller/GenreController.java b/src/main/java/ru/yandex/practicum/filmorate/controller/GenreController.java new file mode 100644 index 0000000..bc2195d --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/GenreController.java @@ -0,0 +1,4 @@ +package ru.yandex.practicum.filmorate.controller; + +public class GenreController { +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/controller/RatingController.java b/src/main/java/ru/yandex/practicum/filmorate/controller/RatingController.java new file mode 100644 index 0000000..b32733d --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/RatingController.java @@ -0,0 +1,4 @@ +package ru.yandex.practicum.filmorate.controller; + +public class RatingController { +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/dao/BaseDao.java b/src/main/java/ru/yandex/practicum/filmorate/dao/BaseDao.java new file mode 100644 index 0000000..c8e7c61 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/BaseDao.java @@ -0,0 +1,4 @@ +package ru.yandex.practicum.filmorate.dao; + +public class BaseDao { +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/dao/FilmDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dao/FilmDbStorage.java new file mode 100644 index 0000000..9aec026 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/FilmDbStorage.java @@ -0,0 +1,4 @@ +package ru.yandex.practicum.filmorate.dao; + +public class FilmDbStorage { +} 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..35eaf5d --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/GenreDbStorage.java @@ -0,0 +1,4 @@ +package ru.yandex.practicum.filmorate.dao; + +public class GenreDbStorage { +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/dao/RatingDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dao/RatingDbStorage.java new file mode 100644 index 0000000..9534134 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/RatingDbStorage.java @@ -0,0 +1,4 @@ +package ru.yandex.practicum.filmorate.dao; + +public class RatingDbStorage { +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/dao/UserDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dao/UserDbStorage.java new file mode 100644 index 0000000..6d715e7 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/UserDbStorage.java @@ -0,0 +1,4 @@ +package ru.yandex.practicum.filmorate.dao; + +public class UserDbStorage { +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/FilmRawMapper.java b/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/FilmRawMapper.java new file mode 100644 index 0000000..ebea56c --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/FilmRawMapper.java @@ -0,0 +1,4 @@ +package ru.yandex.practicum.filmorate.dao.mappers; + +public class FilmRawMapper { +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/GenreRawMapper.java b/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/GenreRawMapper.java new file mode 100644 index 0000000..c790f14 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/GenreRawMapper.java @@ -0,0 +1,4 @@ +package ru.yandex.practicum.filmorate.dao.mappers; + +public class GenreRawMapper { +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/RatingRawMapper.java b/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/RatingRawMapper.java new file mode 100644 index 0000000..ed0c50a --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/RatingRawMapper.java @@ -0,0 +1,4 @@ +package ru.yandex.practicum.filmorate.dao.mappers; + +public class RatingRawMapper { +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/UserRawMapper.java b/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/UserRawMapper.java new file mode 100644 index 0000000..9955890 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/UserRawMapper.java @@ -0,0 +1,4 @@ +package ru.yandex.practicum.filmorate.dao.mappers; + +public class UserRawMapper { +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/controller/ErrorHandler.java b/src/main/java/ru/yandex/practicum/filmorate/exception/ErrorHandler.java similarity index 83% rename from src/main/java/ru/yandex/practicum/filmorate/controller/ErrorHandler.java rename to src/main/java/ru/yandex/practicum/filmorate/exception/ErrorHandler.java index aaf6f71..60edc74 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/ErrorHandler.java +++ b/src/main/java/ru/yandex/practicum/filmorate/exception/ErrorHandler.java @@ -1,11 +1,9 @@ -package ru.yandex.practicum.filmorate.controller; +package ru.yandex.practicum.filmorate.exception; 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.exception.NotFoundException; -import ru.yandex.practicum.filmorate.exception.ValidationException; import ru.yandex.practicum.filmorate.model.ErrorResponse; @RestControllerAdvice diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/Genre.java b/src/main/java/ru/yandex/practicum/filmorate/model/Genre.java new file mode 100644 index 0000000..9f7ad37 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/model/Genre.java @@ -0,0 +1,4 @@ +package ru.yandex.practicum.filmorate.model; + +public class Genre { +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/Rating.java b/src/main/java/ru/yandex/practicum/filmorate/model/Rating.java new file mode 100644 index 0000000..e764459 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/model/Rating.java @@ -0,0 +1,4 @@ +package ru.yandex.practicum.filmorate.model; + +public class Rating { +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 4a23bbc..383a695 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,2 +1,7 @@ server.port=8080 -logging.level.org.zalando.logbook=TRACE \ No newline at end of file +logging.level.org.zalando.logbook=TRACE +spring.sql.init.mode=always +spring.datasource.url=jdbc:h2:file:./db/filmorate +spring.datasource.driverClassName=org.h2.Driver +spring.datasource.username=sa +spring.datasource.password=password \ No newline at end of file diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql new file mode 100644 index 0000000..5490392 --- /dev/null +++ b/src/main/resources/data.sql @@ -0,0 +1,20 @@ +DELETE FROM PUBLIC."Rating"; +DELETE FROM PUBLIC."Genre"; + +ALTER TABLE PUBLIC."Genre" ALTER COLUMN GENRE_ID RESTART WITH 1; +ALTER TABLE PUBLIC."Rating" ALTER COLUMN RATING_ID RESTART WITH 1; + +INSERT INTO PUBLIC."Genre" (NAME) VALUES +('Ужасы'), +('Триллер'), +('Комедия'), +('Драма'), +('Фантастика'), +('Боевик'); + +INSERT INTO PUBLIC."Rating" (NAME) VALUES +('G'), +('PG'), +('PG-13'), +('R'), +('NC-17'); \ No newline at end of file diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql new file mode 100644 index 0000000..847cfff --- /dev/null +++ b/src/main/resources/schema.sql @@ -0,0 +1,53 @@ +CREATE TABLE IF NOT EXISTS PUBLIC."Genre" ( + GENRE_ID INTEGER NOT NULL AUTO_INCREMENT, + NAME CHARACTER VARYING(100) NOT NULL, + CONSTRAINT PK_GENRE PRIMARY KEY (GENRE_ID) +); + +CREATE TABLE IF NOT EXISTS PUBLIC."Rating" ( + RATING_ID INTEGER NOT NULL AUTO_INCREMENT, + NAME CHARACTER VARYING(50) NOT NULL, + CONSTRAINT PK_RATING PRIMARY KEY (RATING_ID) +); + +CREATE TABLE IF NOT EXISTS PUBLIC."Users" ( + USER_ID INTEGER NOT NULL AUTO_INCREMENT, + NAME CHARACTER VARYING(255) NOT NULL, + LOGIN CHARACTER VARYING(100) NOT NULL, + EMAIL CHARACTER VARYING(255) NOT NULL, + BIRTHDAY DATE NOT NULL, + CONSTRAINT PK_USER PRIMARY KEY (USER_ID) +); + +CREATE TABLE IF NOT EXISTS PUBLIC."Film" ( + FILM_ID INTEGER NOT NULL AUTO_INCREMENT, + NAME CHARACTER VARYING(255) NOT NULL, + DESCRIPTION CHARACTER LARGE OBJECT NOT NULL, + RELEASE_DATE DATE NOT NULL, + DURATION INTEGER NOT NULL, + RATING_ID INTEGER NOT NULL, + CONSTRAINT PK_FILM PRIMARY KEY (FILM_ID), + CONSTRAINT FK_FILM_RATING_ID FOREIGN KEY (RATING_ID) REFERENCES PUBLIC."Rating"(RATING_ID) ON DELETE RESTRICT ON UPDATE RESTRICT +); + +CREATE TABLE IF NOT EXISTS PUBLIC."Film_genre" ( + FILM_ID INTEGER NOT NULL, + GENRE_ID INTEGER NOT NULL, + CONSTRAINT FK_FILM_GENRE_FILM_ID FOREIGN KEY (FILM_ID) REFERENCES PUBLIC."Film"(FILM_ID) ON DELETE RESTRICT ON UPDATE RESTRICT, + CONSTRAINT FK_FILM_GENRE_GENRE_ID FOREIGN KEY (GENRE_ID) REFERENCES PUBLIC."Genre"(GENRE_ID) ON DELETE RESTRICT ON UPDATE RESTRICT +); + +CREATE TABLE IF NOT EXISTS PUBLIC."Likes" ( + FILM_ID INTEGER NOT NULL, + USER_ID INTEGER NOT NULL, + CONSTRAINT FK_LIKES_FILM_ID FOREIGN KEY (FILM_ID) REFERENCES PUBLIC."Film"(FILM_ID) ON DELETE RESTRICT ON UPDATE RESTRICT, + CONSTRAINT FK_LIKES_USER_ID FOREIGN KEY (USER_ID) REFERENCES PUBLIC."Users"(USER_ID) ON DELETE RESTRICT ON UPDATE RESTRICT +); + +CREATE TABLE IF NOT EXISTS PUBLIC."User_friends" ( + USER_ID INTEGER NOT NULL, + FRIEND_ID INTEGER NOT NULL, + STATUS INTEGER NOT NULL, + CONSTRAINT FK_USER_FRIENDS_FRIEND_ID FOREIGN KEY (FRIEND_ID) REFERENCES PUBLIC."Users"(USER_ID) ON DELETE RESTRICT ON UPDATE RESTRICT, + CONSTRAINT FK_USER_FRIENDS_USER_ID FOREIGN KEY (USER_ID) REFERENCES PUBLIC."Users"(USER_ID) ON DELETE RESTRICT ON UPDATE RESTRICT +); \ No newline at end of file From d8b1f835e5a7fd6e8190c86f3eae58b63681b9f4 Mon Sep 17 00:00:00 2001 From: Denis Panasyuk Date: Fri, 28 Nov 2025 22:10:14 +0300 Subject: [PATCH 02/12] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D1=8B=20=D0=BA=D0=BE=D0=BD=D1=82=D1=80=D0=BE?= =?UTF-8?q?=D0=BB=D0=BB=D0=B5=D1=80=D1=8B=20=D0=B8=20=D1=81=D0=B5=D1=80?= =?UTF-8?q?=D0=B2=D0=B8=D1=81=D1=8B=20=D0=B0=20=D1=82=D0=B0=D0=BA=D0=B6?= =?UTF-8?q?=D0=B5=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D1=8B?= =?UTF-8?q?=20=D0=BD=D0=BE=D0=B2=D1=8B=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../filmorate/controller/FilmController.java | 22 +++--- .../filmorate/controller/GenreController.java | 31 ++++++++ .../controller/RatingController.java | 31 ++++++++ .../filmorate/controller/UserController.java | 24 +++--- .../practicum/filmorate/dao/BaseDao.java | 32 +++++++- .../filmorate/dao/FilmDbStorage.java | 52 ++++++++++++- .../filmorate/dao/UserDbStorage.java | 68 +++++++++++++++- .../filmorate/dao/mappers/UserRawMapper.java | 23 +++++- .../practicum/filmorate/model/Film.java | 3 + .../practicum/filmorate/model/Genre.java | 7 ++ .../practicum/filmorate/model/Rating.java | 7 ++ .../filmorate/service/FilmService.java | 77 ++++++++++++++++++- .../filmorate/service/GenreService.java | 32 ++++++++ .../filmorate/service/RatingService.java | 32 ++++++++ .../filmorate/service/UserService.java | 58 +++++++++++++- .../filmorate/storage/FilmStorage.java | 10 +++ .../filmorate/storage/GenreStorage.java | 20 +++++ .../storage/InMemoryFilmStorage.java | 25 ++++++ .../storage/InMemoryUserStorage.java | 31 ++++++++ .../filmorate/storage/RatingStorage.java | 14 ++++ .../filmorate/storage/UserStorage.java | 12 +++ 21 files changed, 579 insertions(+), 32 deletions(-) create mode 100644 src/main/java/ru/yandex/practicum/filmorate/service/GenreService.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/service/RatingService.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/storage/GenreStorage.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/storage/RatingStorage.java diff --git a/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java b/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java index 6f50e3c..0bca8fd 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java @@ -14,54 +14,52 @@ @RequestMapping("/films") public class FilmController { - private final FilmStorage inMemoryFilmStorage; - private final FilmService filmService; + private final FilmService service; @Autowired - public FilmController(FilmStorage inMemoryFilmStorage, FilmService filmService) { - this.inMemoryFilmStorage = inMemoryFilmStorage; - this.filmService = filmService; + public FilmController(FilmService filmService) { + this.service = filmService; } @GetMapping public Collection findAll() { log.info("Список фильмов выведен"); - return inMemoryFilmStorage.findAll(); + return service.findAll(); } @GetMapping("/{id}") public Film findById(@PathVariable("id") int filmId) { log.info("Фильм с id: {} выведен", filmId); - return inMemoryFilmStorage.findFilmById(filmId); + return service.findFilmById(filmId); } @GetMapping("/popular") public Collection getPopular(@RequestParam(defaultValue = "10") int count) { log.info("Список популярных фильмов выведен"); - return filmService.getPopular(count); + return service.getPopular(count); } @PostMapping public Film create(@RequestBody Film film) { log.info("Фильм: {} добавлен в базу", film); - return inMemoryFilmStorage.create(film); + return service.create(film); } @PutMapping public Film update(@RequestBody Film newFilm) { log.info("Данные о фильме: {} обновлены", newFilm); - return inMemoryFilmStorage.update(newFilm); + return service.update(newFilm); } @PutMapping("/{id}/like/{userId}") public void addLike(@PathVariable int id, @PathVariable int userId) { log.info("Фильму с id: {} поставил лайк пользователь с id: {}", id, userId); - filmService.addLike(id, userId); + service.addLike(id, userId); } @DeleteMapping("/{id}/like/{userId}") public void deleteLike(@PathVariable int id, @PathVariable int userId) { log.info("У фильма с id: {} убрал лайк пользователь с id: {}", id, userId); - filmService.deleteLike(id, userId); + service.deleteLike(id, userId); } } \ No newline at end of file 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 bc2195d..bf5c74d 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/GenreController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/GenreController.java @@ -1,4 +1,35 @@ package ru.yandex.practicum.filmorate.controller; +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.GenreService; + +import java.util.Collection; + +@Slf4j +@RestController +@RequestMapping("/genres") public class GenreController { + + private final GenreService service; + + public GenreController(GenreService service) { + this.service = service; + } + + @GetMapping + public Collection findAll() { + log.info("Список жанров выведен"); + return service.findAll(); + } + + @GetMapping("/{id}") + public Genre findGenreById(@PathVariable("id") int id) { + log.info("Жанр с id: {} выведен", id); + return service.findGenreById(id); + } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/controller/RatingController.java b/src/main/java/ru/yandex/practicum/filmorate/controller/RatingController.java index b32733d..dd96ab8 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/RatingController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/RatingController.java @@ -1,4 +1,35 @@ package ru.yandex.practicum.filmorate.controller; +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.Rating; +import ru.yandex.practicum.filmorate.service.RatingService; + +import java.util.Collection; + +@Slf4j +@RestController +@RequestMapping("/mpa") public class RatingController { + + private final RatingService service; + + public RatingController(RatingService service) { + this.service = service; + } + + @GetMapping + public Collection findAll() { + log.info("Список рейтингов выведен"); + return service.findAll(); + } + + @GetMapping("/{id}") + public Rating findRatingById(@PathVariable("id") int id) { + log.info("Рейтинг с id: {} выведен", id); + return service.findRatingById(id); + } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java b/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java index c134492..2878d54 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java @@ -13,59 +13,57 @@ @RequestMapping("/users") public class UserController { - private final InMemoryUserStorage inMemoryUserStorage; - private final UserService userService; + private final UserService service; - public UserController(InMemoryUserStorage inMemoryUserStorage, UserService userService) { - this.inMemoryUserStorage = inMemoryUserStorage; - this.userService = userService; + public UserController(UserService userService) { + this.service = userService; } @GetMapping public Collection findAll() { log.info("Список пользователей выведен"); - return inMemoryUserStorage.findAll(); + return service.findAll(); } @GetMapping("/{id}") public User findUserById(@PathVariable("id") int userId) { log.info("Пользователь с id: {} выведен", userId); - return inMemoryUserStorage.findUserById(userId); + return service.findUserById(userId); } @GetMapping("/{id}/friends") public Collection findAllFriends(@PathVariable("id") int userId) { log.info("Список друзей пользователя с id: {} выведен", userId); - return userService.getFriendList(userId); + return service.getFriendList(userId); } @GetMapping("/{id}/friends/common/{otherId}") public Collection findAllCommonFriends(@PathVariable int id, @PathVariable int otherId) { log.info("Список общих друзей пользователей с id: {} и {} выведен", id, otherId); - return userService.getCommonFriendList(id, otherId); + return service.getCommonFriendList(id, otherId); } @PostMapping public User create(@RequestBody User user) { log.info("Пользоввтель: {} создан и добавлен", user); - return inMemoryUserStorage.create(user); + return service.create(user); } @PutMapping public User update(@RequestBody User newUser) { log.info("Данные о пользователе: {} обновлены", newUser); - return inMemoryUserStorage.update(newUser); + return service.update(newUser); } @PutMapping("/{id}/friends/{friendId}") public void addFriend(@PathVariable("id") int userId, @PathVariable int friendId) { log.info("Пользователь с id: {} добавил пользователя с id: {} в друзья", userId, friendId); - userService.addFriend(userId, friendId); + service.addFriend(userId, friendId); } @DeleteMapping("/{id}/friends/{friendId}") public void deleteFriend(@PathVariable("id") int userId, @PathVariable int friendId) { log.info("Пользователь с id: {} удалил пользователя с id: {} из друзей", userId, friendId); - userService.deleteFriend(userId, friendId); + service.deleteFriend(userId, friendId); } } \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/dao/BaseDao.java b/src/main/java/ru/yandex/practicum/filmorate/dao/BaseDao.java index c8e7c61..985e593 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dao/BaseDao.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/BaseDao.java @@ -1,4 +1,34 @@ package ru.yandex.practicum.filmorate.dao; -public class BaseDao { +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; + +import java.util.Collection; +import java.util.Optional; + +public abstract class BaseDao { + protected final JdbcTemplate jdbc; + protected final RowMapper mapper; + + public BaseDao(JdbcTemplate jdbc, RowMapper mapper) { + this.jdbc = jdbc; + this.mapper = mapper; + } + + protected T get(String query, Object... params) { + return jdbc.queryForObject(query, mapper, params); + } + + public Collection getAll(String query, Object... params) { + return jdbc.query(query, mapper, params); + } + + public void delete(String query, Object... params) { + jdbc.update(query, params); + } + + public void update() { + + } } 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 9aec026..1b05d16 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dao/FilmDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/FilmDbStorage.java @@ -1,4 +1,54 @@ package ru.yandex.practicum.filmorate.dao; -public class FilmDbStorage { +import ru.yandex.practicum.filmorate.model.Film; +import ru.yandex.practicum.filmorate.storage.FilmStorage; + +import java.util.Collection; +import java.util.List; + +public class FilmDbStorage implements FilmStorage { + @Override + public Collection findAll() { + return List.of(); + } + + @Override + public Film create(Film film) { + return null; + } + + @Override + public Film update(Film newFilm) { + return null; + } + + @Override + public Film findFilmById(int id) { + return null; + } + + @Override + public void remove(int id) { + + } + + @Override + public Collection getPopular(int count) { + return List.of(); + } + + @Override + public void addLike(int id, int userId) { + + } + + @Override + public void deleteLike(int id, int userId) { + + } + + @Override + public boolean contains(Integer id) { + return false; + } } 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 6d715e7..4168d64 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dao/UserDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/UserDbStorage.java @@ -1,4 +1,70 @@ package ru.yandex.practicum.filmorate.dao; -public class UserDbStorage { +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import ru.yandex.practicum.filmorate.model.User; +import ru.yandex.practicum.filmorate.storage.UserStorage; + +import java.util.Collection; +import java.util.List; + +public class UserDbStorage extends BaseDao implements UserStorage { + + private static final String FIND_ALL_QUERY = "SELECT * FROM PUBLIC.\"Users\""; + private static final String FIND_BY_EMAIL_QUERY = "SELECT * FROM PUBLIC.\"Users\" WHERE USER_ID = ?"; + private static final String INSERT_QUERY = "INSERT INTO users(username, email, password, registration_date)" + + "VALUES (?, ?, ?, ?) returning id"; + public UserDbStorage(JdbcTemplate jdbc, RowMapper mapper) { + super(jdbc, mapper); + } + + @Override + public Collection findAll() { + return List.of(); + } + + @Override + public User create(User user) { + return null; + } + + @Override + public User update(User newUser) { + return null; + } + + @Override + public User findUserById(int id) { + return null; + } + + @Override + public void remove(int id) { + + } + + @Override + public void addFriend(int userId, int friendId) { + + } + + @Override + public void deleteFriend(int userId, int friendId) { + + } + + @Override + public Collection getFriendList(int userId) { + return List.of(); + } + + @Override + public Collection getCommonFriendList(int id, int otherId) { + return List.of(); + } + + @Override + public boolean contains(Integer id) { + return false; + } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/UserRawMapper.java b/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/UserRawMapper.java index 9955890..d7fbe8d 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/UserRawMapper.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/UserRawMapper.java @@ -1,4 +1,25 @@ package ru.yandex.practicum.filmorate.dao.mappers; -public class UserRawMapper { +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Component; +import ru.yandex.practicum.filmorate.model.User; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Timestamp; + +@Component +public class UserRawMapper implements RowMapper { + + @Override + public User mapRow(ResultSet resultSet, int rowNum) throws SQLException { + User user = new User(); + user.setId(resultSet.getInt("USER_ID")); + user.setName(resultSet.getString("NAME")); + user.setLogin(resultSet.getString("LOGIN")); + user.setEmail(resultSet.getString("EMAIL")); + user.setBirthday(resultSet.getDate("BIRTHDAY").toLocalDate()); + + return user; + } } 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 6fbe6f5..95ca381 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/model/Film.java +++ b/src/main/java/ru/yandex/practicum/filmorate/model/Film.java @@ -16,7 +16,10 @@ public class Film { private String description; private LocalDate releaseDate; private int duration; + private Rating mpa; private Set likes = new HashSet<>(); + private Set genres = new HashSet<>(); + public int getRating() { return likes.size(); diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/Genre.java b/src/main/java/ru/yandex/practicum/filmorate/model/Genre.java index 9f7ad37..b05da8c 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/model/Genre.java +++ b/src/main/java/ru/yandex/practicum/filmorate/model/Genre.java @@ -1,4 +1,11 @@ package ru.yandex.practicum.filmorate.model; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Data public class Genre { + @NotNull + private Integer id; + private String name; } diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/Rating.java b/src/main/java/ru/yandex/practicum/filmorate/model/Rating.java index e764459..0a3e66c 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/model/Rating.java +++ b/src/main/java/ru/yandex/practicum/filmorate/model/Rating.java @@ -1,4 +1,11 @@ package ru.yandex.practicum.filmorate.model; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Data public class Rating { + @NotNull + private Integer id; + private String name; } 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 4ea7853..cebbbcc 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/service/FilmService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/FilmService.java @@ -4,11 +4,16 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; +import ru.yandex.practicum.filmorate.exception.NotFoundException; import ru.yandex.practicum.filmorate.exception.ValidationException; 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.storage.GenreStorage; +import ru.yandex.practicum.filmorate.storage.RatingStorage; import ru.yandex.practicum.filmorate.storage.UserStorage; +import java.time.LocalDate; import java.util.Collection; import java.util.Comparator; import java.util.stream.Collectors; @@ -19,12 +24,59 @@ public class FilmService { private final FilmStorage filmStorage; private final UserStorage userStorage; + private final GenreStorage genreStorage; + private final RatingStorage ratingStorage; @Autowired - public FilmService(@Qualifier("inMemoryFilmStorage") FilmStorage filmStorage, - @Qualifier("inMemoryUserStorage") UserStorage userStorage) { + public FilmService(@Qualifier("FilmDbStorage") FilmStorage filmStorage, + @Qualifier("UserDbStorage") UserStorage userStorage, + @Qualifier("GenreDbStorage") GenreStorage genreStorage, + @Qualifier("RatingDbStorage") RatingStorage ratingStorage) { this.filmStorage = filmStorage; this.userStorage = userStorage; + this.genreStorage = genreStorage; + this.ratingStorage = ratingStorage; + } + + + public Collection findAll() { + return filmStorage.findAll(); + } + + public Film create(Film film) { + validateFilm(film); + film = filmStorage.create(film); + if (film.getGenres() != null && !film.getGenres().isEmpty()) { + genreStorage.addGenresToFilm(film); + } + return film; + } + + public Film update(Film newFilm) { + if (newFilm.getId() == null) { + log.warn("Не указан id"); + throw new ValidationException("Id должен быть указан"); + } + validateFilm(newFilm); + Film oldFilm = filmStorage.update(newFilm); + if (oldFilm.getGenres() != null && !oldFilm.getGenres().isEmpty()) { + genreStorage.updateFilmGenres(oldFilm); + } + return oldFilm; + } + + public Film findFilmById(int id) { + if (!filmStorage.contains(id)) { + throw new NotFoundException("Фильм с id = " + id + " не найден"); + } + return filmStorage.findFilmById(id); + } + + public void remove(int id) { + if (!filmStorage.contains(id)) { + throw new NotFoundException("Фильм с id = " + id + " не найден"); + } + filmStorage.remove(id); } public void addLike(int id, int userId) { @@ -54,5 +106,26 @@ public Collection getPopular(int count) { .collect(Collectors.toList()); } + private void validateFilm(Film film) { + if (film.getDescription() != null && film.getDescription().length() > 200) { + log.warn("Ошибка лимита"); + throw new ValidationException("Описание превышает 200 символов"); + } + if (film.getReleaseDate().isBefore(LocalDate.of(1895, 12, 28))) { + log.warn("Ошибка даты"); + throw new ValidationException("Неверная дата релиза"); + } + + if (film.getName() == null || film.getName().isBlank()) { + log.warn("Пустое название фильма"); + throw new ValidationException("Название не может быть пустым"); + } + + if (film.getDuration() < 1) { + log.warn("Ошибка длительности"); + throw new ValidationException("Длительность не может быть меньше 1"); + } + } + public static final Comparator comparator = Comparator.comparingInt(Film::getRating).reversed(); } \ 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..d06c84b --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/service/GenreService.java @@ -0,0 +1,32 @@ +package ru.yandex.practicum.filmorate.service; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import ru.yandex.practicum.filmorate.exception.NotFoundException; +import ru.yandex.practicum.filmorate.model.Genre; +import ru.yandex.practicum.filmorate.storage.GenreStorage; + +import java.util.Collection; + +@Slf4j +@Service +public class GenreService { + + private final GenreStorage genreStorage; + + public GenreService(GenreStorage genreStorage) { + this.genreStorage = genreStorage; + } + + public Collection findAll() { + return genreStorage.findAll(); + } + + public Genre findGenreById(int id) { + if (!genreStorage.contains(id)) { + log.warn("Жанр не найден"); + throw new NotFoundException("Жанра с таким id не найдено"); + } + return genreStorage.findGenreById(id); + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/RatingService.java b/src/main/java/ru/yandex/practicum/filmorate/service/RatingService.java new file mode 100644 index 0000000..6038228 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/service/RatingService.java @@ -0,0 +1,32 @@ +package ru.yandex.practicum.filmorate.service; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import ru.yandex.practicum.filmorate.exception.NotFoundException; +import ru.yandex.practicum.filmorate.model.Rating; +import ru.yandex.practicum.filmorate.storage.RatingStorage; + +import java.util.Collection; + +@Slf4j +@Service +public class RatingService { + + private final RatingStorage ratingStorage; + + public RatingService(RatingStorage ratingStorage) { + this.ratingStorage = ratingStorage; + } + + public Collection findAll() { + return ratingStorage.findAll(); + } + + public Rating findRatingById(int id) { + if (!ratingStorage.contains(id)) { + log.warn("Рейтинг не найден"); + throw new NotFoundException("Рейтинга с таким id не найдено"); + } + return ratingStorage.findGenreById(id); + } +} \ No newline at end of file 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 e03525e..71338bf 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/service/UserService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/UserService.java @@ -3,10 +3,12 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; +import ru.yandex.practicum.filmorate.exception.NotFoundException; import ru.yandex.practicum.filmorate.exception.ValidationException; import ru.yandex.practicum.filmorate.model.User; import ru.yandex.practicum.filmorate.storage.UserStorage; +import java.time.LocalDate; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -17,10 +19,64 @@ public class UserService { private final UserStorage userStorage; - public UserService(@Qualifier("inMemoryUserStorage") UserStorage userStorage) { + public UserService(@Qualifier("UserDbStorage") UserStorage userStorage) { this.userStorage = userStorage; } + public Collection findAll() { + return userStorage.findAll(); + } + + public User create(User user) { + validateUser(user); + user = userStorage.create(user); + return user; + } + + public User update(User newUser) { + if (newUser.getId() == null) { + log.warn("Не указан id"); + throw new ValidationException("Id должен быть указан"); + } + validateUser(newUser); + return userStorage.update(newUser); + } + + public User findUserById(int userId) { + if (!userStorage.contains(userId)) { + log.warn("Пользователь не найден"); + throw new NotFoundException("Пользователя с таким id не найдено"); + } + return userStorage.findUserById(userId); + } + + public void remove(int id) { + if (!userStorage.contains(id)) { + log.warn("Пользователь не найден"); + throw new NotFoundException("Пользователя с таким id не найдено"); + } + userStorage.remove(id); + } + + private void validateUser(User user) { + if (!user.getEmail().contains("@")) { + log.warn("Ошибка в формате почты"); + throw new ValidationException("Неверная почта"); + } + if (user.getBirthday().isAfter(LocalDate.now())) { + log.warn("Ошибка в дате рождения"); + throw new ValidationException("Неверная дата рождения"); + } + if (user.getLogin().contains(" ")) { + log.warn("Ошибка в формате логина"); + throw new ValidationException("Неправильный формат логина"); + } + if (user.getName() == null || user.getName().trim().isEmpty()) { + log.info("Пустое имя пользователя заменено на логин"); + user.setName(user.getLogin()); + } + } + public void addFriend(int userId, int friendId) { if (userId == friendId) { log.warn("Ошибка добавления в друзья"); 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 0b380f8..dd47500 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/storage/FilmStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/FilmStorage.java @@ -13,4 +13,14 @@ public interface FilmStorage { Film update(Film newFilm); Film findFilmById(int id); + + void remove(int id); + + Collection getPopular(int count); + + void addLike(int id, int userId); + + void deleteLike(int id, int userId); + + boolean contains(Integer id); } \ No newline at end of file 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..9079e19 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/GenreStorage.java @@ -0,0 +1,20 @@ +package ru.yandex.practicum.filmorate.storage; + +import ru.yandex.practicum.filmorate.model.Film; +import ru.yandex.practicum.filmorate.model.Genre; + +import java.util.Collection; +import java.util.Optional; + +public interface GenreStorage { + + Collection findAll(); + + Genre findGenreById(int id); + + void addGenresToFilm(Film film); + + void updateFilmGenres(Film film); + + boolean contains(Integer id); +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryFilmStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryFilmStorage.java index b6b340a..28902f6 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryFilmStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryFilmStorage.java @@ -55,6 +55,31 @@ public Film findFilmById(int id) { return films.get(id); } + @Override + public void remove(int id) { + + } + + @Override + public Collection getPopular(int count) { + return List.of(); + } + + @Override + public void addLike(int id, int userId) { + + } + + @Override + public void deleteLike(int id, int userId) { + + } + + @Override + public boolean contains(Integer id) { + return false; + } + private int getNextId() { int currentMaxId = films.keySet() .stream() diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryUserStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryUserStorage.java index d9468af..32be427 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryUserStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryUserStorage.java @@ -9,6 +9,7 @@ import java.time.LocalDate; import java.util.Collection; import java.util.HashMap; +import java.util.List; import java.util.Map; @Slf4j @@ -66,6 +67,36 @@ public User findUserById(int userId) { return users.get(userId); } + @Override + public void remove(int id) { + + } + + @Override + public void addFriend(int userId, int friendId) { + + } + + @Override + public void deleteFriend(int userId, int friendId) { + + } + + @Override + public Collection getFriendList(int userId) { + return List.of(); + } + + @Override + public Collection getCommonFriendList(int id, int otherId) { + return List.of(); + } + + @Override + public boolean contains(Integer id) { + return false; + } + private void validateUser(User user) { if (!user.getEmail().contains("@")) { log.warn("Ошибка в формате почты"); diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/RatingStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/RatingStorage.java new file mode 100644 index 0000000..6786921 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/RatingStorage.java @@ -0,0 +1,14 @@ +package ru.yandex.practicum.filmorate.storage; + +import ru.yandex.practicum.filmorate.model.Rating; + +import java.util.Collection; + +public interface RatingStorage { + + Collection findAll(); + + Rating findGenreById(int id); + + boolean contains(Integer id); +} 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 04935d0..ae1cea1 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/storage/UserStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/UserStorage.java @@ -13,4 +13,16 @@ public interface UserStorage { User update(User newUser); User findUserById(int id); + + void remove(int id); + + void addFriend(int userId, int friendId); + + void deleteFriend(int userId, int friendId); + + Collection getFriendList(int userId); + + Collection getCommonFriendList(int id, int otherId); + + boolean contains(Integer id); } \ No newline at end of file From 06ea5e129699749a33d81ed947914a679187c38c Mon Sep 17 00:00:00 2001 From: Denis Panasyuk Date: Fri, 28 Nov 2025 22:22:56 +0300 Subject: [PATCH 03/12] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D1=8B=20=D0=BA=D0=BE=D0=BD=D1=82=D1=80=D0=BE?= =?UTF-8?q?=D0=BB=D0=BB=D0=B5=D1=80=D1=8B=20=D0=B8=20=D1=81=D0=B5=D1=80?= =?UTF-8?q?=D0=B2=D0=B8=D1=81=D1=8B=20=D0=B0=20=D1=82=D0=B0=D0=BA=D0=B6?= =?UTF-8?q?=D0=B5=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D1=8B?= =?UTF-8?q?=20=D0=BD=D0=BE=D0=B2=D1=8B=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../filmorate/controller/FilmController.java | 4 +-- .../filmorate/controller/UserController.java | 4 +-- .../practicum/filmorate/dao/BaseDao.java | 4 +-- .../filmorate/dao/FilmDbStorage.java | 2 ++ .../filmorate/dao/GenreDbStorage.java | 35 ++++++++++++++++++- .../filmorate/dao/RatingDbStorage.java | 24 ++++++++++++- .../filmorate/dao/UserDbStorage.java | 2 ++ .../filmorate/service/FilmService.java | 7 ++-- .../filmorate/storage/GenreStorage.java | 1 - 9 files changed, 69 insertions(+), 14 deletions(-) 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 0bca8fd..52f859f 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java @@ -17,8 +17,8 @@ public class FilmController { private final FilmService service; @Autowired - public FilmController(FilmService filmService) { - this.service = filmService; + public FilmController(FilmService service) { + this.service = service; } @GetMapping 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 2878d54..eba86b3 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java @@ -15,8 +15,8 @@ public class UserController { private final UserService service; - public UserController(UserService userService) { - this.service = userService; + public UserController(UserService service) { + this.service = service; } @GetMapping diff --git a/src/main/java/ru/yandex/practicum/filmorate/dao/BaseDao.java b/src/main/java/ru/yandex/practicum/filmorate/dao/BaseDao.java index 985e593..6a01711 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dao/BaseDao.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/BaseDao.java @@ -1,12 +1,12 @@ package ru.yandex.practicum.filmorate.dao; -import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Repository; import java.util.Collection; -import java.util.Optional; +@Repository public abstract class BaseDao { protected final JdbcTemplate jdbc; protected final RowMapper mapper; 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 1b05d16..95e41e8 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dao/FilmDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/FilmDbStorage.java @@ -1,11 +1,13 @@ package ru.yandex.practicum.filmorate.dao; +import org.springframework.stereotype.Repository; import ru.yandex.practicum.filmorate.model.Film; import ru.yandex.practicum.filmorate.storage.FilmStorage; import java.util.Collection; import java.util.List; +@Repository("FilmDbStorage") public class FilmDbStorage implements FilmStorage { @Override public Collection findAll() { diff --git a/src/main/java/ru/yandex/practicum/filmorate/dao/GenreDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dao/GenreDbStorage.java index 35eaf5d..4d8aec3 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dao/GenreDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/GenreDbStorage.java @@ -1,4 +1,37 @@ package ru.yandex.practicum.filmorate.dao; -public class GenreDbStorage { +import org.springframework.stereotype.Repository; +import ru.yandex.practicum.filmorate.model.Film; +import ru.yandex.practicum.filmorate.model.Genre; +import ru.yandex.practicum.filmorate.storage.GenreStorage; + +import java.util.Collection; +import java.util.List; + +@Repository("GenreDbStorage") +public class GenreDbStorage implements GenreStorage { + @Override + public Collection findAll() { + return List.of(); + } + + @Override + public Genre findGenreById(int id) { + return null; + } + + @Override + public void addGenresToFilm(Film film) { + + } + + @Override + public void updateFilmGenres(Film film) { + + } + + @Override + public boolean contains(Integer id) { + return false; + } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dao/RatingDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dao/RatingDbStorage.java index 9534134..5f5d539 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dao/RatingDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/RatingDbStorage.java @@ -1,4 +1,26 @@ package ru.yandex.practicum.filmorate.dao; -public class RatingDbStorage { +import org.springframework.stereotype.Repository; +import ru.yandex.practicum.filmorate.model.Rating; +import ru.yandex.practicum.filmorate.storage.RatingStorage; + +import java.util.Collection; +import java.util.List; + +@Repository("RatingDbStorage") +public class RatingDbStorage implements RatingStorage { + @Override + public Collection findAll() { + return List.of(); + } + + @Override + public Rating findGenreById(int id) { + return null; + } + + @Override + public boolean contains(Integer id) { + return false; + } } 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 4168d64..dd7f8d8 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dao/UserDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/UserDbStorage.java @@ -2,12 +2,14 @@ import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Repository; import ru.yandex.practicum.filmorate.model.User; import ru.yandex.practicum.filmorate.storage.UserStorage; import java.util.Collection; import java.util.List; +@Repository("UserDbStorage") public class UserDbStorage extends BaseDao implements UserStorage { private static final String FIND_ALL_QUERY = "SELECT * FROM PUBLIC.\"Users\""; 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 cebbbcc..4f24ba3 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/service/FilmService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/FilmService.java @@ -4,6 +4,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; +import ru.yandex.practicum.filmorate.dao.FilmDbStorage; import ru.yandex.practicum.filmorate.exception.NotFoundException; import ru.yandex.practicum.filmorate.exception.ValidationException; import ru.yandex.practicum.filmorate.model.Film; @@ -25,20 +26,16 @@ public class FilmService { private final FilmStorage filmStorage; private final UserStorage userStorage; private final GenreStorage genreStorage; - private final RatingStorage ratingStorage; @Autowired public FilmService(@Qualifier("FilmDbStorage") FilmStorage filmStorage, @Qualifier("UserDbStorage") UserStorage userStorage, - @Qualifier("GenreDbStorage") GenreStorage genreStorage, - @Qualifier("RatingDbStorage") RatingStorage ratingStorage) { + @Qualifier("GenreDbStorage") GenreStorage genreStorage) { this.filmStorage = filmStorage; this.userStorage = userStorage; this.genreStorage = genreStorage; - this.ratingStorage = ratingStorage; } - public Collection findAll() { return filmStorage.findAll(); } diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/GenreStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/GenreStorage.java index 9079e19..471d042 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/storage/GenreStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/GenreStorage.java @@ -4,7 +4,6 @@ import ru.yandex.practicum.filmorate.model.Genre; import java.util.Collection; -import java.util.Optional; public interface GenreStorage { From 6a26a3d18196bfec3d6662a0f8be7e1edd6821c1 Mon Sep 17 00:00:00 2001 From: Denis Panasyuk Date: Fri, 28 Nov 2025 22:43:06 +0300 Subject: [PATCH 04/12] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D1=8B=20=D0=BA=D0=BE=D0=BD=D1=82=D1=80=D0=BE?= =?UTF-8?q?=D0=BB=D0=BB=D0=B5=D1=80=D1=8B=20=D0=B8=20=D1=81=D0=B5=D1=80?= =?UTF-8?q?=D0=B2=D0=B8=D1=81=D1=8B=20=D0=B0=20=D1=82=D0=B0=D0=BA=D0=B6?= =?UTF-8?q?=D0=B5=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D1=8B?= =?UTF-8?q?=20=D0=BD=D0=BE=D0=B2=D1=8B=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{FilmRawMapper.java => FilmRowMapper.java} | 2 +- .../filmorate/dao/mappers/GenreRowMapper.java | 16 ++++++++++++++++ .../filmorate/dao/mappers/RatingRawMapper.java | 4 ---- ...{GenreRawMapper.java => RatingRowMapper.java} | 2 +- .../{UserRawMapper.java => UserRowMapper.java} | 2 +- 5 files changed, 19 insertions(+), 7 deletions(-) rename src/main/java/ru/yandex/practicum/filmorate/dao/mappers/{FilmRawMapper.java => FilmRowMapper.java} (65%) create mode 100644 src/main/java/ru/yandex/practicum/filmorate/dao/mappers/GenreRowMapper.java delete mode 100644 src/main/java/ru/yandex/practicum/filmorate/dao/mappers/RatingRawMapper.java rename src/main/java/ru/yandex/practicum/filmorate/dao/mappers/{GenreRawMapper.java => RatingRowMapper.java} (63%) rename src/main/java/ru/yandex/practicum/filmorate/dao/mappers/{UserRawMapper.java => UserRowMapper.java} (92%) diff --git a/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/FilmRawMapper.java b/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/FilmRowMapper.java similarity index 65% rename from src/main/java/ru/yandex/practicum/filmorate/dao/mappers/FilmRawMapper.java rename to src/main/java/ru/yandex/practicum/filmorate/dao/mappers/FilmRowMapper.java index ebea56c..125d63f 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/FilmRawMapper.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/FilmRowMapper.java @@ -1,4 +1,4 @@ package ru.yandex.practicum.filmorate.dao.mappers; -public class FilmRawMapper { +public class FilmRowMapper { } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/GenreRowMapper.java b/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/GenreRowMapper.java new file mode 100644 index 0000000..3186372 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/GenreRowMapper.java @@ -0,0 +1,16 @@ +package ru.yandex.practicum.filmorate.dao.mappers; + +import org.springframework.jdbc.core.RowMapper; +import ru.yandex.practicum.filmorate.model.Genre; +import ru.yandex.practicum.filmorate.model.User; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class GenreRowMapper implements RowMapper { + + @Override + public Genre mapRow(ResultSet rs, int rowNum) throws SQLException { + return null; + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/RatingRawMapper.java b/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/RatingRawMapper.java deleted file mode 100644 index ed0c50a..0000000 --- a/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/RatingRawMapper.java +++ /dev/null @@ -1,4 +0,0 @@ -package ru.yandex.practicum.filmorate.dao.mappers; - -public class RatingRawMapper { -} diff --git a/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/GenreRawMapper.java b/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/RatingRowMapper.java similarity index 63% rename from src/main/java/ru/yandex/practicum/filmorate/dao/mappers/GenreRawMapper.java rename to src/main/java/ru/yandex/practicum/filmorate/dao/mappers/RatingRowMapper.java index c790f14..db61361 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/GenreRawMapper.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/RatingRowMapper.java @@ -1,4 +1,4 @@ package ru.yandex.practicum.filmorate.dao.mappers; -public class GenreRawMapper { +public class RatingRowMapper { } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/UserRawMapper.java b/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/UserRowMapper.java similarity index 92% rename from src/main/java/ru/yandex/practicum/filmorate/dao/mappers/UserRawMapper.java rename to src/main/java/ru/yandex/practicum/filmorate/dao/mappers/UserRowMapper.java index d7fbe8d..2907c59 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/UserRawMapper.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/UserRowMapper.java @@ -9,7 +9,7 @@ import java.sql.Timestamp; @Component -public class UserRawMapper implements RowMapper { +public class UserRowMapper implements RowMapper { @Override public User mapRow(ResultSet resultSet, int rowNum) throws SQLException { From 979fdc6f966b00a3c20f55207728fd9e8f6a3b67 Mon Sep 17 00:00:00 2001 From: Denis Panasyuk Date: Sun, 30 Nov 2025 22:09:54 +0300 Subject: [PATCH 05/12] =?UTF-8?q?=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D0=BD=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8?= =?UTF-8?q?=D0=BE=D0=BD=D0=B0=D0=BB=20=D1=81=D0=BE=D0=B3=D0=BB=D0=B0=D1=81?= =?UTF-8?q?=D0=BD=D0=BE=20=D0=A4=D0=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../practicum/filmorate/dao/BaseDao.java | 44 ++++++++++-- .../filmorate/dao/FilmDbStorage.java | 68 +++++++++++++++---- .../filmorate/dao/GenreDbStorage.java | 38 ++++++++--- .../filmorate/dao/RatingDbStorage.java | 22 +++--- .../filmorate/dao/UserDbStorage.java | 63 ++++++++++++----- .../filmorate/dao/mappers/FilmRowMapper.java | 58 +++++++++++++++- .../filmorate/dao/mappers/GenreRowMapper.java | 10 ++- .../dao/mappers/RatingRowMapper.java | 21 +++++- .../filmorate/dao/mappers/UserRowMapper.java | 1 - .../exception/InternalServerException.java | 6 ++ .../practicum/filmorate/model/Film.java | 11 ++- .../practicum/filmorate/model/Genre.java | 4 ++ .../practicum/filmorate/model/Rating.java | 4 ++ .../filmorate/service/FilmService.java | 20 ++---- .../filmorate/service/GenreService.java | 7 -- .../filmorate/service/RatingService.java | 9 +-- .../filmorate/storage/FilmStorage.java | 4 +- .../filmorate/storage/GenreStorage.java | 2 - .../storage/InMemoryFilmStorage.java | 6 +- .../storage/InMemoryUserStorage.java | 13 ++-- .../filmorate/storage/RatingStorage.java | 4 +- .../filmorate/storage/UserStorage.java | 4 +- 22 files changed, 304 insertions(+), 115 deletions(-) create mode 100644 src/main/java/ru/yandex/practicum/filmorate/exception/InternalServerException.java diff --git a/src/main/java/ru/yandex/practicum/filmorate/dao/BaseDao.java b/src/main/java/ru/yandex/practicum/filmorate/dao/BaseDao.java index 6a01711..4a640e2 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dao/BaseDao.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/BaseDao.java @@ -2,9 +2,13 @@ import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.support.GeneratedKeyHolder; import org.springframework.stereotype.Repository; +import ru.yandex.practicum.filmorate.exception.InternalServerException; -import java.util.Collection; +import java.sql.PreparedStatement; +import java.sql.Statement; +import java.util.List; @Repository public abstract class BaseDao { @@ -20,15 +24,45 @@ protected T get(String query, Object... params) { return jdbc.queryForObject(query, mapper, params); } - public Collection getAll(String query, Object... params) { + public List getAll(String query, Object... params) { return jdbc.query(query, mapper, params); } - public void delete(String query, Object... params) { - jdbc.update(query, params); + public void delete(String query, Integer id) { + jdbc.update(query, id); } - public void update() { + public void update(String query, Object...params){ + int rowsUpdated = jdbc.update(query, params); + if (rowsUpdated == 0) { + try { + throw new InternalServerException("Не удалось обновить данные"); + } catch (InternalServerException e) { + throw new RuntimeException(e); + } + } + } + + public int insert(String query, Object... params) { + GeneratedKeyHolder keyHolder = new GeneratedKeyHolder(); + jdbc.update(connection -> { + PreparedStatement ps = connection + .prepareStatement(query, Statement.RETURN_GENERATED_KEYS); + for (int idx = 0; idx < params.length; idx++) { + ps.setObject(idx + 1, params[idx]); + } + return ps;}, keyHolder); + + Integer id = keyHolder.getKeyAs(Integer.class); + if (id != null) { + return id; + } else { + try { + throw new InternalServerException("Не удалось сохранить данные"); + } catch (InternalServerException e) { + throw new RuntimeException(e); + } + } } } 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 95e41e8..12e6137 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dao/FilmDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/FilmDbStorage.java @@ -1,56 +1,100 @@ package ru.yandex.practicum.filmorate.dao; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; import org.springframework.stereotype.Repository; import ru.yandex.practicum.filmorate.model.Film; import ru.yandex.practicum.filmorate.storage.FilmStorage; import java.util.Collection; -import java.util.List; @Repository("FilmDbStorage") -public class FilmDbStorage implements FilmStorage { +public class FilmDbStorage extends BaseDao implements FilmStorage { + + private static final String FIND_ALL_FILMS_QUERY = "SELECT f.FILM_ID, f.NAME, f.DESCRIPTION, f.RELEASE_DATE, " + + "f.DURATION, r.NAME AS RATING_NAME, GROUP_CONCAT(g.NAME) AS GENRES, COUNT(l.USER_ID) AS LIKES_COUNT " + + "FROM PUBLIC.\"Film\" f LEFT JOIN PUBLIC.\"Rating\" r ON f.RATING_ID = r.RATING_ID LEFT " + + "JOIN PUBLIC.\"Film_genre\" fg ON f.FILM_ID = fg.FILM_ID LEFT JOIN PUBLIC.\"Genre\" g " + + "ON fg.GENRE_ID = g.GENRE_ID LEFT JOIN PUBLIC.\"Likes\" l ON f.FILM_ID = l.FILM_ID"; + private static final String INSERT_QUERY = "INSERT INTO PUBLIC.\"Film\" (NAME, DESCRIPTION, RELEASE_DATE, " + + "DURATION, RATING_ID) VALUES (?, ?, ?, ?, ?)"; + private static final String GROUP_BY = "GROUP BY f.FILM_ID"; + private static final String DELETE_QUERY = "DELETE FROM PUBLIC.\"Film\" WHERE FILM_ID = ?"; + private static final String UPDATE_QUERY = "UPDATE PUBLIC.\"Film\" SET NAME = :NAME, DESCRIPTION = :DESCRIPTION, " + + "RELEASE_DATE = :RELEASE_DATE, DURATION = :DURATION, RATING_ID = :RATING_ID WHERE FILM_ID = :FILM_ID"; + private static final String EXISTS_QUERY = "SELECT EXISTS(SELECT 1 FROM PUBLIC.\"Film\" WHERE FILM_ID = ?)"; + + public FilmDbStorage(JdbcTemplate jdbc, RowMapper mapper) { + super(jdbc, mapper); + } + @Override public Collection findAll() { - return List.of(); + return getAll(FIND_ALL_FILMS_QUERY); } @Override public Film create(Film film) { - return null; + int id = insert( + INSERT_QUERY, + film.getName(), + film.getDescription(), + film.getDuration(), + film.getReleaseDate(), + film.getMpa() + ); + film.setId(id); + return film; } @Override - public Film update(Film newFilm) { - return null; + public Film update(Film film) { + update(INSERT_QUERY, + film.getName(), + film.getDescription(), + film.getDuration(), + film.getReleaseDate(), + film.getMpa(), + film.getId() + ); + return film; } @Override public Film findFilmById(int id) { - return null; + return get(FIND_ALL_FILMS_QUERY + " WHERE f.FILM_ID = ?", id); } @Override public void remove(int id) { - + delete(DELETE_QUERY, id); } @Override public Collection getPopular(int count) { - return List.of(); + return jdbc.query(FIND_ALL_FILMS_QUERY + " ORDER BY PUBLIC.\"Likes\" DESC LIMIT ?", mapper, count); } @Override - public void addLike(int id, int userId) { + public Integer addLike(int id, int userId) { + String INSERT_QUERY = "INSERT INTO PUBLIC.\"Likes\"(FILM_ID, USER_ID) VALUES(?, ?)"; + String COUNT_QUERY = "SELECT COUNT(*) FROM PUBLIC.\"Likes\" WHERE film_id = ?"; + jdbc.update(INSERT_QUERY, id, userId); + return jdbc.queryForObject(COUNT_QUERY, Integer.class, id); } @Override - public void deleteLike(int id, int userId) { + public Integer deleteLike(int id, int userId) { + String DELETE_QUERY = "DELETE FROM PUBLIC.\"Likes\" WHERE USER_ID = ?"; + String COUNT_QUERY = "SELECT COUNT(*) FROM PUBLIC.\"Likes\" WHERE FILM_ID = ?"; + jdbc.update(DELETE_QUERY, userId); + return jdbc.queryForObject(COUNT_QUERY, Integer.class, id); } @Override public boolean contains(Integer id) { - return false; + return jdbc.queryForObject(EXISTS_QUERY, Boolean.class, id); } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dao/GenreDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dao/GenreDbStorage.java index 4d8aec3..8de0da0 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dao/GenreDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/GenreDbStorage.java @@ -1,5 +1,7 @@ package ru.yandex.practicum.filmorate.dao; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; import org.springframework.stereotype.Repository; import ru.yandex.practicum.filmorate.model.Film; import ru.yandex.practicum.filmorate.model.Genre; @@ -9,29 +11,45 @@ import java.util.List; @Repository("GenreDbStorage") -public class GenreDbStorage implements GenreStorage { +public class GenreDbStorage extends BaseDao implements GenreStorage { + + private static final String FIND_ALL_QUERY = "SELECT * FROM PUBLIC.\"Genre\""; + private static final String FIND_BY_ID_QUERY = "SELECT * FROM PUBLIC.\"Genre\" WHERE GENRE_ID = ?"; + private static final String ADD_GENRE_TO_FILM_QUERY = "INSERT INTO PUBLIC.\"Film_Genre\" (FILM_ID, GENRE_ID) " + + "VALUES (?, ?)"; + private static final String UPDATE_FILM_GENRE_QUERY = "DELETE FROM PUBLIC.\"Film_Genre\" WHERE FILM_ID = ?"; + + public GenreDbStorage(JdbcTemplate jdbc, RowMapper mapper) { + super(jdbc, mapper); + } + @Override public Collection findAll() { - return List.of(); + return getAll(FIND_ALL_QUERY); } @Override public Genre findGenreById(int id) { - return null; + return get(FIND_BY_ID_QUERY); } @Override public void addGenresToFilm(Film film) { - + if (film.getGenres() == null || film.getGenres().isEmpty()) { + return; + } + Integer filmId = film.getId(); + List batchArgs = film.getGenres() + .stream() + .map(genre -> new Object[]{filmId, genre.getId()}) + .toList(); + + jdbc.batchUpdate(ADD_GENRE_TO_FILM_QUERY, batchArgs); } @Override public void updateFilmGenres(Film film) { - - } - - @Override - public boolean contains(Integer id) { - return false; + jdbc.update(UPDATE_FILM_GENRE_QUERY, film.getId()); + addGenresToFilm(film); } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dao/RatingDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dao/RatingDbStorage.java index 5f5d539..4c758f4 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dao/RatingDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/RatingDbStorage.java @@ -1,26 +1,30 @@ package ru.yandex.practicum.filmorate.dao; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; import org.springframework.stereotype.Repository; import ru.yandex.practicum.filmorate.model.Rating; import ru.yandex.practicum.filmorate.storage.RatingStorage; -import java.util.Collection; import java.util.List; @Repository("RatingDbStorage") -public class RatingDbStorage implements RatingStorage { - @Override - public Collection findAll() { - return List.of(); +public class RatingDbStorage extends BaseDao implements RatingStorage { + + private static final String FIND_ALL_QUERY = "SELECT * FROM PUBLIC.\"Rating\""; + private static final String FIND_BY_ID_QUERY = "SELECT * FROM PUBLIC.\"Rating\" WHERE RATING_ID = ?"; + + public RatingDbStorage(JdbcTemplate jdbc, RowMapper mapper) { + super(jdbc, mapper); } @Override - public Rating findGenreById(int id) { - return null; + public List findAll() { + return getAll(FIND_ALL_QUERY); } @Override - public boolean contains(Integer id) { - return false; + public Rating findRatingById(int id) { + return get(FIND_BY_ID_QUERY); } } 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 dd7f8d8..7417f9c 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dao/UserDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/UserDbStorage.java @@ -7,66 +7,93 @@ import ru.yandex.practicum.filmorate.storage.UserStorage; import java.util.Collection; -import java.util.List; +import java.util.Set; @Repository("UserDbStorage") public class UserDbStorage extends BaseDao implements UserStorage { - private static final String FIND_ALL_QUERY = "SELECT * FROM PUBLIC.\"Users\""; - private static final String FIND_BY_EMAIL_QUERY = "SELECT * FROM PUBLIC.\"Users\" WHERE USER_ID = ?"; - private static final String INSERT_QUERY = "INSERT INTO users(username, email, password, registration_date)" + - "VALUES (?, ?, ?, ?) returning id"; + private static final String FIND_ALL_USERS_QUERY = "SELECT * FROM PUBLIC.\"Users\""; + private static final String FIND_USER_BY_ID_QUERY = "SELECT * FROM PUBLIC.\"Users\" WHERE USER_ID = ?"; + private static final String INSERT_QUERY = "INSERT INTO PUBLIC.\"Users\"(NAME, EMAIL, LOGIN, BIRTHDAY)" + + "VALUES (?, ?, ?, ?)"; + private static final String UPDATE_QUERY = "UPDATE PUBLIC.\"Users\" SET NAME = ?, EMAIL = ?, LOGIN = ?," + + "BIRTHDAY = ? WHERE USER_ID = ?"; + private static final String DELETE_QUERY = "DELETE FROM PUBLIC.\"Users\" WHERE USER_ID = ?"; + private static final String EXISTS = "SELECT EXISTS(SELECT 1 FROM PUBLIC.\"Users\" WHERE USER_ID = ?)"; + private static final String ADD_FRIEND_QUERY = "INSERT INTO PUBLIC.\"User_friends\"(USER_ID, FRIEND_ID) " + + "VALUES (?, ?)"; + private static final String DELETE_FRIEND_QUERY = "DELETE FROM PUBLIC.\"User_friends\" WHERE USER_ID = ? " + + "AND FRIEND_ID = ?"; + private static final String FIND_ALL_FRIENDS_QUERY = "SELECT FRIEND_ID FROM PUBLIC.\"User_friends\" " + + "WHERE USER_ID = ?"; + private static final String FIND_COMMON_FRIENDS_QUERY = "SELECT T1.FRIEND_ID FROM PUBLIC.\"User_friends\" AS T1 " + + "JOIN PUBLIC.\"User_friends\" AS T2 ON T1.FRIEND_ID = T2.FRIEND_ID WHERE T1.USER_ID = ? AND T2.USER_ID = ?"; + public UserDbStorage(JdbcTemplate jdbc, RowMapper mapper) { super(jdbc, mapper); } @Override public Collection findAll() { - return List.of(); + return getAll(FIND_ALL_USERS_QUERY); } @Override public User create(User user) { - return null; + int id = insert(INSERT_QUERY, + user.getName(), + user.getEmail(), + user.getLogin(), + user.getBirthday() + ); + user.setId(id); + return user; } @Override - public User update(User newUser) { - return null; + public User update(User user) { + update(UPDATE_QUERY, + user.getName(), + user.getEmail(), + user.getLogin(), + user.getBirthday(), + user.getId() + ); + return user; } @Override public User findUserById(int id) { - return null; + return get(FIND_USER_BY_ID_QUERY); } @Override public void remove(int id) { - + delete(DELETE_QUERY, id); } @Override public void addFriend(int userId, int friendId) { - + jdbc.update(ADD_FRIEND_QUERY, userId, friendId); } @Override public void deleteFriend(int userId, int friendId) { - + jdbc.update(DELETE_FRIEND_QUERY, userId, friendId); } @Override - public Collection getFriendList(int userId) { - return List.of(); + public Set getFriendList(int userId) { + return Set.copyOf(jdbc.queryForList(FIND_ALL_FRIENDS_QUERY, Integer.class, userId)); } @Override - public Collection getCommonFriendList(int id, int otherId) { - return List.of(); + public Set getCommonFriendList(int id, int otherId) { + return Set.copyOf(jdbc.queryForList(FIND_COMMON_FRIENDS_QUERY, Integer.class, id, otherId)); } @Override public boolean contains(Integer id) { - return false; + return jdbc.queryForObject(EXISTS, Boolean.class, id); } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/FilmRowMapper.java b/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/FilmRowMapper.java index 125d63f..173d810 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/FilmRowMapper.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/FilmRowMapper.java @@ -1,4 +1,58 @@ package ru.yandex.practicum.filmorate.dao.mappers; -public class FilmRowMapper { -} +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Component; +import ru.yandex.practicum.filmorate.model.Film; +import ru.yandex.practicum.filmorate.model.Genre; +import ru.yandex.practicum.filmorate.model.Rating; + +import java.sql.Array; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.HashSet; +import java.util.Set; + +@Component +public class FilmRowMapper implements RowMapper { + + @Override + public Film mapRow(ResultSet resultSet, int rowNum) throws SQLException { + Film film = new Film(); + film.setId(resultSet.getInt("FILM_ID")); + film.setName(resultSet.getString("NAME")); + film.setDescription(resultSet.getString("DESCRIPTION")); + film.setDuration(resultSet.getInt("DURATION")); + film.setReleaseDate(resultSet.getDate("RELEASE_DATE").toLocalDate()); + + mapRating(film, resultSet); + mapGenres(film, resultSet); + return film; + } + + private void mapRating(Film film, ResultSet resultSet) throws SQLException { + int mpaId = resultSet.getInt("RATING_ID"); + if (!resultSet.wasNull()) { + String ratingName = resultSet.getString("NAME"); + film.setMpa(new Rating(mpaId, ratingName)); + } + } + + private void mapGenres(Film film, ResultSet resultSet) throws SQLException { + Set genres = new HashSet<>(); + Array genresId = resultSet.getArray("GENRE_ID"); + if (resultSet.wasNull()) { + return; + } + Array genresName = resultSet.getArray("NAME"); + Integer[] ids = (Integer[]) genresId.getArray(); + String[] names = (String[]) genresName.getArray(); + for (int i = 0; i < ids.length; i++) { + if (ids[i] != null && names[i] != null) { + Integer id = ids[i]; + String name = names[i]; + genres.add(new Genre(id, name)); + } + } + film.setGenres(genres); + } +} \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/GenreRowMapper.java b/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/GenreRowMapper.java index 3186372..5d30a4e 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/GenreRowMapper.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/GenreRowMapper.java @@ -1,16 +1,20 @@ package ru.yandex.practicum.filmorate.dao.mappers; import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Component; import ru.yandex.practicum.filmorate.model.Genre; -import ru.yandex.practicum.filmorate.model.User; import java.sql.ResultSet; import java.sql.SQLException; +@Component public class GenreRowMapper implements RowMapper { @Override - public Genre mapRow(ResultSet rs, int rowNum) throws SQLException { - return null; + public Genre mapRow(ResultSet resultSet, int rowNum) throws SQLException { + Genre genre = new Genre(); + genre.setId(resultSet.getInt("GENRE_ID")); + genre.setName(resultSet.getString("NAME")); + return genre; } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/RatingRowMapper.java b/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/RatingRowMapper.java index db61361..87e3779 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/RatingRowMapper.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/RatingRowMapper.java @@ -1,4 +1,23 @@ package ru.yandex.practicum.filmorate.dao.mappers; -public class RatingRowMapper { +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Component; +import ru.yandex.practicum.filmorate.model.Genre; +import ru.yandex.practicum.filmorate.model.Rating; + +import java.sql.ResultSet; +import java.sql.SQLException; + +@Component +public class RatingRowMapper implements RowMapper { + + Genre genre = new Genre(); + + @Override + public Rating mapRow(ResultSet resultSet, int rowNum) throws SQLException { + Rating rating = new Rating(); + rating.setId(resultSet.getInt("RATING_ID")); + rating.setName(resultSet.getString("NAME")); + return rating; + } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/UserRowMapper.java b/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/UserRowMapper.java index 2907c59..3afb937 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/UserRowMapper.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/UserRowMapper.java @@ -6,7 +6,6 @@ import java.sql.ResultSet; import java.sql.SQLException; -import java.sql.Timestamp; @Component public class UserRowMapper implements RowMapper { diff --git a/src/main/java/ru/yandex/practicum/filmorate/exception/InternalServerException.java b/src/main/java/ru/yandex/practicum/filmorate/exception/InternalServerException.java new file mode 100644 index 0000000..86a5c20 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/exception/InternalServerException.java @@ -0,0 +1,6 @@ +package ru.yandex.practicum.filmorate.exception; + +public class InternalServerException extends Throwable { + public InternalServerException(String неУдалосьСохранитьДанные) { + } +} 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 95ca381..cf1888c 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/model/Film.java +++ b/src/main/java/ru/yandex/practicum/filmorate/model/Film.java @@ -6,9 +6,6 @@ import java.util.HashSet; import java.util.Set; -/** - * Film. - */ @Data public class Film { private Integer id; @@ -17,11 +14,11 @@ public class Film { private LocalDate releaseDate; private int duration; private Rating mpa; - private Set likes = new HashSet<>(); - private Set genres = new HashSet<>(); + private Integer likes = 0; + private Set genres = new HashSet<>(); - public int getRating() { - return likes.size(); + public int getAllLikes() { + return likes; } } \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/Genre.java b/src/main/java/ru/yandex/practicum/filmorate/model/Genre.java index b05da8c..d39eaef 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/model/Genre.java +++ b/src/main/java/ru/yandex/practicum/filmorate/model/Genre.java @@ -1,9 +1,13 @@ package ru.yandex.practicum.filmorate.model; import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; @Data +@AllArgsConstructor +@NoArgsConstructor public class Genre { @NotNull private Integer id; diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/Rating.java b/src/main/java/ru/yandex/practicum/filmorate/model/Rating.java index 0a3e66c..9d771ea 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/model/Rating.java +++ b/src/main/java/ru/yandex/practicum/filmorate/model/Rating.java @@ -1,9 +1,13 @@ package ru.yandex.practicum.filmorate.model; import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; @Data +@AllArgsConstructor +@NoArgsConstructor public class Rating { @NotNull private Integer id; 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 4f24ba3..74ed438 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/service/FilmService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/FilmService.java @@ -4,14 +4,11 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; -import ru.yandex.practicum.filmorate.dao.FilmDbStorage; import ru.yandex.practicum.filmorate.exception.NotFoundException; import ru.yandex.practicum.filmorate.exception.ValidationException; 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.storage.GenreStorage; -import ru.yandex.practicum.filmorate.storage.RatingStorage; import ru.yandex.practicum.filmorate.storage.UserStorage; import java.time.LocalDate; @@ -78,22 +75,19 @@ public void remove(int id) { public void addLike(int id, int userId) { Film film = filmStorage.findFilmById(id); - userStorage.findUserById(userId); - if (film.getLikes().contains(userId)) { - log.warn("Ошибка добавления лайка"); - throw new ValidationException("Лайк от этого пользователя уже стоит"); + if (!userStorage.contains(userId)) { + throw new NotFoundException("Пользователь с id = " + userId + " не найден"); } - film.getLikes().add(userId); + filmStorage.addLike(id, userId); } public void deleteLike(int id, int userId) { Film film = filmStorage.findFilmById(id); userStorage.findUserById(userId); - if (!film.getLikes().contains(userId)) { - log.warn("Ошибка удаления лайка"); - throw new ValidationException("У фильма нет лайка от этого пользователя"); + if (!userStorage.contains(userId)) { + throw new NotFoundException("Пользователь с id = " + userId + " не найден"); } - film.getLikes().remove(userId); + filmStorage.deleteLike(id, userId); } public Collection getPopular(int count) { @@ -124,5 +118,5 @@ private void validateFilm(Film film) { } } - public static final Comparator comparator = Comparator.comparingInt(Film::getRating).reversed(); + public static final Comparator comparator = Comparator.comparingInt(Film::getAllLikes).reversed(); } \ 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 index d06c84b..2c827f2 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/service/GenreService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/GenreService.java @@ -1,14 +1,11 @@ package ru.yandex.practicum.filmorate.service; -import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; -import ru.yandex.practicum.filmorate.exception.NotFoundException; import ru.yandex.practicum.filmorate.model.Genre; import ru.yandex.practicum.filmorate.storage.GenreStorage; import java.util.Collection; -@Slf4j @Service public class GenreService { @@ -23,10 +20,6 @@ public Collection findAll() { } public Genre findGenreById(int id) { - if (!genreStorage.contains(id)) { - log.warn("Жанр не найден"); - throw new NotFoundException("Жанра с таким id не найдено"); - } return genreStorage.findGenreById(id); } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/RatingService.java b/src/main/java/ru/yandex/practicum/filmorate/service/RatingService.java index 6038228..a9f7f1a 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/service/RatingService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/RatingService.java @@ -1,14 +1,11 @@ package ru.yandex.practicum.filmorate.service; -import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; -import ru.yandex.practicum.filmorate.exception.NotFoundException; import ru.yandex.practicum.filmorate.model.Rating; import ru.yandex.practicum.filmorate.storage.RatingStorage; import java.util.Collection; -@Slf4j @Service public class RatingService { @@ -23,10 +20,6 @@ public Collection findAll() { } public Rating findRatingById(int id) { - if (!ratingStorage.contains(id)) { - log.warn("Рейтинг не найден"); - throw new NotFoundException("Рейтинга с таким id не найдено"); - } - return ratingStorage.findGenreById(id); + return ratingStorage.findRatingById(id); } } \ No newline at end of file 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 dd47500..2296e6c 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/storage/FilmStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/FilmStorage.java @@ -18,9 +18,9 @@ public interface FilmStorage { Collection getPopular(int count); - void addLike(int id, int userId); + Integer addLike(int id, int userId); - void deleteLike(int id, int userId); + Integer deleteLike(int id, int userId); boolean contains(Integer id); } \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/GenreStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/GenreStorage.java index 471d042..6ad3471 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/storage/GenreStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/GenreStorage.java @@ -14,6 +14,4 @@ public interface GenreStorage { void addGenresToFilm(Film film); void updateFilmGenres(Film film); - - boolean contains(Integer id); } diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryFilmStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryFilmStorage.java index 28902f6..770cb7e 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryFilmStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryFilmStorage.java @@ -66,13 +66,15 @@ public Collection getPopular(int count) { } @Override - public void addLike(int id, int userId) { + public Integer addLike(int id, int userId) { + return null; } @Override - public void deleteLike(int id, int userId) { + public Integer deleteLike(int id, int userId) { + return null; } @Override diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryUserStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryUserStorage.java index 32be427..bad937a 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryUserStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryUserStorage.java @@ -7,10 +7,7 @@ import ru.yandex.practicum.filmorate.model.User; import java.time.LocalDate; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; @Slf4j @Component("inMemoryUserStorage") @@ -83,13 +80,13 @@ public void deleteFriend(int userId, int friendId) { } @Override - public Collection getFriendList(int userId) { - return List.of(); + public Set getFriendList(int userId) { + return Set.of(); } @Override - public Collection getCommonFriendList(int id, int otherId) { - return List.of(); + public Set getCommonFriendList(int id, int otherId) { + return Set.of(); } @Override diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/RatingStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/RatingStorage.java index 6786921..ff26631 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/storage/RatingStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/RatingStorage.java @@ -8,7 +8,5 @@ public interface RatingStorage { Collection findAll(); - Rating findGenreById(int id); - - boolean contains(Integer id); + Rating findRatingById(int id); } 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 ae1cea1..e15ecfe 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/storage/UserStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/UserStorage.java @@ -20,9 +20,9 @@ public interface UserStorage { void deleteFriend(int userId, int friendId); - Collection getFriendList(int userId); + Collection getFriendList(int userId); - Collection getCommonFriendList(int id, int otherId); + Collection getCommonFriendList(int id, int otherId); boolean contains(Integer id); } \ No newline at end of file From 4dac19369d90146788e2f5208dde31bf541240cc Mon Sep 17 00:00:00 2001 From: Denis Panasyuk Date: Sun, 30 Nov 2025 22:14:26 +0300 Subject: [PATCH 06/12] =?UTF-8?q?=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D0=BD=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8?= =?UTF-8?q?=D0=BE=D0=BD=D0=B0=D0=BB=20=D1=81=D0=BE=D0=B3=D0=BB=D0=B0=D1=81?= =?UTF-8?q?=D0=BD=D0=BE=20=D0=A4=D0=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/ru/yandex/practicum/filmorate/dao/BaseDao.java | 2 +- .../ru/yandex/practicum/filmorate/dao/FilmDbStorage.java | 2 +- .../ru/yandex/practicum/filmorate/dao/GenreDbStorage.java | 2 +- .../ru/yandex/practicum/filmorate/dao/RatingDbStorage.java | 2 +- .../ru/yandex/practicum/filmorate/dao/UserDbStorage.java | 2 +- .../practicum/filmorate/dao/mappers/RatingRowMapper.java | 2 +- .../practicum/filmorate/dao/mappers/UserRowMapper.java | 2 +- .../filmorate/exception/InternalServerException.java | 5 +++-- src/main/java/ru/yandex/practicum/filmorate/model/Film.java | 1 - src/main/java/ru/yandex/practicum/filmorate/model/Genre.java | 2 +- .../java/ru/yandex/practicum/filmorate/model/Rating.java | 2 +- 11 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/main/java/ru/yandex/practicum/filmorate/dao/BaseDao.java b/src/main/java/ru/yandex/practicum/filmorate/dao/BaseDao.java index 4a640e2..e597cab 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dao/BaseDao.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/BaseDao.java @@ -65,4 +65,4 @@ public int insert(String query, Object... params) { } } } -} +} \ No newline at end of file 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 12e6137..387dda0 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dao/FilmDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/FilmDbStorage.java @@ -97,4 +97,4 @@ public Integer deleteLike(int id, int userId) { public boolean contains(Integer id) { return jdbc.queryForObject(EXISTS_QUERY, Boolean.class, id); } -} +} \ 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 index 8de0da0..7ebbefb 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dao/GenreDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/GenreDbStorage.java @@ -52,4 +52,4 @@ public void updateFilmGenres(Film film) { jdbc.update(UPDATE_FILM_GENRE_QUERY, film.getId()); addGenresToFilm(film); } -} +} \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/dao/RatingDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dao/RatingDbStorage.java index 4c758f4..0944985 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dao/RatingDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/RatingDbStorage.java @@ -27,4 +27,4 @@ public List findAll() { public Rating findRatingById(int id) { return get(FIND_BY_ID_QUERY); } -} +} \ No newline at end of file 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 7417f9c..6ac84d5 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dao/UserDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/UserDbStorage.java @@ -96,4 +96,4 @@ public Set getCommonFriendList(int id, int otherId) { public boolean contains(Integer id) { return jdbc.queryForObject(EXISTS, Boolean.class, id); } -} +} \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/RatingRowMapper.java b/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/RatingRowMapper.java index 87e3779..9755585 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/RatingRowMapper.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/RatingRowMapper.java @@ -20,4 +20,4 @@ public Rating mapRow(ResultSet resultSet, int rowNum) throws SQLException { rating.setName(resultSet.getString("NAME")); return rating; } -} +} \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/UserRowMapper.java b/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/UserRowMapper.java index 3afb937..f1f1524 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/UserRowMapper.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/UserRowMapper.java @@ -21,4 +21,4 @@ public User mapRow(ResultSet resultSet, int rowNum) throws SQLException { return user; } -} +} \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/exception/InternalServerException.java b/src/main/java/ru/yandex/practicum/filmorate/exception/InternalServerException.java index 86a5c20..4ca17a6 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/exception/InternalServerException.java +++ b/src/main/java/ru/yandex/practicum/filmorate/exception/InternalServerException.java @@ -1,6 +1,7 @@ package ru.yandex.practicum.filmorate.exception; public class InternalServerException extends Throwable { - public InternalServerException(String неУдалосьСохранитьДанные) { + public InternalServerException(String message) { + super(message); } -} +} \ 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 cf1888c..2de537b 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/model/Film.java +++ b/src/main/java/ru/yandex/practicum/filmorate/model/Film.java @@ -17,7 +17,6 @@ public class Film { private Integer likes = 0; private Set genres = new HashSet<>(); - public int getAllLikes() { return likes; } diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/Genre.java b/src/main/java/ru/yandex/practicum/filmorate/model/Genre.java index d39eaef..7f3b4f0 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/model/Genre.java +++ b/src/main/java/ru/yandex/practicum/filmorate/model/Genre.java @@ -12,4 +12,4 @@ public class Genre { @NotNull private Integer id; private String name; -} +} \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/Rating.java b/src/main/java/ru/yandex/practicum/filmorate/model/Rating.java index 9d771ea..953fc70 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/model/Rating.java +++ b/src/main/java/ru/yandex/practicum/filmorate/model/Rating.java @@ -12,4 +12,4 @@ public class Rating { @NotNull private Integer id; private String name; -} +} \ No newline at end of file From 1ab6fde864c27f2198c8c868ba4306dc40eaec2e Mon Sep 17 00:00:00 2001 From: Denis Panasyuk Date: Sun, 14 Dec 2025 19:55:13 +0300 Subject: [PATCH 07/12] =?UTF-8?q?=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D0=BD=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8?= =?UTF-8?q?=D0=BE=D0=BD=D0=B0=D0=BB=20=D1=81=D0=BE=D0=B3=D0=BB=D0=B0=D1=81?= =?UTF-8?q?=D0=BD=D0=BE=20=D0=A4=D0=97=20=D0=B8=20=D1=82=D0=B5=D1=81=D1=82?= =?UTF-8?q?=D0=B0=D0=BC=20=D0=B2=20=D0=BF=D0=BE=D1=81=D1=82=D0=BC=D0=B0?= =?UTF-8?q?=D0=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 1 - .../filmorate/controller/FilmController.java | 9 +- .../practicum/filmorate/dao/BaseDao.java | 8 ++ .../filmorate/dao/FilmDbStorage.java | 118 +++++++++++++----- .../filmorate/dao/GenreDbStorage.java | 31 +++-- .../filmorate/dao/RatingDbStorage.java | 19 ++- .../filmorate/dao/UserDbStorage.java | 86 +++++++++---- .../filmorate/dao/mappers/FilmRowMapper.java | 35 +++--- .../filmorate/dao/mappers/GenreRowMapper.java | 4 +- .../dao/mappers/RatingRowMapper.java | 4 +- .../filmorate/dao/mappers/UserRowMapper.java | 10 +- .../practicum/filmorate/model/Film.java | 6 +- .../practicum/filmorate/model/Genre.java | 1 + .../filmorate/service/FilmService.java | 35 +++--- .../filmorate/service/GenreService.java | 4 +- .../filmorate/service/RatingService.java | 4 +- .../filmorate/service/UserService.java | 59 +++++---- .../filmorate/storage/FilmStorage.java | 2 +- .../filmorate/storage/GenreStorage.java | 3 +- .../storage/InMemoryFilmStorage.java | 3 +- .../storage/InMemoryUserStorage.java | 4 +- .../filmorate/storage/RatingStorage.java | 3 +- .../filmorate/storage/UserStorage.java | 4 +- src/main/resources/data.sql | 18 +-- src/main/resources/schema.sql | 78 ++++++------ 25 files changed, 333 insertions(+), 216 deletions(-) diff --git a/pom.xml b/pom.xml index 4e4a811..018c5fa 100644 --- a/pom.xml +++ b/pom.xml @@ -58,7 +58,6 @@ com.h2database h2 - 2.1.210 runtime diff --git a/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java b/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java index 52f859f..77749c9 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java @@ -5,7 +5,6 @@ import org.springframework.web.bind.annotation.*; import ru.yandex.practicum.filmorate.model.Film; import ru.yandex.practicum.filmorate.service.FilmService; -import ru.yandex.practicum.filmorate.storage.FilmStorage; import java.util.Collection; @@ -52,14 +51,14 @@ public Film update(@RequestBody Film newFilm) { } @PutMapping("/{id}/like/{userId}") - public void addLike(@PathVariable int id, @PathVariable int userId) { + public Film addLike(@PathVariable int id, @PathVariable int userId) { log.info("Фильму с id: {} поставил лайк пользователь с id: {}", id, userId); - service.addLike(id, userId); + return service.addLike(id, userId); } @DeleteMapping("/{id}/like/{userId}") - public void deleteLike(@PathVariable int id, @PathVariable int userId) { + public Film deleteLike(@PathVariable int id, @PathVariable int userId) { log.info("У фильма с id: {} убрал лайк пользователь с id: {}", id, userId); - service.deleteLike(id, userId); + return service.deleteLike(id, userId); } } \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/dao/BaseDao.java b/src/main/java/ru/yandex/practicum/filmorate/dao/BaseDao.java index e597cab..e5a10d9 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dao/BaseDao.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/BaseDao.java @@ -2,6 +2,7 @@ import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.jdbc.support.GeneratedKeyHolder; import org.springframework.stereotype.Repository; import ru.yandex.practicum.filmorate.exception.InternalServerException; @@ -9,14 +10,17 @@ import java.sql.PreparedStatement; import java.sql.Statement; import java.util.List; +import java.util.Map; @Repository public abstract class BaseDao { protected final JdbcTemplate jdbc; + protected final NamedParameterJdbcTemplate namedJdbc; protected final RowMapper mapper; public BaseDao(JdbcTemplate jdbc, RowMapper mapper) { this.jdbc = jdbc; + this.namedJdbc = new NamedParameterJdbcTemplate(jdbc); this.mapper = mapper; } @@ -43,6 +47,10 @@ public void update(String query, Object...params){ } } + public void update(String sql, Map map) { + namedJdbc.update(sql, map); + } + public int insert(String query, Object... params) { GeneratedKeyHolder keyHolder = new GeneratedKeyHolder(); jdbc.update(connection -> { 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 387dda0..03a146a 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dao/FilmDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/FilmDbStorage.java @@ -1,31 +1,70 @@ package ru.yandex.practicum.filmorate.dao; import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.core.RowMapper; import org.springframework.stereotype.Repository; +import ru.yandex.practicum.filmorate.dao.mappers.FilmRowMapper; +import ru.yandex.practicum.filmorate.exception.NotFoundException; 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.storage.RatingStorage; -import java.util.Collection; +import java.util.*; +import java.util.stream.Collectors; @Repository("FilmDbStorage") public class FilmDbStorage extends BaseDao implements FilmStorage { - private static final String FIND_ALL_FILMS_QUERY = "SELECT f.FILM_ID, f.NAME, f.DESCRIPTION, f.RELEASE_DATE, " + - "f.DURATION, r.NAME AS RATING_NAME, GROUP_CONCAT(g.NAME) AS GENRES, COUNT(l.USER_ID) AS LIKES_COUNT " + - "FROM PUBLIC.\"Film\" f LEFT JOIN PUBLIC.\"Rating\" r ON f.RATING_ID = r.RATING_ID LEFT " + - "JOIN PUBLIC.\"Film_genre\" fg ON f.FILM_ID = fg.FILM_ID LEFT JOIN PUBLIC.\"Genre\" g " + - "ON fg.GENRE_ID = g.GENRE_ID LEFT JOIN PUBLIC.\"Likes\" l ON f.FILM_ID = l.FILM_ID"; - private static final String INSERT_QUERY = "INSERT INTO PUBLIC.\"Film\" (NAME, DESCRIPTION, RELEASE_DATE, " + - "DURATION, RATING_ID) VALUES (?, ?, ?, ?, ?)"; - private static final String GROUP_BY = "GROUP BY f.FILM_ID"; - private static final String DELETE_QUERY = "DELETE FROM PUBLIC.\"Film\" WHERE FILM_ID = ?"; - private static final String UPDATE_QUERY = "UPDATE PUBLIC.\"Film\" SET NAME = :NAME, DESCRIPTION = :DESCRIPTION, " + - "RELEASE_DATE = :RELEASE_DATE, DURATION = :DURATION, RATING_ID = :RATING_ID WHERE FILM_ID = :FILM_ID"; - private static final String EXISTS_QUERY = "SELECT EXISTS(SELECT 1 FROM PUBLIC.\"Film\" WHERE FILM_ID = ?)"; - - public FilmDbStorage(JdbcTemplate jdbc, RowMapper mapper) { + private static final String SELECT_ALL_FIELDS = """ + SELECT f.*, + COUNT(l.film_id) AS likes, + r.mpa_id, + r.name AS mpa_name, + ARRAY_AGG(g.genre_id) AS genre_ids, + ARRAY_AGG(g.name) AS genre_names + """; + + private static final String JOIN_ALL_TABLES = """ + FROM films AS f + LEFT JOIN mpa AS r ON f.mpa_id = r.mpa_id + LEFT JOIN likes AS l ON f.film_id = l.film_id + LEFT JOIN films_genres AS fg ON f.film_id = fg.film_id + LEFT JOIN genres AS g ON fg.genre_id = g.genre_id + """; + + private static final String GROUP_BY = "GROUP BY f.film_id"; + + private static final String INSERT_QUERY = """ + INSERT INTO films (name, description, release_date, duration, mpa_id) + VALUES (?, ?, ?, ?, ?) + """; + + private static final String DELETE_QUERY = "DELETE FROM films WHERE film_id = ?"; + + private static final String UPDATE_QUERY = """ + UPDATE films SET + name = ?, + description = ?, + release_date = ?, + duration = ?, + mpa_id = ? + WHERE film_id = ? + """; + + private static final String EXISTS_QUERY = "SELECT EXISTS(SELECT 1 FROM films WHERE film_id = ?)"; + + private static final String FIND_FILM_BY_ID_QUERY = String.format("%s %s WHERE f.film_id = ? %s", + SELECT_ALL_FIELDS, JOIN_ALL_TABLES, GROUP_BY); + + private static final String FIND_ALL_FILMS_QUERY = String.format("%s %s %s", + SELECT_ALL_FIELDS, JOIN_ALL_TABLES, GROUP_BY); + + private final GenreDbStorage genreDbStorage; + private final RatingStorage ratingStorage; + public FilmDbStorage(JdbcTemplate jdbc, FilmRowMapper mapper, GenreDbStorage genreDbStorage, RatingStorage ratingStorage) { super(jdbc, mapper); + this.genreDbStorage = genreDbStorage; + this.ratingStorage = ratingStorage; } @Override @@ -35,26 +74,40 @@ public Collection findAll() { @Override public Film create(Film film) { - int id = insert( - INSERT_QUERY, + if (ratingStorage.findRatingById(film.getMpa().getId()).isEmpty()) { + throw new NotFoundException("Рейтинг с таким id " + film.getMpa().getId() + " не найден"); + } + + Set ids = genreDbStorage.findAll().stream() + .map(Genre::getId) + .collect(Collectors.toSet()); + + for (Genre genre : film.getGenres()) { + if (!ids.contains(genre.getId())) { + throw new NotFoundException("Жанр с id=" + genre.getId() + " не найден"); + } + } + + int id = insert(INSERT_QUERY, film.getName(), film.getDescription(), - film.getDuration(), film.getReleaseDate(), - film.getMpa() + film.getDuration(), + film.getMpa().getId() ); film.setId(id); + return film; } @Override public Film update(Film film) { - update(INSERT_QUERY, + update(UPDATE_QUERY, film.getName(), film.getDescription(), - film.getDuration(), film.getReleaseDate(), - film.getMpa(), + film.getDuration(), + film.getMpa().getId(), film.getId() ); return film; @@ -62,23 +115,26 @@ public Film update(Film film) { @Override public Film findFilmById(int id) { - return get(FIND_ALL_FILMS_QUERY + " WHERE f.FILM_ID = ?", id); + return get(FIND_FILM_BY_ID_QUERY, id); + } @Override - public void remove(int id) { + public Film remove(int id) { + Film film = findFilmById(id); delete(DELETE_QUERY, id); + return film; } @Override public Collection getPopular(int count) { - return jdbc.query(FIND_ALL_FILMS_QUERY + " ORDER BY PUBLIC.\"Likes\" DESC LIMIT ?", mapper, count); + return jdbc.query(FIND_ALL_FILMS_QUERY + " ORDER BY likes DESC LIMIT ?", mapper, count); } @Override public Integer addLike(int id, int userId) { - String INSERT_QUERY = "INSERT INTO PUBLIC.\"Likes\"(FILM_ID, USER_ID) VALUES(?, ?)"; - String COUNT_QUERY = "SELECT COUNT(*) FROM PUBLIC.\"Likes\" WHERE film_id = ?"; + String INSERT_QUERY = "INSERT INTO likes (film_id, user_id) VALUES(?, ?)"; + String COUNT_QUERY = "SELECT COUNT(*) FROM likes WHERE film_id = ?"; jdbc.update(INSERT_QUERY, id, userId); return jdbc.queryForObject(COUNT_QUERY, Integer.class, id); @@ -86,9 +142,9 @@ public Integer addLike(int id, int userId) { @Override public Integer deleteLike(int id, int userId) { - String DELETE_QUERY = "DELETE FROM PUBLIC.\"Likes\" WHERE USER_ID = ?"; - String COUNT_QUERY = "SELECT COUNT(*) FROM PUBLIC.\"Likes\" WHERE FILM_ID = ?"; - jdbc.update(DELETE_QUERY, userId); + String DELETE_QUERY = "DELETE FROM likes WHERE film_id = ? AND user_id = ?"; + String COUNT_QUERY = "SELECT COUNT(*) FROM likes WHERE film_id = ?"; + jdbc.update(DELETE_QUERY,id, userId); return jdbc.queryForObject(COUNT_QUERY, Integer.class, id); } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dao/GenreDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dao/GenreDbStorage.java index 7ebbefb..d5c13ab 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dao/GenreDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/GenreDbStorage.java @@ -1,25 +1,29 @@ package ru.yandex.practicum.filmorate.dao; +import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.core.RowMapper; import org.springframework.stereotype.Repository; +import ru.yandex.practicum.filmorate.dao.mappers.GenreRowMapper; import ru.yandex.practicum.filmorate.model.Film; import ru.yandex.practicum.filmorate.model.Genre; import ru.yandex.practicum.filmorate.storage.GenreStorage; import java.util.Collection; import java.util.List; +import java.util.Optional; @Repository("GenreDbStorage") public class GenreDbStorage extends BaseDao implements GenreStorage { - private static final String FIND_ALL_QUERY = "SELECT * FROM PUBLIC.\"Genre\""; - private static final String FIND_BY_ID_QUERY = "SELECT * FROM PUBLIC.\"Genre\" WHERE GENRE_ID = ?"; - private static final String ADD_GENRE_TO_FILM_QUERY = "INSERT INTO PUBLIC.\"Film_Genre\" (FILM_ID, GENRE_ID) " + - "VALUES (?, ?)"; - private static final String UPDATE_FILM_GENRE_QUERY = "DELETE FROM PUBLIC.\"Film_Genre\" WHERE FILM_ID = ?"; + private static final String FIND_ALL_QUERY = "SELECT * FROM genres"; + private static final String FIND_BY_ID_QUERY = "SELECT * FROM genres WHERE genre_id = ?"; + private static final String ADD_GENRE_TO_FILM_QUERY = """ + INSERT INTO films_genres (film_id, genre_id) + VALUES (?, ?) + """; + private static final String UPDATE_FILM_GENRE_QUERY = "DELETE FROM films_genres WHERE film_id = ?"; - public GenreDbStorage(JdbcTemplate jdbc, RowMapper mapper) { + public GenreDbStorage(JdbcTemplate jdbc, GenreRowMapper mapper) { super(jdbc, mapper); } @@ -29,16 +33,19 @@ public Collection findAll() { } @Override - public Genre findGenreById(int id) { - return get(FIND_BY_ID_QUERY); + public Optional findGenreById(int id) { + try { + Genre genre = get(FIND_BY_ID_QUERY, id); + return Optional.ofNullable(genre); + } catch (EmptyResultDataAccessException e) { + return Optional.empty(); + } } @Override public void addGenresToFilm(Film film) { - if (film.getGenres() == null || film.getGenres().isEmpty()) { - return; - } Integer filmId = film.getId(); + List batchArgs = film.getGenres() .stream() .map(genre -> new Object[]{filmId, genre.getId()}) diff --git a/src/main/java/ru/yandex/practicum/filmorate/dao/RatingDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dao/RatingDbStorage.java index 0944985..8ffe702 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dao/RatingDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/RatingDbStorage.java @@ -1,20 +1,22 @@ package ru.yandex.practicum.filmorate.dao; +import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.core.RowMapper; import org.springframework.stereotype.Repository; +import ru.yandex.practicum.filmorate.dao.mappers.RatingRowMapper; import ru.yandex.practicum.filmorate.model.Rating; import ru.yandex.practicum.filmorate.storage.RatingStorage; import java.util.List; +import java.util.Optional; @Repository("RatingDbStorage") public class RatingDbStorage extends BaseDao implements RatingStorage { - private static final String FIND_ALL_QUERY = "SELECT * FROM PUBLIC.\"Rating\""; - private static final String FIND_BY_ID_QUERY = "SELECT * FROM PUBLIC.\"Rating\" WHERE RATING_ID = ?"; + private static final String FIND_ALL_QUERY = "SELECT * FROM mpa"; + private static final String FIND_BY_ID_QUERY = "SELECT * FROM mpa WHERE mpa_id = ?"; - public RatingDbStorage(JdbcTemplate jdbc, RowMapper mapper) { + public RatingDbStorage(JdbcTemplate jdbc, RatingRowMapper mapper) { super(jdbc, mapper); } @@ -24,7 +26,12 @@ public List findAll() { } @Override - public Rating findRatingById(int id) { - return get(FIND_BY_ID_QUERY); + public Optional findRatingById(int id) { + try { + Rating rating = get(FIND_BY_ID_QUERY, id); + return Optional.ofNullable(rating); + } catch (EmptyResultDataAccessException e) { + return Optional.empty(); + } } } \ No newline at end of file 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 6ac84d5..5de2c85 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dao/UserDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/UserDbStorage.java @@ -1,36 +1,66 @@ package ru.yandex.practicum.filmorate.dao; import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.stereotype.Repository; +import ru.yandex.practicum.filmorate.dao.mappers.UserRowMapper; import ru.yandex.practicum.filmorate.model.User; import ru.yandex.practicum.filmorate.storage.UserStorage; import java.util.Collection; +import java.util.Collections; +import java.util.List; import java.util.Set; @Repository("UserDbStorage") public class UserDbStorage extends BaseDao implements UserStorage { - private static final String FIND_ALL_USERS_QUERY = "SELECT * FROM PUBLIC.\"Users\""; - private static final String FIND_USER_BY_ID_QUERY = "SELECT * FROM PUBLIC.\"Users\" WHERE USER_ID = ?"; - private static final String INSERT_QUERY = "INSERT INTO PUBLIC.\"Users\"(NAME, EMAIL, LOGIN, BIRTHDAY)" + - "VALUES (?, ?, ?, ?)"; - private static final String UPDATE_QUERY = "UPDATE PUBLIC.\"Users\" SET NAME = ?, EMAIL = ?, LOGIN = ?," + - "BIRTHDAY = ? WHERE USER_ID = ?"; - private static final String DELETE_QUERY = "DELETE FROM PUBLIC.\"Users\" WHERE USER_ID = ?"; - private static final String EXISTS = "SELECT EXISTS(SELECT 1 FROM PUBLIC.\"Users\" WHERE USER_ID = ?)"; - private static final String ADD_FRIEND_QUERY = "INSERT INTO PUBLIC.\"User_friends\"(USER_ID, FRIEND_ID) " + - "VALUES (?, ?)"; - private static final String DELETE_FRIEND_QUERY = "DELETE FROM PUBLIC.\"User_friends\" WHERE USER_ID = ? " + - "AND FRIEND_ID = ?"; - private static final String FIND_ALL_FRIENDS_QUERY = "SELECT FRIEND_ID FROM PUBLIC.\"User_friends\" " + - "WHERE USER_ID = ?"; - private static final String FIND_COMMON_FRIENDS_QUERY = "SELECT T1.FRIEND_ID FROM PUBLIC.\"User_friends\" AS T1 " + - "JOIN PUBLIC.\"User_friends\" AS T2 ON T1.FRIEND_ID = T2.FRIEND_ID WHERE T1.USER_ID = ? AND T2.USER_ID = ?"; - - public UserDbStorage(JdbcTemplate jdbc, RowMapper mapper) { + private static final String FIND_ALL_USERS_QUERY = "SELECT * FROM users"; + + private static final String FIND_USER_BY_ID_QUERY = "SELECT * FROM users WHERE user_id = ?"; + + private static final String INSERT_QUERY = """ + INSERT INTO users (name, email, login, birthday) + VALUES (?, ?, ?, ?) + """; + + private static final String UPDATE_QUERY = """ + UPDATE users SET + name = ?, + email = ?, + login = ?, + birthday = ? + WHERE user_id = ? + """; + + private static final String DELETE_QUERY = "DELETE FROM users WHERE user_id = ?"; + + private static final String EXISTS = "SELECT EXISTS(SELECT 1 FROM users WHERE user_id = ?)"; + + private static final String ADD_FRIEND_QUERY = """ + INSERT INTO user_friends (user_id, friend_id) + VALUES (?, ?) + """; + + private static final String DELETE_FRIEND_QUERY = "DELETE FROM user_friends WHERE user_id = ? AND friend_id = ?"; + + private static final String FIND_ALL_FRIENDS_QUERY = "SELECT friend_id FROM user_friends WHERE user_id = ?"; + + private static final String FIND_COMMON_FRIENDS_QUERY = """ + SELECT T1.friend_id + FROM user_friends AS T1 + JOIN user_friends AS T2 ON T1.friend_id = T2.friend_id + WHERE T1.user_id = ? AND T2.user_id = ? + """; + + private static final String SELECT_ALL_FROM_COLLECTION = "SELECT * FROM users WHERE user_id IN (:set)"; + + protected final NamedParameterJdbcTemplate namedJdbc; + + public UserDbStorage(JdbcTemplate jdbc, UserRowMapper mapper, NamedParameterJdbcTemplate namedJdbc) { super(jdbc, mapper); + + this.namedJdbc = namedJdbc; } @Override @@ -64,7 +94,7 @@ public User update(User user) { @Override public User findUserById(int id) { - return get(FIND_USER_BY_ID_QUERY); + return get(FIND_USER_BY_ID_QUERY, id); } @Override @@ -83,13 +113,21 @@ public void deleteFriend(int userId, int friendId) { } @Override - public Set getFriendList(int userId) { - return Set.copyOf(jdbc.queryForList(FIND_ALL_FRIENDS_QUERY, Integer.class, userId)); + public List getFriendList(int userId) { + Set list = Set.copyOf(jdbc.queryForList(FIND_ALL_FRIENDS_QUERY, Integer.class, userId)); + if (list.isEmpty()) { + return Collections.emptyList(); + } + return namedJdbc.query(SELECT_ALL_FROM_COLLECTION, Collections.singletonMap("set", list), mapper); } @Override - public Set getCommonFriendList(int id, int otherId) { - return Set.copyOf(jdbc.queryForList(FIND_COMMON_FRIENDS_QUERY, Integer.class, id, otherId)); + public List getCommonFriendList(int id, int otherId) { + Set list = Set.copyOf(jdbc.queryForList(FIND_COMMON_FRIENDS_QUERY, Integer.class, id, otherId)); + if (list.isEmpty()) { + return Collections.emptyList(); + } + return namedJdbc.query(SELECT_ALL_FROM_COLLECTION, Collections.singletonMap("set", list), mapper); } @Override diff --git a/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/FilmRowMapper.java b/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/FilmRowMapper.java index 173d810..ce2e5ed 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/FilmRowMapper.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/FilmRowMapper.java @@ -18,11 +18,11 @@ public class FilmRowMapper implements RowMapper { @Override public Film mapRow(ResultSet resultSet, int rowNum) throws SQLException { Film film = new Film(); - film.setId(resultSet.getInt("FILM_ID")); - film.setName(resultSet.getString("NAME")); - film.setDescription(resultSet.getString("DESCRIPTION")); - film.setDuration(resultSet.getInt("DURATION")); - film.setReleaseDate(resultSet.getDate("RELEASE_DATE").toLocalDate()); + film.setId(resultSet.getInt("film_id")); + film.setName(resultSet.getString("name")); + film.setDescription(resultSet.getString("description")); + film.setDuration(resultSet.getInt("duration")); + film.setReleaseDate(resultSet.getDate("release_date").toLocalDate()); mapRating(film, resultSet); mapGenres(film, resultSet); @@ -30,26 +30,31 @@ public Film mapRow(ResultSet resultSet, int rowNum) throws SQLException { } private void mapRating(Film film, ResultSet resultSet) throws SQLException { - int mpaId = resultSet.getInt("RATING_ID"); + int ratigId = resultSet.getInt("mpa_id"); if (!resultSet.wasNull()) { - String ratingName = resultSet.getString("NAME"); - film.setMpa(new Rating(mpaId, ratingName)); + String ratingName = resultSet.getString("mpa_name"); + film.setMpa(new Rating(ratigId, ratingName)); } } private void mapGenres(Film film, ResultSet resultSet) throws SQLException { - Set genres = new HashSet<>(); - Array genresId = resultSet.getArray("GENRE_ID"); + Array genresId = resultSet.getArray("genre_ids"); if (resultSet.wasNull()) { return; } - Array genresName = resultSet.getArray("NAME"); - Integer[] ids = (Integer[]) genresId.getArray(); - String[] names = (String[]) genresName.getArray(); + Array genresName = resultSet.getArray("genre_names"); + + if (genresName == null) { + return; + } + + Object[] ids = (Object[]) genresId.getArray(); + Object[] names = (Object[]) genresName.getArray(); + Set genres = new HashSet<>(); for (int i = 0; i < ids.length; i++) { if (ids[i] != null && names[i] != null) { - Integer id = ids[i]; - String name = names[i]; + Integer id = ((Number)ids[i]).intValue(); + String name = names[i].toString(); genres.add(new Genre(id, name)); } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/GenreRowMapper.java b/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/GenreRowMapper.java index 5d30a4e..1ef7ee0 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/GenreRowMapper.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/GenreRowMapper.java @@ -13,8 +13,8 @@ public class GenreRowMapper implements RowMapper { @Override public Genre mapRow(ResultSet resultSet, int rowNum) throws SQLException { Genre genre = new Genre(); - genre.setId(resultSet.getInt("GENRE_ID")); - genre.setName(resultSet.getString("NAME")); + genre.setId(resultSet.getInt("genre_id")); + genre.setName(resultSet.getString("name")); return genre; } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/RatingRowMapper.java b/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/RatingRowMapper.java index 9755585..eba0af9 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/RatingRowMapper.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/RatingRowMapper.java @@ -16,8 +16,8 @@ public class RatingRowMapper implements RowMapper { @Override public Rating mapRow(ResultSet resultSet, int rowNum) throws SQLException { Rating rating = new Rating(); - rating.setId(resultSet.getInt("RATING_ID")); - rating.setName(resultSet.getString("NAME")); + rating.setId(resultSet.getInt("mpa_id")); + rating.setName(resultSet.getString("name")); return rating; } } \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/UserRowMapper.java b/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/UserRowMapper.java index f1f1524..c260172 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/UserRowMapper.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/UserRowMapper.java @@ -13,11 +13,11 @@ public class UserRowMapper implements RowMapper { @Override public User mapRow(ResultSet resultSet, int rowNum) throws SQLException { User user = new User(); - user.setId(resultSet.getInt("USER_ID")); - user.setName(resultSet.getString("NAME")); - user.setLogin(resultSet.getString("LOGIN")); - user.setEmail(resultSet.getString("EMAIL")); - user.setBirthday(resultSet.getDate("BIRTHDAY").toLocalDate()); + user.setId(resultSet.getInt("user_id")); + user.setName(resultSet.getString("name")); + user.setLogin(resultSet.getString("login")); + user.setEmail(resultSet.getString("email")); + user.setBirthday(resultSet.getDate("birthday").toLocalDate()); return user; } 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 2de537b..7784d28 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/model/Film.java +++ b/src/main/java/ru/yandex/practicum/filmorate/model/Film.java @@ -1,5 +1,6 @@ package ru.yandex.practicum.filmorate.model; +import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; import java.time.LocalDate; @@ -14,10 +15,7 @@ public class Film { private LocalDate releaseDate; private int duration; private Rating mpa; + @JsonProperty(access = JsonProperty.Access.READ_ONLY) private Integer likes = 0; private Set genres = new HashSet<>(); - - public int getAllLikes() { - return likes; - } } \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/Genre.java b/src/main/java/ru/yandex/practicum/filmorate/model/Genre.java index 7f3b4f0..df9a9de 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/model/Genre.java +++ b/src/main/java/ru/yandex/practicum/filmorate/model/Genre.java @@ -9,6 +9,7 @@ @AllArgsConstructor @NoArgsConstructor public class Genre { + @NotNull private Integer id; private String name; 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 74ed438..fe8adb8 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/service/FilmService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/FilmService.java @@ -4,6 +4,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import ru.yandex.practicum.filmorate.exception.NotFoundException; import ru.yandex.practicum.filmorate.exception.ValidationException; import ru.yandex.practicum.filmorate.model.Film; @@ -13,11 +14,10 @@ import java.time.LocalDate; import java.util.Collection; -import java.util.Comparator; -import java.util.stream.Collectors; @Slf4j @Service +@Transactional public class FilmService { private final FilmStorage filmStorage; @@ -48,15 +48,10 @@ public Film create(Film film) { public Film update(Film newFilm) { if (newFilm.getId() == null) { - log.warn("Не указан id"); - throw new ValidationException("Id должен быть указан"); + throw new NotFoundException("Фильм не найден"); } validateFilm(newFilm); - Film oldFilm = filmStorage.update(newFilm); - if (oldFilm.getGenres() != null && !oldFilm.getGenres().isEmpty()) { - genreStorage.updateFilmGenres(oldFilm); - } - return oldFilm; + return filmStorage.update(newFilm); } public Film findFilmById(int id) { @@ -73,28 +68,30 @@ public void remove(int id) { filmStorage.remove(id); } - public void addLike(int id, int userId) { - Film film = filmStorage.findFilmById(id); + public Film addLike(int id, int userId) { + if (!filmStorage.contains(id)) { + throw new NotFoundException("Фильм с id = " + id + " не найден"); + } if (!userStorage.contains(userId)) { throw new NotFoundException("Пользователь с id = " + userId + " не найден"); } filmStorage.addLike(id, userId); + return filmStorage.findFilmById(id); } - public void deleteLike(int id, int userId) { - Film film = filmStorage.findFilmById(id); - userStorage.findUserById(userId); + public Film deleteLike(int id, int userId) { + if (!filmStorage.contains(id)) { + throw new NotFoundException("Фильм с id = " + id + " не найден"); + } if (!userStorage.contains(userId)) { throw new NotFoundException("Пользователь с id = " + userId + " не найден"); } filmStorage.deleteLike(id, userId); + return filmStorage.findFilmById(id); } public Collection getPopular(int count) { - return filmStorage.findAll().stream() - .sorted(comparator) - .limit(count) - .collect(Collectors.toList()); + return filmStorage.getPopular(count); } private void validateFilm(Film film) { @@ -117,6 +114,4 @@ private void validateFilm(Film film) { throw new ValidationException("Длительность не может быть меньше 1"); } } - - public static final Comparator comparator = Comparator.comparingInt(Film::getAllLikes).reversed(); } \ 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 index 2c827f2..354d3b2 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/service/GenreService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/GenreService.java @@ -1,6 +1,7 @@ package ru.yandex.practicum.filmorate.service; import org.springframework.stereotype.Service; +import ru.yandex.practicum.filmorate.exception.NotFoundException; import ru.yandex.practicum.filmorate.model.Genre; import ru.yandex.practicum.filmorate.storage.GenreStorage; @@ -20,6 +21,7 @@ public Collection findAll() { } public Genre findGenreById(int id) { - return genreStorage.findGenreById(id); + return genreStorage.findGenreById(id) + .orElseThrow(()-> new NotFoundException("Жанр с id = " + id + " не найден в базе")); } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/RatingService.java b/src/main/java/ru/yandex/practicum/filmorate/service/RatingService.java index a9f7f1a..7d69999 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/service/RatingService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/RatingService.java @@ -1,6 +1,7 @@ package ru.yandex.practicum.filmorate.service; import org.springframework.stereotype.Service; +import ru.yandex.practicum.filmorate.exception.NotFoundException; import ru.yandex.practicum.filmorate.model.Rating; import ru.yandex.practicum.filmorate.storage.RatingStorage; @@ -20,6 +21,7 @@ public Collection findAll() { } public Rating findRatingById(int id) { - return ratingStorage.findRatingById(id); + return ratingStorage.findRatingById(id) + .orElseThrow(() -> new NotFoundException("Mpa с id = " + id + " не найден")); } } \ No newline at end of file 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 71338bf..e1d3d7f 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/service/UserService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/UserService.java @@ -1,5 +1,6 @@ package ru.yandex.practicum.filmorate.service; +import jakarta.transaction.Transactional; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; @@ -9,12 +10,11 @@ import ru.yandex.practicum.filmorate.storage.UserStorage; import java.time.LocalDate; -import java.util.ArrayList; import java.util.Collection; -import java.util.List; @Slf4j @Service +@Transactional public class UserService { private final UserStorage userStorage; @@ -38,6 +38,9 @@ public User update(User newUser) { log.warn("Не указан id"); throw new ValidationException("Id должен быть указан"); } + if (!userStorage.contains(newUser.getId())) { + throw new NotFoundException("Пользователь с ID " + newUser.getId() + " не найден"); + } validateUser(newUser); return userStorage.update(newUser); } @@ -77,50 +80,46 @@ private void validateUser(User user) { } } + @Transactional public void addFriend(int userId, int friendId) { - if (userId == friendId) { - log.warn("Ошибка добавления в друзья"); - throw new ValidationException("Пользователь не может добавить сам себя в друзья"); + if (!userStorage.contains(userId)) { + throw new NotFoundException("Пользователь с id = " + userId + " не найден"); } - User user = userStorage.findUserById(userId); - User usersFriend = userStorage.findUserById(friendId); - if (user.getFriends().contains(friendId)) { - log.warn("Ошибка добавления в друзья"); - throw new ValidationException("Пользователь уже добавлен в друзья"); + if (!userStorage.contains(friendId)) { + throw new NotFoundException("Пользователь с id = " + friendId + " не найден"); } - user.getFriends().add(friendId); - usersFriend.getFriends().add(userId); + userStorage.addFriend(userId, friendId); } + @Transactional public void deleteFriend(int userId, int friendId) { if (userId == friendId) { log.warn("Ошибка удаления из друзей"); throw new ValidationException("Пользователь не может удалить сам себя"); } - User user = userStorage.findUserById(userId); - User usersFriend = userStorage.findUserById(friendId); - user.getFriends().remove(friendId); - usersFriend.getFriends().remove(userId); + if (!userStorage.contains(userId)) { + throw new NotFoundException("Пользователь с id = " + userId + " не найден"); + } + if (!userStorage.contains(friendId)) { + throw new NotFoundException("Пользователь с id = " + friendId + " не найден"); + } + userStorage.deleteFriend(userId, friendId); } public Collection getFriendList(int userId) { - User user = userStorage.findUserById(userId); - List list = new ArrayList<>(); - for (Integer id : user.getFriends()) { - list.add(userStorage.findUserById(id)); + if (!userStorage.contains(userId)) { + throw new NotFoundException("Пользователь с id = " + userId + " не найден"); } - return list; + return userStorage.getFriendList(userId); } public Collection getCommonFriendList(int id, int otherId) { - User user1 = userStorage.findUserById(id); - User user2 = userStorage.findUserById(otherId); - List list = new ArrayList<>(); - for (Integer userid : user1.getFriends()) { - if (user2.getFriends().contains(userid)) { - list.add(userStorage.findUserById(userid)); - } - } - return list; + if (!userStorage.contains(id)) { + throw new NotFoundException("Пользователь с id = " + id + " не найден"); + } + if (!userStorage.contains(otherId)) { + throw new NotFoundException("Пользователь с id = " + otherId + " не найден"); + } + return userStorage.getCommonFriendList(id, otherId); } } \ No newline at end of file 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 2296e6c..710bbe5 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/storage/FilmStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/FilmStorage.java @@ -14,7 +14,7 @@ public interface FilmStorage { Film findFilmById(int id); - void remove(int id); + Film remove(int id); Collection getPopular(int count); diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/GenreStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/GenreStorage.java index 6ad3471..86d2a4d 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/storage/GenreStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/GenreStorage.java @@ -4,12 +4,13 @@ import ru.yandex.practicum.filmorate.model.Genre; import java.util.Collection; +import java.util.Optional; public interface GenreStorage { Collection findAll(); - Genre findGenreById(int id); + Optional findGenreById(int id); void addGenresToFilm(Film film); diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryFilmStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryFilmStorage.java index 770cb7e..cfc6cf9 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryFilmStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryFilmStorage.java @@ -56,8 +56,9 @@ public Film findFilmById(int id) { } @Override - public void remove(int id) { + public Film remove(int id) { + return null; } @Override diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryUserStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryUserStorage.java index bad937a..161b678 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryUserStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryUserStorage.java @@ -80,12 +80,12 @@ public void deleteFriend(int userId, int friendId) { } @Override - public Set getFriendList(int userId) { + public Set getFriendList(int userId) { return Set.of(); } @Override - public Set getCommonFriendList(int id, int otherId) { + public Set getCommonFriendList(int id, int otherId) { return Set.of(); } diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/RatingStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/RatingStorage.java index ff26631..9c4d919 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/storage/RatingStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/RatingStorage.java @@ -3,10 +3,11 @@ import ru.yandex.practicum.filmorate.model.Rating; import java.util.Collection; +import java.util.Optional; public interface RatingStorage { Collection findAll(); - Rating findRatingById(int id); + Optional findRatingById(int id); } 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 e15ecfe..ae1cea1 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/storage/UserStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/UserStorage.java @@ -20,9 +20,9 @@ public interface UserStorage { void deleteFriend(int userId, int friendId); - Collection getFriendList(int userId); + Collection getFriendList(int userId); - Collection getCommonFriendList(int id, int otherId); + Collection getCommonFriendList(int id, int otherId); boolean contains(Integer id); } \ No newline at end of file diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql index 5490392..6a974b6 100644 --- a/src/main/resources/data.sql +++ b/src/main/resources/data.sql @@ -1,18 +1,18 @@ -DELETE FROM PUBLIC."Rating"; -DELETE FROM PUBLIC."Genre"; +DELETE FROM mpa; +DELETE FROM genres; -ALTER TABLE PUBLIC."Genre" ALTER COLUMN GENRE_ID RESTART WITH 1; -ALTER TABLE PUBLIC."Rating" ALTER COLUMN RATING_ID RESTART WITH 1; +ALTER TABLE genres ALTER COLUMN genre_id RESTART WITH 1; +ALTER TABLE mpa ALTER COLUMN mpa_id RESTART WITH 1; -INSERT INTO PUBLIC."Genre" (NAME) VALUES -('Ужасы'), -('Триллер'), +INSERT INTO genres (name) VALUES ('Комедия'), ('Драма'), -('Фантастика'), +('Мультфильм'), +('Триллер'), +('Документальный'), ('Боевик'); -INSERT INTO PUBLIC."Rating" (NAME) VALUES +INSERT INTO mpa (name) VALUES ('G'), ('PG'), ('PG-13'), diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index 847cfff..f701b42 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -1,53 +1,51 @@ -CREATE TABLE IF NOT EXISTS PUBLIC."Genre" ( - GENRE_ID INTEGER NOT NULL AUTO_INCREMENT, - NAME CHARACTER VARYING(100) NOT NULL, - CONSTRAINT PK_GENRE PRIMARY KEY (GENRE_ID) +CREATE TABLE IF NOT EXISTS genres ( + genre_id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + name VARCHAR NOT NULL ); -CREATE TABLE IF NOT EXISTS PUBLIC."Rating" ( - RATING_ID INTEGER NOT NULL AUTO_INCREMENT, - NAME CHARACTER VARYING(50) NOT NULL, - CONSTRAINT PK_RATING PRIMARY KEY (RATING_ID) +CREATE TABLE IF NOT EXISTS mpa ( + mpa_id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + name CHARACTER VARYING(50) NOT NULL ); -CREATE TABLE IF NOT EXISTS PUBLIC."Users" ( - USER_ID INTEGER NOT NULL AUTO_INCREMENT, - NAME CHARACTER VARYING(255) NOT NULL, - LOGIN CHARACTER VARYING(100) NOT NULL, - EMAIL CHARACTER VARYING(255) NOT NULL, - BIRTHDAY DATE NOT NULL, - CONSTRAINT PK_USER PRIMARY KEY (USER_ID) +CREATE TABLE IF NOT EXISTS users ( + user_id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + name CHARACTER VARYING(255) NOT NULL, + login CHARACTER VARYING(100) NOT NULL, + email CHARACTER VARYING(255) NOT NULL, + birthday DATE NOT NULL ); -CREATE TABLE IF NOT EXISTS PUBLIC."Film" ( - FILM_ID INTEGER NOT NULL AUTO_INCREMENT, - NAME CHARACTER VARYING(255) NOT NULL, - DESCRIPTION CHARACTER LARGE OBJECT NOT NULL, - RELEASE_DATE DATE NOT NULL, - DURATION INTEGER NOT NULL, - RATING_ID INTEGER NOT NULL, - CONSTRAINT PK_FILM PRIMARY KEY (FILM_ID), - CONSTRAINT FK_FILM_RATING_ID FOREIGN KEY (RATING_ID) REFERENCES PUBLIC."Rating"(RATING_ID) ON DELETE RESTRICT ON UPDATE RESTRICT +CREATE TABLE IF NOT EXISTS films ( + film_id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + name CHARACTER VARYING(255) NOT NULL, + description TEXT NOT NULL, + release_date DATE NOT NULL, + duration INTEGER NOT NULL, + mpa_id INTEGER NOT NULL, + FOREIGN KEY (mpa_id) REFERENCES mpa (mpa_id) ON DELETE SET NULL ); -CREATE TABLE IF NOT EXISTS PUBLIC."Film_genre" ( - FILM_ID INTEGER NOT NULL, - GENRE_ID INTEGER NOT NULL, - CONSTRAINT FK_FILM_GENRE_FILM_ID FOREIGN KEY (FILM_ID) REFERENCES PUBLIC."Film"(FILM_ID) ON DELETE RESTRICT ON UPDATE RESTRICT, - CONSTRAINT FK_FILM_GENRE_GENRE_ID FOREIGN KEY (GENRE_ID) REFERENCES PUBLIC."Genre"(GENRE_ID) ON DELETE RESTRICT ON UPDATE RESTRICT +CREATE TABLE IF NOT EXISTS films_genres ( + film_id INTEGER, + genre_id INTEGER, + FOREIGN KEY (film_id) REFERENCES films (film_id) ON DELETE CASCADE, + FOREIGN KEY (genre_id) REFERENCES genres (genre_id) ON DELETE CASCADE, + PRIMARY KEY (film_id, genre_id) ); -CREATE TABLE IF NOT EXISTS PUBLIC."Likes" ( - FILM_ID INTEGER NOT NULL, - USER_ID INTEGER NOT NULL, - CONSTRAINT FK_LIKES_FILM_ID FOREIGN KEY (FILM_ID) REFERENCES PUBLIC."Film"(FILM_ID) ON DELETE RESTRICT ON UPDATE RESTRICT, - CONSTRAINT FK_LIKES_USER_ID FOREIGN KEY (USER_ID) REFERENCES PUBLIC."Users"(USER_ID) ON DELETE RESTRICT ON UPDATE RESTRICT +CREATE TABLE IF NOT EXISTS likes ( + film_id INTEGER NOT NULL, + user_id INTEGER NOT NULL, + FOREIGN KEY (film_id) REFERENCES films (film_id) ON DELETE CASCADE, + FOREIGN KEY (user_id) REFERENCES users (user_id) ON DELETE CASCADE, + PRIMARY KEY (film_id, user_id) ); -CREATE TABLE IF NOT EXISTS PUBLIC."User_friends" ( - USER_ID INTEGER NOT NULL, - FRIEND_ID INTEGER NOT NULL, - STATUS INTEGER NOT NULL, - CONSTRAINT FK_USER_FRIENDS_FRIEND_ID FOREIGN KEY (FRIEND_ID) REFERENCES PUBLIC."Users"(USER_ID) ON DELETE RESTRICT ON UPDATE RESTRICT, - CONSTRAINT FK_USER_FRIENDS_USER_ID FOREIGN KEY (USER_ID) REFERENCES PUBLIC."Users"(USER_ID) ON DELETE RESTRICT ON UPDATE RESTRICT +CREATE TABLE IF NOT EXISTS user_friends ( + user_id INTEGER NOT NULL, + friend_id INTEGER NOT NULL, + FOREIGN KEY (friend_id) REFERENCES users (user_id) ON DELETE CASCADE, + FOREIGN KEY (user_id) REFERENCES users (user_id) ON DELETE CASCADE, + PRIMARY KEY (user_id, friend_id) ); \ No newline at end of file From 0add97e12f614f51dd21fb85e7ec81effbcdaad2 Mon Sep 17 00:00:00 2001 From: Denis Panasyuk Date: Fri, 19 Dec 2025 22:03:22 +0300 Subject: [PATCH 08/12] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B0=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=B0?= =?UTF-8?q?=20=D0=BB=D0=B0=D0=B9=D0=BA=D0=BE=D0=B2=20=D0=B8=20=D0=B4=D0=BE?= =?UTF-8?q?=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D1=8B=20=D0=B8=D0=BD=D1=82?= =?UTF-8?q?=D0=B5=D0=B3=D1=80=D0=B0=D1=86=D0=B8=D0=BE=D0=BD=D0=BD=D1=8B?= =?UTF-8?q?=D0=B5=20=D1=82=D0=B5=D1=81=D1=82=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../filmorate/db/FilmDbStorageTest.java | 0 .../filmorate/db/GenreDbStorageTest.java | 0 .../filmorate/db/RatingDbStorageTest.java | 0 .../filmorate/db/UserDbStorageTest.java | 4 ++ src/test/resources/application.properties | 8 +++ src/test/resources/data.sql | 51 +++++++++++++++++++ src/test/resources/schema.sql | 51 +++++++++++++++++++ 7 files changed, 114 insertions(+) create mode 100644 src/test/java/ru/yandex/practicum/filmorate/db/FilmDbStorageTest.java create mode 100644 src/test/java/ru/yandex/practicum/filmorate/db/GenreDbStorageTest.java create mode 100644 src/test/java/ru/yandex/practicum/filmorate/db/RatingDbStorageTest.java create mode 100644 src/test/java/ru/yandex/practicum/filmorate/db/UserDbStorageTest.java create mode 100644 src/test/resources/application.properties create mode 100644 src/test/resources/data.sql create mode 100644 src/test/resources/schema.sql diff --git a/src/test/java/ru/yandex/practicum/filmorate/db/FilmDbStorageTest.java b/src/test/java/ru/yandex/practicum/filmorate/db/FilmDbStorageTest.java new file mode 100644 index 0000000..e69de29 diff --git a/src/test/java/ru/yandex/practicum/filmorate/db/GenreDbStorageTest.java b/src/test/java/ru/yandex/practicum/filmorate/db/GenreDbStorageTest.java new file mode 100644 index 0000000..e69de29 diff --git a/src/test/java/ru/yandex/practicum/filmorate/db/RatingDbStorageTest.java b/src/test/java/ru/yandex/practicum/filmorate/db/RatingDbStorageTest.java new file mode 100644 index 0000000..e69de29 diff --git a/src/test/java/ru/yandex/practicum/filmorate/db/UserDbStorageTest.java b/src/test/java/ru/yandex/practicum/filmorate/db/UserDbStorageTest.java new file mode 100644 index 0000000..c6f7db1 --- /dev/null +++ b/src/test/java/ru/yandex/practicum/filmorate/db/UserDbStorageTest.java @@ -0,0 +1,4 @@ +package ru.yandex.practicum.filmorate.db; + +public class UserDbStorageTests { +} diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties new file mode 100644 index 0000000..15ec19f --- /dev/null +++ b/src/test/resources/application.properties @@ -0,0 +1,8 @@ +spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1 +spring.datasource.driverClassName=org.h2.Driver +spring.datasource.username=sa +spring.datasource.password= + +spring.sql.init.mode=always +spring.sql.init.schema-locations=classpath:schema.sql +spring.sql.init.data-locations=classpath:data.sql diff --git a/src/test/resources/data.sql b/src/test/resources/data.sql new file mode 100644 index 0000000..c631278 --- /dev/null +++ b/src/test/resources/data.sql @@ -0,0 +1,51 @@ +INSERT INTO genres (name) VALUES +('Комедия'), +('Драма'), +('Мультфильм'), +('Триллер'), +('Документальный'), +('Боевик'); + +INSERT INTO mpa (name) VALUES +('G'), +('PG'), +('PG-13'), +('R'), +('NC-17'); + +INSERT INTO users (email, login, name, birthday) VALUES +('почта1', 'логин1', 'имя1', '1999-01-01'), +('почта2', 'логин2', 'имя2', '2000-01-02'), +('почта3', 'логин3', 'имя3', '2002-02-01'), +('почта4', 'логин4', 'имя4', NULL); + +INSERT INTO user_friends (user_id, friend_id) VALUES +(1, 2), +(2, 1), +(4, 2), +(3, 1); + +INSERT INTO films (name, description, release_date, duration, mpa_id) VALUES +('имя1', 'описание1', '2002-01-10', 120, 1), +('имя2', 'описание2', '2005-05-01', 100, 2), +('имя3', 'описание3', '2006-01-01', 140, 3), +('имя4', 'описание4', '2010-06-01', 90, 4), +('имя5', 'описание5', '2000-08-01', 180, 5); + +INSERT INTO films_genres (film_id, genre_id) VALUES +(4,1), +(5,2), +(5,3), +(5,1), +(3,3); + +INSERT INTO likes (film_id, user_id) VALUES +(1,1), +(1,2), +(2,1), +(2,2), +(2,3), +(3,1), +(3,2), +(3,3), +(3,4); \ No newline at end of file diff --git a/src/test/resources/schema.sql b/src/test/resources/schema.sql new file mode 100644 index 0000000..f701b42 --- /dev/null +++ b/src/test/resources/schema.sql @@ -0,0 +1,51 @@ +CREATE TABLE IF NOT EXISTS genres ( + genre_id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + name VARCHAR NOT NULL +); + +CREATE TABLE IF NOT EXISTS mpa ( + mpa_id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + name CHARACTER VARYING(50) NOT NULL +); + +CREATE TABLE IF NOT EXISTS users ( + user_id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + name CHARACTER VARYING(255) NOT NULL, + login CHARACTER VARYING(100) NOT NULL, + email CHARACTER VARYING(255) NOT NULL, + birthday DATE NOT NULL +); + +CREATE TABLE IF NOT EXISTS films ( + film_id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + name CHARACTER VARYING(255) NOT NULL, + description TEXT NOT NULL, + release_date DATE NOT NULL, + duration INTEGER NOT NULL, + mpa_id INTEGER NOT NULL, + FOREIGN KEY (mpa_id) REFERENCES mpa (mpa_id) ON DELETE SET NULL +); + +CREATE TABLE IF NOT EXISTS films_genres ( + film_id INTEGER, + genre_id INTEGER, + FOREIGN KEY (film_id) REFERENCES films (film_id) ON DELETE CASCADE, + FOREIGN KEY (genre_id) REFERENCES genres (genre_id) ON DELETE CASCADE, + PRIMARY KEY (film_id, genre_id) +); + +CREATE TABLE IF NOT EXISTS likes ( + film_id INTEGER NOT NULL, + user_id INTEGER NOT NULL, + FOREIGN KEY (film_id) REFERENCES films (film_id) ON DELETE CASCADE, + FOREIGN KEY (user_id) REFERENCES users (user_id) ON DELETE CASCADE, + PRIMARY KEY (film_id, user_id) +); + +CREATE TABLE IF NOT EXISTS user_friends ( + user_id INTEGER NOT NULL, + friend_id INTEGER NOT NULL, + FOREIGN KEY (friend_id) REFERENCES users (user_id) ON DELETE CASCADE, + FOREIGN KEY (user_id) REFERENCES users (user_id) ON DELETE CASCADE, + PRIMARY KEY (user_id, friend_id) +); \ No newline at end of file From 9b3774aebbb519ae5598790e749a1ff38abcc966 Mon Sep 17 00:00:00 2001 From: Denis Panasyuk Date: Fri, 19 Dec 2025 22:03:35 +0300 Subject: [PATCH 09/12] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B0=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=B0?= =?UTF-8?q?=20=D0=BB=D0=B0=D0=B9=D0=BA=D0=BE=D0=B2=20=D0=B8=20=D0=B4=D0=BE?= =?UTF-8?q?=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D1=8B=20=D0=B8=D0=BD=D1=82?= =?UTF-8?q?=D0=B5=D0=B3=D1=80=D0=B0=D1=86=D0=B8=D0=BE=D0=BD=D0=BD=D1=8B?= =?UTF-8?q?=D0=B5=20=D1=82=D0=B5=D1=81=D1=82=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 32 +++++- .../practicum/filmorate/dao/BaseDao.java | 5 - .../filmorate/dao/FilmDbStorage.java | 14 +-- .../filmorate/dao/GenreDbStorage.java | 3 +- .../filmorate/dao/UserDbStorage.java | 3 +- .../filmorate/dao/mappers/FilmRowMapper.java | 7 +- .../filmorate/dao/mappers/UserRowMapper.java | 5 +- .../filmorate/service/FilmService.java | 12 +- .../filmorate/service/UserService.java | 4 +- src/main/resources/schema.sql | 4 +- .../filmorate/db/FilmDbStorageTest.java | 100 ++++++++++++++++ .../filmorate/db/GenreDbStorageTest.java | 77 +++++++++++++ .../filmorate/db/RatingDbStorageTest.java | 46 ++++++++ .../filmorate/db/UserDbStorageTest.java | 107 +++++++++++++++++- src/test/resources/application.properties | 2 +- src/test/resources/data.sql | 56 +++++---- src/test/resources/schema.sql | 4 +- 17 files changed, 421 insertions(+), 60 deletions(-) diff --git a/pom.xml b/pom.xml index 018c5fa..2dfade4 100644 --- a/pom.xml +++ b/pom.xml @@ -15,7 +15,9 @@ filmorate 21 + 3.3.1 + org.springframework.boot @@ -52,7 +54,13 @@ org.springframework.boot - spring-boot-starter-data-jpa + spring-boot-starter-jdbc + + + + org.junit.jupiter + junit-jupiter + test @@ -68,6 +76,28 @@ org.springframework.boot spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-checkstyle-plugin + ${maven-checkstyle-plugin.version} + + + true + true + true + checkstyle.xml + + + + + + check + + compile + + + diff --git a/src/main/java/ru/yandex/practicum/filmorate/dao/BaseDao.java b/src/main/java/ru/yandex/practicum/filmorate/dao/BaseDao.java index e5a10d9..a1fd01a 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dao/BaseDao.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/BaseDao.java @@ -10,7 +10,6 @@ import java.sql.PreparedStatement; import java.sql.Statement; import java.util.List; -import java.util.Map; @Repository public abstract class BaseDao { @@ -47,10 +46,6 @@ public void update(String query, Object...params){ } } - public void update(String sql, Map map) { - namedJdbc.update(sql, map); - } - public int insert(String query, Object... params) { GeneratedKeyHolder keyHolder = new GeneratedKeyHolder(); jdbc.update(connection -> { 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 03a146a..230f15a 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dao/FilmDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/FilmDbStorage.java @@ -17,7 +17,7 @@ public class FilmDbStorage extends BaseDao implements FilmStorage { private static final String SELECT_ALL_FIELDS = """ SELECT f.*, - COUNT(l.film_id) AS likes, + COUNT(DISTINCT l.user_id) AS likes, r.mpa_id, r.name AS mpa_name, ARRAY_AGG(g.genre_id) AS genre_ids, @@ -68,7 +68,7 @@ public FilmDbStorage(JdbcTemplate jdbc, FilmRowMapper mapper, GenreDbStorage gen } @Override - public Collection findAll() { + public List findAll() { return getAll(FIND_ALL_FILMS_QUERY); } @@ -127,14 +127,14 @@ public Film remove(int id) { } @Override - public Collection getPopular(int count) { + public List getPopular(int count) { return jdbc.query(FIND_ALL_FILMS_QUERY + " ORDER BY likes DESC LIMIT ?", mapper, count); } @Override public Integer addLike(int id, int userId) { String INSERT_QUERY = "INSERT INTO likes (film_id, user_id) VALUES(?, ?)"; - String COUNT_QUERY = "SELECT COUNT(*) FROM likes WHERE film_id = ?"; + String COUNT_QUERY = "SELECT COUNT(DISTINCT user_id) FROM likes WHERE film_id = ?"; jdbc.update(INSERT_QUERY, id, userId); return jdbc.queryForObject(COUNT_QUERY, Integer.class, id); @@ -142,9 +142,9 @@ public Integer addLike(int id, int userId) { @Override public Integer deleteLike(int id, int userId) { - String DELETE_QUERY = "DELETE FROM likes WHERE film_id = ? AND user_id = ?"; - String COUNT_QUERY = "SELECT COUNT(*) FROM likes WHERE film_id = ?"; - jdbc.update(DELETE_QUERY,id, userId); + String DELETE_QUERY = "DELETE FROM likes WHERE user_id = ?"; + String COUNT_QUERY = "SELECT COUNT(DISTINCT user_id) FROM likes WHERE film_id = ?"; + jdbc.update(DELETE_QUERY, userId); return jdbc.queryForObject(COUNT_QUERY, Integer.class, id); } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dao/GenreDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/dao/GenreDbStorage.java index d5c13ab..a25f54a 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dao/GenreDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/GenreDbStorage.java @@ -8,7 +8,6 @@ import ru.yandex.practicum.filmorate.model.Genre; import ru.yandex.practicum.filmorate.storage.GenreStorage; -import java.util.Collection; import java.util.List; import java.util.Optional; @@ -28,7 +27,7 @@ public GenreDbStorage(JdbcTemplate jdbc, GenreRowMapper mapper) { } @Override - public Collection findAll() { + public List findAll() { return getAll(FIND_ALL_QUERY); } 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 5de2c85..bc6b47b 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dao/UserDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/UserDbStorage.java @@ -7,7 +7,6 @@ import ru.yandex.practicum.filmorate.model.User; import ru.yandex.practicum.filmorate.storage.UserStorage; -import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Set; @@ -64,7 +63,7 @@ public UserDbStorage(JdbcTemplate jdbc, UserRowMapper mapper, NamedParameterJdbc } @Override - public Collection findAll() { + public List findAll() { return getAll(FIND_ALL_USERS_QUERY); } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/FilmRowMapper.java b/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/FilmRowMapper.java index ce2e5ed..bd14593 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/FilmRowMapper.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/FilmRowMapper.java @@ -9,6 +9,7 @@ import java.sql.Array; import java.sql.ResultSet; import java.sql.SQLException; +import java.time.LocalDate; import java.util.HashSet; import java.util.Set; @@ -22,7 +23,9 @@ public Film mapRow(ResultSet resultSet, int rowNum) throws SQLException { film.setName(resultSet.getString("name")); film.setDescription(resultSet.getString("description")); film.setDuration(resultSet.getInt("duration")); - film.setReleaseDate(resultSet.getDate("release_date").toLocalDate()); + film.setLikes(resultSet.getInt("likes")); + LocalDate release_date = resultSet.getObject("release_date", LocalDate.class); + film.setReleaseDate(release_date); mapRating(film, resultSet); mapGenres(film, resultSet); @@ -43,11 +46,9 @@ private void mapGenres(Film film, ResultSet resultSet) throws SQLException { return; } Array genresName = resultSet.getArray("genre_names"); - if (genresName == null) { return; } - Object[] ids = (Object[]) genresId.getArray(); Object[] names = (Object[]) genresName.getArray(); Set genres = new HashSet<>(); diff --git a/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/UserRowMapper.java b/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/UserRowMapper.java index c260172..ea6d290 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/UserRowMapper.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/UserRowMapper.java @@ -6,6 +6,7 @@ import java.sql.ResultSet; import java.sql.SQLException; +import java.time.LocalDate; @Component public class UserRowMapper implements RowMapper { @@ -17,8 +18,8 @@ public User mapRow(ResultSet resultSet, int rowNum) throws SQLException { user.setName(resultSet.getString("name")); user.setLogin(resultSet.getString("login")); user.setEmail(resultSet.getString("email")); - user.setBirthday(resultSet.getDate("birthday").toLocalDate()); - + LocalDate birthday = resultSet.getObject("birthday", LocalDate.class); + user.setBirthday(birthday); return user; } } \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/FilmService.java b/src/main/java/ru/yandex/practicum/filmorate/service/FilmService.java index fe8adb8..35941a5 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/service/FilmService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/FilmService.java @@ -75,8 +75,10 @@ public Film addLike(int id, int userId) { if (!userStorage.contains(userId)) { throw new NotFoundException("Пользователь с id = " + userId + " не найден"); } - filmStorage.addLike(id, userId); - return filmStorage.findFilmById(id); + Integer likes = filmStorage.addLike(id, userId); + Film film = filmStorage.findFilmById(id); + film.setLikes(likes); + return film; } public Film deleteLike(int id, int userId) { @@ -86,8 +88,10 @@ public Film deleteLike(int id, int userId) { if (!userStorage.contains(userId)) { throw new NotFoundException("Пользователь с id = " + userId + " не найден"); } - filmStorage.deleteLike(id, userId); - return filmStorage.findFilmById(id); + Integer likes = filmStorage.deleteLike(id, userId); + Film film = filmStorage.findFilmById(id); + film.setLikes(likes); + return film; } public Collection getPopular(int count) { 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 e1d3d7f..f57ab2e 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/service/UserService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/UserService.java @@ -1,9 +1,9 @@ package ru.yandex.practicum.filmorate.service; -import jakarta.transaction.Transactional; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import ru.yandex.practicum.filmorate.exception.NotFoundException; import ru.yandex.practicum.filmorate.exception.ValidationException; import ru.yandex.practicum.filmorate.model.User; @@ -80,7 +80,6 @@ private void validateUser(User user) { } } - @Transactional public void addFriend(int userId, int friendId) { if (!userStorage.contains(userId)) { throw new NotFoundException("Пользователь с id = " + userId + " не найден"); @@ -91,7 +90,6 @@ public void addFriend(int userId, int friendId) { userStorage.addFriend(userId, friendId); } - @Transactional public void deleteFriend(int userId, int friendId) { if (userId == friendId) { log.warn("Ошибка удаления из друзей"); diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index f701b42..387209e 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -13,14 +13,14 @@ CREATE TABLE IF NOT EXISTS users ( name CHARACTER VARYING(255) NOT NULL, login CHARACTER VARYING(100) NOT NULL, email CHARACTER VARYING(255) NOT NULL, - birthday DATE NOT NULL + birthday DATE ); CREATE TABLE IF NOT EXISTS films ( film_id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, name CHARACTER VARYING(255) NOT NULL, description TEXT NOT NULL, - release_date DATE NOT NULL, + release_date DATE, duration INTEGER NOT NULL, mpa_id INTEGER NOT NULL, FOREIGN KEY (mpa_id) REFERENCES mpa (mpa_id) ON DELETE SET NULL diff --git a/src/test/java/ru/yandex/practicum/filmorate/db/FilmDbStorageTest.java b/src/test/java/ru/yandex/practicum/filmorate/db/FilmDbStorageTest.java index e69de29..51cb1ea 100644 --- a/src/test/java/ru/yandex/practicum/filmorate/db/FilmDbStorageTest.java +++ b/src/test/java/ru/yandex/practicum/filmorate/db/FilmDbStorageTest.java @@ -0,0 +1,100 @@ +package ru.yandex.practicum.filmorate.db; + +import lombok.RequiredArgsConstructor; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest; +import org.springframework.context.annotation.Import; +import org.springframework.dao.EmptyResultDataAccessException; +import ru.yandex.practicum.filmorate.dao.FilmDbStorage; +import ru.yandex.practicum.filmorate.dao.GenreDbStorage; +import ru.yandex.practicum.filmorate.dao.RatingDbStorage; +import ru.yandex.practicum.filmorate.dao.mappers.FilmRowMapper; +import ru.yandex.practicum.filmorate.dao.mappers.GenreRowMapper; +import ru.yandex.practicum.filmorate.dao.mappers.RatingRowMapper; +import ru.yandex.practicum.filmorate.model.Film; +import ru.yandex.practicum.filmorate.model.Rating; +import ru.yandex.practicum.filmorate.storage.RatingStorage; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +@JdbcTest +@Import({FilmDbStorage.class, FilmRowMapper.class, GenreDbStorage.class, GenreRowMapper.class, + RatingDbStorage.class, RatingRowMapper.class}) +@AutoConfigureTestDatabase +@RequiredArgsConstructor(onConstructor_ = @Autowired) +public class FilmDbStorageTest { + private final FilmDbStorage filmDbStorage; + private final RatingDbStorage ratingDbStorage; + + @Test + public void testFindFilmById() { + boolean id = filmDbStorage.contains(1); + assertTrue(id); + Film film = filmDbStorage.findFilmById(1); + assertNotNull(film); + assertEquals("name1", film.getName()); + } + + @Test + public void testFindAllFilms() { + List films = filmDbStorage.findAll(); + assertNotNull(films); + assertEquals(6, films.size()); + } + + @Test + public void testGetNotFound() { + boolean id = filmDbStorage.contains(10); + assertFalse(id); + assertThrows(EmptyResultDataAccessException.class,() -> filmDbStorage.findFilmById(10)); + } + + @Test + public void testAddFilm() { + Film film = new Film(); + film.setName("nam"); + film.setDescription("Desc"); + film.setDuration(60); + film.setMpa(ratingDbStorage.findRatingById(1).get()); + Film newFilm = filmDbStorage.create(film); + assertEquals(7, newFilm.getId()); + } + + @Test + public void testUpdateFilm() { + Film film = filmDbStorage.findFilmById(1); + film.setName("newFilmname"); + Film newFilm = filmDbStorage.update(film); + assertEquals("newFilmname", newFilm.getName()); + } + + @Test + public void testRemoveFilm() { + filmDbStorage.remove(6); + boolean id = filmDbStorage.contains(6); + assertFalse(id); + } + + @Test + public void testGetPopular() { + List popular = filmDbStorage.getPopular(3); + assertEquals(5, popular.get(0).getLikes()); + assertEquals(3, popular.get(1).getLikes()); + } + + @Test + public void testAddLikes() { + Integer likes = filmDbStorage.addLike(1, 4); + assertEquals(3, likes); + } + + @Test + public void testDeleteLikes() { + Integer likes = filmDbStorage.deleteLike(1, 1); + assertEquals(1, likes); + } +} diff --git a/src/test/java/ru/yandex/practicum/filmorate/db/GenreDbStorageTest.java b/src/test/java/ru/yandex/practicum/filmorate/db/GenreDbStorageTest.java index e69de29..a1c2970 100644 --- a/src/test/java/ru/yandex/practicum/filmorate/db/GenreDbStorageTest.java +++ b/src/test/java/ru/yandex/practicum/filmorate/db/GenreDbStorageTest.java @@ -0,0 +1,77 @@ +package ru.yandex.practicum.filmorate.db; + +import lombok.RequiredArgsConstructor; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest; +import org.springframework.context.annotation.Import; +import ru.yandex.practicum.filmorate.dao.FilmDbStorage; +import ru.yandex.practicum.filmorate.dao.GenreDbStorage; +import ru.yandex.practicum.filmorate.dao.RatingDbStorage; +import ru.yandex.practicum.filmorate.dao.mappers.FilmRowMapper; +import ru.yandex.practicum.filmorate.dao.mappers.GenreRowMapper; +import ru.yandex.practicum.filmorate.dao.mappers.RatingRowMapper; +import ru.yandex.practicum.filmorate.model.Film; +import ru.yandex.practicum.filmorate.model.Genre; + +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; + +@JdbcTest +@Import({FilmDbStorage.class, FilmRowMapper.class, GenreDbStorage.class, GenreRowMapper.class, + RatingDbStorage.class, RatingRowMapper.class}) +@AutoConfigureTestDatabase +@RequiredArgsConstructor(onConstructor_ = @Autowired) +public class GenreDbStorageTest { + private final GenreDbStorage genreDbStorage; + private final FilmDbStorage filmDbStorage; + + @Test + public void testFindRatingById() { + Optional genre = genreDbStorage.findGenreById(1); + assertTrue(genre.isPresent(), "Жанр есть"); + assertEquals("Комедия", genre.get().getName()); + } + + @Test + public void testFindAllRating() { + List genres = genreDbStorage.findAll(); + assertNotNull(genres); + assertEquals(6, genres.size()); + } + + @Test + public void testGetEmptyGenre() { + Optional genre = genreDbStorage.findGenreById(100); + assertThat(genre).isEmpty(); + } + + @Test + public void testAddGenreToFilm() { + Film film = filmDbStorage.findFilmById(2); + Set genres = film.getGenres(); + assertEquals(0, genres.size()); + + List allGenres = genreDbStorage.findAll(); + Set filmNewGenre = new HashSet<>(); + + filmNewGenre.add(allGenres.get(0)); + filmNewGenre.add(allGenres.get(1)); + + film.setGenres(filmNewGenre); + genreDbStorage.addGenresToFilm(film); + + Film newFilm = filmDbStorage.findFilmById(2); + Set newGenres = newFilm.getGenres(); + + assertEquals(2, newGenres.size()); + assertEquals(filmNewGenre, newGenres); + } +} \ No newline at end of file diff --git a/src/test/java/ru/yandex/practicum/filmorate/db/RatingDbStorageTest.java b/src/test/java/ru/yandex/practicum/filmorate/db/RatingDbStorageTest.java index e69de29..0029810 100644 --- a/src/test/java/ru/yandex/practicum/filmorate/db/RatingDbStorageTest.java +++ b/src/test/java/ru/yandex/practicum/filmorate/db/RatingDbStorageTest.java @@ -0,0 +1,46 @@ +package ru.yandex.practicum.filmorate.db; + +import lombok.RequiredArgsConstructor; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest; +import org.springframework.context.annotation.Import; +import ru.yandex.practicum.filmorate.dao.RatingDbStorage; +import ru.yandex.practicum.filmorate.dao.mappers.RatingRowMapper; +import ru.yandex.practicum.filmorate.model.Rating; + +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +@JdbcTest +@Import({RatingDbStorage.class, RatingRowMapper.class}) +@AutoConfigureTestDatabase +@RequiredArgsConstructor(onConstructor_ = @Autowired) +public class RatingDbStorageTest { + + private final RatingDbStorage ratingDbStorage; + + @Test + public void testFindRatingById() { + Optional rating = ratingDbStorage.findRatingById(1); + assertTrue(rating.isPresent(), "Рейтинг есть"); + assertEquals("G", rating.get().getName()); + } + + @Test + public void testFindAllRating() { + List ratings = ratingDbStorage.findAll(); + assertNotNull(ratings); + assertEquals(5, ratings.size()); + } + + @Test + public void testGetEmptyRating() { + Optional rating = ratingDbStorage.findRatingById(100); + assertThat(rating).isEmpty(); + } +} \ No newline at end of file diff --git a/src/test/java/ru/yandex/practicum/filmorate/db/UserDbStorageTest.java b/src/test/java/ru/yandex/practicum/filmorate/db/UserDbStorageTest.java index c6f7db1..cd433a4 100644 --- a/src/test/java/ru/yandex/practicum/filmorate/db/UserDbStorageTest.java +++ b/src/test/java/ru/yandex/practicum/filmorate/db/UserDbStorageTest.java @@ -1,4 +1,109 @@ package ru.yandex.practicum.filmorate.db; -public class UserDbStorageTests { +import lombok.RequiredArgsConstructor; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest; +import org.springframework.context.annotation.Import; +import org.springframework.dao.EmptyResultDataAccessException; +import ru.yandex.practicum.filmorate.dao.UserDbStorage; +import ru.yandex.practicum.filmorate.dao.mappers.UserRowMapper; +import ru.yandex.practicum.filmorate.model.User; + + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +@JdbcTest +@Import({UserDbStorage.class, UserRowMapper.class}) +@AutoConfigureTestDatabase +@RequiredArgsConstructor(onConstructor_ = @Autowired) +public class UserDbStorageTest { + private final UserDbStorage userStorage; + + @Test + public void testFindUserById() { + boolean id = userStorage.contains(1); + assertTrue(id); + User user = userStorage.findUserById(1); + assertNotNull(user); + assertEquals("name1", user.getName()); + } + + @Test + public void testFindAllUsers() { + List users = userStorage.findAll(); + assertNotNull(users); + assertEquals(5, users.size()); + } + + @Test + public void testGetNotFound() { + boolean id = userStorage.contains(10); + assertFalse(id); + assertThrows(EmptyResultDataAccessException.class,() -> userStorage.findUserById(10)); + } + + @Test + public void testAddUser() { + User user = new User(); + user.setName("nam"); + user.setLogin("log"); + user.setEmail("mail"); + User newUser = userStorage.create(user); + assertEquals(6, newUser.getId()); + } + + @Test + public void testUpdateUser() { + User user = userStorage.findUserById(1); + user.setName("newUsername"); + User newUser = userStorage.update(user); + assertEquals("newUsername", newUser.getName()); + } + + @Test + public void testRemoveUser() { + userStorage.remove(5); + boolean id = userStorage.contains(6); + assertFalse(id); + } + + @Test + public void testAddFriend() { + User user = userStorage.findUserById(1); + Integer id = user.getId(); + User friend = userStorage.findUserById(4); + Integer friendId = friend.getId(); + List friendList = userStorage.getFriendList(id); + assertEquals(1, friendList.size()); + userStorage.addFriend(id,friendId); + friendList = userStorage.getFriendList(id); + assertEquals(2, friendList.size()); + } + + @Test + public void testDeleteFriend() { + User user = userStorage.findUserById(1); + Integer id = user.getId(); + User friend = userStorage.findUserById(2); + Integer friendId = friend.getId(); + List friendList = userStorage.getFriendList(id); + assertEquals(1, friendList.size()); + userStorage.deleteFriend(id,friendId); + friendList = userStorage.getFriendList(id); + assertEquals(0, friendList.size()); + } + + @Test + public void testGetCommonFriends() { + User user1 = userStorage.findUserById(1); + Integer id1 = user1.getId(); + User user2 = userStorage.findUserById(4); + Integer id2 = user2.getId(); + List friendList = userStorage.getCommonFriendList(id1, id2); + assertEquals(1, friendList.size()); + } } diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties index 15ec19f..941f85a 100644 --- a/src/test/resources/application.properties +++ b/src/test/resources/application.properties @@ -5,4 +5,4 @@ spring.datasource.password= spring.sql.init.mode=always spring.sql.init.schema-locations=classpath:schema.sql -spring.sql.init.data-locations=classpath:data.sql +spring.sql.init.data-locations=classpath:data.sql \ No newline at end of file diff --git a/src/test/resources/data.sql b/src/test/resources/data.sql index c631278..55b096a 100644 --- a/src/test/resources/data.sql +++ b/src/test/resources/data.sql @@ -1,3 +1,10 @@ +INSERT INTO mpa (name) VALUES +('G'), +('PG'), +('PG-13'), +('R'), +('NC-17'); + INSERT INTO genres (name) VALUES ('Комедия'), ('Драма'), @@ -6,46 +13,45 @@ INSERT INTO genres (name) VALUES ('Документальный'), ('Боевик'); -INSERT INTO mpa (name) VALUES -('G'), -('PG'), -('PG-13'), -('R'), -('NC-17'); - INSERT INTO users (email, login, name, birthday) VALUES -('почта1', 'логин1', 'имя1', '1999-01-01'), -('почта2', 'логин2', 'имя2', '2000-01-02'), -('почта3', 'логин3', 'имя3', '2002-02-01'), -('почта4', 'логин4', 'имя4', NULL); +('email1', 'login1', 'name1', '2005-01-02'), +('email2', 'login2', 'name2', '2006-01-03'), +('email3', 'login3', 'name3', '2007-01-04'), +('email4', 'login4', 'name4', '2008-01-05'), +('email5', 'login5', 'name5', NULL); -INSERT INTO user_friends (user_id, friend_id) VALUES +INSERT INTO user_friends(user_id, friend_id) VALUES (1, 2), (2, 1), +(3, 1), (4, 2), -(3, 1); +(3, 4); INSERT INTO films (name, description, release_date, duration, mpa_id) VALUES -('имя1', 'описание1', '2002-01-10', 120, 1), -('имя2', 'описание2', '2005-05-01', 100, 2), -('имя3', 'описание3', '2006-01-01', 140, 3), -('имя4', 'описание4', '2010-06-01', 90, 4), -('имя5', 'описание5', '2000-08-01', 180, 5); +('name1', 'description1', '2002-02-02', 100, 1), +('name2', 'description2', '2003-03-03', 90, 2), +('name3', 'description3', '2004-04-04', 150, 3), +('name4', 'description4', '2005-05-05', 140, 4), +('name5', 'description5', '2006-06-06', 120, 5), +('name6', 'description6', '2007-07-07', 120, 1); INSERT INTO films_genres (film_id, genre_id) VALUES (4,1), -(5,2), -(5,3), +(3,2), +(3,3), (5,1), -(3,3); +(5,2), +(5,3); INSERT INTO likes (film_id, user_id) VALUES (1,1), -(1,2), +(1,5), (2,1), -(2,2), (2,3), -(3,1), +(2,4), (3,2), (3,3), -(3,4); \ No newline at end of file +(3,1), +(3,4), +(3,5), +(6,4); \ No newline at end of file diff --git a/src/test/resources/schema.sql b/src/test/resources/schema.sql index f701b42..387209e 100644 --- a/src/test/resources/schema.sql +++ b/src/test/resources/schema.sql @@ -13,14 +13,14 @@ CREATE TABLE IF NOT EXISTS users ( name CHARACTER VARYING(255) NOT NULL, login CHARACTER VARYING(100) NOT NULL, email CHARACTER VARYING(255) NOT NULL, - birthday DATE NOT NULL + birthday DATE ); CREATE TABLE IF NOT EXISTS films ( film_id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, name CHARACTER VARYING(255) NOT NULL, description TEXT NOT NULL, - release_date DATE NOT NULL, + release_date DATE, duration INTEGER NOT NULL, mpa_id INTEGER NOT NULL, FOREIGN KEY (mpa_id) REFERENCES mpa (mpa_id) ON DELETE SET NULL From 5fce3a0f7ce26d0b46d438b3bae6aa95f1c76608 Mon Sep 17 00:00:00 2001 From: Denis Panasyuk Date: Fri, 19 Dec 2025 22:14:02 +0300 Subject: [PATCH 10/12] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=20check=20style?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../filmorate/controller/UserController.java | 1 - .../practicum/filmorate/dao/BaseDao.java | 4 ++-- .../filmorate/dao/FilmDbStorage.java | 20 ++++++++++--------- .../filmorate/dao/mappers/FilmRowMapper.java | 4 ++-- .../filmorate/service/GenreService.java | 2 +- .../filmorate/db/FilmDbStorageTest.java | 2 -- 6 files changed, 16 insertions(+), 17 deletions(-) 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 eba86b3..fd98c3e 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java @@ -4,7 +4,6 @@ import org.springframework.web.bind.annotation.*; import ru.yandex.practicum.filmorate.model.User; import ru.yandex.practicum.filmorate.service.UserService; -import ru.yandex.practicum.filmorate.storage.InMemoryUserStorage; import java.util.Collection; diff --git a/src/main/java/ru/yandex/practicum/filmorate/dao/BaseDao.java b/src/main/java/ru/yandex/practicum/filmorate/dao/BaseDao.java index a1fd01a..8096974 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dao/BaseDao.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/BaseDao.java @@ -35,7 +35,7 @@ public void delete(String query, Integer id) { jdbc.update(query, id); } - public void update(String query, Object...params){ + public void update(String query, Object...params) { int rowsUpdated = jdbc.update(query, params); if (rowsUpdated == 0) { try { @@ -54,7 +54,7 @@ public int insert(String query, Object... params) { for (int idx = 0; idx < params.length; idx++) { ps.setObject(idx + 1, params[idx]); } - return ps;}, keyHolder); + return ps; }, keyHolder); Integer id = keyHolder.getKeyAs(Integer.class); 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 230f15a..239c91e 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dao/FilmDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/FilmDbStorage.java @@ -61,7 +61,9 @@ INSERT INTO films (name, description, release_date, duration, mpa_id) private final GenreDbStorage genreDbStorage; private final RatingStorage ratingStorage; - public FilmDbStorage(JdbcTemplate jdbc, FilmRowMapper mapper, GenreDbStorage genreDbStorage, RatingStorage ratingStorage) { + + public FilmDbStorage(JdbcTemplate jdbc, FilmRowMapper mapper, GenreDbStorage genreDbStorage, + RatingStorage ratingStorage) { super(jdbc, mapper); this.genreDbStorage = genreDbStorage; this.ratingStorage = ratingStorage; @@ -133,20 +135,20 @@ public List getPopular(int count) { @Override public Integer addLike(int id, int userId) { - String INSERT_QUERY = "INSERT INTO likes (film_id, user_id) VALUES(?, ?)"; - String COUNT_QUERY = "SELECT COUNT(DISTINCT user_id) FROM likes WHERE film_id = ?"; - jdbc.update(INSERT_QUERY, id, userId); + String insert = "INSERT INTO likes (film_id, user_id) VALUES(?, ?)"; + String count = "SELECT COUNT(DISTINCT user_id) FROM likes WHERE film_id = ?"; + jdbc.update(insert, id, userId); - return jdbc.queryForObject(COUNT_QUERY, Integer.class, id); + return jdbc.queryForObject(count, Integer.class, id); } @Override public Integer deleteLike(int id, int userId) { - String DELETE_QUERY = "DELETE FROM likes WHERE user_id = ?"; - String COUNT_QUERY = "SELECT COUNT(DISTINCT user_id) FROM likes WHERE film_id = ?"; - jdbc.update(DELETE_QUERY, userId); + String delete = "DELETE FROM likes WHERE user_id = ?"; + String count = "SELECT COUNT(DISTINCT user_id) FROM likes WHERE film_id = ?"; + jdbc.update(delete, userId); - return jdbc.queryForObject(COUNT_QUERY, Integer.class, id); + return jdbc.queryForObject(count, Integer.class, id); } @Override diff --git a/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/FilmRowMapper.java b/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/FilmRowMapper.java index bd14593..5d6f13d 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/FilmRowMapper.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/FilmRowMapper.java @@ -24,8 +24,8 @@ public Film mapRow(ResultSet resultSet, int rowNum) throws SQLException { film.setDescription(resultSet.getString("description")); film.setDuration(resultSet.getInt("duration")); film.setLikes(resultSet.getInt("likes")); - LocalDate release_date = resultSet.getObject("release_date", LocalDate.class); - film.setReleaseDate(release_date); + LocalDate releaseDate = resultSet.getObject("release_date", LocalDate.class); + film.setReleaseDate(releaseDate); mapRating(film, resultSet); mapGenres(film, resultSet); diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/GenreService.java b/src/main/java/ru/yandex/practicum/filmorate/service/GenreService.java index 354d3b2..57e4bf8 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/service/GenreService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/GenreService.java @@ -22,6 +22,6 @@ public Collection findAll() { public Genre findGenreById(int id) { return genreStorage.findGenreById(id) - .orElseThrow(()-> new NotFoundException("Жанр с id = " + id + " не найден в базе")); + .orElseThrow(() -> new NotFoundException("Жанр с id = " + id + " не найден в базе")); } } diff --git a/src/test/java/ru/yandex/practicum/filmorate/db/FilmDbStorageTest.java b/src/test/java/ru/yandex/practicum/filmorate/db/FilmDbStorageTest.java index 51cb1ea..e0304cb 100644 --- a/src/test/java/ru/yandex/practicum/filmorate/db/FilmDbStorageTest.java +++ b/src/test/java/ru/yandex/practicum/filmorate/db/FilmDbStorageTest.java @@ -14,8 +14,6 @@ import ru.yandex.practicum.filmorate.dao.mappers.GenreRowMapper; import ru.yandex.practicum.filmorate.dao.mappers.RatingRowMapper; import ru.yandex.practicum.filmorate.model.Film; -import ru.yandex.practicum.filmorate.model.Rating; -import ru.yandex.practicum.filmorate.storage.RatingStorage; import java.util.List; From b5c1c2318c101d122665cdfa2a19f88c9296c390 Mon Sep 17 00:00:00 2001 From: Denis Panasyuk Date: Tue, 23 Dec 2025 17:08:16 +0300 Subject: [PATCH 11/12] =?UTF-8?q?=D0=92=D0=BD=D0=B5=D1=81=D0=B5=D0=BD?= =?UTF-8?q?=D1=8B=20=D0=BF=D1=80=D0=B0=D0=B2=D0=BA=D0=B8=20=D1=81=D0=BE?= =?UTF-8?q?=D0=B3=D0=BB=D0=B0=D1=81=D0=BD=D0=BE=20=D0=B7=D0=B0=D0=BC=D0=B5?= =?UTF-8?q?=D1=87=D0=B0=D0=BD=D0=B8=D1=8F=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Filmorate.png | Bin 0 -> 61627 bytes .../filmorate/controller/FilmController.java | 32 ++--- .../filmorate/controller/GenreController.java | 9 +- .../controller/RatingController.java | 9 +- .../filmorate/controller/UserController.java | 28 +++-- .../practicum/filmorate/dao/BaseDao.java | 2 - .../filmorate/dao/FilmDbStorage.java | 16 --- .../filmorate/dao/mappers/FilmRowMapper.java | 5 +- .../filmorate/exception/ErrorHandler.java | 16 ++- .../filmorate/exception/ReleaseDate.java | 18 +++ .../exception/ReleaseDateValidator.java | 17 +++ .../filmorate/model/ErrorResponse.java | 6 +- .../practicum/filmorate/model/Film.java | 13 +- .../practicum/filmorate/model/Rating.java | 1 + .../practicum/filmorate/model/User.java | 7 ++ .../filmorate/service/FilmService.java | 37 +----- .../filmorate/service/UserService.java | 25 +--- .../storage/InMemoryFilmStorage.java | 115 ------------------ .../storage/InMemoryUserStorage.java | 115 ------------------ .../controller/InMemoryFilmStorageTest.java | 36 ------ .../controller/InMemoryUserStorageTest.java | 36 ------ .../filmorate/db/FilmDbStorageTest.java | 4 +- 22 files changed, 125 insertions(+), 422 deletions(-) create mode 100644 Filmorate.png create mode 100644 src/main/java/ru/yandex/practicum/filmorate/exception/ReleaseDate.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/exception/ReleaseDateValidator.java delete mode 100644 src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryFilmStorage.java delete mode 100644 src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryUserStorage.java delete mode 100644 src/test/java/ru/yandex/practicum/filmorate/controller/InMemoryFilmStorageTest.java delete mode 100644 src/test/java/ru/yandex/practicum/filmorate/controller/InMemoryUserStorageTest.java diff --git a/Filmorate.png b/Filmorate.png new file mode 100644 index 0000000000000000000000000000000000000000..3924819bfeb66a4182d1452ca9d07772968416cb GIT binary patch literal 61627 zcmYgX1yodB*Ou;*ZUpIWhDJbCT96RwmIjF-hLBE?ZV&;H?rs>QLAqmrLApEs%j^4n z|F_nKb-A25bIv~d?DIU&-ZxB5MF9ts0`tj}CpfQP$-RB@1aTPn=|M*Yo{Y1bA3S-2 z|KzotjHU;|ZU&kW(d2Z`NWL^WMoSRaZ!dzL0KafL7J3p*HTB(amKprJd3p5;ZzCCX z&2q&9YMF9>I*C&XPer=;c!yyu%A_xuoK;O|J{krs2N$*JEiRT%op&#fG#PnEZKTT( zT2N0Z{^y47W?CBkR25lH8u>QWFOn320RQni!2jZSj%yi3k4R7V@0CKRR=WK7I4$pM z&VQc*H?7a{{IfpIFR-d9?5EV< zFZ$=Vzu#jQ8|IUa|J(2VYw6BescB`ch{rC=(5*f^Hb#Gs_;gsN=`^+e|Gx^E!T)wd zw-fZRJMs2$CjVY^q3FoPj`wX3H&P2;2Q9N)8zHpbUrebj(K19`J5{Cb*2RZD`Cc9x zHw&A+$~P}faloUAl#G$wPTi<6g^S-`?{I6DX${%HKGG5V3iIoi|J!r>J~E<~i2Lp+ zLX7zFtJ+%)4UH;JcTOcypSd%EaoA@xytONM)=2fBU>uC8;CsCVcy9(2^S^Mn(8H$p(n3Rhj*a$u{%|M( z!{K2UUYgs&r`;KXEbn6sJee5Io5d>27?8U?lX9vVl%0Y+%)osyNX+xRyV`pE@aCWm zwwY!haI&1pspt5IF)4Xm>i!bR6dK9f+Z#>IZ<@$$xa6U0=zHg?)|}eOPZp!pvq;3*89209&k4#TmX8BlLR@g!#$+vE1OoMgDnUoJN zmVMJCtN-;}z<>RV(bwOLWEtk7GU*`3QXla>%n8~=daPa9D!$6}y}$aUoN;}&11pMU zaN3?=?e6I@?kH;u2>5cRhR&?_BE%#du0&+L=+Mv;x*;Dy%D~3fkG^(!=|NT1y$F0x zcj!A|qTh#F+Z2$b$_?-Z1+nvyT?KStiMghtDOVj7f%&<(a z{SKdtMYQ3H3dZxy-R0%Ti(^G@|Gi7oZ&{4lSFiy1?9gWuv8kswyGGw@Xf!y~?+3Ws zj9P1GA~=VuZB90a>MQhw7u;_4k|ljaS#04WX_ix5OC}@NSvs}qYGupi&or85A4q^H z6<;+UK`Ad+?k+m6mwj$HTge46V=ne0&}h9!Z@++#dPvn9oXz>$n6o@FTf8n%eNW5r zEaz(NC>wR8L>hJ7?i!5{ezk;{(+(JxH{VseOl*yjiQ1C2mP5YZWz{U*#&%D$@0riy z2rn2xwlXS9>6J1>iBDTyhGs&({(mGflc$^RY`B}$ri@kSxJlZu&L6$mt6NrTBl4%_ z7;&=SuC|?hsy;UMl8RHmkw{zbuJX=WvAeu&r^!vwM^qTr8`afAY8cs@<$z3%1IwQ3;$HvHMy~96(PYDO;fMvNvIwuGEvaZ{N0i z>=$H#Q4`qSy=Y=DZ66X|j?D7C2hD@axh=nOEu}1dfM{0RFH%Sp7omwu zfsdbec6G_9Uh)pO4ANqZb5!g*is+>v7Pkk?Af8OJpJ6^uzR%m7VRp zBtz6PM-j@loBbvWn%jylaRac`QZvZDvh@A#d|iz0o5z#FNDPq=2&V0lk1>=|*gmq- zd>~*+1#E9a5^UGgx>Zx*p zt1yUXfP*;3X1XhTz?tJqtfAXeMM(x{?`@PYUs#MPDspdz6SKE}LD@7HwlFh$uA*n) z6s#Rz(cF=xm-H~pRHZq`W_kI>Nm0%+vd-QFoRVSp>wByqbFhQv54DcKcAoTXl?+jx zYOC17mog6}8?W`P?sTk;zDxTRExvq zBS-{|$!-aFTsD;)TQW8l92OgiqbLN|a$fIV@b0i7w44q8fL9LRcDyJ9|2%^QnBS_m zOXwc=>ap%9gn{Y|;uhG${&R?jO1nMUmKqn$BP=bcb6Pj)Qm^JCil?;{r6d*6H==l4 zg<)4KY?v8bH|dsCm;Ufu1Q()2wMKzbM{pFS(`pcDz8{H{sGYX=fqieGqU(%kzRnJ*Wnld^iAI4k60DSVZ@hORw?Hi4M4m_d zx!axR!e~hv#-h;{I{U4$KFW%S(!+ZxL<65aL70SFr3rO@Bz$nk5`5BN*yP4Jb74Le zLzk&>d7^+4lfu&7;GVzM6Cs8do3QwY*xBjJ*YwDP)(4uVwYByyxWTF-T^{K}itkOP zuT1^H*j2_`%B!70;M9BB0OqC=+9yO)xI3<$E{y*VbXd^+KVVtnIx82b7^UxTOfkB7 z?#Jv>KIU6Xehcj5dOpk?L6Ac zv6sw#!JJ@F|BAUJ12=LOTXJql=Ejh@RIWl4A8$`{N67{XsoxUB)3nL`U^t(tI?$LO zdwS(E62oGvFICXy%c8gvj6aPtku>SVxE{qc_fJp(oqKIThC&PtibJi~_L9-Xb#ih#`~S0$nxB>cJ&t6F{?ZNWfI3G$#`Y>*yMc3kqvOquSoI&y0KF8 zpZ4Yi(+J)@N0d>L*7yrsfARZ^V^~6?ospsW&db|t5e;1l+WO|_3+wr~^0=~$TeItV zo&`xMogqFv(HD$6l@eKI9m!VVA3Ys%iw7==>WQL@dq?rZ-6VPalkPK;mUN+V&-x^? z&dWU~+2YH^zk6dMIlMG&<8Ya>cEI?wYumK8s2N7}@Ay5M{Lr{ITH359** zl?4yg=T*Yu@mf2*!#*;ikk6<%h_b9?Z#xPs#l?*L8bHxbEzM3b=ZBjExYVM49H8Ff z%~i)+QnA5;bn%M?7pMx9g#Tr#FwIn{KBWT=O{AVf6Amvi8(rlq0(#k4elHhi3m{^s zjI?$gEqhp|rq{TLP-T~N71K(sziO?{$`LCq3mM?d^j1siV za6LXEWjcSpm#wxz$UhSu4Zd6wJ4~P=iG&Y}W}ujX~S`DPz>EH_j%On4x)oB-1prWSqhGJ~;s-XI zY#RSw?^_{cuo{_j-6^*|u{il2mFd19Ll|iV$8kRO=AZEEOz`<&ekbzGJveuieeg>p zsewmF^)u%4p&7GU*OoAD1~Is}6|Kb3uAgez3QjckWcHn5G?n%4W>n-xF)eyJtuBvB zTBhT%C3?Su^T1O8B9B7c~kLxMfKdR>ke-B+`rp~=@u zX?N8p*`$~ zzzXQPu0?r!=CcOyQASkM6B(o=G;FG$@^yC$ayP+>p`U=2M>o~MnCJi01s-&n?&bw| zQJ#<~?1^uJTGDUF+9gIZ!h9-xLl}l+`z5KOt4+^kVmRa*t6E(~JnU;-GsP}+hq`o! zC3+ZT1wD2hMcR2mFS8_l22WDSq0U`6!pREV6)F~Crk3XJ7H9keFkohUL&Xazy&zD_ z-LN=ZvQdZEQF7R2ujRG0>A&ZV^vcsi%GKRQq82S&$2S zEevwEAoazQ=wFuFg#U$_{tFI58_{s2%LXl3pM?RNGa-U!-Br2;Q@EwDYmxo`o2C5# z{W8z-T&$n3J{x($;6Ef$8rfR~;F)kmI;6jC5wrX(SP<#)(UG60BN-D5qW#yoftwgY z^pC5%PK^}*C7Hktoe}yg5wyX#AL#*5BJ)aGgXEv_9nvFUSaNqxsr@ghEk(ZF(dnFR zn^K$#2gq=W^q7}#;8(KsQ;MzIeQ#m3o%ZejXaQ}<6Bz(9m3j(r?%sECL-Z@gh}-`i z_sR!=2gCW)qyGzDeF?vu(XS8yk7+u7s{Bh<{~0G@lwY7rcFB(nIK%w^7$gScmB`J} zTHmL?Gdy@YY6T3nL%E$h=p<~skL>2cjWLa1ryAnx*w!TA^&dO<`_Zo?b%N-{i6o?S zy_u{Xv`Hv0plB0m@^SxVOz8HA5LN)2_C0>g-;W_1BXDI`5Ahh;B@D|3B`9M_C|4U^3~y6Td4+dq9D`=SqN=VEbD7 z<>U0E?*Tu6$E3Xn{y+x#qm}-MLc(BH-x_u}9O{hnusmkFJ-kVzEBN;3QPGe#A}o(` z#I+qv1y)&_j$r1ZujGdo6J}mG33PSDLALK(&|!N*%~&lY$R!`VRW*NjBvDdsGsF3c z+?8}q;grXiI>K;2aNKY?Y+c=bt&X(pwZlc5z48~4O6Io;%jW%?lFJCsa1}foaR08k zw*p`|_g%qCz;IbllbdbPR2Uf(>BzzqjjjnKILI5B9;Su}7M}jM8OdVB}*!^Q!$YigSq5iuY=(Mq!X_%cdD9OP#yhTBECC@N%wQi=xO0NuY zX=~{=9g^_upCw-L0UUD4vXlUD$lG-3GF1{2-_F;&qVb_=Qpq841#wjw>b48&)t*j# z*N{8r&Eh$`ZG(q>s)f5h4)yDKg`3URHE(}tRMdkqTljFfV|1ej3{!%w2UrJH)4c5- zXVzYYJmIAR>{b{0@6L}-%0lNazjKj);W+wqfwizO**whDf(8T$7vrHYg|;Mql>NY>^om17MnJhxB>+O(s4dA>(#)H|AXbmfzjte|=1q@^d- zjFIr>W#!Ci$F-`Py?}1PwB466bjniK@5BYo z3TmYMwQ>?Va)RUe1jBdd_eE`nO(dj~9H0L@?01u%Ov8T9ZKzdw3z)pgzXLB_jktFw z;F$10`h>hC-6eh|g>_cS=quF`hm-QsUky*eVLK?4yzZ{)1dUm`FH(uw62j%8oYl#^ z=TTmX#0Fm3oE!D%P3MLuJd6#xZlZp7J7YJRa?!35g@Bu>>9InL55Ff|dak}KDq~Ud zczYCw8VyVb4e=fA4SCIV)N6;;ts`QZhfP%%gUeEf(TzpWrpR}=_LhWqENCcipf^KoCfGWF!!5nK{hbGmGeN3T2-^3Q%coaq^;=@tPU zC^=&z?!AZ*PUHTK_LAdej4{^lG)-)}QX(;+Q90`9|!Pj!L69n6t+>87JZCR4X@Yn8(yugwE zup@#W?RwiHeFKDYAmO_d0vMRJz5x!;KgH$2aV<0KX>}$)nCCA!~+ohre2RG z)yU>9k9<`|M%Z~yTdL5%0r+niHd9`)Jxg$H$>vzi&H2EbImz(-!;Tri5|h9Mac}Fz znC0aIQ<`(b3Mwvsg`J*`Bz}g%x;r(~)n~=~o-;BQ1Q=6rgrnnaaJA5$H*_4cRJFAz z5RRSKSbJqcoio#6NjwH-7hB~Ktf@U--oBU* z`lQ~$r?T3U9Tz@x$$a+rOP`)#w|LwoZf?1Pm8%Q2X-+Z^jO^)d2iQzAn!gJ}%Xp0G ze4BrhYYT*S0ORd-k1>e;XdZ5Ix1akMkIA4;P9>E;3=5IpC=jDQ*v%E`ym3~0`Y(w3 zN8JoGdx!tyD$^rSQ>B>82h4U(1@f0!OiSzSy=&UTS7FRsS^r};e%e1>06RR=xIFe( zyLSHUnEotG-pUVt=lP>|Hg{{#2YDjy-vtQuYa;|=;cl41t1%ZZnW*2t zI1(zw8!u{$f+&~LK8G6Qw)bitr4$Esz9@H_COEm)ZGiFgOPwP1iyul1`@tW?D)${#??44H63IVG^f6EEtJu7RbCd2f9*kkL+w7c%t0D-neDRHZZH8KL(s)s46f{!U;fd{PYWbLPQ# zU&?Z|)w}VB|GENj!v&mT`bO&)7l}n!o7NRH9W%G zt{yH-=e=2j2VaKW=Ke{K{A)XpFd(}^Dt&@Se#jlk`#qp=&QvO`Z3`ZhBN+JDWG3Bj zknnLj56^&%iRN&XS!5Pk7W{m&Q91NgCt zx={2l97h*GT+O*|#I6>sdGI#Np~l#XYmY9XPXmtV=9hG?r}}D7Kc^cYX=Jwc+myY= z0S-$eLjW3_>r?7l$RhH+25__ph6q&$$YJO`vfPR-@IT9w(SMH%bYwn+A!TH_>?eSQ zu%4(Y|L?E^LV)p6PrVoV8#w;{p2-9r?j^lK1z>il-yO!U7mtgGj{1m8%f4|0mT?B!W;EQAJn8`1q|MchIPMVM2{D0OV*Dec8MFLrCaW2Kr z7E1`x$;rtGvle{QgP#`&RcniB@Diu2Vj3*qOy zyrXqVrzMc^nG!F4U&W;`o>cZB>hdy>YUfHG?oCa#A_+i&TrUg$STNo z+-Z9XTDV{4y^SUF*27wW^nqWwo^*eU2uy1|u=Rn|N)$ButlNgLJ3d2kc^Nx+QX;l? zyx`u2>Z+dic3Aajwi?X7M)*Aa1X~9On96U!a|&`<4*ehZ7CItguL@HR5K?EnPLbDC z)1gTx!jJLByUhcOq-5D0*WdnNNIuCOzG6MV|DpJzE<5S^0_R4Fre$)^AkDsc0xlpP z=d`Bzepw~vILEX=dPL}^@ixKwWph^Hwy+qc8B;+=PlP-OPkAPI{6@(sgSVm9MQEtr5tQLR#gh@-fCw1q->zw{GFNZ-69b`k}lDArN6=eNM2jX>~62VhX|y06<~ z+|OU?;a2JB2#kA-#G56vQSJrBe0S!xpoE+Qv^Jf{toxaO{K+>tN}r4)@4WOZhK^I< z85X5$N#7cr?%uB~xqh1J<7k+2qG4vrY%kO(Fk&?Y&N(+~iL+?6QT42`YGX3MgA5Uuu3yGa?$j zH{G9_XEUsJ(_4Q*y=uf-*5Tq2c0@UkSDalDw z8`$@L$l?nhI%m85J~!MDGOKL8MuprXt|URA>uk6zk}h$HT)F`Z&!<~mTt478tB-+6 zRZ+;xO!{6CaZ*_{hO+7+1E+;rrIoQu=GU(u&p~!;xXh_+q!TPr2tslBn@wBK2EjPO$ z$uwNIjCSX2B${utAydroX3$i>weHejt>Q7FJrDqAt++!6{pr>n`upmAzcWhVv^)L> zFY23fTAtoJwC451cY}p(cv3XRP`&emH+7Lo(<)G<HI zQQ`8?h%z!Vjx3T^2J##3fbBi<5$2E2pd)gvzE==&jqe$T_+Mh_zr3XT5y&saVd#5< zWIJC$h?KP*;cWgU_n_-yE%kh_y{TmUOTDF@0N?M^#Rk*&N~T&Zo`;wwTC%CPP@EkG zwx2&h!yjc=o2>_?io;z7V?slDmR@;Uuet5Loq>{@-2VQge2eJHRIuf`pV67*Wl24K zm%@c(d6H)fZcq0rINXrrUYnjRIB#HHar}NIB0Ie6GS63bHhXaB#Vy1;%zamheF3RO zFWHH=6gnPHvfk>#>EZ8j-q9Lrm*}Y~0*PCu@HZryL(*$;MXCmL3gnK~R;tpI?$XYN zlF8@mZ_*D`TWM#@PJD(;AJYsEboc_DprdY#1;|L3h!t2jhE}yZkd@zY`_%fd<7z;= zSkkg4lj;&P)8ky;`)~|ywU|?M^tMc=2D(0`=ks8yO3p1Lh-I>;~O~7B{r5)h$2p#n@8_Tc=jo+3li^&MO@Z2C|N0 znGs5G5;+h>5c!~N59U>HlvMTCJ3mJ$x;HoV0ZQ|Y#HC^_A= zPsG%DPrG-Y5nq=DyD;~lvy-%pms*=sez^4;feF+OTM&Ii%!Yz8i( zoAnLKJEkKfZQ_R8&Y$(5kPq-qZn^1oqSj#vP(9Oh#qR1JWsv+i( zI8Gy(UntYknEiu`gc{|(9UTixcCtU=*c>zKXUC;QxQgYs`i&%H`5*k;uuSs?WpWf- zH#j&YuqfG;Ml6W?qO>!qcUW{m@pdz)+I`zuKe?Iz{r$^7U|pQkgNc=N&8lYitovB0 zozHc|%Maco@T4d6{U_;$o*$VJmzW>-!?YE@=J)BES8{=UoO+Gb4Mb#iVQ(zfg}6K) z?mrhtU%T2o6A+l7nl929*xf77QC6xBqOsUtOfX^75@)}kn&oa|VKL)VB+744zWv>A z*Kb=~e0*u1$P2QY{zr{nc2e@;0CaQ~YiF=Lgei(5S>(^EVKkycdzy%AFzHLK8GPWJfBic0vr50Vg7?qhs%I@M zw|v>|$%gpD-g|#B@^AFRs!O?zbYvWac@=>lK~;HJL!BZoj)R{)e;#2; zowCHYEdFo`v*sN(^!}{9?1?v&Dj-07@)(t(qfpvoCtcEt?+483dXYoC{hs-tP$B-J%MAYAV&oKiZXuGqadr~K-{0i4|izc z!{6vn z#7qrx78p0!>e+~66a=nxwi+DC?FW^`VO!=ttBZ3;8diQ)lZ zg@EDKc>U(I`3&mvHld#wYsSS$pr8)KWARP*A6jw&EJmCvv`KV0)y8US@Yoi2iKpRhboU08e|o z+GEg1cZ#JV*A%drG>}q(u}gE8F);&6*M@A?fc~8zdsar`X8zPP$XnN|JUOd{v)G!>mUoEIv~N#J_X|E z=_f?seoST1+u|@H3enLK0;9N%eRrB?f7gL-34z7pu3cE;VGI}leyl0ws{XtUDT*6Z- z;GK=hkz*TzQOgaj7p6iCG)?k+cQK?@wasXoSIVu6DUQ}QtxF-=XnPPuCg#KjRn4>q zrwA7?%n;Y1!I+i1k}))Ryp|Aw5~#Gwl6iNd&iFq}zUl8;A*S!s7$0sUd`n!@nv4kY zhm%dWuVKN@C_OuarApInafyH__*Z^S4FT3@8vQwqYWL~vNhIC9_m-H|STV-Z-60|W z{Y=>S;f_Na>~lxi+RiGi$yN9}ozojbqqGzkh@flh<(32yfZN=cmeG=GKUz=TNbSy4 zV2xW0$-j9MPB4_fruAe;&|Zf&MrSQGU_k))tMbQ>Y@6J-)T_qE^_QJk_!>FlTE*`_ z2L_r3w|B^fjD1%}60rG=uTV@&1u%rVnLLv}mBcI>jYM-F+<5yFIHM+Qrc$oMm{p8m zn*(xQe z^?RG8f0jlYT|cg62ph}Jo^%pr?c@2OmG|_gc2U5|&v)`;zg~6g1&D$e$!lz;qz6cb)S7h!!sUo$!Wr9@ zk!%Q`J`KRtu9c74o-F2nxHB}ce9#xu-G2b)bmxm>C-#{zF+?GW$gWA8uhbe*!{JhQ%%B_{$h(|Ssz}aviA$b7qTxnM!a)QS%s}NX&Fms83scuAzvIvh$tum zCgL?CtjWG|#z@HWGAX6_+ljeViby1|7+~XuFx-iE`7fvU@9YXIGRh+T%ltF)iD=7u znP;sf%p%|z&4Ok>$jy#bmyP<9$Dk*+e9jH5`jyY=IY&;v=@OQEoXf?v-W;1&w|!IQ z@ljXM8mi8Y8hUCb%J3O0N zxfYJC%9iU?L zX6UuJIcpW#{V0JXZsL35c(J-=!qoA%X zUu7r#53aY}G1LAvFnt=HY#FmZ$9R-Z&5>|LjG*vpiZ-KeX@dB`_cR4#(&Ij%u044U zfV=3xKG_se2G)>J zW+6q+@WaHuz!Kgs$c0?t|-T$y~NLObu0Wy?d(786;8vY>Msk!@ZFwWBG;ADmbdDhmRwb zvIOxI)?iTufrhEov9(+}rwSIznDM>bIj}J1^6}&7q0}38$ju%1$%b4LIq9nr;g+E= zEo#ZRLb}gm{J)#!v0QSqCD*ZDytUhV;rHx`X)ATCy-L*fy9%?8Pc9JHS;o=)AcoNo zsOqwJ@U)>fiA%w4fLoFw;Tu(l#uCx z4^zKv&+7QIoeQw-m4EdJ?v+U=sZgsGXw8lDl=t6kG` zMpC87-HKoAwTd$2nBwZ)C(oIojA$}KsrttiH?+^{>0>JS!(;AgsnHG%wfQu-VWC%S z&X+=rfGE@{k!O=%nijIAJDAyquWdL66<0kA(nfena zLWkBHP|NN(cWcLjp|5P;Q%31${qTAc)~5&%wG#r(JI=CLtP2-h_k%@CC=gYN0M3jV zY)-`bw%eQhR^0wK=nN}0Ha z1nd?>U&ZqLM+@LF?Aq%))QoyxW0MAXo{58MhnV~Xg-TXXL>}Tj<6vTnk>VR`Xij-K*SD#S^jkW@c@8_W@&)+pZFt%VE^8J$DTE- z&Ff|Fw!6?n-TGiI-nir@oauQ_?9#fp;q($`XIc4 zHop(~bt*qLi0XB|3*+jocF|66C~AG%L#9!uGE_Yfx7m%9Hae=tQ(@zo+~BTlf=wee zlhHQ|aF`4*`Ngm((FoCulDONZ4fj7;;V&)=zt$mdby8gvcCT18Xd#k@XFu!01zvH z^GcaIV_FBh@d%}c?WH5Wr%x4#e}sc{QBS1!&l-}UFZ2iw1s*e%c3dqphg8SDS*cRi zH{Dg-sV??_a@S71$^9YHoL$R!tvbcy`-97+O&?IwKMq~}mN#$c)KGZe#Z6f5c8AKN zbzM=AsbJsIV5etjGS_y0j>fbJ)TX6k$2bhrw68kSv9gjEs4{2he;MGHeAlI$K)Zb! zs)nt>B6i}^1SkEhAzejV5IStnH+omui*-d~5J^9@=7^%HPg}8+7u_w4UR&^8QXtri-A(21WzrXE-CF)-P zl(1m9-Gv+Hg_RY$B-t(7w{Q~kINP|crz;W;En^D^!)e%8@pzVzPUu)wb{V}diloq} zbcNV78siz$;|9emi{m2@q1;sz^86PluZc!?L!Qyye$A%ie&zcSU_baq$N`^38(;Ih zA~hI47k#wmfOaWr7DT<}Jux=q419<|Qhu+lrYBb2SRMpMCZa_;>s{#cxN2bTIK;-( zmi)M#0MD`zlp|p^p^-QPnA4YvNMfDg#@hkIDyDmM$*Dc7E)LbRxUn-?37_wv?jKc)x$jR2!}8)YvlQ&fV(5%dV zmUs!KDn%CQ&_)*g;01S$yxYebwHdU`^F%$EZ8>Ho$L{)-Og81@Hq;Jsfh2o5za$#L zUR+^FA!=NG{kvnKZj%e~oVk7Eg%9eSZ-!ZpFCkDfF_`BpLS-b<%}AHz(aNyg{?YO%7FSnpruTEPyyc4a+aZE z1+kLWU~1)kOz8IjuV{eWoKLO9C2u|CJX;JTnsUug^8di^J^tb}#FSR+6RmD$=9m05o(LficDJaAdNYvwVHd>`igbyX!Pp#+Pm&h zlSQi??w~`-d_J|TrWOFe-nu!(%EuYVFmU!X1>s@Z&kY^jzi*6G_RjnjwzS#ZO_&Jx zZUjPo!BA>M1z|zz)!t&=^7=@!1Z~9q9^Mu?tXi=94sH?r(Ae?DhCDC`b=-7GcD1o< z!yc9(I7ru0!GQeKxnM}TI&%a|=CzcBT$+pTx506XO1scacbPZTTVFQ4R~%44<4eX%-2cq}g627u80y^Nj0RGN)UDin5 z9R0P2$+tgc@>NV87hS4KQmspxk%fMh=5H9p#j%wsg#{2jmICBMIo={C>3ctWlgJUh z40*tM{kqR;CYuW&j3N3?O-JyrzJuvDdc|pi_Q(Jv`uL7L*Euh|HS=q4FMLuc$Od@v z3KjhY;l(XfQwav>7)J(0w?Tr;9==RtsD4o_%vP<}j?FAPOsX z0YGpl+xz$srV<0n2xYz6)eU&2fSvvJ969xTUDtPOer1sG-L<$;zkO4K&2Kis>)eis+aLGT{@>0Q?Gad)?_gx@x8snd(=V{xju%C ze!_KT_A&dn+??l3T}_VbjT64Xe*yS$Wocw~Ym)Qc)Tg zoJoOQN7UrD2Ll@*GDui=g1(Z(2hXiFK}J z{5$D)nLal%t2(=pES5oyH6Kmjr4r(DHEap$EaD4PH*4I!ss1h%APQQCk4Fmqr@AMD z$|=}(VRg;h999eE{zq?IaPt%s%(Oa7M+cLu6Ohu=G}@mm$4h%^xU6m`tPoSZ^Y~ zud(&F=7*b{OxXcydv7-@|KaAB91*8uBKw9Ngxf0byrXM3TVIsR7{oH&xL`8SH5wtL z+^$!p`4T#BM`mHZD5T-f-c&{W?Q`?cJBlbBwl`2|5W_Pg2 z^&aeT73DstA!<;0&Mh&$+9-S=%|k&P-2A@Dz#GLqYx>xc zvUpfnJ!||+H&1&aKC9yCo*%GvJtq^o9n`3Q$x~dtxy6Zie*gWH1Vt~JAy-@;Fo9|; zPALRzx{`I0Z#4WpYX!S)%h6m9DaZ1H@@V?DFj;JaG?IoFJBngLaiHz&<*lS89~5#E zgaPyTago84_2S?rHY@phyw@j0;h=4Py{=)`WPB-ICk^#%+h{Y8wm7%?0WA3kuAa<$ z?=_euN;0kQQp3uk`QuB4bzi_AqYS)d2xm*N{5X_JGtlk9dUhG!v9zKg#MPKh{;1qn z!-G51_Q19w0H9oOTol}z%8TO-Ihqk~gB~xOK>LEvcCyuS9C^yXaWC^#LN~z7aqRQ8 zLU4c{*v<*pH6JrEyb{8kM*kW#CD8H$kZ;(N1w%8?N0DT`h%Nwq80A?z<#9OahTQX5J^rEUOnI9b@_=%VX_7TEO9@$p62J z(B1~LyScdTyGDGfR{s(K;b#MYFr(1T95AI0)>(dK#=eoKV+IBaB6tgyCLG-u@G~$_ zRsry8&=aYHQyo!KI#D8jZ{QvOVYKRI0d@TcIy&x1-{QDppu&rV3_&8X)aZ8NJ)oSP zQJ{MwHhYtw;v-8VS2AUPeL~+pA_l@lw2~1dE+Yg~eo6ZZ#b00tT~iLbsjClHD73p~*k`Si3Hc`Ima zL-C-r^(8<74{;EDmac-EUgBT_Q}C~dPL)T%07Pcuax}qz-v4@_1HPiJBu*6b1<*jI zek=930eQ=R%rtd)<6C+Fq19L;QtjT1?qT%8LRV;6TUWNb)L`v2kmJ7Go~NxUfem%C z@^2d1?FQS1EYv+bnJQUMw#zW$UWZeYKkx2!DFC1|P#OGm80?q#6C$1cgo$mx?8)K8 z!~y#E@88$0PR4SNE)QV@ps5qBpoxieyL`u`YV)ExrQagJ{A6AN(Eb<6hw-7*CoMcj z7#O{*h1q?MJZ(GMDncasLp=gKja37$JJ{7Ix5ZSc>OgtpzBsVOc~&rTW`vH2o^kktXKm zpaIF3>k6%_jVcI!ebG_$;Ds7jFw_lzsLYo%Y%wy>3GEmI~ zBz^|!Z+~lD{z)5g9GcW#xr>qHw+q32cuL%#(33pI;CMk@w+4uJ3_v-=pkBR5?~xLk zr4SXYqQFr4s?z*ukF;yF!beOkv@+Q|CCU)c`WL2m+_XA0Vx%IIF*n+2kpV(rav9

#XVTI?@$m3vInaOwp9qL+6}nN;Ze+H*92;H!K0JPi^6Hl;$roJ8MD z^4rW*MSON;OKLVGuxLb=&HdF#$NXX_w$pv)i_Dg%ox(s92uavr`7zni4t`g(QcAPP z3pqZmLQS&iUynOe#q4ER=P|Om?}v9)-OHVrl2X^k z3*z{wr2rlQqzlZpKg@hfVU%Rxz3%1R@`2kWKH>fx!DST;WLtV7XHS%=-5rGNmjXJ2 zFmvRqpcc(jNgn_-ZX9e3>AH4(%mT>~b7Ep{#4)Sp=zJo2lfYpt=LQr8lv^G^6rVHe zo>-v|7dU;s&cAb`gr-w=H8-n!q$c^>)cXO9k_vy|$Lsnyzvkin@8tD9?|zb*HkYSA z>!OgP1X1jjwf!GiXB`&h+OB;Vx}_B9kW#vlRKx-VMHHlK=yIgH8&sql6%gs}loA*~ zI;3If?)>gY*LwH6_jerpvzE*-@jUlc=lQ#azBFx-NT32_@kbhBcmx-79Z?tVql11H zeNp3UP4$%vIXp30E+0ElOCQcQ%P}oT$*20u2g23&+ovDiT7pZKJ7&7a1eSj^w&~CK zfaFq;O|^+&VaBJiiyu4AY>w{%d2DyTp|Vt}G){o|df0^3?(e!;lBn$LjFsO3AcX~i zZd>=0FbUEu=>Wq3j2Rz`IV6ptzI-;3%sKBD0P4#=9hda^NIHouYd$!YiFfmWkKKwe z3I+~e9yCc|B1zbOBj!}D5R3A)AO})=FfMOu%sMsLSr2CGp}mp=6CG*f|+Q_ zdJF8H{nqPNaeBj-U+vjDMm($_Hla^x8J+;~3}> z@=b6l$=LOK3^$+Ek`|wcC>Iwkc~*C0qFjK%mA4#QnAc8qm?*uVesQoTd>*Ak?`Pvl zZP-jYUS9vhkLk;oTP&jUMRkHh-!;2vBTn_hs?7Mo&=;XfCwM}EAHZ)UQqOXWTWD@3 zy(O(ZHUm*Q6xc%QGanob^)p9-Qnkj;HRnYYGnGQUdppm-(P~-I<8qJ3+kS>XPlwAI zasxwZK*FJo>7bij6ePTh_mr{aRDk)nWpyd@UO?Ttj4dN~o4EMc0?t{RQ&O+fqHnVQ z5-+TZ6(j(fFLG{BU^-=0-51_19?VgIiE4XtuMXx1A8J=8+J>?`nzI|(5H0Drg+ptq zQXrDzwC@4LTu^5FIoKQCy@S=0n0CLa*wo$rb!l~mVA3oegXbmXsk9q!{h1jp&z6%f zr-{}UYu+2?tex&O9vT;&X`s*vyPUlt=&<$ZH2t}+OXkD9%3h->4}4<)6vmXk&2u(<4xCHEmy)Rx@nL?7_u*a`yA?Rqg6b zlps}3({|`b){sO7Znw9OZ+5|5hEKkPZ*SWQ*^J}5G2@;cUle@Jw)Y@f3+(V@y0ck6 zF?~9(CgQ$yGLh4%qf7SqjAHjI7SLBgLVz$dIn0NVR58}}U~X1>J{iU4r~gkQlf&9s zPD3B0plL^++NElygBq5UTPHaiyf!#>oaH7WbxCkqBrfA5ZYoQ@mAS6S5P0TtfM)YE zQF>D%>}pdX>S!vFH!%*C+xA&jtNyL90Gss3aM8Ved1MGdz?n#x{ zp<{o1N&)558Co!n@C}KHGp;!qXw%zVNZr|=kFNXr@&G?VXRWy4ru=RQQjFzii)5WPnZgs8hbCuM1@ogWLeuuw%0m(E+3jX zv6`?IY&ibr%i|y7+JOOnX{HOFDTbGEVp$&*9>U!#Db`e=&;KoeHHp8Dd)e8ADU<}n z&C);|GQKxV^MPMnYU*V9g%V&D4M2%8ny#r*uN6oEe+Rsv!@@^zJ;(a_)Ev~+3b0E- z5t+U7_BR^0VfElrU)Q{(*8U?k0T$b*vUW)6)`F&sFj*Bm6B$jK_%#;T(l6utK9LVc z3PF1G_s0P|&T=IBgEsV^wmj&IL0%JheHx9=C{c<)A@W`dh+NiBvOzmW^HJ&5?N@QLT|TlrzD3D*L z2guV=u}R~9J+VQuUZj0PSFqoeegpR=GTJh0J91sZ0|ZxTaV*p;PpI-nW|W9CZ^fAl}Mc)_7wMi zT11JUwJ2x;`2`-I)}(IlRX+KX^8$^InEmP}aciJMT|sqv7tA;rFi&017=p|Njivtb zy6@D*HK3boxiRf#ayqjFVnpA@;gp_4g!^#^Dco@8j*X8o5X!XgA2omXwL2|!#j3m6 z0Z$~N5jE_szYt6Qhq=L`d?z`G}|ZVw^is8${87v=2rZmX;+p^3Cf#r*zWrR z5*U4;eMPvGZ1GaiEc>wt`lp~L-c6h{SZl`8QeZY80H4|!9`gDbZP0B8 zQgfi+DHR`PL%^iN5y;6~iy>ZCpuar<=QIieH(tZF(Ha{D^JHDA-;XGvM zUe|S{Yh)h}`!)X+?Y#X*wBx;=kWfq~F|YC+PK4+tlFFXkDwFJ;Z2$2Ih5P~}9FLzp z6KQWMon;vkn|^;Sa7SQ(yyq{og@ePpRC&wiFyqi$Q5FFz&<@!&qhPYpnk~x@Ym$zB zevF;+xoz$3sY7>jO#AouDgp?F+oL>Bu~pl`L{dzmKJ;Y1{6@w$*rMT2Z%%)MS0}7S zDBU!|$8jEa>RXNR(o%?Kz(}6v-B;|)*C1OHh90Rf7C61O$bgFLI-7`dXR#-@3H-Lx z{1{T(t_?Q;ecNdu#%igp7GygnyMhzzArr9h)7Ss@0>WMF&C}J3Omj6G(A_YTRo?{$ z1k5b=J0;z9Kh4dw;;q4)zT|83)9Xt-?+hwXF-|Y9{Qxh^*J(%C9?SyINln!oc%V#Ub8C-+r?M8@pNH&x4l#8@`Ofvh z+)L!5`IU!kpEy$NB{%X!?OV?n+~N0yQ#=>B$t%te&lqM+-CzmAW`1CdM5;{Ww#dc1 z+aszK8p>OZHZtXg!(%zoDJeXc_d4~FsVPTbV9RMQE#_j|2az?wn$oDNa*%h4_O|I9 zZslAF*ni>#r@Kjrd^|H+DxOKvpZedEt&s-fDoGRz!FJ)7T7S~*_hvhw4t%FoCpL#) z82Be{)AU{Ob7}DXRy+uPwDl+z$2HN=+Pb!0&?8YCop4Bxp`hWIYw6huR%^y*TdVf( ziW1Uxpwn{~lB9^-18kq2W*CQc3ZK9NmHnhN}N6G;L+B$9d44gq%-CKBiXYK&tN#Qr*=yJMbWq>|j{BC$ z0jq)DCEhVs&Qs`Nt%Jn%Q>ufyLRjgq7@x~`uqSe3(HYCul| zdoNo%mxN)fCj9jr`Ve@<%lWGnq{ zox;VVSe>(j%I&VBZ=z1j>O;)h$Fg$&#$g^jrw3xOTyJm|$q{&|sG*YuJQT3Sht*aI9V%xMp&SY}9P*QNG^|Hxk#QoVR&P^rmpuy25` z24}>@qFe)X#E=Q$Pa*r_;NT|a^1aaT@UoitVr2jy@p?T&fm+`)evd8!JeihJ{_k%d zGRgAZx*E)N=laO{=e=OSsKM7dLko^?6ytL+H5^l-PuTW z*BjnzdRz>=N-p{wM6(#MXZ>2=9jsIcj~o49L>ix6g!kQ#b%d-{3S=)ZU(5#(t!6y^ zn&gRTHC{_udm{RNDZdKjLr$^4Z|Uf4A(Tj-cH?7_n~13D(qdHOsUUw3MbF84v(VW^ zFKe2~QK(J5;Mp);^<2q^&-}n+2kCktVVklcdqOp+Wum_co_9TGYna%m<9C>QsoK_~ zs}MVjJUgS}lfoDx)BK$jkofTVc7co|a zRO@3E3|Ubh`9K>h7i302)+?A4%cH4}y)gd2e0@*YWldno_~&7Yy@TGkm`vX98Iwvf zxnLS(X`&&z*EC+Kc_AU8!TEA0uhY zR)dAo#PoN=L<0$1(_0>0t8gVzKT6#4e3;n9(^ijxvpqg@RP^oy|5l2aH_n-~FOWlh z)=kCft9`=4Qg70o68lvHSwx~?2u^Hj5}6&Ym;!bGE?z5tFa*?FJEYSnCmMuZs)LM) zLJx2L%NHZd-{lxL$}A(y)ovSt*~(Jj8>+AFY$8Z5q4eJT7DIEnfWlEvFi~X z-&v9fkb9XuI0d5onS(2-&{>9h-;NGuabbevT`){azkl-9bi6V>-omY!UxBMVZhqIe zD-8@u5xGiJxZ97|r)8WizK%0rQ-|WBg_Lk2j4bW=7r8J#Za+a!)ia8EMTJm2XfM0S4h06d>G6eED+j=&O@FF07Bn7Y-9^Smx6yidy%CuE-~ zuKeT)2rnW^)WUpf4^0)*e4*b))y!f5AkbvAgmN=fNd6jc-r6ip{(Asb^cO!SjCg#7*dGVSf!T35VU zk|dDpJ)QANK>s6oaDHb-Rfr1XcU6m8V;?GGS|Z zcyefQC1}@E3(ko!@udKX2>3s|317Y%ads)%zNwhuPmWtzZ zAePE|TjIE{gAeeZGDLrJ0bQ;5du|tphW;eo{-qZw3G7CNwI8}lV$P17uQfcQ3B?j@n+3wGhrl~VMR3vb zx`p3ry@KuPGn22POl|Oc7;nitwVL6~2!v&XxaTU>K>LF3L;daw7DeaBkGI`w9WAGc zf%S}EvBrMIFXO`pI9+AcY_)|-y_N=jhSgeV6Lf3D?=g;_L)b2nj-{RGCIp(?zRMd7{iEFF7P7M=r86aGiX-?vG2} zQ*%EhK%c-v1wWCII}av{@fWj8*Jn`z$PPww!YdKQUcd?hOq#E;y*EeE(_`8L z{2_YLN^g04YJo%BO2L+py*g4L^O#g>TRqT4U8?dsr{R=+&v(76tO{o1L2a?Sa}IekZn0n@hIT$upz#_H?FTvWb@MTf&{(VU>YpAY9hYgiOn zUGb|<)mU+>(o>QKRM<42mEERj+06^=t2BQC06|y~MB_K-_d;V`SW0E4bjtc<<0=b2 z@AN2zYh+5JLIYQeO_6JVxeiuse_hpzLSiFx<1ySOD7!IZX=-dcG|pg{CS%M@9LsA@ z{V$5kJB|5$LFf2WF&Tum90fi>6E_30-944}{s|`;jU5>GMNS}sOJyXQ ze93*|$#zw#U@vyOZ5*C6)DRQ!i803Ejy@IBeN#ir;nsdp$L&t^@Uoi!;&``uv1dow zeZl}xiIl1{%~(|kCAxq?d(PA#p;GF-E!mUKZye^%2}*Jnf;I=%>!m-6>~TZp3srDL zYDaqZTcx`mJWA6lVvf^yuHpQ+Wvx2k7jk|$Cw%PC53iNp)D=;~=Yx@1)oZGFoP3nA z!Svhe?(2X8AruQ)&^7T;&HCvmD#hpP-C?-OccxtzFq6z$Hl5B(78wuem4t?<9Q28c zM=d95Qgz7aseKkc4er;EqH^d0i{5piv&`I)+-I3eDuhjt(8_&(W4v=97aQLfu}}Q& z+BHm$$#*58TQMJ}-~pXtb?Wsc4_KfsQ4Cs3((zP})rDS8Ru#4g^cu=)BH|Lacyct@ zl212clVUw;4bGSllZ(H{*am%Ouk)UtNABj(2VpRs4D&wrWA z3E-!i-ohV@;0$0>Q%F})&o)RxbPnqb6<2@``R9z{f-f&B$kR;OT!Y~ipVb1HXDDH{ zFWQd^UtfVAzLnGsY_+*>Mu0dIR3ohsB+yS9 zcQHA1yi+71=D>_s@y1G>Gdd>`=O$STzkpHvK*4p(AKT(ZmA}YlDdx8hP0eb1NZfZM z+IYtzf)y%0%gu+VA0dg34?O9(YiQwa1L}!hOtJ-db?oSZOfvDf^9ig87e+-!nOk_H zgD{<c<=2>FP zfN8*qvDe4=NGI)Scb?-l*DT4Nk^M88*Sh(meQa5#J z7zcI|&BUtKa6+fK;t46eLDUW`kVK;Noucx1% z^3NH;80`^*_NMt6RY9#i{I};@<#KEfSPU83m07*#+*?wvtZz%IKB_@ulr1nRZ5FDn z|G0qU)h-~(MF-c^EZQ)?UV*RE6b1J@)BMnGqJWWa<)ecYj*(bJ$$Hs#hT9=zI@d9syY5xI*}%Hm*fI1 zK%HVm7@r&aS#vo?yZRSM1KKz$>2Fp^X%>UJ#LPKY}4`i9AcSMM7o&t>w?8(4D308vG14-Iu8Q zV=!UE903*_NO8T`*W?I@?gNtx^j4NlQQj&jigQh^dPsq8L#|R8P!Vh)sS7YQhm_9Y zYviz9Hx~wpcvsJt;Q)I1=Sx(%!QNv%jWmuytBV(7BBjJO&;{T1a43- zJ7np-S?Vuo6dh1{?rbky@eX#*mxWS=@z4d25GUzo>(HoR{H>~wY0w*rUITSY%+rAD zerQsuE;(}daF+`5JaQNQpb}-s(hPl4%Pvi8EU$Y{l0kKPbdpaV!#h=c0qod8p45qv zeMh=|4c$JG8~xJs z5M>)Kzjma*K0Og_*~mC?BOwx>HOU=Qr^O`#O{Os1G+(_R;hT-J#G|_8=N}t>o(_tB zJ+c`!>ASq=_71KQNwU4l*YBLfH90oelr1W7r~pLM>`#s@aEeBRhhwEKHImqDX{M1@ zVeg9t1NM{bHlzvK&Ua7i)oK>+do9+n#NNMth83y&*sED+w}jZ71?GLv3pjn8mLiuM z<37NdU{~NWxs3FWI8SPHaZa!*@;NUKiwG(9=N5oO?xL7Zt-Yx&!ftZhXb*{eH~|B- zi5aJbf-_-daVF57uX_ZESYUmV@1Jb-38HVN02w~4H!_3tL1PX0H0!!FKUc-0)g^*T zjz1lptS7$tz?^NwH`cW7ZZ9?^4sIck;^ zXJLD&11EWlv>7@7I7kwI{!3MY6qgHj&IP_jy}WO5jL~OeSKDXT2c6$?LGUvj`Vv~` zP(;XZ30st{_1R7CH%U^gU2rE5#(6y_5RE_QJ|p|JrhSIAoWH1BU;H-eFhiAOy#9T{ z6``alLd|a@MJ*{cmE?HUa3c2J_T!jFre`_*kE76v8NFuCTUlQWW+D~B!%OXVi$Noz z%Lcu!O9Rgc-#o@7T7U*|Fs@FkEa{zWSHo|yS#Z2@)W8RtH(`lFJ-Y^5KgLIi+9r}r zhvNB+UEtcsT#-862Sr7CT!N8_6{hGOjAz}9L`j_J`9ux31i4>k;*LgoaqYr@w9W~= zH~@46L3U(s_sI4;tAXKsM22RU`LXHJ8*=I*`z!uitXUZsY4OL4y?K>i)$A1-Q-07f z@HS2k)z}Gejt$ZX>0^~^8dSag_W1mI&5bQZooJoIU%Xv>hg()0np`lMiy|zDz#>l& zm~I&L^mylpyHdYn(z2;!`l}`}BY>lVutN}F{6OCCvwiaJvD0w1<-`&&<@F!$!-sCS zdvQv&+)YD?e>b8`-SY$+MP7W2vOC>3qYYP2&nmq0biF=w=i$-P!*d?>3LFcE6z%DkKaKW=3TTuzyU{o9 zX--$?`HLSdWSTGHTW?&xP!Njc)x-v+E0(-HsNlis3YfmWe)gNH7xiu9)%JHd-g3Gm z2OESWi}wmnln{~23y@275w`1TgU*DpbKH~X0PY56{Gn(vHO{Odsi;fis$5F%Zah#8 zY)L1X=H`;CPOh)LQk>W`nokiTS-(=!^?h^EHq&;5;->-VF=1D0kQc{DyyQ(hY`+na zswjY(d}saYCeq{Tw?mMI>0Ld5)Xj*$IYB}XS@q0pBr~{e5CBA#X+2>6wR=jH#wErX zk!EoPC}7^5!jbLqi~Q5VKx*z2?7Yv1*vhAFZ@#B-LAok8l~t|+~*Q} z!Y9#r+@505d$31hAq8AV0JR{Nh$eI6_{7s89|?$0cz&au>v3fAVd9=v<2Hv1A`E=4 z)<3V`>89JXculiUE2!jIE;ZHg`D-VNtyqQ15!X(vd&*w5*ff2AAnv2t{Q1RrS!RKZ z&Po`kvaUb8ilUK{OJW{C&;ZCxG4)xK&9lkUZ?DAJx>D4KD?PaRc1ATmWW+lo5jI;a z%*>6CTApv;C70n_v9e7uDL0{Gb770CwW?_Qr<+cT?vX#>Jy%6KSdxgBU1JFqV2)b9%K8Th`=3%^7v!p0EKseF1VCCWwnbK7n^xp@U}~SsAmUY zNp}EalkHXYz>Ee5MT`WkHgC51xD1~`QE=5#cpzI#cw+7YKL59BqJdUoJpji3`03NO zm4Peg)nwzUu~=SR9npy)c)Ix zp*NbomUZ3K1a*0a_62@W$YaE2yrR{4jO^y8jM&U}l@O@NATse=Db&g9?qG_E24qby zdN_Qaz8<%hbUS|-^;tLTG~OjnBa%~aYfo@QxS1|CIr2k;b)%J>r+l1n;}`aV2Rj{C zwZAMAHQuVjYNv${n)*nXbxsBse1l*g{q< z+splmT8d@9-!*<3TkMA*V|$kTL5*_pEX1p)-Iz&3j@x`~zQ(KNc0N^fD2qIYkN zBfF}h`nml2NoCEGtonKk7KU0v!rpAPbSeON_Ag+l4jbw&xSO&kp9t)M8M(UdxvuA3 zDWet#73SLq4{-(eeMF2D!-6kC(hU!T4=LRAR!VobJ+=yB=*Ix{oy|||cV<5}f2VMy z#TT!}86!WbB?C2Jx>4|4_Ny|1{4L;v^aKV9%s+lXNaD4E>Q;q}fsi-Qhirz#;*fM% z#{~HySQ!l=9pr_2>MaLy^{Q{IPg~^V!bHV)H=jGb-Jdp9E8(KA&OvJYSaFBfl?d$F zz4)TS@$-7)A7S4yM?`po_UwvRTXv}R=qp3v>F`UT1fTNQ~S8neYKfTf1UYX&;9FQ z{>q)p)8Ehq*aGaGTG0x?E-&;I;~Cbwy^&u?-JW=fUfSlloB6hZbD&TZ-Q&h~N%PdJ z>h`L#YTIYZeMCcAzSAJe1sV+)Nfp6M0 zlwvy&){WglF*vP_R+0tu1`F#fCCk|~#gJG(XlJ9e+n6Y^n|mja$a&)B#qL)^l9|o~ z%0&RiN-ZD$xHA8G-Qq|xTdHY zV6W@>F$S)Fn>nWw%%+Hx6>hX7J_nTS#5>@*!BeMMJO)>34f%U_DDh-&v?WqLutIs9 zfkxMOod{jl#}|C(Q=#EUCjeRAwh#%=mj~@GrPb|WMU}?|qaL_22Uek{(IN8*O(iO; zms^UJ57o0<&iTa#RA~N{w{yU}<-Jl^q@J@B4!ht;D zyw%Q8CI$qx)M@xC?xL+jZnYZS4*2n2%8SQcw`-Qq1)M4#DNH))3zL&*R~Xop2o>1@ z5A#GgzSN(tLJj~&d#5oqcIDTeQwxy~KL8&W{)5I7=O%{VSgRe%)T(?n33t19c?v=$ zd@K@P3cybOA@m+r?~?r63;6$Oy;D65NJ0A5RPNrrr6U_m2D?YR2SLtj+BJhkH$WZg zN?IO(?*fDU?U3n_;yx%4ygKv)3H0!~-u>H2?QSmEkqvJO`#;&K9(CI3(aZC7b^ZVR z-5&E}!xoPRNA%&K!Qn%YlIk~ig_!0D{pt=-{jAbLA7q;EO?q)V; zW`55O+x$jrsRImh5}^5)c-Ut0#(u9J6H&rwm@1f;rdR+~uhixcLiX=VoVg{tLz^3! zi4f?Ux`Mor(+SCjMFI~RUt=B1o2$SFcDC9Sd|=7i+g+!__+>@Nnh;-(zoBB#n}w8AOKuKD@VKuTz($(HoK`ze-c zMS@kt{|8m;n-yIlrF%k_j4x$~RnLmB)*tUaO))Bd6qmAj1x*x^2zA$Gob$ zY9Us5{47!U1}7)yL!s9>>gc&6mHO_I2Ei8iZwE$uMS&kx2*LY2Lkvinu%y}60oPTf z&YV)qpa{xSeB>KGn=d{EZl`M=R|vehd2&TlAcjIgzEKX%Od-I-=_|F9ThPSibN$J!m7Q15j5#d2Wvf+C|_JcL{DN?EAOc>1wM%=_KV|q z3=28^n#eq4usoR z=QXfSPR>m_tvW0$^Z<;VP#zAp?@s{1*~wovZ@i4u#5wsU{!%o$a1ido2d`;d6KQxJ zKozA?X;pY&T<5tYf701mmBJ$@rS1$A{kRp9<7=YU_kgOJNyQ*ZTohP3ffSHwZ0^O^ zxAF+p1#h`PE*rO?s*|lP&zKVGW%tbXlbrHc-=bcHtCNuh30h)=$LYI|;v;{k(17q9 zE#F9l2^1ZEymd(b=9X;;QSH{9rc_5ITAMIA?VsmY!g9+3VO{s_&eYYiAbHNt`5l|| zmT0RTpsr%t`O!)HO^lYf594f;hSy+p`$Of-ZiJKhOxN->>K=LVdqW(L7v4oZZf`q) zkvRiQ;Inn|@I6XA^tQiIyCt+?^(5%E<_L?~p9(>izGXY>$u#=%tx5k29AfJ0z~Hj< zgw{jAIR5YE7J#^*TI=RK`MEav)!A5btSbFuOnO~x9c6K%xUv^L-@fAfq52#-~ ze28X}IKdTm#YmixGrjnK8Q#Y}Xn+7vhpdk)Y1=1(Zd-At&>#-W`@(t`V931m zCqm+pgLkyshd%O2&!ng#W@cT@bMR0ETbgp7;|DRvq3mLuWt;EP1q(^PhED-sjiSM#e@&@t4}I z%kUuLxp!TCg7z@xKfPo<1c(U(5Bx3P`+TH>G?0*O0umpCAE4W{6`CnYzDeCiK`S`S zwV?usB;wTMm3Q*-o&yl{Q5Uha{i zCz8F_fKH?t9@ysfUW%UDllOLmsG;l+Hi9x&tD2BH=ty#s1ft)C4%2PV$Kk~ZAH7DL zU+~WF{jRWR(b&6gG&bFTjH!C4H*KPoSb5;|SNg0S;m)I5MZFUvCH`vvGd>y8qf}i$ z?VoHD;BLHBC3&@rF|mRb;F=*zfQ^%w<@n_wWkqD7n_IJDe!jQie!Rd}2mt_m?!pkj zt{=n`ppZ-`|HjD)*X>gRM7dw5Koymyz4+bzFX_CMcETE{q6j@Zi!vD0K$h~k_2ajH zk`zYJPEB*loG~W0q5guqWM@sjHZajG?KH1%Jz<<%;nsRv^87~$n*`8(aA}vrYL3>i zq)Id^w)jqvJzJhU_kj6t=KqSP=JTwlze;#eOZ00<4O~psoheRFIIIgT0&%!p#e}P` zRRi^fZ|B|uyrr#Yw8Tc{89S>!U>T}cxp22BCj>yOi7JF8l3iGs(vwFc=FXPT#IYRy z&ZiIT%E>-2Z22FbNo{<4{qp+7?LRJ!2aMTxD%&q)5e%STvLh{lW#^#BQOcQcg7a!L z#@-oOE{RmQ{fdh^oU2!0;s>xrjF%zXfh$s-rUv~{1g)`yivb7LULX-e@Xwf$!{eO? zZUS=_wfk)~nUTJM#PdB&a(2txANECVk|ep)7Oz^5mfnQe9cRR}vJ=Cx>h~QUwyj#r zC@V^OtHvOGS;L|}ek`(|Al4cz7Kgxn+!N*VKtB$=Y5!0k5}m7m<1*MU^Uo$FB@up| zF`W54QWWq9vDv`N*z#M>Ypx3Lw^x#%qX~xjnz>tY0dq)bO0@Rg7@LgpkXXSRwRda3 zf`89`41F^<9`QPUe*Ir;S&-{i_3xLibNy#M&M4y7347XhuHDjEJ>69ViqR%#2Y=jJ z9ci)MRidIji6Zy?cNd`qLeTQR1y7X+EzL=ojcta_;%K9$`2V?3JRW+%5`t5V@Xgp# zYS^*@<5jzEry8dyBB^GVvpEC_{n<%EZt%9zy?F7lM>>m0?!J0f{^H_N{n81_4mCtU zM`zVWfM-X=D=5{^#)2kFhK%RQ5%^E31@lMqFSf`~&rDl7pCc#h*1cyezDKb8lISpY zdcjs~cc}DbFj?mB&APMh%SLZG_*Re7CD&1=|0az>^O!r=(4RdWqk&#r;0WmWfL8Y_ z_s2D&%?tjrYCdks#S8rwev?ZZr!(J3OF@7+fON%`D`w0NadBWt!>fD$v!vnJ53R17 zHm(RcS#wAH{;%^w;z#2t7Xf~LZ%tmD?!+75(6UY2!EK(s9W9I`&=is{Ncml4)+O>s zKNQ8KcR_%65by(`&Z2RZI;Qc+LCoB;NqTkKhStv`k1Grue>AOg6wD*2wqaCkhc7np?H;&qZk|Fdj88PCX& zrVcqq;aV0nHJMT|71aGAecQHRJfrE7g>m+rfq)J?KR^vh37#9GJDX0v03K-u4L%*Cc!f`{io&uZJt8lxG|aF{XqD4$48+Ix(2ix4_brZs~;`=KWj5U zp$a3%HJ#PcySw^tP5-yDM*=aHG{$`i=t&Ei(BuDp9eOMv+KId&jAw>jKJbdP3Q>N% z%^qd^{cm&|!NVN(0@1K6CE=f~Tz~(6AP9o4xVhDo=n~}bp_$U(A7@Cx>@kqX9APO#ovQZ}pq9GDjZb>w zq%G)Q6Nz65L;t7$i69>GUXYx&%wpuBqGFit*rw27aVYQo z>A)@dDuJ)F;hsb^gYwZ&DG{{h06eL8tPnwYk*%0=Wbtn=zy~}bcZ?#GnE-nWIMf?s zFgX%dtTIiP)JlD*PIgeb=|CD^XmLb8&;uOU@dy|Ayu(&2pc+y2+N=?4^KVCs+O3b; zsoDU{r_<@l>!x!}SE(S#Z+`Afa6SG|CUpfhc0*V6B;IORBL(i_9hp>xFXnzI`L3o; zblhdCu&{0LK5|5$Sb%7NKF^oA^rCJMT(vjryT6MpR}Pn^&^g(UXZQm`?z*M!_~=+ z#B$4)JW-uU9jWSh03Ir#ZhMwsemFUpr%g_f8QDV6E7>$aI{?L|^q)3ihx_qZvFEIiO4Nb;XCI1`5H~>G3 zNQU?J2*1|#1`=V(Znbg1&;9ngV)z8EyLZ0VJrzZx<8E~IA1$tT91tDYsR^_Hb4+dH zz^qF@+>Z~wA2)kLk)k=Cb=Yhw!~bTo%w@8DTN~-x79NI zzIAOA(bE=U-aSfDBnGSUO6mt*jlmyw(t?))mM_pz8XI6p%p@H+vWGLq zIVthSY3`$@9`}1M#srm1kJrEVQ|%l$0z{dei({B-{l;dX?dqV}yUOCc&aBvSvw7?# zr+PKVu@JR5F)KFul`GoIH^toxh${Q|whfFH zz<;zn%3j?a@A8$=n?q;-=VXw@8Zuvq%sS6$za6?$Lkz zstQ=Gnae{ZHNna7Wt>z?^fleb+3dHbZ$9^gn-C#}uYg?#Cv~xQeOk?A@oX=co#Rw6 zGOhSx%|Ee!FRJya2%OJy*hRB}&DmM-vYSYxT;ydN)=hLJQ7T*&zV?lAvZ}~% zRM$B#wtuY`czoj^XE3kK(Jf|XeBb*?8_t4EYjokte9WKj4+`f^SKh=pJ>oYUg7Ar( z(j=3OIe z!b;_7sw>e&N#FY6E$~&rn~4rkLnV&(r33x_#g%)<2A&idEK(aOf=*nrYV1uwaemlI z*6N(S@DQd$jGzhK1^q3XS5@i$u@(3^Tpzv^c(6~>F9ZgoXcl6vW%>(Gt zzg&8du2v(c4x%AqJT2NcbtqIJuk}G37;H?cP{n>llA@X({nQipoPZju=bneRfGwrh z{(~Cx4Ctqdu>)IT>nPB!aB6k7%|6A#V@GJf>9BFp+ZYD!D0%fC6=F z^v7g$1zGaHBg~F?FVY^$J4;&6g&#EfLMj~G*V>`7eAi}-Ri`%S2oXcHAhQ));hAxR1dbbnqqd^*SFZn@=2aO|`p#Oj0r=X|r82zE_04aKa>3bh*+*??z z65yB0TW)t?lWrqm{m-AzEWVUU4d@1$Lg7vDTch2#;>i~#K`icO{A1>WS)>AB)sNf_rqGYtOlI=)=ZDdjav(pQZsu+LQ~xujHU*>vAb{8wu-NA_)UV5)toP{*`jM5t{_tYDjS>xYr>JlhBJClwz+}xuR;J#zJ z1m;Ix^SVPSBnytk3%|FJhJQ9nFg9SiEm|Q9>dbFQLTR8$9>&{Dt2MR8ZpxZ#4(%iH z$gPbh-?vXIJq5WDHkmGlv>Ddd2G>ARn3jg=wqnJTyC5*Zghqh+b#LM?qLOK5mV+kv4}%>lFFx+sDo-S= zXd4MTB)TjrdGBa zn)|!i65&}Ae%Qr)xF6?|eDyvEw>GlrMU8b%{PAtYLsmA5P=OK_m2@Z4PaJQSb8l0$ z@w0}+)#}pQ1a>s*Q%Rb!*6UREll450+g_l|3~SyW;|VW(d!`Qtf%voOOh8mCVya$+ z`Ba_~HXp$0^qIMt-$X6RENX12uShlSa*Ic`bZy&C@l7>d^i%nahgBSe^&7BTNspU> z@fkU9fJw8r(_4tWWE12hRp9Nf0 z&idqJh9v?K`|Ki3qQO=_xzkFl@iDgp?=q2R%jt*4UA-qE2;!LNVM+N$LCgiKL&Dbw ztI{BO-!xnaPB|}D#_yxUi8KV_pnkC6^`UIRbn@X3tdJ#PiO95}(5Rh-<%Z`qSL$Eh zT-7-!?Cn>zY22^;;XXZwjJ%PFGL{@I=0*QKl}Mn_ZVAA3 zkQ%?&Ln1aVD$`XNv2iXNjRIm-52SEXvVjcu_eqik8T|y(GI}77`XouDTH^N3|yHW z!NT`Qx2e#jqXRv+e;8t98Zng1fR-|jQ8 zgSc~$C{wBMhxi_whK}&;=WEJg>t@!t$vc*=h>|q`xBsxqrd}^nc?s|9I`T$l z7gzl64zwb29@AMw$6WKl$~tEE`Qob19%oc>Zf4b6*%d9?FETK2(nViquQju&4L(~W z=Ymv!&|Wzkdh_r^-W;%3WZ=|@A?0b9IC5*$Etk&5=zML+qNkRAO@-u0_qoLH!NI|o z+t|%J7x}UqaR#O@$|&&t{cpF4C%>N{ahqbY`M5~Zs;Q~g9^_2ZO;oT!qb4L;V?9Yu z60l3bNfFY!S@&b~P1VcigWq4$5~@*4vtLKN+IR?LPhn002L*0B%*Ulit!&nwqupi4 z);nx3OFe>!D|T}YE_h++N?uGlyCOLIWH{Xm;5>$N)`^l%y{p*kv(uW+!1wU1Dk1$D zhV!gUQO|N0+Vi^G8lu;)klf9{Zl+R&ITc^~(@$kmdi^zd^A2#D+#?;om|HzwiR6we zwhSB^FS#-i71=;q@00Fxk)^ls7!j-yUc-dSWko27(Dr-PzaH60!G>mSvQ){Q_Q z6SS>@vw3Qi{*7Fb#wF~Sm5K{dZ{9~JDX{CPb-GI-xX67*-gxJBw;}F}|3}zc07coh zedDmK3eqLg2vP#l-AX8}AYDqV($dW$3epHlw+hk?(jWrTAhC2xH%sStUf$3Byzlp) z`Q|^v>rOEL(&p$w$w1tV0wwB)%78U&-6!@BH zY?h=EPkEJlh+R)JmmOq_X7cn2xUL?l7=uYl^cn@YYnwBieDf!7mRbGp&~S19FIfm( zk!3UVL)Bsb_@Pv3WEQYJqiI@9vq*yu}69ux3&g#o-~ zfan6#S}9f1^veiIFny_arn^5+ePv_2bz$@2PtEsp2IXRtgwuwXae^@}YccYhn1Jqw};^S>KJ&QxY zSHdviz)z#JFEk7h!-M}@3*hvvj`6T`cm6q+IiWS80EdME-j3Z(ykdF$*;SDD?aX_6 zT2beB4NMSx@i5|#kmuf5O9|SAxB!(RW+xqk9a)@a4eKZ2TzSv;G<7Tz8+^(utcN9N z5yGx&w7V=a)7|mUXbyKZAGdv06B0cj|FzgmvYdR=7$NG#3@liCdmTiTm85DnjyuZT zeQ*9zwvKD*&8R{?mkIm2{OPt-PV3id0(k}ooRzF5q~Qn>h*6%6gT$@Jz-Zzw^ZHc`gv^vUn@Me@R>xN4p3v|Da$A z&=FY~P%FyEBK}2-*_cNT>ezI$%WiqM>aHt`+}IwsUSgc(g*B5EexP`Nhu}tRohz+A ziam0IJIDWOsvlmb#Cup|3q+8-UMRP>eQHX&e!|xB0)UThzuH3cMXZGYd5T#=n<=st z?mTAk#|&?&nF&cXju?sF!eNqzOrrE#rXhcnzB2aoeEv-T3SVll4uI)Qv5BsJv$VWF zKDU?3+Tv3-yn9hZ2JktKN16oJVPWT<}SQ{fDM@jB%cXXu`MHnHSA;YvbEQF?Q~yxIo~M)2FFnC z+#ZKbrKRdz(6brJuaYxn4mG|y-_4B;)-L4@uo|H{*~H}xnA_*sWs%KpXMb3t26CG> zNPzHvWBNf-Ql9>ZetuzBuny#iM8w~WYK6$wY2g#miy*C*QA`?EakbH+9FLorog}DVLLY~kZn3ozO z^pdZcGtK-Uv8f^s)>`?dNC-*5JpjPvz7n_z_hF(W8bDFS9|P3DX-si*S0VWru%TJ` zo3ynaYz52AlFX8`EPF{gAaW9}VfoLJ_BI#iCVs{~#sM0wd$`Yp@lQJ5bf`co8x4)0 zKYvCKJXMuz@&$urvx=qk5%4j#87pN@PxIQq8;e4T`gAzCqyr-}b@=gZu?EydhvnEk zm>%@8T!#UW;$r#ctnV&Pg?Sk-%$uS!R6(A|t z`y%-C=a)Q?d_B;iOvVD#?KkXR#oPSLA3Ye&fXAkY^NK^I(e^*```wjluJ4G6iD#LT zVN*=XyD9|!QtG_`cKrs}dbAubh>R{3oLpytn$r-4Oc}PIFnJ4sPk|8XZ$-32CUknYn5+ZfF!e!7H-x7mra6Cc8}#b@#iQ>%ka78!*du~* z7WqH*;%>XX=?jIsbm}TU`{zt%BO7DqGrYDRoVmuCjdxcAxokz?47V`Hn%cT?g8TY_ z+|~j378tv`@LU?#&?x4c9SW~ZUGZO#g@*}TSXPeJyQSqg?fqf5`M>uwfvX&4g5a*M zF+>~4_ehcQp;BBR?WW;=HB2%POW`@zn5V&;1?$vL-TlV|sJO{hLu=1**;849HTh&t zV^+;}1aotx8OSb|;#W<(OL_vJ5$l;$x7BU&SGi+Onn0S9GhT{-TLKMNcT(YBzR%;5 zx{100ndjaKFy>os7&o*E2Er&^>Hpe#zI)27%m8FipK<7P#_0ib5siIbQTyWHil6-T zrQJ@dCsaQJ!zJdPyX}?EIbXDWVNxW`&ttFM-CjGe+!hY((nuqk^-_z>K;wAgugQXW zg6=Ifr&0EotS{;Z4aSmTx-ISo6N>a(AxZw1aj8m%RV~ zs>l?7?Rs9@{I%=JY!5V%>HV`2_hu|%c@hEwsO&3=qmvIVY}L8RCHs>Jm*?#dy?#$i znOtBhMiv1;s=rfU!g(Ko<{{w0E`KDy8<3gs#$!D~Wc}U~*mskiGwBnOe!Qv|FLl%o zIRWtCoH$ieGBVB@=}kK8B{CB}tO2)q#PBmvsN-%Dsl(eZ^)>`_N?w^iZ*|3N2vk3k zg9qYMzJ+AMlI*_|!P&9J1!W2d_$>HdNBH}G8+T&*RJ8^&Cl|#&-Fxz5 z$l~}qA%QL|K`@&0H~;WbwO|nta2a)A3@rN%Wf6 z+JC7@mb4=u6BF08Rmgf0TpnT8+qT;=Ww96XXLXPNNA;~ceOUYR&d|u`P$R?J|JZb zG~k$=?6Si_>FeoJKD9J8(5-+wOI~|ms(Gp=Cjxb6{q_JQsaa|#=6YJ=&6m9a(%19Z zo77;Q{cp)%@;Z=!w3bI^S6GmhL-qV2BF{7sI z9Bxbro6u&+V?ZVD59k)!XB2k#lvoiI!Y>;=FFRmpFsZwCgH-K~C^I>hpJ52#pqjQN z*V!oED%eX?eZDbX<6csh)&VBn=OE^fa<%PR_#gQ>__Ro^ zpG!Z!8axgA6%KTp?~R2b=^i4A4x$9NAkrpXgzT_#oSIIO1Ku=T#BrYx5;os5zMdRx=?*EX6NQ`jq9qIKvm}9R1u=o*G@Sp;H0Iyk@{T=s{`~-x z(&Ec^Z#{P{Y$@lFEmVT&tN*yTYQW8nncS-PCpC`6uD#yUztHJ==3Pqfhv(yvf`wR0 zzOnp3N~OC``2}@lpE5OKYV$_K^VL`^cZ+Vv^86>)#R=%z@nc2k8qLM^hwp`se*f(5 z{D(g4)KPgQqC)QA$Ef7@-5*xR)`Pwvd()y3_3b2?=HPX1(>EOExxPI$K$7Q1*9Q0) z6>^_nC9bxKx@H~(Y?M`(nCdEM$%#N=cO;^qt314xk3EqZRrC&cvhPhXrwKR#15k(P z^XJw7H}oSwm`eEnHoiyOhEaLG#hB5l)Bp3K7g}ngkQC&BgovDsDW!A(_ly@hG*G!b zq3%xF!=3g*TR;*ZTuH+6F@XoXkPtpl5YmUh^1dy`sQuACVO(T*Ok zD_vXv^ReMnaFcMOo2H*GZN?2}x~4#0O?A&utz^SSj*w5XYoAXXeX|BwWfR}~$Q^gN zpK$!vELK;Ibm{z#ah)<|QK3$R{X>d5Ily6Hiq~QqyPaEXi~o>y@n*` zYCfx*>^FNv;gCMmi5KB!S+Y>Uqja=waZIHcIz)G62iN=_c&DoKNtwLDcL>uWlc~Gf zNwZq(?hBn?ks@FpJXyKFlJrP;M zN{d-~{%aZ%wXXd&&-i%;NK<$l-2;mWP>##IsKEB~Iv{l12B`&JF*ngKhu9y^9K@~7~GtPeomni;E@l@Be( zDISWdC94Tp)DmWUR4dEf`hmrC%WOJv>bTn8faZ8#8zM#wb!=w6VQyouB%~F$NEA5} zVznFBIJYEg?JdkBXE1LWQ`GKGUK~GT#cV*G4?5~`!T!B3xu#|Ku@-)axVo;9h1tKg z0Lllu-3=FGXqB@IW=XL%OxXG>jumZf2t8MWjw+)3U`6>^Zgn*YW-8DLU*=){;88x@V0 zK2r@|1I4wd8)q9)(oHd{7Fp!5F~TyoK};TQ}A)dIQIp^31vEwRY^+gI+N= z$vOOhDsK^bNGBx-6p(7jL(OF3+Oj&Y^{M}0GP#Tx=WXrwu#;#fu-*V^gyOR0r*MYG z)c}ug&+c%U2RtXtViLAz`*O-;XH(=_Y7@-42n-`?}An8d=h z{(@_+u{a!NyZP5@-T+zNr4sT-wEDKGcV=Vn9r4mVMKPPuU8If{N8WrwCI6G zO)PU2H$zunkEk4dA|hX<8Y3(MZ7#pslYxXgBOe}R>d3mdJ8^Fq=11c zd;mMQtrjp_gBSw;lU{i%Ayf6JWcrAYl*ULkaMir^9? zC3c|3gd+PUnQ%8(YBm=DF^W=?NuyY0JKvN!T=EAz4?5GFTx>Y0zf6Q1L6*yqp?s48 z9Dv`_fyBfIN`@wY0{+u77dAr9k7)|FkQjj-~GQ} zu-nPLhnvz!fFMf0LdNy7b3AGbqL1XcxPRtm5jzE0IOLQGxweS90I>StN1xpux8;wE ztpCjxRXbixoraH%bQJP1GQxlWvebdtpi~ILmv>M1F{IHP@K+y_5Gq6vH8c{`13L-t z^AjC5%`CF5y3H)7tS16|BtG;F6e;4B(%Id#lsP$iM~;y7Pf6D&_VD~m3zSrzP{N|{ zKeTp!4yC@&kOzkKsfRJ)J*{uZB`$sBw6V!eoj{v@SCjFkUC2Cs<1sSr3D`V66j0L+ zMpEN^e<)HrK;vTJ2J6zv{{c`r*4<$f^%Tl+1w4#30Gz}FkU{0!ziFi-)84L@Ej6!F zN&%LO)bWbbLPl2H>6O$445%Lh;tb)a-O3L4z|ks2m~cGZ#+27#;$>tT>ukbReFUUH z2#^kD6A>va(zQKeU4z{vk-Em?A3u@-S-;<-Lm=JM1;$PriA&Ws|Ib{rXH==^uOn^> zAigYD`=PFT7xML?+S?qLW6bM%Yh6#wZ6MT{sXeTYZ|U6M)?V2n1)D5P=23HEo=Y(? za6txOS#^$OTFnd^E0h@h>sSn9Yn&0tS&mgLRqj0#TwiGDlKNPY;Ub7h`i_P9wWx!TvE{fWQ09)2&W*CFr~Uw1YXz?x9)4hy@6Uhy5IE63byYu7q|LBy>iF^&RDt|XoU%*Or?&)DM||jn<`X<}bwI`CoMd*HSeklo!gy1z zDGj1z4O)#gjX-|gRrgcEnBxqGNKmeIoit8PnyB7*X&27IhEjZWRV9hnFv$I+rouXN z`{B|2NjAOk3EA(Zrg04(Kj}SE@0~;I5z1nZ3D@GOZqdd}u*7s0lf zp&g5A(km<9^J#{u9b^9!xOyeBJz^gDhlogZMDdzhM;kG=`2V@0L} z5wbPTYwx3o+Vx_l$gA3uiz+038`;-V3(vex6P5OrAP#0#k6!?`M~Oc2UDQ1R!140M z)n$N$+(k_V5fznH#5ynvIjo-QU!bMb`}y9!GDj2by(JO-l8ICtV1B*am~TD{F-JHa zDmQO*J551J>Utlx^-@`CnX2p&R0IvaxK++wK}ZLS$?Ew5j9lIze#i=G`nHMq4c!(Q;D33U(^14<};g!XCAXT*y>Yq)=Bhqw=i1QY{C=PI?E z;;spNcEBXA&!|un1gNt9y*xLC#{G7P+@@8o(Q$ye0{{@On*L=7m>S8J z{C2qy%Dg?2eYlCE!mZx2XHP%~=-*}rRKFojU-}SSGmz*QfYKy3U zBk9MK(Sym`OF=-9lQObTAwnq9=+6R$m*la??YzF6^W7uqrL~)AkB1uoTg6LlgWi2# zv5sbJ7zu3i7T#s2A@hPn z`6nm|ZC@le9$znNI7qE-jy!l^C^0C}$0pA(gf?``D9fIb^k8Y|( zVBpb(sTj-kpEQt(<*%KsujrHgr{z!amNPr_o{hHMrNHcw>dXBYM0!ZRVT_P!IU`ku zn^@JiDfsS5sU?f}0}*O#_*@K@NgGh~{sGzJSr<0IAiMO8E}=Ao7iW<7A&^Gwj?qoM zy~DUwTg!UAexJsdPMDG`!s}>LSenOv&67*2s<3~jv%`ACtI7GrU=PF=0G@#_I^)_Q z!SDy~ZayEN_scf$0@Sjdb{_j7$rr<4f2lowENFWy^~!0il})EoK)Xl}F}@=e6rNNy z4?Kx4%4)j%G_^G~0U{&TLc>-C~0Myt55LBnqG%k6|+n4vO zz^1qB6PH=%4DEijXc4gfVEd~B4PhsD2xqRN-CJlTN+dZHfFJL;&+Y(G>YF)QQs3Lb zs|yV+^iHe>uz4d*U*iKST|19VK9-viz-lF4%*AS04wPz+O?UEm7J6?7Wo$Rtb#65* zXb<|y3fDa@1vLBFx}v`%dpzRjlzk^!;nan-EEj=FP#!qDXDQB!7DmglAT+lk*BGeM%8p<<MOxYxa8bz6K%J|*g&;b@N3u^SV$T`YbZ*^Xc1fvldK z+--X&AlT~kDFYSY^Sa}i5s(xgih+IcJ-rU>H8{iyU0gP(Vdr?W2!DG804|&L0Z$0&df`4o&QU|A#K2{`6e&4T z=HcuturlXT<+2eYO!E743jJ>PVR`uGlxv@1*0Ie^tImzl^fLL#FA(sYytjnXYPWqx`L!5(hq*V1t&utxE+Hz+yBa zSkrPAaCPP!Mvs`1Qk?=#0OzxB?>p;4+9HR;N;w*G*Q{R% zH+eXjYukRbr-ylO^S|wuy468Kyd!~m=6lo2cZ*6E4lL*wGzBFrc&_nC{#r+~rH%y( zjkT_;7}UjbHY0KxoC2+<%X#nVJ}5pIHX2w9>3tt^&uR^dPk9c;2=uO;n*6UHSSIXh-KIYSRkAcNZ0PlgcuOtnByPv(CmCyW#vmVtFFed%OY>z2;!AxiCNnb}9D~#g( z{2#-{rKOPproeLOV!5~#L&|52SMtS4MJ=E|3NnLsl)3YBN^it z7Ui*TRG<=y`5>h5SEWyKqm+dA9ttP7CusHYaooN86MI)PF443nO?KRYI}$y!&Mk;ps? zSkt6_sS8=I8zSWUl30ih2MRO_J4>_3q{|6G7e`4_d}n@68{$`j?`73Xw+C2 z?$&H@Y!cf<2l9I6m}(pA2|aik$4^E9(*W*TEr3fSupi51t;}(EO`PWs(IP`Mx4paCu+--7Sp2U9P zneHCL(5M?g6ay5+Wo2cAuU~KFvyRq-+5x7*W1gM$W%f^wCV)dnMJHN?#xY*w#qS7r zzyF;zgoz!Z1VMin5cShuwa$`QUeD2y@8d=nLn|=lt)P%2z*-bCe>g`aiqtB>J{H9c zwA=OBS{~fVKUtGB1!>%kjo(%;7-xN9)i`4gp4sGubH0~rPyR`>t3W*F(`EA6D-x;T zh=rNW*2``lF#Rog?Rztg3p7bsz`RY+yq}Eev|EDzmjwq9SYrxt+kBu#XEA+ug^(Xu zso&JO*%Za)=f1f(rBDGlG&_PILN+Nvwozb8GszV3ORDWlSXhXy@31r{|8&vbtWQY$ z`0)$1xvh7AjjWyN;nF&rZri+(E3^f;XuQp`RQhc+?GC6}dNqMynl zAcnCY(d?O_@b%Mn{SKu*NUY*77gko5LD+b4qwcpX=HnPLe+-5uuUpB|fmo(u)d_Fb z-ZzPY7t$x2i3g(QIwA+(50=ak?D^>5X#iCMDWdHPDJo9%@#x)!8MA5-;4h#+DbipueI3H$+nUpM(`0M0P ziegdKFYO>9c<|8w2ti*NiR;~mRWh2^^}c?cF#fYt1s6KgECt+M{xNu#)~6kh2*Hk` z5E24Zvavxh21QHb<&Dl!1+bmkbO}%1Haj!8hb!OY1Rg}PU0X6#rk^++AzB+1w$b^9 zAhwn@|5C2$A^g}ZK0}vo&3GyV`B#TBZ!x+A%8=C}$r@ks>nlA;;Zk59HK>W*# z?i$~f#sh7W%7oyxy2#91;Ol9|rXzkoA}?!+a3+{5XvcqOH?!Fg?_SOlrf1UNTaylm zQRfLr8(u@es)$6%DKk{-Q^x? zg^{Qqo}7Bm7qeDPfJ1gupY>6spe=DRKIZi2S#LtX^QL5kNkKhr;adL=Bhd={xHV~D zy<5T zPp3&hQ~YQ3-jP-|FM;@vjc-?5J4fHTZ6Ue`A#+@NoE^o&`6Qp(>r_*zpxbU%-!D~y z!y>?el>Dn=ynzloxZ{HA?ToXkFe(lwn0L`$yWP^)PI7?V0l9UZbx}enQU_;8Ii5GB z@Iivd`5V_BbnX)>6;K_dfq6^s)qAS>^FciY)Z7aoYDPLqdXT8u_rQj9ESCyFZa?Ox z9rnv_2RdgKRoL#)NQvImFL@0BwA0Htd(M}o*rgYh5bqj$vS}Rnk9iCHE`VDg4YgI3 z;2Ww%oIELuh25!Eevd zXqIFi-57b#ILiu-zA(=)&%%b2VB##w=&Sa~!}nxlp2;^sIs<1XuugB{9UWNV>z)LU zj0+dz(!YagKIy{az`Rx%bZW1F()#u&7sgo^7+GhC^`V$6{y);Q8ZuyVz6Src1m58Z zQxq@Inew>w1Rke%)AectFt+(MyvxKN{`a z!Q{!nJj**r^F;#mW+HzHqF=WQKkP~n8iD#RaErm&c`OWSjn_8hd*m64p(XP0)XEj2 z!cw;I^QE4Ry5n=hWaW`M-tNq7-&hJdLS*Q`QOm^fxuE@lw0Yx~6N}ApvojA++ zK#R<^Ykq)Sm%$$<5RMQ{zNr!1 z(HY9KL!TJ?B@EaXTl$jy4)YRXg%sca{;6XBVMw-p@wFSg1Z;QMzI%0>ApaI{<(S2M z`G#>83kEjGFrD#NQBVi+JTb4sh7(|VCWumJ>t;nQ#fhPBejNJCMFOK`5?ZxX8HNG* zSEd<9LqfcjFOt0Qi9^L=X|MnS$Im} zZ^{Vn%T(XCXV?fl;gtMu$%}6k#K0$epRYE=LWj_~zl9is*nt#t`fb-y!e5&rA^~XV zS>^~nA>N^f6~0J#Fg}d6DP4xGtt@EoccsLRuXAgsVw=H4OU(xY6|@=YA@QcAfWqSPYy07rI*7E`BWW6l3CMShFg z1Bec1km=%uP~f=D;Qnlp6!m+%cOy3_=qW&|I)GckvU@CehZq|V+>+=$F{(m3pa+}* z^ne@gG`07)YUQsaH4ElK@g}fxG?7W*hWUd9hbP1$+pT*Ti}st-NFAi}l)TMuANG~k z5NAb%2x?b|N6JW)4rhz?IFim@Uh`k7hA?=S*gH4aXVg!u3$z)DCZPuLx(M(+gn@|Q z+;KThqQKcR-6J2`(vgOp-^t|(KQGbbAO@f_2to;BaA00x;hB;W7i$!0`>Hq$Vo>zI z2mRU@6hAlQN2!#+e$zG-M8<`28AW;<3_&dE?Q}DE{@BVSuTDX-AD@Ych`;jKV5n z01%v`0&8E4gie*#E@V@yW6DPwJ%mCdNrPaj{j>YQ_W;5(fi~{bzZ(c zgnR3eaKCu=_O?A`=GJyUmUc z3RC64^ta>F!js*U`qj-c`^lRm(6=3m8ZEhDfMyO_AbH>=ohIH6*cyU1?9MYf=+nkf zPr)vnj2d`LPK;k1r5PAV1Kwdg6kx*;F`*Ne7wCibbNGPGNbnBz4~g27ykn)f$ZaXD)I(vleA|h4?ZDG8zn0brlD`K1~KHASFE#Zud>{; zgnOqF&x|RU;^yIeg>nMN)O2o6LKr zf`(UL!e@vaE{p$owc3ZpW-ywXfj*(f9iarC$L3KJI|dszTV_fIwUEGY0Sh_zQ|!g7 zwE8n-S*F;T$YrC=SGUox`YT3&d#6E0L?o9f`?)Pv&L^C=Z5i<-(&(m!ZH;tNwK%FJDHT*@xe>G zAS(T4@NOweyw5(bVRPMjO_N3gv*a_eQZ-r-h1qf8z`Dsl%LS z%J4mT`sp!)!|VnfzXvHw#+g2)uA4o-&!4@jxkAvEuLjlEY+qEL+N`pqjPWfhKCU%V z#0f-Rq@@YjL*Ykr|R@!u=Q67G2`5F8=-xNd1xL4JjQDZiH zNrR1=_0)k@ZF@b1cBOHq1|-uL{<~Dt_64q6Jh7%s2T+R9Z&sMRhl!D!=qu;R<_eXG1&I9;G-OgoQQwBpWo745H zDg_>=j5?>T-?$Ath@}wtgtD{FyWD@=kgG^qMfRu6>ujxOb!I(-7(L}7)J%+h zRgZn;gQNCGE(Ga!?NRcDxR6|+3_A`S%{!;J?C_LGjpWT((htQHBDdqT?V^T>+$>>2 zRNi32@O1YDRewoF)(lD=l`>`6f%AH6Dam!?*>}#F;=!Ir_NRBIQo6>|xD2wM--tzK z8%k^WQCG<1Rc(#DQng;MSnuoqX5cygN(l+q%_2^3W*ln5rn!t0=D9=42E%4xSX7B` zdF(7uYtCUH$U$qoknB`>+>qwdzlk-z(zz`CEMTS~Z^Vy=-5#cYh03eGm9OT+iAA55 zAK@16y4bhfGeEeSpkL=2B#t<86`!{3?+6$8tsK2gEu_w6N1L(M=&L;8YVl3I_H?3AziHKd?ACP{;8*ut~`cRg+Y+zo@LGLglVR zBTzi;EE8Sd`(DN5eh@y=DjT|IHS;ZyWMN@)FPx@l^q^YCeai26JFu^XSpp7Yl9{K8 z&$wxr4iA$F#?^glI+OdnOW?<2=V0mbFgk~$eo>`8lF=hSzpUwok{}i?!)-=U2L=OF zqk!g&{WNou$K_?3^k5gtb-0ao^>nx~yB5V!m$0J$DMs+5cbe+7mTnxR{1nm`!L?FH zmVfexsU*EBlz+#aU%*uBmkpVJzB51iH5F^-hr44-bxB8`iJOHH?8MU^{*72>HVVmn zL#Q^EKUm87UaoO!VzDlHp{Eh+jHE(`PD3Fk7yw+v(;;>m)f=DvCcn~bp`LkGU_BF?a^`(<%gwG|TI%UM_to-YOOk)2-(D2$Q&E4!{89?9rW z7D;045DdQ?Z}^`6q?7}FharNBmkHSv3Ez-@Jdt1aY&^e=d7yH8@PRJ&$9S3JdV4=s zrHwn2xBYM5Vt5@f*POkMEf<4P);l1OxWA|i-`M_9V%S%KO=HC1Y7iyTzBM}N1kY4r zSjM9+>wO|v?wmygt5jiHo_Y7cP)QR`#D)Z~1T*U88yO<_`zNJun=dshyo{;{NZ>D& zn(B|%heLCu2KC@d2FRb)<*gocFmM-09}OOFC(D!;lDama;4Cyz)eZmN_%;H$4aUEZ zv>g0B2J9WNLs`!{iay!La-^Eaxg5V3I*>b5)XxLY!3~=hbLM80$eHU)Z<5>38HjlN zlY;}8Nn$nN`M7UGWj??i8;(MqvF?L6@);aQ|LOg8Yz86$%p@1%8fW&hURmnzBq74% z_STIw<9zsDGm2g&tMo7nE}4155vC?Iu}u2tHJhcLhUhMmo0ch|DRI=N8coyNQh1?J zMDNA?`IM4B_kJL_gGBbhf0WrDT9v)7b=}(6d;2z^sRVbFJLvGT^5~>wCS05W*)#xu zlpM+O(*sU~osVQDjnx&f9#J{e&!?TU#6Ffoj#;MR!Jq$p3z|^{k3U6@LFk%IN8qQF zT31nwXheGx3rIRIx^0|3;#pF<1hn5W1`WAmY3Ppu!z3xzLx9N*r<|Fx(j<273cc5> zfAYsv=_BR-%TG^P>^Urxy56P)wH1SN;}J?39eu{&{q$Bw#G~?Vi{+})Jms}|#Kju< z&^K)hrEwRdVTxGNRv?!1s{)V-QBz!iUl2|bk-1dGL zv!pC|iP)o#_-9sP%BcmT-BVBQ&z0N5v46rhB(0SNHtKEK$v{Um0-gW&!O|;i28}jY ztb^|S+$H@rR6)!I>oy-ja66L*1Ez9fg6(}G@^B(HUVk&CWM$5Kc3~KxN2VjW#){$N zbS2KKPooh5>Bq)R%smHdCsJxKIfg{gDL;ESuTq1>n8UQgX|eOult)sBlei7hP7bnO zakKH`=-Ej29gWx4_!1R=8H*+RAaM3v z;QTfg??654vWv*h0XQsWqtNnS^3&=|?!+;ep=!!ck_ z?nUu?q)J+k@cNndN2k8bu<~7Zcz54Nuy1Sfa)i@uYmhlbI)dJ{BV_9NV&Xw5iBx6H zn^;#{{hX&?LlkZ7g3)dq+H@tVuTsj*Cq^a>e%ow!{Pw)x2fcsLW$34VNpro2gE`Q! ziR4p6idaYVEdv9qjfcyCf!NX${nG_YHq}w{DK(ro;WJ^3P$WX5`@HrwQk!08;agqN%Rvl|18PmDu;J z%zv(=Z=kimzFOMr*&0LD3q0KRZFm5}S&f3u$5fZY_jG{7qU^Zs)pNuby@l&Jr6f5! zt~N`ejq@p&9qQWJu>>14Mcv&h7sXC-hhAsdX(M`{>h)Ht(G)qxRv}iX+?=;>-CbUy zmRM173i9r;W5%04?R#OrHI^x{$&4AU{Md2*xC>=4FyWbM3<#L5by^&k(Y+*cJPKuJ z$2e%l3z|G|Rn^x|6xT7NR?ciI~XWO2D36+v~>?ldG(5n_C38M7%GX)>hF^Rd&^c*RhS1+Lyxqpyz4!{3Zm0Z7kkDE;MyUdfAhUM7EN(C5=P zL?=eiIzsA&!JC`eBfVL&Wd9yTiLMm8@Dv;!OU^gw_Y3^9=KMo z+b)TDZ>CkYcm1v%CoQb1YEPG? zsKD*qE=0QNTGum-5PDLr@oPf-wC{9twQ!(=bx6y5ve{aU)?{$_(@>jb>}HF<=(b6P z60=^19^%YnSkI@Igo1*VLmR>0eW-8P+9tdlzkDX8+c?cUHZn4;uFd(}GPe4=!kaS& z4M*6b($>f%B_^0HnEju6hcOGsuIQGSvlhKr-ni0bJ`yz$wk=D1hc8?>+3c5#%G!J# z=UcnG^YnVswXNUfl?}emQAw}1#>AeXb-u2y2G6j4wemn+wwkyM2>a20@gO7|(zO>l zjhj+)KB&xjD%EU!Q_D(Q$#FB*UgZ2-aDt%ij~dxv*6V=t#lz(y2ngmrdvYNzP;B)qFaFylEA6D8%UV@p z*^8tt5BrEy$t}lOhR?pC0C@2O21Q6%TQyt+FA&VSJZ-^jVEPZdmaF|}XcLRT_vNO# zeP6T>8+Z5fjtwWBG?p_A({<^HAufCGoWaDQkqXJA9JdKi&;4jMC_6OT+t=#Rt<)nV zT>ZAiwHC`<+%V=9^x8FAH zuDv15!-{oPakqoWI;$QRBnsC36jlD*uG(FN&q@SF9tmO^3oK(zIPi0(ovKjrSL?VG z)@U@vdJ44{+gUir{%XCC5%LhTv4Gn+|CzecH!kBO%A+GqrHX(q0Xy-ca6Xf>uOrfE zCtGj3_m=|21+_~ck#abCnW{fbJ5hr;TdSFDU2F7tQEY9EZ!lcMB5u4qWj|vbXT2u5 zT;n7}YP%d-s|&MbZaf>WcIYVTnf1TWs@H!eXg4IYe6;;}CoNGhx2t!o#3!%q8rG>o zL_x$GutNU7g)EgM>(a2M&inotR^3b9_iRl z6;O5wUwz3Rb=;wT;8ZyevLS4wg+Q?SYDT>YJ zq>bxBpTkYG%wpG<+%QTdLJ(|>(v%-HmmDUn6Tf$b;tTrcsUSGzWiWlLiRT=1pH3cm z)cgb~5-!sO=5Q<_)3>2eJW-E9%z(szm9T?wAZbCZh!~plK=b>LlC!<&dz>7K-EM&m z9r5?0gc%%Wu%K#Byi^A<#{ z|DH&u1UpKE0+&g67dp$oCvOEIi6KqMB-TGS5KTJU3)+wroDm8;|9cPE$54R&ZK1{G z-_wDogzihTMHK;V-@gU6@%Nl$YT!5r<8p<6o(P zbDxb2#OVZGGp!NSc~xFV)ZRI%9=qhUB2l)(4fo5Idr`l57X-ccMSMgoa{2i977w;iVwSZ=FvafV;c=zR^x#>P&M z2Pr*w7f~+RsHJ8vJJgK@*429A&r_1;HF~8R1_inm3S0&Pn#&QJ6|9m*Cf$kDIBo{x z1TLaqW2l5}W-AOfd+jId=GHVK97oE}I`h)4Unv~mySJQPoHv*UKNv3_Gaa)?+y(kWwMT{%|3 zcWM3lP+%_rXRFRcI2Hqijy4Rfmc8&4JNOfk`>agi?xY&+_`R=ov>)v)2Ri~ zF2i`)WTW%*mA69PNWRPFc}>T!(QJU*Rrrx-kX@Pdm_SFX&~R)smV@Fc8(Fk)#TLnY z&vZWeJ?K_;zZJ(ntz*5-VRxAlWUicR1F5FFf6JBQ^#%@SXqddW^Vj&LjN#<6J2ht} zuvyl3@Iy25)%aWD)7GfwYJHlEVzo{En)S*g0kgfAIxOMd2HcWoKL@xuTW;-6a}%zv zQhvY|vK!Ao!EKD7xzbXaK4~2<L`Z-@z-*E>$Pl^CklBNSlBuE%QQ05UITSaF`JJ`{uP3Kk+I|ldvO~qctPPlZ{sW%}}(%#>5zg}k9#QUq5+uE$uhVTWt$S|ot z!+J{AnA-l<*$;G|CfZWfe5U*LsVG*3S-YCiZfNM9)n-!7;ii7xgOfcf=~p2(WAZN^ z)ah5e+-jxR3psLHX`Ki?Pj)|lU3zyS)=DM*yd+mAZ_QUvx6E}aL&(F>c?FLf8mKr? zB1y1oQ12)Kif+-Cs!8cog1L&B-8 z#_Rj*o1LNv7q^RI!gGAOQFU?-noDFFS68uMKj{|{%^u17$Vj)xE^K6u*?C%pUo=)`8}is@S$KH| zdggBxS92#ayZcl$!N1_+*oc8;p6PW&@bj#gf4+gfZU-0k&VOSViA=lp9{)cV3Q+~?&nR0p@qd0w zR)ul&ut^k{;P%f%& zPVw3syIQG9vO}#2t_%1LprC{iwN-fIGApA+q1tOyv>aCKXO4E(=6`ca2%CT04h79Bx3( zC<j`+d&yoI@|Q z8es&lcBi#r@P)L3GY?kV$_z(qX|Z#0Bi4Ow4>1#+6SYbr>u58Lc2NmHwJP1f^kpUhmk2uQrYy1}g1tkt`FQM(Q) zrxzBkm1g5Z0GlMC4pi`|f zEjiYn47>e9It|sXOP`NUgQf3pdOaFjUHvDOvO4_f^W7DsbP)GF=cEg0>f57KY>1iz zx2L*7qkHEdr&2!5FfX;&7~|6U_+dBlymd}TQPwVccR3y~QrV*?jNPp;o~ILJ%uThm z32)r}cnMD>JBBTgH#Q*XhG`gF)Htkm`LS9GH1Ss@-#XBwlsvj6_L%g`FR&%w zLFHmf6RiBYH8uxXf57(c`>-mPj<+G%M7o5e)Hfc;cQ#mnnqWd#q3N_P@OEh5B72R3 zQYG<7P6jdh2L z#()>5i)UZvmfsSL;+}OOWmb^0o?8&o&MQ#t2J1C0-_gY$BfUDl?C6nLg<|m3i715I z`R5ym2nwrw-~kqU$ zJlK!V!b34%$;Gt&9k=-W$9l(w=xBMPwLoO4$CL8aI38aX0*Ci2R%011OtzBfJ;TL} zzV8}Fx0&aTE}pBlQ?GQ@s-~b*e9Zm36u1_{tKV;7B5y=|5S=NzbpX4qJEY=|Z`HAL zrFeK=8VY<+-@IK)x&d9%HJU!3rVLbTH^4$Z*CFx_L+(?;2yN5Q^y-mzq_eJofWZ7$ zN5k>=I;md^Ja&qk}S&JzhU5sSUR$`(b#Tdc--h+g{Gl9la$7vdF1g2D$3Q$AN2UI5i{)fR>YC(WkM zH76thq^&R9BCG?}g`PI>+Y+V~(i~izR$={{K-2)uV1<4EpcGqp{|^2DZUEUy{Bk%5 z0ylB#u{`hqqe6y(G-vjIa4B31Qjr6fxI{K`g|`Xx>vGlN0n8OvZyD7DqXyXmyvL@v zHaK#r_R&TH^kXCh=2%X~7sZY8e+c&P1d06opq8+mdmW(8+k8B}NGl}Dr|Z(k_MgBB znKY9OkfmkYLr(x{<(%bwn|VLV0x%_{_WpxY{6|FCrv_93&!X~jvqPYqBRdnR*;iof zDDCo{Ow9+3CWkq5BgD$dDHoDj`zjfG=MWXbpg6G?%5(Bs>7E?#E{TnEq6uz~Rs1LU zeb+M2Lk;o*4Hv{6VL$+_r)5veZNiVmhMMCv9?ZX?_T}b$uJ$nu0CXd8s=Bz*=_$Q8 zZ7TROtSL1wx|q<630|7bi$@^n-BoArmcKEc`WLAp#UhSdMkmSnFwb>=NmYf3*T;}& zT50p&l;6 zoardju~28mMlN)HQCNsnogx(%G;hd?ZcpJE9 z>wi)twf`4IN~lrsxb%%<{LGTcf(i@Bl%Gy}AT`xp6NaG368U=c&?rC zJRU*+4>O|3F-6Ey1YLu(ll#%MePI3kmnFpcU>oM5IbI>zv8l3Raj=F2}LgXS`Z)mL_kE z?H1`fY<)g7!Jn3?8izGWdE{ao9t5Ib3TCIR)t!1JZryU)->(;Xx@1U7)?C=8G9|Uj zqHI{+$lSbh_XCXYS6$sGlsV?f;-oJ5HD-bsHM&KI@Cb@ye@8B1)0|wFWztd#>sf)t zkTQYjU9;))Bd_5jthQ?}#1_L^wq6~(8z=QNP)GD>fQ2Z5h2Mto zM@KKMxP~Bm;CKqasA;~wES~KM=Sp-;V7)$86>Bk(*rYV{fN|_B_*3vAzVc5ZJLa^@+!Ngb)ti7 z`n7B0x9wRP5OU&7vwFtzP_XGT(z{0PQdhUr-6MTc1p6KQzIy>C=(O6m?E_>wgq>#S&b7N$qD+o_olA*4L`DNAXtX&D4?u7j^;b)Gs?{w~J&jG(iy zSx{4+L!8r2rUSx0jlB)#eyPT`0bTrn82s7dqfoVqKkcUW%F|g?Iem@EH`pcbKg3?_ zKBb~5>g+t?{!(>#x(bFwR08f&=@}Eo;#@tjJE}~xJ|Z7tp=n%%Im+b)AoB(C9?Hy1 zWfmuKPW!vpU42#fQ9p64plfcvU)*pM!O*GjOS17LVsqTd!VI;&$uF9JI2BJbCus0( zJyQ!qlND1UmHyMM>Y)LqKydUXhs-zTp(&>UCiyThKBwn`Cv5p{8wTRta)WwnV+*5X z)0&sGt1Ibn9IWllPKDe?JMdOpY&#clq90MD7dlS2=;Zi2?YBlCNRN7{*;*BxECdLe z-23$#oi@j;2(=hknIED6hZVh=j3Ndp+Nd7{V*;r9o)Lm+{&}`=wXOMb*6jM*MDdJW zef}dw@?AlWqsIlmuJ9hjxH8~$N|}y;=1Q%8iq7~M3mDqk18=uaCZ0T$WRw3=73=R# zMq3wy5SX~!rZ|4rBuIU|V-CGuBAwRWHbfm8t7oB9;cJLX0lK-W-`yX@iWDQ$nmS&_ zYUGpc{?ePBrDgx1g*mrx-=ICUv{8<_1(Mh z$9~jAL|3;i(p3qTON-;)pBkHiZe*r{w6%`V{u5I4dB@2kANN~9;%B=H#OcB|@IG6JzhL($t-m;UYK9{rde zoO@0n@~F!9r8<@G<(FR6gL^8j<=x}PNY?NBBwYYJ3tS55mZs!ffaoZzoYILPa8tnK zirzli{PUHab|;BF7+>D53t4Qz+WoJ9=ZTGqjRxT(h)|U^biD6nCdr{~h{Mu+#Qdfa_-w>4^Z;vl?rxX9WT z++ zAIDm;E4{bK^7?TMm%k%LD^Eg7+)G=^B(cX~V@&n{C$aT!hXY(Qmw|74uGKumWsP&U zcsd>_P(Q;fV}bgEa%`rcsmhAx-qSYuF~QT@ZF}r0?M_hZ_UUy)$4KILr&QYr+<|Y! zIE|E(2G=wZ8SIoQ3$p>uM%~7#1K&Bjcey1$0XGE9etgfl)NAKlk<9o(DJ&z6$PW>_ zhGi&U=4wKMZ{E_P+Kqeb<*C{Fz&?XLAKzeM$^n1>t1;ix(N9A>WWJ;(}fFGJfG86zo~XRov|I z?uulipMTVu?i{PwTT&FI(bI#&$H~vmdZCP}2-dEMgoK=VVi0DsXZmn#AI>99Kjhd6 zZ1F(ZkSsWqXmlidex6wB_~rzz3T~i(&Bo?zFulnnRLMEjdb@CiCAa}4iUhf~-wLD7H#ucrs^fZIml$hr|&v-gP*+>Hcg zPL>r3RXXjRsv7#jWW2K#t@-#zC8w|t_qb) z&ZQpde=H7%ijTOdmmXTc)%v40Eg+%4&e z#oEi57mCRSq{t-7=syzJv#W*j$iqc9?-=PV&FILWlNX4H3st9st``uKyW6aC9a>r} z<WaO-Qg1k}#E+t%stJZX_#!M-8VTt_-e7ZXq{dzlpsf~=O0km}z*z(i zMMx*a$D7XZ+=&AFZQ}gy!jfLku)A|Kfm;M;V82C9glODLcT3hesvcJl*?J2iRJp5a zhkgpN8&QhUUVt8%`CgDH1SP~H*bFbF$3R*DS8DxlI?D;s?d!Cmk=s$fXzF7ZC?XND zQ;CG)9V1V6D=5DX*l*P+<%4$qAcLVFPmU_X6x(>W&A&F1jyQs6Pl~XK0BzA&L2LU) z?#v6I61{D4@C{R-A4%~VTSzZ<;I{i$dCK?MqULscDn~$((6Lt>mQ04MBUJ+&UOUv# zylePi9602eZUi2S7|4Y7eLdYcn8e!TQdp~#Cm@oJL5{V2grZwfkCHBu@X62I1=El)0N3&~}BESUyKBI2y$j9}gUhZlgEsceeaJM{;VptZlCnv>{@H(!O8eju8oF0c4rS1 zy#g9+|LmagW}FX|8D9#r9epsTtjWXwYiHnjHAq}Pow6Bji(Tiq0K9=DwAL~Sz2MmG z&ps5h0tV`bNCa^FHgGRk*gEgU+O~pB={L8Qt3v0Z2g0j+I+|Nj8;ajw findAll() { log.info("Список фильмов выведен"); @@ -27,38 +25,44 @@ public Collection findAll() { } @GetMapping("/{id}") - public Film findById(@PathVariable("id") int filmId) { - log.info("Фильм с id: {} выведен", filmId); - return service.findFilmById(filmId); + public Film findById(@PathVariable @Positive int id) { + log.info("Фильм с id: {} выведен", id); + return service.findFilmById(id); } @GetMapping("/popular") - public Collection getPopular(@RequestParam(defaultValue = "10") int count) { + public Collection getPopular(@RequestParam(defaultValue = "10") @Positive int count) { log.info("Список популярных фильмов выведен"); return service.getPopular(count); } @PostMapping - public Film create(@RequestBody Film film) { + public Film create(@Valid @RequestBody Film film) { log.info("Фильм: {} добавлен в базу", film); return service.create(film); } @PutMapping - public Film update(@RequestBody Film newFilm) { + public Film update(@Valid @RequestBody Film newFilm) { log.info("Данные о фильме: {} обновлены", newFilm); return service.update(newFilm); } @PutMapping("/{id}/like/{userId}") - public Film addLike(@PathVariable int id, @PathVariable int userId) { + public Film addLike(@Positive @PathVariable int id, @Positive @PathVariable int userId) { log.info("Фильму с id: {} поставил лайк пользователь с id: {}", id, userId); return service.addLike(id, userId); } @DeleteMapping("/{id}/like/{userId}") - public Film deleteLike(@PathVariable int id, @PathVariable int userId) { + public Film deleteLike(@Positive @PathVariable int id, @Positive @PathVariable int userId) { log.info("У фильма с id: {} убрал лайк пользователь с id: {}", id, userId); return service.deleteLike(id, userId); } + + @DeleteMapping("/{id}") + public void remove(@PathVariable @Positive int id) { + log.info("Фильм с id: {} удален", id); + service.remove(id); + } } \ No newline at end of file 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 bf5c74d..fd791cf 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/GenreController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/GenreController.java @@ -1,5 +1,7 @@ package ru.yandex.practicum.filmorate.controller; +import jakarta.validation.constraints.Positive; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -12,15 +14,12 @@ @Slf4j @RestController +@RequiredArgsConstructor @RequestMapping("/genres") public class GenreController { private final GenreService service; - public GenreController(GenreService service) { - this.service = service; - } - @GetMapping public Collection findAll() { log.info("Список жанров выведен"); @@ -28,7 +27,7 @@ public Collection findAll() { } @GetMapping("/{id}") - public Genre findGenreById(@PathVariable("id") int id) { + public Genre findGenreById(@Positive @PathVariable("id") int id) { log.info("Жанр с id: {} выведен", id); return service.findGenreById(id); } diff --git a/src/main/java/ru/yandex/practicum/filmorate/controller/RatingController.java b/src/main/java/ru/yandex/practicum/filmorate/controller/RatingController.java index dd96ab8..7c3c4b2 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/RatingController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/RatingController.java @@ -1,5 +1,7 @@ package ru.yandex.practicum.filmorate.controller; +import jakarta.validation.constraints.Positive; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -12,15 +14,12 @@ @Slf4j @RestController +@RequiredArgsConstructor @RequestMapping("/mpa") public class RatingController { private final RatingService service; - public RatingController(RatingService service) { - this.service = service; - } - @GetMapping public Collection findAll() { log.info("Список рейтингов выведен"); @@ -28,7 +27,7 @@ public Collection findAll() { } @GetMapping("/{id}") - public Rating findRatingById(@PathVariable("id") int id) { + public Rating findRatingById(@Positive @PathVariable("id") int id) { log.info("Рейтинг с id: {} выведен", id); return service.findRatingById(id); } diff --git a/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java b/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java index fd98c3e..ed484f8 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java @@ -1,5 +1,8 @@ package ru.yandex.practicum.filmorate.controller; +import jakarta.validation.Valid; +import jakarta.validation.constraints.Positive; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.*; import ru.yandex.practicum.filmorate.model.User; @@ -9,15 +12,12 @@ @Slf4j @RestController +@RequiredArgsConstructor @RequestMapping("/users") public class UserController { private final UserService service; - public UserController(UserService service) { - this.service = service; - } - @GetMapping public Collection findAll() { log.info("Список пользователей выведен"); @@ -25,31 +25,31 @@ public Collection findAll() { } @GetMapping("/{id}") - public User findUserById(@PathVariable("id") int userId) { - log.info("Пользователь с id: {} выведен", userId); - return service.findUserById(userId); + public User findUserById(@Positive @PathVariable int id) { + log.info("Пользователь с id: {} выведен", id); + return service.findUserById(id); } @GetMapping("/{id}/friends") - public Collection findAllFriends(@PathVariable("id") int userId) { + public Collection findAllFriends(@Positive @PathVariable("id") int userId) { log.info("Список друзей пользователя с id: {} выведен", userId); return service.getFriendList(userId); } @GetMapping("/{id}/friends/common/{otherId}") - public Collection findAllCommonFriends(@PathVariable int id, @PathVariable int otherId) { + public Collection findAllCommonFriends(@Positive @PathVariable int id, @Positive @PathVariable int otherId) { log.info("Список общих друзей пользователей с id: {} и {} выведен", id, otherId); return service.getCommonFriendList(id, otherId); } @PostMapping - public User create(@RequestBody User user) { + public User create(@Valid @RequestBody User user) { log.info("Пользоввтель: {} создан и добавлен", user); return service.create(user); } @PutMapping - public User update(@RequestBody User newUser) { + public User update(@Valid @RequestBody User newUser) { log.info("Данные о пользователе: {} обновлены", newUser); return service.update(newUser); } @@ -65,4 +65,10 @@ public void deleteFriend(@PathVariable("id") int userId, @PathVariable int frien log.info("Пользователь с id: {} удалил пользователя с id: {} из друзей", userId, friendId); service.deleteFriend(userId, friendId); } + + @DeleteMapping("/{id}") + public void remove(@Positive @PathVariable int id) { + log.info("Пользователь с id: {} удален", id); + service.remove(id); + } } \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/dao/BaseDao.java b/src/main/java/ru/yandex/practicum/filmorate/dao/BaseDao.java index 8096974..e72f1b2 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dao/BaseDao.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/BaseDao.java @@ -4,14 +4,12 @@ import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.jdbc.support.GeneratedKeyHolder; -import org.springframework.stereotype.Repository; import ru.yandex.practicum.filmorate.exception.InternalServerException; import java.sql.PreparedStatement; import java.sql.Statement; import java.util.List; -@Repository public abstract class BaseDao { protected final JdbcTemplate jdbc; protected final NamedParameterJdbcTemplate namedJdbc; 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 239c91e..67839e7 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dao/FilmDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/FilmDbStorage.java @@ -3,14 +3,11 @@ import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; import ru.yandex.practicum.filmorate.dao.mappers.FilmRowMapper; -import ru.yandex.practicum.filmorate.exception.NotFoundException; 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.storage.RatingStorage; import java.util.*; -import java.util.stream.Collectors; @Repository("FilmDbStorage") public class FilmDbStorage extends BaseDao implements FilmStorage { @@ -76,19 +73,6 @@ public List findAll() { @Override public Film create(Film film) { - if (ratingStorage.findRatingById(film.getMpa().getId()).isEmpty()) { - throw new NotFoundException("Рейтинг с таким id " + film.getMpa().getId() + " не найден"); - } - - Set ids = genreDbStorage.findAll().stream() - .map(Genre::getId) - .collect(Collectors.toSet()); - - for (Genre genre : film.getGenres()) { - if (!ids.contains(genre.getId())) { - throw new NotFoundException("Жанр с id=" + genre.getId() + " не найден"); - } - } int id = insert(INSERT_QUERY, film.getName(), diff --git a/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/FilmRowMapper.java b/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/FilmRowMapper.java index 5d6f13d..c6d2f10 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/FilmRowMapper.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dao/mappers/FilmRowMapper.java @@ -23,7 +23,6 @@ public Film mapRow(ResultSet resultSet, int rowNum) throws SQLException { film.setName(resultSet.getString("name")); film.setDescription(resultSet.getString("description")); film.setDuration(resultSet.getInt("duration")); - film.setLikes(resultSet.getInt("likes")); LocalDate releaseDate = resultSet.getObject("release_date", LocalDate.class); film.setReleaseDate(releaseDate); @@ -33,10 +32,10 @@ public Film mapRow(ResultSet resultSet, int rowNum) throws SQLException { } private void mapRating(Film film, ResultSet resultSet) throws SQLException { - int ratigId = resultSet.getInt("mpa_id"); + int ratingId = resultSet.getInt("mpa_id"); if (!resultSet.wasNull()) { String ratingName = resultSet.getString("mpa_name"); - film.setMpa(new Rating(ratigId, ratingName)); + film.setMpa(new Rating(ratingId, ratingName)); } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/exception/ErrorHandler.java b/src/main/java/ru/yandex/practicum/filmorate/exception/ErrorHandler.java index 60edc74..b0d770b 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/exception/ErrorHandler.java +++ b/src/main/java/ru/yandex/practicum/filmorate/exception/ErrorHandler.java @@ -1,29 +1,43 @@ package ru.yandex.practicum.filmorate.exception; +import lombok.extern.slf4j.Slf4j; +import org.springframework.dao.DataIntegrityViolationException; import org.springframework.http.HttpStatus; +import org.springframework.web.bind.MethodArgumentNotValidException; 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; +@Slf4j @RestControllerAdvice public class ErrorHandler { @ExceptionHandler @ResponseStatus(HttpStatus.BAD_REQUEST) - public ErrorResponse handleValidationException(final ValidationException e) { + public ErrorResponse handleValidationExceptions(MethodArgumentNotValidException e) { + log.warn("Ошибка запроса"); return new ErrorResponse(e.getMessage()); } @ExceptionHandler @ResponseStatus(HttpStatus.NOT_FOUND) public ErrorResponse handleNotFoundException(final NotFoundException e) { + log.warn("Объект не найден"); return new ErrorResponse(e.getMessage()); } @ExceptionHandler @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public ErrorResponse handleAllOtherExceptions(Throwable e) { + log.warn("Ошибка сервера"); + return new ErrorResponse(e.getMessage()); + } + + @ExceptionHandler + @ResponseStatus(HttpStatus.NOT_FOUND) + public ErrorResponse handleDataIntegrityViolationException(DataIntegrityViolationException e) { + log.warn("Объект не найден"); return new ErrorResponse(e.getMessage()); } } \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/exception/ReleaseDate.java b/src/main/java/ru/yandex/practicum/filmorate/exception/ReleaseDate.java new file mode 100644 index 0000000..9329055 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/exception/ReleaseDate.java @@ -0,0 +1,18 @@ +package ru.yandex.practicum.filmorate.exception; + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +@Constraint(validatedBy = ReleaseDateValidator.class) +public @interface ReleaseDate { + String message() default "Дата выхода не может быть раньше 28 декабря 1895 года."; + Class[] groups() default {}; + Class[] payload() default {}; +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/exception/ReleaseDateValidator.java b/src/main/java/ru/yandex/practicum/filmorate/exception/ReleaseDateValidator.java new file mode 100644 index 0000000..e89e71e --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/exception/ReleaseDateValidator.java @@ -0,0 +1,17 @@ +package ru.yandex.practicum.filmorate.exception; + +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; + +import java.time.LocalDate; + +public class ReleaseDateValidator implements ConstraintValidator { + + @Override + public boolean isValid(LocalDate localDate, ConstraintValidatorContext constraintValidatorContext) { + if (localDate == null) { + return true; + } + return !localDate.isBefore(LocalDate.of(1895, 12, 28)); + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/ErrorResponse.java b/src/main/java/ru/yandex/practicum/filmorate/model/ErrorResponse.java index 6a9e1f5..f7e345a 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/model/ErrorResponse.java +++ b/src/main/java/ru/yandex/practicum/filmorate/model/ErrorResponse.java @@ -1,5 +1,8 @@ package ru.yandex.practicum.filmorate.model; +import lombok.Getter; + +@Getter public class ErrorResponse { String error; @@ -7,7 +10,4 @@ public ErrorResponse(String error) { this.error = error; } - public String getError() { - return error; - } } \ 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 7784d28..84ab8be 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/model/Film.java +++ b/src/main/java/ru/yandex/practicum/filmorate/model/Film.java @@ -1,7 +1,11 @@ package ru.yandex.practicum.filmorate.model; -import com.fasterxml.jackson.annotation.JsonProperty; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.Size; import lombok.Data; +import ru.yandex.practicum.filmorate.exception.ReleaseDate; + import java.time.LocalDate; import java.util.HashSet; @@ -9,13 +13,16 @@ @Data public class Film { + private Integer id; + @NotBlank(message = "Название не может быть пустым") private String name; + @Size(max = 200, message = "Описание превышает 200 символов") private String description; + @ReleaseDate private LocalDate releaseDate; + @Positive private int duration; private Rating mpa; - @JsonProperty(access = JsonProperty.Access.READ_ONLY) - private Integer likes = 0; private Set genres = new HashSet<>(); } \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/Rating.java b/src/main/java/ru/yandex/practicum/filmorate/model/Rating.java index 953fc70..06c0f8e 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/model/Rating.java +++ b/src/main/java/ru/yandex/practicum/filmorate/model/Rating.java @@ -9,6 +9,7 @@ @AllArgsConstructor @NoArgsConstructor public class Rating { + @NotNull private Integer id; private String name; diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/User.java b/src/main/java/ru/yandex/practicum/filmorate/model/User.java index e06c2d7..cc96223 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/model/User.java +++ b/src/main/java/ru/yandex/practicum/filmorate/model/User.java @@ -1,5 +1,8 @@ package ru.yandex.practicum.filmorate.model; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.PastOrPresent; import lombok.Data; import java.time.LocalDate; @@ -9,9 +12,13 @@ @Data public class User { private Integer id; + @NotBlank(message = "Почта не может быть пустой") + @Email(message = "Некорректный формат email") private String email; + @NotBlank(message = "Логин не может быть пустой") private String login; private String name; + @PastOrPresent private LocalDate birthday; private Set friends = new HashSet<>(); } \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/FilmService.java b/src/main/java/ru/yandex/practicum/filmorate/service/FilmService.java index 35941a5..504b1fa 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/service/FilmService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/FilmService.java @@ -6,13 +6,11 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import ru.yandex.practicum.filmorate.exception.NotFoundException; -import ru.yandex.practicum.filmorate.exception.ValidationException; import ru.yandex.practicum.filmorate.model.Film; import ru.yandex.practicum.filmorate.storage.FilmStorage; import ru.yandex.practicum.filmorate.storage.GenreStorage; import ru.yandex.practicum.filmorate.storage.UserStorage; -import java.time.LocalDate; import java.util.Collection; @Slf4j @@ -38,7 +36,6 @@ public Collection findAll() { } public Film create(Film film) { - validateFilm(film); film = filmStorage.create(film); if (film.getGenres() != null && !film.getGenres().isEmpty()) { genreStorage.addGenresToFilm(film); @@ -50,7 +47,6 @@ public Film update(Film newFilm) { if (newFilm.getId() == null) { throw new NotFoundException("Фильм не найден"); } - validateFilm(newFilm); return filmStorage.update(newFilm); } @@ -75,10 +71,8 @@ public Film addLike(int id, int userId) { if (!userStorage.contains(userId)) { throw new NotFoundException("Пользователь с id = " + userId + " не найден"); } - Integer likes = filmStorage.addLike(id, userId); - Film film = filmStorage.findFilmById(id); - film.setLikes(likes); - return film; + filmStorage.addLike(id, userId); + return filmStorage.findFilmById(id); } public Film deleteLike(int id, int userId) { @@ -88,34 +82,11 @@ public Film deleteLike(int id, int userId) { if (!userStorage.contains(userId)) { throw new NotFoundException("Пользователь с id = " + userId + " не найден"); } - Integer likes = filmStorage.deleteLike(id, userId); - Film film = filmStorage.findFilmById(id); - film.setLikes(likes); - return film; + filmStorage.deleteLike(id, userId); + return filmStorage.findFilmById(id); } public Collection getPopular(int count) { return filmStorage.getPopular(count); } - - private void validateFilm(Film film) { - if (film.getDescription() != null && film.getDescription().length() > 200) { - log.warn("Ошибка лимита"); - throw new ValidationException("Описание превышает 200 символов"); - } - if (film.getReleaseDate().isBefore(LocalDate.of(1895, 12, 28))) { - log.warn("Ошибка даты"); - throw new ValidationException("Неверная дата релиза"); - } - - if (film.getName() == null || film.getName().isBlank()) { - log.warn("Пустое название фильма"); - throw new ValidationException("Название не может быть пустым"); - } - - if (film.getDuration() < 1) { - log.warn("Ошибка длительности"); - throw new ValidationException("Длительность не может быть меньше 1"); - } - } } \ No newline at end of file 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 f57ab2e..ab4de94 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/service/UserService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/UserService.java @@ -9,7 +9,6 @@ import ru.yandex.practicum.filmorate.model.User; import ru.yandex.practicum.filmorate.storage.UserStorage; -import java.time.LocalDate; import java.util.Collection; @Slf4j @@ -28,7 +27,9 @@ public Collection findAll() { } public User create(User user) { - validateUser(user); + if (user.getName() == null || user.getName().isBlank()) { + user.setName(user.getLogin()); + } user = userStorage.create(user); return user; } @@ -41,7 +42,6 @@ public User update(User newUser) { if (!userStorage.contains(newUser.getId())) { throw new NotFoundException("Пользователь с ID " + newUser.getId() + " не найден"); } - validateUser(newUser); return userStorage.update(newUser); } @@ -61,25 +61,6 @@ public void remove(int id) { userStorage.remove(id); } - private void validateUser(User user) { - if (!user.getEmail().contains("@")) { - log.warn("Ошибка в формате почты"); - throw new ValidationException("Неверная почта"); - } - if (user.getBirthday().isAfter(LocalDate.now())) { - log.warn("Ошибка в дате рождения"); - throw new ValidationException("Неверная дата рождения"); - } - if (user.getLogin().contains(" ")) { - log.warn("Ошибка в формате логина"); - throw new ValidationException("Неправильный формат логина"); - } - if (user.getName() == null || user.getName().trim().isEmpty()) { - log.info("Пустое имя пользователя заменено на логин"); - user.setName(user.getLogin()); - } - } - public void addFriend(int userId, int friendId) { if (!userStorage.contains(userId)) { throw new NotFoundException("Пользователь с id = " + userId + " не найден"); 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 cfc6cf9..0000000 --- a/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryFilmStorage.java +++ /dev/null @@ -1,115 +0,0 @@ -package ru.yandex.practicum.filmorate.storage; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; -import ru.yandex.practicum.filmorate.exception.ValidationException; -import ru.yandex.practicum.filmorate.exception.NotFoundException; -import ru.yandex.practicum.filmorate.model.Film; - -import java.time.LocalDate; -import java.util.*; - -@Slf4j -@Component("inMemoryFilmStorage") -public class InMemoryFilmStorage implements FilmStorage { - - private final Map films = new HashMap<>(); - - @Override - public Collection findAll() { - return films.values(); - } - - @Override - public Film create(Film film) { - validateFilm(film); - film.setId(getNextId()); - films.put(film.getId(), film); - return film; - } - - @Override - public Film update(Film newFilm) { - if (newFilm.getId() == null) { - log.warn("Не указан id"); - throw new ValidationException("Id должен быть указан"); - } - if (!films.containsKey(newFilm.getId())) { - log.warn("Фильм с указанным id не найден"); - throw new NotFoundException("Фильм с id = " + newFilm.getId() + " не найден"); - } - validateFilm(newFilm); - Film oldFilm = films.get(newFilm.getId()); - oldFilm.setDescription(newFilm.getDescription()); - oldFilm.setDuration(newFilm.getDuration()); - oldFilm.setName(newFilm.getName()); - oldFilm.setReleaseDate(newFilm.getReleaseDate()); - return oldFilm; - } - - @Override - public Film findFilmById(int id) { - if (!films.containsKey(id)) { - throw new NotFoundException("Фильма с таким id не найдено"); - } - return films.get(id); - } - - @Override - public Film remove(int id) { - - return null; - } - - @Override - public Collection getPopular(int count) { - return List.of(); - } - - @Override - public Integer addLike(int id, int userId) { - - return null; - } - - @Override - public Integer deleteLike(int id, int userId) { - - return null; - } - - @Override - public boolean contains(Integer id) { - return false; - } - - private int getNextId() { - int currentMaxId = films.keySet() - .stream() - .mapToInt(id -> id) - .max() - .orElse(0); - return ++currentMaxId; - } - - private void validateFilm(Film film) { - if (film.getDescription() != null && film.getDescription().length() > 200) { - log.warn("Ошибка лимита"); - throw new ValidationException("Описание превышает 200 символов"); - } - if (film.getReleaseDate().isBefore(LocalDate.of(1895, 12, 28))) { - log.warn("Ошибка даты"); - throw new ValidationException("Неверная дата релиза"); - } - - if (film.getName() == null || film.getName().isBlank()) { - log.warn("Пустое название фильма"); - throw new ValidationException("Название не может быть пустым"); - } - - if (film.getDuration() < 1) { - log.warn("Ошибка длительности"); - throw new ValidationException("Длительность не может быть меньше 1"); - } - } -} \ No newline at end of file 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 161b678..0000000 --- a/src/main/java/ru/yandex/practicum/filmorate/storage/InMemoryUserStorage.java +++ /dev/null @@ -1,115 +0,0 @@ -package ru.yandex.practicum.filmorate.storage; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; -import ru.yandex.practicum.filmorate.exception.NotFoundException; -import ru.yandex.practicum.filmorate.exception.ValidationException; -import ru.yandex.practicum.filmorate.model.User; - -import java.time.LocalDate; -import java.util.*; - -@Slf4j -@Component("inMemoryUserStorage") -public class InMemoryUserStorage implements UserStorage { - - private final Map users = new HashMap<>(); - - @Override - public Collection findAll() { - return users.values(); - } - - @Override - public User create(User user) { - validateUser(user); - user.setId(getNextId()); - users.put(user.getId(), user); - return user; - } - - @Override - public User update(User newUser) { - if (newUser.getId() == null) { - log.warn("Не указан id"); - throw new ValidationException("Id должен быть указан"); - } - if (!users.containsKey(newUser.getId())) { - log.warn("Пользователь с указанным id не найден"); - throw new NotFoundException("Пользователь с id = " + newUser.getId() + " не найден"); - } - validateUser(newUser); - User oldUser = users.get(newUser.getId()); - oldUser.setEmail(newUser.getEmail()); - oldUser.setName(newUser.getName()); - oldUser.setLogin(newUser.getLogin()); - oldUser.setBirthday(newUser.getBirthday()); - return oldUser; - } - - private int getNextId() { - int currentMaxId = users.keySet() - .stream() - .mapToInt(id -> id) - .max() - .orElse(0); - return ++currentMaxId; - } - - public User findUserById(int userId) { - if (!users.containsKey(userId)) { - log.warn("Пользователь не найден"); - throw new NotFoundException("Пользователя с таким id не найдено"); - } - return users.get(userId); - } - - @Override - public void remove(int id) { - - } - - @Override - public void addFriend(int userId, int friendId) { - - } - - @Override - public void deleteFriend(int userId, int friendId) { - - } - - @Override - public Set getFriendList(int userId) { - return Set.of(); - } - - @Override - public Set getCommonFriendList(int id, int otherId) { - return Set.of(); - } - - @Override - public boolean contains(Integer id) { - return false; - } - - private void validateUser(User user) { - if (!user.getEmail().contains("@")) { - log.warn("Ошибка в формате почты"); - throw new ValidationException("Неверная почта"); - } - if (user.getBirthday().isAfter(LocalDate.now())) { - log.warn("Ошибка в дате рождения"); - throw new ValidationException("Неверная дата рождения"); - } - if (user.getLogin().contains(" ")) { - log.warn("Ошибка в формате логина"); - throw new ValidationException("Неправильный формат логина"); - } - if (user.getName() == null || user.getName().trim().isEmpty()) { - log.info("Пустое имя пользователя заменено на логин"); - user.setName(user.getLogin()); - } - } -} \ No newline at end of file diff --git a/src/test/java/ru/yandex/practicum/filmorate/controller/InMemoryFilmStorageTest.java b/src/test/java/ru/yandex/practicum/filmorate/controller/InMemoryFilmStorageTest.java deleted file mode 100644 index 875d090..0000000 --- a/src/test/java/ru/yandex/practicum/filmorate/controller/InMemoryFilmStorageTest.java +++ /dev/null @@ -1,36 +0,0 @@ -package ru.yandex.practicum.filmorate.controller; - -import org.junit.jupiter.api.Test; -import ru.yandex.practicum.filmorate.exception.ValidationException; -import ru.yandex.practicum.filmorate.model.Film; -import ru.yandex.practicum.filmorate.storage.InMemoryFilmStorage; - -import static org.junit.jupiter.api.Assertions.*; - -import java.time.LocalDate; - -public class InMemoryFilmStorageTest { - InMemoryFilmStorage inMemoryFilmStorage = new InMemoryFilmStorage(); - - @Test - void getFilmTest() { - Film film = new Film(); - film.setReleaseDate(LocalDate.now()); - film.setDuration(100); - film.setName("test"); - film.setDescription("t"); - inMemoryFilmStorage.create(film); - film.setName(" "); - assertThrows(ValidationException.class, () -> inMemoryFilmStorage.update(film)); - film.setName("test"); - String description200 = "A".repeat(200); - film.setDescription(film.getDescription() + description200); - assertThrows(ValidationException.class, () -> inMemoryFilmStorage.update(film)); - film.setDescription("t"); - film.setReleaseDate(LocalDate.of(1895, 12, 27)); - assertThrows(ValidationException.class, () -> inMemoryFilmStorage.update(film)); - film.setReleaseDate(LocalDate.now()); - film.setDuration(0); - assertThrows(ValidationException.class, () -> inMemoryFilmStorage.update(film)); - } -} \ No newline at end of file diff --git a/src/test/java/ru/yandex/practicum/filmorate/controller/InMemoryUserStorageTest.java b/src/test/java/ru/yandex/practicum/filmorate/controller/InMemoryUserStorageTest.java deleted file mode 100644 index 3174a9a..0000000 --- a/src/test/java/ru/yandex/practicum/filmorate/controller/InMemoryUserStorageTest.java +++ /dev/null @@ -1,36 +0,0 @@ -package ru.yandex.practicum.filmorate.controller; - -import org.junit.jupiter.api.Test; -import ru.yandex.practicum.filmorate.exception.ValidationException; -import ru.yandex.practicum.filmorate.model.User; -import ru.yandex.practicum.filmorate.storage.InMemoryUserStorage; - -import java.time.LocalDate; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - -public class InMemoryUserStorageTest { - InMemoryUserStorage inMemoryUserStorage = new InMemoryUserStorage(); - - @Test - void getFilmTest() { - User user = new User(); - user.setName("test1"); - user.setLogin("test2"); - user.setBirthday(LocalDate.of(2000, 12, 27)); - user.setEmail("test@"); - inMemoryUserStorage.create(user); - user.setEmail(" "); - assertThrows(ValidationException.class, () -> inMemoryUserStorage.update(user)); - user.setEmail("test2@"); - user.setLogin("TEST ETS"); - assertThrows(ValidationException.class, () -> inMemoryUserStorage.update(user)); - user.setLogin("test2"); - user.setName(" "); - inMemoryUserStorage.update(user); - assertEquals(user.getLogin(), user.getName(), "Имя совпадает с логином"); - user.setBirthday(LocalDate.of(2200, 12, 27)); - assertThrows(ValidationException.class, () -> inMemoryUserStorage.update(user)); - } -} \ No newline at end of file diff --git a/src/test/java/ru/yandex/practicum/filmorate/db/FilmDbStorageTest.java b/src/test/java/ru/yandex/practicum/filmorate/db/FilmDbStorageTest.java index e0304cb..17ad066 100644 --- a/src/test/java/ru/yandex/practicum/filmorate/db/FilmDbStorageTest.java +++ b/src/test/java/ru/yandex/practicum/filmorate/db/FilmDbStorageTest.java @@ -80,8 +80,8 @@ public void testRemoveFilm() { @Test public void testGetPopular() { List popular = filmDbStorage.getPopular(3); - assertEquals(5, popular.get(0).getLikes()); - assertEquals(3, popular.get(1).getLikes()); + assertEquals(popular.get(0), filmDbStorage.findFilmById(3)); + assertEquals(popular.get(1), filmDbStorage.findFilmById(2)); } @Test From 4a4fe4bfbd94c81e3e0182e5e23de9fa86a26cd0 Mon Sep 17 00:00:00 2001 From: Denis Panasyuk Date: Thu, 25 Dec 2025 00:05:14 +0300 Subject: [PATCH 12/12] =?UTF-8?q?=D0=92=D0=BD=D0=B5=D1=81=D0=B5=D0=BD?= =?UTF-8?q?=D1=8B=20=D0=BF=D1=80=D0=B0=D0=B2=D0=BA=D0=B8=20=D1=81=D0=BE?= =?UTF-8?q?=D0=B3=D0=BB=D0=B0=D1=81=D0=BD=D0=BE=20=D0=B7=D0=B0=D0=BC=D0=B5?= =?UTF-8?q?=D1=87=D0=B0=D0=BD=D0=B8=D1=8F=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../practicum/filmorate/model/ErrorResponse.java | 2 +- src/main/resources/schema.sql | 12 ++++++------ src/test/resources/schema.sql | 12 ++++++------ 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/ErrorResponse.java b/src/main/java/ru/yandex/practicum/filmorate/model/ErrorResponse.java index f7e345a..9336824 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/model/ErrorResponse.java +++ b/src/main/java/ru/yandex/practicum/filmorate/model/ErrorResponse.java @@ -4,7 +4,7 @@ @Getter public class ErrorResponse { - String error; + private final String error; public ErrorResponse(String error) { this.error = error; diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index 387209e..7cf15af 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -1,15 +1,15 @@ CREATE TABLE IF NOT EXISTS genres ( - genre_id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + genre_id INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY PRIMARY KEY, name VARCHAR NOT NULL ); CREATE TABLE IF NOT EXISTS mpa ( - mpa_id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + mpa_id INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY PRIMARY KEY, name CHARACTER VARYING(50) NOT NULL ); CREATE TABLE IF NOT EXISTS users ( - user_id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + user_id INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY PRIMARY KEY, name CHARACTER VARYING(255) NOT NULL, login CHARACTER VARYING(100) NOT NULL, email CHARACTER VARYING(255) NOT NULL, @@ -17,7 +17,7 @@ CREATE TABLE IF NOT EXISTS users ( ); CREATE TABLE IF NOT EXISTS films ( - film_id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + film_id INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY PRIMARY KEY, name CHARACTER VARYING(255) NOT NULL, description TEXT NOT NULL, release_date DATE, @@ -27,8 +27,8 @@ CREATE TABLE IF NOT EXISTS films ( ); CREATE TABLE IF NOT EXISTS films_genres ( - film_id INTEGER, - genre_id INTEGER, + film_id INTEGER NOT NULL, + genre_id INTEGER NOT NULL, FOREIGN KEY (film_id) REFERENCES films (film_id) ON DELETE CASCADE, FOREIGN KEY (genre_id) REFERENCES genres (genre_id) ON DELETE CASCADE, PRIMARY KEY (film_id, genre_id) diff --git a/src/test/resources/schema.sql b/src/test/resources/schema.sql index 387209e..7cf15af 100644 --- a/src/test/resources/schema.sql +++ b/src/test/resources/schema.sql @@ -1,15 +1,15 @@ CREATE TABLE IF NOT EXISTS genres ( - genre_id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + genre_id INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY PRIMARY KEY, name VARCHAR NOT NULL ); CREATE TABLE IF NOT EXISTS mpa ( - mpa_id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + mpa_id INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY PRIMARY KEY, name CHARACTER VARYING(50) NOT NULL ); CREATE TABLE IF NOT EXISTS users ( - user_id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + user_id INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY PRIMARY KEY, name CHARACTER VARYING(255) NOT NULL, login CHARACTER VARYING(100) NOT NULL, email CHARACTER VARYING(255) NOT NULL, @@ -17,7 +17,7 @@ CREATE TABLE IF NOT EXISTS users ( ); CREATE TABLE IF NOT EXISTS films ( - film_id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + film_id INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY PRIMARY KEY, name CHARACTER VARYING(255) NOT NULL, description TEXT NOT NULL, release_date DATE, @@ -27,8 +27,8 @@ CREATE TABLE IF NOT EXISTS films ( ); CREATE TABLE IF NOT EXISTS films_genres ( - film_id INTEGER, - genre_id INTEGER, + film_id INTEGER NOT NULL, + genre_id INTEGER NOT NULL, FOREIGN KEY (film_id) REFERENCES films (film_id) ON DELETE CASCADE, FOREIGN KEY (genre_id) REFERENCES genres (genre_id) ON DELETE CASCADE, PRIMARY KEY (film_id, genre_id)