Added email reset code and follow request.
This commit is contained in:
parent
bc1e2b4397
commit
3653fe697e
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>3.0.2</version>
|
||||
<version>3.0.5</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<groupId>nl.andrewlalis</groupId>
|
||||
|
@ -51,25 +51,25 @@
|
|||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-csv</artifactId>
|
||||
<version>1.9.0</version>
|
||||
<version>1.10.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- JWT dependencies -->
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-api</artifactId>
|
||||
<version>0.11.2</version>
|
||||
<version>0.11.5</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-impl</artifactId>
|
||||
<version>0.11.2</version>
|
||||
<version>0.11.5</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-jackson</artifactId> <!-- or jjwt-gson if Gson is preferred -->
|
||||
<version>0.11.2</version>
|
||||
<version>0.11.5</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
|
@ -82,7 +82,7 @@
|
|||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<version>5.2.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/com.h2database/h2 -->
|
||||
|
|
|
@ -15,6 +15,11 @@ import org.springframework.web.filter.OncePerRequestFilter;
|
|||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* A filter that performs token authentication on incoming requests, and if a
|
||||
* user's token is valid, sets the security context's authentication to a new
|
||||
* instance of {@link TokenAuthentication}.
|
||||
*/
|
||||
@Component
|
||||
public class TokenAuthenticationFilter extends OncePerRequestFilter {
|
||||
private final TokenService tokenService;
|
||||
|
|
|
@ -53,6 +53,18 @@ public class UserController {
|
|||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
@PostMapping(path = "/auth/me/email-reset-code")
|
||||
public ResponseEntity<Void> generateEmailResetCode(@AuthenticationPrincipal User user, @RequestBody EmailUpdatePayload payload) {
|
||||
userService.generateEmailResetCode(user.getId(), payload);
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
@PostMapping(path = "/auth/me/email")
|
||||
public ResponseEntity<Void> updateMyEmail(@AuthenticationPrincipal User user, @RequestParam String code) {
|
||||
userService.updateEmail(user.getId(), code);
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
@GetMapping(path = "/auth/me/personal-details")
|
||||
public UserPersonalDetailsResponse getMyPersonalDetails(@AuthenticationPrincipal User user) {
|
||||
return userService.getPersonalDetails(user.getId());
|
||||
|
@ -85,9 +97,8 @@ public class UserController {
|
|||
}
|
||||
|
||||
@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();
|
||||
public UserFollowResponse followUser(@AuthenticationPrincipal User myUser, @PathVariable String userId) {
|
||||
return userService.followUser(myUser.getId(), userId);
|
||||
}
|
||||
|
||||
@DeleteMapping(path = "/auth/users/{userId}/followers")
|
||||
|
@ -96,13 +107,23 @@ public class UserController {
|
|||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
@PostMapping(path = "/auth/me/follow-requests/{followRequestId}")
|
||||
public ResponseEntity<Void> respondToFollowRequest(
|
||||
@AuthenticationPrincipal User myUser,
|
||||
@PathVariable long followRequestId,
|
||||
@RequestBody UserFollowRequestApproval payload
|
||||
) {
|
||||
userService.respondToFollowRequest(myUser.getId(), followRequestId, payload.approve());
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
@GetMapping(path = "/auth/me/followers")
|
||||
public Page<UserResponse> getFollowers(@AuthenticationPrincipal User user, Pageable pageable) {
|
||||
public Page<UserResponse> getMyFollowers(@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) {
|
||||
public Page<UserResponse> getMyFollowing(@AuthenticationPrincipal User user, Pageable pageable) {
|
||||
return userService.getFollowing(user.getId(), pageable);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
package nl.andrewlalis.gymboard_api.domains.auth.dao;
|
||||
|
||||
import nl.andrewlalis.gymboard_api.domains.auth.model.EmailResetCode;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Modifying;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Repository
|
||||
public interface EmailResetCodeRepository extends JpaRepository<EmailResetCode, String> {
|
||||
@Modifying
|
||||
void deleteAllByCreatedAtBefore(LocalDateTime cutoff);
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package nl.andrewlalis.gymboard_api.domains.auth.dao;
|
||||
|
||||
import nl.andrewlalis.gymboard_api.domains.auth.model.UserFollowRequest;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Modifying;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Repository
|
||||
public interface UserFollowRequestRepository extends JpaRepository<UserFollowRequest, Long> {
|
||||
@Modifying
|
||||
void deleteAllByCreatedAtBefore(LocalDateTime cutoff);
|
||||
}
|
|
@ -17,4 +17,7 @@ public interface UserFollowingRepository extends JpaRepository<UserFollowing, Lo
|
|||
|
||||
Page<UserFollowing> findAllByFollowedUserOrderByCreatedAtDesc(User followedUser, Pageable pageable);
|
||||
Page<UserFollowing> findAllByFollowingUserOrderByCreatedAtDesc(User followingUser, Pageable pageable);
|
||||
|
||||
long countByFollowedUser(User followedUser);
|
||||
long countByFollowingUser(User followingUser);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
package nl.andrewlalis.gymboard_api.domains.auth.dto;
|
||||
|
||||
public record EmailUpdatePayload(String newEmail) {}
|
|
@ -0,0 +1,3 @@
|
|||
package nl.andrewlalis.gymboard_api.domains.auth.dto;
|
||||
|
||||
public record UserFollowRequestApproval(boolean approve) {}
|
|
@ -0,0 +1,24 @@
|
|||
package nl.andrewlalis.gymboard_api.domains.auth.dto;
|
||||
|
||||
/**
|
||||
* A response that's sent when a user requests to follow another.
|
||||
*/
|
||||
public record UserFollowResponse(
|
||||
String result
|
||||
) {
|
||||
private static final String RESULT_FOLLOWED = "FOLLOWED";
|
||||
private static final String RESULT_REQUESTED = "REQUESTED";
|
||||
private static final String RESULT_ALREADY_FOLLOWED = "ALREADY_FOLLOWED";
|
||||
|
||||
public static UserFollowResponse followed() {
|
||||
return new UserFollowResponse(RESULT_FOLLOWED);
|
||||
}
|
||||
|
||||
public static UserFollowResponse requested() {
|
||||
return new UserFollowResponse(RESULT_REQUESTED);
|
||||
}
|
||||
|
||||
public static UserFollowResponse alreadyFollowed() {
|
||||
return new UserFollowResponse(RESULT_ALREADY_FOLLOWED);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
package nl.andrewlalis.gymboard_api.domains.auth.model;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import org.hibernate.annotations.CreationTimestamp;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* A code that's sent to a user's new email address to confirm that they own
|
||||
* it. Once confirmed, the user's email address will be updated.
|
||||
*/
|
||||
@Table(name = "auth_email_reset_code")
|
||||
@Entity
|
||||
public class EmailResetCode {
|
||||
public static final Duration VALID_FOR = Duration.ofMinutes(30);
|
||||
|
||||
@Id
|
||||
@Column(nullable = false, updatable = false, length = 127)
|
||||
private String code;
|
||||
|
||||
@Column(nullable = false, updatable = false, unique = true)
|
||||
private String newEmail;
|
||||
|
||||
@CreationTimestamp
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@ManyToOne(optional = false, fetch = FetchType.LAZY)
|
||||
private User user;
|
||||
|
||||
public EmailResetCode() {}
|
||||
|
||||
public EmailResetCode(String code, String newEmail, User user) {
|
||||
this.code = code;
|
||||
this.newEmail = newEmail;
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getNewEmail() {
|
||||
return newEmail;
|
||||
}
|
||||
|
||||
public LocalDateTime getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public User getUser() {
|
||||
return user;
|
||||
}
|
||||
}
|
|
@ -6,6 +6,10 @@ import org.hibernate.annotations.CreationTimestamp;
|
|||
import java.time.Duration;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* A code that's sent to a user's email address to grant them access to change
|
||||
* their password without needing to log in.
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "auth_user_password_reset_code")
|
||||
public class PasswordResetCode {
|
||||
|
|
|
@ -6,6 +6,13 @@ import org.springframework.security.core.GrantedAuthority;
|
|||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* The authentication instance that's used to represent a user who has
|
||||
* authenticated with an API token (so most users).
|
||||
* @param user The user who authenticated. The user entity has its roles eagerly
|
||||
* loaded.
|
||||
* @param token The token that was used.
|
||||
*/
|
||||
public record TokenAuthentication(
|
||||
User user,
|
||||
String token
|
||||
|
|
|
@ -76,6 +76,10 @@ public class User {
|
|||
return email;
|
||||
}
|
||||
|
||||
public void setEmail(String email) {
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
public String getPasswordHash() {
|
||||
return passwordHash;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
package nl.andrewlalis.gymboard_api.domains.auth.model;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import org.hibernate.annotations.CreationTimestamp;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* A request that one user sends to ask to follow another user.
|
||||
*/
|
||||
@Table(
|
||||
name = "auth_user_follow_request",
|
||||
uniqueConstraints = @UniqueConstraint(columnNames = {"requesting_user_id", "user_to_follow_id"})
|
||||
)
|
||||
@Entity
|
||||
public class UserFollowRequest {
|
||||
public static final Duration VALID_FOR = Duration.ofDays(7);
|
||||
|
||||
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@CreationTimestamp
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@ManyToOne(optional = false, fetch = FetchType.LAZY)
|
||||
private User requestingUser;
|
||||
|
||||
@ManyToOne(optional = false, fetch = FetchType.LAZY)
|
||||
private User userToFollow;
|
||||
|
||||
@Column
|
||||
private Boolean approved;
|
||||
|
||||
@Column
|
||||
private LocalDateTime decidedAt;
|
||||
|
||||
public UserFollowRequest() {}
|
||||
|
||||
public UserFollowRequest(User requestingUser, User userToFollow) {
|
||||
this.requestingUser = requestingUser;
|
||||
this.userToFollow = userToFollow;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public LocalDateTime getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public User getRequestingUser() {
|
||||
return requestingUser;
|
||||
}
|
||||
|
||||
public User getUserToFollow() {
|
||||
return userToFollow;
|
||||
}
|
||||
|
||||
public Boolean getApproved() {
|
||||
return approved;
|
||||
}
|
||||
|
||||
public LocalDateTime getDecidedAt() {
|
||||
return decidedAt;
|
||||
}
|
||||
|
||||
public void setApproved(boolean approved) {
|
||||
this.approved = approved;
|
||||
this.decidedAt = LocalDateTime.now();
|
||||
}
|
||||
}
|
|
@ -5,6 +5,10 @@ import org.hibernate.annotations.CreationTimestamp;
|
|||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* A user report is submitted by one user to report inappropriate actions of
|
||||
* another user.
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "auth_user_report")
|
||||
public class UserReport {
|
||||
|
|
|
@ -28,6 +28,8 @@ import java.time.LocalDateTime;
|
|||
import java.util.Random;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static nl.andrewlalis.gymboard_api.util.DataUtils.findByIdOrThrow;
|
||||
|
||||
@Service
|
||||
public class UserService {
|
||||
private static final Logger log = LoggerFactory.getLogger(UserService.class);
|
||||
|
@ -37,7 +39,9 @@ public class UserService {
|
|||
private final UserPreferencesRepository userPreferencesRepository;
|
||||
private final UserActivationCodeRepository activationCodeRepository;
|
||||
private final PasswordResetCodeRepository passwordResetCodeRepository;
|
||||
private final EmailResetCodeRepository emailResetCodeRepository;
|
||||
private final UserFollowingRepository userFollowingRepository;
|
||||
private final UserFollowRequestRepository followRequestRepository;
|
||||
private final UserAccessService userAccessService;
|
||||
private final ULID ulid;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
|
@ -52,7 +56,10 @@ public class UserService {
|
|||
UserPreferencesRepository userPreferencesRepository,
|
||||
UserActivationCodeRepository activationCodeRepository,
|
||||
PasswordResetCodeRepository passwordResetCodeRepository,
|
||||
UserFollowingRepository userFollowingRepository, UserAccessService userAccessService, ULID ulid,
|
||||
EmailResetCodeRepository emailResetCodeRepository, UserFollowingRepository userFollowingRepository,
|
||||
UserFollowRequestRepository followRequestRepository,
|
||||
UserAccessService userAccessService,
|
||||
ULID ulid,
|
||||
PasswordEncoder passwordEncoder,
|
||||
JavaMailSender mailSender
|
||||
) {
|
||||
|
@ -61,7 +68,9 @@ public class UserService {
|
|||
this.userPreferencesRepository = userPreferencesRepository;
|
||||
this.activationCodeRepository = activationCodeRepository;
|
||||
this.passwordResetCodeRepository = passwordResetCodeRepository;
|
||||
this.emailResetCodeRepository = emailResetCodeRepository;
|
||||
this.userFollowingRepository = userFollowingRepository;
|
||||
this.followRequestRepository = followRequestRepository;
|
||||
this.userAccessService = userAccessService;
|
||||
this.ulid = ulid;
|
||||
this.passwordEncoder = passwordEncoder;
|
||||
|
@ -218,6 +227,59 @@ public class UserService {
|
|||
userRepository.save(user);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void generateEmailResetCode(String id, EmailUpdatePayload payload) {
|
||||
User user = findByIdOrThrow(id, userRepository);
|
||||
if (userRepository.existsByEmail(payload.newEmail())) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Email is taken.");
|
||||
}
|
||||
EmailResetCode emailResetCode = emailResetCodeRepository.save(new EmailResetCode(
|
||||
StringGenerator.randomString(127, StringGenerator.Alphabet.ALPHANUMERIC),
|
||||
payload.newEmail(),
|
||||
user
|
||||
));
|
||||
String emailContent = String.format(
|
||||
"""
|
||||
<p>Hello %s,</p>
|
||||
|
||||
<p>
|
||||
You've just requested to change your email from %s to this email address.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Please click enter this code to reset your email: %s
|
||||
</p>
|
||||
""",
|
||||
user.getName(),
|
||||
user.getEmail(),
|
||||
emailResetCode.getCode()
|
||||
);
|
||||
MimeMessage msg = mailSender.createMimeMessage();
|
||||
try {
|
||||
MimeMessageHelper helper = new MimeMessageHelper(msg, "UTF-8");
|
||||
helper.setFrom("Gymboard <noreply@gymboard.io>");
|
||||
helper.setSubject("Gymboard Account Email Update");
|
||||
helper.setTo(emailResetCode.getNewEmail());
|
||||
helper.setText(emailContent, true);
|
||||
mailSender.send(msg);
|
||||
} catch (MessagingException e) {
|
||||
log.error("Error sending user email update email.", e);
|
||||
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void updateEmail(String userId, String code) {
|
||||
User user = findByIdOrThrow(userId, userRepository);
|
||||
EmailResetCode emailResetCode = findByIdOrThrow(code, emailResetCodeRepository);
|
||||
if (!emailResetCode.getUser().getId().equals(user.getId())) {
|
||||
throw new ResponseStatusException(HttpStatus.NOT_FOUND);
|
||||
}
|
||||
user.setEmail(emailResetCode.getNewEmail());
|
||||
userRepository.save(user);
|
||||
emailResetCodeRepository.delete(emailResetCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scheduled task that periodically removes all old authentication entities
|
||||
* so that they don't clutter up the system.
|
||||
|
@ -229,6 +291,10 @@ public class UserService {
|
|||
passwordResetCodeRepository.deleteAllByCreatedAtBefore(passwordResetCodeCutoff);
|
||||
LocalDateTime activationCodeCutoff = LocalDateTime.now().minus(UserActivationCode.VALID_FOR);
|
||||
activationCodeRepository.deleteAllByCreatedAtBefore(activationCodeCutoff);
|
||||
LocalDateTime followRequestCutoff = LocalDateTime.now().minus(UserFollowRequest.VALID_FOR);
|
||||
followRequestRepository.deleteAllByCreatedAtBefore(followRequestCutoff);
|
||||
LocalDateTime emailResetCodeCutoff = LocalDateTime.now().minus(EmailResetCode.VALID_FOR);
|
||||
emailResetCodeRepository.deleteAllByCreatedAtBefore(emailResetCodeCutoff);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
|
@ -280,30 +346,64 @@ public class UserService {
|
|||
return new UserPreferencesResponse(p);
|
||||
}
|
||||
|
||||
/**
|
||||
* When a user indicates that they'd like to follow another, this method is
|
||||
* invoked. If the person they want to follow is private, we create a new
|
||||
* {@link UserFollowRequest} that the person must approve. Otherwise, the
|
||||
* user just starts following the person. A 400 bad request is thrown if the
|
||||
* user tries to follow themselves.
|
||||
* @param followerId The id of the user that's trying to follow a user.
|
||||
* @param followedId The id of the user that's being followed.
|
||||
* @return A response that indicates the outcome.
|
||||
*/
|
||||
@Transactional
|
||||
public void followUser(String followerId, String followedId) {
|
||||
if (followerId.equals(followedId)) return;
|
||||
User follower = userRepository.findById(followerId).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
|
||||
User followed = userRepository.findById(followedId).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
|
||||
public UserFollowResponse followUser(String followerId, String followedId) {
|
||||
if (followerId.equals(followedId)) throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "You can't follow yourself.");
|
||||
User follower = findByIdOrThrow(followerId, userRepository);
|
||||
User followed = findByIdOrThrow(followedId, userRepository);
|
||||
|
||||
if (!userFollowingRepository.existsByFollowedUserAndFollowingUser(followed, follower)) {
|
||||
userFollowingRepository.save(new UserFollowing(followed, follower));
|
||||
if (followed.getPreferences().isAccountPrivate()) {
|
||||
userFollowingRepository.save(new UserFollowing(followed, follower));
|
||||
return UserFollowResponse.requested();
|
||||
} else {
|
||||
followRequestRepository.save(new UserFollowRequest(follower, followed));
|
||||
return UserFollowResponse.followed();
|
||||
}
|
||||
}
|
||||
return UserFollowResponse.alreadyFollowed();
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void unfollowUser(String followerId, String followedId) {
|
||||
if (followerId.equals(followedId)) return;
|
||||
User follower = userRepository.findById(followerId).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
|
||||
User followed = userRepository.findById(followedId).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
|
||||
User follower = findByIdOrThrow(followerId, userRepository);
|
||||
User followed = findByIdOrThrow(followedId, userRepository);
|
||||
|
||||
userFollowingRepository.deleteByFollowedUserAndFollowingUser(followed, follower);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void respondToFollowRequest(String userId, long followRequestId, boolean approved) {
|
||||
User followedUser = findByIdOrThrow(userId, userRepository);
|
||||
UserFollowRequest followRequest = findByIdOrThrow(followRequestId, followRequestRepository);
|
||||
if (!followRequest.getUserToFollow().getId().equals(followedUser.getId())) {
|
||||
throw new ResponseStatusException(HttpStatus.NOT_FOUND);
|
||||
}
|
||||
if (followRequest.getApproved() != null) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Request already decided.");
|
||||
}
|
||||
followRequest.setApproved(approved);
|
||||
followRequestRepository.save(followRequest);
|
||||
if (approved) {
|
||||
userFollowingRepository.save(new UserFollowing(followedUser, followRequest.getRequestingUser()));
|
||||
// TODO: Send notification to the user who requested to follow.
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public Page<UserResponse> getFollowers(String userId, Pageable pageable) {
|
||||
User user = userRepository.findById(userId)
|
||||
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
|
||||
User user = findByIdOrThrow(userId, userRepository);
|
||||
userAccessService.enforceUserAccess(user);
|
||||
return userFollowingRepository.findAllByFollowedUserOrderByCreatedAtDesc(user, pageable)
|
||||
.map(UserFollowing::getFollowingUser)
|
||||
|
@ -312,20 +412,25 @@ public class UserService {
|
|||
|
||||
@Transactional(readOnly = true)
|
||||
public Page<UserResponse> getFollowing(String userId, Pageable pageable) {
|
||||
User user = userRepository.findById(userId)
|
||||
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
|
||||
User user = findByIdOrThrow(userId, userRepository);
|
||||
userAccessService.enforceUserAccess(user);
|
||||
return userFollowingRepository.findAllByFollowingUserOrderByCreatedAtDesc(user, pageable)
|
||||
.map(UserFollowing::getFollowedUser)
|
||||
.map(UserResponse::new);
|
||||
}
|
||||
|
||||
public long getFollowerCount(String userId) {
|
||||
return userFollowingRepository.countByFollowedUser(findByIdOrThrow(userId, userRepository));
|
||||
}
|
||||
|
||||
public long getFollowingCount(String userId) {
|
||||
return userFollowingRepository.countByFollowingUser(findByIdOrThrow(userId, userRepository));
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public UserRelationshipResponse getRelationship(String user1Id, String user2Id) {
|
||||
User user1 = userRepository.findById(user1Id)
|
||||
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
|
||||
User user2 = userRepository.findById(user2Id)
|
||||
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
|
||||
User user1 = findByIdOrThrow(user1Id, userRepository);
|
||||
User user2 = findByIdOrThrow(user2Id, userRepository);
|
||||
userAccessService.enforceUserAccess(user1);
|
||||
boolean user1FollowingUser2 = userFollowingRepository.existsByFollowedUserAndFollowingUser(user2, user1);
|
||||
boolean user1FollowedByUser2 = userFollowingRepository.existsByFollowedUserAndFollowingUser(user1, user2);
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
package nl.andrewlalis.gymboard_api.util;
|
||||
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
public class DataUtils {
|
||||
/**
|
||||
* Finds an entity by its id, or throws a 404 not found exception.
|
||||
* @param id The id to look for.
|
||||
* @param repo The repository to search in.
|
||||
* @return The entity that was found.
|
||||
* @param <T> The entity type.
|
||||
* @param <ID> The id type.
|
||||
* @throws ResponseStatusException If the entity wasn't found.
|
||||
*/
|
||||
public static <T, ID> T findByIdOrThrow(ID id, CrudRepository<T, ID> repo) throws ResponseStatusException {
|
||||
return repo.findById(id).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue