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 @@ -30,9 +30,11 @@ public class MonthlyReportController {
private final MonthlyReportService monthlyReportService;
private final MonthlyReportQueryService monthlyReportQueryService;

@Deprecated(since = "2026-05-15", forRemoval = true)
@PostMapping("/start")
@PreAuthorize("isAuthenticated()")
@Operation(
deprecated = true,
summary = "월간 리포트 생성 시작",
description = """
사용자의 (지난 달에 대한) 월간 리포트 생성을 시작합니다. </br>
Expand Down Expand Up @@ -83,9 +85,11 @@ public ResponseEntity<ApiResponseDto<MonthlyReportStartResponse>> startMonthlyRe
return ApiResponseEntity.ok(response);
}

@Deprecated(since = "2026-05-15", forRemoval = true)
@GetMapping
@PreAuthorize("isAuthenticated()")
@Operation(
deprecated = true,
summary = "나의 월간 리포트 조회",
description = """
사용자의 (지난 달에 대한) 월간 리포트와 이전 월간 리포트를 조회합니다. </br>
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.devkor.ifive.nadab.domain.monthlyreport.api.dto.response;

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

@Schema(description = "전체 리포트 목록 아이템")
public record AllReportItemResponseV2(
@Schema(description = "리포트 ID")
Long id,
@Schema(description = "리포트 타입", example = "MONTHLY")
String type,
@Schema(description = "기간 문자열", example = "1월 4주차")
String period,
@Schema(description = "요약")
String summary,
@Schema(description = "리포트 버전", example = "2")
int version
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.devkor.ifive.nadab.domain.monthlyreport.api.dto.response;

import com.devkor.ifive.nadab.domain.monthlyreport.core.content.InterestStatsContent;
import com.devkor.ifive.nadab.domain.monthlyreport.core.entity.MonthlyReportComparisonType;
import com.devkor.ifive.nadab.domain.monthlyreport.core.entity.MonthlyReportStatus;
import com.devkor.ifive.nadab.domain.typereport.core.content.TypeEmotionStatsContent;
import com.devkor.ifive.nadab.domain.typereport.core.content.TypeTextContent;
import com.devkor.ifive.nadab.global.shared.reportcontent.StyledText;
import io.swagger.v3.oas.annotations.media.Schema;

@Schema(description = "월간 리포트 V2 조회 응답")
public record MonthlyReportResponseV2(

@Schema(description = "리포트 대상 월")
int month,

@Schema(description = "리포트 상태", example = "PENDING")
MonthlyReportStatus status,

@Schema(description = "비교 타입", example = "BASELINE")
MonthlyReportComparisonType comparisonType,

@Schema(description = "요약")
String summary,

@Schema(description = "이미지 URL", nullable = true)
String imageUrl,

@Schema(description = "발견한 점(styled)")
StyledText discovered,

@Schema(description = "핵심 키워드")
String dominantKeyword,

@Schema(description = "감정 통계")
TypeEmotionStatsContent emotionStats,

@Schema(description = "감정 요약(styled)")
TypeTextContent emotionSummaryContent,

@Schema(description = "감정 흐름 요약")
String emotionTrend,

@Schema(description = "코멘트(styled)")
StyledText comment,

@Schema(description = "코멘트 요약")
String commentSummary,

@Schema(description = "관심사 통계")
InterestStatsContent interestStats
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.devkor.ifive.nadab.domain.monthlyreport.api.dto.response;

public enum ReportListTypeV2 {
ALL,
MONTHLY,
WEEKLY
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
package com.devkor.ifive.nadab.domain.monthlyreport.application;

import com.devkor.ifive.nadab.domain.monthlyreport.api.dto.response.AllReportItemResponseV2;
import com.devkor.ifive.nadab.domain.monthlyreport.api.dto.response.MonthlyReportResponseV2;
import com.devkor.ifive.nadab.domain.monthlyreport.api.dto.response.ReportListTypeV2;
import com.devkor.ifive.nadab.domain.monthlyreport.core.content.MonthlyContentFactory;
import com.devkor.ifive.nadab.domain.monthlyreport.core.entity.MonthlyReport;
import com.devkor.ifive.nadab.domain.monthlyreport.core.entity.MonthlyReportStatus;
import com.devkor.ifive.nadab.domain.monthlyreport.core.entity.MonthlyReportV2;
import com.devkor.ifive.nadab.domain.monthlyreport.core.entity.MonthlyReportV2Content;
import com.devkor.ifive.nadab.domain.monthlyreport.core.repository.MonthlyReportRepository;
import com.devkor.ifive.nadab.domain.monthlyreport.core.repository.MonthlyReportV2Repository;
import com.devkor.ifive.nadab.domain.typereport.core.content.TypeContentFactory;
import com.devkor.ifive.nadab.domain.user.core.repository.UserRepository;
import com.devkor.ifive.nadab.domain.user.infra.ProfileImageUrlBuilder;
import com.devkor.ifive.nadab.domain.weeklyreport.core.entity.WeeklyReport;
import com.devkor.ifive.nadab.domain.weeklyreport.core.entity.WeeklyReportStatus;
import com.devkor.ifive.nadab.domain.weeklyreport.core.repository.WeeklyReportRepository;
import com.devkor.ifive.nadab.global.core.response.ErrorCode;
import com.devkor.ifive.nadab.global.exception.ForbiddenException;
import com.devkor.ifive.nadab.global.exception.NotFoundException;
import com.devkor.ifive.nadab.global.shared.util.MonthRangeCalculator;
import com.devkor.ifive.nadab.global.shared.util.WeekRangeCalculator;
import com.devkor.ifive.nadab.global.shared.util.dto.MonthRangeDto;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class MonthlyReportQueryServiceV2 {

private final MonthlyReportRepository monthlyReportRepository;
private final MonthlyReportV2Repository monthlyReportV2Repository;
private final WeeklyReportRepository weeklyReportRepository;
private final UserRepository userRepository;
private final ProfileImageUrlBuilder profileImageUrlBuilder;

public List<AllReportItemResponseV2> getAllReports(Long userId, ReportListTypeV2 type) {
if (!userRepository.existsById(userId)) {
throw new NotFoundException(ErrorCode.USER_NOT_FOUND);
}

List<ReportListRow> rows = new ArrayList<>();

if (type == ReportListTypeV2.ALL || type == ReportListTypeV2.MONTHLY) {
List<MonthlyReport> monthlyV1 = monthlyReportRepository.findAllByUserIdAndStatus(userId, MonthlyReportStatus.COMPLETED);
for (MonthlyReport report : monthlyV1) {
LocalDate month = report.getMonthStartDate();
rows.add(new ReportListRow(
report.getId(),
"MONTHLY",
month.getYear(),
month.getMonthValue(),
99,
month.getMonthValue() + "월",
report.getSummary(),
1
));
}

List<MonthlyReportV2> monthlyV2 = monthlyReportV2Repository.findAllByUserIdAndStatus(userId, MonthlyReportStatus.COMPLETED);
for (MonthlyReportV2 report : monthlyV2) {
LocalDate month = report.getMonthStartDate();
rows.add(new ReportListRow(
report.getId(),
"MONTHLY",
month.getYear(),
month.getMonthValue(),
99,
month.getMonthValue() + "월",
report.getSummary(),
2
));
}
}

if (type == ReportListTypeV2.ALL || type == ReportListTypeV2.WEEKLY) {
List<WeeklyReport> weekly = weeklyReportRepository.findAllByUserIdAndStatus(userId, WeeklyReportStatus.COMPLETED);
for (WeeklyReport report : weekly) {
LocalDate weekStart = report.getWeekStartDate();
int weekOfMonth = WeekRangeCalculator.getWeekOfMonth(WeekRangeCalculator.weekRangeOf(weekStart));
rows.add(new ReportListRow(
report.getId(),
"WEEKLY",
weekStart.getYear(),
weekStart.getMonthValue(),
weekOfMonth,
weekStart.getMonthValue() + "월 " + weekOfMonth + "주차",
report.getSummary(),
1
));
}
}

rows.sort(
Comparator.comparingInt(ReportListRow::year).reversed()
.thenComparing(Comparator.comparingInt(ReportListRow::month).reversed())
.thenComparing(Comparator.comparingInt(ReportListRow::weekOrder).reversed())
.thenComparing(Comparator.comparingInt(ReportListRow::version).reversed())
.thenComparing(Comparator.comparingLong(ReportListRow::id).reversed())
);

return rows.stream()
.map(r -> new AllReportItemResponseV2(r.id(), r.type(), r.period(), r.summary(), r.version()))
.toList();
}

public MonthlyReportResponseV2 getMyMonthlyReport(Long userId) {
if (!userRepository.existsById(userId)) {
throw new NotFoundException(ErrorCode.USER_NOT_FOUND);
}

MonthRangeDto range = MonthRangeCalculator.getLastMonthRange();
return monthlyReportV2Repository.findByUserIdAndMonthStartDate(userId, range.monthStartDate())
.map(this::toResponse)
.orElse(null);
}

public MonthlyReportResponseV2 getMonthlyReportById(Long userId, Long id) {
MonthlyReportV2 report = monthlyReportV2Repository.findById(id)
.orElseThrow(() -> new NotFoundException(ErrorCode.MONTHLY_REPORT_NOT_FOUND));
if (report.getUser() == null || report.getUser().getId() == null || !report.getUser().getId().equals(userId)) {
throw new ForbiddenException(ErrorCode.MONTHLY_REPORT_ACCESS_FORBIDDEN);
}
return toResponse(report);
}

private MonthlyReportResponseV2 toResponse(MonthlyReportV2 report) {
MonthlyReportV2Content content = report.getContent() == null
? new MonthlyReportV2Content("", "", "", "", TypeContentFactory.emptyText().styledText(), TypeContentFactory.emptyText().styledText())
: report.getContent().normalized();

String imageUrl = report.getImageKey() == null ? null : profileImageUrlBuilder.buildUrl(report.getImageKey());

return new MonthlyReportResponseV2(
report.getMonthStartDate().getMonthValue(),
report.getStatus() == null ? MonthlyReportStatus.PENDING : report.getStatus(),
report.getComparisonType(),
content.summary(),
imageUrl,
content.discovered(),
content.dominantKeyword(),
report.getEmotionStats() == null ? TypeContentFactory.emptyEmotionStats() : report.getEmotionStats().normalized(),
report.getEmotionSummaryContent() == null ? TypeContentFactory.emptyText() : report.getEmotionSummaryContent().normalized(),
content.emotionTrend(),
content.comment(),
content.commentSummary(),
report.getInterestStats() == null ? MonthlyContentFactory.emptyInterestStats() : report.getInterestStats().normalized()
);
}

private record ReportListRow(
Long id,
String type,
int year,
int month,
int weekOrder,
String period,
String summary,
int version
) {
}
}
Loading
Loading