using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; namespace Domain.Entities.Members; public class RefreshToken { [ForeignKey(nameof(MemberID))] public virtual Member? Member { get; private set; } [Key] public long ID { get; private set; } public int MemberID { get; private set; } public string Token { get; private set; } = default!; public DateTime ExpiresAt { get; private set; } public DateTime CreatedAt { get; private set; } = DateTime.UtcNow; public bool IsRevoked { get; private set; } = false; public DateTime? RevokedAt { get; private set; } /// 회전 시 발급된 후속 토큰. race condition grace period 흡수용. public string? ReplacedByToken { get; private set; } private RefreshToken() { } public static RefreshToken Create( int memberID, string token, DateTime expiresAt ) { return new RefreshToken { MemberID = memberID, Token = token, ExpiresAt = expiresAt }; } public void Revoke() { IsRevoked = true; RevokedAt = DateTime.UtcNow; } /// 회전 시 호출 — 새 토큰을 ReplacedByToken에 기록. public void MarkReplaced(string newToken) { ReplacedByToken = newToken; } public bool IsExpired => DateTime.UtcNow >= ExpiresAt; public bool IsActive => !IsRevoked && !IsExpired; }