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 @@ -21,7 +21,6 @@ public record FeedItemResponse(
@Schema(description = "대표 이미지 URL", example = "https://example.com/image.jpg")
String imageUrl,
@Schema(description = "좋아요 여부", example = "true") boolean isLiked,
@Schema(description = "작성자 본인 여부", example = "false") boolean isMine,
@Schema(description = "작성자 정보") FeedAuthorResponse author) {}

@Schema(name = "FeedAuthorResponse", description = "피드 작성자 정보")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import org.clokey.domain.feed.query.FollowScope;
import org.clokey.history.entity.History;
import org.clokey.member.entity.QBlock;
import org.clokey.member.enums.Visibility;
import org.springframework.stereotype.Repository;

@Repository
Expand All @@ -42,6 +43,8 @@ public List<History> findFeeds(
.fetchJoin()
.where(
history.banned.isFalse(),
notSelfCondition(currentMemberId),
visibilityCondition(currentMemberId),
followScopeCondition(currentMemberId, followScope),
notBlockedCondition(currentMemberId),
styleFilterCondition(styleIds),
Expand All @@ -62,6 +65,8 @@ public List<History> findFeedsByIds(Long currentMemberId, List<Long> historyIds)
.fetchJoin()
.where(
history.banned.isFalse(),
notSelfCondition(currentMemberId),
visibilityCondition(currentMemberId),
history.id.in(historyIds),
notBlockedCondition(currentMemberId))
.fetch();
Expand Down Expand Up @@ -135,4 +140,20 @@ private BooleanExpression notBlockedCondition(Long currentMemberId) {
JPAExpressions.selectOne().from(blockCheck).where(blockCondition).exists())
.not();
}

private BooleanExpression visibilityCondition(Long currentMemberId) {
if (currentMemberId == null) {
return member.visibility.eq(Visibility.PUBLIC);
}

return member.visibility.eq(Visibility.PUBLIC).or(member.id.eq(currentMemberId));
}

private BooleanExpression notSelfCondition(Long currentMemberId) {
if (currentMemberId == null) {
return null;
}

return member.id.ne(currentMemberId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -143,9 +143,6 @@ public FeedListResponse getFeeds(
history.getCreatedAt(),
imageUrlMap.get(history.getId()),
likedHistoryIds.contains(history.getId()),
history.getMember()
.getId()
.equals(currentMember.getId()),
toAuthorResponse(
history.getMember(),
followedMemberIds.contains(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public BaseResponse<HistoryCreateResponse> createHistory(
return BaseResponse.onSuccess(GlobalBaseSuccessCode.CREATED, response);
}

@PatchMapping("/{historyId}")
@PutMapping("/{historyId}")
@Operation(
operationId = "History_updateHistory",
summary = "기록 수정",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
package org.clokey.domain.history.repository;

import io.lettuce.core.dynamic.annotation.Param;
import java.util.List;
import org.clokey.history.entity.HistoryHashtag;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

public interface HistoryHashtagRepository
extends JpaRepository<HistoryHashtag, Long>, HistoryHashtagRepositoryCustom {

@EntityGraph(attributePaths = "hashtag")
List<HistoryHashtag> findByHistoryId(Long historyId);

@Modifying
@Query("delete from HistoryHashtag hh where hh.history.id = :historyId")
void deleteAllByHistoryId(@Param("historyId") Long historyId);

@Query(
"""
select hh from HistoryHashtag hh
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,18 @@
import java.util.List;
import org.clokey.history.entity.HistoryStyle;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

public interface HistoryStyleRepository
extends JpaRepository<HistoryStyle, Long>, HistoryStyleRepositoryCustom {
List<HistoryStyle> findByHistoryId(Long historyId);

@Modifying
@Query("delete from HistoryStyle hs where hs.history.id = :historyId")
void deleteAllByHistoryId(@Param("historyId") Long historyId);

@Query(
"""
select new org.clokey.domain.history.repository.HistoryStyleRepository$HistoryStyleInfo(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,11 @@ public void updateHistory(Long historyId, HistoryUpdateRequest request) {
List<HistoryImage> existingImages = historyImageRepository.findByHistoryId(historyId);
Map<String, HistoryImage> existingImageMap =
existingImages.stream()
.collect(Collectors.toMap(HistoryImage::getImageUrl, Function.identity()));
.collect(
Collectors.toMap(
HistoryImage::getImageUrl,
Function.identity(),
(left, right) -> left));

Set<String> requestedImageUrls =
request.payloads().stream()
Expand Down Expand Up @@ -362,6 +366,7 @@ public void deleteHistory(Long historyId) {
clearStylesAndHashtags(historyId);
memberLikeRepository.deleteAllByHistoryId(historyId);
commentRepository.deleteAllByHistoryId(historyId);
commentRepository.flush();
reportRepository.deleteAllByTargetTypeAndTargetId(TargetType.HISTORY, historyId);

historyRepository.delete(history);
Expand Down Expand Up @@ -565,15 +570,11 @@ private void validateHistoryOwner(History history, Long memberId) {
}

private void clearStylesAndHashtags(Long historyId) {
List<HistoryStyle> styles = historyStyleRepository.findByHistoryId(historyId);
if (!styles.isEmpty()) {
historyStyleRepository.deleteAll(styles);
}
historyStyleRepository.deleteAllByHistoryId(historyId);
historyStyleRepository.flush();

List<HistoryHashtag> hashtags = historyHashtagRepository.findByHistoryId(historyId);
if (!hashtags.isEmpty()) {
historyHashtagRepository.deleteAll(hashtags);
}
historyHashtagRepository.deleteAllByHistoryId(historyId);
historyHashtagRepository.flush();
}

private void deleteClothTagsByHistoryImageIds(List<Long> historyImageIds) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ class 피드_조회_요청_시 {
LocalDateTime.parse("2025-01-01T12:00:00"),
"https://image.test/10.png",
true,
false,
new FeedListResponse.FeedAuthorResponse(
2L,
"clokey2",
Expand Down Expand Up @@ -77,7 +76,6 @@ class 피드_조회_요청_시 {
jsonPath("$.result.items[0].imageUrl")
.value("https://image.test/10.png"))
.andExpect(jsonPath("$.result.items[0].isLiked").value(true))
.andExpect(jsonPath("$.result.items[0].isMine").value(false))
.andExpect(jsonPath("$.result.items[0].author.memberId").value(2L))
.andExpect(jsonPath("$.result.items[0].author.nickname").value("clokey2"))
.andExpect(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,20 +59,26 @@ class 전체_피드_조회할_때 {
void setUp() {
Member member1 =
Member.createMember(
"testEmail10",
"testNickName10",
OauthInfo.createOauthInfo("oauth-current-10", OauthProvider.KAKAO));
"testEmail1",
"testNickName1",
OauthInfo.createOauthInfo("testOauthId1", OauthProvider.KAKAO));
Member member2 =
Member.createMember(
"testEmail20",
"testNickName20",
OauthInfo.createOauthInfo("oauth-a-20", OauthProvider.KAKAO));
"testEmail2",
"testNickName2",
OauthInfo.createOauthInfo("testOauthId2", OauthProvider.KAKAO));
Member member3 =
Member.createMember(
"testEmail30",
"testNickName30",
OauthInfo.createOauthInfo("oauth-b-30", OauthProvider.KAKAO));
memberRepository.saveAll(List.of(member1, member2, member3));
"testEmail3",
"testNickName3",
OauthInfo.createOauthInfo("testOauthId3", OauthProvider.KAKAO));
Member member4 =
Member.createMember(
"testEmail4",
"testNickName4",
OauthInfo.createOauthInfo("testOauthId4", OauthProvider.KAKAO));
member4.changeVisibility();
memberRepository.saveAll(List.of(member1, member2, member3, member4));
given(memberUtil.getCurrentMember()).willReturn(member1);

Situation situation = Situation.createSituation("daily");
Expand All @@ -90,15 +96,22 @@ void setUp() {
History.createHistory(LocalDate.of(2025, 1, 4), "B1", member3, situation);
History history3_2 =
History.createHistory(LocalDate.of(2025, 1, 5), "B2", member3, situation);
historyRepository.saveAll(List.of(history3_1, history3_2));

History history4_1 =
History.createHistory(LocalDate.of(2025, 1, 4), "C1", member4, situation);
History history4_2 =
History.createHistory(LocalDate.of(2025, 1, 5), "C2", member4, situation);
historyRepository.saveAll(List.of(history3_1, history3_2, history4_1, history4_2));

historyImageRepository.saveAll(
List.of(
HistoryImage.createHistoryImage("img-a1", history2_1),
HistoryImage.createHistoryImage("img-a2", history2_2),
HistoryImage.createHistoryImage("img-a3", history2_3),
HistoryImage.createHistoryImage("img-b1", history3_1),
HistoryImage.createHistoryImage("img-b2", history3_2)));
HistoryImage.createHistoryImage("img-b2", history3_2),
HistoryImage.createHistoryImage("img-c1", history4_1),
HistoryImage.createHistoryImage("img-c2", history4_2)));
}

@Test
Expand Down Expand Up @@ -187,9 +200,10 @@ void setUp() {
@Test
void 차단한_사용자의_피드는_노출되지_않는다() {
// given
Member member1 = memberRepository.findByNickname("testNickName10").orElseThrow();
Member member2 = memberRepository.findByNickname("testNickName20").orElseThrow();
Member member1 = memberRepository.findByNickname("testNickName1").orElseThrow();
Member member2 = memberRepository.findByNickname("testNickName2").orElseThrow();
blockRepository.save(Block.createBlock(member1, member2));

// when
FeedListResponse response =
feedService.getFeeds(FollowScope.ALL, List.of(), List.of(), 3, null);
Expand All @@ -215,6 +229,36 @@ void setUp() {
assertThat(response.items()).hasSize(2);
assertThat(response.hasNext()).isFalse();
}

@Test
void 비공개_계정의_피드는_전체_피드에서_노출되지_않는다() {
// when
FeedListResponse response =
feedService.getFeeds(FollowScope.ALL, List.of(), List.of(), 3, null);

// then
Map<String, Long> historyIds =
historyRepository.findAll().stream()
.collect(
Collectors.toMap(
History::getContent,
History::getId,
(left, right) -> left));
Long history3_1 = historyIds.get("B1");
Long history3_2 = historyIds.get("B2");
Long history2_3 = historyIds.get("A3");
Long history4_1 = historyIds.get("C1");
Long history4_2 = historyIds.get("C2");

assertThat(response.items()).hasSize(3);
List<Long> feedIds =
response.items().stream()
.map(FeedListResponse.FeedItemResponse::feedId)
.toList();
assertThat(feedIds).containsExactly(history3_2, history2_3, history3_1);
assertThat(feedIds).doesNotContain(history4_1, history4_2);
assertThat(response.hasNext()).isTrue();
}
}

@Nested
Expand All @@ -237,7 +281,13 @@ void setUp() {
"testEmail3",
"testNickName3",
OauthInfo.createOauthInfo("oauth-b", OauthProvider.KAKAO));
memberRepository.saveAll(List.of(member1, member2, member3));
Member member4 =
Member.createMember(
"testEmail4",
"testNickName4",
OauthInfo.createOauthInfo("testOauthId4", OauthProvider.KAKAO));
member4.changeVisibility();
memberRepository.saveAll(List.of(member1, member2, member3, member4));
given(memberUtil.getCurrentMember()).willReturn(member1);

Situation situation = Situation.createSituation("daily");
Expand All @@ -255,20 +305,27 @@ void setUp() {
History.createHistory(LocalDate.of(2025, 1, 4), "B1", member3, situation);
History history3_2 =
History.createHistory(LocalDate.of(2025, 1, 5), "B2", member3, situation);
historyRepository.saveAll(List.of(history3_1, history3_2));
History history4_1 =
History.createHistory(LocalDate.of(2025, 1, 4), "C1", member4, situation);
History history4_2 =
History.createHistory(LocalDate.of(2025, 1, 5), "C2", member4, situation);
historyRepository.saveAll(List.of(history3_1, history3_2, history4_1, history4_2));

historyImageRepository.saveAll(
List.of(
HistoryImage.createHistoryImage("img-a1", history2_1),
HistoryImage.createHistoryImage("img-a2", history2_2),
HistoryImage.createHistoryImage("img-a3", history2_3),
HistoryImage.createHistoryImage("img-b1", history3_1),
HistoryImage.createHistoryImage("img-b2", history3_2)));
HistoryImage.createHistoryImage("img-b2", history3_2),
HistoryImage.createHistoryImage("img-c1", history4_1),
HistoryImage.createHistoryImage("img-c2", history4_2)));

followRepository.saveAll(
List.of(
Follow.createFollow(member1, member2),
Follow.createFollow(member1, member3)));
Follow.createFollow(member1, member3),
Follow.createFollow(member1, member4)));

memberLikeRepository.save(MemberLike.createMemberLike(member1, history3_2));
}
Expand Down Expand Up @@ -331,5 +388,35 @@ void setUp() {
assertThat(second.hasNext()).isFalse();
assertThat(second.nextCursor()).isNull();
}

@Test
void 비공개_계정의_피드는_팔로우하더라도_노출되지_않는다() {
// when
FeedListResponse response =
feedService.getFeeds(FollowScope.FOLLOWING, List.of(), List.of(), 3, null);

// then
Map<String, Long> historyIds =
historyRepository.findAll().stream()
.collect(
Collectors.toMap(
History::getContent,
History::getId,
(left, right) -> left));
Long history3_1 = historyIds.get("B1");
Long history3_2 = historyIds.get("B2");
Long history2_3 = historyIds.get("A3");
Long history4_1 = historyIds.get("C1");
Long history4_2 = historyIds.get("C2");

assertThat(response.items()).hasSize(3);
List<Long> feedIds =
response.items().stream()
.map(FeedListResponse.FeedItemResponse::feedId)
.toList();
assertThat(feedIds).containsExactly(history3_2, history3_1, history2_3);
assertThat(feedIds).doesNotContain(history4_1, history4_2);
assertThat(response.hasNext()).isTrue();
}
}
}
Loading