diff --git a/src/main/java/com/depromeet/domain/mission/api/MissionController.java b/src/main/java/com/depromeet/domain/mission/api/MissionController.java index caa807264..500645aac 100644 --- a/src/main/java/com/depromeet/domain/mission/api/MissionController.java +++ b/src/main/java/com/depromeet/domain/mission/api/MissionController.java @@ -3,29 +3,17 @@ import com.depromeet.domain.mission.application.MissionService; import com.depromeet.domain.mission.dto.request.MissionCreateRequest; import com.depromeet.domain.mission.dto.request.MissionUpdateRequest; -import com.depromeet.domain.mission.dto.response.FinishedMissionResponse; -import com.depromeet.domain.mission.dto.response.FollowMissionFindAllResponse; -import com.depromeet.domain.mission.dto.response.MissionCreateResponse; -import com.depromeet.domain.mission.dto.response.MissionFindAllResponse; -import com.depromeet.domain.mission.dto.response.MissionFindResponse; -import com.depromeet.domain.mission.dto.response.MissionSymbolStackResponse; -import com.depromeet.domain.mission.dto.response.MissionUpdateResponse; +import com.depromeet.domain.mission.dto.response.*; import com.depromeet.domain.missionRecord.dto.response.MissionRecordSummaryResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; +import java.time.LocalDate; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; @Tag(name = "2. [미션]", description = "미션 관련 API입니다.") @RestController @@ -67,6 +55,12 @@ public MissionRecordSummaryResponse missionRecordFindSummary() { return missionService.findSummaryMissionRecord(); } + @Operation(summary = "미션 전체 현황 - 리스트", description = "년, 월, 일을 입력받아 해당 날짜의 미션 리스트를 조회합니다.") + @GetMapping("/summary-list") + public MissionSummaryListResponse missionSummaryList(@RequestParam LocalDate date) { + return missionService.findSummaryList(date); + } + @Operation(summary = "종료미션 보관함", description = "종료된 미션 리스트를 조회합니다.") @GetMapping("/finished") public List missionFindAllFinished() { diff --git a/src/main/java/com/depromeet/domain/mission/application/MissionService.java b/src/main/java/com/depromeet/domain/mission/application/MissionService.java index fd0ef7f8b..ede4dfca8 100644 --- a/src/main/java/com/depromeet/domain/mission/application/MissionService.java +++ b/src/main/java/com/depromeet/domain/mission/application/MissionService.java @@ -23,6 +23,7 @@ import java.util.Comparator; import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -303,4 +304,52 @@ public List findCompletedMissionRecords(List missions) { missionRecord.getUploadStatus() == ImageUploadStatus.COMPLETE) .toList(); } + + @Transactional(readOnly = true) + public MissionSummaryListResponse findSummaryList(LocalDate date) { + final Member currentMember = memberUtil.getCurrentMember(); + List missions = + missionRepository.findMissionsWithRecordsByDate(date, currentMember.getId()); + + List result = + missions.stream() + .map(mission -> getMissionSummaryItem(mission)) + .sorted( + Comparator.comparing(MissionSummaryItem::missionStatus) + .reversed() + .thenComparing( + Comparator.comparing(MissionSummaryItem::finishedAt) + .reversed())) + .collect(Collectors.toList()); + + result.sort( + Comparator.comparing(MissionSummaryItem::missionStatus) + .reversed() + .thenComparing( + Comparator.comparing(MissionSummaryItem::finishedAt).reversed())); + + long missionAllCount = missions.size(); + long missionCompleteCount = + result.stream() + .filter( + missionSummaryItem -> + missionSummaryItem.missionStatus() + == MissionStatus.COMPLETED) + .count(); + long missionNoneCount = missionAllCount - missionCompleteCount; + return MissionSummaryListResponse.of( + missionAllCount, missionCompleteCount, missionNoneCount, result); + } + + private static MissionSummaryItem getMissionSummaryItem(Mission mission) { + boolean isCompleted = + mission.getMissionRecords().stream() + .anyMatch( + missionRecord -> + missionRecord.getUploadStatus() + == ImageUploadStatus.COMPLETE); + return isCompleted + ? MissionSummaryItem.of(mission, MissionStatus.COMPLETED) + : MissionSummaryItem.of(mission, MissionStatus.NONE); + } } diff --git a/src/main/java/com/depromeet/domain/mission/dao/MissionRepositoryCustom.java b/src/main/java/com/depromeet/domain/mission/dao/MissionRepositoryCustom.java index 085b4e967..81ee397d2 100644 --- a/src/main/java/com/depromeet/domain/mission/dao/MissionRepositoryCustom.java +++ b/src/main/java/com/depromeet/domain/mission/dao/MissionRepositoryCustom.java @@ -1,6 +1,7 @@ package com.depromeet.domain.mission.dao; import com.depromeet.domain.mission.domain.Mission; +import java.time.LocalDate; import java.time.LocalDateTime; import java.util.List; @@ -15,4 +16,6 @@ public interface MissionRepositoryCustom { void updateFinishedDurationStatus(LocalDateTime today); List findAllFinishedMission(Long memberId); + + List findMissionsWithRecordsByDate(LocalDate date, Long memberId); } diff --git a/src/main/java/com/depromeet/domain/mission/dao/MissionRepositoryImpl.java b/src/main/java/com/depromeet/domain/mission/dao/MissionRepositoryImpl.java index 6ce7a6f29..ab7726c72 100644 --- a/src/main/java/com/depromeet/domain/mission/dao/MissionRepositoryImpl.java +++ b/src/main/java/com/depromeet/domain/mission/dao/MissionRepositoryImpl.java @@ -10,6 +10,7 @@ import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.jpa.impl.JPAQuery; import com.querydsl.jpa.impl.JPAQueryFactory; +import java.time.LocalDate; import java.time.LocalDateTime; import java.util.List; import lombok.RequiredArgsConstructor; @@ -84,6 +85,22 @@ public List findAllFinishedMission(Long memberId) { .fetch(); } + @Override + public List findMissionsWithRecordsByDate(LocalDate date, Long memberId) { + LocalDateTime startedAt = date.atTime(0, 0, 0); + LocalDateTime finishedAt = startedAt.plusDays(1); + JPAQuery query = + jpaQueryFactory + .selectFrom(mission) + .leftJoin(mission.missionRecords, missionRecord) + .where( + memberIdEq(memberId), + mission.startedAt.loe(startedAt), + mission.finishedAt.goe(finishedAt)) + .fetchJoin(); + return query.fetch(); + } + // 미션의 사용자 id 조건 검증 메서드 private BooleanExpression memberIdEq(Long memberId) { return memberId == null ? null : mission.member.id.eq(memberId); diff --git a/src/main/java/com/depromeet/domain/mission/domain/Mission.java b/src/main/java/com/depromeet/domain/mission/domain/Mission.java index 157384410..78be2614b 100644 --- a/src/main/java/com/depromeet/domain/mission/domain/Mission.java +++ b/src/main/java/com/depromeet/domain/mission/domain/Mission.java @@ -2,6 +2,7 @@ import com.depromeet.domain.common.model.BaseTimeEntity; import com.depromeet.domain.member.domain.Member; +import com.depromeet.domain.missionRecord.domain.ImageUploadStatus; import com.depromeet.domain.missionRecord.domain.MissionRecord; import jakarta.persistence.CascadeType; import jakarta.persistence.Column; @@ -127,8 +128,9 @@ public boolean isCompletedMissionToday() { .filter( record -> record.getStartedAt() - .toLocalDate() - .equals(LocalDateTime.now().toLocalDate())) + .toLocalDate() + .equals(LocalDateTime.now().toLocalDate()) + && record.getUploadStatus() == ImageUploadStatus.COMPLETE) .findFirst() .isPresent(); } diff --git a/src/main/java/com/depromeet/domain/mission/dto/response/MissionSummaryItem.java b/src/main/java/com/depromeet/domain/mission/dto/response/MissionSummaryItem.java new file mode 100644 index 000000000..18f3c8695 --- /dev/null +++ b/src/main/java/com/depromeet/domain/mission/dto/response/MissionSummaryItem.java @@ -0,0 +1,33 @@ +package com.depromeet.domain.mission.dto.response; + +import com.depromeet.domain.mission.domain.*; +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.v3.oas.annotations.media.Schema; +import java.time.LocalDateTime; + +public record MissionSummaryItem( + @Schema(description = "미션 ID", defaultValue = "1") Long missionId, + @Schema(description = "미션 이름", defaultValue = "default name") String name, + @Schema(description = "미션 카테고리", defaultValue = "STUDY") MissionCategory category, + @Schema(description = "미션 공개여부", defaultValue = "ALL") MissionVisibility visibility, + @Schema(description = "미션 상태", defaultValue = "1") MissionStatus missionStatus, + @JsonFormat( + shape = JsonFormat.Shape.STRING, + pattern = "yyyy-MM-dd HH:mm:ss", + timezone = "Asia/Seoul") + @Schema( + description = "미션 종료 시간", + defaultValue = "2024-01-03 00:34:00", + type = "string") + LocalDateTime finishedAt) { + + public static MissionSummaryItem of(Mission mission, MissionStatus missionStatus) { + return new MissionSummaryItem( + mission.getId(), + mission.getName(), + mission.getCategory(), + mission.getVisibility(), + missionStatus, + mission.getFinishedAt()); + } +} diff --git a/src/main/java/com/depromeet/domain/mission/dto/response/MissionSummaryListResponse.java b/src/main/java/com/depromeet/domain/mission/dto/response/MissionSummaryListResponse.java new file mode 100644 index 000000000..a79adb85e --- /dev/null +++ b/src/main/java/com/depromeet/domain/mission/dto/response/MissionSummaryListResponse.java @@ -0,0 +1,18 @@ +package com.depromeet.domain.mission.dto.response; + +import java.util.List; + +public record MissionSummaryListResponse( + long missionAllCount, + long missionCompleteCount, + long missionNoneCount, + List missionSummaryItems) { + public static MissionSummaryListResponse of( + long missionAllCount, + long missionCompleteCount, + long missionNoneCount, + List missionSummaryItems) { + return new MissionSummaryListResponse( + missionAllCount, missionCompleteCount, missionNoneCount, missionSummaryItems); + } +}