h-lab/src/main/java/com/hlab/yanalyst/service/DashboardService.java
hehihoho3@gmail.com 2ec3915789 feat(dashboard): add full-set pipeline summary with single endpoint
Add GET /api/dashboard/summary aggregating pipeline status, category
distribution, publish summary, and outperformers in one call. Rewrite
dashboard.html with 5 KPI cards, pipeline funnel, publish status, and
category/source-format breakdowns (CSS bars, no chart lib).

Backend: ChannelVideoRepository counts (shorts/uncategorized),
PublishPackageRepository.countByStatus, pipelineStats shorts/longForm,
CategoryService.distribution, PublishService.dashboardSummary, new
DashboardService + DashboardApiController.

Fix: PublishService.list(null) hit UnsupportedOperationException because
findAll(Sort) uses Criteria, which rejects nullsLast precedence. Route the
no-status path through a @Query method so Sort is appended as HQL ORDER BY
(supports NULLS LAST). Also fixes the latent bug in /api/v1/publish all-list.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 22:31:21 +09:00

39 lines
1.6 KiB
Java

package com.hlab.yanalyst.service;
import com.hlab.yanalyst.domain.category.CategoryService;
import com.hlab.yanalyst.domain.channel.ChannelVideo;
import com.hlab.yanalyst.domain.channel.ChannelVideoCurationService;
import com.hlab.yanalyst.domain.publish.PublishService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* 홈 대시보드용 단일 집계 — 파이프라인(수집→큐레이션→발행) 현황을 한 번에 묶어 반환한다.
* 각 도메인 서비스의 집계를 조합만 한다(리포지토리 직접 접근 없음).
*/
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class DashboardService {
private final ChannelVideoCurationService curationService;
private final CategoryService categoryService;
private final PublishService publishService;
public Map<String, Object> summary() {
Map<String, Object> result = new LinkedHashMap<>();
result.put("pipeline", curationService.pipelineStats()); // total, byStatus, bySource, shorts, longForm
result.put("categories", categoryService.distribution()); // categories[], uncategorized
result.put("publish", publishService.dashboardSummary()); // byStatus, total, recent
List<ChannelVideo> outperformers = curationService.findOutperformers(5, BigDecimal.ONE);
result.put("outperformers", outperformers);
return result;
}
}