Skip to content

Commit 89b2765

Browse files
committed
Consolidate user friendship into dto to remove extra api call
1 parent 93cabd1 commit 89b2765

4 files changed

Lines changed: 170 additions & 3 deletions

File tree

src/main/java/com/danielagapov/spawn/user/api/UserController.java

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,14 +91,24 @@ public ResponseEntity<List<MinimalFriendDTO>> getUserFriendsMinimal(@PathVariabl
9191
}
9292

9393
// full path: /api/v1/users/{id}
94+
// Optional query parameter: requestingUserId - when provided, includes relationship status in response
9495
@GetMapping("{id}")
95-
public ResponseEntity<BaseUserDTO> getUser(@PathVariable UUID id) {
96+
public ResponseEntity<BaseUserDTO> getUser(
97+
@PathVariable UUID id,
98+
@RequestParam(required = false) UUID requestingUserId) {
9699
if (id == null) {
97100
logger.error("Invalid parameter: user ID is null");
98101
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
99102
}
100103
try {
101-
return new ResponseEntity<>(userService.getBaseUserById(id), HttpStatus.OK);
104+
BaseUserDTO user;
105+
if (requestingUserId != null) {
106+
// Include relationship status when requestingUserId is provided
107+
user = userService.getBaseUserByIdWithRelationship(id, requestingUserId);
108+
} else {
109+
user = userService.getBaseUserById(id);
110+
}
111+
return new ResponseEntity<>(user, HttpStatus.OK);
102112
} catch (BaseNotFoundException e) {
103113
logger.error("User not found: " + LoggingUtils.formatUserIdInfo(id) + ": " + e.getMessage());
104114
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
@@ -281,6 +291,10 @@ public ResponseEntity<List<RecentlySpawnedUserDTO>> getRecentlySpawnedWithUsers(
281291
}
282292

283293
// full path: /api/v1/users/{userId}/is-friend/{potentialFriendId}
294+
// @deprecated Use GET /api/v1/users/{id}?requestingUserId={requestingUserId} instead.
295+
// The user endpoint now returns relationshipStatus and pendingFriendRequestId fields
296+
// when requestingUserId is provided, eliminating the need for this separate endpoint.
297+
@Deprecated(since = "1.0", forRemoval = true)
284298
@GetMapping("{userId}/is-friend/{potentialFriendId}")
285299
public ResponseEntity<Boolean> isUserFriendOfUser(
286300
@PathVariable UUID userId,

src/main/java/com/danielagapov/spawn/user/api/dto/BaseUserDTO.java

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.danielagapov.spawn.user.api.dto;
22

3+
import com.danielagapov.spawn.shared.util.UserRelationshipType;
34
import com.fasterxml.jackson.annotation.JsonCreator;
45
import com.fasterxml.jackson.annotation.JsonProperty;
56
import lombok.Getter;
@@ -15,19 +16,25 @@ public class BaseUserDTO extends AbstractUserDTO {
1516
private String profilePicture;
1617
private Boolean hasCompletedOnboarding;
1718
private String provider; // Auth provider: "google", "apple", or "email"
19+
private UserRelationshipType relationshipStatus; // Relationship status relative to requesting user
20+
private UUID pendingFriendRequestId; // ID of pending friend request if one exists
1821

1922
public BaseUserDTO(UUID id, String name, String email, String username, String bio, String profilePicture) {
2023
super(id, name, email, username, bio);
2124
this.profilePicture = profilePicture;
2225
this.hasCompletedOnboarding = false; // Default value for backward compatibility
2326
this.provider = null;
27+
this.relationshipStatus = null;
28+
this.pendingFriendRequestId = null;
2429
}
2530

2631
public BaseUserDTO(UUID id, String name, String email, String username, String bio, String profilePicture, Boolean hasCompletedOnboarding) {
2732
super(id, name, email, username, bio);
2833
this.profilePicture = profilePicture;
2934
this.hasCompletedOnboarding = hasCompletedOnboarding != null ? hasCompletedOnboarding : false;
3035
this.provider = null;
36+
this.relationshipStatus = null;
37+
this.pendingFriendRequestId = null;
3138
}
3239

3340
@JsonCreator
@@ -39,10 +46,32 @@ public BaseUserDTO(
3946
@JsonProperty("bio") String bio,
4047
@JsonProperty("profilePicture") String profilePicture,
4148
@JsonProperty("hasCompletedOnboarding") Boolean hasCompletedOnboarding,
42-
@JsonProperty("provider") String provider) {
49+
@JsonProperty("provider") String provider,
50+
@JsonProperty("relationshipStatus") UserRelationshipType relationshipStatus,
51+
@JsonProperty("pendingFriendRequestId") UUID pendingFriendRequestId) {
4352
super(id, name, email, username, bio);
4453
this.profilePicture = profilePicture;
4554
this.hasCompletedOnboarding = hasCompletedOnboarding != null ? hasCompletedOnboarding : false;
4655
this.provider = provider;
56+
this.relationshipStatus = relationshipStatus;
57+
this.pendingFriendRequestId = pendingFriendRequestId;
58+
}
59+
60+
// Backward-compatible constructor (without relationship fields)
61+
public BaseUserDTO(
62+
UUID id,
63+
String name,
64+
String email,
65+
String username,
66+
String bio,
67+
String profilePicture,
68+
Boolean hasCompletedOnboarding,
69+
String provider) {
70+
super(id, name, email, username, bio);
71+
this.profilePicture = profilePicture;
72+
this.hasCompletedOnboarding = hasCompletedOnboarding != null ? hasCompletedOnboarding : false;
73+
this.provider = provider;
74+
this.relationshipStatus = null;
75+
this.pendingFriendRequestId = null;
4776
}
4877
}

src/main/java/com/danielagapov/spawn/user/internal/services/IUserService.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,18 @@ public interface IUserService {
240240
*/
241241
BaseUserDTO getBaseUserById(UUID id);
242242

243+
/**
244+
* Retrieves a user as a BaseUserDTO by their unique identifier with relationship status.
245+
* When a requestingUserId is provided, the returned DTO includes the relationship status
246+
* between the requesting user and the target user.
247+
*
248+
* @param id the unique identifier of the user to retrieve
249+
* @param requestingUserId the unique identifier of the user making the request (optional)
250+
* @return BaseUserDTO object with relationship status if requestingUserId is provided
251+
* @throws com.danielagapov.spawn.Exceptions.Base.BaseNotFoundException if user doesn't exist
252+
*/
253+
BaseUserDTO getBaseUserByIdWithRelationship(UUID id, UUID requestingUserId);
254+
243255
/**
244256
* Updates a user's information with the provided update data.
245257
*

src/main/java/com/danielagapov/spawn/user/internal/services/UserService.java

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,16 @@
66
import com.danielagapov.spawn.user.api.dto.FriendUser.RecommendedFriendUserDTO;
77
import com.danielagapov.spawn.user.api.dto.Profile.UserProfileInfoDTO;
88
import com.danielagapov.spawn.shared.util.EntityType;
9+
import com.danielagapov.spawn.shared.util.UserRelationshipType;
910
import com.danielagapov.spawn.shared.util.UserStatus;
1011
import com.danielagapov.spawn.shared.exceptions.Base.BaseNotFoundException;
1112
import com.danielagapov.spawn.shared.exceptions.Base.BaseSaveException;
1213
import com.danielagapov.spawn.shared.exceptions.Base.BasesNotFoundException;
1314
import com.danielagapov.spawn.shared.exceptions.Logger.ILogger;
1415
import com.danielagapov.spawn.shared.util.UserMapper;
16+
import com.danielagapov.spawn.social.api.dto.CreateFriendRequestDTO;
1517
import com.danielagapov.spawn.social.internal.domain.Friendship;
18+
import com.danielagapov.spawn.social.internal.services.IFriendRequestService;
1619
import com.danielagapov.spawn.user.internal.domain.User;
1720
import com.danielagapov.spawn.social.internal.repositories.IFriendshipRepository;
1821
import com.danielagapov.spawn.auth.internal.repositories.IUserIdExternalIdMapRepository;
@@ -46,6 +49,7 @@ public class UserService implements IUserService {
4649
private final ILogger logger;
4750
private final IUserSearchQueryService userSearchQueryService;
4851
private final IUserFriendshipQueryService friendshipQueryService;
52+
private final IFriendRequestService friendRequestService;
4953
private final CacheManager cacheManager;
5054
private final ApplicationEventPublisher eventPublisher;
5155
private final IUserIdExternalIdMapRepository userIdExternalIdMapRepository;
@@ -59,6 +63,7 @@ public UserService(IUserRepository repository,
5963
IS3Service s3Service, ILogger logger,
6064
IUserSearchQueryService userSearchQueryService,
6165
IUserFriendshipQueryService friendshipQueryService,
66+
IFriendRequestService friendRequestService,
6267
CacheManager cacheManager,
6368
ApplicationEventPublisher eventPublisher,
6469
IUserIdExternalIdMapRepository userIdExternalIdMapRepository) {
@@ -68,6 +73,7 @@ public UserService(IUserRepository repository,
6873
this.logger = logger;
6974
this.userSearchQueryService = userSearchQueryService;
7075
this.friendshipQueryService = friendshipQueryService;
76+
this.friendRequestService = friendRequestService;
7177
this.cacheManager = cacheManager;
7278
this.eventPublisher = eventPublisher;
7379
this.userIdExternalIdMapRepository = userIdExternalIdMapRepository;
@@ -434,6 +440,112 @@ public BaseUserDTO getBaseUserById(UUID id) {
434440
}
435441
}
436442

443+
@Override
444+
public BaseUserDTO getBaseUserByIdWithRelationship(UUID id, UUID requestingUserId) {
445+
try {
446+
User user = repository.findById(id)
447+
.orElseThrow(() -> new BaseNotFoundException(EntityType.User, id));
448+
449+
// Hide admin user from front-end
450+
if (isAdminUser(user)) {
451+
throw new BaseNotFoundException(EntityType.User, id);
452+
}
453+
454+
BaseUserDTO dto = UserMapper.toDTO(user);
455+
456+
// If requestingUserId is provided and different from the target user, determine relationship
457+
if (requestingUserId != null && !requestingUserId.equals(id)) {
458+
UserRelationshipType relationshipStatus = determineRelationshipStatus(requestingUserId, id);
459+
dto.setRelationshipStatus(relationshipStatus);
460+
461+
// Get pending friend request ID if applicable
462+
UUID pendingRequestId = getPendingFriendRequestId(requestingUserId, id, relationshipStatus);
463+
dto.setPendingFriendRequestId(pendingRequestId);
464+
}
465+
466+
return dto;
467+
} catch (Exception e) {
468+
logger.error("Error getting user with relationship: " + LoggingUtils.formatUserIdInfo(id) + ": " + e.getMessage());
469+
throw e;
470+
}
471+
}
472+
473+
/**
474+
* Determines the relationship status between the requesting user and a target user.
475+
*
476+
* @param requestingUserId The ID of the user making the request
477+
* @param targetUserId The ID of the target user
478+
* @return UserRelationshipType representing the current relationship status
479+
*/
480+
private UserRelationshipType determineRelationshipStatus(UUID requestingUserId, UUID targetUserId) {
481+
try {
482+
// Check if they are already friends
483+
if (friendshipQueryService.isUserFriendOfUser(requestingUserId, targetUserId)) {
484+
return UserRelationshipType.FRIEND;
485+
}
486+
487+
// Check for outgoing friend request (requesting user sent to target user)
488+
List<CreateFriendRequestDTO> outgoingRequests = friendRequestService.getSentFriendRequestsByUserId(requestingUserId);
489+
boolean hasOutgoingRequest = outgoingRequests.stream()
490+
.anyMatch(request -> request.getReceiverUserId().equals(targetUserId));
491+
492+
if (hasOutgoingRequest) {
493+
return UserRelationshipType.OUTGOING_FRIEND_REQUEST;
494+
}
495+
496+
// Check for incoming friend request (target user sent to requesting user)
497+
List<CreateFriendRequestDTO> incomingRequests = friendRequestService.getIncomingCreateFriendRequestsByUserId(requestingUserId);
498+
boolean hasIncomingRequest = incomingRequests.stream()
499+
.anyMatch(request -> request.getSenderUserId().equals(targetUserId));
500+
501+
if (hasIncomingRequest) {
502+
return UserRelationshipType.INCOMING_FRIEND_REQUEST;
503+
}
504+
505+
// Default to recommended friend if no existing relationship
506+
return UserRelationshipType.RECOMMENDED_FRIEND;
507+
508+
} catch (Exception e) {
509+
logger.error("Error determining relationship status between users " + requestingUserId + " and " + targetUserId + ": " + e.getMessage());
510+
return UserRelationshipType.RECOMMENDED_FRIEND;
511+
}
512+
}
513+
514+
/**
515+
* Gets the pending friend request ID if there is one between the users.
516+
*
517+
* @param requestingUserId The ID of the user making the request
518+
* @param targetUserId The ID of the target user
519+
* @param relationshipStatus The current relationship status
520+
* @return UUID of the pending friend request, or null if none exists
521+
*/
522+
private UUID getPendingFriendRequestId(UUID requestingUserId, UUID targetUserId, UserRelationshipType relationshipStatus) {
523+
try {
524+
if (relationshipStatus == UserRelationshipType.OUTGOING_FRIEND_REQUEST) {
525+
// Find the outgoing request ID
526+
List<CreateFriendRequestDTO> outgoingRequests = friendRequestService.getSentFriendRequestsByUserId(requestingUserId);
527+
return outgoingRequests.stream()
528+
.filter(request -> request.getReceiverUserId().equals(targetUserId))
529+
.map(CreateFriendRequestDTO::getId)
530+
.findFirst()
531+
.orElse(null);
532+
} else if (relationshipStatus == UserRelationshipType.INCOMING_FRIEND_REQUEST) {
533+
// Find the incoming request ID
534+
List<CreateFriendRequestDTO> incomingRequests = friendRequestService.getIncomingCreateFriendRequestsByUserId(requestingUserId);
535+
return incomingRequests.stream()
536+
.filter(request -> request.getSenderUserId().equals(targetUserId))
537+
.map(CreateFriendRequestDTO::getId)
538+
.findFirst()
539+
.orElse(null);
540+
}
541+
542+
return null;
543+
} catch (Exception e) {
544+
logger.error("Error getting pending friend request ID between users " + requestingUserId + " and " + targetUserId + ": " + e.getMessage());
545+
return null;
546+
}
547+
}
548+
437549
@Override
438550
public BaseUserDTO updateUser(UUID id, UserUpdateDTO updateDTO) {
439551
try {

0 commit comments

Comments
 (0)