using System.ComponentModel.DataAnnotations; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Hosting; namespace Nuuru.Server.Models { public class ApplicationUser : IdentityUser { public ICollection BooruPosts { get; set; } public ICollection BooruComments { get; set; } public ICollection ForumThreads { get; set; } public ICollection ForumPosts { get; set; } public string Status { get; set; } public string Biography { get; set; } public string? BiographyHtml { get; set; } public string? AvatarStorageIdentifier { get; set; } public string? BackgroundImageStorageIdentifier { get; set; } public DateTime DateCreated { get; set; } = DateTime.UtcNow; public DateTime? SignupVerificationAvailableAt { get; set; } public DateTime? SignupVerificationCompletedAt { get; set; } public string? SignupVerificationIpAddress { get; set; } public string? SignupVerificationCountryCode { get; set; } public string? SignupVerificationRegionCode { get; set; } public string? SignupVerificationCity { get; set; } public string? SignupVerificationIspAsn { get; set; } public string? SignupVerificationIspName { get; set; } public ICollection Notifications { get; set; } public ICollection UserIps { get; set; } public ICollection Bans { get; set; } = new List(); public ICollection BooruPostVotes { get; set; } = new List(); public ICollection BooruPostFavorites { get; set; } = new List(); public ICollection ConversationParticipations { get; set; } = []; public ICollection Messages { get; set; } = []; public bool IsSystemAccount { get; set; } = false; public bool IsBabyMode { get; set; } = false; public bool IsGeoVerificationPending { get; set; } public int ReactionScore { get; set; } = 0; public int Boints { get; set; } = 0; public int? FeaturedThreadId { get; set; } // Boints cosmetics [MaxLength(50)] public bool HasProfileBorder { get; set; } = false; public bool HasRenameImmunity { get; set; } = false; public DateTime? RenameImmunityUntil { get; set; } [MaxLength(30)] public string? ForcedDisplayName { get; set; } public DateTime? ForcedDisplayNameUntil { get; set; } // Computed temporal checks — use on materialized entities only (not in EF LINQ projections) public string? ActiveForcedDisplayName => ForcedDisplayName != null && ForcedDisplayNameUntil > DateTime.UtcNow ? ForcedDisplayName : null; public static string? GetAvatarUrl(string? userName, string? avatarStorageIdentifier) { if (avatarStorageIdentifier == null || string.IsNullOrEmpty(userName)) return null; var hash = avatarStorageIdentifier.Length >= 8 ? avatarStorageIdentifier[..8] : avatarStorageIdentifier; return $"/api/user/{userName}/avatar?v={hash}"; } public static string? GetBackgroundImageUrl(string? userName, string? storageId) { if (storageId == null || string.IsNullOrEmpty(userName)) return null; var hash = storageId.Length >= 8 ? storageId[..8] : storageId; return $"/api/user/{userName}/background?v={hash}"; } } public class ApplicationRole : IdentityRole { public string? Color { get; set; } public int Priority { get; set; } = 0; } }