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 30 commits into
base: be/dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
650cd90
Create PR for #430
github-actions[bot] Dec 5, 2024
f3b4963
Merge remote-tracking branch 'origin/be/dev' into be/feat/430
tackyu Dec 8, 2024
6d279bc
Merge remote-tracking branch 'origin/be/dev' into be/feat/430
tackyu Dec 16, 2024
445864c
:sparkles: add follow count field
tackyu Dec 16, 2024
56d5e0a
:sparkles: implement UserFollow
tackyu Dec 16, 2024
f9df833
:sparkles: follow User
tackyu Dec 16, 2024
d9751a5
:sparkles: unfollow User
tackyu Dec 16, 2024
d92c465
:sparkles: remove user with UserFollow
tackyu Dec 16, 2024
f2618a4
:white_check_mark: add data
tackyu Dec 16, 2024
1d45b50
:white_check_mark: test with UserFollow
tackyu Dec 16, 2024
517f013
:bug: add request field
tackyu Dec 28, 2024
6ccf1a5
Merge remote-tracking branch 'origin/be/dev' into be/feat/430
tackyu Dec 29, 2024
9e6adc5
:recycle: change name of field
tackyu Dec 30, 2024
c3b879f
:sparkles: add validation
tackyu Dec 30, 2024
a415d5d
:white_check_mark: test count of follow
tackyu Dec 30, 2024
7da55bf
:sparkles: add following condition
tackyu Dec 30, 2024
828d714
:sparkles: retrieve following recipes
tackyu Dec 30, 2024
bf8a555
:white_check_mark: UserFollow validation test
tackyu Dec 30, 2024
1e5bab0
:white_check_mark: UserFollow creation test
tackyu Dec 30, 2024
8411209
:recycle: add getUser
tackyu Jan 6, 2025
578ffb2
:sparkles: retrieve follow information of user
tackyu Jan 6, 2025
8d4edb0
:white_check_mark: test follow information retrieval
tackyu Jan 6, 2025
00195da
Merge remote-tracking branch 'origin/be/dev' into be/feat/430
tackyu Jan 6, 2025
05d9f95
:recycle: change return type
tackyu Jan 6, 2025
ebfb72c
:recycle: change code to single line
tackyu Feb 2, 2025
d798b4f
:recycle: remove unnecessary conversion to RecipeHomeResponse
tackyu Feb 2, 2025
472f323
:recycle: separate retrieve followingInfo API
tackyu Feb 2, 2025
1a12a2e
:recycle: unfollow user when blocking a user
tackyu Feb 2, 2025
dcab263
:sparkles: add validation
tackyu Feb 3, 2025
3ecc31d
:white_check_mark: add validation test
tackyu Feb 3, 2025
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 @@ -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")
Copy link
Contributor

Choose a reason for hiding this comment

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

UserController에서는 왜 클래스단에 RequestMapping이 안묶여있죠???
그냥 궁금증...

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.

UserController에서는 왜 클래스단에 RequestMapping이 안묶여있죠???
그냥 궁금증...

그러게요 예전에 제가 작업한거긴한데 부탁드려요.

@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;
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);
}
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 함수를 만들어 검사하는것이 빠르게 예외를 발생시킬 방법인것 같아요

Copy link
Contributor

Choose a reason for hiding this comment

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

UserFollow에 동등성 검증 로직,
서비스에 팔로우 중복 여부 검증 로직 추가했습니다!

}
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(),
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을 구현해야 할까요?
사용자가 차단 한 상황에 팔로우 목록에서 보여줄지 고민이 필요할것 같아요.

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

Copy link
Contributor

Choose a reason for hiding this comment

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

팔로워 목록에서는 차단한 사용자를 제외할 수 있을 것 같은데
팔로잉 목록에서도 가능한지 얘기해보고 싶습니다😢

Copy link
Contributor

Choose a reason for hiding this comment

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

차단하면 당연히 팔로우 하는 사람 목록과 팔로우 받는 사람 목록에서 빠져야 된다고 생각합니다.
전 아예 지워버려야 하는 게 맞다고 봐요!
테이블에서도 지우고, 인원 카운트에서도 내리고요.

Copy link
Contributor

Choose a reason for hiding this comment

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

차단하면 실제 UserFollow의 값이 변해야 한다는 의견에 동의합니다!
팔로우 관계를 조회할 때, 모든 사용자가 같은 값을 받는 것이 적절할 것 같아요

}
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);
}
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("팔로워 정보를 조회할 수 없습니다."));

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