From ba620e1e493f315b269c00ef7a7c7f5f9b5629a2 Mon Sep 17 00:00:00 2001 From: TTaiJin Date: Mon, 14 Apr 2025 12:13:03 +0900 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20Diary=20=EB=8B=A4=EA=B1=B4=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EC=8B=9C=20=EC=9E=91=EC=84=B1=EC=9E=90=20?= =?UTF-8?q?=EC=A0=95=EB=B3=B4=20=EA=B0=99=EC=9D=B4=20=EB=B0=98=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/diary/dto/DiaryResponseDto.java | 27 ++++ .../domain/diary/dto/DiaryWithAuthorDto.java | 10 ++ .../repository/CustomDiaryRepository.java | 12 +- .../repository/CustomDiaryRepositoryImpl.java | 68 ++++------ .../domain/diary/service/DiaryService.java | 125 +++++++++++------- .../diary/service/DiaryServiceTest.java | 19 +-- .../example/log4u/fixture/DiaryFixture.java | 47 +++++++ 7 files changed, 197 insertions(+), 111 deletions(-) create mode 100644 src/main/java/com/example/log4u/domain/diary/dto/DiaryWithAuthorDto.java diff --git a/src/main/java/com/example/log4u/domain/diary/dto/DiaryResponseDto.java b/src/main/java/com/example/log4u/domain/diary/dto/DiaryResponseDto.java index 20472f0b..ef23d910 100644 --- a/src/main/java/com/example/log4u/domain/diary/dto/DiaryResponseDto.java +++ b/src/main/java/com/example/log4u/domain/diary/dto/DiaryResponseDto.java @@ -86,4 +86,31 @@ public static DiaryResponseDto of( .build(); } + // DiaryWithAuthorDto 전용 메서드 + public static DiaryResponseDto of( + DiaryWithAuthorDto dto, + List media, + List hashtagList + ) { + return DiaryResponseDto.builder() + .diaryId(dto.diary().getDiaryId()) + .authorId(dto.diary().getUserId()) + .authorNickname(dto.authorNickname()) + .authorProfileImage(dto.authorProfileImage()) + .location(LocationDto.of(dto.diary().getLocation())) + .title(dto.diary().getTitle()) + .content(dto.diary().getContent()) + .weatherInfo(dto.diary().getWeatherInfo().name()) + .visibility(dto.diary().getVisibility().name()) + .createdAt(dto.diary().getCreatedAt()) + .updatedAt(dto.diary().getUpdatedAt()) + .thumbnailUrl(dto.diary().getThumbnailUrl()) + .likeCount(dto.diary().getLikeCount()) + .mediaList(media.stream() + .map(MediaResponseDto::of).toList()) + .hashtagList(hashtagList) + .isLiked(false) + .build(); + } + } diff --git a/src/main/java/com/example/log4u/domain/diary/dto/DiaryWithAuthorDto.java b/src/main/java/com/example/log4u/domain/diary/dto/DiaryWithAuthorDto.java new file mode 100644 index 00000000..e3ca3081 --- /dev/null +++ b/src/main/java/com/example/log4u/domain/diary/dto/DiaryWithAuthorDto.java @@ -0,0 +1,10 @@ +package com.example.log4u.domain.diary.dto; + +import com.example.log4u.domain.diary.entity.Diary; + +public record DiaryWithAuthorDto( + Diary diary, + String authorNickname, + String authorProfileImage +) { +} diff --git a/src/main/java/com/example/log4u/domain/diary/repository/CustomDiaryRepository.java b/src/main/java/com/example/log4u/domain/diary/repository/CustomDiaryRepository.java index 733e24ef..e13cc353 100644 --- a/src/main/java/com/example/log4u/domain/diary/repository/CustomDiaryRepository.java +++ b/src/main/java/com/example/log4u/domain/diary/repository/CustomDiaryRepository.java @@ -2,31 +2,25 @@ import java.util.List; -import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import com.example.log4u.domain.diary.SortType; import com.example.log4u.domain.diary.VisibilityType; +import com.example.log4u.domain.diary.dto.DiaryWithAuthorDto; import com.example.log4u.domain.diary.entity.Diary; import com.example.log4u.domain.map.dto.response.DiaryMarkerResponseDto; public interface CustomDiaryRepository { - Page searchDiaries( - String keyword, - List visibilities, - SortType sort, - Pageable pageable - ); - Slice findByUserIdAndVisibilityInAndCursorId( + Slice findByUserIdAndVisibilityInAndCursorId( Long userId, List visibilities, Long cursorId, Pageable pageable ); - Slice searchDiariesByCursor( + Slice searchDiariesByCursor( String keyword, List visibilities, SortType sort, diff --git a/src/main/java/com/example/log4u/domain/diary/repository/CustomDiaryRepositoryImpl.java b/src/main/java/com/example/log4u/domain/diary/repository/CustomDiaryRepositoryImpl.java index cfc50ad1..9244982d 100644 --- a/src/main/java/com/example/log4u/domain/diary/repository/CustomDiaryRepositoryImpl.java +++ b/src/main/java/com/example/log4u/domain/diary/repository/CustomDiaryRepositoryImpl.java @@ -2,8 +2,6 @@ import java.util.List; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.stereotype.Repository; @@ -12,18 +10,18 @@ import com.example.log4u.common.util.PageableUtil; import com.example.log4u.domain.diary.SortType; import com.example.log4u.domain.diary.VisibilityType; +import com.example.log4u.domain.diary.dto.DiaryWithAuthorDto; import com.example.log4u.domain.diary.entity.Diary; import com.example.log4u.domain.diary.entity.QDiary; import com.example.log4u.domain.hashtag.entity.QDiaryHashtag; import com.example.log4u.domain.hashtag.entity.QHashtag; import com.example.log4u.domain.like.entity.QLike; import com.example.log4u.domain.map.dto.response.DiaryMarkerResponseDto; -import com.example.log4u.domain.media.entity.QMedia; +import com.example.log4u.domain.user.entity.QUser; import com.querydsl.core.types.OrderSpecifier; import com.querydsl.core.types.Projections; import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.jpa.JPAExpressions; -import com.querydsl.jpa.impl.JPAQuery; import com.querydsl.jpa.impl.JPAQueryFactory; import lombok.RequiredArgsConstructor; @@ -38,42 +36,10 @@ public class CustomDiaryRepositoryImpl implements CustomDiaryRepository { private final QLike like = QLike.like; private final QDiaryHashtag diaryHashtag = QDiaryHashtag.diaryHashtag; private final QHashtag hashtag = QHashtag.hashtag; - private final QMedia media = QMedia.media; + private final QUser user = QUser.user; @Override - public Page searchDiaries( - String keyword, - List visibilities, - SortType sort, - Pageable pageable - ) { - // 조건 생성 - BooleanExpression condition = createSearchCondition(keyword, visibilities, null); - - // 쿼리 실행 - JPAQuery query = queryFactory - .selectFrom(diary) - .where(condition); - - // 전체 카운트 조회 - Long total = queryFactory - .select(diary.count()) - .from(diary) - .where(condition) - .fetchOne(); - - // 데이터 조회 - List content = query - .orderBy(createOrderSpecifier(sort)) - .offset(pageable.getOffset()) - .limit(pageable.getPageSize()) - .fetch(); - - return new PageImpl<>(content, pageable, total != null ? total : 0); - } - - @Override - public Slice findByUserIdAndVisibilityInAndCursorId( + public Slice findByUserIdAndVisibilityInAndCursorId( Long userId, List visibilities, Long cursorId, @@ -104,9 +70,15 @@ public Slice findByUserIdAndVisibilityInAndCursorId( } } - // limit + 1로 다음 페이지 존재 여부 확인 - List content = queryFactory - .selectFrom(diary) + // 다이어리와 작성자 정보를 함께 조회 + List content = queryFactory + .select(Projections.constructor(DiaryWithAuthorDto.class, + diary, + user.nickname, + user.profileImage + )) + .from(diary) + .join(user).on(diary.userId.eq(user.userId)) .where(condition) .orderBy(diary.createdAt.desc(), diary.diaryId.desc()) .limit(pageable.getPageSize() + 1) @@ -117,7 +89,7 @@ public Slice findByUserIdAndVisibilityInAndCursorId( } @Override - public Slice searchDiariesByCursor( + public Slice searchDiariesByCursor( String keyword, List visibilities, SortType sort, @@ -162,9 +134,15 @@ public Slice searchDiariesByCursor( } } - // limit + 1로 다음 페이지 존재 여부 확인 - List content = queryFactory - .selectFrom(diary) + // 다이어리와 작성자 정보를 함께 조회 + List content = queryFactory + .select(Projections.constructor(DiaryWithAuthorDto.class, + diary, + user.nickname, + user.profileImage + )) + .from(diary) + .join(user).on(diary.userId.eq(user.userId)) .where(condition) .orderBy(createOrderSpecifier(sort)) .limit(pageable.getPageSize() + 1) diff --git a/src/main/java/com/example/log4u/domain/diary/service/DiaryService.java b/src/main/java/com/example/log4u/domain/diary/service/DiaryService.java index e283b03a..8748d5e6 100644 --- a/src/main/java/com/example/log4u/domain/diary/service/DiaryService.java +++ b/src/main/java/com/example/log4u/domain/diary/service/DiaryService.java @@ -16,6 +16,7 @@ import com.example.log4u.domain.diary.VisibilityType; import com.example.log4u.domain.diary.dto.DiaryRequestDto; import com.example.log4u.domain.diary.dto.DiaryResponseDto; +import com.example.log4u.domain.diary.dto.DiaryWithAuthorDto; import com.example.log4u.domain.diary.entity.Diary; import com.example.log4u.domain.diary.exception.NotFoundDiaryException; import com.example.log4u.domain.diary.exception.OwnerAccessDeniedException; @@ -54,27 +55,27 @@ public Slice searchDiariesByCursor( Long cursorId, int size ) { - Slice diaries = diaryRepository.searchDiariesByCursor( + Slice diaries = diaryRepository.searchDiariesByCursor( keyword, List.of(VisibilityType.PUBLIC), sort, cursorId != null ? cursorId : Long.MAX_VALUE, PageRequest.of(0, size) ); - return mapToDtoSlice(diaries); + return this.mapDiaryWithAuthorToDtoSlice(diaries); } // 다이어리 목록 (프로필 페이지) @Transactional(readOnly = true) public Slice getDiaryResponseDtoSlice(Long userId, Long targetUserId, Long cursorId, int size) { List visibilities = determineAccessibleVisibilities(userId, targetUserId); - Slice diaries = diaryRepository.findByUserIdAndVisibilityInAndCursorId( + Slice diaries = diaryRepository.findByUserIdAndVisibilityInAndCursorId( targetUserId, visibilities, cursorId != null ? cursorId : Long.MAX_VALUE, PageRequest.of(0, size) ); - return mapToDtoSlice(diaries); + return this.mapDiaryWithAuthorToDtoSlice(diaries); } // 다이어리 수정 @@ -90,6 +91,46 @@ public void deleteDiary(Diary diary) { diaryRepository.delete(diary); } + @Transactional(readOnly = true) + public PageResponse getMyDiariesByCursor(Long userId, VisibilityType visibilityType, + Long cursorId, int size) { + List visibilities = + visibilityType == null ? List.of(VisibilityType.PUBLIC, VisibilityType.PRIVATE, VisibilityType.FOLLOWER) : + List.of(visibilityType); + + Slice diaries = diaryRepository.findByUserIdAndVisibilityInAndCursorId( + userId, + visibilities, + cursorId != null ? cursorId : Long.MAX_VALUE, + PageRequest.of(0, size) + ); + + Slice dtoSlice = this.mapDiaryWithAuthorToDtoSlice(diaries); + + Long nextCursor = !dtoSlice.isEmpty() ? dtoSlice.getContent().getLast().diaryId() : null; + + return PageResponse.of(dtoSlice, nextCursor); + } + + @Transactional(readOnly = true) + public PageResponse getLikeDiariesByCursor(Long userId, Long targetUserId, Long cursorId, + int size) { + List visibilities = determineAccessibleVisibilities(userId, targetUserId); + + Slice diaries = diaryRepository.getLikeDiarySliceByUserId( + targetUserId, + visibilities, + cursorId != null ? cursorId : Long.MAX_VALUE, + PageRequest.of(0, size) + ); + + Slice dtoSlice = this.mapDiarySliceToDtoSlice(diaries); + + Long nextCursor = !dtoSlice.isEmpty() ? dtoSlice.getContent().getLast().diaryId() : null; + + return PageResponse.of(dtoSlice, nextCursor); + } + private Diary findDiaryOrThrow(Long diaryId) { return diaryRepository.findById(diaryId) .orElseThrow(NotFoundDiaryException::new); @@ -97,18 +138,46 @@ private Diary findDiaryOrThrow(Long diaryId) { // Page용 매핑 메서드 private Page mapToDtoPage(Page page) { - List content = getDiaryResponsesWithMediaAndHashtags(page.getContent()); + List content = getDiaryResponse(page.getContent()); return new PageImpl<>(content, page.getPageable(), page.getTotalElements()); } // Slice용 매핑 메서드 - private Slice mapToDtoSlice(Slice slice) { - List content = getDiaryResponsesWithMediaAndHashtags(slice.getContent()); + private Slice mapDiarySliceToDtoSlice(Slice slice) { + List content = getDiaryResponse(slice.getContent()); + return new SliceImpl<>(content, slice.getPageable(), slice.hasNext()); + } + + // DiaryWithAuthor Slice 매핑 메서드 + private Slice mapDiaryWithAuthorToDtoSlice(Slice slice) { + List content = getDiaryResponseWithAuthor(slice.getContent()); return new SliceImpl<>(content, slice.getPageable(), slice.hasNext()); } + // 다이어리 + 작성자 + 미디어 + 해시태그 같이 반환 + private List getDiaryResponseWithAuthor(List diaryWithAuthorDtoList) { + if (diaryWithAuthorDtoList.isEmpty()) { + return List.of(); + } + + List diaryIds = diaryWithAuthorDtoList.stream() + .map(dto -> dto.diary().getDiaryId()) + .toList(); + + Map> mediaMap = mediaService.getMediaMapByDiaryIds(diaryIds); + Map> hashtagMap = hashtagService.getHashtagMapByDiaryIds(diaryIds); + + return diaryWithAuthorDtoList.stream() + .map(dto -> DiaryResponseDto.of( + dto, + mediaMap.getOrDefault(dto.diary().getDiaryId(), List.of()), + hashtagMap.getOrDefault(dto.diary().getDiaryId(), List.of()) + )) + .toList(); + } + // 다이어리 + 미디어 + 해시태그 같이 반환 - private List getDiaryResponsesWithMediaAndHashtags(List diaries) { + private List getDiaryResponse(List diaries) { if (diaries.isEmpty()) { return List.of(); } @@ -204,44 +273,4 @@ public void checkDiaryExists(Long diaryId) { throw new NotFoundDiaryException(); } } - - @Transactional(readOnly = true) - public PageResponse getMyDiariesByCursor(Long userId, VisibilityType visibilityType, - Long cursorId, int size) { - List visibilities = - visibilityType == null ? List.of(VisibilityType.PUBLIC, VisibilityType.PRIVATE, VisibilityType.FOLLOWER) : - List.of(visibilityType); - - Slice diaries = diaryRepository.findByUserIdAndVisibilityInAndCursorId( - userId, - visibilities, - cursorId != null ? cursorId : Long.MAX_VALUE, - PageRequest.of(0, size) - ); - - Slice dtoSlice = mapToDtoSlice(diaries); - - Long nextCursor = !dtoSlice.isEmpty() ? dtoSlice.getContent().getLast().diaryId() : null; - - return PageResponse.of(dtoSlice, nextCursor); - } - - @Transactional(readOnly = true) - public PageResponse getLikeDiariesByCursor(Long userId, Long targetUserId, Long cursorId, - int size) { - List visibilities = determineAccessibleVisibilities(userId, targetUserId); - - Slice diaries = diaryRepository.getLikeDiarySliceByUserId( - targetUserId, - visibilities, - cursorId != null ? cursorId : Long.MAX_VALUE, - PageRequest.of(0, size) - ); - - Slice dtoSlice = mapToDtoSlice(diaries); - - Long nextCursor = !dtoSlice.isEmpty() ? dtoSlice.getContent().getLast().diaryId() : null; - - return PageResponse.of(dtoSlice, nextCursor); - } } \ No newline at end of file diff --git a/src/test/java/com/example/log4u/domain/diary/service/DiaryServiceTest.java b/src/test/java/com/example/log4u/domain/diary/service/DiaryServiceTest.java index 27ec4cfc..e9a8975a 100644 --- a/src/test/java/com/example/log4u/domain/diary/service/DiaryServiceTest.java +++ b/src/test/java/com/example/log4u/domain/diary/service/DiaryServiceTest.java @@ -23,6 +23,7 @@ import com.example.log4u.domain.diary.VisibilityType; import com.example.log4u.domain.diary.dto.DiaryRequestDto; import com.example.log4u.domain.diary.dto.DiaryResponseDto; +import com.example.log4u.domain.diary.dto.DiaryWithAuthorDto; import com.example.log4u.domain.diary.entity.Diary; import com.example.log4u.domain.diary.exception.NotFoundDiaryException; import com.example.log4u.domain.diary.exception.OwnerAccessDeniedException; @@ -87,8 +88,8 @@ void searchDiariesByCursor() { Long cursorId = null; int size = 10; - List diaries = DiaryFixture.createDiariesWithIdsFixture(3); - Slice diarySlice = new SliceImpl<>(diaries, PageRequest.of(0, size), false); + List diaries = DiaryFixture.createDiariesWithAuthorDtoFixture(3); + Slice diarySlice = new SliceImpl<>(diaries, PageRequest.of(0, size), false); given(diaryRepository.searchDiariesByCursor( eq(keyword), @@ -98,7 +99,7 @@ void searchDiariesByCursor() { any(PageRequest.class) )).willReturn(diarySlice); - List diaryIds = diaries.stream().map(Diary::getDiaryId).toList(); + List diaryIds = diaries.stream().map(dto -> dto.diary().getDiaryId()).toList(); Map> mediaMap = Map.of( 1L, List.of(MediaFixture.createMediaFixture(1L, 1L)), 2L, List.of(MediaFixture.createMediaFixture(2L, 2L)), @@ -134,8 +135,8 @@ void getDiaryResponseDtoSlice() { Long cursorId = null; int size = 10; - List diaries = DiaryFixture.createDiariesWithIdsFixture(3); - Slice diarySlice = new SliceImpl<>(diaries, PageRequest.of(0, size), false); + List diaries = DiaryFixture.createDiariesWithAuthorDtoFixture(3); + Slice diarySlice = new SliceImpl<>(diaries, PageRequest.of(0, size), false); given(diaryRepository.findByUserIdAndVisibilityInAndCursorId( eq(targetUserId), @@ -144,7 +145,7 @@ void getDiaryResponseDtoSlice() { any(PageRequest.class) )).willReturn(diarySlice); - List diaryIds = diaries.stream().map(Diary::getDiaryId).toList(); + List diaryIds = diaries.stream().map(dto -> dto.diary().getDiaryId()).toList(); Map> mediaMap = Map.of( 1L, List.of(MediaFixture.createMediaFixture(1L, 1L)), 2L, List.of(MediaFixture.createMediaFixture(2L, 2L)), @@ -425,8 +426,8 @@ void getMyDiariesByCursor() { Long cursorId = null; int size = 10; - List diaries = DiaryFixture.createDiariesWithIdsFixture(3); - Slice diarySlice = new SliceImpl<>(diaries, PageRequest.of(0, size), false); + List diaries = DiaryFixture.createDiariesWithAuthorDtoFixture(3); + Slice diarySlice = new SliceImpl<>(diaries, PageRequest.of(0, size), false); given(diaryRepository.findByUserIdAndVisibilityInAndCursorId( eq(userId), @@ -435,7 +436,7 @@ void getMyDiariesByCursor() { any(PageRequest.class) )).willReturn(diarySlice); - List diaryIds = diaries.stream().map(Diary::getDiaryId).toList(); + List diaryIds = diaries.stream().map(dto -> dto.diary().getDiaryId()).toList(); Map> mediaMap = Map.of( 1L, List.of(MediaFixture.createMediaFixture(1L, 1L)), 2L, List.of(MediaFixture.createMediaFixture(2L, 2L)), diff --git a/src/test/java/com/example/log4u/fixture/DiaryFixture.java b/src/test/java/com/example/log4u/fixture/DiaryFixture.java index 737af0ca..f0f836f8 100644 --- a/src/test/java/com/example/log4u/fixture/DiaryFixture.java +++ b/src/test/java/com/example/log4u/fixture/DiaryFixture.java @@ -7,6 +7,7 @@ import com.example.log4u.domain.diary.VisibilityType; import com.example.log4u.domain.diary.WeatherInfo; import com.example.log4u.domain.diary.dto.DiaryRequestDto; +import com.example.log4u.domain.diary.dto.DiaryWithAuthorDto; import com.example.log4u.domain.diary.entity.Diary; import com.example.log4u.domain.map.entitiy.Location; import com.example.log4u.domain.media.dto.MediaRequestDto; @@ -285,4 +286,50 @@ public static List createDiariesWithIdsFixture(int count) { } return diaries; } + + // DiaryWithAuthorDto 생성 메서드 추가 + public static DiaryWithAuthorDto createDiaryWithAuthorDtoFixture(Long diaryId, Long userId) { + Diary diary = createCustomDiaryFixture( + diaryId, + userId, + "테스트 제목", + "테스트 내용", + "https://example.com/test.jpg", + VisibilityType.PUBLIC, + LocationFixture.createDefaultLocation(), + WeatherInfo.SUNNY, + 5L + ); + return new DiaryWithAuthorDto(diary, "테스트닉네임", "테스트프로필이미지"); + } + + // 여러 개의 DiaryWithAuthorDto 생성 메서드 추가 + public static List createDiariesWithAuthorDtoFixture(int count) { + List dtos = new ArrayList<>(); + for (int i = 0; i < count; i++) { + Location location = LocationFixture.createCustomLocation( + 37.5665 + (i * 0.001), + 126.9780 + (i * 0.001), + "서울특별시", + "중구", + "명동" + (i + 1) + "가" + ); + + Diary diary = Diary.builder() + .diaryId((long)(i + 1)) + .userId(1L) + .title("테스트 다이어리 " + (i + 1)) + .content("테스트 내용 " + (i + 1)) + .thumbnailUrl("https://example.com/thumbnail" + (i + 1) + ".jpg") + .visibility(VisibilityType.PUBLIC) + .location(location) + .weatherInfo(WeatherInfo.SUNNY) + .likeCount(5L + i) + .diaryDate(LocalDate.now().minusDays(i)) + .build(); + + dtos.add(new DiaryWithAuthorDto(diary, "테스트닉네임" + (i + 1), "테스트프로필이미지" + (i + 1))); + } + return dtos; + } } \ No newline at end of file From 7e239d75472438e8cc8b07850576eb42107ef8ff Mon Sep 17 00:00:00 2001 From: TTaiJin Date: Mon, 14 Apr 2025 22:06:50 +0900 Subject: [PATCH 2/3] =?UTF-8?q?feat:=20Top10=20=EB=8B=A4=EC=9D=B4=EC=96=B4?= =?UTF-8?q?=EB=A6=AC=20=EC=A1=B0=ED=9A=8C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../diary/controller/DiaryController.java | 9 ++++++++ .../domain/diary/dto/PopularDiaryDto.java | 18 ++++++++++++++++ .../domain/diary/facade/DiaryFacade.java | 13 ++++++++++++ .../diary/repository/DiaryRepository.java | 4 ++++ .../domain/diary/service/DiaryService.java | 5 +++++ .../domain/media/service/MediaService.java | 21 +++++++++++++++++++ 6 files changed, 70 insertions(+) create mode 100644 src/main/java/com/example/log4u/domain/diary/dto/PopularDiaryDto.java diff --git a/src/main/java/com/example/log4u/domain/diary/controller/DiaryController.java b/src/main/java/com/example/log4u/domain/diary/controller/DiaryController.java index 669fce38..681abfa3 100644 --- a/src/main/java/com/example/log4u/domain/diary/controller/DiaryController.java +++ b/src/main/java/com/example/log4u/domain/diary/controller/DiaryController.java @@ -1,5 +1,7 @@ package com.example.log4u.domain.diary.controller; +import java.util.List; + import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; @@ -18,6 +20,7 @@ import com.example.log4u.domain.diary.SortType; import com.example.log4u.domain.diary.dto.DiaryRequestDto; import com.example.log4u.domain.diary.dto.DiaryResponseDto; +import com.example.log4u.domain.diary.dto.PopularDiaryDto; import com.example.log4u.domain.diary.facade.DiaryFacade; import jakarta.validation.Valid; @@ -106,4 +109,10 @@ public ResponseEntity deleteDiary( diaryFacade.deleteDiary(customOAuth2User.getUserId(), diaryId); return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); } + + @GetMapping("/popular") + public ResponseEntity> getPopularDiaries() { + List popularDiaries = diaryFacade.getPopularDiaries(10); + return ResponseEntity.ok(popularDiaries); + } } \ No newline at end of file diff --git a/src/main/java/com/example/log4u/domain/diary/dto/PopularDiaryDto.java b/src/main/java/com/example/log4u/domain/diary/dto/PopularDiaryDto.java new file mode 100644 index 00000000..c8168b14 --- /dev/null +++ b/src/main/java/com/example/log4u/domain/diary/dto/PopularDiaryDto.java @@ -0,0 +1,18 @@ +package com.example.log4u.domain.diary.dto; + +import com.example.log4u.domain.diary.entity.Diary; + +import lombok.Builder; + +@Builder +public record PopularDiaryDto( + Long diaryId, + String title +) { + public static PopularDiaryDto of(Diary diary) { + return PopularDiaryDto.builder() + .diaryId(diary.getDiaryId()) + .title(diary.getTitle()) + .build(); + } +} diff --git a/src/main/java/com/example/log4u/domain/diary/facade/DiaryFacade.java b/src/main/java/com/example/log4u/domain/diary/facade/DiaryFacade.java index 229990c0..3839bcb1 100644 --- a/src/main/java/com/example/log4u/domain/diary/facade/DiaryFacade.java +++ b/src/main/java/com/example/log4u/domain/diary/facade/DiaryFacade.java @@ -10,6 +10,7 @@ import com.example.log4u.domain.diary.SortType; import com.example.log4u.domain.diary.dto.DiaryRequestDto; import com.example.log4u.domain.diary.dto.DiaryResponseDto; +import com.example.log4u.domain.diary.dto.PopularDiaryDto; import com.example.log4u.domain.diary.entity.Diary; import com.example.log4u.domain.diary.service.DiaryService; import com.example.log4u.domain.hashtag.service.HashtagService; @@ -139,4 +140,16 @@ public PageResponse searchDiariesByCursor( Long nextCursor = !dtoSlice.isEmpty() ? dtoSlice.getContent().getLast().diaryId() : null; return PageResponse.of(dtoSlice, nextCursor); } + + @Transactional(readOnly = true) + public List getPopularDiaries(int limit) { + List popularDiaries = diaryService.getTop10Diaries(); + + if (popularDiaries.isEmpty()) { + return List.of(); + } + return popularDiaries.stream() + .map(PopularDiaryDto::of) + .toList(); + } } \ No newline at end of file diff --git a/src/main/java/com/example/log4u/domain/diary/repository/DiaryRepository.java b/src/main/java/com/example/log4u/domain/diary/repository/DiaryRepository.java index 14153e6b..022738b8 100644 --- a/src/main/java/com/example/log4u/domain/diary/repository/DiaryRepository.java +++ b/src/main/java/com/example/log4u/domain/diary/repository/DiaryRepository.java @@ -1,8 +1,12 @@ package com.example.log4u.domain.diary.repository; +import java.util.List; + import org.springframework.data.jpa.repository.JpaRepository; +import com.example.log4u.domain.diary.VisibilityType; import com.example.log4u.domain.diary.entity.Diary; public interface DiaryRepository extends JpaRepository, CustomDiaryRepository { + List findTop10ByVisibilityOrderByLikeCountDesc(VisibilityType visibility); } diff --git a/src/main/java/com/example/log4u/domain/diary/service/DiaryService.java b/src/main/java/com/example/log4u/domain/diary/service/DiaryService.java index 8748d5e6..ff0738ae 100644 --- a/src/main/java/com/example/log4u/domain/diary/service/DiaryService.java +++ b/src/main/java/com/example/log4u/domain/diary/service/DiaryService.java @@ -273,4 +273,9 @@ public void checkDiaryExists(Long diaryId) { throw new NotFoundDiaryException(); } } + + @Transactional(readOnly = true) + public List getTop10Diaries() { + return diaryRepository.findTop10ByVisibilityOrderByLikeCountDesc(VisibilityType.PUBLIC); + } } \ No newline at end of file diff --git a/src/main/java/com/example/log4u/domain/media/service/MediaService.java b/src/main/java/com/example/log4u/domain/media/service/MediaService.java index c0fe28f2..fa338870 100644 --- a/src/main/java/com/example/log4u/domain/media/service/MediaService.java +++ b/src/main/java/com/example/log4u/domain/media/service/MediaService.java @@ -173,4 +173,25 @@ private void validateMediaLimit(List mediaList) { throw new MediaLimitExceededException(); } } + + private boolean isMediaListUnchanged(List existingMediaList, List newMediaList) { + // 개수가 다르면 변경된 것 + if (existingMediaList.size() != newMediaList.size()) { + return false; + } + + Map existingMediaMap = existingMediaList.stream() + .collect(Collectors.toMap(Media::getMediaId, Media::getOrderIndex)); + + // 모든 새 미이더가 기존 미디어와 ID 및 순서가 동일한지 확인 + for (MediaRequestDto newMedia : newMediaList) { + Integer existingOrder = existingMediaMap.get(newMedia.mediaId()); + + // ID가 없거나 순서가 다르면 변경된 것 + if (existingOrder == null || !existingOrder.equals(newMedia.orderIndex())) { + return false; + } + } + return true; + } } \ No newline at end of file From 1cd379a43771e416c5b1465cc3ef7a691e8456bd Mon Sep 17 00:00:00 2001 From: TTaiJin Date: Mon, 14 Apr 2025 23:41:27 +0900 Subject: [PATCH 3/3] . --- .../example/log4u/domain/follow/controller/FollowController.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/com/example/log4u/domain/follow/controller/FollowController.java b/src/main/java/com/example/log4u/domain/follow/controller/FollowController.java index 20326d26..05b1a0eb 100644 --- a/src/main/java/com/example/log4u/domain/follow/controller/FollowController.java +++ b/src/main/java/com/example/log4u/domain/follow/controller/FollowController.java @@ -38,5 +38,4 @@ public ResponseEntity deleteFollow( followService.deleteFollow(customOAuth2User.getUserId(), nickname); return ResponseEntity.ok().build(); } - }