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

[feat/#49] follow api #52

Merged
merged 3 commits into from
Jan 30, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@
import com.clokey.server.domain.model.mapping.Follow;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface FollowRepository extends JpaRepository<Follow, Long> {

boolean existsByFollowing_IdAndFollowed_Id(Long followingId, Long followedId);


Optional<Follow> findByFollowing_IdAndFollowed_Id(Long followingId, Long followedId);
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
package com.clokey.server.domain.member.api;

import com.clokey.server.domain.member.application.FollowCommandService;
import com.clokey.server.domain.member.application.GetUserQueryService;
import com.clokey.server.domain.member.dto.MemberResponseDTO;
import com.clokey.server.domain.member.dto.MemberDTO;
import com.clokey.server.domain.member.application.ProfileCommandService;
import com.clokey.server.domain.member.exception.annotation.IdExist;
import com.clokey.server.domain.member.exception.annotation.IdValid;
import com.clokey.server.domain.member.exception.annotation.NotFollowMyself;
import com.clokey.server.global.common.response.BaseResponse;
import com.clokey.server.global.error.code.status.SuccessStatus;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
Expand All @@ -21,14 +22,15 @@ public class MemberRestController {

private final ProfileCommandService profileCommandService;
private final GetUserQueryService getUserQueryService;
private final FollowCommandService followCommandService;

@Operation(summary = "ν”„λ‘œν•„ μˆ˜μ • API", description = "μ‚¬μš©μžμ˜ ν”„λ‘œν•„ 정보λ₯Ό μˆ˜μ •ν•˜λŠ” APIμž…λ‹ˆλ‹€.")
@PatchMapping("users/{user_id}/profile")
public BaseResponse<MemberResponseDTO.ProfileRP> updateProfile(
public BaseResponse<MemberDTO.ProfileRP> updateProfile(
@PathVariable("user_id") Long userId,
@RequestBody @Valid MemberResponseDTO.ProfileRQ request) {
@RequestBody @Valid MemberDTO.ProfileRQ request) {

MemberResponseDTO.ProfileRP response = profileCommandService.updateProfile(userId, request);
MemberDTO.ProfileRP response = profileCommandService.updateProfile(userId, request);

return BaseResponse.onSuccess(SuccessStatus.MEMBER_ACTION_SUCCESS, response);
}
Expand All @@ -47,10 +49,34 @@ public BaseResponse<Object> checkID(
public BaseResponse<Object> getUser(
@IdValid @PathVariable("clokey_id") String clokeyId) {

MemberResponseDTO.GetUserRP response = getUserQueryService.getUser(clokeyId);
MemberDTO.GetUserRP response = getUserQueryService.getUser(clokeyId);

return BaseResponse.onSuccess(SuccessStatus.MEMBER_SUCCESS, response);
}


@Operation(summary = "νŒ”λ‘œμš° 쑰회 API", description = "λ‚΄κ°€ λ‹€λ₯Έ μ‚¬μš©μžλ₯Ό νŒ”λ‘œμš°ν•˜κ³ μžˆλŠ”μ§€ ν™•μΈν•˜λŠ” APIμž…λ‹ˆλ‹€.")
@PostMapping("users/follow/check")
public BaseResponse<MemberDTO.FollowRP> followCheck(
@RequestBody @Valid MemberDTO.FollowRQ request){

MemberDTO.FollowRP response= followCommandService.followCheck(request);

return BaseResponse.onSuccess(SuccessStatus.MEMBER_SUCCESS, response);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

μΈμŠ€νƒ€κ·Έλž¨ 처럼 νšŒμ›μ„ μ‘°νšŒν•  경우 νŒ”λ‘œμš°λ₯Ό ν•˜κ³  μžˆλŠ”μ§€ μ•Œμ•„μ•Ό ν•  것 κ°™μ•„μš”.
λ”°λΌμ„œ, νšŒμ› 쑰회 API의 Response에 "isFollowing"κ³Ό 같이 νŒ”λ‘œμš° 쀑인지 정보λ₯Ό λ˜μ Έμ£Όμ–΄μ•Ό ν•  것 κ°™μ•„μš”!

μ΄λŠ” μƒˆλ‘œμš΄ APIκ°€ μ•„λ‹Œ νšŒμ› μ‘°νšŒμ— μš°μ„ μ μœΌλ‘œ μΆ”κ°€κ°€ λ˜μ–΄μ•Ό ν•œλ‹€κ³  μƒκ°ν•©λ‹ˆλ‹€!

  1. νšŒμ›μ„ μ‘°νšŒν•˜λ©΄ νŒ”λ‘œμš° 정보가 λ– μ•Ό ν•˜κΈ° λ•Œλ¬Έμ— νšŒμ› 쑰회 API에 isFollowing을 μΆ”κ°€μ μœΌλ‘œ μ•Œλ €μ€˜μ•Όν•¨.
  2. νšŒμ› 쑰회 -> νŒ”λ‘œμš° κΈ°λŠ₯이 ν˜ΈμΆœλ˜λŠ” μˆœμ„œλ‘œ μ§„ν–‰λ˜κΈ° λ•Œλ¬Έμ— νŒ”λ‘œμš°APIμ—μ„œ isFollowing의 정보λ₯Ό 받도둝 ν•  수 μžˆλ‹€.
  3. isFollowing의 정보λ₯Ό λ°”νƒ•μœΌλ‘œ νŒ”λ‘œμš° μƒνƒœλ₯Ό λ³€ν™˜ν•œλ‹€(λ ˆν¬μ§€ν† λ¦¬λ₯Ό 보고 νŒ”λ‘œμš° 정보λ₯Ό 확인 ν•˜λ˜ 뢀뢄은 κ²€μ¦μœΌλ‘œ λŒ€μ²΄).




@Operation(summary = "νŒ”λ‘œμš° API", description = "λ‹€λ₯Έ μ‚¬μš©μžλ₯Ό νŒ”λ‘œμš°/μ–ΈνŒ”λ‘œμš°ν•˜λŠ” APIμž…λ‹ˆλ‹€. ν˜ΈμΆœμ‹œλ§ˆλ‹€ κΈ°μ‘΄ μƒνƒœμ™€ λ°˜λŒ€λ‘œ λ³€κ²½λ©λ‹ˆλ‹€.")
@PostMapping("users/follow")
public BaseResponse<Object> follow(
@NotFollowMyself @RequestBody @Valid MemberDTO.FollowRQ request) {

followCommandService.follow(request);

return BaseResponse.onSuccess(SuccessStatus.MEMBER_ACTION_SUCCESS, null);
}


}

Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.clokey.server.domain.member.application;

import com.clokey.server.domain.member.dto.MemberDTO;

public interface FollowCommandService {
void follow(MemberDTO.FollowRQ request);

MemberDTO.FollowRP followCheck(MemberDTO.FollowRQ request);
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package com.clokey.server.domain.member.application;

import com.clokey.server.domain.follow.dao.FollowRepository;
import com.clokey.server.domain.member.dto.MemberDTO;
import com.clokey.server.domain.model.Member;
import com.clokey.server.domain.model.mapping.Follow;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;


@Service
@RequiredArgsConstructor
public class FollowCommandServiceImpl implements FollowCommandService {

private final MemberRepositoryService memberRepositoryService;
private final FollowRepository followRepository;

@Override
public MemberDTO.FollowRP followCheck(MemberDTO.FollowRQ request) {
Long myUserId = memberRepositoryService.findMemberByClokeyId(request.getMyClokeyId()).getId();
Long yourUserId = memberRepositoryService.findMemberByClokeyId(request.getYourClokeyId()).getId();

boolean isFollow = followRepository.existsByFollowing_IdAndFollowed_Id(myUserId, yourUserId);

return new MemberDTO.FollowRP(isFollow);
}

@Override
@Transactional
public void follow(MemberDTO.FollowRQ request) {
// myClokeyId둜 μ‚¬μš©μž 쑰회
Long myUserId = memberRepositoryService.findMemberByClokeyId(request.getMyClokeyId()).getId();
Long yourUserId = memberRepositoryService.findMemberByClokeyId(request.getYourClokeyId()).getId();

// νŒ”λ‘œμš° 관계가 μ‘΄μž¬ν•˜λŠ”μ§€ 확인
boolean isFollow = followRepository.existsByFollowing_IdAndFollowed_Id(myUserId, yourUserId);

if (isFollow) {
// νŒ”λ‘œμš°κ°€ 이미 μ‘΄μž¬ν•˜λ©΄ μ–ΈνŒ”λ‘œμš° 처리
Follow follow = followRepository.findByFollowing_IdAndFollowed_Id(myUserId, yourUserId)
.orElseThrow(() -> new IllegalStateException("νŒ”λ‘œμš° 관계가 μ‘΄μž¬ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€."));

// νŒ”λ‘œμš° μ‚­μ œ (μ–ΈνŒ”λ‘œμš°)
followRepository.delete(follow);
} else {
// νŒ”λ‘œμš°κ°€ μ‘΄μž¬ν•˜μ§€ μ•ŠμœΌλ©΄ νŒ”λ‘œμš° 처리
Follow follow = Follow.builder()
.following(memberRepositoryService.findMemberById(myUserId))
.followed(memberRepositoryService.findMemberById(yourUserId))
.build();

// νŒ”λ‘œμš° μ €μž₯
followRepository.save(follow);
}
}

}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package com.clokey.server.domain.member.application;

import com.clokey.server.domain.member.dto.MemberResponseDTO;
import com.clokey.server.domain.member.dto.MemberDTO;

public interface GetUserQueryService {

MemberResponseDTO.GetUserRP getUser(String clokeyId);
MemberDTO.GetUserRP getUser(String clokeyId);

}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.clokey.server.domain.member.application;

import com.clokey.server.domain.member.converter.GetUserConverter;
import com.clokey.server.domain.member.dto.MemberResponseDTO;
import com.clokey.server.domain.member.dto.MemberDTO;
import com.clokey.server.domain.model.Member;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
Expand All @@ -21,8 +21,8 @@ public class GetUserQueryServiceImpl implements GetUserQueryService {
private EntityManager entityManager;

@Override
@Transactional
public MemberResponseDTO.GetUserRP getUser(String clokeyId) {
@Transactional(readOnly = true) // νŠΈλžœμž­μ…˜ 읽기 μ „μš©μœΌλ‘œ μ„€μ •
public MemberDTO.GetUserRP getUser(String clokeyId) {
Member member = memberRepositoryService.findMemberByClokeyId(clokeyId);

Long recordCount = countHistoryByMember(member);
Expand All @@ -32,21 +32,24 @@ public MemberResponseDTO.GetUserRP getUser(String clokeyId) {
return GetUserConverter.toGetUserResponseDTO(member, recordCount, followerCount, followingCount);
}

private Long countHistoryByMember(Member member) {
@Transactional(readOnly = true) // νŠΈλžœμž­μ…˜ 읽기 μ „μš©μœΌλ‘œ μ„€μ •
public Long countHistoryByMember(Member member) {
String jpql = "SELECT COUNT(h) FROM History h WHERE h.member = :member";
TypedQuery<Long> query = entityManager.createQuery(jpql, Long.class);
query.setParameter("member", member);
return query.getSingleResult();
}

private Long countFollowersByMember(Member member) {
@Transactional(readOnly = true) // νŠΈλžœμž­μ…˜ 읽기 μ „μš©μœΌλ‘œ μ„€μ •
public Long countFollowersByMember(Member member) {
String jpql = "SELECT COUNT(f) FROM Follow f WHERE f.followed = :member";
TypedQuery<Long> query = entityManager.createQuery(jpql, Long.class);
query.setParameter("member", member);
return query.getSingleResult();
}

private Long countFollowingByMember(Member member) {
@Transactional(readOnly = true) // νŠΈλžœμž­μ…˜ 읽기 μ „μš©μœΌλ‘œ μ„€μ •
public Long countFollowingByMember(Member member) {
String jpql = "SELECT COUNT(f) FROM Follow f WHERE f.following = :member";
TypedQuery<Long> query = entityManager.createQuery(jpql, Long.class);
query.setParameter("member", member);
Expand All @@ -56,3 +59,4 @@ private Long countFollowingByMember(Member member) {




Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import jakarta.persistence.TypedQuery;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Optional;

Expand All @@ -23,28 +24,32 @@ public class MemberRepositoryServiceImpl implements MemberRepositoryService {
private EntityManager entityManager;

@Override
@Transactional(readOnly = true) // 읽기 μ „μš© νŠΈλžœμž­μ…˜
public boolean memberExist(Long memberId) {
return memberRepository.existsById(memberId);
}

@Override
@Transactional(readOnly = true) // 읽기 μ „μš© νŠΈλžœμž­μ…˜
public Optional<Member> getMember(Long memberId) {
return memberRepository.findById(memberId);
}


@Override
@Transactional(readOnly = true) // 읽기 μ „μš© νŠΈλžœμž­μ…˜
public Member findMemberById(Long memberId) {
return memberRepository.findById(memberId)
.orElseThrow(() -> new MemberException(ErrorStatus.NO_SUCH_MEMBER));
}

@Override
@Transactional // μ“°κΈ° νŠΈλžœμž­μ…˜
public Member saveMember(Member member) {
return memberRepository.save(member);
}

@Override
@Transactional(readOnly = true) // 읽기 μ „μš© νŠΈλžœμž­μ…˜
public boolean idExist(String clokeyId) {
String jpql = "SELECT COUNT(m) > 0 FROM Member m WHERE m.clokeyId = :clokeyId";
TypedQuery<Boolean> query = entityManager.createQuery(jpql, Boolean.class);
Expand All @@ -53,6 +58,7 @@ public boolean idExist(String clokeyId) {
}

@Override
@Transactional(readOnly = true) // 읽기 μ „μš© νŠΈλžœμž­μ…˜
public Member findMemberByClokeyId(String clokeyId) {
String jpql = "SELECT m FROM Member m WHERE m.clokeyId = :clokeyId";
TypedQuery<Member> query = entityManager.createQuery(jpql, Member.class);
Expand All @@ -62,5 +68,4 @@ public Member findMemberByClokeyId(String clokeyId) {
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("ν΄λ‘œν‚€ 아이디에 ν•΄λ‹Ήν•˜λŠ” μ‚¬μš©μžκ°€ μ—†μŠ΅λ‹ˆλ‹€."));
}

}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package com.clokey.server.domain.member.application;


import com.clokey.server.domain.member.dto.MemberResponseDTO;
import com.clokey.server.domain.member.dto.MemberDTO;


public interface ProfileCommandService {
MemberResponseDTO.ProfileRP updateProfile(Long userId, MemberResponseDTO.ProfileRQ request);
MemberDTO.ProfileRP updateProfile(Long userId, MemberDTO.ProfileRQ request);
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.clokey.server.domain.member.application;

import com.clokey.server.domain.member.converter.ProfileConverter;
import com.clokey.server.domain.member.dto.MemberResponseDTO;
import com.clokey.server.domain.member.dto.MemberDTO;
import com.clokey.server.domain.model.Member;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
Expand All @@ -15,7 +15,7 @@ public class ProfileCommandServiceImpl implements ProfileCommandService {

@Override
@Transactional
public MemberResponseDTO.ProfileRP updateProfile(Long userId, MemberResponseDTO.ProfileRQ request) {
public MemberDTO.ProfileRP updateProfile(Long userId, MemberDTO.ProfileRQ request) {
// μ‚¬μš©μž 확인
Member member = memberRepositoryService.findMemberById(userId);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package com.clokey.server.domain.member.converter;

import com.clokey.server.domain.member.dto.MemberResponseDTO;
import com.clokey.server.domain.member.dto.MemberDTO;
import com.clokey.server.domain.model.Member;

public class GetUserConverter {

public static MemberResponseDTO.GetUserRP toGetUserResponseDTO(Member member, Long recordCount, Long followerCount, Long followingCount) {
return MemberResponseDTO.GetUserRP.builder()
public static MemberDTO.GetUserRP toGetUserResponseDTO(Member member, Long recordCount, Long followerCount, Long followingCount) {
return MemberDTO.GetUserRP.builder()
.clokeyId(member.getClokeyId())
.profileImageUrl(member.getProfileImageUrl())
.recordCount(recordCount)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
package com.clokey.server.domain.member.converter;

import com.clokey.server.domain.member.dto.MemberResponseDTO;
import com.clokey.server.domain.member.dto.MemberDTO;
import com.clokey.server.domain.model.Member;

import java.time.LocalDateTime;


public class ProfileConverter {

public static MemberResponseDTO.ProfileRP toProfileRPDTO(Member member) {
return MemberResponseDTO.ProfileRP.builder()
public static MemberDTO.ProfileRP toProfileRPDTO(Member member) {
return MemberDTO.ProfileRP.builder()
.id(member.getId())
.bio(member.getBio())
.email(member.getEmail())
Expand Down
Loading