152 lines
4.8 KiB
C#
152 lines
4.8 KiB
C#
using System.Text.RegularExpressions;
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
namespace DysonNetwork.Sphere.Account;
|
|
|
|
/// <summary>
|
|
/// Service for handling username generation and validation
|
|
/// </summary>
|
|
public class AccountUsernameService(AppDatabase db)
|
|
{
|
|
private readonly Random _random = new Random();
|
|
|
|
/// <summary>
|
|
/// Generates a unique username based on the provided base name
|
|
/// </summary>
|
|
/// <param name="baseName">The preferred username</param>
|
|
/// <returns>A unique username</returns>
|
|
public async Task<string> 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}";
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generates a display name, adding numbers if needed
|
|
/// </summary>
|
|
/// <param name="baseName">The preferred display name</param>
|
|
/// <returns>A display name with optional suffix</returns>
|
|
public Task<string> 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}");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sanitizes a username by removing invalid characters and converting to lowercase
|
|
/// </summary>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks if a username already exists
|
|
/// </summary>
|
|
public async Task<bool> IsUsernameExistsAsync(string username)
|
|
{
|
|
return await db.Accounts.AnyAsync(a => a.Name == username);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generates a username from an email address
|
|
/// </summary>
|
|
/// <param name="email">The email address to generate a username from</param>
|
|
/// <returns>A unique username derived from the email</returns>
|
|
public async Task<string> 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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generates a display name from an email address
|
|
/// </summary>
|
|
/// <param name="email">The email address to generate a display name from</param>
|
|
/// <returns>A display name derived from the email</returns>
|
|
public async Task<string> 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);
|
|
}
|
|
} |