using System.ComponentModel.DataAnnotations; namespace Domain.Entities.Crypto { public class Coin { public virtual List CoinCategoryMap { get; private set; } = []; public virtual List CoinMarket { get; private set; } = []; [Key] public int ID { get; private set; } public string Symbol { get; private set; } = default!; public string KorName { get; private set; } = default!; public string EngName { get; private set; } = default!; public string? LogoImage { get; private set; } public string? Description { get; private set; } public string? ContractAddress { get; private set; } public string? WebsiteUrl { get; private set; } public string? WhitepaperUrl { get; private set; } public string? TwitterUrl { get; private set; } public string? TelegramUrl { get; private set; } public bool IsActive { get; private set; } = false; public bool IsWarning { get; private set; } = false; public bool IsNew { get; private set; } = false; public bool IsDelisted { get; private set; } = false; public bool IsFeatured { get; private set; } = false; public short DisplayOrder { get; private set; } = 0; public DateTime? UpdatedAt { get; private set; } public DateTime CreatedAt { get; private set; } = DateTime.UtcNow; private Coin() { } private Coin( string symbol, string korName, string engName, string? description, string? contractAddress, string? websiteUrl, string? whitepaperUrl, string? twitterUrl, string? telegramUrl, bool isActive, bool isWarning, bool isNew, bool isDelisted ) { if (string.IsNullOrWhiteSpace(symbol)) { throw new ArgumentException("Symbol is required.", nameof(symbol)); } if (symbol.Length > 30) { throw new ArgumentOutOfRangeException(nameof(symbol)); } if (string.IsNullOrWhiteSpace(korName)) { throw new ArgumentException("KorName is required.", nameof(korName)); } if (korName.Length > 200) { throw new ArgumentOutOfRangeException(nameof(korName)); } if (string.IsNullOrWhiteSpace(engName)) { throw new ArgumentException("EngName is required.", nameof(engName)); } if (engName.Length > 200) { throw new ArgumentOutOfRangeException(nameof(engName)); } Symbol = symbol.ToUpper(); KorName = korName; EngName = engName; Description = description; ContractAddress = contractAddress; WebsiteUrl = websiteUrl; WhitepaperUrl = whitepaperUrl; TwitterUrl = twitterUrl; TelegramUrl = telegramUrl; IsActive = isActive; IsWarning = isWarning; IsNew = isNew; IsDelisted = isDelisted; } public static Coin Create( string symbol, string korName, string engName, string? description = null, string? contractAddress = null, string? websiteUrl = null, string? whitepaperUrl = null, string? twitterUrl = null, string? telegramUrl = null, bool isActive = false, bool isWarning = false, bool isNew = false, bool isDelisted = false ) => new(symbol, korName, engName, description, contractAddress, websiteUrl, whitepaperUrl, twitterUrl, telegramUrl, isActive, isWarning, isNew, isDelisted); public void Update( string symbol, string korName, string engName, string? description, string? contractAddress, string? websiteUrl, string? whitepaperUrl, string? twitterUrl, string? telegramUrl, bool isActive, bool isWarning, bool isNew, bool isDelisted ) { if (string.IsNullOrWhiteSpace(symbol)) { throw new ArgumentException("Symbol is required.", nameof(symbol)); } if (symbol.Length > 30) { throw new ArgumentOutOfRangeException(nameof(symbol)); } if (string.IsNullOrWhiteSpace(korName)) { throw new ArgumentException("KorName is required.", nameof(korName)); } if (korName.Length > 200) { throw new ArgumentOutOfRangeException(nameof(korName)); } if (string.IsNullOrWhiteSpace(engName)) { throw new ArgumentException("EngName is required.", nameof(engName)); } if (engName.Length > 200) { throw new ArgumentOutOfRangeException(nameof(engName)); } Symbol = symbol.ToUpper(); KorName = korName; EngName = engName; Description = description; ContractAddress = contractAddress; WebsiteUrl = websiteUrl; WhitepaperUrl = whitepaperUrl; TwitterUrl = twitterUrl; TelegramUrl = telegramUrl; IsActive = isActive; IsWarning = isWarning; IsNew = isNew; IsDelisted = isDelisted; UpdatedAt = DateTime.UtcNow; } public void SetLogoImage(string? logoImage) { LogoImage = logoImage; UpdatedAt = DateTime.UtcNow; } public void SetCuration(bool isFeatured, short displayOrder) { IsFeatured = isFeatured; DisplayOrder = displayOrder; UpdatedAt = DateTime.UtcNow; } // Upbit 마켓 데이터로 동기화 (신규 생성용) public static Coin SyncFromUpbit(string symbol, string korName, string engName, bool isWarning) { return new(symbol, korName, engName, null, null, null, null, null, null, isActive: true, isWarning, isNew: false, isDelisted: false); } // 상장폐지 처리 public void MarkDelisted() { IsDelisted = true; IsActive = false; UpdatedAt = DateTime.UtcNow; } // 동기화 (이름/경고 상태 갱신, 상폐 해제) public void SyncUpdate(string korName, string engName, bool isWarning) { KorName = korName; EngName = engName; IsWarning = isWarning; if (IsDelisted) { IsDelisted = false; IsActive = true; } UpdatedAt = DateTime.UtcNow; } } }