diff --git a/gotcha-domain/src/main/java/gotcha_domain/user/User.java b/gotcha-domain/src/main/java/gotcha_domain/user/User.java index fc9be831..fb2d61f1 100644 --- a/gotcha-domain/src/main/java/gotcha_domain/user/User.java +++ b/gotcha-domain/src/main/java/gotcha_domain/user/User.java @@ -121,7 +121,7 @@ public class User extends BaseTimeEntity { private Set bugReports = new HashSet<>(); @Builder - public User(Long id, String email, String password, String nickname, Role role, String uuid){ + public User(Long id, String email, String password, String nickname, Role role, String uuid) { this.id = id; this.email = email; this.password = password; @@ -139,16 +139,22 @@ public void updateChatSettings(ChatOption chatOption, PrivateChatOption privateC this.privateChatOption = privateChatOption; } + @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) { + return true; + } + if (!(o instanceof User)) { + return false; + } User user = (User) o; - return id != null && id.equals(user.id); + + return id != null && id.equals(user.getId()); } @Override public int hashCode() { return (id != null) ? id.hashCode() : 0; } -} \ No newline at end of file +} diff --git a/gotcha/src/main/java/Gotcha/domain/friend/api/FriendApi.java b/gotcha/src/main/java/Gotcha/domain/friend/api/FriendApi.java index 2070dc6e..d98d7e3d 100644 --- a/gotcha/src/main/java/Gotcha/domain/friend/api/FriendApi.java +++ b/gotcha/src/main/java/Gotcha/domain/friend/api/FriendApi.java @@ -23,7 +23,7 @@ public interface FriendApi { @ApiResponse(responseCode = "200", description = "친구 목록 조회 성공", content = @Content(mediaType = "application/json", examples = { @ExampleObject(value = """ - + """) })) }) @@ -50,7 +50,7 @@ public interface FriendApi { })) }) ResponseEntity searchUser(@AuthenticationPrincipal SecurityUserDetails userDetails, - @RequestParam(value = "keyword") String keyword); + @RequestParam(value = "keyword") String keyword); @Operation(summary = "친구 신청 목록 조회", description = "친구 신청 목록 조회 API") @ApiResponses({ @@ -158,7 +158,8 @@ ResponseEntity requestFriend(@AuthenticationPrincipal SecurityUserDetails use })) }) ResponseEntity acceptFriend(@PathVariable(value = "id") Long friendRequestId, - @AuthenticationPrincipal SecurityUserDetails userDetails); + @AuthenticationPrincipal SecurityUserDetails userDetails) + throws NoSuchMethodException; @Operation(summary = "친구 요청 거절", description = "친구 요청 거절 API") @ApiResponses({ @@ -200,7 +201,7 @@ ResponseEntity rejectFriend(@PathVariable(value = "id") Long friendRequestId, @ApiResponse(responseCode = "204", description = "친구 삭제 성공", content = @Content(mediaType = "application/json", examples = { @ExampleObject(value = """ - + """) })), @ApiResponse(responseCode = "400", description = "친구가 아닌 사용자", @@ -216,4 +217,38 @@ ResponseEntity rejectFriend(@PathVariable(value = "id") Long friendRequestId, }) ResponseEntity deleteFriend(@PathVariable(value = "uuid") String uuid, @AuthenticationPrincipal SecurityUserDetails userDetails); + + @Operation(summary = "친구 따라가기", description = "친구 따라가기 클릭 시, 해당 친구가 속한 ROOM_ID 반환 기능 API") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "친구가 속한 방 id 정상 반환", + content = @Content(mediaType = "application/json", examples = { + @ExampleObject(value = """ + { + "roomId": "1111" + } + """) + })), + @ApiResponse(responseCode = "403", description = "친구 관계가 아니라 따라가기 기능 불가.", + content = @Content(mediaType = "application/json", examples = { + @ExampleObject(value = """ + { + "code": "FRIEND-403-002", + "status": "FORBIDDEN", + "message": "친구 관계가 아닙니다. 친구 추가 후 다시 시도해주세요." + } + """) + })), + @ApiResponse(responseCode = "404", description = "친구가 방에 속해있지 않음.", + content = @Content(mediaType = "application/json", examples = { + @ExampleObject(value = """ + { + "code": "FRIEND-404-002", + "status": "NOT_FOUND", + "message": "친구가 방에 속해있지 않습니다." + } + """) + })) + }) + ResponseEntity followingFriend(@PathVariable(value = "uuid") String friendUuid, + @AuthenticationPrincipal SecurityUserDetails securityUserDetails); } diff --git a/gotcha/src/main/java/Gotcha/domain/friend/controller/FriendController.java b/gotcha/src/main/java/Gotcha/domain/friend/controller/FriendController.java index 82246206..15f6f456 100644 --- a/gotcha/src/main/java/Gotcha/domain/friend/controller/FriendController.java +++ b/gotcha/src/main/java/Gotcha/domain/friend/controller/FriendController.java @@ -22,7 +22,7 @@ @RestController @RequiredArgsConstructor @RequestMapping("/api/v1/friends") -public class FriendController implements FriendApi{ +public class FriendController implements FriendApi { private final FriendService friendService; @GetMapping() @@ -32,7 +32,7 @@ public ResponseEntity getFriends(@AuthenticationPrincipal SecurityUserDetails @GetMapping("/search") public ResponseEntity searchUser(@AuthenticationPrincipal SecurityUserDetails userDetails, - @RequestParam(value = "keyword") String keyword) { + @RequestParam(value = "keyword") String keyword) { return ResponseEntity.ok(friendService.searchUser(userDetails.getId(), keyword)); } @@ -68,4 +68,12 @@ public ResponseEntity deleteFriend(@PathVariable(value = "uuid") String frien friendService.deleteFriend(userDetails.getId(), friendUuid); return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); } + + @PostMapping("/follow/{uuid}") + public ResponseEntity followingFriend(@PathVariable(value = "uuid") String friendUuid, + @AuthenticationPrincipal SecurityUserDetails userDetails) { + return ResponseEntity.ok(friendService.followFriend(userDetails.getUuid(), friendUuid)); + } + + } diff --git a/gotcha/src/main/java/Gotcha/domain/friend/dto/FriendFollowingRes.java b/gotcha/src/main/java/Gotcha/domain/friend/dto/FriendFollowingRes.java new file mode 100644 index 00000000..d2da3aa7 --- /dev/null +++ b/gotcha/src/main/java/Gotcha/domain/friend/dto/FriendFollowingRes.java @@ -0,0 +1,9 @@ +package Gotcha.domain.friend.dto; + +public record FriendFollowingRes( + String roomId +) { + public static FriendFollowingRes from(String roomId){ + return new FriendFollowingRes(roomId); + } +} diff --git a/gotcha/src/main/java/Gotcha/domain/friend/exception/FriendExceptionCode.java b/gotcha/src/main/java/Gotcha/domain/friend/exception/FriendExceptionCode.java index c1710937..7b5d7ae7 100644 --- a/gotcha/src/main/java/Gotcha/domain/friend/exception/FriendExceptionCode.java +++ b/gotcha/src/main/java/Gotcha/domain/friend/exception/FriendExceptionCode.java @@ -5,14 +5,15 @@ import org.springframework.http.HttpStatus; @AllArgsConstructor -public enum FriendExceptionCode implements ExceptionCode { +public enum FriendExceptionCode implements ExceptionCode { SELF_REQUEST_NOT_ALLOWED(HttpStatus.BAD_REQUEST, "FRIEND-400-001", "자기 자신에게는 친구 요청을 보낼 수 없습니다."), NOT_FRIEND(HttpStatus.BAD_REQUEST, "FRIEND-400-002", "해당 사용자와는 친구가 아닙니다."), + INVALID_REQUEST_ACCESS(HttpStatus.FORBIDDEN, "FRIEND-403-001", "해당 친구 요청에 접근 권한이 없습니다."), + FRIENDSHIP_REQUIRED(HttpStatus.FORBIDDEN, "FRIEND-403-002", "친구 관계가 아닙니다. 친구 추가 후 다시 시도해주세요."), + FRIEND_REQUEST_NOT_FOUND(HttpStatus.NOT_FOUND, "FRIEND-404-001", "친구 요청을 찾을 수 없습니다."), + FRIEND_NOT_IN_ROOM(HttpStatus.NOT_FOUND, "FRIEND-404-002", "친구가 방에 속해있지 않습니다."), FRIEND_REQUEST_ALREADY_EXISTS(HttpStatus.CONFLICT, "FRIEND-409-001", "이미 친구 요청을 보낸 상태입니다."), - FRIEND_REQUEST_NOT_FOUND(HttpStatus.NOT_FOUND, "FIREND-404-001", "친구 요청을 찾을 수 없습니다."), - ALREADY_FRIENDS(HttpStatus.CONFLICT, "FRIEND-409-002", "이미 친구인 사용자입니다."), - INVALID_REQUEST_ACCESS(HttpStatus.FORBIDDEN,"FRIEND-403-001", "해당 친구 요청에 접근 권한이 없습니다."); - + ALREADY_FRIENDS(HttpStatus.CONFLICT, "FRIEND-409-002", "이미 친구인 사용자입니다."); private final HttpStatus status; private final String code; diff --git a/gotcha/src/main/java/Gotcha/domain/friend/service/FriendService.java b/gotcha/src/main/java/Gotcha/domain/friend/service/FriendService.java index 6109db3b..661656ea 100644 --- a/gotcha/src/main/java/Gotcha/domain/friend/service/FriendService.java +++ b/gotcha/src/main/java/Gotcha/domain/friend/service/FriendService.java @@ -1,5 +1,6 @@ package Gotcha.domain.friend.service; +import Gotcha.domain.friend.dto.FriendFollowingRes; import Gotcha.domain.friend.dto.FriendReq; import Gotcha.domain.friend.dto.FriendRequestRes; import Gotcha.domain.friend.dto.FriendRes; @@ -12,6 +13,7 @@ import gotcha_domain.user.User; import gotcha_user.service.UserService; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import gotcha_common.util.RedisUtil; import org.springframework.transaction.annotation.Transactional; @@ -20,7 +22,9 @@ import socket_server.domain.friend.service.FriendSocketService; import java.util.List; +import socket_server.domain.room.service.RoomUserService; +@Slf4j @Service @RequiredArgsConstructor public class FriendService { @@ -29,6 +33,7 @@ public class FriendService { private final UserService userService; private final FriendSocketService friendSocketService; private final RedisUtil redisUtil; + private final RoomUserService roomUserService; private static final String FRIEND_CACHE_PREFIX = "user:"; @@ -169,4 +174,21 @@ public void deleteFriend(Long userId, String friendUuid) { friendSocketService.sendFriendAlert(user.getUuid(), friendUuid, user.getUuid(), FriendEventType.DELETE); } + + public FriendFollowingRes followFriend(String userUuid, String friendUuid) { + boolean isFriend = redisUtil.isSetMember("user:" + userUuid + ":friends", friendUuid); + + if (!isFriend) { + throw new CustomException(FriendExceptionCode.FRIENDSHIP_REQUIRED); + } + + String friendRoomId = roomUserService.findRoomIdByUserUuid(friendUuid); + + if (friendRoomId == null) { + throw new CustomException(FriendExceptionCode.FRIEND_NOT_IN_ROOM); + } + + return FriendFollowingRes.from(friendRoomId); + } + }