Skip to content

Commit

Permalink
Merge pull request #225 from bounswe/202-implement-bookmark-and-like-…
Browse files Browse the repository at this point in the history
…features-for-posts

Implemented bookmark and like feature for posts.
  • Loading branch information
oguzhekim authored Nov 25, 2024
2 parents de6aedc + dc79ee2 commit f959a5f
Show file tree
Hide file tree
Showing 10 changed files with 169 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,23 +26,23 @@ public ResponseEntity<PostResponse> createPost(@RequestBody PostRequest postRequ

@GetMapping
public ResponseEntity<List<PostResponse>> fetchPosts(
@RequestParam(required = false) Set<String> tags) {
@RequestParam(required = false) Set<String> tags, HttpServletRequest request) {

List<PostResponse> posts;

if (tags != null) {
posts = postService.getPostsByTags(tags);
posts = postService.getPostsByTags(tags, request);
} else {
posts = postService.getAllPosts();
posts = postService.getAllPosts(request);
}

return ResponseEntity.ok(posts);
}

@GetMapping("/user/{username}")
public ResponseEntity<List<PostResponse>> getPostsByUser(@PathVariable String username) {
public ResponseEntity<List<PostResponse>> getPostsByUser(@PathVariable String username, HttpServletRequest request) {
try {
List<PostResponse> posts = postService.getPostsByUser(username);
List<PostResponse> posts = postService.getPostsByUser(username, request);
return ResponseEntity.ok(posts);
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
Expand All @@ -63,10 +63,40 @@ public ResponseEntity<String> deletePost(@PathVariable Long postId, HttpServletR

@GetMapping("/random")
public ResponseEntity<List<PostResponse>> getRandomPosts(
@RequestParam(defaultValue = "10") int count) {
List<PostResponse> randomPosts = postService.getRandomPosts(count);
@RequestParam(defaultValue = "10") int count, HttpServletRequest request) {
List<PostResponse> randomPosts = postService.getRandomPosts(count, request);
return ResponseEntity.ok(randomPosts);
}

@PostMapping("/{postId}/like")
public ResponseEntity<Void> likePost(@PathVariable Long postId, HttpServletRequest request) {
postService.likePost(postId, request);
return ResponseEntity.ok().build();
}

@DeleteMapping("/{postId}/like")
public ResponseEntity<Void> unlikePost(@PathVariable Long postId, HttpServletRequest request) {
postService.unlikePost(postId, request);
return ResponseEntity.ok().build();
}

@PostMapping("/{postId}/bookmark")
public ResponseEntity<Void> bookmarkPost(@PathVariable Long postId, HttpServletRequest request) {
postService.bookmarkPost(postId, request);
return ResponseEntity.ok().build();
}

@DeleteMapping("/{postId}/bookmark")
public ResponseEntity<Void> unbookmarkPost(@PathVariable Long postId, HttpServletRequest request) {
postService.unbookmarkPost(postId, request);
return ResponseEntity.ok().build();
}

@GetMapping("/bookmarked")
public ResponseEntity<List<PostResponse>> getBookmarkedPosts(HttpServletRequest request) {
List<PostResponse> bookmarkedPosts = postService.getBookmarkedPosts(request);
return ResponseEntity.ok(bookmarkedPosts);
}

}

Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ public class UserController {
private final UserService userService;

@GetMapping("/{username}")
public ResponseEntity<UserProfileResponse> getUserByUsername(@PathVariable String username) {
public ResponseEntity<UserProfileResponse> getUserByUsername(@PathVariable String username, HttpServletRequest request) {
try {
UserProfileResponse user = userService.getUserProfile(username);
UserProfileResponse user = userService.getUserProfile(username, request);
return ResponseEntity.ok(user);
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.group7.demo.dtos;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

Expand All @@ -10,6 +11,7 @@
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class PostResponse {

private Long id;
Expand All @@ -18,5 +20,8 @@ public class PostResponse {
private LocalDateTime createdAt;
private String username;
private TrainingProgramResponse trainingProgram;
private int likeCount;
private boolean isLiked;
private boolean isBookmarked;
private String imageUrl;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

import com.group7.demo.dtos.*;
import com.group7.demo.models.*;
import com.group7.demo.services.AuthenticationService;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

import java.util.Comparator;
Expand All @@ -11,17 +14,23 @@
import java.util.stream.Collectors;

@Component
@RequiredArgsConstructor
public class Mapper {
public PostResponse mapToPostResponse(Post post) {
return new PostResponse(
post.getId(),
post.getContent(),
post.getTags().stream().map(Tag::getName).collect(Collectors.toSet()), // Only tag names
post.getCreatedAt(),
post.getUser().getUsername(),
post.getTrainingProgram() == null ? null : mapToTrainingProgramResponse(post.getTrainingProgram()),
post.getImageUrl()
);
private final AuthenticationService authenticationService;
public PostResponse mapToPostResponse(Post post, HttpServletRequest request) {
User currentUser = authenticationService.getAuthenticatedUserInternal(request);

return PostResponse.builder()
.id(post.getId())
.content(post.getContent())
.tags(post.getTags().stream().map(Tag::getName).collect(Collectors.toSet()))
.createdAt(post.getCreatedAt())
.username(post.getUser().getUsername())
.trainingProgram(post.getTrainingProgram() == null ? null : mapToTrainingProgramResponse(post.getTrainingProgram()))
.likeCount(post.getLikedByUsers() == null ? 0 : post.getLikedByUsers().size())
.isLiked(currentUser != null && post.getLikedByUsers() != null && post.getLikedByUsers().contains(currentUser))
.isBookmarked(currentUser != null && post.getBookmarkedByUsers() != null && post.getBookmarkedByUsers().contains(currentUser))
.build();
}

public TrainingProgramResponse mapToTrainingProgramResponse(TrainingProgram program) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.group7.demo.exceptions;

public class UnauthorizedException extends RuntimeException {
public UnauthorizedException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.group7.demo.exceptions.handler;

import com.group7.demo.exceptions.ErrorResponse;
import com.group7.demo.exceptions.UnauthorizedException;
import jakarta.persistence.EntityNotFoundException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
Expand Down Expand Up @@ -45,6 +46,13 @@ public ResponseEntity<Object> handleEntityNotFoundException(EntityNotFoundExcept
return new ResponseEntity<>(body, HttpStatus.NOT_FOUND);
}

@ExceptionHandler(UnauthorizedException.class)
public ResponseEntity<Object> handleUnauthorizedException(UnauthorizedException ex) {
Map<String, Object> body = new HashMap<>();
body.put("timestamp", LocalDateTime.now());
body.put("message", ex.getMessage());

return new ResponseEntity<>(body, HttpStatus.UNAUTHORIZED);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
@NoArgsConstructor
@AllArgsConstructor
@Entity
@EqualsAndHashCode(exclude = "tags") // Avoid using tags in equals and hashCode to prevent recursion
@EqualsAndHashCode(exclude = {"tags", "likedByUsers", "bookmarkedByUsers"}) // Avoid using tags in equals and hashCode to prevent recursion
public class Post {

@Id
Expand Down Expand Up @@ -41,6 +41,20 @@ public class Post {
@JoinColumn(name = "training_program_id")
private TrainingProgram trainingProgram;

@ManyToMany
@JoinTable(
name = "post_likes",
joinColumns = @JoinColumn(name = "post_id"),
inverseJoinColumns = @JoinColumn(name = "user_id"))
private Set<User> likedByUsers = new HashSet<>();

@ManyToMany
@JoinTable(
name = "post_bookmarks",
joinColumns = @JoinColumn(name = "post_id"),
inverseJoinColumns = @JoinColumn(name = "user_id"))
private Set<User> bookmarkedByUsers = new HashSet<>();

@Column(nullable = true)
private String imageUrl;
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ public interface PostRepository extends JpaRepository<Post, Long> {
@Query("SELECT DISTINCT p FROM Post p JOIN p.tags t WHERE t.name IN :tagNames")
List<Post> findPostsByTags(@Param("tagNames") Set<String> tagNames);

List<Post> findByBookmarkedByUsersContaining(User user);

@Query("SELECT p FROM Post p " +
"LEFT JOIN p.tags t " +
"LEFT JOIN p.trainingProgram tp " +
Expand All @@ -24,5 +26,4 @@ public interface PostRepository extends JpaRepository<Post, Long> {
"LOWER(tp.title) LIKE LOWER(CONCAT('%', :query, '%')) OR " +
"LOWER(p.user.username) LIKE LOWER(CONCAT('%', :query, '%'))")
List<Post> search(@Param("query") String query);

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.group7.demo.dtos.PostRequest;
import com.group7.demo.dtos.PostResponse;
import com.group7.demo.dtos.mapper.Mapper;
import com.group7.demo.exceptions.UnauthorizedException;
import com.group7.demo.models.*;
import com.group7.demo.repository.PostRepository;
import com.group7.demo.repository.TagRepository;
Expand All @@ -12,6 +13,8 @@
import jakarta.transaction.Transactional;
import jakarta.servlet.http.HttpServletRequest;
import lombok.AllArgsConstructor;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
Expand Down Expand Up @@ -64,27 +67,27 @@ public PostResponse createPost(PostRequest postRequest, HttpServletRequest reque

Post savedPost = postRepository.save(post);

return mapper.mapToPostResponse(savedPost);
return mapper.mapToPostResponse(savedPost, request);
}

@Transactional
public List<PostResponse> getAllPosts() {
public List<PostResponse> getAllPosts(HttpServletRequest request) {
List<Post> posts = postRepository.findAll();

return posts.stream()
.map(mapper::mapToPostResponse)
.map(post -> mapper.mapToPostResponse(post, request))
.collect(Collectors.toList());
}

@Transactional
public List<PostResponse> getPostsByUser(String username) {
public List<PostResponse> getPostsByUser(String username, HttpServletRequest request) {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new EntityNotFoundException("User not found with username: " + username));

List<Post> posts = postRepository.findByUser(user);

return posts.stream()
.map(mapper::mapToPostResponse)
.map(post -> mapper.mapToPostResponse(post, request))
.collect(Collectors.toList());
}

Expand Down Expand Up @@ -116,20 +119,80 @@ public void deletePost(Long postId, HttpServletRequest request) throws IllegalAc
}

@Transactional
public List<PostResponse> getPostsByTags(Set<String> tagNames) {
public List<PostResponse> getPostsByTags(Set<String> tagNames, HttpServletRequest request) {
List<Post> posts = postRepository.findPostsByTags(tagNames);
return posts.stream()
.map(mapper::mapToPostResponse)
.map(post -> mapper.mapToPostResponse(post, request))
.collect(Collectors.toList());
}

@Transactional
public List<PostResponse> getRandomPosts(int count) {
public List<PostResponse> getRandomPosts(int count, HttpServletRequest request) {
List<Post> allPosts = postRepository.findAll();
Collections.shuffle(allPosts);
return allPosts.stream()
.limit(count)
.map(mapper::mapToPostResponse)
.map(post -> mapper.mapToPostResponse(post, request))
.collect(Collectors.toList());
}

private User validateAuthenticatedUser(HttpServletRequest request) {
User user = authenticationService.getAuthenticatedUserInternal(request);
if (user == null) {
throw new UnauthorizedException("You must be logged in to perform this action");
}
return user;
}

@Transactional
public void likePost(Long postId, HttpServletRequest request) {
User user = validateAuthenticatedUser(request);
Post post = postRepository.findById(postId)
.orElseThrow(() -> new EntityNotFoundException("Post not found"));

if (!post.getLikedByUsers().contains(user)) {
post.getLikedByUsers().add(user);
postRepository.save(post);
}
}

@Transactional
public void unlikePost(Long postId, HttpServletRequest request) {
User user = validateAuthenticatedUser(request);
Post post = postRepository.findById(postId)
.orElseThrow(() -> new EntityNotFoundException("Post not found"));

post.getLikedByUsers().remove(user);
postRepository.save(post);
}

@Transactional
public void bookmarkPost(Long postId, HttpServletRequest request) {
User user = validateAuthenticatedUser(request);
Post post = postRepository.findById(postId)
.orElseThrow(() -> new EntityNotFoundException("Post not found"));

if (!post.getBookmarkedByUsers().contains(user)) {
post.getBookmarkedByUsers().add(user);
postRepository.save(post);
}
}

@Transactional
public void unbookmarkPost(Long postId, HttpServletRequest request) {
User user = validateAuthenticatedUser(request);
Post post = postRepository.findById(postId)
.orElseThrow(() -> new EntityNotFoundException("Post not found"));

post.getBookmarkedByUsers().remove(user);
postRepository.save(post);
}

@Transactional
public List<PostResponse> getBookmarkedPosts(HttpServletRequest request) {
User user = validateAuthenticatedUser(request);
return postRepository.findByBookmarkedByUsersContaining(user).stream()
.map(post -> mapper.mapToPostResponse(post, request))
.collect(Collectors.toList());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public UserService(UserRepository userRepository,
}

@Transactional
public UserProfileResponse getUserProfile(String username) throws Exception {
public UserProfileResponse getUserProfile(String username, HttpServletRequest request) throws Exception {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new Exception("User not found"));

Expand All @@ -47,7 +47,7 @@ public UserProfileResponse getUserProfile(String username) throws Exception {
.map(User::getUsername)
.collect(Collectors.toSet());

List<PostResponse> posts = postService.getPostsByUser(user.getUsername());
List<PostResponse> posts = postService.getPostsByUser(user.getUsername(), request);
List<TrainingProgramResponse> trainingPrograms = trainingProgramService.getTrainingProgramByTrainer(user.getUsername());
List<UserTrainingProgramResponse> joinedPrograms = trainingProgramService.getJoinedTrainingPrograms(user.getUsername());

Expand Down

0 comments on commit f959a5f

Please sign in to comment.