Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
14 commits
Select commit Hold shift + click to select a range
c03a1aa
[feat] 피드 조회시 유저 정보 조회 api (2개) controller 개발 (#115)
seongjunnoh Jul 31, 2025
e3be4e2
[feat] 피드 조회시 유저 정보 조회 api (2개) use case 개발 (#115)
seongjunnoh Jul 31, 2025
2f9da6d
[feat] 유저가 작성한 공개 피드 개수 반환 메서드 구현 (#115)
seongjunnoh Jul 31, 2025
0c3e128
[feat] 팔로워의 프로필 이미지 조회하는 영속성 메서드 추가 (#115)
seongjunnoh Jul 31, 2025
20ba819
[feat] 팔로워의 프로필 이미지 조회하는 QueryDSL 코드 구현 (#115)
seongjunnoh Jul 31, 2025
388635e
[test] 피드 조회 시 유저의 정보 조회 api (2개) 통합 테스트 코드 작성 (#115)
seongjunnoh Jul 31, 2025
782a524
[refactor] UserJpaEntity 에서 imageUrl 필드 삭제 (#115)
seongjunnoh Jul 31, 2025
a839915
[refactor] UserMapper 수정 (#115)
seongjunnoh Jul 31, 2025
e31757a
[refactor] UserJpaEntity 에서 imageUrl 필드를 제거함에 따른 프로덕션 코드 수정 (#115)
seongjunnoh Jul 31, 2025
9360e05
[refactor] UserJpaEntity 에서 imageUrl 필드를 제거함에 따른 TestEntityFactory 코드…
seongjunnoh Jul 31, 2025
c778d0b
[refactor] UserJpaEntity 에서 imageUrl 필드를 제거함에 따른 나머지 테스트 코드 수정 (#115)
seongjunnoh Jul 31, 2025
05fc9cb
[refactor] 메서드 네이밍 수정 (#115)
seongjunnoh Jul 31, 2025
92d2c23
[feat] api 설명을 위해 스웨거 operation 추가 (#115)
seongjunnoh Jul 31, 2025
333e001
[refactor] response 구성 책임을 mapper가 수행하도록 수정 (#115)
seongjunnoh Jul 31, 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 @@ -5,12 +5,15 @@
import io.swagger.v3.oas.annotations.tags.Tag;
import konkuk.thip.common.dto.BaseResponse;
import konkuk.thip.common.security.annotation.UserId;
import konkuk.thip.feed.adapter.in.web.response.FeedShowUserInfoResponse;
import konkuk.thip.feed.adapter.in.web.response.FeedShowMineResponse;
import konkuk.thip.feed.adapter.in.web.response.FeedShowAllResponse;
import konkuk.thip.feed.application.port.in.FeedShowAllUseCase;
import konkuk.thip.feed.application.port.in.FeedShowMineUseCase;
import konkuk.thip.feed.application.port.in.FeedShowUserInfoUseCase;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

Expand All @@ -21,6 +24,7 @@ public class FeedQueryController {

private final FeedShowAllUseCase feedShowAllUseCase;
private final FeedShowMineUseCase feedShowMineUseCase;
private final FeedShowUserInfoUseCase feedShowUserInfoUseCase;

@Operation(
summary = "피드 전체 조회",
Expand All @@ -45,4 +49,24 @@ public BaseResponse<FeedShowMineResponse> showMyFeeds(
@RequestParam(value = "cursor", required = false) final String cursor) {
return BaseResponse.ok(feedShowMineUseCase.showMyFeeds(userId, cursor));
}

@Operation(
summary = "내 피드 조회의 상단 화면 구성",
description = "사용자의 정보, 사용자의 팔로워 정보, 사용자가 작성한 전체 피드 개수를 조회합니다."
)
@GetMapping("/feeds/mine/info")
public BaseResponse<FeedShowUserInfoResponse> showMyInfoInFeeds(@Parameter(hidden = true) @UserId final Long userId) {
return BaseResponse.ok(feedShowUserInfoUseCase.showMyInfoInFeeds(userId));
}

@Operation(
summary = "특정 유저 피드 조회의 상단 화면 구성",
description = "사용자의 정보, 사용자의 팔로워 정보, 사용자가 작성한 공개 피드 개수를 조회합니다."
)
@GetMapping("/feeds/users/{userId}/info")
public BaseResponse<FeedShowUserInfoResponse> showAnotherUserInfoInFeeds(
@Parameter(description = "피드 조회할 유저의 userId 값") @PathVariable final Long userId
) {
return BaseResponse.ok(feedShowUserInfoUseCase.showAnotherUserInfoInFeeds(userId));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package konkuk.thip.feed.adapter.in.web.response;

import lombok.Builder;

import java.util.List;

@Builder
public record FeedShowUserInfoResponse(
String profileImageUrl,
String nickname,
String aliasName,
String aliasColor,
int followerCount,
int totalFeedCount,
List<String> latestFollowerProfileImageUrls
) { }
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,13 @@ public CursorBasedList<FeedQueryDto> findMyFeedsByCreatedAt(Long userId, Cursor
}

@Override
public int countFeedsByUserId(Long userId) {
public int countAllFeedsByUserId(Long userId) {
// int 로 강제 형변환 해도 괜찮죠??
return (int) feedJpaRepository.countFeedsByUserId(userId, StatusType.ACTIVE);
return (int) feedJpaRepository.countAllFeedsByUserId(userId, StatusType.ACTIVE);
}

@Override
public int countPublicFeedsByUserId(Long userId) {
return (int) feedJpaRepository.countPublicFeedsByUserId(userId, StatusType.ACTIVE);
}
Comment on lines +72 to 80
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

여기서는 파라미터로 statusType까지 전달해서 동적으로 필터링한 이유가 있나요?? (단순 궁금)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

그냥 jpa repository 내부에서 status = ACTIVE 구문을 추가할까 하다가, 영속성 adapter 에서 주입해주는게 "status가 ACTIVE인 것을 찾는다" 라는 것을 보여주고 싶어서 위와 같이 구현해보았습니다

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,8 @@
public interface FeedJpaRepository extends JpaRepository<FeedJpaEntity, Long>, FeedQueryRepository {

@Query("SELECT COUNT(f) FROM FeedJpaEntity f WHERE f.userJpaEntity.userId = :userId AND f.status = :status")
long countFeedsByUserId(@Param("userId") Long userId, @Param("status") StatusType status);
long countAllFeedsByUserId(@Param("userId") Long userId, @Param("status") StatusType status);

@Query("SELECT COUNT(f) FROM FeedJpaEntity f WHERE f.userJpaEntity.userId = :userId AND f.isPublic = TRUE AND f.status = :status")
long countPublicFeedsByUserId(@Param("userId") Long userId, @Param("status") StatusType status);
}
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ private FeedQueryDto toDto(FeedJpaEntity e, Integer priority) {
.feedId(e.getPostId())
.creatorId(e.getUserJpaEntity().getUserId())
.creatorNickname(e.getUserJpaEntity().getNickname())
.creatorProfileImageUrl(e.getUserJpaEntity().getImageUrl())
.creatorProfileImageUrl(e.getUserJpaEntity().getAliasForUserJpaEntity().getImageUrl())
.alias(e.getUserJpaEntity().getAliasForUserJpaEntity().getValue())
.createdAt(e.getCreatedAt())
.isbn(e.getBookJpaEntity().getIsbn())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@
import konkuk.thip.common.util.DateUtil;
import konkuk.thip.feed.adapter.in.web.response.FeedShowAllResponse;
import konkuk.thip.feed.adapter.in.web.response.FeedShowMineResponse;
import konkuk.thip.feed.adapter.in.web.response.FeedShowUserInfoResponse;
import konkuk.thip.feed.application.port.out.dto.FeedQueryDto;
import konkuk.thip.user.domain.User;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.ReportingPolicy;

import java.util.List;
import java.util.Set;

@Mapper(
Expand All @@ -34,4 +37,13 @@ FeedShowAllResponse.FeedDto toFeedShowAllResponse(
expression = "java(DateUtil.formatBeforeTime(dto.createdAt()))"
)
FeedShowMineResponse.FeedDto toFeedShowMineResponse(FeedQueryDto dto);

@Mapping(target = "profileImageUrl", source = "feedOwner.alias.imageUrl")
@Mapping(target = "nickname", source = "feedOwner.nickname")
@Mapping(target = "aliasName", source = "feedOwner.alias.value")
@Mapping(target = "aliasColor", source = "feedOwner.alias.color")
@Mapping(target = "followerCount", source = "feedOwner.followerCount")
@Mapping(target = "totalFeedCount", source = "totalFeedCount")
@Mapping(target = "latestFollowerProfileImageUrls", source = "latestFollowerProfileImageUrls")
FeedShowUserInfoResponse toFeedShowUserInfoResponse(User feedOwner, int totalFeedCount, List<String> latestFollowerProfileImageUrls);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package konkuk.thip.feed.application.port.in;

import konkuk.thip.feed.adapter.in.web.response.FeedShowUserInfoResponse;

public interface FeedShowUserInfoUseCase {

FeedShowUserInfoResponse showMyInfoInFeeds(Long userId);

FeedShowUserInfoResponse showAnotherUserInfoInFeeds(Long anotherUserId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,7 @@ public interface FeedQueryPort {

CursorBasedList<FeedQueryDto> findMyFeedsByCreatedAt(Long userId, Cursor cursor);

int countFeedsByUserId(Long userId);
int countAllFeedsByUserId(Long userId);

int countPublicFeedsByUserId(Long userId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package konkuk.thip.feed.application.service;

import konkuk.thip.feed.adapter.in.web.response.FeedShowUserInfoResponse;
import konkuk.thip.feed.application.mapper.FeedQueryMapper;
import konkuk.thip.feed.application.port.in.FeedShowUserInfoUseCase;
import konkuk.thip.feed.application.port.out.FeedQueryPort;
import konkuk.thip.user.application.port.out.FollowingQueryPort;
import konkuk.thip.user.application.port.out.UserCommandPort;
import konkuk.thip.user.domain.User;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@RequiredArgsConstructor
@Service
public class FeedShowUserInfoService implements FeedShowUserInfoUseCase {

private static final int FOLLOWER_DISPLAY_LIMIT = 5;
private final UserCommandPort userCommandPort;
private final FollowingQueryPort followingQueryPort;
private final FeedQueryPort feedQueryPort;
private final FeedQueryMapper feedQueryMapper;

@Transactional(readOnly = true)
@Override
public FeedShowUserInfoResponse showMyInfoInFeeds(Long userId) {
// 1. User 찾기
User feedOwner = userCommandPort.findById(userId);

// 2. 해당 유저를 팔로우 하는 유저들을 프로필 이미지 정보 구하기
List<String> latestFollowerProfileImageUrls = followingQueryPort.getLatestFollowerImageUrls(userId, FOLLOWER_DISPLAY_LIMIT);

// 3. 내가 작성한 전체 피드 개수 구하기
int allFeedCount = feedQueryPort.countAllFeedsByUserId(userId);

return feedQueryMapper.toFeedShowUserInfoResponse(feedOwner, allFeedCount, latestFollowerProfileImageUrls);
}

@Transactional(readOnly = true)
@Override
public FeedShowUserInfoResponse showAnotherUserInfoInFeeds(Long anotherUserId) {
// 1. User 찾기
User feedOwner = userCommandPort.findById(anotherUserId);

// 2. 해당 유저를 팔로우 하는 유저들을 프로필 이미지 정보 구하기
List<String> latestFollowerProfileImageUrls = followingQueryPort.getLatestFollowerImageUrls(anotherUserId, FOLLOWER_DISPLAY_LIMIT);

// 3. 유저가 작성한 공개 피드 개수 구하기
int publicFeedCount = feedQueryPort.countPublicFeedsByUserId(anotherUserId);

return feedQueryMapper.toFeedShowUserInfoResponse(feedOwner, publicFeedCount, latestFollowerProfileImageUrls);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ private QPostQueryDto selectPostQueryDto() {
pageExpr(),
user.userId,
user.nickname,
user.imageUrl,
user.aliasForUserJpaEntity.imageUrl,
post.content,
post.likeCount,
post.commentCount,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,6 @@ public class UserJpaEntity extends BaseJpaEntity {
@Column(length = 60, nullable = false)
private String nickname;

@Column(name = "image_url", columnDefinition = "TEXT", nullable = false)
private String imageUrl;

Comment on lines -26 to -28
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

👍🏻 👍🏻

@Column(name = "oauth2_id", length = 50, nullable = false)
private String oauth2Id;

Expand All @@ -43,8 +40,7 @@ public class UserJpaEntity extends BaseJpaEntity {
public void updateFrom(User user) {
this.nickname = user.getNickname();
Assert.notNull(user.getAlias(), "Alias must not be null");
this.imageUrl = user.getAlias().getImageUrl();
this.role = UserRole.from(user.getUserRole());
this.followerCount = user.getFollowerCount();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ public class UserMapper {
public UserJpaEntity toJpaEntity(User user, AliasJpaEntity aliasJpaEntity) {
return UserJpaEntity.builder()
.nickname(user.getNickname())
.imageUrl(user.getAlias().getImageUrl())
.role(UserRole.from(user.getUserRole()))
.oauth2Id(user.getOauth2Id())
.followerCount(user.getFollowerCount())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,9 @@ public CursorBasedList<UserQueryDto> getFollowingByUserId(Long userId, String cu

return CursorBasedList.of(followingDtos, size, followingDto -> followingDto.createdAt().toString());
}

@Override
public List<String> getLatestFollowerImageUrls(Long userId, int size) {
return followingJpaRepository.findLatestFollowerImageUrls(userId, size);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,6 @@ public interface FollowingQueryRepository {

List<UserQueryDto> findFollowerDtosByUserIdBeforeCreatedAt(Long userId, LocalDateTime cursor, int size);
List<UserQueryDto> findFollowingDtosByUserIdBeforeCreatedAt(Long userId, LocalDateTime cursor, int size);

List<String> findLatestFollowerImageUrls(Long userId, int size);
}
Original file line number Diff line number Diff line change
Expand Up @@ -89,4 +89,22 @@ private List<UserQueryDto> findFollowDtos(Long userId, LocalDateTime cursor, int
.limit(size + 1)
.fetch();
}

@Override
public List<String> findLatestFollowerImageUrls(Long userId, int size) {
QFollowingJpaEntity following = QFollowingJpaEntity.followingJpaEntity;
QUserJpaEntity follower = QUserJpaEntity.userJpaEntity; // userId 를 팔로우하는 사람들(= follower)
QAliasJpaEntity alias = QAliasJpaEntity.aliasJpaEntity;

return jpaQueryFactory
.select(alias.imageUrl)
.from(following)
.join(following.userJpaEntity, follower)
.join(follower.aliasForUserJpaEntity, alias)
.where(following.followingUserJpaEntity.userId.eq(userId)
.and(following.status.eq(StatusType.ACTIVE)))
.orderBy(following.createdAt.desc())
.limit(size)
.fetch();
}
Comment on lines +93 to +109
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@
import konkuk.thip.common.util.CursorBasedList;
import konkuk.thip.user.application.port.out.dto.UserQueryDto;

import java.util.List;

public interface FollowingQueryPort {
CursorBasedList<UserQueryDto> getFollowersByUserId(Long userId, String cursor, int size);
CursorBasedList<UserQueryDto> getFollowingByUserId(Long userId, String cursor, int size);

List<String> getLatestFollowerImageUrls(Long userId, int size);
}

Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ void setUp() {
UserJpaEntity user = userJpaRepository.save(UserJpaEntity.builder()
.oauth2Id("kakao_432708231")
.nickname("User1")
.imageUrl("https://avatar1.jpg")
.role(UserRole.USER)
.aliasForUserJpaEntity(alias)
.build());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@ void setup() {
UserJpaEntity user = userJpaRepository.save(UserJpaEntity.builder()
.oauth2Id("kakao_432708231")
.nickname("User1")
.imageUrl("https://avatar1.jpg")
.role(UserRole.USER)
.aliasForUserJpaEntity(alias)
.build());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ void setUp() {
UserJpaEntity user = userJpaRepository.save(UserJpaEntity.builder()
.oauth2Id("kakao_432708231")
.nickname("User1")
.imageUrl("https://avatar1.jpg")
.role(UserRole.USER)
.aliasForUserJpaEntity(alias)
.build());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ void setUp() {
UserJpaEntity user = userJpaRepository.save(UserJpaEntity.builder()
.oauth2Id("kakao_432708231")
.nickname("User1")
.imageUrl("https://avatar1.jpg")
.role(UserRole.USER)
.aliasForUserJpaEntity(alias)
.build());
Expand Down
4 changes: 1 addition & 3 deletions src/test/java/konkuk/thip/common/util/TestEntityFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ public static CategoryJpaEntity createScienceCategory(AliasJpaEntity alias) {
public static UserJpaEntity createUser(AliasJpaEntity alias) {
return UserJpaEntity.builder()
.nickname("테스터")
.imageUrl("https://test.img")
.oauth2Id("kakao_12345678")
.aliasForUserJpaEntity(alias)
.role(UserRole.USER)
Expand All @@ -77,7 +76,6 @@ public static UserJpaEntity createUser(AliasJpaEntity alias) {
public static UserJpaEntity createUser(AliasJpaEntity alias, String nickname) {
return UserJpaEntity.builder()
.nickname(nickname)
.imageUrl("https://test.img")
.oauth2Id("kakao_12345678")
.aliasForUserJpaEntity(alias)
.role(UserRole.USER)
Expand Down Expand Up @@ -188,7 +186,7 @@ public static CommentLikeJpaEntity createCommentLike(CommentJpaEntity comment, U
.build();
}

public static FollowingJpaEntity createFollowing(UserJpaEntity followerUser,UserJpaEntity followingUser) {
public static FollowingJpaEntity createFollowing(UserJpaEntity followerUser, UserJpaEntity followingUser) {
return FollowingJpaEntity.builder()
.userJpaEntity(followerUser)
.followingUserJpaEntity(followingUser)
Expand Down
Loading