Skip to content

Commit

Permalink
issue/147 ✨ [FEAT] 팔로우 팔로잉 API 작업 (#150)
Browse files Browse the repository at this point in the history
* ✨ Feat : 팔로우하기 API 구현

* ✨ Feat : 팔로우중인 사용자 조회 API 구현

* ✨ Feat : 나를 팔로우 중인 사용자 조회 API 구현

* redis 설정 임시로 변경
  • Loading branch information
CYY1007 authored Oct 1, 2023
1 parent 9344b93 commit a5dc21e
Show file tree
Hide file tree
Showing 13 changed files with 325 additions and 5 deletions.
4 changes: 4 additions & 0 deletions src/main/java/zipdabang/server/base/Code.java
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,11 @@ public enum Code {
ALREADY_BLOCKED_MEMBER(HttpStatus.OK, 4062, "이미 차단된 사용자입니다."),
BLOCK_SELF(HttpStatus.OK, 4063, "자신을 차단할 수 없습니다."),

// BAD_REQUEST
TARGET_MEMBER_NOT_FOUND(HttpStatus.OK, 4064,"대상 사용자가 없습니다.."),

//FORBIDDEN
SELF_FOLLOW_FORBIDDEN(HttpStatus.OK, 4065, "스스로 팔로우는 안됩니다."),

// market error

Expand Down
61 changes: 61 additions & 0 deletions src/main/java/zipdabang/server/converter/MemberConverter.java
Original file line number Diff line number Diff line change
Expand Up @@ -337,4 +337,65 @@ public static MemberResponseDto.PagingMemberListDto toPagingMemberListDto(Page<M
.build();
}

public static Follow toFollow(){
return Follow.builder().build();
}

public static MemberResponseDto.FollowingResultDto toFollowingResultDto(Follow follow){
return MemberResponseDto.FollowingResultDto.builder()
.targetId(follow.getTargetMember().getMemberId())
.followAt(LocalDateTime.now())
.build();
}

public static MemberResponseDto.FollowInfoDto toFollowInfoDto(Member member){
return MemberResponseDto.FollowInfoDto.builder()
.id(member.getMemberId())
.caption(member.getCaption())
.nickname(member.getNickname())
.imageUrl(member.getProfileUrl())
.build();
}

public static MemberResponseDto.FollowerInfoDto toFollowerInfoDto(Member member, Member owner){

List<Member> followingMembers = owner.getFollowingList().stream()
.map(Follow::getTargetMember).collect(Collectors.toList());

return MemberResponseDto.FollowerInfoDto.builder()
.id(member.getMemberId())
.caption(member.getCaption())
.nickname(member.getNickname())
.imageUrl(member.getProfileUrl())
.isFollowing(followingMembers.contains(member))
.build();
}

public static MemberResponseDto.FollowingListDto toFollowingListDto(Page<Follow> followList){
List<MemberResponseDto.FollowInfoDto> followInfoDtoList = followList.stream()
.map(follow -> toFollowInfoDto(follow.getTargetMember())).collect(Collectors.toList());

return MemberResponseDto.FollowingListDto.builder()
.followingList(followInfoDtoList)
.isFirst(followList.isFirst())
.isLast(followList.isLast())
.totalPage(followList.getTotalPages())
.totalElements(followList.getTotalElements())
.currentPageElements(followList.getNumberOfElements())
.build();
}

public static MemberResponseDto.FollowerListDto toFollowerListDto(Page<Follow> followList, Member owner){
List<MemberResponseDto.FollowerInfoDto> followerInfoDtoList = followList.stream()
.map(follow -> toFollowerInfoDto(follow.getFollowingMember(), owner)).collect(Collectors.toList());

return MemberResponseDto.FollowerListDto.builder()
.followerList(followerInfoDtoList)
.isFirst(followList.isFirst())
.isLast(followList.isLast())
.totalPage(followList.getTotalPages())
.totalElements(followList.getTotalElements())
.currentPageElements(followList.getNumberOfElements())
.build();
}
}
23 changes: 22 additions & 1 deletion src/main/java/zipdabang/server/domain/member/Follow.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,28 @@ public class Follow extends BaseEntity {
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

// follow 객체
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "target_id")
private Member member;
private Member targetMember;

// follow 주체
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "following_id")
private Member followingMember;

public void setTargetMember(Member targetMember){
if (this.targetMember != null)
targetMember.getFollowerList().remove(this);
this.targetMember = targetMember;
targetMember.getFollowerList().add(this);
}

public void setFollowingMember(Member followingMember){
if (this.followingMember != null)
followingMember.getFollowerList().remove(this);
this.followingMember = followingMember;
followingMember.getFollowerList().add(this);
}

}
9 changes: 9 additions & 0 deletions src/main/java/zipdabang/server/domain/member/Member.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ public class Member extends BaseEntity {
@Column(length = 18)
private String phoneNum;

private String caption;

@Column(length = 6)
private String birth;

Expand Down Expand Up @@ -107,6 +109,13 @@ public class Member extends BaseEntity {
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL)
private List<Inquery> inqueryList;

// 나를 따르는 = follow 테이블에서 targetMember가 나인,
@OneToMany(mappedBy = "targetMember", cascade = CascadeType.ALL)
private List<Follow> followerList;

// 내가 따르는 = follow 테이블에서 followingMember가 나인,
@OneToMany(mappedBy = "followingMember", cascade = CascadeType.ALL)
private List<Follow> followingList;

public void setProfileUrl(String profileUrl) {
this.profileUrl = profileUrl;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package zipdabang.server.repository.memberRepositories;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import zipdabang.server.domain.member.Follow;
import zipdabang.server.domain.member.Member;

public interface FollowRepository extends JpaRepository<Follow, Long> {

Page<Follow> findAllByTargetMember(Member member, PageRequest pageRequest);
Page<Follow> findAllByFollowingMember(Member member, PageRequest pageRequest);
}
9 changes: 9 additions & 0 deletions src/main/java/zipdabang/server/service/MemberService.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import zipdabang.server.domain.Category;
import zipdabang.server.domain.member.Follow;
import zipdabang.server.domain.member.Inquery;
import zipdabang.server.domain.enums.DeregisterType;
import zipdabang.server.domain.member.Member;
Expand All @@ -22,6 +23,8 @@ public interface MemberService {

Optional<Member> checkExistNickname(String nickname);

Optional<Member> findMemberById(Long id);

public void existByPhoneNumber(String phoneNum);

OAuthJoin.OAuthJoinDto joinInfoComplete(MemberRequestDto.MemberInfoDto request, String type);
Expand Down Expand Up @@ -56,4 +59,10 @@ public interface MemberService {
public void blockMember(Member owner, Long blocked);
public void unblockMember(Member owner, Long blockedId);
public Page<Member> findBlockedMember(Integer page, Member member);

public Follow createFollow(Long targetId, Member member);

Page<Follow> findFollowing(Member member, Integer page);

Page<Follow> findFollower(Member member, Integer page);
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ public class MemberServiceImpl implements MemberService {
private final DeregisterReasonRepository deregisterReasonRepository;
private final BlockedMemberRepository blockedMemberRepository;

private final FollowRepository followRepository;

@Value("${paging.size}")
private Integer pageSize;

Expand Down Expand Up @@ -120,6 +122,11 @@ public Optional<Member> checkExistNickname(String nickname){
return memberRepository.findByNickname(nickname);
}

@Override
public Optional<Member> findMemberById(Long id) {
return memberRepository.findById(id);
}

@Override
public void existByPhoneNumber(String phoneNum) {
if (memberRepository.existsByPhoneNum(phoneNum)) {
Expand Down Expand Up @@ -356,6 +363,40 @@ public Page<Member> findBlockedMember(Integer page, Member member) {
return blockedMembers;

}

@Override
@Transactional
public Follow createFollow(Long targetId, Member member) {

if(targetId.equals(member.getMemberId()))
throw new MemberException(Code.SELF_FOLLOW_FORBIDDEN);
Follow follow = MemberConverter.toFollow();
follow.setFollowingMember(member);
follow.setTargetMember(memberRepository.findById(targetId).get());
return followRepository.save(follow);
}

@Override
public Page<Follow> findFollowing(Member member, Integer page) {
page -= 1;
Page<Follow> followingMember = followRepository.findAllByFollowingMember(member, PageRequest.of(page, pageSize, Sort.by(Sort.Direction.DESC, "createdAt")));

if(followingMember.getTotalPages() <= page)
throw new MemberException(Code.OVER_PAGE_INDEX_ERROR);

return followingMember;
}

@Override
public Page<Follow> findFollower(Member member, Integer page) {
page -= 1;
Page<Follow> followerMember = followRepository.findAllByTargetMember(member, PageRequest.of(page, pageSize, Sort.by(Sort.Direction.DESC, "createdAt")));

if(followerMember.getTotalPages() <= page)
throw new MemberException(Code.OVER_PAGE_INDEX_ERROR);

return followerMember;
}
}


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package zipdabang.server.validation.annotation;

import zipdabang.server.validation.validator.ExistMemberRequestBodyValidator;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;

@Documented
@Constraint(validatedBy = ExistMemberRequestBodyValidator.class)
@Target( { ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
public @interface ExistMember {
String message() default "해당하는 유저가 존재하지 않습니다.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package zipdabang.server.validation.validator;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import zipdabang.server.base.Code;
import zipdabang.server.domain.member.Member;
import zipdabang.server.service.MemberService;
import zipdabang.server.validation.annotation.ExistMember;
import zipdabang.server.web.dto.requestDto.MemberRequestDto;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.Optional;

@Component
@RequiredArgsConstructor
public class ExistMemberRequestBodyValidator implements ConstraintValidator<ExistMember, Long>{

private final MemberService memberService;

@Override
public void initialize(ExistMember constraintAnnotation) {
ConstraintValidator.super.initialize(constraintAnnotation);
}

@Override
public boolean isValid(Long value, ConstraintValidatorContext context) {

Optional<Member> memberById = memberService.findMemberById(value);
if(memberById.isEmpty()) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(Code.TARGET_MEMBER_NOT_FOUND.toString()).addConstraintViolation();
return false;
}
return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@
import zipdabang.server.base.Code;
import zipdabang.server.base.ResponseDto;
import zipdabang.server.base.exception.handler.MemberException;
import zipdabang.server.base.exception.handler.RecipeException;
import zipdabang.server.converter.MemberConverter;
import zipdabang.server.domain.Category;
import zipdabang.server.domain.member.Follow;
import zipdabang.server.domain.member.Inquery;
import zipdabang.server.domain.member.Member;
import zipdabang.server.redis.domain.RefreshToken;
Expand All @@ -37,6 +37,7 @@
import zipdabang.server.validation.annotation.CheckPage;
import zipdabang.server.validation.annotation.CheckTempMember;
import zipdabang.server.validation.annotation.CheckDeregister;
import zipdabang.server.validation.annotation.ExistMember;
import zipdabang.server.web.dto.requestDto.MemberRequestDto;
import zipdabang.server.web.dto.responseDto.MemberResponseDto;

Expand Down Expand Up @@ -410,4 +411,50 @@ else if (page < 1)
Page<Member> blockedMembers = memberService.findBlockedMember(page, member);
return ResponseDto.of(MemberConverter.toPagingMemberListDto(blockedMembers));
}


@Operation(summary = "🎪팔로우하기 API", description = "팔로우하기 API 입니다.")
@PostMapping("/members/followings/{targetId}")
@Parameters({
@Parameter(name = "member", hidden = true)
})
@ApiResponses({
@ApiResponse(responseCode = "2000", description = "OK 성공"),
@ApiResponse(responseCode = "4064", description = "BAD_REQEUST , 팔로우하려는 대상이 없음", content = @Content(schema = @Schema(implementation = ResponseDto.class))),
@ApiResponse(responseCode = "4065", description = "FORBIDDEN , 스스로는 팔로우가 안됩니다", content = @Content(schema = @Schema(implementation = ResponseDto.class))),
})
public ResponseDto<MemberResponseDto.FollowingResultDto> followMember(@CheckTempMember @AuthMember Member member, @ExistMember @PathVariable(name = "targetId") Long targetId){
Follow follow = memberService.createFollow(targetId, member);
return ResponseDto.of(MemberConverter.toFollowingResultDto(follow));
}

@Operation(summary = "🎪팔로우중인 사용자 조회 API", description = "팔로우중인 사용자 조회 API 입니다. 페이지 주세요")
@GetMapping("/members/followings")
@Parameters({
@Parameter(name = "member", hidden = true)
})
@ApiResponses({
@ApiResponse(responseCode = "2000", description = "OK 성공"),
@ApiResponse(responseCode = "4054", description = "BAD_REQUEST , 페이지 번호가 없거나 0 이하", content = @Content(schema = @Schema(implementation = ResponseDto.class))),
@ApiResponse(responseCode = "4055", description = "BAD_REQUEST , 페이지 번호가 초과함", content = @Content(schema = @Schema(implementation = ResponseDto.class))),
})
public ResponseDto<MemberResponseDto.FollowingListDto> getFollowingMember(@CheckPage Integer page, @CheckTempMember @AuthMember Member member){
Page<Follow> following = memberService.findFollowing(member, page);
return ResponseDto.of(MemberConverter.toFollowingListDto(following));
}

@Operation(summary = "🎪나를 팔로잉 하는 사용자 조회 API", description = "나를 팔로잉 하는 사용자 조회 API 입니다. 페이지 주세요")
@GetMapping("/members/followers")
@Parameters({
@Parameter(name = "member", hidden = true)
})
@ApiResponses({
@ApiResponse(responseCode = "2000", description = "OK 성공"),
@ApiResponse(responseCode = "4054", description = "BAD_REQUEST , 페이지 번호가 없거나 0 이하", content = @Content(schema = @Schema(implementation = ResponseDto.class))),
@ApiResponse(responseCode = "4055", description = "BAD_REQUEST , 페이지 번호가 초과함", content = @Content(schema = @Schema(implementation = ResponseDto.class))),
})
public ResponseDto<MemberResponseDto.FollowerListDto> getFollowerMember(@CheckPage Integer page, @CheckTempMember @AuthMember Member member){
Page<Follow> follower = memberService.findFollower(member, page);
return ResponseDto.of(MemberConverter.toFollowerListDto(follower, member));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.util.List;

import org.springframework.lang.Nullable;
import org.springframework.web.multipart.MultipartFile;
import zipdabang.server.domain.enums.DeregisterType;
import zipdabang.server.web.dto.responseDto.MemberResponseDto;

public class MemberRequestDto {

Expand Down
Loading

0 comments on commit a5dc21e

Please sign in to comment.