Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
30757ce
Add files via upload
n20va Oct 18, 2025
9271c54
Add files via upload
n20va Oct 18, 2025
f7a60c1
Add files via upload
n20va Oct 18, 2025
cd008fc
Delete src/main/java/ru/yandex/practicum/filmorate/repository/feed
n20va Oct 18, 2025
15ad4cb
Add files via upload
n20va Oct 18, 2025
42d5f70
Add files via upload
n20va Oct 18, 2025
22147fd
Update UserServiceImpl.java
n20va Oct 18, 2025
89a60d0
Update ReviewServiceImpl.java
n20va Oct 18, 2025
ff37a84
Update FilmServiceImpl.java
n20va Oct 18, 2025
2035b36
Update schema.sql
n20va Oct 18, 2025
9f8957b
Update Event.java
n20va Oct 18, 2025
0095ef0
Add files via upload
n20va Oct 18, 2025
dcff02f
Add files via upload
n20va Oct 18, 2025
ec8bd25
Add files via upload
n20va Oct 18, 2025
c667680
Update FeedService.java
n20va Oct 18, 2025
1ee81dd
Update FeedServiceImpl.java
n20va Oct 18, 2025
4e3da99
Update FeedController.java
n20va Oct 18, 2025
cf4b20f
Update UserServiceImpl.java
n20va Oct 18, 2025
e92266c
Update FilmServiceImpl.java
n20va Oct 18, 2025
074777c
Update ReviewServiceImpl.java
n20va Oct 18, 2025
7cebfeb
Update JdbcFeedRepository.java
n20va Oct 18, 2025
37c6fb2
Delete src/main/java/ru/yandex/practicum/filmorate/mapper/EventRowMap…
n20va Oct 18, 2025
d7d22cb
Add files via upload
n20va Oct 18, 2025
aafea6f
Update FeedController.java
n20va Oct 18, 2025
5d7a589
Update FeedController.java
n20va Oct 18, 2025
78ecfca
Update JdbcFeedRepository.java
n20va Oct 18, 2025
b980a1c
Update FeedService.java
n20va Oct 18, 2025
a6d8266
Update FeedServiceImpl.java
n20va Oct 18, 2025
b97b777
Update FilmServiceImpl.java
n20va Oct 18, 2025
0764313
Update UserServiceImpl.java
n20va Oct 18, 2025
45183d2
Update ReviewServiceImpl.java
n20va Oct 18, 2025
7915367
Update EventDto.java
n20va Oct 18, 2025
8862cf6
Update EventMapper.java
n20va Oct 18, 2025
8d525c9
Update EventMapper.java
n20va Oct 18, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package ru.yandex.practicum.filmorate.controller;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import ru.yandex.practicum.filmorate.dto.event.EventDto;
import ru.yandex.practicum.filmorate.service.feed.FeedService;

import java.util.List;

@RestController
@RequestMapping("/users")
@Slf4j
@RequiredArgsConstructor
public class FeedController {
private final FeedService feedService;

@GetMapping("/{id}/feed")
public List<EventDto> getUserFeed(@PathVariable long id) {
log.info("GET /users/{}/feed", id);
return feedService.getUserFeed(id);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package ru.yandex.practicum.filmorate.dto.event;

import lombok.Builder;
import lombok.Data;
import ru.yandex.practicum.filmorate.model.EventType;
import ru.yandex.practicum.filmorate.model.Operation;

@Data
@Builder
public class EventDto {
private Long timestamp;
private Long userId;
private EventType eventType;
private Operation operation;
private Long eventId;
private Long entityId;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package ru.yandex.practicum.filmorate.mapper;

import lombok.experimental.UtilityClass;
import ru.yandex.practicum.filmorate.dto.event.EventDto;
import ru.yandex.practicum.filmorate.model.Event;

@UtilityClass
public class EventMapper {

public EventDto toEventDto(Event event) {
return EventDto.builder()
.timestamp(event.getTimestamp())
.userId(event.getUserId())
.eventType(event.getEventType())
.operation(event.getOperation())
.eventId(event.getEventId())
.entityId(event.getEntityId())
.build();
}
}

18 changes: 18 additions & 0 deletions src/main/java/ru/yandex/practicum/filmorate/model/Event.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package ru.yandex.practicum.filmorate.model;

import lombok.AccessLevel;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.FieldDefaults;

@Data
@Builder
@FieldDefaults(level = AccessLevel.PRIVATE)
public class Event {
Long eventId;
Long timestamp;
Long userId;
EventType eventType;
Operation operation;
Long entityId;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package ru.yandex.practicum.filmorate.model;

public enum EventType {
LIKE, REVIEW, FRIEND
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package ru.yandex.practicum.filmorate.model;

public enum Operation {
REMOVE, ADD, UPDATE
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package ru.yandex.practicum.filmorate.repository.feed;

import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Component;
import ru.yandex.practicum.filmorate.model.Event;
import ru.yandex.practicum.filmorate.model.EventType;
import ru.yandex.practicum.filmorate.model.Operation;

import java.sql.ResultSet;
import java.sql.SQLException;

@Component
public class EventRowMapper implements RowMapper<Event> {
@Override
public Event mapRow(ResultSet rs, int rowNum) throws SQLException {
return Event.builder()
.eventId(rs.getLong("event_id"))
.timestamp(rs.getLong("timestamp"))
.userId(rs.getLong("user_id"))
.eventType(EventType.valueOf(rs.getString("event_type")))
.operation(Operation.valueOf(rs.getString("operation")))
.entityId(rs.getLong("entity_id"))
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package ru.yandex.practicum.filmorate.repository.feed;

import ru.yandex.practicum.filmorate.model.Event;
import java.util.List;

public interface FeedRepository {
List<Event> findByUserId(long userId);

Event save(Event event);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package ru.yandex.practicum.filmorate.repository.feed;

import lombok.RequiredArgsConstructor;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.stereotype.Repository;
import ru.yandex.practicum.filmorate.model.Event;

import java.util.List;

@Repository
@RequiredArgsConstructor
public class JdbcFeedRepository implements FeedRepository {
private final NamedParameterJdbcOperations jdbc;
private final EventRowMapper eventRowMapper;

@Override
public List<Event> findByUserId(long userId) {
String sql = "SELECT * FROM feed_events WHERE user_id = :user_id ORDER BY timestamp ASC";
return jdbc.query(sql,
new MapSqlParameterSource("user_id", userId),
eventRowMapper);
}

@Override
public Event save(Event event) {
GeneratedKeyHolder keyHolder = new GeneratedKeyHolder();
MapSqlParameterSource params = new MapSqlParameterSource()
.addValue("timestamp", event.getTimestamp())
.addValue("user_id", event.getUserId())
.addValue("event_type", event.getEventType().toString())
.addValue("operation", event.getOperation().toString())
.addValue("entity_id", event.getEntityId());
String sql = """
INSERT INTO feed_events (timestamp, user_id, event_type, operation, entity_id)
VALUES (:timestamp, :user_id, :event_type, :operation, :entity_id)
""";
jdbc.update(sql, params, keyHolder, new String[]{"event_id"});
event.setEventId(keyHolder.getKeyAs(Long.class));
return event;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package ru.yandex.practicum.filmorate.service.feed;

import ru.yandex.practicum.filmorate.dto.event.EventDto;
import ru.yandex.practicum.filmorate.model.EventType;
import ru.yandex.practicum.filmorate.model.Operation;

import java.util.List;

public interface FeedService {
List<EventDto> getUserFeed(long userId);

void addEvent(EventType eventType, Operation operation, long userId, long entityId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package ru.yandex.practicum.filmorate.service.feed;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import ru.yandex.practicum.filmorate.dto.event.EventDto;
import ru.yandex.practicum.filmorate.exception.NotFoundException;
import ru.yandex.practicum.filmorate.mapper.EventMapper;
import ru.yandex.practicum.filmorate.model.Event;
import ru.yandex.practicum.filmorate.model.EventType;
import ru.yandex.practicum.filmorate.model.Operation;
import ru.yandex.practicum.filmorate.repository.feed.FeedRepository;
import ru.yandex.practicum.filmorate.repository.user.UserRepository;

import java.util.List;

@Service
@Slf4j
@RequiredArgsConstructor
public class FeedServiceImpl implements FeedService {
private final FeedRepository feedRepository;
private final UserRepository userRepository;

@Override
public List<EventDto> getUserFeed(long userId) {
userRepository.findById(userId)
.orElseThrow(NotFoundException.supplier("User with id %d not found", userId));

List<Event> events = feedRepository.findByUserId(userId);
log.info("Retrieved {} events for user {}", events.size(), userId);
return events.stream()
.map(EventMapper::toEventDto)
.toList();
}

@Override
public void addEvent(EventType eventType, Operation operation, long userId, long entityId) {
Event event = Event.builder()
.timestamp(System.currentTimeMillis())
.userId(userId)
.eventType(eventType)
.operation(operation)
.entityId(entityId)
.build();
feedRepository.save(event);
log.debug("Event saved: type={}, operation={}, userId={}, entityId={}",
eventType, operation, userId, entityId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,17 @@
import ru.yandex.practicum.filmorate.exception.NotFoundException;
import ru.yandex.practicum.filmorate.mapper.FilmMapper;
import ru.yandex.practicum.filmorate.model.Director;
import ru.yandex.practicum.filmorate.model.EventType;
import ru.yandex.practicum.filmorate.model.Film;
import ru.yandex.practicum.filmorate.model.Genre;
import ru.yandex.practicum.filmorate.model.Operation;
import ru.yandex.practicum.filmorate.repository.director.DirectorRepository;
import ru.yandex.practicum.filmorate.repository.film.FilmRepository;
import ru.yandex.practicum.filmorate.repository.genre.GenreRepository;
import ru.yandex.practicum.filmorate.repository.like.LikesRepository;
import ru.yandex.practicum.filmorate.repository.mparating.MPARatingRepository;
import ru.yandex.practicum.filmorate.repository.user.UserRepository;
import ru.yandex.practicum.filmorate.service.feed.FeedService;

import java.util.Collection;
import java.util.List;
Expand All @@ -37,6 +40,7 @@ public class FilmServiceImpl implements FilmService {
GenreRepository genreRepository;
MPARatingRepository mpaRepository;
DirectorRepository directorRepository;
FeedService feedService;

@Override
public Collection<FilmDto> findAll() {
Expand Down Expand Up @@ -137,6 +141,7 @@ public void addLike(long filmId, long userId) {
throwIfFilmNotFound(filmId);
throwIfUserNotFound(userId);
likesRepository.addLike(userId, filmId);
feedService.addEvent(EventType.LIKE, Operation.ADD, userId, filmId);
log.info("Like to film with id {} has been added by user {}", filmId, userId);
}

Expand All @@ -145,6 +150,7 @@ public void removeLike(long filmId, long userId) {
throwIfFilmNotFound(filmId);
throwIfUserNotFound(userId);
likesRepository.removeLike(userId, filmId);
feedService.addEvent(EventType.LIKE, Operation.REMOVE, userId, filmId);
log.info("Like to film with id {} has been removed by user {}", filmId, userId);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,18 @@
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import ru.yandex.practicum.filmorate.dto.review.*;
import ru.yandex.practicum.filmorate.dto.review.NewReviewRequest;
import ru.yandex.practicum.filmorate.dto.review.ReviewDto;
import ru.yandex.practicum.filmorate.dto.review.UpdateReviewRequest;
import ru.yandex.practicum.filmorate.exception.NotFoundException;
import ru.yandex.practicum.filmorate.mapper.ReviewMapper;
import ru.yandex.practicum.filmorate.model.EventType;
import ru.yandex.practicum.filmorate.model.Operation;
import ru.yandex.practicum.filmorate.model.Review;
import ru.yandex.practicum.filmorate.repository.film.FilmRepository;
import ru.yandex.practicum.filmorate.repository.review.ReviewRepository;
import ru.yandex.practicum.filmorate.repository.user.UserRepository;
import ru.yandex.practicum.filmorate.service.feed.FeedService;

import java.util.Collection;

Expand All @@ -20,13 +25,15 @@ public class ReviewServiceImpl implements ReviewService {
private final ReviewRepository reviewRepository;
private final UserRepository userRepository;
private final FilmRepository filmRepository;
private final FeedService feedService;

@Override
public ReviewDto create(NewReviewRequest request) {
throwIfUserNotFound(request.getUserId());
throwIfFilmNotFound(request.getFilmId());
Review review = ReviewMapper.toReview(request);
review = reviewRepository.save(review);
feedService.addEvent(EventType.REVIEW, Operation.ADD, request.getUserId(), review.getId());
log.info("Review with reviewId {} has been created", review.getId());
return ReviewMapper.toDto(review);
}
Expand All @@ -35,14 +42,16 @@ public ReviewDto create(NewReviewRequest request) {
public ReviewDto update(UpdateReviewRequest request) {
Review review = getReviewOrThrow(request.getReviewId());
review = ReviewMapper.updateReviewFields(review, request);
feedService.addEvent(EventType.REVIEW, Operation.UPDATE, review.getUserId(), review.getId());
log.info("Review with reviewId {} has been updated", review.getId());
reviewRepository.update(review);
return ReviewMapper.toDto(review);
}

@Override
public void delete(long id) {
getReviewOrThrow(id);
Review review = getReviewOrThrow(id);
feedService.addEvent(EventType.REVIEW, Operation.REMOVE, review.getUserId(), id);
log.info("Review with reviewId {} has been deleted", id);
reviewRepository.delete(id);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@
import ru.yandex.practicum.filmorate.dto.user.UserDto;
import ru.yandex.practicum.filmorate.exception.NotFoundException;
import ru.yandex.practicum.filmorate.mapper.UserMapper;
import ru.yandex.practicum.filmorate.model.EventType;
import ru.yandex.practicum.filmorate.model.Operation;
import ru.yandex.practicum.filmorate.model.User;
import ru.yandex.practicum.filmorate.repository.friendship.FriendshipsRepository;
import ru.yandex.practicum.filmorate.repository.user.UserRepository;
import ru.yandex.practicum.filmorate.service.feed.FeedService;

import java.util.Collection;

Expand All @@ -20,6 +23,7 @@
public class UserServiceImpl implements UserService {
private final UserRepository userRepository;
private final FriendshipsRepository friendshipsRepository;
private final FeedService feedService;

@Override
public Collection<UserDto> findAll() {
Expand Down Expand Up @@ -68,8 +72,8 @@ public void deleteById(long id) {
public void addFriend(long userId, long friendId) {
throwIfUserNotFound(userId);
throwIfUserNotFound(friendId);

friendshipsRepository.addFriendship(userId, friendId);
feedService.addEvent(EventType.FRIEND, Operation.ADD, userId, friendId);
log.info("User with id {} added user with id {} as friend", userId, friendId);
}

Expand All @@ -78,6 +82,7 @@ public void removeFriend(long userId, long friendId) {
throwIfUserNotFound(userId);
throwIfUserNotFound(friendId);
friendshipsRepository.removeFriendship(userId, friendId);
feedService.addEvent(EventType.FRIEND, Operation.REMOVE, userId, friendId);
log.info("User with id {} removed user with id {} from friends", userId, friendId);
}

Expand Down
12 changes: 11 additions & 1 deletion src/main/resources/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -101,4 +101,14 @@ CREATE TABLE IF NOT EXISTS review_likes
PRIMARY KEY (review_id, user_id),
FOREIGN KEY (review_id) REFERENCES reviews (review_id) ON DELETE CASCADE,
FOREIGN KEY (user_id) REFERENCES users (user_id) ON DELETE CASCADE
);
);

CREATE TABLE IF NOT EXISTS feed_events (
event_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
timestamp BIGINT NOT NULL,
user_id BIGINT NOT NULL,
event_type ENUM('LIKE', 'REVIEW', 'FRIEND') NOT NULL,
operation ENUM('REMOVE', 'ADD', 'UPDATE') NOT NULL,
entity_id BIGINT NOT NULL,
FOREIGN KEY (user_id) REFERENCES users (user_id) ON DELETE CASCADE
);