Skip to content

Commit 208e3ae

Browse files
committed
feat: 관리자 LLM 지출분석 통계 조회
1 parent e3779a4 commit 208e3ae

7 files changed

Lines changed: 300 additions & 0 deletions

File tree

src/main/java/store/lastdance/controller/admin/AdminController.java

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import store.lastdance.domain.user.UserRole;
1919
import store.lastdance.dto.admin.*;
2020
import store.lastdance.dto.common.ErrorResponseDTO;
21+
import store.lastdance.dto.expense.ExpenseAnalysisHistoryDTO;
2122
import store.lastdance.security.oauth.CustomOAuth2User;
2223
import store.lastdance.service.admin.AdminService;
2324
import store.lastdance.dto.response.ApiResponse;
@@ -537,4 +538,116 @@ public ResponseEntity<ApiResponse<AiJudgmentStatsDTO>> getAiJudgmentStats(
537538
return ResponseEntity.ok(ApiResponse.success(stats, "AI 피드백 통계 조회 성공"));
538539
}
539540

541+
@Operation(
542+
summary = "LLM 지출분석 피드백 통계 조회",
543+
description = "LLM 지출분석 피드백에 대한 통계 정보를 조회합니다. 만족도, 불만족 카운트, 추세 등을 포함합니다.",
544+
security = @SecurityRequirement(name = "bearerAuth")
545+
)
546+
@ApiResponses(value = {
547+
@io.swagger.v3.oas.annotations.responses.ApiResponse(
548+
responseCode = "200",
549+
description = "LLM 지출분석 피드백 통계 조회 성공",
550+
content = @Content(schema = @Schema(implementation = ExpenseAnalyzerFeedbackStatsDTO.class))
551+
),
552+
@io.swagger.v3.oas.annotations.responses.ApiResponse(
553+
responseCode = "400",
554+
description = "잘못된 기간 파라미터",
555+
content = @Content(schema = @Schema(implementation = ErrorResponseDTO.class))
556+
),
557+
@io.swagger.v3.oas.annotations.responses.ApiResponse(
558+
responseCode = "403",
559+
description = "관리자 권한 없음",
560+
content = @Content(schema = @Schema(implementation = ErrorResponseDTO.class))
561+
)
562+
})
563+
@GetMapping("/expense/analyzer/stats")
564+
public ResponseEntity<ApiResponse<ExpenseAnalyzerFeedbackStatsDTO>> getExpenseAnalyzerFeedbackStats(
565+
@Parameter(description = "조회 기간 (daily, weekly, monthly)") @RequestParam(value = "period", defaultValue = "weekly") String period,
566+
@AuthenticationPrincipal CustomOAuth2User user) {
567+
568+
log.info("LLM 지출분석 피드백 통계 요청: period={}, adminEmail={}", period, user.getEmail());
569+
570+
ExpenseAnalyzerFeedbackStatsDTO stats = adminService.getExpenseAnalyzerFeedbackStats(user.getUserId(), period);
571+
572+
log.info("LLM 지출분석 피드백 통계 응답: {}", stats);
573+
574+
return ResponseEntity.ok(ApiResponse.success(stats, "LLM 지출분석 피드백 통계 조회 성공"));
575+
}
576+
577+
@Operation(
578+
summary = "LLM 지출분석 내역 조회",
579+
description = "LLM 지출분석 내역을 조회합니다. 사용자, 평가, 날짜 등으로 필터링할 수 있습니다.",
580+
security = @SecurityRequirement(name = "bearerAuth")
581+
)
582+
@ApiResponses(value = {
583+
@io.swagger.v3.oas.annotations.responses.ApiResponse(
584+
responseCode = "200",
585+
description = "LLM 지출분석 내역 조회 성공",
586+
content = @Content(schema = @Schema(implementation = AdminExpenseAnalyzerHistoryResponseDTO.class))
587+
),
588+
@io.swagger.v3.oas.annotations.responses.ApiResponse(
589+
responseCode = "403",
590+
description = "관리자 권한 없음",
591+
content = @Content(schema = @Schema(implementation = ErrorResponseDTO.class))
592+
)
593+
})
594+
@GetMapping("/expense/analyzer")
595+
public ResponseEntity<ApiResponse<AdminExpenseAnalyzerHistoryResponseDTO>> getExpenseAnalyzerHistory(
596+
@Parameter(description = "페이지 번호 (1부터 시작)") @RequestParam(value = "page", defaultValue = "1") int page,
597+
@Parameter(description = "페이지당 조회할 항목 수") @RequestParam(value = "limit", defaultValue = "20") int limit,
598+
@Parameter(description = "검색 키워드 (사용자 이메일, 닉네임)") @RequestParam(value = "search", required = false) String search,
599+
@Parameter(description = "평가 필터 (UP, DOWN)") @RequestParam(value = "rating", required = false) String rating,
600+
@Parameter(description = "시작 날짜 (YYYY-MM-DD)") @RequestParam(value = "dateFrom", required = false) String dateFrom,
601+
@Parameter(description = "종료 날짜 (YYYY-MM-DD)") @RequestParam(value = "dateTo", required = false) String dateTo,
602+
@AuthenticationPrincipal CustomOAuth2User user) {
603+
604+
log.info("LLM 지출분석 내역 요청: page={}, limit={}, search={}, rating={}, dateFrom={}, dateTo={}, adminEmail={}",
605+
page, limit, search, rating, dateFrom, dateTo, user.getEmail());
606+
607+
AdminExpenseAnalyzerHistoryResponseDTO historyList = adminService.getExpenseAnalyzerHistory(
608+
user.getUserId(), page, limit, search, rating, dateFrom, dateTo
609+
);
610+
611+
if (historyList != null && historyList.histories() != null) {
612+
log.info("LLM 지출분석 내역 응답: {}건 조회", historyList.histories().size());
613+
}
614+
615+
return ResponseEntity.ok(ApiResponse.success(historyList, "LLM 지출분석 내역 조회 성공"));
616+
}
617+
618+
@Operation(
619+
summary = "LLM 지출분석 상세 조회",
620+
description = "특정 LLM 지출분석의 상세 정보를 조회합니다.",
621+
security = @SecurityRequirement(name = "bearerAuth")
622+
)
623+
@ApiResponses(value = {
624+
@io.swagger.v3.oas.annotations.responses.ApiResponse(
625+
responseCode = "200",
626+
description = "LLM 지출분석 상세 조회 성공",
627+
content = @Content(schema = @Schema(implementation = ExpenseAnalysisHistoryDTO.class))
628+
),
629+
@io.swagger.v3.oas.annotations.responses.ApiResponse(
630+
responseCode = "403",
631+
description = "관리자 권한 없음",
632+
content = @Content(schema = @Schema(implementation = ErrorResponseDTO.class))
633+
),
634+
@io.swagger.v3.oas.annotations.responses.ApiResponse(
635+
responseCode = "404",
636+
description = "내역을 찾을 수 없음",
637+
content = @Content(schema = @Schema(implementation = ErrorResponseDTO.class))
638+
)
639+
})
640+
@GetMapping("/expense/analyzer/{historyId}")
641+
public ResponseEntity<ApiResponse<ExpenseAnalysisHistoryDTO>> getExpenseAnalyzerHistoryDetail(
642+
@Parameter(description = "조회할 내역 ID", required = true) @PathVariable Long historyId,
643+
@AuthenticationPrincipal CustomOAuth2User user) {
644+
645+
log.info("LLM 지출분석 상세 요청: historyId={}, adminEmail={}", historyId, user.getEmail());
646+
647+
ExpenseAnalysisHistoryDTO historyDetail = adminService.getExpenseAnalyzerHistoryDetail(user.getUserId(), historyId);
648+
649+
log.info("LLM 지출분석 상세 응답: {}", historyDetail);
650+
651+
return ResponseEntity.ok(ApiResponse.success(historyDetail, "LLM 지출분석 상세 조회 성공"));
652+
}
540653
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package store.lastdance.dto.admin;
2+
3+
import com.fasterxml.jackson.annotation.JsonFormat;
4+
import store.lastdance.domain.expense.ExpenseAnalysisHistory;
5+
6+
import java.time.LocalDateTime;
7+
8+
public record AdminExpenseAnalyzerHistoryDTO(
9+
Long id,
10+
String email,
11+
String nickname,
12+
LocalDateTime createdAt,
13+
Boolean up,
14+
Boolean down
15+
) {
16+
public static AdminExpenseAnalyzerHistoryDTO from(ExpenseAnalysisHistory history) {
17+
return new AdminExpenseAnalyzerHistoryDTO(
18+
history.getId(),
19+
history.getUser().getEmail(),
20+
history.getUser().getNickname(),
21+
history.getCreatedAt(),
22+
history.getUp(),
23+
history.getDown()
24+
);
25+
}
26+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package store.lastdance.dto.admin;
2+
3+
import java.util.List;
4+
5+
public record AdminExpenseAnalyzerHistoryResponseDTO(
6+
List<AdminExpenseAnalyzerHistoryDTO> histories,
7+
PaginationDTO pagination
8+
) {
9+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package store.lastdance.dto.admin;
2+
3+
import java.time.LocalDateTime;
4+
5+
6+
import java.util.List;
7+
8+
public record ExpenseAnalyzerFeedbackStatsDTO(
9+
long totalFeedbacks,
10+
long upCount,
11+
long downCount,
12+
double satisfactionRate,
13+
List<FeedbackTrendDTO> trends
14+
) {
15+
public record FeedbackTrendDTO(
16+
String date,
17+
long totalCount,
18+
long upCount,
19+
long downCount
20+
) {}
21+
}

src/main/java/store/lastdance/repository/expense/ExpenseAnalysisHistoryRepository.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22

33
import org.springframework.data.domain.Page;
44
import org.springframework.data.domain.Pageable;
5+
import org.springframework.data.jpa.domain.Specification;
56
import org.springframework.data.jpa.repository.JpaRepository;
67
import store.lastdance.domain.expense.ExpenseAnalysisHistory;
78
import store.lastdance.domain.user.User;
89

10+
import java.time.LocalDateTime;
911
import java.util.List;
1012
import java.util.UUID;
1113

@@ -15,4 +17,8 @@ public interface ExpenseAnalysisHistoryRepository extends JpaRepository<ExpenseA
1517
List<ExpenseAnalysisHistory> findByUserOrderByCreatedAtDesc(User user);
1618

1719
Page<ExpenseAnalysisHistory> findByUser(User user, Pageable pageable);
20+
21+
List<ExpenseAnalysisHistory> findByCreatedAtBetween(LocalDateTime startDate, LocalDateTime endDate);
22+
23+
Page<ExpenseAnalysisHistory> findAll(Specification<ExpenseAnalysisHistory> spec, Pageable pageable);
1824
}

src/main/java/store/lastdance/service/admin/AdminService.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import store.lastdance.domain.admin.ReportType;
55
import store.lastdance.domain.user.UserRole;
66
import store.lastdance.dto.admin.*;
7+
import store.lastdance.dto.expense.ExpenseAnalysisHistoryDTO;
78

89
import java.util.List;
910
import java.util.UUID;
@@ -35,4 +36,10 @@ public interface AdminService {
3536
AiJudgmentDetailDTO getAiJudgmentDetail(UUID userId, UUID judgmentId);
3637

3738
AiJudgmentStatsDTO getAiJudgmentStats(UUID userId, String period);
39+
40+
ExpenseAnalyzerFeedbackStatsDTO getExpenseAnalyzerFeedbackStats(UUID userId, String period);
41+
42+
AdminExpenseAnalyzerHistoryResponseDTO getExpenseAnalyzerHistory(UUID userId, int page, int limit, String search, String rating, String dateFrom, String dateTo);
43+
44+
ExpenseAnalysisHistoryDTO getExpenseAnalyzerHistoryDetail(UUID userId, Long historyId);
3845
}

0 commit comments

Comments
 (0)