diff --git a/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/domains/auth/controller/AuthController.java b/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/domains/auth/controller/AuthController.java index 6dcf0bd..7622471 100644 --- a/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/domains/auth/controller/AuthController.java +++ b/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/domains/auth/controller/AuthController.java @@ -1,12 +1,10 @@ package nl.andrewlalis.gymboard_api.domains.auth.controller; import nl.andrewlalis.gymboard_api.domains.auth.dto.*; -import nl.andrewlalis.gymboard_api.domains.auth.model.User; import nl.andrewlalis.gymboard_api.domains.auth.service.TokenService; import nl.andrewlalis.gymboard_api.domains.auth.service.UserService; import org.springframework.http.ResponseEntity; import org.springframework.security.core.Authentication; -import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; @RestController diff --git a/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/domains/auth/controller/UserController.java b/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/domains/auth/controller/UserController.java index 090606d..d17c5ab 100644 --- a/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/domains/auth/controller/UserController.java +++ b/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/domains/auth/controller/UserController.java @@ -3,12 +3,11 @@ package nl.andrewlalis.gymboard_api.domains.auth.controller; import nl.andrewlalis.gymboard_api.domains.auth.dto.*; import nl.andrewlalis.gymboard_api.domains.auth.model.User; import nl.andrewlalis.gymboard_api.domains.auth.service.UserService; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; @RestController public class UserController { @@ -66,4 +65,26 @@ public class UserController { ) { return userService.updatePreferences(user.getId(), payload); } + + @PostMapping(path = "/auth/users/{userId}/followers") + public ResponseEntity followUser(@AuthenticationPrincipal User myUser, @PathVariable String userId) { + userService.followUser(myUser.getId(), userId); + return ResponseEntity.ok().build(); + } + + @DeleteMapping(path = "/auth/users/{userId}/followers") + public ResponseEntity unfollowUser(@AuthenticationPrincipal User myUser, @PathVariable String userId) { + userService.unfollowUser(myUser.getId(), userId); + return ResponseEntity.ok().build(); + } + + @GetMapping(path = "/auth/me/followers") + public Page getFollowers(@AuthenticationPrincipal User user, Pageable pageable) { + return userService.getFollowers(user.getId(), pageable); + } + + @GetMapping(path = "/auth/me/following") + public Page getFollowing(@AuthenticationPrincipal User user, Pageable pageable) { + return userService.getFollowing(user.getId(), pageable); + } } diff --git a/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/domains/auth/dao/UserFollowingRepository.java b/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/domains/auth/dao/UserFollowingRepository.java new file mode 100644 index 0000000..f48e434 --- /dev/null +++ b/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/domains/auth/dao/UserFollowingRepository.java @@ -0,0 +1,20 @@ +package nl.andrewlalis.gymboard_api.domains.auth.dao; + +import nl.andrewlalis.gymboard_api.domains.auth.model.User; +import nl.andrewlalis.gymboard_api.domains.auth.model.UserFollowing; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.stereotype.Repository; + +@Repository +public interface UserFollowingRepository extends JpaRepository { + boolean existsByFollowedUserAndFollowingUser(User followedUser, User followingUser); + + @Modifying + void deleteByFollowedUserAndFollowingUser(User followedUser, User followingUser); + + Page findAllByFollowedUserOrderByCreatedAtDesc(User followedUser, Pageable pageable); + Page findAllByFollowingUserOrderByCreatedAtDesc(User followingUser, Pageable pageable); +} diff --git a/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/domains/auth/model/UserFollowing.java b/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/domains/auth/model/UserFollowing.java index 42bb450..72246a7 100644 --- a/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/domains/auth/model/UserFollowing.java +++ b/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/domains/auth/model/UserFollowing.java @@ -10,7 +10,10 @@ import java.time.LocalDateTime; * have impacts on the notifications that a user can receive. */ @Entity -@Table(name = "auth_user_following") +@Table( + name = "auth_user_following", + uniqueConstraints = @UniqueConstraint(columnNames = {"followed_user_id", "following_user_id"}) +) public class UserFollowing { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) diff --git a/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/domains/auth/service/UserService.java b/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/domains/auth/service/UserService.java index e3d3b34..e505b88 100644 --- a/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/domains/auth/service/UserService.java +++ b/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/domains/auth/service/UserService.java @@ -5,15 +5,14 @@ import jakarta.mail.internet.MimeMessage; import nl.andrewlalis.gymboard_api.domains.api.model.WeightUnit; import nl.andrewlalis.gymboard_api.domains.auth.dao.*; import nl.andrewlalis.gymboard_api.domains.auth.dto.*; -import nl.andrewlalis.gymboard_api.domains.auth.model.PasswordResetCode; -import nl.andrewlalis.gymboard_api.domains.auth.model.User; -import nl.andrewlalis.gymboard_api.domains.auth.model.UserActivationCode; -import nl.andrewlalis.gymboard_api.domains.auth.model.UserPersonalDetails; +import nl.andrewlalis.gymboard_api.domains.auth.model.*; import nl.andrewlalis.gymboard_api.util.StringGenerator; import nl.andrewlalis.gymboard_api.util.ULID; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.http.HttpStatus; import org.springframework.mail.javamail.JavaMailSender; import org.springframework.mail.javamail.MimeMessageHelper; @@ -38,6 +37,7 @@ public class UserService { private final UserPreferencesRepository userPreferencesRepository; private final UserActivationCodeRepository activationCodeRepository; private final PasswordResetCodeRepository passwordResetCodeRepository; + private final UserFollowingRepository userFollowingRepository; private final ULID ulid; private final PasswordEncoder passwordEncoder; private final JavaMailSender mailSender; @@ -51,7 +51,7 @@ public class UserService { UserPreferencesRepository userPreferencesRepository, UserActivationCodeRepository activationCodeRepository, PasswordResetCodeRepository passwordResetCodeRepository, - ULID ulid, + UserFollowingRepository userFollowingRepository, ULID ulid, PasswordEncoder passwordEncoder, JavaMailSender mailSender ) { @@ -60,6 +60,7 @@ public class UserService { this.userPreferencesRepository = userPreferencesRepository; this.activationCodeRepository = activationCodeRepository; this.passwordResetCodeRepository = passwordResetCodeRepository; + this.userFollowingRepository = userFollowingRepository; this.ulid = ulid; this.passwordEncoder = passwordEncoder; this.mailSender = mailSender; @@ -276,4 +277,40 @@ public class UserService { p = userPreferencesRepository.save(p); return new UserPreferencesResponse(p); } + + @Transactional + public void followUser(String followerId, String followedId) { + User follower = userRepository.findById(followerId).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); + User followed = userRepository.findById(followedId).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); + + if (!userFollowingRepository.existsByFollowedUserAndFollowingUser(followed, follower)) { + userFollowingRepository.save(new UserFollowing(followed, follower)); + } + } + + @Transactional + public void unfollowUser(String followerId, String followedId) { + User follower = userRepository.findById(followerId).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); + User followed = userRepository.findById(followedId).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); + + userFollowingRepository.deleteByFollowedUserAndFollowingUser(followed, follower); + } + + @Transactional(readOnly = true) + public Page getFollowers(String userId, Pageable pageable) { + User user = userRepository.findById(userId) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); + return userFollowingRepository.findAllByFollowedUserOrderByCreatedAtDesc(user, pageable) + .map(UserFollowing::getFollowingUser) + .map(UserResponse::new); + } + + @Transactional(readOnly = true) + public Page getFollowing(String userId, Pageable pageable) { + User user = userRepository.findById(userId) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); + return userFollowingRepository.findAllByFollowingUserOrderByCreatedAtDesc(user, pageable) + .map(UserFollowing::getFollowedUser) + .map(UserResponse::new); + } }