using System.ComponentModel.DataAnnotations.Schema; using System.Security.Cryptography; using System.Text; using Cysharp.Text; namespace OliverBooth.Data.Blog; /// /// Represents a user. /// internal sealed class User : IUser, IBlogAuthor { /// [NotMapped] public Uri AvatarUrl => GetAvatarUrl(); /// public string EmailAddress { get; set; } = string.Empty; /// public string DisplayName { get; set; } = string.Empty; /// public Guid Id { get; private set; } = Guid.NewGuid(); /// public DateTimeOffset Registered { get; private set; } = DateTimeOffset.UtcNow; /// /// Gets or sets the password hash. /// /// The password hash. internal string Password { get; set; } = string.Empty; /// /// Gets or sets the salt used to hash the password. /// /// The salt used to hash the password. internal string Salt { get; set; } = string.Empty; /// public Uri GetAvatarUrl(int size = 28) { if (string.IsNullOrWhiteSpace(EmailAddress)) { return new Uri($"https://www.gravatar.com/avatar/0?size={size}"); } ReadOnlySpan span = EmailAddress.AsSpan(); int byteCount = Encoding.UTF8.GetByteCount(span); Span bytes = stackalloc byte[byteCount]; Encoding.UTF8.GetBytes(span, bytes); Span hash = stackalloc byte[16]; MD5.TryHashData(bytes, hash, out _); using Utf8ValueStringBuilder builder = ZString.CreateUtf8StringBuilder(); Span hex = stackalloc char[2]; for (var index = 0; index < hash.Length; index++) { if (hash[index].TryFormat(hex, out _, "x2")) builder.Append(hex); else builder.Append("00"); } return new Uri($"https://www.gravatar.com/avatar/{builder}?size={size}"); } /// public bool TestCredentials(string password) { return false; } }