Added endpoints and logic for user following system.

This commit is contained in:
Andrew Lalis 2023-02-06 10:41:46 +01:00
parent e69008a582
commit 6097b0df7e
5 changed files with 91 additions and 12 deletions

View File

@ -1,12 +1,10 @@
package nl.andrewlalis.gymboard_api.domains.auth.controller; package nl.andrewlalis.gymboard_api.domains.auth.controller;
import nl.andrewlalis.gymboard_api.domains.auth.dto.*; 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.TokenService;
import nl.andrewlalis.gymboard_api.domains.auth.service.UserService; import nl.andrewlalis.gymboard_api.domains.auth.service.UserService;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@RestController @RestController

View File

@ -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.dto.*;
import nl.andrewlalis.gymboard_api.domains.auth.model.User; import nl.andrewlalis.gymboard_api.domains.auth.model.User;
import nl.andrewlalis.gymboard_api.domains.auth.service.UserService; 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.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController @RestController
public class UserController { public class UserController {
@ -66,4 +65,26 @@ public class UserController {
) { ) {
return userService.updatePreferences(user.getId(), payload); return userService.updatePreferences(user.getId(), payload);
} }
@PostMapping(path = "/auth/users/{userId}/followers")
public ResponseEntity<Void> followUser(@AuthenticationPrincipal User myUser, @PathVariable String userId) {
userService.followUser(myUser.getId(), userId);
return ResponseEntity.ok().build();
}
@DeleteMapping(path = "/auth/users/{userId}/followers")
public ResponseEntity<Void> unfollowUser(@AuthenticationPrincipal User myUser, @PathVariable String userId) {
userService.unfollowUser(myUser.getId(), userId);
return ResponseEntity.ok().build();
}
@GetMapping(path = "/auth/me/followers")
public Page<UserResponse> getFollowers(@AuthenticationPrincipal User user, Pageable pageable) {
return userService.getFollowers(user.getId(), pageable);
}
@GetMapping(path = "/auth/me/following")
public Page<UserResponse> getFollowing(@AuthenticationPrincipal User user, Pageable pageable) {
return userService.getFollowing(user.getId(), pageable);
}
} }

View File

@ -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<UserFollowing, Long> {
boolean existsByFollowedUserAndFollowingUser(User followedUser, User followingUser);
@Modifying
void deleteByFollowedUserAndFollowingUser(User followedUser, User followingUser);
Page<UserFollowing> findAllByFollowedUserOrderByCreatedAtDesc(User followedUser, Pageable pageable);
Page<UserFollowing> findAllByFollowingUserOrderByCreatedAtDesc(User followingUser, Pageable pageable);
}

View File

@ -10,7 +10,10 @@ import java.time.LocalDateTime;
* have impacts on the notifications that a user can receive. * have impacts on the notifications that a user can receive.
*/ */
@Entity @Entity
@Table(name = "auth_user_following") @Table(
name = "auth_user_following",
uniqueConstraints = @UniqueConstraint(columnNames = {"followed_user_id", "following_user_id"})
)
public class UserFollowing { public class UserFollowing {
@Id @Id
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)

View File

@ -5,15 +5,14 @@ import jakarta.mail.internet.MimeMessage;
import nl.andrewlalis.gymboard_api.domains.api.model.WeightUnit; import nl.andrewlalis.gymboard_api.domains.api.model.WeightUnit;
import nl.andrewlalis.gymboard_api.domains.auth.dao.*; import nl.andrewlalis.gymboard_api.domains.auth.dao.*;
import nl.andrewlalis.gymboard_api.domains.auth.dto.*; 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.*;
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.util.StringGenerator; import nl.andrewlalis.gymboard_api.util.StringGenerator;
import nl.andrewlalis.gymboard_api.util.ULID; import nl.andrewlalis.gymboard_api.util.ULID;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value; 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.http.HttpStatus;
import org.springframework.mail.javamail.JavaMailSender; import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper; import org.springframework.mail.javamail.MimeMessageHelper;
@ -38,6 +37,7 @@ public class UserService {
private final UserPreferencesRepository userPreferencesRepository; private final UserPreferencesRepository userPreferencesRepository;
private final UserActivationCodeRepository activationCodeRepository; private final UserActivationCodeRepository activationCodeRepository;
private final PasswordResetCodeRepository passwordResetCodeRepository; private final PasswordResetCodeRepository passwordResetCodeRepository;
private final UserFollowingRepository userFollowingRepository;
private final ULID ulid; private final ULID ulid;
private final PasswordEncoder passwordEncoder; private final PasswordEncoder passwordEncoder;
private final JavaMailSender mailSender; private final JavaMailSender mailSender;
@ -51,7 +51,7 @@ public class UserService {
UserPreferencesRepository userPreferencesRepository, UserPreferencesRepository userPreferencesRepository,
UserActivationCodeRepository activationCodeRepository, UserActivationCodeRepository activationCodeRepository,
PasswordResetCodeRepository passwordResetCodeRepository, PasswordResetCodeRepository passwordResetCodeRepository,
ULID ulid, UserFollowingRepository userFollowingRepository, ULID ulid,
PasswordEncoder passwordEncoder, PasswordEncoder passwordEncoder,
JavaMailSender mailSender JavaMailSender mailSender
) { ) {
@ -60,6 +60,7 @@ public class UserService {
this.userPreferencesRepository = userPreferencesRepository; this.userPreferencesRepository = userPreferencesRepository;
this.activationCodeRepository = activationCodeRepository; this.activationCodeRepository = activationCodeRepository;
this.passwordResetCodeRepository = passwordResetCodeRepository; this.passwordResetCodeRepository = passwordResetCodeRepository;
this.userFollowingRepository = userFollowingRepository;
this.ulid = ulid; this.ulid = ulid;
this.passwordEncoder = passwordEncoder; this.passwordEncoder = passwordEncoder;
this.mailSender = mailSender; this.mailSender = mailSender;
@ -276,4 +277,40 @@ public class UserService {
p = userPreferencesRepository.save(p); p = userPreferencesRepository.save(p);
return new UserPreferencesResponse(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<UserResponse> 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<UserResponse> 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);
}
} }