using System.Collections.Generic; using System.Threading.Tasks; using GameAuthApi.Models; using Microsoft.Extensions.Configuration; using MongoDB.Driver; using System; using System.Linq; namespace GameAuthApi.Services; public class UserService { private readonly IMongoCollection _users; public UserService(IConfiguration config) { var client = new MongoClient(config["MongoDB:ConnectionString"]); var database = client.GetDatabase(config["MongoDB:DatabaseName"]); _users = database.GetCollection("Users"); } public virtual async Task GetByUsernameAsync(string username) => await _users.Find(u => u.Username == username).FirstOrDefaultAsync(); public virtual async Task GetByLoginAsync(string login) { var normalized = Normalize(login); return await _users.Find(u => u.NormalizedUsername == normalized || u.NormalizedEmail == normalized || u.Username == login || u.Email == login).FirstOrDefaultAsync(); } public virtual async Task GetByIdAsync(string id) => await _users.Find(u => u.Id == id).FirstOrDefaultAsync(); public virtual async Task> GetAllAsync() => await _users.Find(_ => true).ToListAsync(); public virtual async Task DeleteAsync(string username) { var result = await _users.DeleteOneAsync(u => u.Username == username); return result.DeletedCount > 0; } public virtual async Task CreateAsync(User user) { ApplyNormalizedFields(user); await _users.InsertOneAsync(user); } public virtual async Task UpdateAsync(User user) { ApplyNormalizedFields(user); await _users.ReplaceOneAsync(u => u.Id == user.Id, user); } public virtual async Task EnsureIndexesAsync() { var username = new CreateIndexModel( Builders.IndexKeys.Ascending(u => u.NormalizedUsername), new CreateIndexOptions { Unique = true, Sparse = true, Name = "normalized_username_unique" }); var email = new CreateIndexModel( Builders.IndexKeys.Ascending(u => u.NormalizedEmail), new CreateIndexOptions { Unique = true, Sparse = true, Name = "normalized_email_unique" }); await _users.Indexes.CreateManyAsync(new[] { username, email }); } public virtual async Task BackfillNormalizedFieldsAsync() { var users = await _users.Find(_ => true).ToListAsync(); var duplicateUsername = users.GroupBy(u => Normalize(u.Username)).FirstOrDefault(g => g.Key != string.Empty && g.Count() > 1); var duplicateEmail = users.Where(u => !string.IsNullOrWhiteSpace(u.Email)).GroupBy(u => Normalize(u.Email)).FirstOrDefault(g => g.Count() > 1); if (duplicateUsername != null || duplicateEmail != null) throw new InvalidOperationException("Duplicate normalized usernames or emails must be resolved before starting the authentication service."); foreach (var user in users) { var normalizedUsername = Normalize(user.Username); var normalizedEmail = string.IsNullOrWhiteSpace(user.Email) ? null : Normalize(user.Email); if (user.NormalizedUsername == normalizedUsername && user.NormalizedEmail == normalizedEmail) continue; user.NormalizedUsername = normalizedUsername; user.NormalizedEmail = normalizedEmail; await _users.ReplaceOneAsync(u => u.Id == user.Id, user); } } public static string Normalize(string value) => value?.Trim().ToUpperInvariant() ?? string.Empty; private static void ApplyNormalizedFields(User user) { user.NormalizedUsername = Normalize(user.Username); user.NormalizedEmail = string.IsNullOrWhiteSpace(user.Email) ? null : Normalize(user.Email); } }