using adas_core.Application.Repositories.Interfaces; using adas_core.Domain.Enums; using adas_core.Domain.Models.AppSettings; using adas_core.Domain.Models.Filter; using adas_core.Domain.Models.MongoModels; using Microsoft.Extensions.Options; using MongoDB.Bson; using MongoDB.Driver; using System.Text.RegularExpressions; namespace adas_core.Infrastructure.Repositories; /// /// Represents a MongoDB-backed repository for managing entities. /// /// /// Inherits core data access functionality from and implements the contract. /// public class UserRepository : MongoRepository, IUserRepository { private readonly ApiSettings _apiSettings; public UserRepository(IOptions apiSettings, IMongoDatabase database) : base(database) { if (apiSettings == null) throw new ArgumentNullException(nameof(apiSettings)); _apiSettings = apiSettings.Value; } /// /// Retrieves the API collection name for users from the configured API settings. /// /// The configured name of the users collection. public override string GetCollectionName() { return _apiSettings.Users; } /// /// Retrieves a user from the collection whose username and password match the provided credentials. /// Returns the first matching user, or null when no user with the given username and password combination is found. /// /// The username to look up in the collection. /// The password that must match the stored value for the user to be returned. /// A instance when a matching record is found; otherwise, null. public async Task GetUser(string username, string password) { var filter = Builders .Filter.Eq(p => p.UserName, username) & Builders .Filter.Eq(p => p.Password, password); var result = await Collection.FindAsync(filter); return await result.FirstOrDefaultAsync(); } /// /// Retrieves a user from the collection by their unique identifier, returning null when no matching document is found. /// /// The of the user to look up. /// A instance if a matching document exists; otherwise, null. public async Task GetById(ObjectId id) { var filter = Builders .Filter.Eq(p => p.Id, id); var result = await Collection.FindAsync(filter); return await result.FirstOrDefaultAsync(); } /// /// Retrieves a user from the data store by their unique username. /// Returns null when no user matches the provided username. /// /// The username used to look up the user. /// A instance if a match is found; otherwise, null. public async Task GetByUserName(string name) { var filter = Builders .Filter.Eq(p => p.UserName, name); var result = await Collection.FindAsync(filter); return await result.FirstOrDefaultAsync(); } /// /// Retrieves a user by their username along with their associated authorizations, using a MongoDB aggregation pipeline that joins the user with the authorizations collection and resolves the user's role (Admin role is preserved, otherwise mapped to "Some"). Returns null when no user matches the provided username. /// /// The username used to match the user document in the collection. /// A that yields the matching with its authorizations and resolved role, or null if no user is found. public async Task GetByUserAndAuthoritesName(string name) { var matchStage = new BsonDocument("$match", new BsonDocument("userName", name)); var lookupStage = new BsonDocument("$lookup", new BsonDocument { { "from", "authorizations" }, { "localField", "_id" }, { "foreignField", "userId" }, { "as", "Authorization" } }); var projectStage = new BsonDocument("$project", new BsonDocument { { "_id", 1 }, { "userName", 1 }, { "email", 1 }, { "name", 1 }, { "Authorization", "$Authorization" }, { "rol", new BsonDocument("$cond", new BsonArray { new BsonDocument("$eq", new BsonArray { "$Authorization.rol", "Admin" }), "$rol", "Some" }) } }); var pipeline = new[] { matchStage, lookupStage, projectStage }; var options = new AggregateOptions { AllowDiskUse = true }; var result = await Collection.AggregateAsync(pipeline, options); var bsonResult = await result.FirstOrDefaultAsync(); return bsonResult; } /// /// Retrieves a user from the data store whose name exactly matches the specified value, returning null if no matching user exists. /// /// The name of the user to look up. /// A instance if a match is found; otherwise, null. public async Task GetByName(string name) { var filter = Builders .Filter.Eq(p => p.Name, name); var result = await Collection.FindAsync(filter); return await result.FirstOrDefaultAsync(); } /// /// Updates an existing user in the data store with the provided information, optionally including the password. /// When is true, the user's password is also persisted; otherwise, it is left unchanged. /// /// The user entity whose fields will be applied to the existing record, identified by . /// If true, the password field is included in the update; if false, the password is not modified. /// The updated retrieved after the update, or null if no matching user was found. public async Task UpdateUser(User user, bool updatePass) { var filter = Builders .Filter.Eq(p => p.Id, user.Id); var update = Builders.Update .Set(u => u.UserName, user.UserName) .Set(u => u.Name, user.Name) .Set(u => u.Email, user.Email) .Set(u => u.LockExpirationDate, user.LockExpirationDate) .Set(u => u.LastLogin, user.LastLogin) .Set(u => u.IsEnabled, user.IsEnabled); if (updatePass) update = update.Set(u => u.Password, user.Password); await Collection.UpdateOneAsync(filter, update); var result = await Collection.Find(filter).FirstOrDefaultAsync(); return result; } /// /// Retrieves a paginated and filterable queryable collection of users based on the supplied pagination and filter criteria. Applies a case-insensitive text search across the user's Name, Email, and UserName fields when a text filter is provided, and conditionally combines user status and user type filters; when no filter payload is supplied, returns the base sorted find fluent without applying additional filters. /// /// The pagination filter containing paging details and the optional filter payload (text, user status, and user type) used to build the MongoDB query. /// An of sorted ascending by user name and shaped by the assembled filter definitions. public IFindFluent GetPaginatedUsers(PaginationFilter filteredRequest) { // Crear variable con la clase que construye los filtros que necesitamos var filterBuilder = Builders.Filter; var sort = Builders.Sort.Ascending("userName"); // Crear una lista de filtros que pueden venir de tu servicio var filters = new List>(); if (filteredRequest.FilteredRequest == null) return CreateFindFluent(filters, sort); var textFilter = filteredRequest.FilteredRequest?.Text; if (!string.IsNullOrEmpty(textFilter)) { var textFilterEscaped = Regex.Escape(textFilter); var orFilters = new List> { filterBuilder.Regex(p => p.Name, new BsonRegularExpression(textFilterEscaped, "i")), filterBuilder.Regex(p => p.Email, new BsonRegularExpression(textFilterEscaped, "i")), filterBuilder.Regex(p => p.UserName, new BsonRegularExpression(textFilterEscaped, "i")) }; filters.Add(filterBuilder.Or(orFilters)); } var userStatus = filteredRequest.FilteredRequest?.UserStatus; filters.Add(filterBuilder.And(GetUserStatusFilter(userStatus))); if (Enum.TryParse(filteredRequest.FilteredRequest?.UserType, out UserEnum.Type userType)) { GetUserTypeFilter(userType); filters.Add(filterBuilder.And(GetUserTypeFilter(userType))); } return CreateFindFluent(filters, sort); } /// /// Retrieves the system user identified by the username "System", creating and inserting a new one with default credentials if no existing user is found. /// /// The existing system user if found; otherwise, the newly created and inserted system user. public async Task GetOrCreateSystemUser() { var user = await GetByUserName("System"); if (user == null) { var userToInsert = new User { UserName = "System", Name = "System", Password = "$2a$12$crWa3EN1izcZBXNc81RzmOlfaYW2TPr2NdDQEWI7RzLTnA0Dd68WG" }; await Collection.InsertOneAsync(userToInsert); return userToInsert; } return user; } /// /// Inserts the initial load of data by ensuring that the system user exists, creating one if necessary. /// public sealed override async Task InsertInitialLoad() { await GetOrCreateSystemUser(); } /// /// Creates a fluent find query for users by combining the provided filters with an AND operation /// and applying the specified sort definition. When no filters are supplied, an empty filter is used /// as a fallback, which matches all documents. /// /// The list of filter definitions to be combined with logical AND. /// The sort definition to apply to the query results. /// A fluent find query for the collection with the combined filter and sort applied. private IFindFluent CreateFindFluent(List> filters, SortDefinition sort) { var combinedFilter = filters.Any() ? Builders.Filter.And(filters) : Builders.Filter.Empty; // Filtra todo si no hay filtros return Collection.Find(combinedFilter).Sort(sort); } /// /// Builds a list of MongoDB filter definitions for users based on the specified user type. /// Only Local and Ldap user types produce filters; any other value or null results in an empty filter list. /// /// The user type to filter by. If null or not one of the handled values, no filter is added. /// A list of FilterDefinition objects matching the specified user type, or an empty list if no specific type was matched. private List> GetUserTypeFilter(UserEnum.Type? type) { var filters = new List>(); var filterBuilder = Builders.Filter; switch (type) { case UserEnum.Type.Local: filters.Add(filterBuilder.Eq(u => u.Type, UserEnum.Type.Local)); break; case UserEnum.Type.Ldap: filters.Add(filterBuilder.Eq(u => u.Type, UserEnum.Type.Ldap)); break; } return filters; } /// /// Builds a list of MongoDB filters that match users according to the requested status, handling enabled (including accounts where the enabled flag is missing), enabled and unlocked, enabled and locked, and disabled cases. /// /// The user status used to select which filter combination is applied; a null or unhandled value yields an empty filter list. /// A list of filters that should be combined to query documents for the specified status. private List> GetUserStatusFilter(StatusEnum.User? status) { var filters = new List>(); var filterBuilder = Builders.Filter; switch (status) { case StatusEnum.User.Enabled: filters.Add(filterBuilder.Or( filterBuilder.Eq(u => u.IsEnabled, true), filterBuilder.Exists(u => u.IsEnabled, false) )); break; case StatusEnum.User.EnabledUnlocked: filters.Add(filterBuilder.Or( filterBuilder.Eq(u => u.IsEnabled, true), filterBuilder.Exists(u => u.IsEnabled, false) )); filters.Add(filterBuilder.Ne(u => u.LockExpirationDate, null)); break; case StatusEnum.User.EnabledLocked: filters.Add(filterBuilder.Or( filterBuilder.Eq(u => u.IsEnabled, true), filterBuilder.Exists(u => u.IsEnabled, false) )); filters.Add(filterBuilder.Eq(u => u.LockExpirationDate, null)); break; case StatusEnum.User.Disabled: filters.Add(filterBuilder.Eq(u => u.IsEnabled, false)); break; } return filters; } }