From 22dd861ee64412747a63b35c141b32a3ab6e6fe2 Mon Sep 17 00:00:00 2001 From: Remenchik Ilya Date: Tue, 22 Apr 2025 15:16:32 +0300 Subject: [PATCH 01/13] add recommendations --- .../filmorate/controller/UserController.java | 12 ++- .../filmorate/dal/FilmRepository.java | 24 +++++ .../filmorate/service/film/FilmService.java | 10 ++ .../filmorate/storage/film/FilmDbStorage.java | 93 ++++++++++++++++++- src/main/resources/data.sql | 34 +++++-- 5 files changed, 161 insertions(+), 12 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 1c16497..1af4c04 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java @@ -5,8 +5,10 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.*; +import ru.yandex.practicum.filmorate.dto.FilmDto; import ru.yandex.practicum.filmorate.dto.UserDto; import ru.yandex.practicum.filmorate.model.User; +import ru.yandex.practicum.filmorate.service.film.FilmService; import ru.yandex.practicum.filmorate.service.user.UserService; import ru.yandex.practicum.filmorate.storage.user.UserDbStorage; @@ -17,10 +19,12 @@ @RequestMapping("/users") public class UserController { private final UserService userService; + private final FilmService filmService; @Autowired - public UserController(UserDbStorage userDbStorage) { + public UserController(UserDbStorage userDbStorage, FilmService filmService) { userService = new UserService(userDbStorage); + this.filmService = filmService; } @GetMapping @@ -72,5 +76,11 @@ public Collection getFriends(@PathVariable int id) { log.info("getFriends"); return userService.getFriends(id); } + + @GetMapping("/{id}/recommendations") + public Collection getRecommendations(@PathVariable long id) { + log.info("get recommendations"); + return filmService.getRecommendations(id); + } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/FilmRepository.java b/src/main/java/ru/yandex/practicum/filmorate/dal/FilmRepository.java index 4adda28..87bc528 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/FilmRepository.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/FilmRepository.java @@ -1,6 +1,8 @@ package ru.yandex.practicum.filmorate.dal; +import org.springframework.dao.DataAccessException; import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.ResultSetExtractor; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; @@ -14,6 +16,8 @@ import java.sql.Date; import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; import java.util.*; import java.util.stream.Collectors; @@ -172,6 +176,25 @@ public Film update(Film film) { return findById(film.getId()).orElseThrow(() -> new IllegalStateException("Updated film not found, id: " + film.getId())); } + public Map> getAllLikesGroupedByUser() { + String sql = "SELECT * FROM Likes"; + + return jdbc.query(sql, new ResultSetExtractor>>() { + @Override + public Map> extractData(ResultSet rs) throws SQLException, DataAccessException { + Map> userLikes = new HashMap<>(); + while (rs.next()) { + long userId = rs.getLong("user_id"); + long filmId = rs.getLong("film_id"); + + List filmIds = userLikes.computeIfAbsent(userId, k -> new ArrayList<>()); + filmIds.add(filmId); + } + return userLikes; + } + }); + } + private void deleteGenres(long filmId) { String sql = "DELETE FROM film_genre WHERE film_id = ?"; jdbc.update(sql, filmId); @@ -194,6 +217,7 @@ private void saveGenres(Film film) { } } + public Set getLikes(long filmId) { List likesList = jdbc.queryForList(GET_LIKES_QUERY, Long.class, filmId); return new HashSet<>(likesList); diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/film/FilmService.java b/src/main/java/ru/yandex/practicum/filmorate/service/film/FilmService.java index 4f83d6e..01653ec 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/service/film/FilmService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/film/FilmService.java @@ -134,6 +134,16 @@ public RatingDto getRatingById(int id) { return RatingMapper.mapToRatingDto(rating); } + + public List getRecommendations(long id) { + userExists(id); + log.info("Getting recommendations films for user with id = {}", id); + Collection films = filmStorage.getRecommendations(id); + return films.stream() + .map(film -> FilmMapper.mapToFilmDto(film, filmStorage.getLikes(film.getId()))) + .collect(Collectors.toList()); + } + private void validateFilm(Film film) { if (film.getReleaseDate() != null && film.getReleaseDate().isBefore(MIN_RELEASE_DATE)) { log.warn("Validation failed"); diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/film/FilmDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/film/FilmDbStorage.java index 5cb7f56..76f23c4 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/storage/film/FilmDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/film/FilmDbStorage.java @@ -9,9 +9,8 @@ import ru.yandex.practicum.filmorate.model.Genre; import ru.yandex.practicum.filmorate.model.Rating; -import java.util.Collection; -import java.util.List; -import java.util.Set; +import java.util.*; +import java.util.stream.Collectors; @Repository public class FilmDbStorage implements FilmStorage { @@ -109,4 +108,92 @@ private void validateGenresExist(List genres) { } } } + + public Collection getRecommendations(long targetUserId) { + Optional mostSimilarUserOpt = findUserWithMostCommonLikes(targetUserId); + + if (mostSimilarUserOpt.isEmpty()) { + return Collections.emptyList(); + } + long mostSimilarUserId = mostSimilarUserOpt.get(); + + Map> allUserLikes = filmRepository.getAllLikesGroupedByUser(); + + Set filmsLikedByTarget = new HashSet<>(allUserLikes.getOrDefault(targetUserId, Collections.emptyList())); + + List filmsLikedBySimilar = allUserLikes.getOrDefault(mostSimilarUserId, Collections.emptyList()); + + List recommendedFilmIds = filmsLikedBySimilar.stream() + .filter(filmId -> !filmsLikedByTarget.contains(filmId)) + .collect(Collectors.toList()); + + if (recommendedFilmIds.isEmpty()) { + return Collections.emptyList(); + } + + return recommendedFilmIds.stream() + .map(filmId -> { + try { + return getFilmById(filmId); + } catch (NotFoundException e) { + return null; + } + }) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } + + public Optional findUserWithMostCommonLikes(long id) { + Map crossesFilms = findCrosses(id); + + if (crossesFilms == null || crossesFilms.isEmpty()) { + return Optional.empty(); + } + + Optional> maxEntry = crossesFilms.entrySet() + .stream() + .max(Map.Entry.comparingByValue()); + + return maxEntry.map(Map.Entry::getKey); + } + + + private Map findCrosses(long targetUserId) { + Map> allUserLikes = filmRepository.getAllLikesGroupedByUser(); + + List filmsLikedByTargetUser = allUserLikes.get(targetUserId); + + if (filmsLikedByTargetUser == null || filmsLikedByTargetUser.isEmpty()) { + return Collections.emptyMap(); + } + + Set targetLikedSet = new HashSet<>(filmsLikedByTargetUser); + + Map commonLikesCountMap = new HashMap<>(); + + for (Map.Entry> entry : allUserLikes.entrySet()) { + long otherUserId = entry.getKey(); + List filmsLikedByOtherUser = entry.getValue(); + + if (otherUserId == targetUserId) { + continue; + } + + if (filmsLikedByOtherUser == null || filmsLikedByOtherUser.isEmpty()) { + continue; + } + + int commonCount = 0; + for (Long filmLikedByOther : filmsLikedByOtherUser) { + if (targetLikedSet.contains(filmLikedByOther)) { + commonCount++; + } + } + + if (commonCount > 0) { + commonLikesCountMap.put(otherUserId, commonCount); + } + } + return commonLikesCountMap; + } } \ No newline at end of file diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql index 2b54194..33c30a6 100644 --- a/src/main/resources/data.sql +++ b/src/main/resources/data.sql @@ -1,18 +1,36 @@ -INSERT INTO Users (email, login, name, birthday) -VALUES ('test@user.com', 'testlogin', 'Test User Name', '1991-11-11'); - -INSERT INTO Users (email, login, name, birthday) -VALUES ('test1@user.com', 'test2login', 'Another User', '1992-11-11'); - -INSERT INTO Rating(rating_id, rating_name) +INSERT INTO RATING(rating_id, rating_name) VALUES (1, 'G'), (2, 'PG'), (3, 'PG-13'), (4, 'R'), (5, 'NC-17'); +INSERT INTO USERS (email, login, name, birthday) +VALUES ('test@user.com', 'testlogin', 'Test User Name', '1991-11-11'); + +INSERT INTO USERS (email, login, name, birthday) +VALUES ('test1@user.com', 'test2login', 'Another User', '1992-11-11'); + +INSERT INTO USERS (email, login, name, birthday) +VALUES ('test2@user.com', 'test3login', 'Another User1', '1993-11-11'); + +INSERT INTO FILMS (NAME, DESCRIPTION, RELEASE_DATE, DURATION, RATING_ID) +VALUES ('film1', 'its film1', '2010-10-10', '100', '1'), + ('film2', 'its film2', '2020-12-20', '200', '2'), + ('film3', 'its film3', '2003-03-30', '300', '3'), + ('film4', 'its film4', '2024-04-14', '400', '4'); + +INSERT INTO LIKES(user_id, film_id) +VALUES (1, 1), + (1, 3), + (2, 1), + (2, 2), + (2, 4), + (3, 1), + (3, 2), + (3, 4); -INSERT INTO Genre (name) +INSERT INTO GENRE (name) VALUES ('Комедия'), ('Драма'), ('Мультфильм'), From d3e9cd24c1f944b8cd454c02f3f062e3e47014fa Mon Sep 17 00:00:00 2001 From: Plastinin-Igor Date: Tue, 22 Apr 2025 18:43:39 +0300 Subject: [PATCH 02/13] =?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=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D1=8F-=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F=20=D1=80=D0=B5=D0=B6=D0=B8=D1=81?= =?UTF-8?q?=D1=81=D1=91=D1=80=D0=B0,=20=D0=BF=D0=BE=D0=BB=D1=83=D1=87?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D1=8F=20=D1=81=D0=BF=D0=B8=D1=81=D0=BA=D0=B0?= =?UTF-8?q?=20=D1=80=D0=B5=D0=B6=D0=B8=D1=81=D1=81=D1=91=D1=80=D0=BE=D0=B2?= =?UTF-8?q?=20=D0=BF=D0=BE=20id,=20=D0=BF=D0=BE=D0=BB=D1=83=D1=87=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D1=81=D0=BF=D0=B8=D1=81=D0=BA=D0=B0=20?= =?UTF-8?q?=D0=B2=D1=81=D0=B5=D1=85=20=D1=80=D0=B5=D0=B6=D0=B8=D1=81=D1=81?= =?UTF-8?q?=D1=91=D1=80=D0=BE=D0=B2.=20=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8?= =?UTF-8?q?=D0=B7=D0=BE=D0=B2=D0=B0=D0=BD=D0=BE=20=D0=B4=D0=BE=D0=B1=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5/=D0=B8=D1=81=D0=BF=D1=80?= =?UTF-8?q?=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5/=D0=BF=D0=BE=D0=BB?= =?UTF-8?q?=D1=83=D1=87=D0=B5=D0=BD=D0=B8=D0=B5=20=D1=84=D0=B8=D0=BB=D1=8C?= =?UTF-8?q?=D0=BC=D0=BE=D0=B2=20=D1=81=20=D1=83=D1=87=D0=B5=D1=82=D0=BE?= =?UTF-8?q?=D0=BC=20=D0=B8=D0=BD=D1=84=D0=BE=D1=80=D0=BC=D0=B0=D1=86=D0=B8?= =?UTF-8?q?=D0=B8=20=D0=BE=20=D1=80=D0=B5=D0=B6=D0=B8=D1=81=D1=81=D0=B5?= =?UTF-8?q?=D1=80=D0=B5.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/DirectorController.java | 55 ++++++++++++ .../filmorate/dal/DirectorRepository.java | 48 +++++++++++ .../filmorate/dal/FilmRepository.java | 86 ++++++++++++++++++- .../dal/mapper/DirectorRowMapper.java | 20 +++++ .../filmorate/dal/mapper/FilmRowMapper.java | 1 + .../practicum/filmorate/dto/DirectorDto.java | 9 ++ .../practicum/filmorate/dto/FilmDto.java | 1 + .../filmorate/dto/NewFilmRequest.java | 2 + .../filmorate/dto/UpdateFilmRequest.java | 2 + .../filmorate/mappers/DirectorMapper.java | 36 ++++++++ .../filmorate/mappers/FilmMapper.java | 12 +++ .../practicum/filmorate/model/Director.java | 9 ++ .../practicum/filmorate/model/Film.java | 1 + .../service/director/DirectorService.java | 54 ++++++++++++ .../storage/director/DirectorDBStorage.java | 43 ++++++++++ src/main/resources/application.properties | 8 +- src/main/resources/data.sql | 45 +++++++--- src/main/resources/schema.sql | 17 ++++ 18 files changed, 431 insertions(+), 18 deletions(-) create mode 100644 src/main/java/ru/yandex/practicum/filmorate/controller/DirectorController.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/dal/DirectorRepository.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/dal/mapper/DirectorRowMapper.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/dto/DirectorDto.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/mappers/DirectorMapper.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/model/Director.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/service/director/DirectorService.java create mode 100644 src/main/java/ru/yandex/practicum/filmorate/storage/director/DirectorDBStorage.java diff --git a/src/main/java/ru/yandex/practicum/filmorate/controller/DirectorController.java b/src/main/java/ru/yandex/practicum/filmorate/controller/DirectorController.java new file mode 100644 index 0000000..4e9b914 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/DirectorController.java @@ -0,0 +1,55 @@ +package ru.yandex.practicum.filmorate.controller; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import ru.yandex.practicum.filmorate.dto.DirectorDto; +import ru.yandex.practicum.filmorate.model.Director; +import ru.yandex.practicum.filmorate.service.director.DirectorService; + +import java.util.Collection; + +@Slf4j +@RestController +@RequestMapping("/directors") +@RequiredArgsConstructor +@Validated +public class DirectorController { + + private final DirectorService directorService; + + @GetMapping + public Collection getDirectors() { + log.info("Received GET /directors request"); + return directorService.getDirectors(); + } + + @GetMapping("/{directorId}") + public DirectorDto getDirectorById(@PathVariable long directorId) { + log.info("Received GET /directors/{} request", directorId); + return directorService.getDirectorById(directorId); + } + + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + public DirectorDto addDirector(@RequestBody Director director) { + log.info("Received POST /directors request with body: {}", director); + return directorService.addDirector(director); + } + + @PutMapping + public DirectorDto updateDirector(@RequestBody Director director) { + log.info("Received PUT /directors request with body: {}", director); + return directorService.updateDirector(director); + } + + @DeleteMapping("/{directorId}") + public void deleteDirector(@PathVariable long directorId) { + log.info("Received DELETE /directors/{} request", directorId); + directorService.deleteDirector(directorId); + + } + +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/DirectorRepository.java b/src/main/java/ru/yandex/practicum/filmorate/dal/DirectorRepository.java new file mode 100644 index 0000000..f18d332 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/DirectorRepository.java @@ -0,0 +1,48 @@ +package ru.yandex.practicum.filmorate.dal; + +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Repository; +import ru.yandex.practicum.filmorate.model.Director; + +import java.util.List; +import java.util.Optional; + +@Repository +public class DirectorRepository extends BaseRepository { + + private static final String FIND_ALL_QUERY = "SELECT * FROM DIRECTORS d"; + private static final String FIND_BY_ID_QUERY = "SELECT * FROM DIRECTORS d WHERE d.director_id = ?"; + private static final String INSERT_QUERY = "INSERT INTO DIRECTORS (NAME) VALUES(?)"; + private static final String UPDATE_QUERY = "UPDATE DIRECTORS SET NAME = ? WHERE DIRECTOR_ID = ?"; + private static final String DELETE_QUERY = "DELETE FROM DIRECTORS WHERE DIRECTOR_ID = ?"; + + public DirectorRepository(JdbcTemplate jdbc, + RowMapper mapper) { + super(jdbc, mapper); + } + + public List findAll() { + return findMany(FIND_ALL_QUERY); + } + + public Optional findById(long id) { + return findOne(FIND_BY_ID_QUERY, id); + } + + public Director save(Director director) { + Long id = (Long) insert(INSERT_QUERY, director.getName()); + director.setId(id); + return director; + } + + public Director update(Director director) { + update(UPDATE_QUERY, director.getName(), director.getId()); + return director; + } + + public void delete(long id) { + update(DELETE_QUERY, id); + } + +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/FilmRepository.java b/src/main/java/ru/yandex/practicum/filmorate/dal/FilmRepository.java index 4adda28..825184e 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/FilmRepository.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/FilmRepository.java @@ -8,6 +8,7 @@ import org.springframework.jdbc.support.KeyHolder; import org.springframework.stereotype.Repository; import ru.yandex.practicum.filmorate.dal.mapper.FilmRowMapper; +import ru.yandex.practicum.filmorate.model.Director; import ru.yandex.practicum.filmorate.model.Film; import ru.yandex.practicum.filmorate.model.Genre; import ru.yandex.practicum.filmorate.model.Rating; @@ -33,9 +34,18 @@ public class FilmRepository extends BaseRepository { private static final String FIND_GENRES_FOR_FILMS_QUERY = "SELECT fg.film_id, g.id as genre_id, g.name as genre_name " + - "FROM genre g " + - "JOIN film_genre fg ON g.id = fg.genre_id " + - "WHERE fg.film_id IN (:filmIds)"; + "FROM genre g " + + "JOIN film_genre fg ON g.id = fg.genre_id " + + "WHERE fg.film_id IN (:filmIds)"; + + private static final String FIND_DIRECTOR_FOR_FILMS_QUERY = """ + SELECT fd.FILM_ID, + d.DIRECTOR_ID, + d.NAME as DIRECTOR_NAME + FROM FILM_DIRECTORS fd + JOIN DIRECTORS d ON (fd.DIRECTOR_ID = d.DIRECTOR_ID) + WHERE fd.film_id in (:filmIds) + """; private final JdbcTemplate jdbc; private final NamedParameterJdbcTemplate namedJdbcTemplate; @@ -51,6 +61,16 @@ private static class FilmGenreRelation { } } + private static class FilmDirectorRelation { + final long filmId; + final Director director; + + FilmDirectorRelation(long filmId, Director director) { + this.filmId = filmId; + this.director = director; + } + } + private final RowMapper filmGenreRelationRowMapper = (rs, rowNum) -> { Genre genre = new Genre(); genre.setId(rs.getInt("genre_id")); @@ -58,6 +78,13 @@ private static class FilmGenreRelation { return new FilmGenreRelation(rs.getLong("film_id"), genre); }; + private final RowMapper filmDirectorRelationRowMapper = (rs, rowNum) -> { + Director director = new Director(); + director.setId(rs.getLong("director_id")); + director.setName(rs.getString("director_name")); + return new FilmDirectorRelation(rs.getLong("film_id"), director); + }; + public FilmRepository(JdbcTemplate jdbc, NamedParameterJdbcTemplate namedJdbcTemplate, FilmRowMapper filmMapper) { @@ -83,6 +110,7 @@ public List findAll() { if (!films.isEmpty()) { setGenresForFilms(films); + setDirectorForFilm(films); } return films; } @@ -95,6 +123,7 @@ public Optional findById(long id) { } else { Film film = films.get(0); setGenresForFilms(List.of(film)); + setDirectorForFilm(List.of(film)); return Optional.of(film); } } @@ -148,7 +177,7 @@ public Film save(Film film) { film.setId(id); saveGenres(film); - + saveDirector(film); return findById(id).orElseThrow(() -> new IllegalStateException("Saved film not found, id: " + id)); } @@ -206,4 +235,53 @@ public void addLike(long filmId, long userId) { public void removeLike(long filmId, long userId) { jdbc.update(REMOVE_LIKE_QUERY, filmId, userId); } + + private void saveDirector(Film film) { + if (film.getDirector() == null || film.getDirector().isEmpty()) { + return; + } + String sql = "INSERT INTO FILM_DIRECTORS (FILM_ID, DIRECTOR_ID) VALUES(?, ?)"; + + List batchArgs = film.getDirector().stream() + .filter(Objects::nonNull) + .filter(director -> director.getId() > 0) + .distinct() + .map(director -> new Object[]{film.getId(), director.getId()}) + .collect(Collectors.toList()); + + if (!batchArgs.isEmpty()) { + jdbc.batchUpdate(sql, batchArgs); + } + } + + private void setDirectorForFilm(List films) { + List filmIds = films.stream() + .map(Film::getId) + .collect(Collectors.toList()); + + if (filmIds.isEmpty()) { + return; + } + + MapSqlParameterSource parameters = new MapSqlParameterSource(); + parameters.addValue("filmIds", filmIds); + + + List relations = namedJdbcTemplate.query( + FIND_DIRECTOR_FOR_FILMS_QUERY, + parameters, + filmDirectorRelationRowMapper + ); + + Map> directorsByFilmId = relations.stream() + .collect(groupingBy( + relation -> relation.filmId, + mapping(relation -> relation.director, toList()) + )); + + films.forEach(film -> + film.setDirector(directorsByFilmId.getOrDefault(film.getId(), Collections.emptyList())) + ); + } + } \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/mapper/DirectorRowMapper.java b/src/main/java/ru/yandex/practicum/filmorate/dal/mapper/DirectorRowMapper.java new file mode 100644 index 0000000..e306b93 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/mapper/DirectorRowMapper.java @@ -0,0 +1,20 @@ +package ru.yandex.practicum.filmorate.dal.mapper; + +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Component; +import ru.yandex.practicum.filmorate.model.Director; + +import java.sql.ResultSet; +import java.sql.SQLException; + +@Component +public class DirectorRowMapper implements RowMapper { + @Override + public Director mapRow(ResultSet rs, int rowNum) throws SQLException { + Director director = new Director(); + director.setId(rs.getLong("director_id")); + director.setName(rs.getString("name")); + + return director; + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/mapper/FilmRowMapper.java b/src/main/java/ru/yandex/practicum/filmorate/dal/mapper/FilmRowMapper.java index dcda2df..b79b139 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/mapper/FilmRowMapper.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/mapper/FilmRowMapper.java @@ -27,6 +27,7 @@ public Film mapRow(ResultSet rs, int rowNum) throws SQLException { film.setMpa(mpa); film.setGenres(new ArrayList<>()); + film.setDirector(new ArrayList<>()); return film; } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dto/DirectorDto.java b/src/main/java/ru/yandex/practicum/filmorate/dto/DirectorDto.java new file mode 100644 index 0000000..e617f0f --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/dto/DirectorDto.java @@ -0,0 +1,9 @@ +package ru.yandex.practicum.filmorate.dto; + +import lombok.Data; + +@Data +public class DirectorDto { + private Long id; + private String name; +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/dto/FilmDto.java b/src/main/java/ru/yandex/practicum/filmorate/dto/FilmDto.java index ccfb1ba..b9fe18d 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dto/FilmDto.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dto/FilmDto.java @@ -18,4 +18,5 @@ public class FilmDto { private RatingDto mpa; private List genres = new ArrayList<>(); private Set likes = new HashSet<>(); + private List directors = new ArrayList<>(); } \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/dto/NewFilmRequest.java b/src/main/java/ru/yandex/practicum/filmorate/dto/NewFilmRequest.java index 9a18bba..a60d2a2 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dto/NewFilmRequest.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dto/NewFilmRequest.java @@ -1,6 +1,7 @@ package ru.yandex.practicum.filmorate.dto; import lombok.Data; +import ru.yandex.practicum.filmorate.model.Director; import ru.yandex.practicum.filmorate.model.Rating; @Data @@ -10,4 +11,5 @@ public class NewFilmRequest { private String releaseDate; private String duration; private Rating ratingId; + private Director director; } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dto/UpdateFilmRequest.java b/src/main/java/ru/yandex/practicum/filmorate/dto/UpdateFilmRequest.java index d9fda56..2a097d0 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dto/UpdateFilmRequest.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dto/UpdateFilmRequest.java @@ -1,6 +1,7 @@ package ru.yandex.practicum.filmorate.dto; import lombok.Data; +import ru.yandex.practicum.filmorate.model.Director; import ru.yandex.practicum.filmorate.model.Rating; @Data @@ -10,6 +11,7 @@ public class UpdateFilmRequest { private String releaseDate; private String duration; private Rating ratingId; + private Director director; public boolean hasName() { return !(name == null || name.isBlank()); diff --git a/src/main/java/ru/yandex/practicum/filmorate/mappers/DirectorMapper.java b/src/main/java/ru/yandex/practicum/filmorate/mappers/DirectorMapper.java new file mode 100644 index 0000000..2e34790 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/mappers/DirectorMapper.java @@ -0,0 +1,36 @@ +package ru.yandex.practicum.filmorate.mappers; + +import lombok.experimental.UtilityClass; +import ru.yandex.practicum.filmorate.dto.DirectorDto; +import ru.yandex.practicum.filmorate.model.Director; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +@UtilityClass +public class DirectorMapper { + public static Director mapToDirector(Director request) { + Director director = new Director(); + director.setId(request.getId()); + director.setName(request.getName()); + return director; + } + + public static DirectorDto mapToDirectorDto(Director director) { + DirectorDto directorDto = new DirectorDto(); + directorDto.setId(director.getId()); + directorDto.setName(director.getName()); + return directorDto; + } + + public static List mapToDirectorDtoList(List directors) { + if (directors == null || directors.isEmpty()) { + return Collections.emptyList(); + } + return directors.stream() + .map(DirectorMapper::mapToDirectorDto) + .collect(Collectors.toList()); + } + +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/mappers/FilmMapper.java b/src/main/java/ru/yandex/practicum/filmorate/mappers/FilmMapper.java index 66c3506..7229aef 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/mappers/FilmMapper.java +++ b/src/main/java/ru/yandex/practicum/filmorate/mappers/FilmMapper.java @@ -2,6 +2,7 @@ import lombok.experimental.UtilityClass; import ru.yandex.practicum.filmorate.dto.FilmDto; +import ru.yandex.practicum.filmorate.model.Director; import ru.yandex.practicum.filmorate.model.Film; import ru.yandex.practicum.filmorate.model.Genre; @@ -33,6 +34,16 @@ public static Film mapToFilm(Film requestFilm) { film.setGenres(new ArrayList<>()); } + if (requestFilm.getDirector() != null) { + List directorsWithIdOnly = requestFilm.getDirector() + .stream() + .filter(d -> d != null && d.getId() != 0) + .collect(Collectors.toList()); + film.setDirector(directorsWithIdOnly); + } else { + film.setDirector(new ArrayList<>()); + } + return film; } @@ -48,6 +59,7 @@ public static FilmDto mapToFilmDto(Film film, Set likes) { dto.setMpa(RatingMapper.mapToRatingDto(film.getMpa())); dto.setGenres(GenreMapper.mapToGenreDtoList(film.getGenres())); dto.setLikes(Optional.ofNullable(likes).orElse(Collections.emptySet())); + dto.setDirectors(DirectorMapper.mapToDirectorDtoList(film.getDirector())); return dto; } diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/Director.java b/src/main/java/ru/yandex/practicum/filmorate/model/Director.java new file mode 100644 index 0000000..e4e8ef4 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/model/Director.java @@ -0,0 +1,9 @@ +package ru.yandex.practicum.filmorate.model; + +import lombok.Data; + +@Data +public class Director { + private Long id; + private String name; +} 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 79bf0c2..f693771 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/model/Film.java +++ b/src/main/java/ru/yandex/practicum/filmorate/model/Film.java @@ -23,4 +23,5 @@ public class Film { @NotNull(message = "Rating cannot be null") private Rating mpa; private List genres = new ArrayList<>(); + private List director = new ArrayList<>(); } diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/director/DirectorService.java b/src/main/java/ru/yandex/practicum/filmorate/service/director/DirectorService.java new file mode 100644 index 0000000..35aebbf --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/service/director/DirectorService.java @@ -0,0 +1,54 @@ +package ru.yandex.practicum.filmorate.service.director; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import ru.yandex.practicum.filmorate.dto.DirectorDto; +import ru.yandex.practicum.filmorate.mappers.DirectorMapper; +import ru.yandex.practicum.filmorate.model.Director; +import ru.yandex.practicum.filmorate.storage.director.DirectorDBStorage; + +import java.util.Collection; +import java.util.stream.Collectors; + +@Slf4j +@Service +@RequiredArgsConstructor +public class DirectorService { + + private final DirectorDBStorage directorStorage; + + public Collection getDirectors() { + log.info("Getting all directors"); + Collection directors = directorStorage.getDirectors(); + return directors.stream() + .map(DirectorMapper::mapToDirectorDto) + .collect(Collectors.toList()); + } + + public DirectorDto getDirectorById(long directorId) { + log.info("Getting director by id: {}", directorId); + Director director = directorStorage.getDirectorById(directorId); + return DirectorMapper.mapToDirectorDto(director); + } + + public DirectorDto addDirector(Director director) { + log.info("Adding new director: {}", director.getName()); + Director newDirector = directorStorage.addDirector(director); + log.info("Director {} added with id: {}", director.getName(), director.getId()); + return DirectorMapper.mapToDirectorDto(director); + } + + public DirectorDto updateDirector(Director director) { + log.info("Updating director with id: {}", director.getId()); + Director updateDirector = directorStorage.updateDirector(director); + log.info("Director {} with id: {} updated", updateDirector.getName(), updateDirector.getId()); + return DirectorMapper.mapToDirectorDto(updateDirector); + } + + public void deleteDirector(long directorId) { + log.info("Deleting director by id: {}", directorId); + directorStorage.deleteDirector(directorId); + } + +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/director/DirectorDBStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/director/DirectorDBStorage.java new file mode 100644 index 0000000..750de3a --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/director/DirectorDBStorage.java @@ -0,0 +1,43 @@ +package ru.yandex.practicum.filmorate.storage.director; + +import org.springframework.stereotype.Repository; +import ru.yandex.practicum.filmorate.dal.DirectorRepository; +import ru.yandex.practicum.filmorate.exception.NotFoundException; +import ru.yandex.practicum.filmorate.model.Director; + +import java.util.Collection; + +@Repository +public class DirectorDBStorage { + private final DirectorRepository directorRepository; + + public DirectorDBStorage(DirectorRepository directorRepository) { + this.directorRepository = directorRepository; + } + + public Director addDirector(Director director) { + return directorRepository.save(director); + } + + public Director updateDirector(Director director) { + directorRepository.findById(director.getId()) + .orElseThrow(() -> new NotFoundException("Director with id " + director.getId() + " not found")); + return directorRepository.update(director); + } + + public Collection getDirectors() { + return directorRepository.findAll(); + } + + public Director getDirectorById(long directorId) { + return directorRepository.findById(directorId) + .orElseThrow(() -> new NotFoundException("Director with id " + directorId + " not found")); + } + + public void deleteDirector(long directorId) { + directorRepository.findById(directorId) + .orElseThrow(() -> new NotFoundException("Director with id " + directorId + " not found")); + directorRepository.delete(directorId); + } +} + diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 1730186..f61b284 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1,7 @@ -logging.level.org.zalando.logbook:ERROR \ No newline at end of file +logging.level.org.zalando.logbook:ERROR + +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 index 2b54194..f41a90a 100644 --- a/src/main/resources/data.sql +++ b/src/main/resources/data.sql @@ -1,21 +1,40 @@ -INSERT INTO Users (email, login, name, birthday) -VALUES ('test@user.com', 'testlogin', 'Test User Name', '1991-11-11'); +delete +from FILMS; +delete +from LIKES; +delete +from GENRE; +delete +from RATING; +delete +from USERS; +delete +from FILM_GENRE; +delete +from FRIENDSHIP; +delete +from FILM_DIRECTORS; +delete +from DIRECTORS; -INSERT INTO Users (email, login, name, birthday) -VALUES ('test1@user.com', 'test2login', 'Another User', '1992-11-11'); +insert into Users (email, login, name, birthday) +values ('test@user.com', 'testlogin', 'Test User Name', '1991-11-11'); -INSERT INTO Rating(rating_id, rating_name) -VALUES (1, 'G'), +insert into Users (email, login, name, birthday) +values ('test1@user.com', 'test2login', 'Another User', '1992-11-11'); + +insert into Rating(rating_id, rating_name) +values (1, 'G'), (2, 'PG'), (3, 'PG-13'), (4, 'R'), (5, 'NC-17'); -INSERT INTO Genre (name) -VALUES ('Комедия'), - ('Драма'), - ('Мультфильм'), - ('Триллер'), - ('Документальный'), - ('Боевик'); \ No newline at end of file +insert into Genre (id, name) +values (1, 'Комедия'), + (2, 'Драма'), + (3, 'Мультфильм'), + (4, 'Триллер'), + (5, 'Документальный'), + (6, 'Боевик'); \ No newline at end of file diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index c290170..289841c 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -55,4 +55,21 @@ CREATE TABLE IF NOT EXISTS Film_genre PRIMARY KEY (film_id, genre_id), FOREIGN KEY (film_id) REFERENCES Films (id) ON DELETE CASCADE, FOREIGN KEY (genre_id) REFERENCES Genre (id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS directors +( + director_id BIGINT NOT NULL GENERATED BY DEFAULT AS IDENTITY, + name varchar(255) NOT NULL, + CONSTRAINT directors_pk PRIMARY KEY (director_id) +); + +CREATE TABLE IF NOT EXISTS film_directors +( + id BIGINT NOT NULL GENERATED BY DEFAULT AS IDENTITY, + film_id BIGINT NOT NULL, + director_id BIGINT NOT NULL, + CONSTRAINT film_directors_pk PRIMARY KEY (id), + FOREIGN KEY (film_id) REFERENCES Films (id) ON DELETE CASCADE, + FOREIGN KEY (director_id) REFERENCES directors (director_id) ON DELETE CASCADE ); \ No newline at end of file From d87683cb9b39f5a31154d697c2a158c5569b9190 Mon Sep 17 00:00:00 2001 From: Plastinin-Igor Date: Tue, 22 Apr 2025 22:32:52 +0300 Subject: [PATCH 03/13] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D0=BE=D0=BD?= =?UTF-8?q?=D0=B0=D0=BB=20=D0=B2=D0=BE=D0=B7=D0=B2=D1=80=D0=B0=D1=89=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F=20=D1=81=D0=BF=D0=B8=D1=81=D0=BA=D0=B0=20?= =?UTF-8?q?=D1=84=D0=B8=D0=BB=D1=8C=D0=BC=D0=BE=D0=B2=20=D1=80=D0=B5=D0=B6?= =?UTF-8?q?=D0=B8=D1=81=D1=81=D0=B5=D1=80=D0=B0=20=D0=BE=D1=82=D1=81=D0=BE?= =?UTF-8?q?=D1=80=D1=82=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=BD=D1=8B?= =?UTF-8?q?=D1=85=20=D0=BF=D0=BE=20=D0=BB=D0=B0=D0=B9=D0=BA=D0=B0=D0=BC=20?= =?UTF-8?q?=D0=B8=D0=BB=D0=B8=20=D0=B3=D0=BE=D0=B4=D1=83=20=D0=B2=D1=8B?= =?UTF-8?q?=D0=BF=D1=83=D1=81=D0=BA=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../filmorate/controller/FilmController.java | 10 +++++ .../filmorate/dal/FilmRepository.java | 45 +++++++++++++++++-- .../filmorate/service/film/FilmService.java | 9 ++++ .../filmorate/storage/film/FilmDbStorage.java | 5 +++ src/main/resources/data.sql | 1 + 5 files changed, 67 insertions(+), 3 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 79be9ce..ba179e8 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java @@ -4,6 +4,7 @@ import jakarta.validation.constraints.Positive; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Sort; import org.springframework.http.HttpStatus; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; @@ -67,4 +68,13 @@ public List getTopFilms(@RequestParam(defaultValue = "10") @Positive(me log.info("Received GET /films/popular?count={} request", count); return filmService.getTopFilms(count); } + + //TODO добавить проверку на корректность параметра sortMode + @GetMapping("/director/{directorId}") + public List findByDirector(@PathVariable long directorId, + @RequestParam(name = "sortBy") String sortMode) { + log.info("Received GET /films/director/{}?sortBy={} request", directorId, sortMode); + return filmService.findByDirector(directorId, sortMode); + } + } \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/FilmRepository.java b/src/main/java/ru/yandex/practicum/filmorate/dal/FilmRepository.java index 825184e..ccf0c05 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/FilmRepository.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/FilmRepository.java @@ -34,9 +34,9 @@ public class FilmRepository extends BaseRepository { private static final String FIND_GENRES_FOR_FILMS_QUERY = "SELECT fg.film_id, g.id as genre_id, g.name as genre_name " + - "FROM genre g " + - "JOIN film_genre fg ON g.id = fg.genre_id " + - "WHERE fg.film_id IN (:filmIds)"; + "FROM genre g " + + "JOIN film_genre fg ON g.id = fg.genre_id " + + "WHERE fg.film_id IN (:filmIds)"; private static final String FIND_DIRECTOR_FOR_FILMS_QUERY = """ SELECT fd.FILM_ID, @@ -47,6 +47,28 @@ JOIN DIRECTORS d ON (fd.DIRECTOR_ID = d.DIRECTOR_ID) WHERE fd.film_id in (:filmIds) """; + private static final String FIND_BY_DIRECTOR_SORT_BY_LIKES = """ + SELECT f.*, + r.rating_id as mpa_id, + r.rating_name as mpa_name + FROM films AS f + JOIN rating AS r ON f.rating_id = r.rating_id + JOIN FILM_DIRECTORS fd ON f.ID = fd.FILM_ID + WHERE fd.DIRECTOR_ID = ? + ORDER BY (SELECT COUNT(1) FROM LIKES l WHERE l.FILM_ID = f.ID) DESC + """; + + private static final String FIND_BY_DIRECTOR_SORT_BY_YEAR = """ + SELECT f.*, + r.rating_id as mpa_id, + r.rating_name as mpa_name + FROM films AS f + JOIN rating AS r ON f.rating_id = r.rating_id + JOIN FILM_DIRECTORS fd ON f.ID = fd.FILM_ID + WHERE fd.DIRECTOR_ID = ? + ORDER BY f.RELEASE_DATE + """; + private final JdbcTemplate jdbc; private final NamedParameterJdbcTemplate namedJdbcTemplate; private FilmRowMapper filmMapper; @@ -236,6 +258,23 @@ public void removeLike(long filmId, long userId) { jdbc.update(REMOVE_LIKE_QUERY, filmId, userId); } + public List findByDirector(long directorId, String sortMode) { + String sql = ""; + if (sortMode.equals("likes")) { + sql = FIND_BY_DIRECTOR_SORT_BY_LIKES; + } else if (sortMode.equals("year")) { + sql = FIND_BY_DIRECTOR_SORT_BY_YEAR; + } + + List films = jdbc.query(sql, filmWithRatingMapper, directorId); + + if (!films.isEmpty()) { + setGenresForFilms(films); + setDirectorForFilm(films); + } + return films; + } + private void saveDirector(Film film) { if (film.getDirector() == null || film.getDirector().isEmpty()) { return; diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/film/FilmService.java b/src/main/java/ru/yandex/practicum/filmorate/service/film/FilmService.java index 4f83d6e..97a7524 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/service/film/FilmService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/film/FilmService.java @@ -148,4 +148,13 @@ private void userExists(long userId) { private void filmExists(long filmId) { filmStorage.getFilmById(filmId); } + + public List findByDirector(long directorId, String sortMode) { + log.info("Getting films by director with id: {} and sort mode {}", directorId, sortMode); + Collection films = filmStorage.findByDirector(directorId, sortMode); + return films.stream() + .map(film -> FilmMapper.mapToFilmDto(film, filmStorage.getLikes(film.getId()))) + .collect(Collectors.toList()); + } + } \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/film/FilmDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/film/FilmDbStorage.java index 5cb7f56..8c78cae 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/storage/film/FilmDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/film/FilmDbStorage.java @@ -109,4 +109,9 @@ private void validateGenresExist(List genres) { } } } + + public List findByDirector(long directorId, String sortMode) { + return filmRepository.findByDirector(directorId, sortMode); + } + } \ No newline at end of file diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql index f41a90a..b177ec6 100644 --- a/src/main/resources/data.sql +++ b/src/main/resources/data.sql @@ -23,6 +23,7 @@ values ('test@user.com', 'testlogin', 'Test User Name', '1991-11-11'); insert into Users (email, login, name, birthday) values ('test1@user.com', 'test2login', 'Another User', '1992-11-11'); + insert into Rating(rating_id, rating_name) values (1, 'G'), (2, 'PG'), From 5e79791a7ee07146aa570d04cc2991374ea1c91a Mon Sep 17 00:00:00 2001 From: Plastinin-Igor Date: Tue, 22 Apr 2025 22:39:03 +0300 Subject: [PATCH 04/13] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D1=82=D1=8C=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA=D0=B8=20Che?= =?UTF-8?q?ckstyle?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ru/yandex/practicum/filmorate/controller/FilmController.java | 1 - 1 file changed, 1 deletion(-) 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 ba179e8..1f05ce4 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java @@ -4,7 +4,6 @@ import jakarta.validation.constraints.Positive; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.data.domain.Sort; import org.springframework.http.HttpStatus; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; From 32d32a211c38f78874f24a38fceb78d027713f61 Mon Sep 17 00:00:00 2001 From: Plastinin-Igor Date: Tue, 22 Apr 2025 23:10:09 +0300 Subject: [PATCH 05/13] =?UTF-8?q?=D0=9E=D1=82=D0=BB=D0=B0=D0=B4=D0=BA?= =?UTF-8?q?=D0=B0=20=D1=82=D0=B5=D1=81=D1=82=D0=BE=D0=B2=20Postman:=20?= =?UTF-8?q?=D0=BF=D0=B5=D1=80=D0=B5=D0=B8=D0=BC=D0=B5=D0=BD=D0=BE=D0=B2?= =?UTF-8?q?=D0=B0=D1=82=D1=8C=20=D0=BF=D0=BE=D0=BB=D0=B5=20director=20?= =?UTF-8?q?=D0=B2=20directors;=20=D0=B2=D0=BD=D0=B5=D1=81=D1=82=D0=B8=20?= =?UTF-8?q?=D0=BF=D1=80=D0=B0=D0=B2=D0=BA=D0=B8=20=D0=B2=20=D1=84=D1=83?= =?UTF-8?q?=D0=BD=D0=BA=D1=86=D0=B8=D0=BE=D0=BD=D0=B0=D0=BB=20=D0=B8=D1=81?= =?UTF-8?q?=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F=20=D1=84?= =?UTF-8?q?=D0=B8=D0=BB=D1=8C=D0=BC=D0=B0=20=D0=B2=20=D1=87=D0=B0=D1=81?= =?UTF-8?q?=D1=82=D0=B8=20=D0=B8=D0=BD=D1=84=D0=BE=D1=80=D0=BC=D0=B0=D1=86?= =?UTF-8?q?=D0=B8=D0=B8=20=D0=BE=20=D1=80=D0=B5=D0=B6=D0=B8=D1=81=D1=81?= =?UTF-8?q?=D0=B5=D1=80=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../practicum/filmorate/dal/FilmRepository.java | 14 +++++++++++--- .../filmorate/dal/mapper/FilmRowMapper.java | 2 +- .../practicum/filmorate/dto/NewFilmRequest.java | 2 +- .../practicum/filmorate/dto/UpdateFilmRequest.java | 2 +- .../practicum/filmorate/mappers/FilmMapper.java | 10 +++++----- .../ru/yandex/practicum/filmorate/model/Film.java | 2 +- 6 files changed, 20 insertions(+), 12 deletions(-) diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/FilmRepository.java b/src/main/java/ru/yandex/practicum/filmorate/dal/FilmRepository.java index ccf0c05..1e45d59 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/FilmRepository.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/FilmRepository.java @@ -220,6 +220,9 @@ public Film update(Film film) { deleteGenres(film.getId()); saveGenres(film); + deleteFilms(film.getId()); + saveDirector(film); + return findById(film.getId()).orElseThrow(() -> new IllegalStateException("Updated film not found, id: " + film.getId())); } @@ -276,12 +279,12 @@ public List findByDirector(long directorId, String sortMode) { } private void saveDirector(Film film) { - if (film.getDirector() == null || film.getDirector().isEmpty()) { + if (film.getDirectors() == null || film.getDirectors().isEmpty()) { return; } String sql = "INSERT INTO FILM_DIRECTORS (FILM_ID, DIRECTOR_ID) VALUES(?, ?)"; - List batchArgs = film.getDirector().stream() + List batchArgs = film.getDirectors().stream() .filter(Objects::nonNull) .filter(director -> director.getId() > 0) .distinct() @@ -319,8 +322,13 @@ private void setDirectorForFilm(List films) { )); films.forEach(film -> - film.setDirector(directorsByFilmId.getOrDefault(film.getId(), Collections.emptyList())) + film.setDirectors(directorsByFilmId.getOrDefault(film.getId(), Collections.emptyList())) ); } + private void deleteFilms(long filmId) { + String sql = "DELETE FROM FILM_DIRECTORS WHERE film_id = ?"; + jdbc.update(sql, filmId); + } + } \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/mapper/FilmRowMapper.java b/src/main/java/ru/yandex/practicum/filmorate/dal/mapper/FilmRowMapper.java index b79b139..4c442a1 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/mapper/FilmRowMapper.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/mapper/FilmRowMapper.java @@ -27,7 +27,7 @@ public Film mapRow(ResultSet rs, int rowNum) throws SQLException { film.setMpa(mpa); film.setGenres(new ArrayList<>()); - film.setDirector(new ArrayList<>()); + film.setDirectors(new ArrayList<>()); return film; } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dto/NewFilmRequest.java b/src/main/java/ru/yandex/practicum/filmorate/dto/NewFilmRequest.java index a60d2a2..1280050 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dto/NewFilmRequest.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dto/NewFilmRequest.java @@ -11,5 +11,5 @@ public class NewFilmRequest { private String releaseDate; private String duration; private Rating ratingId; - private Director director; + private Director directors; } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dto/UpdateFilmRequest.java b/src/main/java/ru/yandex/practicum/filmorate/dto/UpdateFilmRequest.java index 2a097d0..44f24c0 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dto/UpdateFilmRequest.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dto/UpdateFilmRequest.java @@ -11,7 +11,7 @@ public class UpdateFilmRequest { private String releaseDate; private String duration; private Rating ratingId; - private Director director; + private Director directors; public boolean hasName() { return !(name == null || name.isBlank()); diff --git a/src/main/java/ru/yandex/practicum/filmorate/mappers/FilmMapper.java b/src/main/java/ru/yandex/practicum/filmorate/mappers/FilmMapper.java index 7229aef..e52c360 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/mappers/FilmMapper.java +++ b/src/main/java/ru/yandex/practicum/filmorate/mappers/FilmMapper.java @@ -34,14 +34,14 @@ public static Film mapToFilm(Film requestFilm) { film.setGenres(new ArrayList<>()); } - if (requestFilm.getDirector() != null) { - List directorsWithIdOnly = requestFilm.getDirector() + if (requestFilm.getDirectors() != null) { + List directorsWithIdOnly = requestFilm.getDirectors() .stream() .filter(d -> d != null && d.getId() != 0) .collect(Collectors.toList()); - film.setDirector(directorsWithIdOnly); + film.setDirectors(directorsWithIdOnly); } else { - film.setDirector(new ArrayList<>()); + film.setDirectors(new ArrayList<>()); } return film; @@ -59,7 +59,7 @@ public static FilmDto mapToFilmDto(Film film, Set likes) { dto.setMpa(RatingMapper.mapToRatingDto(film.getMpa())); dto.setGenres(GenreMapper.mapToGenreDtoList(film.getGenres())); dto.setLikes(Optional.ofNullable(likes).orElse(Collections.emptySet())); - dto.setDirectors(DirectorMapper.mapToDirectorDtoList(film.getDirector())); + dto.setDirectors(DirectorMapper.mapToDirectorDtoList(film.getDirectors())); return dto; } 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 f693771..ca8c868 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/model/Film.java +++ b/src/main/java/ru/yandex/practicum/filmorate/model/Film.java @@ -23,5 +23,5 @@ public class Film { @NotNull(message = "Rating cannot be null") private Rating mpa; private List genres = new ArrayList<>(); - private List director = new ArrayList<>(); + private List directors = new ArrayList<>(); } From d280de1e6c88703464f9206e18578d9f7b294534 Mon Sep 17 00:00:00 2001 From: Plastinin-Igor Date: Wed, 23 Apr 2025 10:12:33 +0300 Subject: [PATCH 06/13] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B5=D0=B8=D0=BC?= =?UTF-8?q?=D0=B5=D0=BD=D0=BE=D0=B2=D0=B0=D1=82=D1=8C=20=D0=BC=D0=B5=D1=82?= =?UTF-8?q?=D0=BE=D0=B4=20deleteFilms=20=D0=B2=20deleteFilmDirectors=20?= =?UTF-8?q?=D0=B2=20=D0=BA=D0=BB=D0=B0=D1=81=D1=81=D0=B5=20FilmRepository.?= =?UTF-8?q?=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D1=82=D1=8C=20=D0=BF?= =?UTF-8?q?=D1=80=D0=BE=D0=B2=D0=B5=D1=80=D0=BA=D1=83=20=D0=BD=D0=B0=20?= =?UTF-8?q?=D0=BA=D0=BE=D1=80=D1=80=D0=B5=D0=BA=D1=82=D0=BD=D0=BE=D1=81?= =?UTF-8?q?=D1=82=D1=8C=20=D0=B2=D0=B2=D0=B5=D0=B4=D1=91=D0=BD=D0=BD=D1=8B?= =?UTF-8?q?=D1=85=20=D0=BF=D0=B0=D1=80=D0=B0=D0=BC=D0=B5=D1=82=D1=80=D0=BE?= =?UTF-8?q?=D0=B2=20=D0=B2=20=D0=BA=D0=BE=D0=BD=D1=82=D1=80=D0=BE=D0=BB?= =?UTF-8?q?=D0=BB=D0=B5=D1=80=D0=B5=20FilmController=20=D0=B2=20=D0=BC?= =?UTF-8?q?=D0=B5=D1=82=D0=BE=D0=B4=D0=B5=20findByDirector?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../filmorate/exception/ParameterNotValidException.java | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 src/main/java/ru/yandex/practicum/filmorate/exception/ParameterNotValidException.java diff --git a/src/main/java/ru/yandex/practicum/filmorate/exception/ParameterNotValidException.java b/src/main/java/ru/yandex/practicum/filmorate/exception/ParameterNotValidException.java new file mode 100644 index 0000000..19750f4 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/exception/ParameterNotValidException.java @@ -0,0 +1,4 @@ +package ru.yandex.practicum.filmorate.exception; + +public class ParameterNotValidException { +} From e07b41fd3f437a407a2f7ffd3aa92a4144ed3ec3 Mon Sep 17 00:00:00 2001 From: b1mgd Date: Thu, 24 Apr 2025 20:03:34 +0300 Subject: [PATCH 07/13] Add remove endpoint for films and users (#14) --- .gitignore | 3 +++ .../filmorate/controller/ErrorHandler.java | 7 +++++++ .../filmorate/controller/FilmController.java | 7 +++++++ .../filmorate/controller/UserController.java | 7 +++++++ .../practicum/filmorate/dal/FilmRepository.java | 5 +++++ .../practicum/filmorate/dal/UserRepository.java | 5 +++++ .../exception/InternalServerException.java | 7 +++++++ .../filmorate/service/film/FilmService.java | 15 +++++++++++++++ .../filmorate/service/user/UserService.java | 16 ++++++++++++++++ .../filmorate/storage/film/FilmDbStorage.java | 5 +++++ .../filmorate/storage/film/FilmStorage.java | 2 ++ .../filmorate/storage/user/UserDbStorage.java | 5 +++++ .../filmorate/storage/user/UserStorage.java | 2 ++ 13 files changed, 86 insertions(+) create mode 100644 src/main/java/ru/yandex/practicum/filmorate/exception/InternalServerException.java diff --git a/.gitignore b/.gitignore index d7268a3..97fa19e 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,9 @@ target/ !**/src/main/**/target/ !**/src/test/**/target/ +### macOS ### +.DS_Store + ### STS ### .apt_generated .classpath diff --git a/src/main/java/ru/yandex/practicum/filmorate/controller/ErrorHandler.java b/src/main/java/ru/yandex/practicum/filmorate/controller/ErrorHandler.java index 86a12a3..95cfe67 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/ErrorHandler.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/ErrorHandler.java @@ -38,4 +38,11 @@ public ErrorResponse handleConstraintViolation(final ConstraintViolationExceptio log.warn("constraint violation"); return new ErrorResponse(e.getMessage()); } + + @ExceptionHandler(InternalServerException.class) + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public ErrorResponse handleException(Exception e) { + log.error("Ошибка сервера: {}", e.getMessage()); + return new ErrorResponse("Внутренняя ошибка сервера: " + e.getMessage()); + } } 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 79be9ce..1ab750a 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java @@ -67,4 +67,11 @@ public List getTopFilms(@RequestParam(defaultValue = "10") @Positive(me log.info("Received GET /films/popular?count={} request", count); return filmService.getTopFilms(count); } + + @DeleteMapping("/{filmId}") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void deleteFilm(@PathVariable long filmId) { + log.info("Received DELETE /films/{} request", filmId); + filmService.deleteFilm(filmId); + } } \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java b/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java index 1c16497..d5c7121 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java @@ -72,5 +72,12 @@ public Collection getFriends(@PathVariable int id) { log.info("getFriends"); return userService.getFriends(id); } + + @DeleteMapping("/{userId}") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void deleteUser(@PathVariable int userId) { + log.info("Recieved DELETE /users/{} request", userId); + userService.deleteUser(userId); + } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/FilmRepository.java b/src/main/java/ru/yandex/practicum/filmorate/dal/FilmRepository.java index 4adda28..fc8612d 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/FilmRepository.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/FilmRepository.java @@ -36,6 +36,7 @@ public class FilmRepository extends BaseRepository { "FROM genre g " + "JOIN film_genre fg ON g.id = fg.genre_id " + "WHERE fg.film_id IN (:filmIds)"; + private static final String DELETE_FILM_QUERY = "DELETE FROM Films WHERE id = ?"; private final JdbcTemplate jdbc; private final NamedParameterJdbcTemplate namedJdbcTemplate; @@ -172,6 +173,10 @@ public Film update(Film film) { return findById(film.getId()).orElseThrow(() -> new IllegalStateException("Updated film not found, id: " + film.getId())); } + public boolean deleteFilm(long id) { + return delete(DELETE_FILM_QUERY, id); + } + private void deleteGenres(long filmId) { String sql = "DELETE FROM film_genre WHERE film_id = ?"; jdbc.update(sql, filmId); diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/UserRepository.java b/src/main/java/ru/yandex/practicum/filmorate/dal/UserRepository.java index e043ce3..317b7bc 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/UserRepository.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/UserRepository.java @@ -15,6 +15,7 @@ public class UserRepository extends BaseRepository { private static final String FIND_BY_LOGIN_QUERY = "SELECT * FROM users WHERE login = ?"; private static final String INSERT_QUERY = "INSERT INTO users(email, login, name, birthday) VALUES (?, ?, ?, ?)"; private static final String UPDATE_QUERY = "UPDATE users SET email = ?, login = ?, name = ?, birthday = ? WHERE id = ?"; + private static final String DELETE_USER_QUERY = "DELETE FROM users WHERE id = ?"; public UserRepository(JdbcTemplate jdbc, UserRowMapper mapper) { super(jdbc, mapper); @@ -60,6 +61,10 @@ public User update(User user) { return user; } + public boolean deleteUser(long id) { + return delete(DELETE_USER_QUERY, id); + } + public Set getFriends(long userId) { String sql = "SELECT friend_id FROM friendship WHERE user_id = ?"; 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..c9eba3a --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/exception/InternalServerException.java @@ -0,0 +1,7 @@ +package ru.yandex.practicum.filmorate.exception; + +public class InternalServerException extends RuntimeException { + public InternalServerException(String message) { + super(message); + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/film/FilmService.java b/src/main/java/ru/yandex/practicum/filmorate/service/film/FilmService.java index 4f83d6e..cf96bb6 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/service/film/FilmService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/film/FilmService.java @@ -9,6 +9,7 @@ import ru.yandex.practicum.filmorate.dto.GenreDto; import ru.yandex.practicum.filmorate.dto.RatingDto; import ru.yandex.practicum.filmorate.exception.ConstraintViolationException; +import ru.yandex.practicum.filmorate.exception.InternalServerException; import ru.yandex.practicum.filmorate.exception.NotFoundException; import ru.yandex.practicum.filmorate.mappers.FilmMapper; import ru.yandex.practicum.filmorate.mappers.GenreMapper; @@ -134,6 +135,20 @@ public RatingDto getRatingById(int id) { return RatingMapper.mapToRatingDto(rating); } + @Transactional + public void deleteFilm(long filmId) { + FilmDto filmDto = getFilmById(filmId); + log.info("Deleting film: {}", filmDto); + + boolean isDeleted = filmStorage.deleteFilm(filmId); + + if (isDeleted) { + log.info("Film deleted successfully"); + } else { + throw new InternalServerException("Film was not deleted due to internal error."); + } + } + private void validateFilm(Film film) { if (film.getReleaseDate() != null && film.getReleaseDate().isBefore(MIN_RELEASE_DATE)) { log.warn("Validation failed"); diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/user/UserService.java b/src/main/java/ru/yandex/practicum/filmorate/service/user/UserService.java index c8f052f..8f641bd 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/service/user/UserService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/user/UserService.java @@ -4,10 +4,12 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import ru.yandex.practicum.filmorate.dto.UserDto; import ru.yandex.practicum.filmorate.exception.EmptyFieldException; +import ru.yandex.practicum.filmorate.exception.InternalServerException; import ru.yandex.practicum.filmorate.exception.NotFoundException; import ru.yandex.practicum.filmorate.mappers.UserMapper; import ru.yandex.practicum.filmorate.model.User; @@ -128,6 +130,20 @@ public UserDto getUserById(@PathVariable long id) { return UserMapper.mapToUserDto(userDbStorage.getUserById(id)); } + @Transactional + public void deleteUser(long userId) { + UserDto userDto = getUserById(userId); + log.info("Deleting user: {}", userDto); + + boolean isDeleted = userDbStorage.deleteUser(userId); + + if (isDeleted) { + log.info("User deleted successfully"); + } else { + throw new InternalServerException("User was not deleted due to internal server error"); + } + } + private boolean isExistsUser(User user) { return userDbStorage.getUsers().stream() .noneMatch(userCheck -> userCheck.getId() == user.getId()); diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/film/FilmDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/film/FilmDbStorage.java index 5cb7f56..8c93be5 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/storage/film/FilmDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/film/FilmDbStorage.java @@ -61,6 +61,11 @@ public Film getFilmById(long filmId) { .orElseThrow(() -> new NotFoundException("Film with id " + filmId + " not found")); } + @Override + public boolean deleteFilm(long filmId) { + return filmRepository.deleteFilm(filmId); + } + public Set getLikes(long filmId) { getFilmById(filmId); return filmRepository.getLikes(filmId); diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/film/FilmStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/film/FilmStorage.java index 4d2f3d5..2d9ad94 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/storage/film/FilmStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/film/FilmStorage.java @@ -14,4 +14,6 @@ public interface FilmStorage { Film updateFilm(@RequestBody Film film); Film getFilmById(long filmId); + + boolean deleteFilm(long filmId); } diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/user/UserDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/user/UserDbStorage.java index 3b9222c..04242c0 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/storage/user/UserDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/user/UserDbStorage.java @@ -38,6 +38,11 @@ public User getUserById(long userId) { return repository.findById(userId).orElseThrow(() -> new NotFoundException("User with id " + userId + " not found")); } + @Override + public boolean deleteUser(long userId) { + return repository.deleteUser(userId); + } + public Set getFriends(long userId) { return repository.getFriends(userId); } diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/user/UserStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/user/UserStorage.java index eaf2924..3c85c73 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/storage/user/UserStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/user/UserStorage.java @@ -14,4 +14,6 @@ public interface UserStorage { User updateUser(@RequestBody User user); User getUserById(long userId); + + boolean deleteUser(long userId); } From bab42a54014cb06e0950abe51c3f62be97a0fed2 Mon Sep 17 00:00:00 2001 From: Stanislav Mun <146199590+Nesailormun@users.noreply.github.com> Date: Thu, 24 Apr 2025 20:04:06 +0300 Subject: [PATCH 08/13] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B0=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D0=BE?= =?UTF-8?q?=D0=BD=D0=B0=D0=BB=D1=8C=D0=BD=D0=BE=D1=81=D1=82=D1=8C=20=C2=AB?= =?UTF-8?q?=D0=9F=D0=BE=D0=BF=D1=83=D0=BB=D1=8F=D1=80=D0=BD=D1=8B=D0=B5=20?= =?UTF-8?q?=D1=84=D0=B8=D0=BB=D1=8C=D0=BC=D1=8B=C2=BB=20(#13)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Обновлен метод getTopFilms в цепочке FilmController->FilmService->FilmDbStorage->FilmRepository согласно новой функциональности (фильтрации по genreId и year) * Обновлен метод getTopFilms в цепочке FilmController->FilmService->FilmDbStorage->FilmRepository согласно новой функциональности (фильтрации по genreId и year) * делаю коммит псÐоскольку force push не триггерит пайплайн по всей видимости * Исправил логику работы метода getTopFilms(...) (параметры строки запроса genreId and year являются необязательными) --------- Co-authored-by: nesailormun --- pom.xml | 2 ++ .../filmorate/controller/FilmController.java | 8 +++-- .../filmorate/dal/FilmRepository.java | 32 +++++++++++++++++-- .../filmorate/service/film/FilmService.java | 17 ++++------ .../filmorate/storage/film/FilmDbStorage.java | 4 +++ src/main/resources/application.properties | 8 ++++- 6 files changed, 54 insertions(+), 17 deletions(-) diff --git a/pom.xml b/pom.xml index 0c428fa..4f7f5f6 100644 --- a/pom.xml +++ b/pom.xml @@ -43,6 +43,7 @@ org.projectlombok lombok + 1.18.30 true @@ -81,6 +82,7 @@ org.projectlombok lombok + 1.18.30 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 1ab750a..3961387 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java @@ -63,9 +63,11 @@ public void deleteLikeToFilm(@PathVariable long filmId, @PathVariable long userI } @GetMapping("/popular") - public List getTopFilms(@RequestParam(defaultValue = "10") @Positive(message = "Count must be positive") int count) { - log.info("Received GET /films/popular?count={} request", count); - return filmService.getTopFilms(count); + public List getTopFilms(@RequestParam(defaultValue = "10") @Positive(message = "Count must be positive") int count, + @RequestParam(defaultValue = "-1") int genreId, + @RequestParam(defaultValue = "-1") int year) { + log.info("Received GET /films/popular?count={}&genreId={}&year={} request", count, genreId, year); + return filmService.getTopFilms(count, genreId, year); } @DeleteMapping("/{filmId}") diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/FilmRepository.java b/src/main/java/ru/yandex/practicum/filmorate/dal/FilmRepository.java index fc8612d..1984622 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/FilmRepository.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/FilmRepository.java @@ -94,7 +94,7 @@ public Optional findById(long id) { if (films.isEmpty()) { return Optional.empty(); } else { - Film film = films.get(0); + Film film = films.getFirst(); setGenresForFilms(List.of(film)); return Optional.of(film); } @@ -211,4 +211,32 @@ public void addLike(long filmId, long userId) { public void removeLike(long filmId, long userId) { jdbc.update(REMOVE_LIKE_QUERY, filmId, userId); } -} \ No newline at end of file + + public List getTopFilms(int count, Integer genreId, Integer year) { + + StringBuilder queryBuilder = new StringBuilder("SELECT f.*, r.rating_id AS mpa_id, r.rating_name AS mpa_name " + + "FROM films AS f JOIN rating AS r on f.rating_id = r.rating_id " + + "JOIN film_genre AS fg ON f.id = fg.film_id " + + "JOIN likes AS l ON f.id = l.film_id WHERE 1=1"); + + List filterParams = new ArrayList<>(); + + if (genreId != -1) { + queryBuilder.append(" AND fg.genre_id = ?"); + filterParams.add(genreId); + } + + if (year != -1) { + queryBuilder.append(" AND EXTRACT(YEAR FROM f.release_date) = ?"); + filterParams.add(year); + } + + queryBuilder.append(" GROUP BY f.id, r.rating_id, r.rating_name ORDER BY COUNT(l.user_id) DESC LIMIT ?;"); + filterParams.add(count); + + List films = jdbc.query(queryBuilder.toString(), filmWithRatingMapper, filterParams.toArray()); + setGenresForFilms(films); + return films; + } + +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/film/FilmService.java b/src/main/java/ru/yandex/practicum/filmorate/service/film/FilmService.java index cf96bb6..8ce7663 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/service/film/FilmService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/film/FilmService.java @@ -23,7 +23,6 @@ import java.time.LocalDate; import java.util.Collection; import java.util.List; -import java.util.Map; import java.util.Set; import java.util.stream.Collectors; @@ -90,23 +89,19 @@ public void deleteLikeToFilm(long filmId, long userId) { log.info("Like removed successfully"); } - public List getTopFilms(int count) { - log.info("Getting top {} popular films", count); + public List getTopFilms(int count, int genreId, int year) { + log.info("Getting top {} popular films with genreId = {} and release year = {}", count, genreId, year); if (count <= 0) { throw new ValidationException("Count parameter must be positive."); } - Collection allFilms = filmStorage.getFilms(); - return allFilms.stream() + return filmStorage.getTopFilms(count, genreId, year) + .stream() .map(film -> { Set likes = filmStorage.getLikes(film.getId()); - return Map.entry(film, likes.size()); + return FilmMapper.mapToFilmDto(film, likes); }) - .sorted(Map.Entry.comparingByValue().reversed()) - .limit(count) - .map(Map.Entry::getKey) - .map(film -> FilmMapper.mapToFilmDto(film, filmStorage.getLikes(film.getId()))) - .collect(Collectors.toList()); + .toList(); } public Collection getGenres() { diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/film/FilmDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/film/FilmDbStorage.java index 8c93be5..d67ae29 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/storage/film/FilmDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/film/FilmDbStorage.java @@ -97,6 +97,10 @@ public Rating getRatingById(int id) { .orElseThrow(() -> new NotFoundException("Rating with id " + id + " not found")); } + public List getTopFilms(int count, int genreId, int year) { + return filmRepository.getTopFilms(count, genreId, year); + } + private void validateRatingExists(Rating mpa) { if (mpa == null || mpa.getId() == 0) { throw new IllegalArgumentException("Film Rating (MPA) is required."); diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 1730186..73fd17b 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1,7 @@ -logging.level.org.zalando.logbook:ERROR \ No newline at end of file +logging.level.org.zalando.logbook=TRACE +logging.level.ru.yandex.practicum.filmorate=INFO +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 From 7930dd3b14b0c829d1a54ff75e652aed2b7e55c0 Mon Sep 17 00:00:00 2001 From: 1Boolean1 <124202814+1Boolean1@users.noreply.github.com> Date: Thu, 24 Apr 2025 20:25:38 +0300 Subject: [PATCH 09/13] add getCommonFilms for user and friend (#10) * add getCommonFilms for user and friend * little change * checkstyle --- .../filmorate/controller/FilmController.java | 8 ++++ .../filmorate/dal/FilmRepository.java | 19 ++++++++++ .../practicum/filmorate/dto/UserDto.java | 1 + .../filmorate/service/film/FilmService.java | 15 ++++++++ .../filmorate/storage/film/FilmDbStorage.java | 5 +++ src/main/resources/data.sql | 37 +++++++++++++++---- 6 files changed, 78 insertions(+), 7 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 3961387..8baed43 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java @@ -76,4 +76,12 @@ public void deleteFilm(@PathVariable long filmId) { log.info("Received DELETE /films/{} request", filmId); filmService.deleteFilm(filmId); } + + @GetMapping("/common") + public List getCommonFilms( + @RequestParam long userId, + @RequestParam long friendId) { + log.info("GET common films for userId={} and friendId={}", userId, friendId); + return filmService.getCommonFilms(userId, friendId); + } } \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/FilmRepository.java b/src/main/java/ru/yandex/practicum/filmorate/dal/FilmRepository.java index 1984622..042be2e 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/FilmRepository.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/FilmRepository.java @@ -30,6 +30,17 @@ public class FilmRepository extends BaseRepository { private static final String GET_LIKES_QUERY = "SELECT user_id FROM Likes WHERE film_id = ?"; private static final String ADD_LIKE_QUERY = "INSERT INTO Likes (user_id, film_id) VALUES (?, ?)"; private static final String REMOVE_LIKE_QUERY = "DELETE FROM Likes WHERE film_id = ? AND user_id = ?"; + private static final String GET_COMMON_FILMS = "SELECT f.*, r.rating_id as mpa_id, r.rating_name as mpa_name " + + "FROM Films f " + + "JOIN rating AS r ON f.rating_id = r.rating_id " + + "JOIN Likes l ON f.id = l.film_id " + + "WHERE l.film_id IN ( " + + " SELECT film_id FROM Likes WHERE user_id = ? " + + " INTERSECT " + + " SELECT film_id FROM Likes WHERE user_id = ? " + + ") " + + "GROUP BY f.id " + + "ORDER BY COUNT(l.user_id) DESC"; private static final String FIND_GENRES_FOR_FILMS_QUERY = "SELECT fg.film_id, g.id as genre_id, g.name as genre_name " + @@ -88,6 +99,14 @@ public List findAll() { return films; } + public List findCommon(long userId, long filmId) { + List films = jdbc.query(GET_COMMON_FILMS, filmWithRatingMapper, userId, filmId); + if (!films.isEmpty()) { + setGenresForFilms(films); + } + return films; + } + public Optional findById(long id) { List films = jdbc.query(FIND_BY_ID_QUERY, filmWithRatingMapper, id); diff --git a/src/main/java/ru/yandex/practicum/filmorate/dto/UserDto.java b/src/main/java/ru/yandex/practicum/filmorate/dto/UserDto.java index d59f55f..53ff743 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dto/UserDto.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dto/UserDto.java @@ -1,4 +1,5 @@ package ru.yandex.practicum.filmorate.dto; + import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/film/FilmService.java b/src/main/java/ru/yandex/practicum/filmorate/service/film/FilmService.java index 8ce7663..020dfb3 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/service/film/FilmService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/film/FilmService.java @@ -130,6 +130,20 @@ public RatingDto getRatingById(int id) { return RatingMapper.mapToRatingDto(rating); } + + public List getCommonFilms(long userId, long friendId) { + userExists(userId); + userExists(friendId); + if (userId == friendId) { + throw new ConstraintViolationException("Id's can't be the same"); + } + log.info("Getting MPA common films for users: {}, {}", userId, friendId); + Collection films = filmStorage.getCommonFilms(userId, friendId); + return films.stream() + .map(film -> FilmMapper.mapToFilmDto(film, filmStorage.getLikes(film.getId()))) + .collect(Collectors.toList()); + } + @Transactional public void deleteFilm(long filmId) { FilmDto filmDto = getFilmById(filmId); @@ -142,6 +156,7 @@ public void deleteFilm(long filmId) { } else { throw new InternalServerException("Film was not deleted due to internal error."); } + } private void validateFilm(Film film) { diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/film/FilmDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/film/FilmDbStorage.java index d67ae29..7d18f16 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/storage/film/FilmDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/film/FilmDbStorage.java @@ -97,6 +97,11 @@ public Rating getRatingById(int id) { .orElseThrow(() -> new NotFoundException("Rating with id " + id + " not found")); } + + public Collection getCommonFilms(long userId, long friendId) { + return filmRepository.findCommon(userId, friendId); + } + public List getTopFilms(int count, int genreId, int year) { return filmRepository.getTopFilms(count, genreId, year); } diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql index 2b54194..c7bdbc0 100644 --- a/src/main/resources/data.sql +++ b/src/main/resources/data.sql @@ -1,18 +1,41 @@ -INSERT INTO Users (email, login, name, birthday) -VALUES ('test@user.com', 'testlogin', 'Test User Name', '1991-11-11'); - -INSERT INTO Users (email, login, name, birthday) -VALUES ('test1@user.com', 'test2login', 'Another User', '1992-11-11'); +DELETE +FROM FILMS; +DELETE +FROM LIKES; +DELETE +FROM GENRE; +DELETE +FROM RATING; +DELETE +FROM USERS; +DELETE +FROM FILM_GENRE; +DELETE +FROM FRIENDSHIP; -INSERT INTO Rating(rating_id, rating_name) +INSERT INTO RATING(rating_id, rating_name) VALUES (1, 'G'), (2, 'PG'), (3, 'PG-13'), (4, 'R'), (5, 'NC-17'); +INSERT INTO USERS (email, login, name, birthday) +VALUES ('test@user.com', 'testlogin', 'Test User Name', '1991-11-11'); + +INSERT INTO USERS ( email, login, name, birthday) +VALUES ('test1@user.com', 'test2login', 'Another User', '1992-11-11'); + +INSERT INTO FILMS (NAME, DESCRIPTION, RELEASE_DATE, DURATION, RATING_ID) +VALUES ('film1', 'its film1', '2010-10-10', '100', '1'), + ('film2', 'its film2', '2020-12-20', '200', '2'); + +INSERT INTO LIKES(user_id, film_id) +VALUES (1, 1), + (2, 1), + (2, 2); -INSERT INTO Genre (name) +INSERT INTO GENRE (name) VALUES ('Комедия'), ('Драма'), ('Мультфильм'), From dc711ce92ca9a3f1041100f70b21b35e74db6e1b Mon Sep 17 00:00:00 2001 From: b1mgd Date: Thu, 24 Apr 2025 20:03:34 +0300 Subject: [PATCH 10/13] Add remove endpoint for films and users (#14) --- .gitignore | 3 +++ .../filmorate/controller/ErrorHandler.java | 7 +++++++ .../filmorate/controller/FilmController.java | 7 +++++++ .../filmorate/controller/UserController.java | 7 +++++++ .../practicum/filmorate/dal/FilmRepository.java | 6 +++++- .../practicum/filmorate/dal/UserRepository.java | 5 +++++ .../exception/InternalServerException.java | 7 +++++++ .../filmorate/service/film/FilmService.java | 15 +++++++++++++++ .../filmorate/service/user/UserService.java | 16 ++++++++++++++++ .../filmorate/storage/film/FilmDbStorage.java | 5 +++++ .../filmorate/storage/film/FilmStorage.java | 2 ++ .../filmorate/storage/user/UserDbStorage.java | 5 +++++ .../filmorate/storage/user/UserStorage.java | 2 ++ 13 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 src/main/java/ru/yandex/practicum/filmorate/exception/InternalServerException.java diff --git a/.gitignore b/.gitignore index d7268a3..97fa19e 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,9 @@ target/ !**/src/main/**/target/ !**/src/test/**/target/ +### macOS ### +.DS_Store + ### STS ### .apt_generated .classpath diff --git a/src/main/java/ru/yandex/practicum/filmorate/controller/ErrorHandler.java b/src/main/java/ru/yandex/practicum/filmorate/controller/ErrorHandler.java index 86a12a3..95cfe67 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/ErrorHandler.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/ErrorHandler.java @@ -38,4 +38,11 @@ public ErrorResponse handleConstraintViolation(final ConstraintViolationExceptio log.warn("constraint violation"); return new ErrorResponse(e.getMessage()); } + + @ExceptionHandler(InternalServerException.class) + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public ErrorResponse handleException(Exception e) { + log.error("Ошибка сервера: {}", e.getMessage()); + return new ErrorResponse("Внутренняя ошибка сервера: " + e.getMessage()); + } } 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 79be9ce..1ab750a 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java @@ -67,4 +67,11 @@ public List getTopFilms(@RequestParam(defaultValue = "10") @Positive(me log.info("Received GET /films/popular?count={} request", count); return filmService.getTopFilms(count); } + + @DeleteMapping("/{filmId}") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void deleteFilm(@PathVariable long filmId) { + log.info("Received DELETE /films/{} request", filmId); + filmService.deleteFilm(filmId); + } } \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java b/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java index 1af4c04..9e5f8e5 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java @@ -77,6 +77,13 @@ public Collection getFriends(@PathVariable int id) { return userService.getFriends(id); } + @DeleteMapping("/{userId}") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void deleteUser(@PathVariable int userId) { + log.info("Recieved DELETE /users/{} request", userId); + userService.deleteUser(userId); + } + @GetMapping("/{id}/recommendations") public Collection getRecommendations(@PathVariable long id) { log.info("get recommendations"); diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/FilmRepository.java b/src/main/java/ru/yandex/practicum/filmorate/dal/FilmRepository.java index 87bc528..0874d1d 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/FilmRepository.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/FilmRepository.java @@ -40,6 +40,7 @@ public class FilmRepository extends BaseRepository { "FROM genre g " + "JOIN film_genre fg ON g.id = fg.genre_id " + "WHERE fg.film_id IN (:filmIds)"; + private static final String DELETE_FILM_QUERY = "DELETE FROM Films WHERE id = ?"; private final JdbcTemplate jdbc; private final NamedParameterJdbcTemplate namedJdbcTemplate; @@ -176,6 +177,10 @@ public Film update(Film film) { return findById(film.getId()).orElseThrow(() -> new IllegalStateException("Updated film not found, id: " + film.getId())); } + public boolean deleteFilm(long id) { + return delete(DELETE_FILM_QUERY, id); + } + public Map> getAllLikesGroupedByUser() { String sql = "SELECT * FROM Likes"; @@ -217,7 +222,6 @@ private void saveGenres(Film film) { } } - public Set getLikes(long filmId) { List likesList = jdbc.queryForList(GET_LIKES_QUERY, Long.class, filmId); return new HashSet<>(likesList); diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/UserRepository.java b/src/main/java/ru/yandex/practicum/filmorate/dal/UserRepository.java index e043ce3..317b7bc 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/UserRepository.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/UserRepository.java @@ -15,6 +15,7 @@ public class UserRepository extends BaseRepository { private static final String FIND_BY_LOGIN_QUERY = "SELECT * FROM users WHERE login = ?"; private static final String INSERT_QUERY = "INSERT INTO users(email, login, name, birthday) VALUES (?, ?, ?, ?)"; private static final String UPDATE_QUERY = "UPDATE users SET email = ?, login = ?, name = ?, birthday = ? WHERE id = ?"; + private static final String DELETE_USER_QUERY = "DELETE FROM users WHERE id = ?"; public UserRepository(JdbcTemplate jdbc, UserRowMapper mapper) { super(jdbc, mapper); @@ -60,6 +61,10 @@ public User update(User user) { return user; } + public boolean deleteUser(long id) { + return delete(DELETE_USER_QUERY, id); + } + public Set getFriends(long userId) { String sql = "SELECT friend_id FROM friendship WHERE user_id = ?"; 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..c9eba3a --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/exception/InternalServerException.java @@ -0,0 +1,7 @@ +package ru.yandex.practicum.filmorate.exception; + +public class InternalServerException extends RuntimeException { + public InternalServerException(String message) { + super(message); + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/film/FilmService.java b/src/main/java/ru/yandex/practicum/filmorate/service/film/FilmService.java index 01653ec..a513acb 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/service/film/FilmService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/film/FilmService.java @@ -9,6 +9,7 @@ import ru.yandex.practicum.filmorate.dto.GenreDto; import ru.yandex.practicum.filmorate.dto.RatingDto; import ru.yandex.practicum.filmorate.exception.ConstraintViolationException; +import ru.yandex.practicum.filmorate.exception.InternalServerException; import ru.yandex.practicum.filmorate.exception.NotFoundException; import ru.yandex.practicum.filmorate.mappers.FilmMapper; import ru.yandex.practicum.filmorate.mappers.GenreMapper; @@ -134,6 +135,20 @@ public RatingDto getRatingById(int id) { return RatingMapper.mapToRatingDto(rating); } + @Transactional + public void deleteFilm(long filmId) { + FilmDto filmDto = getFilmById(filmId); + log.info("Deleting film: {}", filmDto); + + boolean isDeleted = filmStorage.deleteFilm(filmId); + + if (isDeleted) { + log.info("Film deleted successfully"); + } else { + throw new InternalServerException("Film was not deleted due to internal error."); + } + } + public List getRecommendations(long id) { userExists(id); diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/user/UserService.java b/src/main/java/ru/yandex/practicum/filmorate/service/user/UserService.java index c8f052f..8f641bd 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/service/user/UserService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/user/UserService.java @@ -4,10 +4,12 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import ru.yandex.practicum.filmorate.dto.UserDto; import ru.yandex.practicum.filmorate.exception.EmptyFieldException; +import ru.yandex.practicum.filmorate.exception.InternalServerException; import ru.yandex.practicum.filmorate.exception.NotFoundException; import ru.yandex.practicum.filmorate.mappers.UserMapper; import ru.yandex.practicum.filmorate.model.User; @@ -128,6 +130,20 @@ public UserDto getUserById(@PathVariable long id) { return UserMapper.mapToUserDto(userDbStorage.getUserById(id)); } + @Transactional + public void deleteUser(long userId) { + UserDto userDto = getUserById(userId); + log.info("Deleting user: {}", userDto); + + boolean isDeleted = userDbStorage.deleteUser(userId); + + if (isDeleted) { + log.info("User deleted successfully"); + } else { + throw new InternalServerException("User was not deleted due to internal server error"); + } + } + private boolean isExistsUser(User user) { return userDbStorage.getUsers().stream() .noneMatch(userCheck -> userCheck.getId() == user.getId()); diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/film/FilmDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/film/FilmDbStorage.java index 76f23c4..15599af 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/storage/film/FilmDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/film/FilmDbStorage.java @@ -60,6 +60,11 @@ public Film getFilmById(long filmId) { .orElseThrow(() -> new NotFoundException("Film with id " + filmId + " not found")); } + @Override + public boolean deleteFilm(long filmId) { + return filmRepository.deleteFilm(filmId); + } + public Set getLikes(long filmId) { getFilmById(filmId); return filmRepository.getLikes(filmId); diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/film/FilmStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/film/FilmStorage.java index 4d2f3d5..2d9ad94 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/storage/film/FilmStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/film/FilmStorage.java @@ -14,4 +14,6 @@ public interface FilmStorage { Film updateFilm(@RequestBody Film film); Film getFilmById(long filmId); + + boolean deleteFilm(long filmId); } diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/user/UserDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/user/UserDbStorage.java index 3b9222c..04242c0 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/storage/user/UserDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/user/UserDbStorage.java @@ -38,6 +38,11 @@ public User getUserById(long userId) { return repository.findById(userId).orElseThrow(() -> new NotFoundException("User with id " + userId + " not found")); } + @Override + public boolean deleteUser(long userId) { + return repository.deleteUser(userId); + } + public Set getFriends(long userId) { return repository.getFriends(userId); } diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/user/UserStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/user/UserStorage.java index eaf2924..3c85c73 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/storage/user/UserStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/user/UserStorage.java @@ -14,4 +14,6 @@ public interface UserStorage { User updateUser(@RequestBody User user); User getUserById(long userId); + + boolean deleteUser(long userId); } From 4642f4c59edeb105d771ddf99ceb544130f078fd Mon Sep 17 00:00:00 2001 From: Stanislav Mun <146199590+Nesailormun@users.noreply.github.com> Date: Thu, 24 Apr 2025 20:04:06 +0300 Subject: [PATCH 11/13] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B0=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D0=BE?= =?UTF-8?q?=D0=BD=D0=B0=D0=BB=D1=8C=D0=BD=D0=BE=D1=81=D1=82=D1=8C=20=C2=AB?= =?UTF-8?q?=D0=9F=D0=BE=D0=BF=D1=83=D0=BB=D1=8F=D1=80=D0=BD=D1=8B=D0=B5=20?= =?UTF-8?q?=D1=84=D0=B8=D0=BB=D1=8C=D0=BC=D1=8B=C2=BB=20(#13)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Обновлен метод getTopFilms в цепочке FilmController->FilmService->FilmDbStorage->FilmRepository согласно новой функциональности (фильтрации по genreId и year) * Обновлен метод getTopFilms в цепочке FilmController->FilmService->FilmDbStorage->FilmRepository согласно новой функциональности (фильтрации по genreId и year) * делаю коммит псÐоскольку force push не триггерит пайплайн по всей видимости * Исправил логику работы метода getTopFilms(...) (параметры строки запроса genreId and year являются необязательными) --------- Co-authored-by: nesailormun --- pom.xml | 2 ++ .../filmorate/controller/FilmController.java | 8 +++-- .../filmorate/dal/FilmRepository.java | 32 +++++++++++++++++-- .../filmorate/service/film/FilmService.java | 17 ++++------ .../filmorate/storage/film/FilmDbStorage.java | 4 +++ src/main/resources/application.properties | 8 ++++- 6 files changed, 54 insertions(+), 17 deletions(-) diff --git a/pom.xml b/pom.xml index 0c428fa..4f7f5f6 100644 --- a/pom.xml +++ b/pom.xml @@ -43,6 +43,7 @@ org.projectlombok lombok + 1.18.30 true @@ -81,6 +82,7 @@ org.projectlombok lombok + 1.18.30 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 1ab750a..3961387 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java @@ -63,9 +63,11 @@ public void deleteLikeToFilm(@PathVariable long filmId, @PathVariable long userI } @GetMapping("/popular") - public List getTopFilms(@RequestParam(defaultValue = "10") @Positive(message = "Count must be positive") int count) { - log.info("Received GET /films/popular?count={} request", count); - return filmService.getTopFilms(count); + public List getTopFilms(@RequestParam(defaultValue = "10") @Positive(message = "Count must be positive") int count, + @RequestParam(defaultValue = "-1") int genreId, + @RequestParam(defaultValue = "-1") int year) { + log.info("Received GET /films/popular?count={}&genreId={}&year={} request", count, genreId, year); + return filmService.getTopFilms(count, genreId, year); } @DeleteMapping("/{filmId}") diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/FilmRepository.java b/src/main/java/ru/yandex/practicum/filmorate/dal/FilmRepository.java index 0874d1d..69df544 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/FilmRepository.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/FilmRepository.java @@ -98,7 +98,7 @@ public Optional findById(long id) { if (films.isEmpty()) { return Optional.empty(); } else { - Film film = films.get(0); + Film film = films.getFirst(); setGenresForFilms(List.of(film)); return Optional.of(film); } @@ -234,4 +234,32 @@ public void addLike(long filmId, long userId) { public void removeLike(long filmId, long userId) { jdbc.update(REMOVE_LIKE_QUERY, filmId, userId); } -} \ No newline at end of file + + public List getTopFilms(int count, Integer genreId, Integer year) { + + StringBuilder queryBuilder = new StringBuilder("SELECT f.*, r.rating_id AS mpa_id, r.rating_name AS mpa_name " + + "FROM films AS f JOIN rating AS r on f.rating_id = r.rating_id " + + "JOIN film_genre AS fg ON f.id = fg.film_id " + + "JOIN likes AS l ON f.id = l.film_id WHERE 1=1"); + + List filterParams = new ArrayList<>(); + + if (genreId != -1) { + queryBuilder.append(" AND fg.genre_id = ?"); + filterParams.add(genreId); + } + + if (year != -1) { + queryBuilder.append(" AND EXTRACT(YEAR FROM f.release_date) = ?"); + filterParams.add(year); + } + + queryBuilder.append(" GROUP BY f.id, r.rating_id, r.rating_name ORDER BY COUNT(l.user_id) DESC LIMIT ?;"); + filterParams.add(count); + + List films = jdbc.query(queryBuilder.toString(), filmWithRatingMapper, filterParams.toArray()); + setGenresForFilms(films); + return films; + } + +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/film/FilmService.java b/src/main/java/ru/yandex/practicum/filmorate/service/film/FilmService.java index a513acb..535aafd 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/service/film/FilmService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/film/FilmService.java @@ -23,7 +23,6 @@ import java.time.LocalDate; import java.util.Collection; import java.util.List; -import java.util.Map; import java.util.Set; import java.util.stream.Collectors; @@ -90,23 +89,19 @@ public void deleteLikeToFilm(long filmId, long userId) { log.info("Like removed successfully"); } - public List getTopFilms(int count) { - log.info("Getting top {} popular films", count); + public List getTopFilms(int count, int genreId, int year) { + log.info("Getting top {} popular films with genreId = {} and release year = {}", count, genreId, year); if (count <= 0) { throw new ValidationException("Count parameter must be positive."); } - Collection allFilms = filmStorage.getFilms(); - return allFilms.stream() + return filmStorage.getTopFilms(count, genreId, year) + .stream() .map(film -> { Set likes = filmStorage.getLikes(film.getId()); - return Map.entry(film, likes.size()); + return FilmMapper.mapToFilmDto(film, likes); }) - .sorted(Map.Entry.comparingByValue().reversed()) - .limit(count) - .map(Map.Entry::getKey) - .map(film -> FilmMapper.mapToFilmDto(film, filmStorage.getLikes(film.getId()))) - .collect(Collectors.toList()); + .toList(); } public Collection getGenres() { diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/film/FilmDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/film/FilmDbStorage.java index 15599af..f91c3fb 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/storage/film/FilmDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/film/FilmDbStorage.java @@ -96,6 +96,10 @@ public Rating getRatingById(int id) { .orElseThrow(() -> new NotFoundException("Rating with id " + id + " not found")); } + public List getTopFilms(int count, int genreId, int year) { + return filmRepository.getTopFilms(count, genreId, year); + } + private void validateRatingExists(Rating mpa) { if (mpa == null || mpa.getId() == 0) { throw new IllegalArgumentException("Film Rating (MPA) is required."); diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 1730186..73fd17b 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1,7 @@ -logging.level.org.zalando.logbook:ERROR \ No newline at end of file +logging.level.org.zalando.logbook=TRACE +logging.level.ru.yandex.practicum.filmorate=INFO +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 From 02c4821e1419e666bb1933c39c1ea0e6ab3b86cb Mon Sep 17 00:00:00 2001 From: 1Boolean1 <124202814+1Boolean1@users.noreply.github.com> Date: Thu, 24 Apr 2025 20:25:38 +0300 Subject: [PATCH 12/13] add getCommonFilms for user and friend (#10) * add getCommonFilms for user and friend * little change * checkstyle --- .../filmorate/controller/FilmController.java | 8 +++++ .../filmorate/dal/FilmRepository.java | 19 ++++++++++++ .../practicum/filmorate/dto/UserDto.java | 1 + .../filmorate/service/film/FilmService.java | 15 +++++++++ .../filmorate/storage/film/FilmDbStorage.java | 5 +++ src/main/resources/data.sql | 31 +++++++++++-------- 6 files changed, 66 insertions(+), 13 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 3961387..8baed43 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java @@ -76,4 +76,12 @@ public void deleteFilm(@PathVariable long filmId) { log.info("Received DELETE /films/{} request", filmId); filmService.deleteFilm(filmId); } + + @GetMapping("/common") + public List getCommonFilms( + @RequestParam long userId, + @RequestParam long friendId) { + log.info("GET common films for userId={} and friendId={}", userId, friendId); + return filmService.getCommonFilms(userId, friendId); + } } \ No newline at end of file diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/FilmRepository.java b/src/main/java/ru/yandex/practicum/filmorate/dal/FilmRepository.java index 69df544..63a4da6 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/FilmRepository.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/FilmRepository.java @@ -34,6 +34,17 @@ public class FilmRepository extends BaseRepository { private static final String GET_LIKES_QUERY = "SELECT user_id FROM Likes WHERE film_id = ?"; private static final String ADD_LIKE_QUERY = "INSERT INTO Likes (user_id, film_id) VALUES (?, ?)"; private static final String REMOVE_LIKE_QUERY = "DELETE FROM Likes WHERE film_id = ? AND user_id = ?"; + private static final String GET_COMMON_FILMS = "SELECT f.*, r.rating_id as mpa_id, r.rating_name as mpa_name " + + "FROM Films f " + + "JOIN rating AS r ON f.rating_id = r.rating_id " + + "JOIN Likes l ON f.id = l.film_id " + + "WHERE l.film_id IN ( " + + " SELECT film_id FROM Likes WHERE user_id = ? " + + " INTERSECT " + + " SELECT film_id FROM Likes WHERE user_id = ? " + + ") " + + "GROUP BY f.id " + + "ORDER BY COUNT(l.user_id) DESC"; private static final String FIND_GENRES_FOR_FILMS_QUERY = "SELECT fg.film_id, g.id as genre_id, g.name as genre_name " + @@ -92,6 +103,14 @@ public List findAll() { return films; } + public List findCommon(long userId, long filmId) { + List films = jdbc.query(GET_COMMON_FILMS, filmWithRatingMapper, userId, filmId); + if (!films.isEmpty()) { + setGenresForFilms(films); + } + return films; + } + public Optional findById(long id) { List films = jdbc.query(FIND_BY_ID_QUERY, filmWithRatingMapper, id); diff --git a/src/main/java/ru/yandex/practicum/filmorate/dto/UserDto.java b/src/main/java/ru/yandex/practicum/filmorate/dto/UserDto.java index d59f55f..53ff743 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dto/UserDto.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dto/UserDto.java @@ -1,4 +1,5 @@ package ru.yandex.practicum.filmorate.dto; + import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/film/FilmService.java b/src/main/java/ru/yandex/practicum/filmorate/service/film/FilmService.java index 535aafd..0aff2a0 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/service/film/FilmService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/film/FilmService.java @@ -130,6 +130,20 @@ public RatingDto getRatingById(int id) { return RatingMapper.mapToRatingDto(rating); } + + public List getCommonFilms(long userId, long friendId) { + userExists(userId); + userExists(friendId); + if (userId == friendId) { + throw new ConstraintViolationException("Id's can't be the same"); + } + log.info("Getting MPA common films for users: {}, {}", userId, friendId); + Collection films = filmStorage.getCommonFilms(userId, friendId); + return films.stream() + .map(film -> FilmMapper.mapToFilmDto(film, filmStorage.getLikes(film.getId()))) + .collect(Collectors.toList()); + } + @Transactional public void deleteFilm(long filmId) { FilmDto filmDto = getFilmById(filmId); @@ -142,6 +156,7 @@ public void deleteFilm(long filmId) { } else { throw new InternalServerException("Film was not deleted due to internal error."); } + } diff --git a/src/main/java/ru/yandex/practicum/filmorate/storage/film/FilmDbStorage.java b/src/main/java/ru/yandex/practicum/filmorate/storage/film/FilmDbStorage.java index f91c3fb..6465920 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/storage/film/FilmDbStorage.java +++ b/src/main/java/ru/yandex/practicum/filmorate/storage/film/FilmDbStorage.java @@ -96,6 +96,11 @@ public Rating getRatingById(int id) { .orElseThrow(() -> new NotFoundException("Rating with id " + id + " not found")); } + + public Collection getCommonFilms(long userId, long friendId) { + return filmRepository.findCommon(userId, friendId); + } + public List getTopFilms(int count, int genreId, int year) { return filmRepository.getTopFilms(count, genreId, year); } diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql index 33c30a6..c7bdbc0 100644 --- a/src/main/resources/data.sql +++ b/src/main/resources/data.sql @@ -1,3 +1,18 @@ +DELETE +FROM FILMS; +DELETE +FROM LIKES; +DELETE +FROM GENRE; +DELETE +FROM RATING; +DELETE +FROM USERS; +DELETE +FROM FILM_GENRE; +DELETE +FROM FRIENDSHIP; + INSERT INTO RATING(rating_id, rating_name) VALUES (1, 'G'), (2, 'PG'), @@ -8,27 +23,17 @@ VALUES (1, 'G'), INSERT INTO USERS (email, login, name, birthday) VALUES ('test@user.com', 'testlogin', 'Test User Name', '1991-11-11'); -INSERT INTO USERS (email, login, name, birthday) +INSERT INTO USERS ( email, login, name, birthday) VALUES ('test1@user.com', 'test2login', 'Another User', '1992-11-11'); -INSERT INTO USERS (email, login, name, birthday) -VALUES ('test2@user.com', 'test3login', 'Another User1', '1993-11-11'); - INSERT INTO FILMS (NAME, DESCRIPTION, RELEASE_DATE, DURATION, RATING_ID) VALUES ('film1', 'its film1', '2010-10-10', '100', '1'), - ('film2', 'its film2', '2020-12-20', '200', '2'), - ('film3', 'its film3', '2003-03-30', '300', '3'), - ('film4', 'its film4', '2024-04-14', '400', '4'); + ('film2', 'its film2', '2020-12-20', '200', '2'); INSERT INTO LIKES(user_id, film_id) VALUES (1, 1), - (1, 3), (2, 1), - (2, 2), - (2, 4), - (3, 1), - (3, 2), - (3, 4); + (2, 2); INSERT INTO GENRE (name) VALUES ('Комедия'), From f9a3cb5db8498684d13ef4ab7166d52284ba2b66 Mon Sep 17 00:00:00 2001 From: Remenchik Ilya Date: Tue, 22 Apr 2025 00:32:11 +0300 Subject: [PATCH 13/13] add getCommonFilms for user and friend --- .../ru/yandex/practicum/filmorate/controller/FilmController.java | 1 + 1 file changed, 1 insertion(+) 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 8baed43..a8b34ca 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java @@ -84,4 +84,5 @@ public List getCommonFilms( log.info("GET common films for userId={} and friendId={}", userId, friendId); return filmService.getCommonFilms(userId, friendId); } + } \ No newline at end of file