Skip to content
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 @@ -9,6 +9,7 @@
import jakarta.validation.constraints.NotBlank;
import konkuk.thip.common.dto.BaseResponse;
import konkuk.thip.common.security.annotation.UserId;
import konkuk.thip.user.application.port.in.*;
import konkuk.thip.user.application.port.in.dto.UserReactionType;
import konkuk.thip.user.adapter.in.web.response.*;
import konkuk.thip.common.swagger.annotation.ExceptionDescription;
Expand All @@ -18,13 +19,7 @@
import konkuk.thip.user.adapter.in.web.response.UserVerifyNicknameResponse;
import konkuk.thip.user.adapter.in.web.response.UserIsFollowingResponse;
import konkuk.thip.user.adapter.in.web.response.UserViewAliasChoiceResponse;
import konkuk.thip.user.application.port.in.UserGetFollowUsecase;
import konkuk.thip.user.application.port.in.UserVerifyNicknameUseCase;
import konkuk.thip.user.application.port.in.UserIsFollowingUsecase;
import konkuk.thip.user.application.port.in.UserSearchUsecase;
import konkuk.thip.user.application.port.in.UserViewAliasChoiceUseCase;
import konkuk.thip.user.application.port.in.dto.UserSearchQuery;
import konkuk.thip.user.application.port.in.UserMyPageUseCase;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
Expand All @@ -48,6 +43,7 @@ public class UserQueryController {
private final UserVerifyNicknameUseCase userVerifyNicknameUseCase;
private final UserSearchUsecase userSearchUsecase;
private final UserMyPageUseCase userMyPageUseCase;
private final UserShowFollowingRecentWritersUseCase userShowFollowingRecentWritersUseCase;

@Operation(
summary = "닉네임 중복 확인",
Expand Down Expand Up @@ -160,4 +156,15 @@ public BaseResponse<Long> showUserMyId(
@Parameter(hidden = true) @UserId final Long userId) {
return BaseResponse.ok(userId);
}

@Operation(
summary = "최근에 공개 피드를 작성한 내 팔로잉 리스트(= 내 띱 리스트) 조회",
description = "내가 팔로잉 하는 사람들 중, 최근에 공개 피드를 작성한 사람들의 정보를 최대 10명 반환합니다."
)
@GetMapping("/users/my-followings/recent-feeds")
public BaseResponse<UserFollowingRecentWritersResponse> showMyFollowingRecentWriters(
@Parameter(hidden = true) @UserId Long userId
) {
return BaseResponse.ok(userShowFollowingRecentWritersUseCase.showMyFollowingRecentWriters(userId));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package konkuk.thip.user.adapter.in.web.response;

import io.swagger.v3.oas.annotations.media.Schema;

import java.util.List;

@Schema(description = "전체 피드 조회 상단에서의 내 띱 목록 응답 DTO")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

와우 친절띠니 LGTM

public record UserFollowingRecentWritersResponse(
@Schema(description = "내가 팔로잉하는 사람들 중, 최근에 공개 피드를 작성한 사람들")
List<RecentWriter> recentWriters
) {
public record RecentWriter(
@Schema(description = "최근에 피드를 작성한 사람의 userId 값")
Long userId,

@Schema(description = "최근에 피드를 작성한 사람의 nickname 값")
String nickname,

@Schema(description = "최근에 피드를 작성한 사람의 profileImageUrl 값")
String profileImageUrl
) {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@ public CursorBasedList<ReactionQueryDto> findBothReactionsByUserId(Long userId,
);
}

@Override
public List<UserQueryDto> findRecentFeedWritersOfMyFollowings(Long userId, int size) {
return userJpaRepository.findFeedWritersOfMyFollowingsOrderByCreatedAtDesc(userId, size);
}

private CursorBasedList<ReactionQueryDto> getReactions(Long userId, Cursor cursor, ReactionQueryFunction reactionQueryFunction) {
LocalDateTime cursorLocalDateTime = cursor.isFirstRequest() ? null : cursor.getLocalDateTime(0);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ public interface UserQueryRepository {

List<ReactionQueryDto> findLikeAndCommentByUserId(Long userId, LocalDateTime cursorLocalDateTime, Integer size, String likeLabel, String commentLabel);

}
List<UserQueryDto> findFeedWritersOfMyFollowingsOrderByCreatedAtDesc(Long userId, Integer size);
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@
import com.querydsl.jpa.impl.JPAQueryFactory;
import konkuk.thip.comment.adapter.out.jpa.QCommentJpaEntity;
import konkuk.thip.common.entity.StatusType;
import konkuk.thip.feed.adapter.out.jpa.QFeedJpaEntity;
import konkuk.thip.post.adapter.out.jpa.QPostJpaEntity;
import konkuk.thip.post.adapter.out.jpa.QPostLikeJpaEntity;
import konkuk.thip.room.adapter.out.jpa.QRoomJpaEntity;
import konkuk.thip.room.adapter.out.jpa.QRoomParticipantJpaEntity;
import konkuk.thip.user.adapter.out.jpa.QAliasJpaEntity;
import konkuk.thip.user.adapter.out.jpa.QFollowingJpaEntity;
import konkuk.thip.user.adapter.out.jpa.QUserJpaEntity;
import konkuk.thip.user.application.port.out.dto.QReactionQueryDto;
import konkuk.thip.user.application.port.out.dto.QUserQueryDto;
Expand Down Expand Up @@ -159,5 +161,36 @@ public List<ReactionQueryDto> findLikeAndCommentByUserId(Long userId, LocalDateT
.toList();
}

@Override
public List<UserQueryDto> findFeedWritersOfMyFollowingsOrderByCreatedAtDesc(Long userId, Integer size) {
QFollowingJpaEntity following = QFollowingJpaEntity.followingJpaEntity;
QUserJpaEntity writer = new QUserJpaEntity("writer");
QFeedJpaEntity feed = QFeedJpaEntity.feedJpaEntity;
QAliasJpaEntity alias = QAliasJpaEntity.aliasJpaEntity;

return queryFactory
.select(new QUserQueryDto(
writer.userId,
writer.nickname,
alias.imageUrl
))
.from(following)
.join(following.followingUserJpaEntity, writer) // writer : 나에게 팔로잉 당하는 사람들
.join(feed).on(feed.userJpaEntity.eq(writer))
.join(writer.aliasForUserJpaEntity, alias)
.where(
following.userJpaEntity.userId.eq(userId) // 내가 팔로잉 하는 사람
.and(writer.status.eq(StatusType.ACTIVE)) // 그 중 아직 회원인 사람
.and(feed.status.eq(StatusType.ACTIVE)) // 그 중 삭제 X & 공개피드를 작성한 사람
.and(feed.isPublic.isTrue())
)
.groupBy( // 그룹핑
writer.userId,
writer.nickname,
alias.imageUrl
)
Comment on lines +181 to +191
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.

굿굿 꼼꼼하네욥 👍🏻

.orderBy(feed.createdAt.max().desc()) // 피드 작성 시각 중 최대값 == 가장 최근에 작성한 피드
.limit(size) // 무한 스크롤 X
.fetch();
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package konkuk.thip.user.application.mapper;

import konkuk.thip.user.adapter.in.web.response.UserFollowingRecentWritersResponse;
import konkuk.thip.user.adapter.in.web.response.UserSearchResponse;
import konkuk.thip.user.application.port.out.dto.UserQueryDto;
import org.mapstruct.Mapper;
Expand All @@ -12,6 +13,12 @@ public interface UserQueryMapper {
// List<QueryDto> -> List<DTO>
List<UserSearchResponse.UserDto> toUserDtoList(List<UserQueryDto> userQueryDtos);



}
// 단건 매핑: UserQueryDto -> RecentWriter
UserFollowingRecentWritersResponse.RecentWriter toRecentWriter(UserQueryDto dto);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

오 이거는 미리 만들어두신건가여??

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.

넵 맞습니다! map struct 가 사용할 매핑 메서드를 미리 인터페이스에 정의해놓았습니다

// 리스트 매핑: List<UserQueryDto> -> List<RecentWriter>
List<UserFollowingRecentWritersResponse.RecentWriter> toRecentWriterList(List<UserQueryDto> dtos);
// 래핑: List<UserQueryDto> -> UserFollowingRecentWritersResponse
default UserFollowingRecentWritersResponse toRecentWriterResponses(List<UserQueryDto> dtos) {
return new UserFollowingRecentWritersResponse(toRecentWriterList(dtos));
}
Comment on lines +19 to +23
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.

오호 dto 생성 책임도 mapper가 갖도록 했네요! 좋습니다! 서비스 로직이 가벼워질 것 같아요

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package konkuk.thip.user.application.port.in;

import konkuk.thip.user.adapter.in.web.response.UserFollowingRecentWritersResponse;

public interface UserShowFollowingRecentWritersUseCase {

UserFollowingRecentWritersResponse showMyFollowingRecentWriters(Long userId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,6 @@ public interface UserQueryPort {
CursorBasedList<ReactionQueryDto> findCommentReactionsByUserId(Long userId, Cursor cursor, String label);

CursorBasedList<ReactionQueryDto> findBothReactionsByUserId(Long userId, Cursor cursor, String likeLabel, String commentLabel);
}

List<UserQueryDto> findRecentFeedWritersOfMyFollowings(Long userId, int size);
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,18 @@ public record UserQueryDto(Long userId,
Assert.notNull(userId, "userId must not be null");
Assert.notNull(nickname, "nickname must not be null");
Assert.notNull(profileImageUrl, "profileImageUrl must not be null");
Assert.notNull(aliasName, "aliasName must not be null");
Assert.notNull(aliasColor, "aliasColor must not be null");
// Assert.notNull(aliasName, "aliasName must not be null");
// Assert.notNull(aliasColor, "aliasColor must not be null");
// Assert.notNull(followerCount, "followerCount must not be null"); // 내 팔로잉 목록 조회에서는 필요 x
Assert.notNull(createdAt, "createdAt must not be null");
// Assert.notNull(createdAt, "createdAt must not be null");
Comment on lines +21 to +24
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.

👍🏻

}
}

@QueryProjection
public UserQueryDto(
Long userId,
String nickname,
String profileImageUrl
) {
this(userId, nickname, profileImageUrl, null, null, null, null);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package konkuk.thip.user.application.service;

import konkuk.thip.user.adapter.in.web.response.UserFollowingRecentWritersResponse;
import konkuk.thip.user.application.mapper.UserQueryMapper;
import konkuk.thip.user.application.port.in.UserShowFollowingRecentWritersUseCase;
import konkuk.thip.user.application.port.out.UserQueryPort;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
public class UserShowFollowingRecentWritersService implements UserShowFollowingRecentWritersUseCase {

private static final int SIZE = 10;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

p2: 페이징 처리 필요하지않으면 사용하지 않으니 빼셔도 좋을듯합니다!

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.

이 부분은 아마 기본 사이즈라도 유지보수하기 쉽게 서비스 로직에서 LIMIT도 넘겨주기로 통일했던 것 같습니다!

private final UserQueryPort userQueryPort;
private final UserQueryMapper userQueryMapper;

@Override
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

p2: 트랜잭션 어노테이션 누락된것같습니다!

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.

엇 서비스에서 도메인 조회가 없어서 트랜잭션을 추가하진 않았는데 팀 컨벤션 차원으로 추가해보겟습니다!

@Transactional(readOnly = true)
public UserFollowingRecentWritersResponse showMyFollowingRecentWriters(Long userId) {
return userQueryMapper.toRecentWriterResponses(userQueryPort.findRecentFeedWritersOfMyFollowings(userId, SIZE));
}
}
Loading