From eaed21447a31776706f09ff6e46c7d94e15d125f Mon Sep 17 00:00:00 2001 From: yb__char <68099546+char-yb@users.noreply.github.com> Date: Sun, 29 Sep 2024 21:16:58 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EB=AF=B8=EC=85=98=20=EA=B8=B0=EB=A1=9D?= =?UTF-8?q?=20=EC=99=84=EB=A3=8C=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20(#281)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 미션 기록 완료 리스트 * feat: 미션 기록 완료 리스트 Projection 개선 * refactor: 미션 완료 기록 리스트 조건 및 DTO 수정 * fix: completedAt Type 체크 --- .../api/MissionRecordController.java | 7 +++ .../application/MissionRecordService.java | 11 +++- .../dao/MissionRecordRepositoryCustom.java | 6 ++ .../dao/MissionRecordRepositoryImpl.java | 60 ++++++++++++++++++- .../MissionRecordTabListResponse.java | 25 ++++++++ .../dto/response/MissionTabResponse.java | 24 +++++++- .../application/MissionRecordServiceTest.java | 3 +- 7 files changed, 128 insertions(+), 8 deletions(-) create mode 100644 src/main/java/com/depromeet/stonebed/domain/missionRecord/dto/response/MissionRecordTabListResponse.java diff --git a/src/main/java/com/depromeet/stonebed/domain/missionRecord/api/MissionRecordController.java b/src/main/java/com/depromeet/stonebed/domain/missionRecord/api/MissionRecordController.java index b9695c2e..371ea7da 100644 --- a/src/main/java/com/depromeet/stonebed/domain/missionRecord/api/MissionRecordController.java +++ b/src/main/java/com/depromeet/stonebed/domain/missionRecord/api/MissionRecordController.java @@ -8,6 +8,7 @@ import com.depromeet.stonebed.domain.missionRecord.dto.response.MissionRecordCalendarResponse; import com.depromeet.stonebed.domain.missionRecord.dto.response.MissionRecordCompleteTotal; import com.depromeet.stonebed.domain.missionRecord.dto.response.MissionRecordIdResponse; +import com.depromeet.stonebed.domain.missionRecord.dto.response.MissionRecordTabListResponse; import com.depromeet.stonebed.domain.missionRecord.dto.response.MissionTabResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -26,6 +27,12 @@ public class MissionRecordController { private final MissionRecordService missionRecordService; + @Operation(summary = "미션 탭 완료된 기록 리스트", description = "미션 탭에서 완료된 기록 리스트를 조회한다.") + @GetMapping + public MissionRecordTabListResponse missionRecordsFind() { + return missionRecordService.findCompleteMissionRecords(); + } + @Operation(summary = "미션 탭 상태 조회", description = "미션 탭의 상태를 조회한다.") @GetMapping("/status") public MissionTabResponse getMissionRecordStatus(@RequestParam Long missionId) { diff --git a/src/main/java/com/depromeet/stonebed/domain/missionRecord/application/MissionRecordService.java b/src/main/java/com/depromeet/stonebed/domain/missionRecord/application/MissionRecordService.java index 5d605f90..54e068e1 100644 --- a/src/main/java/com/depromeet/stonebed/domain/missionRecord/application/MissionRecordService.java +++ b/src/main/java/com/depromeet/stonebed/domain/missionRecord/application/MissionRecordService.java @@ -18,6 +18,7 @@ import com.depromeet.stonebed.domain.missionRecord.dto.response.MissionRecordCalendarResponse; import com.depromeet.stonebed.domain.missionRecord.dto.response.MissionRecordCompleteTotal; import com.depromeet.stonebed.domain.missionRecord.dto.response.MissionRecordIdResponse; +import com.depromeet.stonebed.domain.missionRecord.dto.response.MissionRecordTabListResponse; import com.depromeet.stonebed.domain.missionRecord.dto.response.MissionTabResponse; import com.depromeet.stonebed.global.error.ErrorCode; import com.depromeet.stonebed.global.error.exception.CustomException; @@ -246,7 +247,7 @@ public MissionTabResponse getMissionTabStatus(Long missionId) { mission.getTitle(), mission.getIllustrationUrl(), missionRecord.getContent(), - missionRecord.getUpdatedAt().toLocalDate()); + missionRecord.getUpdatedAt().toLocalDate().toString()); } @Transactional(propagation = Propagation.REQUIRES_NEW) @@ -275,4 +276,12 @@ public void expiredMissionsToNotCompletedUpdate() { LocalDateTime endOfYesterday = LocalDate.now().minusDays(1).atTime(23, 59, 59); missionRecordRepository.updateExpiredMissionsToNotCompleted(endOfYesterday); } + + @Transactional(readOnly = true) + public MissionRecordTabListResponse findCompleteMissionRecords() { + final Member member = memberUtil.getCurrentMember(); + return MissionRecordTabListResponse.from( + missionRecordRepository.findAllTabMissionsByMemberAndStatus( + member, MissionRecordStatus.COMPLETED)); + } } diff --git a/src/main/java/com/depromeet/stonebed/domain/missionRecord/dao/MissionRecordRepositoryCustom.java b/src/main/java/com/depromeet/stonebed/domain/missionRecord/dao/MissionRecordRepositoryCustom.java index 724856f4..d5eb9ed9 100644 --- a/src/main/java/com/depromeet/stonebed/domain/missionRecord/dao/MissionRecordRepositoryCustom.java +++ b/src/main/java/com/depromeet/stonebed/domain/missionRecord/dao/MissionRecordRepositoryCustom.java @@ -1,7 +1,10 @@ package com.depromeet.stonebed.domain.missionRecord.dao; +import com.depromeet.stonebed.domain.member.domain.Member; import com.depromeet.stonebed.domain.missionRecord.domain.MissionRecord; import com.depromeet.stonebed.domain.missionRecord.domain.MissionRecordDisplay; +import com.depromeet.stonebed.domain.missionRecord.domain.MissionRecordStatus; +import com.depromeet.stonebed.domain.missionRecord.dto.response.MissionTabResponse; import java.time.LocalDateTime; import java.util.List; import org.springframework.data.domain.Pageable; @@ -17,4 +20,7 @@ List findByMemberIdAndCreatedAtFromWithPagination( Pageable pageable); void updateExpiredMissionsToNotCompleted(LocalDateTime dateTime); + + List findAllTabMissionsByMemberAndStatus( + Member member, MissionRecordStatus status); } diff --git a/src/main/java/com/depromeet/stonebed/domain/missionRecord/dao/MissionRecordRepositoryImpl.java b/src/main/java/com/depromeet/stonebed/domain/missionRecord/dao/MissionRecordRepositoryImpl.java index 0177a21d..f62d4c65 100644 --- a/src/main/java/com/depromeet/stonebed/domain/missionRecord/dao/MissionRecordRepositoryImpl.java +++ b/src/main/java/com/depromeet/stonebed/domain/missionRecord/dao/MissionRecordRepositoryImpl.java @@ -1,12 +1,21 @@ package com.depromeet.stonebed.domain.missionRecord.dao; +import static com.depromeet.stonebed.domain.mission.domain.QMission.*; +import static com.depromeet.stonebed.domain.missionHistory.domain.QMissionHistory.*; import static com.depromeet.stonebed.domain.missionRecord.domain.QMissionRecord.missionRecord; +import com.depromeet.stonebed.domain.member.domain.Member; import com.depromeet.stonebed.domain.missionRecord.domain.MissionRecord; import com.depromeet.stonebed.domain.missionRecord.domain.MissionRecordDisplay; import com.depromeet.stonebed.domain.missionRecord.domain.MissionRecordStatus; +import com.depromeet.stonebed.domain.missionRecord.dto.response.MissionTabResponse; +import com.querydsl.core.types.ConstantImpl; +import com.querydsl.core.types.Projections; import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.core.types.dsl.DateTemplate; +import com.querydsl.core.types.dsl.Expressions; import com.querydsl.jpa.impl.JPAQueryFactory; +import java.time.LocalDate; import java.time.LocalDateTime; import java.util.List; import lombok.RequiredArgsConstructor; @@ -24,7 +33,7 @@ public List findByMemberIdWithPagination( Long memberId, List displays, Pageable pageable) { return queryFactory .selectFrom(missionRecord) - .where(isMemberId(memberId).and(InDisplays(displays))) + .where(isMemberId(memberId).and(inDisplays(displays))) .orderBy(missionRecord.createdAt.asc()) .offset(pageable.getOffset()) .limit(pageable.getPageSize()) @@ -43,7 +52,7 @@ public List findByMemberIdAndCreatedAtFromWithPagination( isMemberId(memberId) .and(createdAtFrom(createdAt)) .and(isCompleted()) - .and(InDisplays(displays))) + .and(inDisplays(displays))) .orderBy(missionRecord.createdAt.asc()) .offset(pageable.getOffset()) .limit(pageable.getPageSize()) @@ -64,6 +73,51 @@ public void updateExpiredMissionsToNotCompleted(LocalDateTime currentTime) { .execute(); } + @Override + public List findAllTabMissionsByMemberAndStatus( + Member member, MissionRecordStatus status) { + DateTemplate completedAt = + Expressions.dateTemplate( + String.class, + "DATE_FORMAT({0}, {1})", + missionRecord.updatedAt, + ConstantImpl.create("%Y-%m-%d")); + + DateTemplate year = + Expressions.dateTemplate(Integer.class, "YEAR({0})", missionRecord.updatedAt); + DateTemplate month = + Expressions.dateTemplate(Integer.class, "MONTH({0})", missionRecord.updatedAt); + + int currentYear = LocalDate.now().getYear(); + int currentMonth = LocalDate.now().getMonthValue(); + + return queryFactory + .select( + Projections.constructor( + MissionTabResponse.class, + missionRecord.id.as("recordId"), + missionRecord.imageUrl, + missionRecord.status, + mission.title.as("missionTitle"), + mission.illustrationUrl, + missionRecord.content, + completedAt)) + .from(missionRecord) + .leftJoin(missionRecord.missionHistory, missionHistory) + .on(missionHistory.id.eq(missionRecord.missionHistory.id)) + .leftJoin(missionHistory.mission, mission) + .on(mission.id.eq(missionHistory.mission.id)) + .where( + missionRecord + .member + .eq(member) + .and(missionRecord.status.eq(status)) + .and(year.eq(currentYear)) + .and(month.eq(currentMonth))) + .orderBy(missionRecord.updatedAt.desc()) + .fetch(); + } + private BooleanExpression isMemberId(Long memberId) { return missionRecord.member.id.eq(memberId); } @@ -76,7 +130,7 @@ private BooleanExpression isCompleted() { return missionRecord.status.eq(MissionRecordStatus.COMPLETED); } - private BooleanExpression InDisplays(List displays) { + private BooleanExpression inDisplays(List displays) { return missionRecord.display.in(displays); } } diff --git a/src/main/java/com/depromeet/stonebed/domain/missionRecord/dto/response/MissionRecordTabListResponse.java b/src/main/java/com/depromeet/stonebed/domain/missionRecord/dto/response/MissionRecordTabListResponse.java new file mode 100644 index 00000000..0303a632 --- /dev/null +++ b/src/main/java/com/depromeet/stonebed/domain/missionRecord/dto/response/MissionRecordTabListResponse.java @@ -0,0 +1,25 @@ +package com.depromeet.stonebed.domain.missionRecord.dto.response; + +import io.swagger.v3.oas.annotations.media.Schema; +import java.util.List; + +public record MissionRecordTabListResponse( + @Schema( + description = "미션 탭 목록", + example = + "[" + + "{" + + "\"recordId\": 1," + + "\"imageUrl\": \"example.jpeg\"," + + "\"status\": \"NOT_COMPLETED\"," + + "\"missionTitle\": \"산책하기\"," + + "\"illustrationUrl\": \"example.jpeg\"," + + "\"content\": \"오늘 마실다녀왔어요\"," + + "\"completedAt\": \"2021-10-10\"" + + "}" + + "]") + List list) { + public static MissionRecordTabListResponse from(List list) { + return new MissionRecordTabListResponse(list); + } +} diff --git a/src/main/java/com/depromeet/stonebed/domain/missionRecord/dto/response/MissionTabResponse.java b/src/main/java/com/depromeet/stonebed/domain/missionRecord/dto/response/MissionTabResponse.java index 9ebfb74f..335ed52a 100644 --- a/src/main/java/com/depromeet/stonebed/domain/missionRecord/dto/response/MissionTabResponse.java +++ b/src/main/java/com/depromeet/stonebed/domain/missionRecord/dto/response/MissionTabResponse.java @@ -1,8 +1,8 @@ package com.depromeet.stonebed.domain.missionRecord.dto.response; import com.depromeet.stonebed.domain.missionRecord.domain.MissionRecordStatus; +import com.querydsl.core.annotations.QueryProjection; import io.swagger.v3.oas.annotations.media.Schema; -import java.time.LocalDate; public record MissionTabResponse( @Schema(description = "기록 ID", example = "1") Long recordId, @@ -11,7 +11,25 @@ public record MissionTabResponse( @Schema(description = "미션 제목", example = "산책하기") String missionTitle, @Schema(description = "미션 일러스트 이미지", example = "example.jpeg") String illustrationUrl, @Schema(description = "기록 내용", example = "오늘 마실다녀왔어요") String content, - @Schema(description = "완료 일자", example = "2021-10-10") LocalDate completedAt) { + @Schema(description = "완료 일자", example = "2021-10-10") String completedAt) { + + @QueryProjection + public MissionTabResponse( + Long recordId, + String imageUrl, + MissionRecordStatus status, + String missionTitle, + String illustrationUrl, + String content, + String completedAt) { + this.recordId = recordId; + this.imageUrl = imageUrl; + this.status = status; + this.missionTitle = missionTitle; + this.illustrationUrl = illustrationUrl; + this.content = content; + this.completedAt = completedAt; + } public static MissionTabResponse of( Long recordId, @@ -20,7 +38,7 @@ public static MissionTabResponse of( String missionTitle, String illustrationUrl, String content, - LocalDate completedAt) { + String completedAt) { return new MissionTabResponse( recordId, imageUrl, status, missionTitle, illustrationUrl, content, completedAt); } diff --git a/src/test/java/com/depromeet/stonebed/domain/missionRecord/application/MissionRecordServiceTest.java b/src/test/java/com/depromeet/stonebed/domain/missionRecord/application/MissionRecordServiceTest.java index 7c44b1c0..4ea618d4 100644 --- a/src/test/java/com/depromeet/stonebed/domain/missionRecord/application/MissionRecordServiceTest.java +++ b/src/test/java/com/depromeet/stonebed/domain/missionRecord/application/MissionRecordServiceTest.java @@ -331,7 +331,8 @@ class MissionRecordServiceTest extends FixtureMonkeySetUp { then(response.imageUrl()).isEqualTo(missionRecord.getImageUrl()); then(response.status()).isEqualTo(MissionRecordStatus.COMPLETED); then(response.recordId()).isEqualTo(missionRecord.getId()); - then(response.completedAt()).isEqualTo(missionRecord.getUpdatedAt().toLocalDate()); + then(response.completedAt()) + .isEqualTo(missionRecord.getUpdatedAt().toLocalDate().toString()); then(response.content()).isEqualTo(missionRecord.getContent()); verify(memberUtil).getCurrentMember();