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

✨ follow user #431

Open
wants to merge 10 commits into
base: be/dev
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
import net.pengcook.user.dto.UpdateProfileResponse;
import net.pengcook.user.dto.UserBlockRequest;
import net.pengcook.user.dto.UserBlockResponse;
import net.pengcook.user.dto.UserFollowRequest;
import net.pengcook.user.dto.UserFollowResponse;
import net.pengcook.user.dto.UsernameCheckResponse;
import net.pengcook.user.service.UserService;
import org.springframework.http.HttpStatus;
Expand Down Expand Up @@ -84,4 +86,31 @@ public UserBlockResponse blockUser(
) {
return userService.blockUser(userInfo.getId(), userBlockRequest.blockeeId());
}

@PostMapping("/user/follow")
@ResponseStatus(HttpStatus.CREATED)
public UserFollowResponse followUser(
@LoginUser UserInfo userInfo,
@RequestBody @Valid UserFollowRequest userFollowRequest
) {
return userService.followUser(userInfo.getId(), userFollowRequest.targetId());
}

@DeleteMapping("/user/follow")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void unfollowUser(
@LoginUser UserInfo userInfo,
@RequestBody @Valid UserFollowRequest userFollowRequest
) {
userService.unfollowUser(userInfo.getId(), userFollowRequest.targetId());
}

@DeleteMapping("/user/follower")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void removeFollower(
@LoginUser UserInfo userInfo,
@RequestBody @Valid UserFollowRequest userFollowRequest
) {
userService.unfollowUser(userFollowRequest.targetId(), userInfo.getId());
}
}
35 changes: 33 additions & 2 deletions backend/src/main/java/net/pengcook/user/domain/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.ColumnDefault;

@Entity
@Table(name = "users")
Expand Down Expand Up @@ -39,8 +40,22 @@ public class User {
@Column(nullable = false)
private String region;

public User(String email, String username, String nickname, String image, String region) {
this(0L, email, username, nickname, image, region);
@Column(nullable = false)
@ColumnDefault("0")
private long userFollowerCount;

@Column(nullable = false)
@ColumnDefault("0")
private long userFolloweeCount;
Comment on lines +43 to +49
Copy link
Contributor

Choose a reason for hiding this comment

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

변수명 앞에 user는 빼도 되지 않을까요???

+) 저희 DB 수정도 필요하겠네요....!!


public User(
String email,
String username,
String nickname,
String image,
String region
) {
this(0L, email, username, nickname, image, region, 0, 0);
}

public boolean isSameUser(long userId) {
Expand All @@ -53,4 +68,20 @@ public void update(String username, String nickname, String image, String region
this.image = image;
this.region = region;
}

public void increaseUserFollowerCount() {
userFollowerCount++;
}

public void decreaseUserFollowerCount() {
userFollowerCount--;
}

public void increaseUserFolloweeCount() {
userFolloweeCount++;
}

public void decreaseUserFolloweeCount() {
userFolloweeCount--;
}
}
38 changes: 38 additions & 0 deletions backend/src/main/java/net/pengcook/user/domain/UserFollow.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package net.pengcook.user.domain;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Table(uniqueConstraints = {@UniqueConstraint(columnNames = {"follower_id", "followee_id"})})
Copy link
Contributor

Choose a reason for hiding this comment

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

👍

@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@Getter
public class UserFollow {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;

@ManyToOne
@JoinColumn(name = "follower_id")
private User follower;

@ManyToOne
@JoinColumn(name = "followee_id")
private User followee;

public UserFollow(User follower, User followee) {
this(0L, follower, followee);
}
Comment on lines +35 to +37
Copy link
Contributor

Choose a reason for hiding this comment

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

followerfollowee가 같은지 확인하지 않아도 될까요?

Choose a reason for hiding this comment

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

좋네요. �유니크 키가 있긴 하지만 validate 함수를 만들어 검사하는것이 빠르게 예외를 발생시킬 방법인것 같아요

}
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ public ProfileResponse(User user, long recipeCount) {
user.getImage(),
user.getRegion(),
"hello world",
0,
0,
user.getUserFollowerCount(),
user.getUserFolloweeCount(),
Comment on lines -27 to +28
Copy link
Contributor

Choose a reason for hiding this comment

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

팔로우 여부를 필드로 추가해야 하겠어요!

recipeCount
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package net.pengcook.user.dto;

import jakarta.validation.constraints.NotNull;

public record UserFollowRequest(@NotNull long targetId) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package net.pengcook.user.dto;

import net.pengcook.user.domain.UserFollow;

public record UserFollowResponse(
long followerId,
long followeeId
) {
public UserFollowResponse(UserFollow userFollow) {
this(
userFollow.getFollower().getId(),
userFollow.getFollowee().getId()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package net.pengcook.user.repository;

import java.util.List;
import java.util.Optional;
import net.pengcook.user.domain.UserFollow;
import org.springframework.data.jpa.repository.JpaRepository;

public interface UserFollowRepository extends JpaRepository<UserFollow, Long> {

Optional<UserFollow> findByFollowerIdAndFolloweeId(Long followerId, Long followeeId);

List<UserFollow> findAllByFollowerId(long followerId);

List<UserFollow> findAllByFolloweeId(long followeeId);
Comment on lines +10 to +14

Choose a reason for hiding this comment

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

UserFollow 도 authorAble을 구현해야 할까요?
사용자가 차단 한 상황에 팔로우 목록에서 보여줄지 고민이 필요할것 같아요.

또는 차단 했을때 팔로우를 지울지도 고민이 필요해보여요. 이 부분은 정책 회의때 이야기 해봐야 할것 같네요.

}
44 changes: 43 additions & 1 deletion backend/src/main/java/net/pengcook/user/service/UserService.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,21 @@
import net.pengcook.user.domain.BlockedUserGroup;
import net.pengcook.user.domain.User;
import net.pengcook.user.domain.UserBlock;
import net.pengcook.user.domain.UserFollow;
import net.pengcook.user.domain.UserReport;
import net.pengcook.user.dto.ProfileResponse;
import net.pengcook.user.dto.ReportRequest;
import net.pengcook.user.dto.ReportResponse;
import net.pengcook.user.dto.UpdateProfileRequest;
import net.pengcook.user.dto.UpdateProfileResponse;
import net.pengcook.user.dto.UserBlockResponse;
import net.pengcook.user.dto.UserFollowResponse;
import net.pengcook.user.dto.UserResponse;
import net.pengcook.user.dto.UsernameCheckResponse;
import net.pengcook.user.exception.NotFoundException;
import net.pengcook.user.exception.UserNotFoundException;
import net.pengcook.user.repository.UserBlockRepository;
import net.pengcook.user.repository.UserFollowRepository;
import net.pengcook.user.repository.UserReportRepository;
import net.pengcook.user.repository.UserRepository;
import org.springframework.stereotype.Service;
Expand All @@ -42,6 +45,7 @@ public class UserService {
private final UserBlockRepository userBlockRepository;
private final UserReportRepository userReportRepository;
private final ImageClientService imageClientService;
private final UserFollowRepository userFollowRepository;

@Transactional(readOnly = true)
public ProfileResponse getUserById(long userId) {
Expand Down Expand Up @@ -127,11 +131,49 @@ public void deleteUser(UserInfo userInfo) {
userBlockRepository.deleteByBlockeeId(userInfo.getId());
userReportRepository.deleteByReporterId(userInfo.getId());
userReportRepository.deleteByReporteeId(userInfo.getId());

List<Long> userRecipes = recipeRepository.findRecipeIdsByUserId(userInfo.getId());
for (Long recipeId : userRecipes) {
recipeService.deleteRecipe(userInfo, recipeId);
}

List<UserFollow> followings = userFollowRepository.findAllByFollowerId(userInfo.getId());
for (UserFollow userFollow : followings) {
userFollow.getFollowee().decreaseUserFollowerCount();
userFollowRepository.delete(userFollow);
}
List<UserFollow> followers = userFollowRepository.findAllByFolloweeId(userInfo.getId());
for (UserFollow userFollow : followers) {
userFollow.getFollower().decreaseUserFolloweeCount();
userFollowRepository.delete(userFollow);
}
Comment on lines +139 to +148
Copy link
Contributor

Choose a reason for hiding this comment

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

생각해보니 유저가 탈퇴했을 때 좋아요 개수는 변하지 않지만 팔로우 개수는 변하는군요...!!!
다음 회의 때 이야기해보면 좋을 것 같아요

Choose a reason for hiding this comment

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

제가 보기에는 팔로우 수가 줄어드는게 맞는것 같아요. 👍
회의에서 이야기 해봅시다

userRepository.delete(user);
}

@Transactional
public UserFollowResponse followUser(long followerId, long followeeId) {
User follower = userRepository.findById(followerId)
.orElseThrow(() -> new NotFoundException("팔로워 정보를 조회할 수 없습니다."));
Comment on lines +152 to +155

Choose a reason for hiding this comment

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

서비스 메서드가 재사용 가능하게 잘 만들어진것 같아요.

User followee = userRepository.findById(followeeId)
.orElseThrow(() -> new NotFoundException("팔로이 정보를 조회할 수 없습니다."));
UserFollow userFollow = new UserFollow(follower, followee);

userFollowRepository.save(userFollow);
follower.increaseUserFolloweeCount();
followee.increaseUserFollowerCount();
return new UserFollowResponse(userFollow);
}

@Transactional
public void unfollowUser(long followerId, long followeeId) {
User follower = userRepository.findById(followerId)
.orElseThrow(() -> new NotFoundException("팔로워 정보를 조회할 수 없습니다."));
User followee = userRepository.findById(followeeId)
.orElseThrow(() -> new NotFoundException("팔로이 정보를 조회할 수 없습니다."));
UserFollow userFollow = userFollowRepository.findByFollowerIdAndFolloweeId(followerId, followeeId)
.orElseThrow(() -> new NotFoundException("팔로우 관계를 찾을 수 없습니다."));

userFollowRepository.delete(userFollow);
follower.decreaseUserFolloweeCount();
followee.decreaseUserFollowerCount();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import net.pengcook.user.dto.UpdateProfileRequest;
import net.pengcook.user.dto.UpdateProfileResponse;
import net.pengcook.user.dto.UserBlockRequest;
import net.pengcook.user.dto.UserFollowRequest;
import net.pengcook.user.repository.UserRepository;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -224,7 +225,7 @@ void checkUsernameWhenDuplicateUsername() {
}

@Test
@WithLoginUser
@WithLoginUser(email = "[email protected]")
@DisplayName("레시피 또는 사용자 또는 댓글을 신고한다.")
void report() {
ReportRequest spamReportRequest = new ReportRequest(
Expand Down Expand Up @@ -263,7 +264,7 @@ void report() {
.then().log().all()
.statusCode(201)
.body("reportId", is(1))
.body("reporterId", is(9))
.body("reporterId", is(5))
.body("reporteeId", is(1))
.body("reason", is(Reason.SPAM_CONTENT.name()))
.body("type", is(Type.RECIPE.name()))
Expand Down Expand Up @@ -338,7 +339,7 @@ void blockUser() {
}

@Test
@WithLoginUser(email = "[email protected]")
@WithLoginUser
@DisplayName("사용자를 삭제한다.")
void deleteUser() {
RestAssured.given(spec).log().all()
Expand All @@ -351,8 +352,73 @@ void deleteUser() {
.then().log().all()
.statusCode(204);

boolean exists = userRepository.existsByEmail("loki@pengcook.net");
boolean exists = userRepository.existsByEmail("tester@pengcook.net");

assertThat(exists).isFalse();
}

@Test
@WithLoginUser(email = "[email protected]")
@DisplayName("사용자를 팔로우한다.")
void follow() {
UserFollowRequest userFollowRequest = new UserFollowRequest(3);

RestAssured.given(spec).log().all()
.filter(document(DEFAULT_RESTDOCS_PATH,
"사용자를 팔로우한다.",
"팔로우 API",
requestFields(
fieldWithPath("targetId").description("팔로이 id")
),
responseFields(
fieldWithPath("followerId").description("팔로워 id"),
fieldWithPath("followeeId").description("팔로이 id")
)))
.contentType(ContentType.JSON)
.when()
.body(userFollowRequest)
.post("/user/follow")
.then().log().all()
.statusCode(201)
.body("followerId", is(5))
.body("followeeId", is(3));
}

@Test
@WithLoginUser(email = "[email protected]")
@DisplayName("사용자를 언팔로우한다.")
void unfollow() {
UserFollowRequest userFollowRequest = new UserFollowRequest(4);

RestAssured.given(spec).log().all()
.filter(document(DEFAULT_RESTDOCS_PATH,
"사용자를 언팔로우한다.",
"언팔로우 API"
))
.contentType(ContentType.JSON)
.when()
.body(userFollowRequest)
.delete("/user/follow")
.then().log().all()
.statusCode(204);
}

@Test
@WithLoginUser(email = "[email protected]")
@DisplayName("팔로워를 삭제한다.")
void removeFollower() {
UserFollowRequest userFollowRequest = new UserFollowRequest(4);

RestAssured.given(spec).log().all()
.filter(document(DEFAULT_RESTDOCS_PATH,
"팔로워를 삭제한다.",
"팔로워 삭제 API"
))
.contentType(ContentType.JSON)
.when()
.body(userFollowRequest)
.delete("/user/follower")
.then().log().all()
.statusCode(204);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ class BlockedUserGroupTest {

@Test
void isBlocked() {
User loki = new User(1L, "[email protected]", "loki", "로키", "loki.jpg", "KOREA");
User pond = new User(2L, "[email protected]", "pond", "폰드", "pond.jpg", "KOREA");
User loki = new User(1L, "[email protected]", "loki", "로키", "loki.jpg", "KOREA", 0, 0);
User pond = new User(2L, "[email protected]", "pond", "폰드", "pond.jpg", "KOREA", 0, 0);

BlockedUserGroup blockedUserGroup = new BlockedUserGroup(Set.of(pond));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ class UserBlockTest {
@Test
@DisplayName("UserBlock 객체를 생성한다.")
void create() {
User user_loki = new User(1L, "[email protected]", "loki", "로키", "loki.jpg", "KOREA");
User user_pond = new User(2L, "[email protected]", "pond", "폰드", "pond.jpg", "KOREA");
User user_loki = new User(1L, "[email protected]", "loki", "로키", "loki.jpg", "KOREA", 0, 0);
User user_pond = new User(2L, "[email protected]", "pond", "폰드", "pond.jpg", "KOREA", 0, 0);

UserBlock userBlock = new UserBlock(user_loki, user_pond);

Expand Down
Loading
Loading