From c47649c1227124d7703288832aa8447b253a9f27 Mon Sep 17 00:00:00 2001 From: kwonhee1 Date: Sat, 20 Sep 2025 16:12:54 +0900 Subject: [PATCH] feat : follow --- .../NextLevel/demo/exception/ErrorCode.java | 3 ++ .../demo/follow/FollowController.java | 24 +++++++++++ .../java/NextLevel/demo/follow/FollowDto.java | 15 +++++++ .../NextLevel/demo/follow/FollowEntity.java | 27 ++++++++++++ .../demo/follow/FollowRepository.java | 14 +++++++ .../NextLevel/demo/follow/FollowService.java | 41 +++++++++++++++++++ .../demo/funding/service/CouponService.java | 2 +- .../demo/funding/service/FundingService.java | 8 ++-- .../ProjectCommunityAnswerService.java | 6 +-- .../project/service/ProjectService.java | 3 +- .../demo/social/service/SocialService.java | 2 +- .../demo/user/controller/UserController.java | 6 +-- .../demo/user/service/UserService.java | 3 +- .../user/service/UserValidateService.java | 8 +++- .../service/userService/UserServiceTest.java | 2 +- 15 files changed, 145 insertions(+), 19 deletions(-) create mode 100644 src/main/java/NextLevel/demo/follow/FollowController.java create mode 100644 src/main/java/NextLevel/demo/follow/FollowDto.java create mode 100644 src/main/java/NextLevel/demo/follow/FollowEntity.java create mode 100644 src/main/java/NextLevel/demo/follow/FollowRepository.java create mode 100644 src/main/java/NextLevel/demo/follow/FollowService.java diff --git a/src/main/java/NextLevel/demo/exception/ErrorCode.java b/src/main/java/NextLevel/demo/exception/ErrorCode.java index aeb3ba9..0f3a427 100644 --- a/src/main/java/NextLevel/demo/exception/ErrorCode.java +++ b/src/main/java/NextLevel/demo/exception/ErrorCode.java @@ -38,6 +38,9 @@ public enum ErrorCode { // option + // follow + CAN_NOT_FOLLOW_SELF(HttpStatus.BAD_REQUEST, "07001","can not follow self"), + // 시발 이게 뭐지? error SIBAL_WHAT_IS_IT(HttpStatus.INTERNAL_SERVER_ERROR, "05001","알지 모르는 error 발생 : %s"); diff --git a/src/main/java/NextLevel/demo/follow/FollowController.java b/src/main/java/NextLevel/demo/follow/FollowController.java new file mode 100644 index 0000000..d552e7a --- /dev/null +++ b/src/main/java/NextLevel/demo/follow/FollowController.java @@ -0,0 +1,24 @@ +package NextLevel.demo.follow; + +import NextLevel.demo.common.SuccessResponse; +import NextLevel.demo.util.jwt.JWTUtil; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; + +@Controller +@RequiredArgsConstructor +public class FollowController { + + private final FollowService followService; + + @PostMapping("/social/follow") + public ResponseEntity follow(@RequestBody @Valid FollowDto dto) { + followService.follow(JWTUtil.getUserIdFromSecurityContext(), dto.getTargetId(), dto.getFollow()); + return ResponseEntity.ok().body(new SuccessResponse("success", null)); + } + +} diff --git a/src/main/java/NextLevel/demo/follow/FollowDto.java b/src/main/java/NextLevel/demo/follow/FollowDto.java new file mode 100644 index 0000000..b120b5d --- /dev/null +++ b/src/main/java/NextLevel/demo/follow/FollowDto.java @@ -0,0 +1,15 @@ +package NextLevel.demo.follow; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@NoArgsConstructor +@Getter +@Setter +public class FollowDto { + + private Long targetId; + private Boolean follow; + +} diff --git a/src/main/java/NextLevel/demo/follow/FollowEntity.java b/src/main/java/NextLevel/demo/follow/FollowEntity.java new file mode 100644 index 0000000..6ce886d --- /dev/null +++ b/src/main/java/NextLevel/demo/follow/FollowEntity.java @@ -0,0 +1,27 @@ +package NextLevel.demo.follow; + +import NextLevel.demo.user.entity.UserEntity; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Table(name = "follow" , uniqueConstraints = {@UniqueConstraint(name="likeUniqueConstraint", columnNames = {"user_id", "target_id"})}) +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Builder +@AllArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +public class FollowEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne + @JoinColumn(name = "user_id", nullable = false) + private UserEntity user; + + @ManyToOne + @JoinColumn(name = "target_id", nullable = false) + private UserEntity target; + +} diff --git a/src/main/java/NextLevel/demo/follow/FollowRepository.java b/src/main/java/NextLevel/demo/follow/FollowRepository.java new file mode 100644 index 0000000..3f3c03a --- /dev/null +++ b/src/main/java/NextLevel/demo/follow/FollowRepository.java @@ -0,0 +1,14 @@ +package NextLevel.demo.follow; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.Optional; + +public interface FollowRepository extends JpaRepository { + + @Query("select f from FollowEntity f where f.user.id = :userId and f.target.id = :targetId") + Optional findByUserIdAndTargetId(@Param("userId") Long userId, @Param("targetId") Long targetId); + +} diff --git a/src/main/java/NextLevel/demo/follow/FollowService.java b/src/main/java/NextLevel/demo/follow/FollowService.java new file mode 100644 index 0000000..bf97946 --- /dev/null +++ b/src/main/java/NextLevel/demo/follow/FollowService.java @@ -0,0 +1,41 @@ +package NextLevel.demo.follow; + +import NextLevel.demo.exception.CustomException; +import NextLevel.demo.exception.ErrorCode; +import NextLevel.demo.user.entity.UserEntity; +import NextLevel.demo.user.service.UserValidateService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.Optional; + +@Service +@RequiredArgsConstructor +public class FollowService { + + private final FollowRepository followRepository; + private final UserValidateService userValidateService; + + public void follow(long userId, long targetId, boolean follow) { + if(userId == targetId) + throw new CustomException(ErrorCode.CAN_NOT_FOLLOW_SELF); + + UserEntity user = userValidateService.getUserInfoWithAccessToken(userId); + UserEntity targetUser = userValidateService.findUserWithUserId(targetId); + Optional followOpt = followRepository.findByUserIdAndTargetId(user.getId(), targetUser.getId()); + + if(followOpt.isEmpty() && follow) { + followRepository.save( + FollowEntity + .builder() + .user(user) + .target(targetUser) + .build() + ); + } + if(followOpt.isPresent() && !follow) { + followRepository.deleteById(followOpt.get().getId()); + } + } + +} diff --git a/src/main/java/NextLevel/demo/funding/service/CouponService.java b/src/main/java/NextLevel/demo/funding/service/CouponService.java index f9b7abe..d2c4181 100644 --- a/src/main/java/NextLevel/demo/funding/service/CouponService.java +++ b/src/main/java/NextLevel/demo/funding/service/CouponService.java @@ -23,7 +23,7 @@ public class CouponService { @Transactional public void addCoupon(RequestAddCouponDto dto) { - UserEntity user = userValidateService.getUserInfo(dto.getUserId()); + UserEntity user = userValidateService.getUserInfoWithAccessToken(dto.getUserId()); couponRepository.save(dto.toEntity(user)); } diff --git a/src/main/java/NextLevel/demo/funding/service/FundingService.java b/src/main/java/NextLevel/demo/funding/service/FundingService.java index 700356f..bd2f789 100644 --- a/src/main/java/NextLevel/demo/funding/service/FundingService.java +++ b/src/main/java/NextLevel/demo/funding/service/FundingService.java @@ -43,7 +43,7 @@ public class FundingService { @Transactional public void cancelFreeFunding(RequestCancelFundingDto dto) { - UserEntity user = userValidateService.getUserInfo(dto.getUserId()); + UserEntity user = userValidateService.getUserInfoWithAccessToken(dto.getUserId()); FreeFundingEntity funding = freeFundingRepository.findById(dto.getId()).orElseThrow( ()->{return new CustomException(ErrorCode.NOT_FOUND, "freeFunding");} ); @@ -55,7 +55,7 @@ public void cancelFreeFunding(RequestCancelFundingDto dto) { @Transactional public void cancelOptionFunding(RequestCancelFundingDto dto) { - UserEntity user = userValidateService.getUserInfo(dto.getUserId()); + UserEntity user = userValidateService.getUserInfoWithAccessToken(dto.getUserId()); OptionFundingEntity funding = optionFundingRepository.findById(dto.getId()).orElseThrow( ()->{return new CustomException(ErrorCode.NOT_FOUND, "optionFunding");} ); @@ -68,7 +68,7 @@ public void cancelOptionFunding(RequestCancelFundingDto dto) { @Transactional public void optionFunding(@Valid RequestOptionFundingDto dto) { - UserEntity user = userValidateService.getUserInfo(dto.getUserId()); + UserEntity user = userValidateService.getUserInfoWithAccessToken(dto.getUserId()); OptionEntity option = optionValidateService.getOption(dto.getOptionId()); OptionFundingEntity entity = dto.toEntity(user, option); @@ -93,7 +93,7 @@ public void optionFunding(@Valid RequestOptionFundingDto dto) { @Transactional public void freeFunding(@Valid RequestFreeFundingDto dto) { - UserEntity user = userValidateService.getUserInfo(dto.getUserId()); + UserEntity user = userValidateService.getUserInfoWithAccessToken(dto.getUserId()); ProjectEntity project = projectValidateService.getProjectEntity(dto.getProjectId()); if(dto.getFreePrice() > user.getPoint()) diff --git a/src/main/java/NextLevel/demo/project/community/service/ProjectCommunityAnswerService.java b/src/main/java/NextLevel/demo/project/community/service/ProjectCommunityAnswerService.java index 343e61d..492dbc5 100644 --- a/src/main/java/NextLevel/demo/project/community/service/ProjectCommunityAnswerService.java +++ b/src/main/java/NextLevel/demo/project/community/service/ProjectCommunityAnswerService.java @@ -30,7 +30,7 @@ public void addAnswer(SaveCommunityDto dto) { ProjectCommunityAskEntity ask = projectCommunityAskRepository.findById(askId).orElseThrow( ()->{return new CustomException(ErrorCode.NOT_FOUND, "community");} ); - UserEntity user = userValidateService.getUserInfo(dto.getUserId()); + UserEntity user = userValidateService.getUserInfoWithAccessToken(dto.getUserId()); projectValidateService.validateAuthor(ask.getProject(), user); @@ -49,7 +49,7 @@ public void updateAnswer(SaveCommunityDto dto) { ProjectCommunityAnswerEntity answer = projectCommunityAnswerRepository.findById(answerId).orElseThrow( ()->{return new CustomException(ErrorCode.NOT_FOUND, "community");} ); - UserEntity user = userValidateService.getUserInfo(dto.getUserId()); + UserEntity user = userValidateService.getUserInfoWithAccessToken(dto.getUserId()); projectValidateService.validateAuthor(answer.getAsk().getProject(), user); answer.update(dto); } @@ -59,7 +59,7 @@ public void deleteAnswer(Long answerId, Long userId) { ProjectCommunityAnswerEntity answer = projectCommunityAnswerRepository.findById(answerId).orElseThrow( ()->{return new CustomException(ErrorCode.NOT_FOUND, "community");} ); - UserEntity user = userValidateService.getUserInfo(userId); + UserEntity user = userValidateService.getUserInfoWithAccessToken(userId); projectValidateService.validateAuthor(answer.getAsk().getProject(), user); answer.getAsk().setNoAnswer(); projectCommunityAnswerRepository.deleteById(answerId); diff --git a/src/main/java/NextLevel/demo/project/project/service/ProjectService.java b/src/main/java/NextLevel/demo/project/project/service/ProjectService.java index a62c185..46f1fac 100644 --- a/src/main/java/NextLevel/demo/project/project/service/ProjectService.java +++ b/src/main/java/NextLevel/demo/project/project/service/ProjectService.java @@ -14,7 +14,6 @@ import NextLevel.demo.project.project.entity.ProjectEntity; import NextLevel.demo.project.project.repository.ProjectDslRepository; import NextLevel.demo.project.project.repository.ProjectRepository; -import NextLevel.demo.project.story.entity.ProjectStoryEntity; import NextLevel.demo.project.story.service.ProjectStoryService; import NextLevel.demo.project.tag.service.TagService; import NextLevel.demo.project.view.ProjectViewService; @@ -53,7 +52,7 @@ public class ProjectService { @Transactional public void save(CreateProjectDto dto, ArrayList imgPaths) { // user 처리 - UserEntity user = userValidateService.getUserInfo(dto.getUserId()); + UserEntity user = userValidateService.getUserInfoWithAccessToken(dto.getUserId()); validateUser(user); if(dto.getTitleImg() == null || dto.getTitleImg().isEmpty()) diff --git a/src/main/java/NextLevel/demo/social/service/SocialService.java b/src/main/java/NextLevel/demo/social/service/SocialService.java index 4c3c5e8..e5d0561 100644 --- a/src/main/java/NextLevel/demo/social/service/SocialService.java +++ b/src/main/java/NextLevel/demo/social/service/SocialService.java @@ -39,7 +39,7 @@ public class SocialService { @ImgTransaction @Transactional public void create(RequestSocialCreateDto dto, ArrayList imgPaths) { - UserEntity user = userValidateService.getUserInfo(dto.getUserId()); + UserEntity user = userValidateService.getUserInfoWithAccessToken(dto.getUserId()); SocialEntity social = socialRepository.save(dto.toEntity(user)); saveImgs(dto.getImgs(), social, imgPaths); diff --git a/src/main/java/NextLevel/demo/user/controller/UserController.java b/src/main/java/NextLevel/demo/user/controller/UserController.java index edafa13..22149cb 100644 --- a/src/main/java/NextLevel/demo/user/controller/UserController.java +++ b/src/main/java/NextLevel/demo/user/controller/UserController.java @@ -1,8 +1,6 @@ package NextLevel.demo.user.controller; import NextLevel.demo.common.SuccessResponse; -import NextLevel.demo.project.project.dto.request.RequestMainPageProjectListDto; -import NextLevel.demo.project.project.service.ProjectService; import NextLevel.demo.user.dto.LikeDto; import NextLevel.demo.user.dto.user.request.RequestMyPageProjectListDto; import NextLevel.demo.user.dto.user.request.RequestUpdatePasswordDto; @@ -42,7 +40,7 @@ public class UserController { public ResponseEntity getUserInfo() { Long userId = JWTUtil.getUserIdFromSecurityContext(); - UserEntity user = userValidateService.getUserInfo(userId); + UserEntity user = userValidateService.getUserInfoWithAccessToken(userId); ResponseUserInfoDetailDto dto = ResponseUserInfoDetailDto.of(user); @@ -57,7 +55,7 @@ public ResponseEntity logout(HttpServletResponse response) { @GetMapping("/my-point") public ResponseEntity getUser() { - UserEntity user = userValidateService.getUserInfo(JWTUtil.getUserIdFromSecurityContext()); + UserEntity user = userValidateService.getUserInfoWithAccessToken(JWTUtil.getUserIdFromSecurityContext()); return ResponseEntity.status(HttpStatus.OK).body(new SuccessResponse("success", Map.of("point",user.getPoint()))); } diff --git a/src/main/java/NextLevel/demo/user/service/UserService.java b/src/main/java/NextLevel/demo/user/service/UserService.java index 51bd8cc..6482f1b 100644 --- a/src/main/java/NextLevel/demo/user/service/UserService.java +++ b/src/main/java/NextLevel/demo/user/service/UserService.java @@ -4,7 +4,6 @@ import NextLevel.demo.exception.ErrorCode; import NextLevel.demo.img.service.ImgService; import NextLevel.demo.img.service.ImgTransaction; -import NextLevel.demo.project.project.dto.response.ResponseProjectListDetailDto; import NextLevel.demo.project.project.dto.response.ResponseProjectListDto; import NextLevel.demo.user.dto.user.request.RequestMyPageProjectListDto; import NextLevel.demo.user.dto.user.request.RequestUpdatePasswordDto; @@ -47,7 +46,7 @@ public class UserService { @Transactional public UserEntity updateUserInfo(RequestUpdateUserInfoDto dto, HttpServletRequest request, HttpServletResponse response) { - UserEntity oldUser = userValidateService.getUserInfo(dto.getId()); + UserEntity oldUser = userValidateService.getUserInfoWithAccessToken(dto.getId()); switch(dto.getName()){ case "email": diff --git a/src/main/java/NextLevel/demo/user/service/UserValidateService.java b/src/main/java/NextLevel/demo/user/service/UserValidateService.java index 4aa6629..288f1dc 100644 --- a/src/main/java/NextLevel/demo/user/service/UserValidateService.java +++ b/src/main/java/NextLevel/demo/user/service/UserValidateService.java @@ -19,12 +19,18 @@ public class UserValidateService { private final UserRepository userRepository; private final UserDetailRepository userDetailRepository; - public UserEntity getUserInfo(Long userId) { + public UserEntity getUserInfoWithAccessToken(Long userId) { return userRepository.findUserFullInfoByUserId(userId).orElseThrow( ()->{throw new CustomException(ErrorCode.ACCESS_TOKEN_ERROR);} ); } + public UserEntity findUserWithUserId(Long userId) { + return userRepository.findUserFullInfoByUserId(userId).orElseThrow( + ()->{throw new CustomException(ErrorCode.NOT_FOUND, "user");} + ); + } + public Optional findBySocialProviderAndSocialId(String socialProvider, String socialId) { return userDetailRepository.findBySocialProviderAndSocialId(socialProvider, socialId); } diff --git a/src/test/java/NextLevel/demo/user/service/userService/UserServiceTest.java b/src/test/java/NextLevel/demo/user/service/userService/UserServiceTest.java index 4da9a0c..a78768d 100644 --- a/src/test/java/NextLevel/demo/user/service/userService/UserServiceTest.java +++ b/src/test/java/NextLevel/demo/user/service/userService/UserServiceTest.java @@ -60,7 +60,7 @@ public void setUp() { .build(); mockImg = ImgEntity.builder().id(1L).build(); - Mockito.lenient().when(userValidateService.getUserInfo(Mockito.anyLong())).thenReturn(mockUser); + Mockito.lenient().when(userValidateService.getUserInfoWithAccessToken(Mockito.anyLong())).thenReturn(mockUser); Mockito.lenient().when(imgService.saveImg(Mockito.any(), Mockito.any())).thenReturn(mockImg); Mockito.lenient().when(imgService.updateImg(Mockito.any(),Mockito.any(),Mockito.any())).thenReturn(mockImg); }