h-lab/src/main/java/com/hlab/yanalyst/domain/channel/Channel.java

152 lines
4.7 KiB
Java

package com.hlab.yanalyst.domain.channel;
import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import java.time.LocalDateTime;
@Entity
@Table(name = "youtube_channels")
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@EntityListeners(AuditingEntityListener.class)
public class Channel {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
private String channelId; // YouTube Channel ID
@Column(nullable = false)
private String title;
@Column(columnDefinition = "TEXT")
private String description;
@Column(length = 2083)
private String thumbnailUrl;
private Long subscriberCount;
private Long viewCount;
private Long videoCount;
@Column(name = "uploads_playlist_id")
private String uploadsPlaylistId;
@Column(length = 8)
private String country; // YouTube snippet.country (ISO-3166 alpha-2, 예: KR/JP/US). 없으면 null
/**
* 채널 역할: MY(내 채널) / SOURCE(소재 원본 웹예능) / RIVAL(경쟁 쇼츠).
* ddl-auto:update 특성상 기존 행은 null 이므로 조회 시 null 은 MY 로 간주한다({@link ChannelRole#normalize}).
*/
@Column(length = 10)
private String role = ChannelRole.MY;
/** 피드 분류. 기존 null 행은 예능으로 간주한다. */
@Column(name = "feed_topic", length = 20)
private String feedTopic = FeedTopic.ENTERTAINMENT.name();
/** 시드별 수집 길이 규칙. 기존 null 행은 10분 이상 롱폼으로 간주한다. */
@Column(name = "feed_format", length = 20)
private String feedFormat = FeedFormat.LONG_FORM.name();
/**
* 피드 수집 연속 실패 횟수. 3회 이상이면 자동 스킵해 쿼터 낭비를 막는다.
* 성공하면 0으로 리셋된다.
*/
@Column(name = "feed_fail_count")
private Integer feedFailCount = 0;
private LocalDateTime publishedAt;
@CreatedDate
@Column(updatable = false)
private LocalDateTime createdAt;
@LastModifiedDate
private LocalDateTime updatedAt;
@Builder
public Channel(String channelId, String title, String description, String thumbnailUrl, Long subscriberCount, Long viewCount, Long videoCount, LocalDateTime publishedAt, String uploadsPlaylistId) {
this.channelId = channelId;
this.title = title;
this.description = description;
this.thumbnailUrl = thumbnailUrl;
this.subscriberCount = subscriberCount;
this.viewCount = viewCount;
this.videoCount = videoCount;
this.publishedAt = publishedAt;
this.uploadsPlaylistId = uploadsPlaylistId;
}
public void update(String title, String description, String thumbnailUrl, Long subscriberCount, Long viewCount, Long videoCount) {
this.title = title;
this.description = description;
this.thumbnailUrl = thumbnailUrl;
this.subscriberCount = subscriberCount;
this.viewCount = viewCount;
this.videoCount = videoCount;
}
public void setUploadsPlaylistId(String uploadsPlaylistId) {
this.uploadsPlaylistId = uploadsPlaylistId;
}
public void setCountry(String country) {
this.country = country;
}
/** 역할 변경. null/미인식 값은 MY 로 보정된다. */
public void changeRole(String role) {
this.role = ChannelRole.normalize(role);
}
/** null 을 MY 로 보정한 실제 역할. */
public String roleOrDefault() {
return ChannelRole.normalize(this.role);
}
public void configureFeed(String topic, String format) {
this.feedTopic = FeedTopic.normalize(topic).name();
this.feedFormat = FeedFormat.normalize(format).name();
}
public FeedTopic feedTopicOrDefault() {
return FeedTopic.normalize(this.feedTopic);
}
public FeedFormat feedFormatOrDefault() {
return FeedFormat.normalize(this.feedFormat);
}
public int feedFailCountOrZero() {
return this.feedFailCount == null ? 0 : this.feedFailCount;
}
/** 피드 수집 실패 누적. */
public void recordFeedFailure() {
this.feedFailCount = feedFailCountOrZero() + 1;
}
/** 피드 수집 성공 — 실패 카운터 리셋. */
public void resetFeedFailure() {
this.feedFailCount = 0;
}
/** 연속 실패로 자동 비활성화된 상태인지. */
public boolean isFeedDisabled() {
return feedFailCountOrZero() >= 3;
}
}