From e34e83211999a311815c45ed01496a6c9fd05780 Mon Sep 17 00:00:00 2001 From: yumzen Date: Tue, 23 Jul 2024 18:04:25 +0900 Subject: [PATCH 1/5] =?UTF-8?q?#17=20Feat:=20=ED=8C=94=EB=A1=9C=EC=9A=B0?= =?UTF-8?q?=20=ED=85=8C=EC=9D=B4=EB=B8=94=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../backend/global/domain/entity/Follow.java | 25 +++++++++++++++++++ .../backend/global/domain/entity/Member.java | 5 ++++ 2 files changed, 30 insertions(+) create mode 100644 src/main/java/com/codiary/backend/global/domain/entity/Follow.java diff --git a/src/main/java/com/codiary/backend/global/domain/entity/Follow.java b/src/main/java/com/codiary/backend/global/domain/entity/Follow.java new file mode 100644 index 00000000..57895ea7 --- /dev/null +++ b/src/main/java/com/codiary/backend/global/domain/entity/Follow.java @@ -0,0 +1,25 @@ +package com.codiary.backend.global.domain.entity; + +import com.codiary.backend.global.domain.common.BaseEntity; +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Follow extends BaseEntity { + @Id@GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name="follow_id", nullable = false, columnDefinition = "bigint") + private Long FollowId; + + @ManyToOne + @JoinColumn(name = "from_member") + private Member fromMember; + + @ManyToOne + @JoinColumn(name = "to_member") + private Member toMember; +} diff --git a/src/main/java/com/codiary/backend/global/domain/entity/Member.java b/src/main/java/com/codiary/backend/global/domain/entity/Member.java index 4603f85f..edbd1d62 100644 --- a/src/main/java/com/codiary/backend/global/domain/entity/Member.java +++ b/src/main/java/com/codiary/backend/global/domain/entity/Member.java @@ -73,4 +73,9 @@ public enum Gender {Male, Female} @OneToMany(mappedBy = "member", cascade = CascadeType.ALL, orphanRemoval = true) private List commentList = new ArrayList<>(); + @OneToMany(mappedBy = "from_user", fetch = FetchType.LAZY) + private List followings; + + @OneToMany(mappedBy = "to_user", fetch = FetchType.LAZY) + private List followers; } From c18ddbc420ef3e32249c7a8340cfd8038fbbc370 Mon Sep 17 00:00:00 2001 From: yumzen Date: Fri, 9 Aug 2024 18:30:22 +0900 Subject: [PATCH 2/5] =?UTF-8?q?Feat:=20Profile=20=EB=8B=89=EB=84=A4?= =?UTF-8?q?=EC=9E=84=20=EB=B0=98=ED=99=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/codiary/backend/global/converter/MemberConverter.java | 1 + .../codiary/backend/global/web/dto/Member/MemberResponseDTO.java | 1 + 2 files changed, 2 insertions(+) diff --git a/src/main/java/com/codiary/backend/global/converter/MemberConverter.java b/src/main/java/com/codiary/backend/global/converter/MemberConverter.java index da59fa6b..dbffe026 100644 --- a/src/main/java/com/codiary/backend/global/converter/MemberConverter.java +++ b/src/main/java/com/codiary/backend/global/converter/MemberConverter.java @@ -152,6 +152,7 @@ public static MemberResponseDTO.UserProfileDTO toProfileResponseDto(Member membe return MemberResponseDTO.UserProfileDTO.builder() .currentMemberId(member.getMemberId()) .userId(user.getMemberId()) + .userName(user.getNickname()) .photoUrl((member.getImage() != null) ? member.getImage().getImageUrl() : "") diff --git a/src/main/java/com/codiary/backend/global/web/dto/Member/MemberResponseDTO.java b/src/main/java/com/codiary/backend/global/web/dto/Member/MemberResponseDTO.java index 08600b8b..e473042d 100644 --- a/src/main/java/com/codiary/backend/global/web/dto/Member/MemberResponseDTO.java +++ b/src/main/java/com/codiary/backend/global/web/dto/Member/MemberResponseDTO.java @@ -89,6 +89,7 @@ public static class MemberImageDTO { public static class UserProfileDTO { Long currentMemberId; Long userId; + String userName; String photoUrl; String githubUrl; String linkedinUrl; From ec32d4bd00035623a8abf169d7a69a6b3ab197b6 Mon Sep 17 00:00:00 2001 From: yumzen Date: Sun, 18 Aug 2024 10:55:08 +0900 Subject: [PATCH 3/5] =?UTF-8?q?#173=20Fix:=20FollowService=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=EC=B2=B4/=EC=9D=B8=ED=84=B0=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=8A=A4=20=EB=B6=84=EB=A6=AC=20=EC=9E=91=EC=97=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/MemberService/FollowService.java | 108 +---------------- .../MemberService/FollowServiceImpl.java | 113 ++++++++++++++++++ 2 files changed, 118 insertions(+), 103 deletions(-) create mode 100644 src/main/java/com/codiary/backend/global/service/MemberService/FollowServiceImpl.java diff --git a/src/main/java/com/codiary/backend/global/service/MemberService/FollowService.java b/src/main/java/com/codiary/backend/global/service/MemberService/FollowService.java index 0096ebab..42449170 100644 --- a/src/main/java/com/codiary/backend/global/service/MemberService/FollowService.java +++ b/src/main/java/com/codiary/backend/global/service/MemberService/FollowService.java @@ -1,112 +1,14 @@ package com.codiary.backend.global.service.MemberService; -import com.codiary.backend.global.apiPayload.code.status.ErrorStatus; -import com.codiary.backend.global.apiPayload.exception.GeneralException; import com.codiary.backend.global.domain.entity.Follow; import com.codiary.backend.global.domain.entity.Member; -import com.codiary.backend.global.repository.FollowRepository; -import com.codiary.backend.global.repository.MemberRepository; - -import jakarta.transaction.Transactional; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; import java.util.List; -import java.util.stream.Collectors; - -@Service -@RequiredArgsConstructor -public class FollowService { - private final FollowRepository followRepository; - private final MemberRepository memberRepository; - - @Transactional - public Follow follow(Long toId, Member fromMember) { - if (fromMember == null) { - throw new GeneralException(ErrorStatus.MEMBER_NOT_FOUND); - } - if (toId.equals(fromMember.getMemberId())) { - throw new GeneralException(ErrorStatus.MEMBER_SELF_FOLLOW); - } - - fromMember = memberRepository.findByToIdWithFollowings(fromMember.getMemberId()) - .orElseThrow(() -> new GeneralException(ErrorStatus.MEMBER_NOT_FOUND)); - Member toMember = memberRepository.findByToIdWithFollowers(toId) - .orElseThrow(() -> new GeneralException(ErrorStatus.MEMBER_NOT_FOUND)); - - Follow follow = followRepository.findByFromMemberAndToMember(fromMember, toMember) - .orElse(null); - - if (follow == null) { - follow = Follow.builder() - .fromMember(fromMember) - .toMember(toMember) - .followStatus(true) - .build(); - fromMember.getFollowings().add(follow); - toMember.getFollowers().add(follow); - } else { - if (follow.getFollowStatus()) { - follow.update(false); - fromMember.getFollowings().remove(follow); - toMember.getFollowers().remove(follow); - } else { - follow.update(true); - fromMember.getFollowings().add(follow); - toMember.getFollowers().add(follow); - } - } - followRepository.save(follow); - - return follow; - } - - @Transactional - public Boolean isFollowing(Long toId, Member fromMember) { - if(fromMember == null){ - throw new GeneralException(ErrorStatus.MEMBER_NOT_FOUND); - } - if (toId.equals(fromMember.getMemberId())) { - throw new GeneralException(ErrorStatus.MEMBER_SELF_FOLLOW); - } - Member toMember = memberRepository.findById(toId) - .orElseThrow(() -> new GeneralException(ErrorStatus.MEMBER_NOT_FOUND)); - - return followRepository.findByFromMemberAndToMember(fromMember, toMember) - .map(Follow::getFollowStatus) - .orElse(false); - } - - - @Transactional - public List getFollowings(Member member) { - if (member == null) { - throw new GeneralException(ErrorStatus.MEMBER_NOT_FOUND); - } - - member = memberRepository.findByToIdWithFollowings(member.getMemberId()) - .orElseThrow(() -> new GeneralException(ErrorStatus.MEMBER_NOT_FOUND)); - - List followings = followRepository.findByFromMemberAndFollowStatusTrueOrderByUpdatedAt(member); - - return followings.stream() - .map(Follow::getToMember) - .collect(Collectors.toList()); - } - - @Transactional - public List getFollowers(Member member) { - if (member == null) { - throw new GeneralException(ErrorStatus.MEMBER_NOT_FOUND); - } - - member = memberRepository.findByToIdWithFollowers(member.getMemberId()) - .orElseThrow(() -> new GeneralException(ErrorStatus.MEMBER_NOT_FOUND)); - List followers = followRepository.findByToMemberAndFollowStatusTrueOrderByUpdatedAt(member); +public interface FollowService { - return followers.stream() - .map(Follow::getFromMember) - .collect(Collectors.toList()); - } + Follow follow(Long toId, Member fromMember); + Boolean isFollowing(Long toId, Member fromMember); + List getFollowings(Member member); + List getFollowers(Member member); } diff --git a/src/main/java/com/codiary/backend/global/service/MemberService/FollowServiceImpl.java b/src/main/java/com/codiary/backend/global/service/MemberService/FollowServiceImpl.java new file mode 100644 index 00000000..41653e9f --- /dev/null +++ b/src/main/java/com/codiary/backend/global/service/MemberService/FollowServiceImpl.java @@ -0,0 +1,113 @@ +package com.codiary.backend.global.service.MemberService; + + +import com.codiary.backend.global.apiPayload.code.status.ErrorStatus; +import com.codiary.backend.global.apiPayload.exception.GeneralException; +import com.codiary.backend.global.domain.entity.Follow; +import com.codiary.backend.global.domain.entity.Member; +import com.codiary.backend.global.repository.FollowRepository; +import com.codiary.backend.global.repository.MemberRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class FollowServiceImpl implements FollowService { + private final FollowRepository followRepository; + private final MemberRepository memberRepository; + + @Override + public Follow follow(Long toId, Member fromMember) { + //Validation: fromMember 존재 여부 확인/ toMember 존재 여부 확인/ 자기 자신 팔로우 불가/ 이미 팔로우 중인지 확인 + if (fromMember == null) { + throw new GeneralException(ErrorStatus.MEMBER_NOT_FOUND); + } + + Member toMember = memberRepository.findByToIdWithFollowers(toId) + .orElseThrow(() -> new GeneralException(ErrorStatus.MEMBER_NOT_FOUND)); + + if (toId.equals(fromMember.getMemberId())) { + throw new GeneralException(ErrorStatus.MEMBER_SELF_FOLLOW); + } + + Follow follow = followRepository.findByFromMemberAndToMember(fromMember, toMember) + .orElse(null); + + // Business Logic: 팔로잉 여부에 따라 팔로잉 추가/삭제 + if (follow == null) { + follow = Follow.builder() + .fromMember(fromMember) + .toMember(toMember) + .followStatus(true) + .build(); + fromMember.getFollowings().add(follow); + toMember.getFollowers().add(follow); + } else { + if (follow.getFollowStatus()) { + follow.update(false); + fromMember.getFollowings().remove(follow); + toMember.getFollowers().remove(follow); + } else { + follow.update(true); + fromMember.getFollowings().add(follow); + toMember.getFollowers().add(follow); + } + } + followRepository.save(follow); + + // Response: 팔로우 정보 반환 + return follow; + } + + @Override + public Boolean isFollowing(Long toId, Member fromMember) { + //Validation: fromMember 존재 여부 확인/ toMember 존재 여부 확인/ 자기 자신 팔로우 불가 + if(fromMember == null){ + throw new GeneralException(ErrorStatus.MEMBER_NOT_FOUND); + } + if (toId.equals(fromMember.getMemberId())) { + throw new GeneralException(ErrorStatus.MEMBER_SELF_FOLLOW); + } + Member toMember = memberRepository.findById(toId) + .orElseThrow(() -> new GeneralException(ErrorStatus.MEMBER_NOT_FOUND)); + + // Response: 팔로잉 여부 반환 + return followRepository.findByFromMemberAndToMember(fromMember, toMember) + .map(Follow::getFollowStatus) + .orElse(false); + } + + + @Override + public List getFollowings(Member member) { + //Validation: member 존재 여부 확인 + member = memberRepository.findByToIdWithFollowings(member.getMemberId()) + .orElseThrow(() -> new GeneralException(ErrorStatus.MEMBER_NOT_FOUND)); + + //Business Logic: 팔로잉 리스트 조회 + List followings = followRepository.findByFromMemberAndFollowStatusTrueOrderByUpdatedAt(member); + + //Response: 팔로잉 리스트 반환 + return followings.stream() + .map(Follow::getToMember) + .collect(Collectors.toList()); + } + + @Override + public List getFollowers(Member member) { + //Validation: member 존재 여부 확인 + member = memberRepository.findByToIdWithFollowers(member.getMemberId()) + .orElseThrow(() -> new GeneralException(ErrorStatus.MEMBER_NOT_FOUND)); + + //Business Logic: 팔로워 리스트 조회 + List followers = followRepository.findByToMemberAndFollowStatusTrueOrderByUpdatedAt(member); + + //Response: 팔로워 리스트 반환 + return followers.stream() + .map(Follow::getFromMember) + .collect(Collectors.toList()); + } +} From 6bb7a8f18fd8bd91b0b003831e4640e8b2a6387d Mon Sep 17 00:00:00 2001 From: yumzen Date: Tue, 20 Aug 2024 13:26:46 +0900 Subject: [PATCH 4/5] =?UTF-8?q?#173=20Feat:=20=ED=8C=94=EB=A1=9C=EC=9A=B0?= =?UTF-8?q?=20=EA=B8=B0=EB=8A=A5=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../web/controller/MemberController.java | 29 ++++++++++++++----- .../web/dto/Member/FollowResponseDto.java | 2 ++ 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/codiary/backend/global/web/controller/MemberController.java b/src/main/java/com/codiary/backend/global/web/controller/MemberController.java index f7e9aba5..45576731 100644 --- a/src/main/java/com/codiary/backend/global/web/controller/MemberController.java +++ b/src/main/java/com/codiary/backend/global/web/controller/MemberController.java @@ -1,8 +1,8 @@ package com.codiary.backend.global.web.controller; import com.codiary.backend.global.apiPayload.ApiResponse; +import com.codiary.backend.global.apiPayload.code.ErrorReasonDTO; import com.codiary.backend.global.converter.MemberConverter; -import com.codiary.backend.global.converter.PostConverter; import com.codiary.backend.global.domain.entity.*; import com.codiary.backend.global.domain.entity.mapping.MemberCategory; import com.codiary.backend.global.domain.enums.TechStack; @@ -10,24 +10,22 @@ import com.codiary.backend.global.service.MemberService.MemberQueryService; import com.codiary.backend.global.web.dto.Member.MemberRequestDTO; import com.codiary.backend.global.web.dto.Member.MemberResponseDTO; -import com.codiary.backend.global.web.dto.Post.PostResponseDTO; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.data.web.PageableDefault; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.codiary.backend.global.apiPayload.code.status.SuccessStatus; import com.codiary.backend.global.service.MemberService.FollowService; -import com.codiary.backend.global.web.dto.Member.FollowResponseDto; import com.codiary.backend.global.web.dto.Member.MemberSumResponseDto; import org.springframework.web.bind.annotation.*; @@ -87,12 +85,27 @@ public ApiResponse logout(@RequestHeader("Authorization") String token) description = "id를 가진 유저에 대해 팔로우하거나 취소할 수 있습니다." ) @PostMapping("/follow/{memberId}") - public ApiResponse follow(@PathVariable("memberId") Long toId) { - Member fromMember = memberCommandService.getRequester(); - Follow follow = followService.follow(toId, fromMember); + @ApiResponses(value = { + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "MEMBER_1000", description = "성공입니다.", + content = @Content(mediaType = "application/json", + schema = @Schema(implementation = ApiResponse.class))), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "MEMBER_1001", description = "사용자가 없습니다.", + content = @Content(mediaType = "application/json", + schema = @Schema(implementation = ErrorReasonDTO.class ))), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "MEMBER_1100", description = "셀프 팔로우 기능은 제공하지 않습니다.", + content = @Content(mediaType = "application/json", + schema = @Schema(implementation = ErrorReasonDTO.class ))), + }) + public ApiResponse follow(@PathVariable("memberId") Long toId) { + Member member = memberCommandService.getRequester(); + + // 팔로우 요청 처리 + Follow follow = followService.follow(toId, member); + return ApiResponse.onSuccess(SuccessStatus.MEMBER_OK, MemberConverter.toManageFollowDto(follow)); } + @Operation( summary = "팔로우 여부 조회 기능", description = "id를 가진 유저에 대해 팔로우 했는지 여부를 확인할 수 있습니다. true-팔로우O / false-팔로우X" diff --git a/src/main/java/com/codiary/backend/global/web/dto/Member/FollowResponseDto.java b/src/main/java/com/codiary/backend/global/web/dto/Member/FollowResponseDto.java index 97470caa..62772a8f 100644 --- a/src/main/java/com/codiary/backend/global/web/dto/Member/FollowResponseDto.java +++ b/src/main/java/com/codiary/backend/global/web/dto/Member/FollowResponseDto.java @@ -1,8 +1,10 @@ package com.codiary.backend.global.web.dto.Member; +import com.fasterxml.jackson.annotation.JsonInclude; import lombok.Builder; @Builder +@JsonInclude(JsonInclude.Include.NON_NULL) public record FollowResponseDto( Long followId, Long followerId, From 105fcbb76483751fb7946866897d6451054c1a12 Mon Sep 17 00:00:00 2001 From: yumzen Date: Tue, 20 Aug 2024 17:44:34 +0900 Subject: [PATCH 5/5] =?UTF-8?q?Feat:=20=ED=8C=80=20ID=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=20=EB=B0=98=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../backend/global/converter/MemberConverter.java | 11 ++++++++--- .../global/web/dto/Member/MemberResponseDTO.java | 3 ++- .../backend/global/web/dto/Team/TeamResponseDTO.java | 11 +++++++++++ 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/codiary/backend/global/converter/MemberConverter.java b/src/main/java/com/codiary/backend/global/converter/MemberConverter.java index 3e4c96d9..18523891 100644 --- a/src/main/java/com/codiary/backend/global/converter/MemberConverter.java +++ b/src/main/java/com/codiary/backend/global/converter/MemberConverter.java @@ -5,11 +5,11 @@ import com.codiary.backend.global.domain.entity.Member; import com.codiary.backend.global.domain.entity.Team; import com.codiary.backend.global.domain.entity.mapping.MemberCategory; -import com.codiary.backend.global.domain.entity.mapping.TeamMember; import com.codiary.backend.global.domain.entity.mapping.TechStacks; import com.codiary.backend.global.web.dto.Member.FollowResponseDto; import com.codiary.backend.global.web.dto.Member.MemberResponseDTO; import com.codiary.backend.global.web.dto.Member.MemberSumResponseDto; +import com.codiary.backend.global.web.dto.Team.TeamResponseDTO; import org.springframework.data.domain.Page; import org.springframework.stereotype.Component; @@ -174,8 +174,13 @@ public static MemberResponseDTO.UserProfileDTO toProfileResponseDto(Member membe .map(TechStacks::getName) .collect(Collectors.toList())) .teamList(user.getTeamMemberList().stream() - .map(TeamMember::getTeam) - .map(Team::getName) + .map(teamMember -> { + Team team = teamMember.getTeam(); + return TeamResponseDTO.TeamInfoDTO.builder() + .teamId(team.getTeamId()) + .teamName(team.getName()) + .build(); + }) .collect(Collectors.toList())) .myPage(user.getMemberId().equals(member.getMemberId())) .build(); diff --git a/src/main/java/com/codiary/backend/global/web/dto/Member/MemberResponseDTO.java b/src/main/java/com/codiary/backend/global/web/dto/Member/MemberResponseDTO.java index ff01dda2..e1c1b5f0 100644 --- a/src/main/java/com/codiary/backend/global/web/dto/Member/MemberResponseDTO.java +++ b/src/main/java/com/codiary/backend/global/web/dto/Member/MemberResponseDTO.java @@ -2,6 +2,7 @@ import com.codiary.backend.global.domain.enums.TechStack; import com.codiary.backend.global.jwt.TokenInfo; +import com.codiary.backend.global.web.dto.Team.TeamResponseDTO; import lombok.*; import java.time.LocalDateTime; @@ -97,7 +98,7 @@ public static class UserProfileDTO { String discordUrl; String introduction; List techStacksList; - List teamList; + List teamList; Boolean myPage; } diff --git a/src/main/java/com/codiary/backend/global/web/dto/Team/TeamResponseDTO.java b/src/main/java/com/codiary/backend/global/web/dto/Team/TeamResponseDTO.java index b7a075d6..c6846dae 100644 --- a/src/main/java/com/codiary/backend/global/web/dto/Team/TeamResponseDTO.java +++ b/src/main/java/com/codiary/backend/global/web/dto/Team/TeamResponseDTO.java @@ -89,4 +89,15 @@ public static class TeamImageDTO { String url; } + + @Builder + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + public static class TeamInfoDTO { + private Long teamId; + private String teamName; + } + }