h-lab/src/main/java/com/hlab/yanalyst/domain/channel/RecommendedChannelController.java
hehihoho3@gmail.com fd18a8981a feat(discover): 제외 목록 보기 + 복원 기능
- 목록 API status 파라미터(NEW|EXCLUDED|REGISTERED), POST /{id}/restore(→NEW)
- recommend.html: 헤더 '제외 목록' 토글로 EXCLUDED 채널 보기, 카드에 '복원' 버튼
  (제외 보기에선 등록/제외 대신 복원+채널이동만), 지역 탭은 그대로 적용

검증: 제외→EXCLUDED 목록 노출→복원→NEW 복귀 end-to-end 확인.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 11:48:34 +09:00

69 lines
3.0 KiB
Java

package com.hlab.yanalyst.domain.channel;
import com.hlab.yanalyst.global.common.ApiResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/recommended-channels")
@RequiredArgsConstructor
@Tag(name = "Recommended Channel API", description = "자동 발굴된 추천 채널(떡상 Shorts)")
public class RecommendedChannelController {
private final RecommendedChannelRepository repository;
private final ChannelDiscoveryService discoveryService;
private final ChannelService channelService;
@GetMapping
@Operation(summary = "추천 채널 목록", description = "status(NEW|EXCLUDED|REGISTERED, 기본 NEW)를 배율 내림차순으로 반환")
public ApiResponse<List<RecommendedChannel>> list(@RequestParam(defaultValue = "NEW") String status) {
return ApiResponse.ok(repository.findByStatusOrderByRatioDesc(status));
}
@PostMapping("/{id}/register")
@Operation(summary = "내 채널 등록", description = "추천 채널을 내 채널로 등록하고 REGISTERED 처리")
@Transactional
public ApiResponse<Void> register(@PathVariable Long id) {
RecommendedChannel rc = repository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("추천 채널을 찾을 수 없습니다: " + id));
channelService.saveChannelFromUrl("https://www.youtube.com/channel/" + rc.getChannelId());
rc.setStatus("REGISTERED");
repository.save(rc);
return ApiResponse.ok(null);
}
@PostMapping("/{id}/exclude")
@Operation(summary = "추천 제외", description = "다음 발굴에서 다시 뜨지 않게 EXCLUDED 처리")
@Transactional
public ApiResponse<Void> exclude(@PathVariable Long id) {
RecommendedChannel rc = repository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("추천 채널을 찾을 수 없습니다: " + id));
rc.setStatus("EXCLUDED");
repository.save(rc);
return ApiResponse.ok(null);
}
@PostMapping("/{id}/restore")
@Operation(summary = "제외 복원", description = "EXCLUDED 를 다시 NEW(추천 목록)로 되돌린다")
@Transactional
public ApiResponse<Void> restore(@PathVariable Long id) {
RecommendedChannel rc = repository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("추천 채널을 찾을 수 없습니다: " + id));
rc.setStatus("NEW");
repository.save(rc);
return ApiResponse.ok(null);
}
@PostMapping("/run")
@Operation(summary = "수동 발굴 실행", description = "스케줄과 동일한 발굴을 즉시 1회 실행")
public ApiResponse<Map<String, Object>> run() {
return ApiResponse.ok(discoveryService.runDiscovery());
}
}