Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

issue/147 ✨ [FEAT] 팔로우 팔로잉 API 작업 #150

Merged
merged 4 commits into from
Oct 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading