using System.Text.RegularExpressions; using Microsoft.EntityFrameworkCore; namespace DysonNetwork.Sphere.Account; /// /// Service for handling username generation and validation /// public class AccountUsernameService(AppDatabase db) { private readonly Random _random = new Random(); /// /// Generates a unique username based on the provided base name /// /// The preferred username /// A unique username public async Task GenerateUniqueUsernameAsync(string baseName) { // Sanitize the base name var sanitized = SanitizeUsername(baseName); // If the base name is empty after sanitization, use a default if (string.IsNullOrEmpty(sanitized)) { sanitized = "user"; } // Check if the sanitized name is available if (!await IsUsernameExistsAsync(sanitized)) { return sanitized; } // Try up to 10 times with random numbers for (int i = 0; i < 10; i++) { var suffix = _random.Next(1000, 9999); var candidate = $"{sanitized}{suffix}"; if (!await IsUsernameExistsAsync(candidate)) { return candidate; } } // If all attempts fail, use a timestamp var timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); return $"{sanitized}{timestamp}"; } /// /// Generates a display name, adding numbers if needed /// /// The preferred display name /// A display name with optional suffix public Task GenerateUniqueDisplayNameAsync(string baseName) { // If the base name is empty, use a default if (string.IsNullOrEmpty(baseName)) { baseName = "User"; } // Truncate if too long if (baseName.Length > 50) { baseName = baseName.Substring(0, 50); } // Since display names can be duplicated, just return the base name // But add a random suffix to make it more unique visually var suffix = _random.Next(1000, 9999); return Task.FromResult($"{baseName}{suffix}"); } /// /// Sanitizes a username by removing invalid characters and converting to lowercase /// public string SanitizeUsername(string username) { if (string.IsNullOrEmpty(username)) return string.Empty; // Replace spaces and special characters with underscores var sanitized = Regex.Replace(username, @"[^a-zA-Z0-9_\-]", ""); // Convert to lowercase sanitized = sanitized.ToLowerInvariant(); // Ensure it starts with a letter if (sanitized.Length > 0 && !char.IsLetter(sanitized[0])) { sanitized = "u" + sanitized; } // Truncate if too long if (sanitized.Length > 30) { sanitized = sanitized[..30]; } return sanitized; } /// /// Checks if a username already exists /// public async Task IsUsernameExistsAsync(string username) { return await db.Accounts.AnyAsync(a => a.Name == username); } /// /// Generates a username from an email address /// /// The email address to generate a username from /// A unique username derived from the email public async Task GenerateUsernameFromEmailAsync(string email) { if (string.IsNullOrEmpty(email)) return await GenerateUniqueUsernameAsync("user"); // Extract the local part of the email (before the @) var localPart = email.Split('@')[0]; // Use the local part as the base for username generation return await GenerateUniqueUsernameAsync(localPart); } /// /// Generates a display name from an email address /// /// The email address to generate a display name from /// A display name derived from the email public async Task GenerateDisplayNameFromEmailAsync(string email) { if (string.IsNullOrEmpty(email)) return await GenerateUniqueDisplayNameAsync("User"); // Extract the local part of the email (before the @) var localPart = email.Split('@')[0]; // Capitalize first letter and replace dots/underscores with spaces var displayName = Regex.Replace(localPart, @"[._-]+", " "); // Capitalize words displayName = System.Globalization.CultureInfo.CurrentCulture.TextInfo.ToTitleCase(displayName); return await GenerateUniqueDisplayNameAsync(displayName); } }