From a26e358500be5649342a098844e251827771d5f1 Mon Sep 17 00:00:00 2001 From: janghyunjun Date: Mon, 14 Jul 2025 21:44:58 +0900 Subject: [PATCH 01/19] =?UTF-8?q?[test]=20=ED=86=B5=ED=95=A9=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20displayname=20=EC=88=98=EC=A0=95=20(#69)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thip/user/adapter/in/web/UserSignupControllerTest.java | 2 +- .../user/adapter/in/web/UserVerifyNicknameControllerTest.java | 2 +- .../user/adapter/in/web/UserViewAliasChoiceControllerTest.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/java/konkuk/thip/user/adapter/in/web/UserSignupControllerTest.java b/src/test/java/konkuk/thip/user/adapter/in/web/UserSignupControllerTest.java index 2d788ed70..ecf1d2172 100644 --- a/src/test/java/konkuk/thip/user/adapter/in/web/UserSignupControllerTest.java +++ b/src/test/java/konkuk/thip/user/adapter/in/web/UserSignupControllerTest.java @@ -31,7 +31,7 @@ @SpringBootTest @ActiveProfiles("test") @AutoConfigureMockMvc -@DisplayName("[통합] UserSignupController 테스트") +@DisplayName("[통합] 회원가입 api 테스트") class UserSignupControllerTest { @Autowired diff --git a/src/test/java/konkuk/thip/user/adapter/in/web/UserVerifyNicknameControllerTest.java b/src/test/java/konkuk/thip/user/adapter/in/web/UserVerifyNicknameControllerTest.java index 69b2a9c84..fe36a4bd5 100644 --- a/src/test/java/konkuk/thip/user/adapter/in/web/UserVerifyNicknameControllerTest.java +++ b/src/test/java/konkuk/thip/user/adapter/in/web/UserVerifyNicknameControllerTest.java @@ -30,7 +30,7 @@ @SpringBootTest @ActiveProfiles("test") @AutoConfigureMockMvc(addFilters = false) -@DisplayName("[통합] UserVerifyNicknameController 테스트") +@DisplayName("[통합] 닉네임 중복 검증 api 테스트") class UserVerifyNicknameControllerTest { @Autowired diff --git a/src/test/java/konkuk/thip/user/adapter/in/web/UserViewAliasChoiceControllerTest.java b/src/test/java/konkuk/thip/user/adapter/in/web/UserViewAliasChoiceControllerTest.java index 3f73a4610..ee5c153b3 100644 --- a/src/test/java/konkuk/thip/user/adapter/in/web/UserViewAliasChoiceControllerTest.java +++ b/src/test/java/konkuk/thip/user/adapter/in/web/UserViewAliasChoiceControllerTest.java @@ -30,7 +30,7 @@ @SpringBootTest @ActiveProfiles("test") @AutoConfigureMockMvc(addFilters = false) -@DisplayName("[통합] UserViewAliasChoiceController 테스트") +@DisplayName("[통합] 사용자 칭호 선택 api 테스트") class UserViewAliasChoiceControllerTest { @Autowired From a33c8e3754984ce4af8eeec5cc2347249b052b2d Mon Sep 17 00:00:00 2001 From: janghyunjun Date: Mon, 14 Jul 2025 21:45:33 +0900 Subject: [PATCH 02/19] =?UTF-8?q?[feat]=20BaseDomainEntity=EC=97=90=20stat?= =?UTF-8?q?us=20=EB=B3=80=EA=B2=BD=20=EB=A9=94=EC=84=9C=EB=93=9C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20(#69)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/konkuk/thip/common/entity/BaseDomainEntity.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/java/konkuk/thip/common/entity/BaseDomainEntity.java b/src/main/java/konkuk/thip/common/entity/BaseDomainEntity.java index d4948cb94..fd0f1257b 100644 --- a/src/main/java/konkuk/thip/common/entity/BaseDomainEntity.java +++ b/src/main/java/konkuk/thip/common/entity/BaseDomainEntity.java @@ -14,4 +14,12 @@ public class BaseDomainEntity { private LocalDateTime modifiedAt; private StatusType status; + + protected void changeStatus() { + if (this.status == StatusType.ACTIVE) { + this.status = StatusType.INACTIVE; + } else { + this.status = StatusType.ACTIVE; + } + } } From 0594ade631042c8822c2a8216b0e2f351be6cb95 Mon Sep 17 00:00:00 2001 From: janghyunjun Date: Mon, 14 Jul 2025 21:45:39 +0900 Subject: [PATCH 03/19] =?UTF-8?q?[feat]=20=ED=95=84=EC=9A=94=ED=95=9C=20?= =?UTF-8?q?=EC=97=90=EB=9F=AC=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80=20(#6?= =?UTF-8?q?9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../konkuk/thip/common/exception/code/ErrorCode.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main/java/konkuk/thip/common/exception/code/ErrorCode.java b/src/main/java/konkuk/thip/common/exception/code/ErrorCode.java index 8d363aa16..1c5d7d843 100644 --- a/src/main/java/konkuk/thip/common/exception/code/ErrorCode.java +++ b/src/main/java/konkuk/thip/common/exception/code/ErrorCode.java @@ -37,6 +37,16 @@ public enum ErrorCode implements ResponseCode { * 70000 : user error */ USER_NOT_FOUND(HttpStatus.NOT_FOUND, 70000, "존재하지 않는 USER 입니다."), + USER_ALREADY_FOLLOWED(HttpStatus.BAD_REQUEST, 70001, "이미 팔로우한 사용자입니다."), + + /** + * 75000 : follow error + */ + FOLLOW_NOT_FOUND(HttpStatus.NOT_FOUND, 750001, "존재하지 않는 FOLLOW 입니다."), + USER_ALREADY_UNFOLLOWED(HttpStatus.BAD_REQUEST, 75002, "이미 언팔로우한 사용자입니다."), + USER_CANNOT_FOLLOW_SELF(HttpStatus.BAD_REQUEST, 75003, "사용자는 자신을 팔로우할 수 없습니다."), + + /** * 80000 : book error From cd4742c3e056f95ee4119456a4da0c7be7116419 Mon Sep 17 00:00:00 2001 From: janghyunjun Date: Mon, 14 Jul 2025 21:45:52 +0900 Subject: [PATCH 04/19] =?UTF-8?q?[feat]=20Following=20=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=20=EB=8F=84=EB=A9=94=EC=9D=B8=20=EA=B7=9C=EC=B9=99=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(#69)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../konkuk/thip/user/domain/Following.java | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/main/java/konkuk/thip/user/domain/Following.java b/src/main/java/konkuk/thip/user/domain/Following.java index 8e50baad9..6ffa7efb2 100644 --- a/src/main/java/konkuk/thip/user/domain/Following.java +++ b/src/main/java/konkuk/thip/user/domain/Following.java @@ -1,9 +1,14 @@ package konkuk.thip.user.domain; import konkuk.thip.common.entity.BaseDomainEntity; +import konkuk.thip.common.entity.StatusType; +import konkuk.thip.common.exception.InvalidStateException; import lombok.Getter; import lombok.experimental.SuperBuilder; +import static konkuk.thip.common.exception.code.ErrorCode.USER_ALREADY_FOLLOWED; +import static konkuk.thip.common.exception.code.ErrorCode.USER_ALREADY_UNFOLLOWED; + @Getter @SuperBuilder public class Following extends BaseDomainEntity { @@ -13,4 +18,30 @@ public class Following extends BaseDomainEntity { private Long userId; private Long followingUserId; + + public static Following withoutId(Long userId, Long followingUserId) { + return Following.builder() + .userId(userId) + .followingUserId(followingUserId) + .status(StatusType.ACTIVE) + .build(); + } + + public boolean changeFollowingState(boolean isFollowRequest) { + StatusType currentStatus = getStatus(); + validateFollowingState(isFollowRequest, currentStatus); + + super.changeStatus(); + return isFollowRequest; + } + + private void validateFollowingState(boolean isFollowRequest, StatusType currentStatus) { + if (isFollowRequest && currentStatus == StatusType.ACTIVE) { // 팔로우 요청일 때 이미 팔로우 중인 경우 + throw new InvalidStateException(USER_ALREADY_FOLLOWED); + } + + if (!isFollowRequest && currentStatus == StatusType.INACTIVE) { // 언팔로우 요청일 때 이미 언팔로우 중인 경우 + throw new InvalidStateException(USER_ALREADY_UNFOLLOWED); + } + } } From bf2fda7559692aa7bfdefb86df0ad9829433c5e2 Mon Sep 17 00:00:00 2001 From: janghyunjun Date: Mon, 14 Jul 2025 21:47:32 +0900 Subject: [PATCH 05/19] =?UTF-8?q?[feat]=20Following=20=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=20repository=20=EC=A3=BC=EC=9E=85=20(#69)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../persistence/FollowingJpaRepository.java | 11 +++++++ .../persistence/FollowingQueryRepository.java | 9 ++++++ .../FollowingQueryRepositoryImpl.java | 29 +++++++++++++++++++ 3 files changed, 49 insertions(+) create mode 100644 src/main/java/konkuk/thip/user/adapter/out/persistence/FollowingJpaRepository.java create mode 100644 src/main/java/konkuk/thip/user/adapter/out/persistence/FollowingQueryRepository.java create mode 100644 src/main/java/konkuk/thip/user/adapter/out/persistence/FollowingQueryRepositoryImpl.java diff --git a/src/main/java/konkuk/thip/user/adapter/out/persistence/FollowingJpaRepository.java b/src/main/java/konkuk/thip/user/adapter/out/persistence/FollowingJpaRepository.java new file mode 100644 index 000000000..dd3ba397f --- /dev/null +++ b/src/main/java/konkuk/thip/user/adapter/out/persistence/FollowingJpaRepository.java @@ -0,0 +1,11 @@ +package konkuk.thip.user.adapter.out.persistence; + +import konkuk.thip.user.adapter.out.jpa.FollowingJpaEntity; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface FollowingJpaRepository extends JpaRepository, FollowingQueryRepository { + + +} diff --git a/src/main/java/konkuk/thip/user/adapter/out/persistence/FollowingQueryRepository.java b/src/main/java/konkuk/thip/user/adapter/out/persistence/FollowingQueryRepository.java new file mode 100644 index 000000000..9d4def40e --- /dev/null +++ b/src/main/java/konkuk/thip/user/adapter/out/persistence/FollowingQueryRepository.java @@ -0,0 +1,9 @@ +package konkuk.thip.user.adapter.out.persistence; + +import konkuk.thip.user.adapter.out.jpa.FollowingJpaEntity; + +import java.util.Optional; + +public interface FollowingQueryRepository { + Optional findByUserAndTargetUser(Long userId, Long targetUserId); +} diff --git a/src/main/java/konkuk/thip/user/adapter/out/persistence/FollowingQueryRepositoryImpl.java b/src/main/java/konkuk/thip/user/adapter/out/persistence/FollowingQueryRepositoryImpl.java new file mode 100644 index 000000000..a1680e16d --- /dev/null +++ b/src/main/java/konkuk/thip/user/adapter/out/persistence/FollowingQueryRepositoryImpl.java @@ -0,0 +1,29 @@ +package konkuk.thip.user.adapter.out.persistence; + +import com.querydsl.jpa.impl.JPAQueryFactory; +import konkuk.thip.user.adapter.out.jpa.FollowingJpaEntity; +import konkuk.thip.user.adapter.out.jpa.QFollowingJpaEntity; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +@RequiredArgsConstructor +public class FollowingQueryRepositoryImpl implements FollowingQueryRepository { + + private final JPAQueryFactory jpaQueryFactory; + + @Override + public Optional findByUserAndTargetUser(Long userId, Long targetUserId) { + QFollowingJpaEntity following = QFollowingJpaEntity.followingJpaEntity; + + FollowingJpaEntity followingJpaEntity = jpaQueryFactory + .selectFrom(following) + .where(following.userJpaEntity.userId.eq(userId) + .and(following.followingUserJpaEntity.userId.eq(targetUserId))) + .fetchOne(); + + return Optional.ofNullable(followingJpaEntity); + } +} From 12f1378895dc7526423c0c8a30fc38b1835c3df0 Mon Sep 17 00:00:00 2001 From: janghyunjun Date: Mon, 14 Jul 2025 21:48:43 +0900 Subject: [PATCH 06/19] =?UTF-8?q?[feat]=20Following=20=ED=85=8C=EC=9D=B4?= =?UTF-8?q?=EB=B8=94=20=EC=86=8C=ED=94=84=ED=8A=B8=20=EC=82=AD=EC=A0=9C=20?= =?UTF-8?q?=EC=A0=84=EB=9E=B5=EC=84=A4=EC=A0=95=20(#69)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../konkuk/thip/user/adapter/out/jpa/FollowingJpaEntity.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/konkuk/thip/user/adapter/out/jpa/FollowingJpaEntity.java b/src/main/java/konkuk/thip/user/adapter/out/jpa/FollowingJpaEntity.java index ef4f6b682..bcc4e9669 100644 --- a/src/main/java/konkuk/thip/user/adapter/out/jpa/FollowingJpaEntity.java +++ b/src/main/java/konkuk/thip/user/adapter/out/jpa/FollowingJpaEntity.java @@ -2,11 +2,14 @@ import jakarta.persistence.*; import konkuk.thip.common.entity.BaseJpaEntity; +import konkuk.thip.common.entity.StatusType; import lombok.*; +import org.hibernate.annotations.SQLDelete; @Entity @Table(name = "followings") @Getter +@SQLDelete(sql = "UPDATE followings SET status = 'INACTIVE' WHERE following_id = ?") @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor @Builder From 13b2738333b18d5133c3b4acd0e039a15eaca51f Mon Sep 17 00:00:00 2001 From: janghyunjun Date: Mon, 14 Jul 2025 21:48:52 +0900 Subject: [PATCH 07/19] =?UTF-8?q?[feat]=20Following=20=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=20adapter=20(#69)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../FollowingCommandPersistenceAdapter.java | 50 +++++++++++++++++++ .../port/out/FollowingCommandPort.java | 14 ++++++ 2 files changed, 64 insertions(+) create mode 100644 src/main/java/konkuk/thip/user/adapter/out/persistence/FollowingCommandPersistenceAdapter.java create mode 100644 src/main/java/konkuk/thip/user/application/port/out/FollowingCommandPort.java diff --git a/src/main/java/konkuk/thip/user/adapter/out/persistence/FollowingCommandPersistenceAdapter.java b/src/main/java/konkuk/thip/user/adapter/out/persistence/FollowingCommandPersistenceAdapter.java new file mode 100644 index 000000000..caae803cc --- /dev/null +++ b/src/main/java/konkuk/thip/user/adapter/out/persistence/FollowingCommandPersistenceAdapter.java @@ -0,0 +1,50 @@ +package konkuk.thip.user.adapter.out.persistence; + +import konkuk.thip.common.exception.EntityNotFoundException; +import konkuk.thip.user.adapter.out.jpa.FollowingJpaEntity; +import konkuk.thip.user.adapter.out.jpa.UserJpaEntity; +import konkuk.thip.user.adapter.out.mapper.FollowingMapper; +import konkuk.thip.user.application.port.out.FollowingCommandPort; +import konkuk.thip.user.domain.Following; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +import static konkuk.thip.common.exception.code.ErrorCode.FOLLOW_NOT_FOUND; +import static konkuk.thip.common.exception.code.ErrorCode.USER_NOT_FOUND; + +@Repository +@RequiredArgsConstructor +public class FollowingCommandPersistenceAdapter implements FollowingCommandPort { + + private final FollowingJpaRepository followingJpaRepository; + private final UserJpaRepository userJpaRepository; + + private final FollowingMapper followingMapper; + + @Override + public Optional findByUserIdAndTargetUserId(Long userId, Long targetUserId) { + Optional followingJpaEntity = followingJpaRepository.findByUserAndTargetUser(userId, targetUserId); + return followingJpaEntity.map(followingMapper::toDomainEntity); + } + + @Override + public void save(Following following) { // insert용 + UserJpaEntity userJpaEntity = userJpaRepository.findById(following.getUserId()).orElseThrow( + () -> new EntityNotFoundException(USER_NOT_FOUND)); + + UserJpaEntity targetUser = userJpaRepository.findById(following.getFollowingUserId()).orElseThrow( + () -> new EntityNotFoundException(USER_NOT_FOUND)); + + followingJpaRepository.save(followingMapper.toJpaEntity(userJpaEntity, targetUser)); + } + + @Override + public void updateStatus(Following following) { // 상태변경 용 + FollowingJpaEntity entity = followingJpaRepository.findByUserAndTargetUser(following.getUserId(), following.getFollowingUserId()) + .orElseThrow(() -> new EntityNotFoundException(FOLLOW_NOT_FOUND)); + + entity.setStatus(following.getStatus()); + } +} diff --git a/src/main/java/konkuk/thip/user/application/port/out/FollowingCommandPort.java b/src/main/java/konkuk/thip/user/application/port/out/FollowingCommandPort.java new file mode 100644 index 000000000..7f7ad8ac8 --- /dev/null +++ b/src/main/java/konkuk/thip/user/application/port/out/FollowingCommandPort.java @@ -0,0 +1,14 @@ +package konkuk.thip.user.application.port.out; + +import konkuk.thip.user.domain.Following; + +import java.util.Optional; + +public interface FollowingCommandPort { + + Optional findByUserIdAndTargetUserId(Long userId, Long targetUserId); + + void save(Following following); + + void updateStatus(Following following); +} From 8433d9e1f5e4b1a96192dad723e843ba132fff29 Mon Sep 17 00:00:00 2001 From: janghyunjun Date: Mon, 14 Jul 2025 21:48:56 +0900 Subject: [PATCH 08/19] =?UTF-8?q?[feat]=20Following=20=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=20controller=20(#69)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapter/in/web/UserCommandController.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/main/java/konkuk/thip/user/adapter/in/web/UserCommandController.java b/src/main/java/konkuk/thip/user/adapter/in/web/UserCommandController.java index 55ca9a027..fe2173e81 100644 --- a/src/main/java/konkuk/thip/user/adapter/in/web/UserCommandController.java +++ b/src/main/java/konkuk/thip/user/adapter/in/web/UserCommandController.java @@ -4,14 +4,20 @@ import jakarta.validation.Valid; import konkuk.thip.common.dto.BaseResponse; import konkuk.thip.common.security.annotation.Oauth2Id; +import konkuk.thip.common.security.annotation.UserId; import konkuk.thip.common.security.util.JwtUtil; import konkuk.thip.user.adapter.in.web.request.UserSignupRequest; import konkuk.thip.user.adapter.in.web.request.UserVerifyNicknameRequest; +import konkuk.thip.user.adapter.in.web.response.UserFollowResponse; import konkuk.thip.user.adapter.in.web.response.UserSignupResponse; import konkuk.thip.user.adapter.in.web.response.UserVerifyNicknameResponse; +import konkuk.thip.user.application.port.in.UserFollowUsecase; import konkuk.thip.user.application.port.in.UserSignupUseCase; import konkuk.thip.user.application.port.in.UserVerifyNicknameUseCase; +import konkuk.thip.user.adapter.in.web.request.UserFollowRequest; +import konkuk.thip.user.application.port.in.dto.UserFollowCommand; import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; @@ -25,6 +31,7 @@ public class UserCommandController { private final UserSignupUseCase userSignupUseCase; private final UserVerifyNicknameUseCase userVerifyNicknameUseCase; + private final UserFollowUsecase userFollowUsecase; private final JwtUtil jwtUtil; @PostMapping("/users/signup") @@ -43,4 +50,14 @@ public BaseResponse verifyNickname(@Valid @RequestBo userVerifyNicknameUseCase.isNicknameUnique(request.nickname())) ); } + + // 팔루우 상태 변경 : true -> 팔로우, false -> 언팔로우 + @PostMapping("/users/following/{followingUserId}") + public BaseResponse followUser(@UserId Long userId, + @PathVariable Long followingUserId, + @RequestBody @Valid UserFollowRequest request) { + return BaseResponse.ok(UserFollowResponse.of(userFollowUsecase.changeFollowingState( + UserFollowCommand.toCommand(userId, followingUserId, request.type()) + ))); + } } From 2d0ad52124dc64b819dc75832c59f6dade9a2e61 Mon Sep 17 00:00:00 2001 From: janghyunjun Date: Mon, 14 Jul 2025 21:49:02 +0900 Subject: [PATCH 09/19] =?UTF-8?q?[feat]=20Following=20=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=20dto=20(#69)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapter/in/web/request/UserFollowRequest.java | 10 ++++++++++ .../adapter/in/web/response/UserFollowResponse.java | 9 +++++++++ .../application/port/in/dto/UserFollowCommand.java | 11 +++++++++++ 3 files changed, 30 insertions(+) create mode 100644 src/main/java/konkuk/thip/user/adapter/in/web/request/UserFollowRequest.java create mode 100644 src/main/java/konkuk/thip/user/adapter/in/web/response/UserFollowResponse.java create mode 100644 src/main/java/konkuk/thip/user/application/port/in/dto/UserFollowCommand.java diff --git a/src/main/java/konkuk/thip/user/adapter/in/web/request/UserFollowRequest.java b/src/main/java/konkuk/thip/user/adapter/in/web/request/UserFollowRequest.java new file mode 100644 index 000000000..b4250c2e6 --- /dev/null +++ b/src/main/java/konkuk/thip/user/adapter/in/web/request/UserFollowRequest.java @@ -0,0 +1,10 @@ +package konkuk.thip.user.adapter.in.web.request; + +import jakarta.validation.constraints.NotNull; + +public record UserFollowRequest( + @NotNull(message = "type은 필수 파라미터입니다.") + Boolean type // true -> 팔로우, false -> 언팔로우 +) { + +} diff --git a/src/main/java/konkuk/thip/user/adapter/in/web/response/UserFollowResponse.java b/src/main/java/konkuk/thip/user/adapter/in/web/response/UserFollowResponse.java new file mode 100644 index 000000000..db34baaa1 --- /dev/null +++ b/src/main/java/konkuk/thip/user/adapter/in/web/response/UserFollowResponse.java @@ -0,0 +1,9 @@ +package konkuk.thip.user.adapter.in.web.response; + +public record UserFollowResponse( + boolean isFollowing +) { + public static UserFollowResponse of(boolean isFollowing) { + return new UserFollowResponse(isFollowing); + } +} diff --git a/src/main/java/konkuk/thip/user/application/port/in/dto/UserFollowCommand.java b/src/main/java/konkuk/thip/user/application/port/in/dto/UserFollowCommand.java new file mode 100644 index 000000000..c7344c12f --- /dev/null +++ b/src/main/java/konkuk/thip/user/application/port/in/dto/UserFollowCommand.java @@ -0,0 +1,11 @@ +package konkuk.thip.user.application.port.in.dto; + +public record UserFollowCommand( + Long userId, + Long targetUserId, + Boolean type // true -> 팔로우, false -> 언팔로우 +) { + public static UserFollowCommand toCommand(Long userId, Long targetUserId, Boolean type) { + return new UserFollowCommand(userId, targetUserId, type); + } +} From 52d4917ec4781bb7d9ac40afe2f1159ff3b84cbb Mon Sep 17 00:00:00 2001 From: janghyunjun Date: Mon, 14 Jul 2025 21:49:13 +0900 Subject: [PATCH 10/19] =?UTF-8?q?[feat]=20Following=20=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=EB=B0=8F=20=EC=9C=A0=EC=A6=88?= =?UTF-8?q?=EC=BC=80=EC=9D=B4=EC=8A=A4=20(#69)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../port/in/UserFollowUsecase.java | 8 +++ .../service/UserFollowService.java | 54 +++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 src/main/java/konkuk/thip/user/application/port/in/UserFollowUsecase.java create mode 100644 src/main/java/konkuk/thip/user/application/service/UserFollowService.java diff --git a/src/main/java/konkuk/thip/user/application/port/in/UserFollowUsecase.java b/src/main/java/konkuk/thip/user/application/port/in/UserFollowUsecase.java new file mode 100644 index 000000000..6f3aa9ed7 --- /dev/null +++ b/src/main/java/konkuk/thip/user/application/port/in/UserFollowUsecase.java @@ -0,0 +1,8 @@ +package konkuk.thip.user.application.port.in; + + +import konkuk.thip.user.application.port.in.dto.UserFollowCommand; + +public interface UserFollowUsecase { + Boolean changeFollowingState(UserFollowCommand followCommand); +} diff --git a/src/main/java/konkuk/thip/user/application/service/UserFollowService.java b/src/main/java/konkuk/thip/user/application/service/UserFollowService.java new file mode 100644 index 000000000..507459603 --- /dev/null +++ b/src/main/java/konkuk/thip/user/application/service/UserFollowService.java @@ -0,0 +1,54 @@ +package konkuk.thip.user.application.service; + +import konkuk.thip.common.exception.InvalidStateException; +import konkuk.thip.user.application.port.in.UserFollowUsecase; +import konkuk.thip.user.application.port.in.dto.UserFollowCommand; +import konkuk.thip.user.application.port.out.FollowingCommandPort; +import konkuk.thip.user.domain.Following; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Objects; +import java.util.Optional; + +import static konkuk.thip.common.exception.code.ErrorCode.USER_ALREADY_UNFOLLOWED; +import static konkuk.thip.common.exception.code.ErrorCode.USER_CANNOT_FOLLOW_SELF; + +@Service +@RequiredArgsConstructor +public class UserFollowService implements UserFollowUsecase { + + private final FollowingCommandPort followingCommandPort; + + @Override + @Transactional + public Boolean changeFollowingState(UserFollowCommand followCommand) { + Long userId = followCommand.userId(); + Long targetUserId = followCommand.targetUserId(); + Boolean type = followCommand.type(); + + validateParams(userId, targetUserId); + + Optional optionalFollowing = followingCommandPort.findByUserIdAndTargetUserId(userId, targetUserId); + + if (optionalFollowing.isPresent()) { // 이미 팔로우 관계가 존재하는 경우 + Following following = optionalFollowing.get(); + boolean isFollowing = following.changeFollowingState(type); + followingCommandPort.updateStatus(following); + return isFollowing; + } else { // 팔로우 관계가 존재하지 않는 경우 + if (!type) { + throw new InvalidStateException(USER_ALREADY_UNFOLLOWED); // 언팔로우 요청인데 팔로우 관계가 존재하지 않으므로 이미 언팔로우 상태 + } + followingCommandPort.save(Following.withoutId(userId, targetUserId)); + return true; // 새로 팔로우한 경우 + } + } + + private void validateParams(Long userId, Long targetUserId) { + if(Objects.equals(userId, targetUserId)) { + throw new InvalidStateException(USER_CANNOT_FOLLOW_SELF); + } + } +} From 155a795273bca8d28530525c4bbb9387f751f0a9 Mon Sep 17 00:00:00 2001 From: janghyunjun Date: Mon, 14 Jul 2025 22:30:11 +0900 Subject: [PATCH 11/19] =?UTF-8?q?[test]=20Following=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=EB=8B=A8=EC=9C=84=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thip/user/domain/FollowingTest.java | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 src/test/java/konkuk/thip/user/domain/FollowingTest.java diff --git a/src/test/java/konkuk/thip/user/domain/FollowingTest.java b/src/test/java/konkuk/thip/user/domain/FollowingTest.java new file mode 100644 index 000000000..c4426ad7d --- /dev/null +++ b/src/test/java/konkuk/thip/user/domain/FollowingTest.java @@ -0,0 +1,93 @@ +package konkuk.thip.user.domain; + +import konkuk.thip.common.entity.StatusType; +import konkuk.thip.common.exception.InvalidStateException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import static konkuk.thip.common.exception.code.ErrorCode.USER_ALREADY_FOLLOWED; +import static konkuk.thip.common.exception.code.ErrorCode.USER_ALREADY_UNFOLLOWED; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class FollowingTest { + + @Nested + @DisplayName("팔로우 요청") + class Follow { + + @Test + @DisplayName("inactive 상태에서 follow 요청 → active로 변경") + void follow_from_inactive() { + Following following = Following.builder() + .userId(1L) + .followingUserId(2L) + .status(StatusType.INACTIVE) + .build(); + + boolean result = following.changeFollowingState(true); + + assertThat(result).isTrue(); + assertThat(following.getStatus()).isEqualTo(StatusType.ACTIVE); + } + + @Test + @DisplayName("이미 active 상태에서 follow 요청 → 예외 발생") + void follow_from_active_should_throw() { + Following following = Following.builder() + .userId(1L) + .followingUserId(2L) + .status(StatusType.ACTIVE) + .build(); + + assertThatThrownBy(() -> following.changeFollowingState(true)) + .isInstanceOf(InvalidStateException.class) + .hasMessage(USER_ALREADY_FOLLOWED.getMessage()); + } + } + + @Nested + @DisplayName("언팔로우 요청") + class Unfollow { + + @Test + @DisplayName("active 상태에서 unfollow 요청 → inactive로 변경") + void unfollow_from_active() { + Following following = Following.builder() + .userId(1L) + .followingUserId(2L) + .status(StatusType.ACTIVE) + .build(); + + boolean result = following.changeFollowingState(false); + + assertThat(result).isFalse(); + assertThat(following.getStatus()).isEqualTo(StatusType.INACTIVE); + } + + @Test + @DisplayName("이미 inactive 상태에서 unfollow 요청 → 예외 발생") + void unfollow_from_inactive_should_throw() { + Following following = Following.builder() + .userId(1L) + .followingUserId(2L) + .status(StatusType.INACTIVE) + .build(); + + assertThatThrownBy(() -> following.changeFollowingState(false)) + .isInstanceOf(InvalidStateException.class) + .hasMessage(USER_ALREADY_UNFOLLOWED.getMessage()); + } + } + + @Test + @DisplayName("새로운 팔로우 생성 시 상태는 ACTIVE") + void create_following_should_be_active() { + Following following = Following.withoutId(1L, 2L); + + assertThat(following.getUserId()).isEqualTo(1L); + assertThat(following.getFollowingUserId()).isEqualTo(2L); + assertThat(following.getStatus()).isEqualTo(StatusType.ACTIVE); + } +} \ No newline at end of file From 4df0745ecfd0bd54031707a574bc1e4f5ea8cba2 Mon Sep 17 00:00:00 2001 From: janghyunjun Date: Mon, 14 Jul 2025 22:30:26 +0900 Subject: [PATCH 12/19] =?UTF-8?q?[test]=20=ED=8C=94=EB=A1=9C=EC=9E=89=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=EB=B3=80=EA=B2=BD=20=EC=84=9C=EB=B9=84?= =?UTF-8?q?=EC=8A=A4=20=EB=8B=A8=EC=9C=84=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/UserFollowServiceTest.java | 145 ++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 src/test/java/konkuk/thip/user/application/service/UserFollowServiceTest.java diff --git a/src/test/java/konkuk/thip/user/application/service/UserFollowServiceTest.java b/src/test/java/konkuk/thip/user/application/service/UserFollowServiceTest.java new file mode 100644 index 000000000..2cdc2b62d --- /dev/null +++ b/src/test/java/konkuk/thip/user/application/service/UserFollowServiceTest.java @@ -0,0 +1,145 @@ +package konkuk.thip.user.application.service; + +import konkuk.thip.common.exception.InvalidStateException; +import konkuk.thip.user.application.port.in.dto.UserFollowCommand; +import konkuk.thip.user.application.port.out.FollowingCommandPort; +import konkuk.thip.user.domain.Following; +import konkuk.thip.common.entity.StatusType; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; + +import java.util.Optional; + +import static konkuk.thip.common.exception.code.ErrorCode.USER_ALREADY_UNFOLLOWED; +import static konkuk.thip.common.exception.code.ErrorCode.USER_CANNOT_FOLLOW_SELF; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.*; + +class UserFollowServiceTest { + + private FollowingCommandPort followingCommandPort; + private UserFollowService userFollowService; + + @BeforeEach + void setUp() { + followingCommandPort = mock(FollowingCommandPort.class); + userFollowService = new UserFollowService(followingCommandPort); + } + + @Nested + @DisplayName("팔로우 요청(type = true)") + class Follow { + + @Test + @DisplayName("기존 inactive row가 존재하면 active로 변경") + void activate_existingFollowing() { + // given + Long userId = 1L, targetUserId = 2L; + Following inactiveFollowing = Following.builder() + .id(10L) + .userId(userId) + .followingUserId(targetUserId) + .status(StatusType.INACTIVE) + .build(); + + when(followingCommandPort.findByUserIdAndTargetUserId(userId, targetUserId)) + .thenReturn(Optional.of(inactiveFollowing)); + + UserFollowCommand command = new UserFollowCommand(userId, targetUserId, true); + + // when + Boolean result = userFollowService.changeFollowingState(command); + + // then + assertThat(result).isTrue(); + assertThat(inactiveFollowing.getStatus()).isEqualTo(StatusType.ACTIVE); + verify(followingCommandPort).updateStatus(inactiveFollowing); + } + + @Test + @DisplayName("팔로우 관계가 존재하지 않으면 새로 생성") + void create_newFollowing() { + // given + Long userId = 1L, targetUserId = 2L; + when(followingCommandPort.findByUserIdAndTargetUserId(userId, targetUserId)) + .thenReturn(Optional.empty()); + + UserFollowCommand command = new UserFollowCommand(userId, targetUserId, true); + + // when + Boolean result = userFollowService.changeFollowingState(command); + + // then + assertThat(result).isTrue(); + ArgumentCaptor captor = ArgumentCaptor.forClass(Following.class); + verify(followingCommandPort).save(captor.capture()); + + Following saved = captor.getValue(); + assertThat(saved.getUserId()).isEqualTo(userId); + assertThat(saved.getFollowingUserId()).isEqualTo(targetUserId); + assertThat(saved.getStatus()).isEqualTo(StatusType.ACTIVE); + } + } + + @Nested + @DisplayName("언팔로우 요청(type = false)") + class Unfollow { + + @Test + @DisplayName("active row가 존재하면 inactive로 변경") + void deactivate_existingFollowing() { + // given + Long userId = 1L, targetUserId = 2L; + Following activeFollowing = Following.builder() + .id(10L) + .userId(userId) + .followingUserId(targetUserId) + .status(StatusType.ACTIVE) + .build(); + + when(followingCommandPort.findByUserIdAndTargetUserId(userId, targetUserId)) + .thenReturn(Optional.of(activeFollowing)); + + UserFollowCommand command = new UserFollowCommand(userId, targetUserId, false); + + // when + Boolean result = userFollowService.changeFollowingState(command); + + // then + assertThat(result).isFalse(); + assertThat(activeFollowing.getStatus()).isEqualTo(StatusType.INACTIVE); + verify(followingCommandPort).updateStatus(activeFollowing); + } + + @Test + @DisplayName("언팔로우 요청인데 팔로우 관계가 없으면 예외 발생") + void unfollow_withoutRelation() { + // given + Long userId = 1L, targetUserId = 2L; + when(followingCommandPort.findByUserIdAndTargetUserId(userId, targetUserId)) + .thenReturn(Optional.empty()); + + UserFollowCommand command = new UserFollowCommand(userId, targetUserId, false); + + // when & then + assertThatThrownBy(() -> userFollowService.changeFollowingState(command)) + .isInstanceOf(InvalidStateException.class) + .hasMessageContaining(USER_ALREADY_UNFOLLOWED.getMessage()); + } + } + + @Test + @DisplayName("자기 자신을 팔로우하는 요청이면 예외 발생") + void cannot_follow_self() { + Long userId = 1L; + UserFollowCommand command = new UserFollowCommand(userId, userId, true); + + assertThatThrownBy(() -> userFollowService.changeFollowingState(command)) + .isInstanceOf(InvalidStateException.class) + .hasMessageContaining(USER_CANNOT_FOLLOW_SELF.getMessage()); + } +} \ No newline at end of file From c799eb78911d7e46a87c951174f780b66c65e4a5 Mon Sep 17 00:00:00 2001 From: janghyunjun Date: Mon, 14 Jul 2025 22:30:32 +0900 Subject: [PATCH 13/19] =?UTF-8?q?[test]=20=ED=8C=94=EB=A1=9C=EC=9E=89=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=EB=B3=80=EA=B2=BD=20api=20=ED=86=B5=ED=95=A9?= =?UTF-8?q?=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapter/in/web/UserFollowApiTest.java | 101 ++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 src/test/java/konkuk/thip/user/adapter/in/web/UserFollowApiTest.java diff --git a/src/test/java/konkuk/thip/user/adapter/in/web/UserFollowApiTest.java b/src/test/java/konkuk/thip/user/adapter/in/web/UserFollowApiTest.java new file mode 100644 index 000000000..eb6077e36 --- /dev/null +++ b/src/test/java/konkuk/thip/user/adapter/in/web/UserFollowApiTest.java @@ -0,0 +1,101 @@ +package konkuk.thip.user.adapter.in.web; + +import com.fasterxml.jackson.databind.ObjectMapper; +import konkuk.thip.common.util.TestEntityFactory; +import konkuk.thip.user.adapter.out.jpa.AliasJpaEntity; +import konkuk.thip.user.adapter.out.jpa.FollowingJpaEntity; +import konkuk.thip.user.adapter.out.jpa.UserJpaEntity; +import konkuk.thip.user.adapter.out.jpa.UserRole; +import konkuk.thip.user.adapter.out.persistence.AliasJpaRepository; +import konkuk.thip.user.adapter.out.persistence.FollowingJpaRepository; +import konkuk.thip.user.adapter.out.persistence.UserJpaRepository; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.web.servlet.MockMvc; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@ActiveProfiles("test") +@AutoConfigureMockMvc(addFilters = false) +@DisplayName("[통합] 팔로잉 상태 변경 API 통합 테스트") +class UserFollowApiTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @Autowired + private UserJpaRepository userJpaRepository; + + @Autowired + private AliasJpaRepository aliasJpaRepository; + + @Autowired + private FollowingJpaRepository followingJpaRepository; + + @AfterEach + void tearDown() { + followingJpaRepository.deleteAll(); + userJpaRepository.deleteAll(); + aliasJpaRepository.deleteAll(); + } + + @Test + @DisplayName("팔로우 요청 후 언팔로우 요청 시 상태가 변경되는지 확인한다.") + void changeFollowingState_follow_then_unfollow() throws Exception { + // 사용자 2명 저장 + AliasJpaEntity alias = aliasJpaRepository.save(TestEntityFactory.createScienceAlias()); + + UserJpaEntity user = userJpaRepository.save(UserJpaEntity.builder() + .nickname("user100") + .imageUrl("http://image") + .oauth2Id("oauth2_user100") + .role(UserRole.USER) + .aliasForUserJpaEntity(alias) + .build()); + + UserJpaEntity target = userJpaRepository.save(UserJpaEntity.builder() + .nickname("user200") + .imageUrl("http://image") + .oauth2Id("oauth2_user200") + .role(UserRole.USER) + .aliasForUserJpaEntity(alias) + .build()); + + // 팔로우 요청 + mockMvc.perform(post("/users/following/{followingUserId}", target.getUserId()) + .requestAttr("userId", user.getUserId()) + .contentType(MediaType.APPLICATION_JSON) + .content("{\"type\": true}")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.isFollowing").value(true)); + + // DB에 팔로우 상태가 ACTIVE로 저장되었는지 확인 + FollowingJpaEntity followEntity = followingJpaRepository.findByUserAndTargetUser(user.getUserId(), target.getUserId()).orElseThrow(); + assertThat(followEntity.getStatus().name()).isEqualTo("ACTIVE"); + + // 언팔로우 요청 + mockMvc.perform(post("/users/following/{followingUserId}", target.getUserId()) + .requestAttr("userId", user.getUserId()) + .contentType(MediaType.APPLICATION_JSON) + .content("{\"type\": false}")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.isFollowing").value(false)); + + // DB에 상태가 INACTIVE로 변경되었는지 확인 + FollowingJpaEntity updatedEntity = followingJpaRepository.findByUserAndTargetUser(user.getUserId(), target.getUserId()).orElseThrow(); + assertThat(updatedEntity.getStatus().name()).isEqualTo("INACTIVE"); + } +} \ No newline at end of file From 57edf6b785ff5dff02ad11201eb7f94510cd5638 Mon Sep 17 00:00:00 2001 From: janghyunjun Date: Mon, 14 Jul 2025 22:30:38 +0900 Subject: [PATCH 14/19] =?UTF-8?q?[test]=20=ED=8C=94=EB=A1=9C=EC=9E=89=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=EB=B3=80=EA=B2=BD=20controller=20=EB=8B=A8?= =?UTF-8?q?=EC=9C=84=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../in/web/UserFollowControllerTest.java | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 src/test/java/konkuk/thip/user/adapter/in/web/UserFollowControllerTest.java diff --git a/src/test/java/konkuk/thip/user/adapter/in/web/UserFollowControllerTest.java b/src/test/java/konkuk/thip/user/adapter/in/web/UserFollowControllerTest.java new file mode 100644 index 000000000..16aec5cbf --- /dev/null +++ b/src/test/java/konkuk/thip/user/adapter/in/web/UserFollowControllerTest.java @@ -0,0 +1,57 @@ +package konkuk.thip.user.adapter.in.web; + +import com.fasterxml.jackson.databind.ObjectMapper; +import konkuk.thip.common.exception.code.ErrorCode; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.web.servlet.MockMvc; + +import java.util.HashMap; +import java.util.Map; + +import static org.hamcrest.Matchers.containsString; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@ActiveProfiles("test") +@AutoConfigureMockMvc(addFilters = false) +@DisplayName("[단위] 팔로잉 상태 변경 API controller 단위 테스트") +class UserFollowControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + private Map buildValidRequest() { + Map req = new HashMap<>(); + req.put("type", true); + return req; + } + + private void assertBad(Map req, String msg) throws Exception { + mockMvc.perform(post("/users/following/{followingUserId}", 2L) + .requestAttr("userId", 1L) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(req))) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value(ErrorCode.API_INVALID_PARAM.getCode())) + .andExpect(jsonPath("$.message", containsString(msg))); + } + + @Test + @DisplayName("type이 null이면 400 에러") + void null_type() throws Exception { + Map req = new HashMap<>(); // type 없음 + assertBad(req, "type은 필수 파라미터입니다."); + } + +} \ No newline at end of file From 51595e00a1d285432c8e4a3dc2597cdfdd11d0c8 Mon Sep 17 00:00:00 2001 From: janghyunjun Date: Mon, 14 Jul 2025 22:43:38 +0900 Subject: [PATCH 15/19] =?UTF-8?q?[fix]=20=EC=9E=98=EB=AA=BB=EB=90=9C=20?= =?UTF-8?q?=EC=97=90=EB=9F=AC=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95=20(#6?= =?UTF-8?q?9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/konkuk/thip/common/exception/code/ErrorCode.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/konkuk/thip/common/exception/code/ErrorCode.java b/src/main/java/konkuk/thip/common/exception/code/ErrorCode.java index ff3e45fca..fa5614ef7 100644 --- a/src/main/java/konkuk/thip/common/exception/code/ErrorCode.java +++ b/src/main/java/konkuk/thip/common/exception/code/ErrorCode.java @@ -42,7 +42,7 @@ public enum ErrorCode implements ResponseCode { /** * 75000 : follow error */ - FOLLOW_NOT_FOUND(HttpStatus.NOT_FOUND, 750001, "존재하지 않는 FOLLOW 입니다."), + FOLLOW_NOT_FOUND(HttpStatus.NOT_FOUND, 75001, "존재하지 않는 FOLLOW 입니다."), USER_ALREADY_UNFOLLOWED(HttpStatus.BAD_REQUEST, 75002, "이미 언팔로우한 사용자입니다."), USER_CANNOT_FOLLOW_SELF(HttpStatus.BAD_REQUEST, 75003, "사용자는 자신을 팔로우할 수 없습니다."), From c604db090a603570e150b324101da3a44ea397a1 Mon Sep 17 00:00:00 2001 From: janghyunjun Date: Mon, 14 Jul 2025 22:43:54 +0900 Subject: [PATCH 16/19] =?UTF-8?q?[fix]=20=ED=95=84=EC=9A=94=EC=97=86?= =?UTF-8?q?=EB=8A=94=20import=20=EC=A0=9C=EA=B1=B0=20(#69)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../konkuk/thip/user/adapter/out/jpa/FollowingJpaEntity.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/konkuk/thip/user/adapter/out/jpa/FollowingJpaEntity.java b/src/main/java/konkuk/thip/user/adapter/out/jpa/FollowingJpaEntity.java index bcc4e9669..c457388a0 100644 --- a/src/main/java/konkuk/thip/user/adapter/out/jpa/FollowingJpaEntity.java +++ b/src/main/java/konkuk/thip/user/adapter/out/jpa/FollowingJpaEntity.java @@ -2,7 +2,6 @@ import jakarta.persistence.*; import konkuk.thip.common.entity.BaseJpaEntity; -import konkuk.thip.common.entity.StatusType; import lombok.*; import org.hibernate.annotations.SQLDelete; From db279deb10976cce1608001af3df94eb1fffc02d Mon Sep 17 00:00:00 2001 From: janghyunjun Date: Mon, 14 Jul 2025 23:58:06 +0900 Subject: [PATCH 17/19] =?UTF-8?q?[fix]=20followRepository=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=20=EC=88=9C=EC=84=9C=20=EB=B3=B4=EC=9E=A5=20(?= =?UTF-8?q?=EB=B0=B0=EC=B9=98=20=EC=82=AD=EC=A0=9C=20=EC=A0=84=EB=9E=B5=20?= =?UTF-8?q?=EB=8F=84=EC=9E=85)=20(#74)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thip/room/adapter/in/web/RoomGetMemberListApiTest.java | 2 +- .../java/konkuk/thip/user/adapter/in/web/UserFollowApiTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/konkuk/thip/room/adapter/in/web/RoomGetMemberListApiTest.java b/src/test/java/konkuk/thip/room/adapter/in/web/RoomGetMemberListApiTest.java index 40d645f0f..8ef260db4 100644 --- a/src/test/java/konkuk/thip/room/adapter/in/web/RoomGetMemberListApiTest.java +++ b/src/test/java/konkuk/thip/room/adapter/in/web/RoomGetMemberListApiTest.java @@ -99,7 +99,7 @@ void setUp() { @AfterEach void tearDown() { - followingJpaRepository.deleteAll(); + followingJpaRepository.deleteAllInBatch(); userRoomJpaRepository.deleteAll(); roomJpaRepository.deleteAll(); bookJpaRepository.deleteAll(); diff --git a/src/test/java/konkuk/thip/user/adapter/in/web/UserFollowApiTest.java b/src/test/java/konkuk/thip/user/adapter/in/web/UserFollowApiTest.java index eb6077e36..3ceb2109f 100644 --- a/src/test/java/konkuk/thip/user/adapter/in/web/UserFollowApiTest.java +++ b/src/test/java/konkuk/thip/user/adapter/in/web/UserFollowApiTest.java @@ -47,7 +47,7 @@ class UserFollowApiTest { @AfterEach void tearDown() { - followingJpaRepository.deleteAll(); + followingJpaRepository.deleteAllInBatch(); userJpaRepository.deleteAll(); aliasJpaRepository.deleteAll(); } From b6dcd4b92a2f02af531c2bee49e75897ad1cd260 Mon Sep 17 00:00:00 2001 From: janghyunjun Date: Tue, 15 Jul 2025 02:39:36 +0900 Subject: [PATCH 18/19] =?UTF-8?q?[refactor]=20=ED=8C=8C=EB=9D=BC=EB=AF=B8?= =?UTF-8?q?=ED=84=B0=20=EB=B6=88=EB=B3=80=20=EB=B3=B4=EC=9E=A5=20(#69)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/adapter/in/web/UserCommandController.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/konkuk/thip/user/adapter/in/web/UserCommandController.java b/src/main/java/konkuk/thip/user/adapter/in/web/UserCommandController.java index fe2173e81..938059496 100644 --- a/src/main/java/konkuk/thip/user/adapter/in/web/UserCommandController.java +++ b/src/main/java/konkuk/thip/user/adapter/in/web/UserCommandController.java @@ -35,8 +35,8 @@ public class UserCommandController { private final JwtUtil jwtUtil; @PostMapping("/users/signup") - public BaseResponse signup(@Valid @RequestBody UserSignupRequest request, - @Oauth2Id String oauth2Id, + public BaseResponse signup(@Valid @RequestBody final UserSignupRequest request, + @Oauth2Id final String oauth2Id, HttpServletResponse response) { Long userId = userSignupUseCase.signup(request.toCommand(oauth2Id)); String accessToken = jwtUtil.createAccessToken(userId); @@ -45,7 +45,7 @@ public BaseResponse signup(@Valid @RequestBody UserSignupReq } @PostMapping("/users/nickname") - public BaseResponse verifyNickname(@Valid @RequestBody UserVerifyNicknameRequest request) { + public BaseResponse verifyNickname(@Valid @RequestBody final UserVerifyNicknameRequest request) { return BaseResponse.ok(UserVerifyNicknameResponse.of( userVerifyNicknameUseCase.isNicknameUnique(request.nickname())) ); @@ -53,9 +53,9 @@ public BaseResponse verifyNickname(@Valid @RequestBo // 팔루우 상태 변경 : true -> 팔로우, false -> 언팔로우 @PostMapping("/users/following/{followingUserId}") - public BaseResponse followUser(@UserId Long userId, - @PathVariable Long followingUserId, - @RequestBody @Valid UserFollowRequest request) { + public BaseResponse followUser(@UserId final Long userId, + @PathVariable final Long followingUserId, + @RequestBody @Valid final UserFollowRequest request) { return BaseResponse.ok(UserFollowResponse.of(userFollowUsecase.changeFollowingState( UserFollowCommand.toCommand(userId, followingUserId, request.type()) ))); From 799145eb94391e76fa9136277e4335e6a97fc2cb Mon Sep 17 00:00:00 2001 From: janghyunjun Date: Tue, 15 Jul 2025 14:21:22 +0900 Subject: [PATCH 19/19] =?UTF-8?q?[refactor]=20request=20dto=20->=20command?= =?UTF-8?q?=20dto=20=EB=B3=80=ED=99=98=20=EC=B1=85=EC=9E=84=EC=9D=84=20ada?= =?UTF-8?q?terp=20=EA=B3=84=EC=B8=B5=EC=97=90=EC=84=9C=20=EA=B0=96?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95=20(#69)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thip/user/adapter/in/web/UserCommandController.java | 5 ++--- .../thip/user/adapter/in/web/request/UserFollowRequest.java | 5 ++++- .../thip/user/application/port/in/dto/UserFollowCommand.java | 3 --- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/main/java/konkuk/thip/user/adapter/in/web/UserCommandController.java b/src/main/java/konkuk/thip/user/adapter/in/web/UserCommandController.java index 938059496..e09821ab4 100644 --- a/src/main/java/konkuk/thip/user/adapter/in/web/UserCommandController.java +++ b/src/main/java/konkuk/thip/user/adapter/in/web/UserCommandController.java @@ -6,6 +6,7 @@ import konkuk.thip.common.security.annotation.Oauth2Id; import konkuk.thip.common.security.annotation.UserId; import konkuk.thip.common.security.util.JwtUtil; +import konkuk.thip.user.adapter.in.web.request.UserFollowRequest; import konkuk.thip.user.adapter.in.web.request.UserSignupRequest; import konkuk.thip.user.adapter.in.web.request.UserVerifyNicknameRequest; import konkuk.thip.user.adapter.in.web.response.UserFollowResponse; @@ -14,8 +15,6 @@ import konkuk.thip.user.application.port.in.UserFollowUsecase; import konkuk.thip.user.application.port.in.UserSignupUseCase; import konkuk.thip.user.application.port.in.UserVerifyNicknameUseCase; -import konkuk.thip.user.adapter.in.web.request.UserFollowRequest; -import konkuk.thip.user.application.port.in.dto.UserFollowCommand; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; @@ -57,7 +56,7 @@ public BaseResponse followUser(@UserId final Long userId, @PathVariable final Long followingUserId, @RequestBody @Valid final UserFollowRequest request) { return BaseResponse.ok(UserFollowResponse.of(userFollowUsecase.changeFollowingState( - UserFollowCommand.toCommand(userId, followingUserId, request.type()) + UserFollowRequest.toCommand(userId, followingUserId, request.type()) ))); } } diff --git a/src/main/java/konkuk/thip/user/adapter/in/web/request/UserFollowRequest.java b/src/main/java/konkuk/thip/user/adapter/in/web/request/UserFollowRequest.java index b4250c2e6..48f4d916d 100644 --- a/src/main/java/konkuk/thip/user/adapter/in/web/request/UserFollowRequest.java +++ b/src/main/java/konkuk/thip/user/adapter/in/web/request/UserFollowRequest.java @@ -1,10 +1,13 @@ package konkuk.thip.user.adapter.in.web.request; import jakarta.validation.constraints.NotNull; +import konkuk.thip.user.application.port.in.dto.UserFollowCommand; public record UserFollowRequest( @NotNull(message = "type은 필수 파라미터입니다.") Boolean type // true -> 팔로우, false -> 언팔로우 ) { - + public static UserFollowCommand toCommand(Long userId, Long targetUserId, Boolean type) { + return new UserFollowCommand(userId, targetUserId, type); + } } diff --git a/src/main/java/konkuk/thip/user/application/port/in/dto/UserFollowCommand.java b/src/main/java/konkuk/thip/user/application/port/in/dto/UserFollowCommand.java index c7344c12f..a53eff480 100644 --- a/src/main/java/konkuk/thip/user/application/port/in/dto/UserFollowCommand.java +++ b/src/main/java/konkuk/thip/user/application/port/in/dto/UserFollowCommand.java @@ -5,7 +5,4 @@ public record UserFollowCommand( Long targetUserId, Boolean type // true -> 팔로우, false -> 언팔로우 ) { - public static UserFollowCommand toCommand(Long userId, Long targetUserId, Boolean type) { - return new UserFollowCommand(userId, targetUserId, type); - } }