diff --git a/src/main/java/ru/yandex/practicum/filmorate/Enum/EventType.java b/src/main/java/ru/yandex/practicum/filmorate/Enum/EventType.java new file mode 100644 index 0000000..86b32c0 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/Enum/EventType.java @@ -0,0 +1,8 @@ +package ru.yandex.practicum.filmorate.Enum; + +public enum EventType { + LIKE, + DISLIKE, + FRIEND, + REVIEW +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/Enum/OperationType.java b/src/main/java/ru/yandex/practicum/filmorate/Enum/OperationType.java new file mode 100644 index 0000000..3163dbc --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/Enum/OperationType.java @@ -0,0 +1,7 @@ +package ru.yandex.practicum.filmorate.Enum; + +public enum OperationType { + ADD, + UPDATE, + REMOVE +} 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 b17a7e8..488b5ac 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java @@ -6,10 +6,12 @@ import org.springframework.http.HttpStatus; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +import ru.yandex.practicum.filmorate.model.Activity; import ru.yandex.practicum.filmorate.model.User; import ru.yandex.practicum.filmorate.service.UserServiceImpl; import java.util.Collection; +import java.util.List; @RestController @RequiredArgsConstructor @@ -19,6 +21,12 @@ public class UserController { private final UserServiceImpl userService; + @GetMapping("/{userId}/feed") + @ResponseStatus(HttpStatus.OK) + public List getActivityByUserId(@PathVariable long userId) { + return userService.getActivityById(userId); + } + @GetMapping @ResponseStatus(HttpStatus.OK) public Collection findAll() { diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/JdbcFilmRepository.java b/src/main/java/ru/yandex/practicum/filmorate/dal/JdbcFilmRepository.java index ae65f2b..add1ea3 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/JdbcFilmRepository.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/JdbcFilmRepository.java @@ -6,6 +6,8 @@ import org.springframework.jdbc.support.GeneratedKeyHolder; import org.springframework.jdbc.support.rowset.SqlRowSet; import org.springframework.stereotype.Repository; +import ru.yandex.practicum.filmorate.Enum.EventType; +import ru.yandex.practicum.filmorate.Enum.OperationType; import ru.yandex.practicum.filmorate.dal.mappers.DirectorRowMapper; import ru.yandex.practicum.filmorate.dal.mappers.FilmRowMapper; import ru.yandex.practicum.filmorate.dal.mappers.GenreRowMapper; @@ -13,6 +15,7 @@ import ru.yandex.practicum.filmorate.model.Film; import ru.yandex.practicum.filmorate.model.Genre; +import java.time.Instant; import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -94,6 +97,19 @@ WHERE fd.film_id IN (:film_ids) ORDER BY f.name; """; + private static final String ACTIVITY_GENERAL = + "INSERT INTO activity (userId, entityId, eventType, operation, timestamp) VALUES(:userId, :entityId, '"; + + private static final String ACTIVITY_FILM_LIKE = ACTIVITY_GENERAL + + EventType.LIKE + "','" + OperationType.ADD + "', " + instantOfMilliSecond() + ")"; + + private static final String ACTIVITY_FILM_LIKE_DELETE = ACTIVITY_GENERAL + + EventType.LIKE + "','" + OperationType.REMOVE + "'," + instantOfMilliSecond() + ")"; + + private static long instantOfMilliSecond() { + return Instant.now().toEpochMilli(); + } + @Override public Film create(Film film) { GeneratedKeyHolder keyHolder = new GeneratedKeyHolder(); @@ -257,6 +273,12 @@ public void addLike(long filmId, long userId) { params.addValue("user_id", userId); params.addValue("film_id", filmId); jdbc.update(ADD_LIKE_QUERY, params, keyHolder); + + MapSqlParameterSource paramsActivity = new MapSqlParameterSource(); + paramsActivity.addValue("userId", userId); + paramsActivity.addValue("entityId", filmId); + + jdbc.update(ACTIVITY_FILM_LIKE, paramsActivity); } @Override @@ -266,6 +288,12 @@ public void deleteLike(long filmId, long userId) { params.addValue("user_id", userId); params.addValue("film_id", filmId); jdbc.update(DELETE_LIKE_QUERY, params, keyHolder); + + MapSqlParameterSource paramsActivity = new MapSqlParameterSource(); + paramsActivity.addValue("userId", userId); + paramsActivity.addValue("entityId", filmId); + + jdbc.update(ACTIVITY_FILM_LIKE_DELETE, paramsActivity); } @Override diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/JdbcReviewRepository.java b/src/main/java/ru/yandex/practicum/filmorate/dal/JdbcReviewRepository.java index c5229ae..707cfe4 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/JdbcReviewRepository.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/JdbcReviewRepository.java @@ -5,9 +5,12 @@ import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; import org.springframework.jdbc.support.GeneratedKeyHolder; import org.springframework.stereotype.Repository; +import ru.yandex.practicum.filmorate.Enum.EventType; +import ru.yandex.practicum.filmorate.Enum.OperationType; import ru.yandex.practicum.filmorate.dal.mappers.ReviewRowMapper; import ru.yandex.practicum.filmorate.model.Review; +import java.time.Instant; import java.util.List; import java.util.Optional; import java.util.stream.Stream; @@ -71,6 +74,30 @@ INSERT INTO reviews_likes (reviewId, userId, reaction_type) WHERE reviewId=:reviewId AND userId=:userId """; + private static final String ACTIVITY_GENERAL = + "INSERT INTO activity (userId, entityId, eventType, operation, timestamp) VALUES(:userId, :entityId, '"; + + private static final String ACTIVITY_REVIEW_CREATE = ACTIVITY_GENERAL + + EventType.REVIEW + "','" + OperationType.ADD + "', " + instantOfMilliSecond() + ")"; + + private static final String ACTIVITY_REVIEW_UPDATE = ACTIVITY_GENERAL + + EventType.REVIEW + "','" + OperationType.UPDATE + "', " + instantOfMilliSecond() + ")"; + + private static final String ACTIVITY_REVIEW_DELETE = ACTIVITY_GENERAL + + EventType.REVIEW + "','" + OperationType.REMOVE + "', " + instantOfMilliSecond() + ")"; + + private static final String ACTIVITY_REVIEW_ADD_LIKE = ACTIVITY_GENERAL + + EventType.LIKE + "','" + OperationType.ADD + "', " + instantOfMilliSecond() + ")"; + + private static final String ACTIVITY_REVIEW_ADD_DISLIKE = ACTIVITY_GENERAL + + EventType.DISLIKE + "','" + OperationType.ADD + "', " + instantOfMilliSecond() + ")"; + + private static final String ACTIVITY_REVIEW_DELETE_REACTION = ACTIVITY_GENERAL + + EventType.LIKE + "','" + OperationType.REMOVE + "', " + instantOfMilliSecond() + ")"; + + private static long instantOfMilliSecond() { + return Instant.now().toEpochMilli(); + } @Override public Optional getReviewById(long reviewId) { @@ -100,6 +127,12 @@ public Review create(Review review) { jdbc.update(CREATE_REVIEW, params, keyHolder); review.setReviewId(keyHolder.getKeyAs(Long.class)); + MapSqlParameterSource paramsActivity = new MapSqlParameterSource(); + paramsActivity.addValue("userId", review.getUserId()); + paramsActivity.addValue("entityId", review.getReviewId()); + + jdbc.update(ACTIVITY_REVIEW_CREATE, paramsActivity); + return review; } @@ -117,6 +150,12 @@ public Review update(Review review) { jdbc.update(UPDATE_REVIEW, params, keyHolder); + MapSqlParameterSource paramsActivity = new MapSqlParameterSource(); + paramsActivity.addValue("userId", review.getUserId()); + paramsActivity.addValue("entityId", review.getReviewId()); + + jdbc.update(ACTIVITY_REVIEW_UPDATE, paramsActivity); + return review; } @@ -124,6 +163,13 @@ public Review update(Review review) { public void deleteReview(long reviewId) { MapSqlParameterSource params = new MapSqlParameterSource(); params.addValue("reviewId", reviewId); + + Optional review = getReviewById(reviewId); + MapSqlParameterSource paramsActivity = new MapSqlParameterSource(); + paramsActivity.addValue("userId", review.get().getUserId()); + paramsActivity.addValue("entityId", review.get().getReviewId()); + + jdbc.update(ACTIVITY_REVIEW_DELETE, paramsActivity); jdbc.update(DELETE_REVIEW, params); } @@ -134,6 +180,12 @@ public void addLike(long reviewId, long userId) { params.addValue("reviewId", reviewId); params.addValue("userId", userId); jdbc.update(ADD_LIKE_REVIEW, params); + + MapSqlParameterSource paramsActivity = new MapSqlParameterSource(); + paramsActivity.addValue("userId", userId); + paramsActivity.addValue("entityId", reviewId); + + jdbc.update(ACTIVITY_REVIEW_ADD_LIKE, paramsActivity); } @Override @@ -143,6 +195,12 @@ public void addDislike(long reviewId, long userId) { params.addValue("reviewId", reviewId); params.addValue("userId", userId); jdbc.update(ADD_DISLIKE_REVIEW, params); + + MapSqlParameterSource paramsActivity = new MapSqlParameterSource(); + paramsActivity.addValue("userId", userId); + paramsActivity.addValue("entityId", reviewId); + + jdbc.update(ACTIVITY_REVIEW_ADD_DISLIKE, paramsActivity); } @Override @@ -151,6 +209,12 @@ public void deleteReaction(long reviewId, long userId) { params.addValue("reviewId", reviewId); params.addValue("userId", userId); jdbc.update(DELETE_LIKE_REVIEW, params); + + MapSqlParameterSource paramsActivity = new MapSqlParameterSource(); + paramsActivity.addValue("userId", userId); + paramsActivity.addValue("entityId", reviewId); + + jdbc.update(ACTIVITY_REVIEW_DELETE_REACTION, paramsActivity); } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/JdbcUserRepository.java b/src/main/java/ru/yandex/practicum/filmorate/dal/JdbcUserRepository.java index b559549..4b14936 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/JdbcUserRepository.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/JdbcUserRepository.java @@ -6,9 +6,15 @@ import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; import org.springframework.jdbc.support.GeneratedKeyHolder; import org.springframework.stereotype.Repository; +import ru.yandex.practicum.filmorate.Enum.EventType; +import ru.yandex.practicum.filmorate.Enum.OperationType; +import ru.yandex.practicum.filmorate.dal.mappers.ActivityRowMapper; +import ru.yandex.practicum.filmorate.model.Activity; import ru.yandex.practicum.filmorate.model.User; -import java.util.*; +import java.time.Instant; +import java.util.List; +import java.util.Optional; import java.util.stream.Stream; @Repository @@ -16,6 +22,7 @@ public class JdbcUserRepository implements UserRepository { private final NamedParameterJdbcOperations jdbc; private final RowMapper mapper; + private final ActivityRowMapper activityRowMapper; private static final String CREATE_USER_QUERY = "INSERT INTO users (login, email, name, birthday) VALUES(:login,:email,:name,:birthday)"; private static final String UPDATE_USER_QUERY = "UPDATE users SET login=:login, email=:email, name=:name, birthday=:birthday WHERE user_id=:user_id"; @@ -29,6 +36,28 @@ public class JdbcUserRepository implements UserRepository { + "JOIN friends f2 ON u.user_id = f2.friend_id WHERE f1.user_id = :user_id AND f2.user_id = :other_id"; private static final String FIND_USERS_BY_IDS_QUERY = "SELECT * FROM users WHERE user_id IN (:ids)"; + private static final String ACTIVITY_GENERAL = + "INSERT INTO activity (userId, entityId, eventType, operation, timestamp) VALUES(:userId, :entityId, '"; + + private static final String ACTIVITY_FRIEND_ADD = ACTIVITY_GENERAL + + EventType.FRIEND + "','" + OperationType.ADD + "'," + instantOfMilliSecond() + ")"; + + private static final String ACTIVITY_FRIEND_DELETE = ACTIVITY_GENERAL + + EventType.FRIEND + "','" + OperationType.REMOVE + "', " + instantOfMilliSecond() + ")"; + + private static final String GET_ACTIVITY_BY_USER_ID = "SELECT * FROM activity WHERE userId = :userId"; + + private static long instantOfMilliSecond() { + return Instant.now().toEpochMilli(); + } + + @Override + public List getActivityById(long userId) { + MapSqlParameterSource paramsActivity = new MapSqlParameterSource(); + paramsActivity.addValue("userId", userId); + return jdbc.query(GET_ACTIVITY_BY_USER_ID, paramsActivity, activityRowMapper); + } + @Override public User create(User user) { GeneratedKeyHolder keyHolder = new GeneratedKeyHolder(); @@ -83,6 +112,11 @@ public void addFriend(long userId, long friendId) { params.addValue("user_id", userId); params.addValue("friend_id", friendId); jdbc.update(ADD_FRIEND_QUERY, params, keyHolder); + + MapSqlParameterSource paramsActivity = new MapSqlParameterSource(); + paramsActivity.addValue("userId", userId); + paramsActivity.addValue("entityId", friendId); + jdbc.update(ACTIVITY_FRIEND_ADD, paramsActivity); } @Override @@ -92,6 +126,11 @@ public void deleteFriend(long userId, long friendId) { params.addValue("user_id", userId); params.addValue("friend_id", friendId); jdbc.update(DELETE_FRIEND_QUERY, params, keyHolder); + + MapSqlParameterSource paramsActivity = new MapSqlParameterSource(); + paramsActivity.addValue("userId", userId); + paramsActivity.addValue("entityId", friendId); + jdbc.update(ACTIVITY_FRIEND_DELETE, paramsActivity); } @Override 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 531de81..1d08cf9 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dal/UserRepository.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/UserRepository.java @@ -1,11 +1,14 @@ package ru.yandex.practicum.filmorate.dal; +import ru.yandex.practicum.filmorate.model.Activity; import ru.yandex.practicum.filmorate.model.User; import java.util.List; import java.util.Optional; public interface UserRepository { + List getActivityById(long activityId); + Optional getUserById(long userId); List getUsersByIds(List userIds); diff --git a/src/main/java/ru/yandex/practicum/filmorate/dal/mappers/ActivityRowMapper.java b/src/main/java/ru/yandex/practicum/filmorate/dal/mappers/ActivityRowMapper.java new file mode 100644 index 0000000..d32ad12 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/dal/mappers/ActivityRowMapper.java @@ -0,0 +1,23 @@ +package ru.yandex.practicum.filmorate.dal.mappers; + +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Component; +import ru.yandex.practicum.filmorate.model.Activity; + +import java.sql.ResultSet; +import java.sql.SQLException; + +@Component +public class ActivityRowMapper implements RowMapper { + @Override + public Activity mapRow(ResultSet rs, int rowNum) throws SQLException { + Activity activity = new Activity(); + activity.setEventId(rs.getLong("eventId")); + activity.setUserId(rs.getLong("userId")); + activity.setEntityId(rs.getLong("entityId")); + activity.setEventType(rs.getString("eventType")); + activity.setOperation(rs.getString("operation")); + activity.setTimestamp(rs.getLong("timestamp")); + return activity; + } +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/model/Activity.java b/src/main/java/ru/yandex/practicum/filmorate/model/Activity.java new file mode 100644 index 0000000..a6e54f6 --- /dev/null +++ b/src/main/java/ru/yandex/practicum/filmorate/model/Activity.java @@ -0,0 +1,23 @@ +package ru.yandex.practicum.filmorate.model; + +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class Activity { + private long eventId; + @NotNull + private long userId; + @NotNull + private long entityId; + @NotNull + private String eventType; + @NotNull + private String operation; + @NotNull + private long timestamp; +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/UserService.java b/src/main/java/ru/yandex/practicum/filmorate/service/UserService.java index 95971a3..0b68c8a 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/service/UserService.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/UserService.java @@ -1,10 +1,13 @@ package ru.yandex.practicum.filmorate.service; +import ru.yandex.practicum.filmorate.model.Activity; import ru.yandex.practicum.filmorate.model.User; import java.util.List; public interface UserService { + List getActivityById(long activityId); + User getUserById(long userId); List getUsers(); diff --git a/src/main/java/ru/yandex/practicum/filmorate/service/UserServiceImpl.java b/src/main/java/ru/yandex/practicum/filmorate/service/UserServiceImpl.java index 19c8a55..5357699 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/service/UserServiceImpl.java +++ b/src/main/java/ru/yandex/practicum/filmorate/service/UserServiceImpl.java @@ -7,9 +7,11 @@ import ru.yandex.practicum.filmorate.dal.JdbcUserRepository; import ru.yandex.practicum.filmorate.exception.NotFoundException; import ru.yandex.practicum.filmorate.exception.ValidationException; +import ru.yandex.practicum.filmorate.model.Activity; import ru.yandex.practicum.filmorate.model.User; -import java.util.*; +import java.util.ArrayList; +import java.util.List; @Service @Slf4j @@ -18,6 +20,11 @@ public class UserServiceImpl implements UserService { private final JdbcUserRepository jdbcUserRepository; + @Override + public List getActivityById(long userId) { + return jdbcUserRepository.getActivityById(userId); + } + @Override public User getUserById(long userId) { return jdbcUserRepository.getUserById(userId) diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index 696ddca..26525b5 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -70,3 +70,12 @@ CREATE TABLE IF NOT EXISTS reviews_likes ( reaction_type SMALLINT NOT NULL CHECK (reaction_type IN (-1, 1)), PRIMARY KEY (reviewId, userId) ); + +CREATE TABLE IF NOT EXISTS activity ( + eventId BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + userId BIGINT NOT NULL REFERENCES users(user_id) ON DELETE CASCADE, + entityId INT NOT NULL, + eventType VARCHAR(20) NOT NULL, + operation VARCHAR(20) NOT NULL, + timestamp BIGINT NOT NULL +);