// *********************************************************************** // // Heath // // *********************************************************************** using Core.Cerberos.Adapters; using Core.Cerberos.Adapters.Common.Constants; using Core.Cerberos.Adapters.Common.Enums; using Core.Cerberos.Domain.Contexts.Onboarding.Mappers; using Core.Cerberos.Domain.Contexts.Onboarding.Request; using Core.Cerberos.Infraestructure.Caching.Configs; using Core.Cerberos.Infraestructure.Caching.Contracts; using Core.Cerberos.Provider.Contracts; using LSA.Core.Dapper.Service.Caching; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using MongoDB.Bson; using MongoDB.Bson.Serialization; using MongoDB.Driver; using System.Text.RegularExpressions; using Core.Blueprint.Storage.Contracts; using Core.Blueprint.Storage.Adapters; namespace Core.Cerberos.Provider.Providers.Onboarding { /// /// Handles all services and business rules related to . /// public class UserService(ILogger logger, IHttpContextAccessor httpContextAccessor, ICacheService cacheService, IOptions cacheSettings, IMongoDatabase database, IBlobStorageProvider blobStorageProvider) : IUserService { private readonly CacheSettings _cacheSettings = cacheSettings.Value; /// /// Creates a new User. /// /// The User to be created. /// A representing /// the asynchronous execution of the service. public async Task CreateUserService(UserRequest newUser) { try { var entity = newUser.ToAdapter(httpContextAccessor); await database.GetCollection(CollectionNames.User).InsertOneAsync(entity); entity.Id = (entity as dynamic ?? "").Id.ToString(); return entity; } catch (Exception ex) { logger.LogError(ex, $"CreateUserService: Error in getting data - {ex.Message}"); throw new Exception(ex.Message, ex); } } /// /// Gets an User by identifier. /// /// The User identifier. /// A representing /// the asynchronous execution of the service. public async Task GetUserByIdService(string id) { var cacheKey = CacheKeyHelper.GenerateCacheKey(this, "GetUserByIdService", id); var cachedData = await cacheService.GetAsync(cacheKey); if (cachedData is not null) return cachedData; try { var filter = Builders.Filter.And( Builders.Filter.Eq("_id", ObjectId.Parse(id)), Builders.Filter.Eq("status", StatusEnum.Active.ToString()) ); var user = await database.GetCollection(CollectionNames.User) .Find(filter) .FirstOrDefaultAsync(); var cacheDuration = CacheHelper.GetCacheDuration(_cacheSettings); await cacheService.SetAsync(cacheKey, user, cacheDuration); return user; } catch (Exception ex) { logger.LogError(ex, $"GetUserByIdService: Error in getting data - {ex.Message}"); throw new Exception(ex.Message, ex); } } /// /// Gets all the users. /// /// A representing /// the asynchronous execution of the service. public async Task> GetAllUsersService() { var cacheKey = CacheKeyHelper.GenerateCacheKey(this, "GetAllUsersService"); var cachedData = await cacheService.GetAsync>(cacheKey) ?? []; if (cachedData.Any()) return cachedData; try { var filter = Builders.Filter.Eq("status", StatusEnum.Active.ToString()); var users = await database.GetCollection(CollectionNames.User) .Find(filter) .ToListAsync(); var cacheDuration = CacheHelper.GetCacheDuration(_cacheSettings); await cacheService.SetAsync(cacheKey, users, cacheDuration); return users; } catch (Exception ex) { logger.LogError(ex, $"GetAllUsersService: Error in getting data - {ex.Message}"); throw new Exception(ex.Message, ex); } } /// /// Gets an User by email. /// /// The User email. /// A representing /// the asynchronous execution of the service. public async Task GetUserByEmailService(string? email) { try { var filter = Builders.Filter.And( Builders.Filter.Regex("email", new BsonRegularExpression($"^{Regex.Escape(email ?? "")}$", "i")), Builders.Filter.Eq("status", StatusEnum.Active.ToString()) ); var user = await database.GetCollection(CollectionNames.User) .Find(filter) .FirstOrDefaultAsync(); return user; } catch (Exception ex) { logger.LogError(ex, $"GetUserByEmailService: Error in getting data - {ex.Message}"); throw new Exception(ex.Message, ex); } } /// /// Validates if a users exists by email.. /// /// The User email. /// A representing /// the asynchronous execution of the service. public async Task ValidateUserExistenceService(string? email) { try { var filter = Builders.Filter.And( Builders.Filter.Regex("email", new BsonRegularExpression($"^{Regex.Escape(email ?? "")}$", "i")), Builders.Filter.Eq("status", StatusEnum.Active.ToString()) ); var user = await database.GetCollection(CollectionNames.User) .Find(filter) .FirstOrDefaultAsync(); return user; } catch (Exception ex) { logger.LogError(ex, $"ValidateUserExistenceService: Error in getting data - {ex.Message}"); throw new Exception(ex.Message, ex); } } /// /// Deletes an User by id. /// /// The User identifier. /// A representing /// the asynchronous execution of the service. public async Task DeleteUserService(string id) { try { var filter = Builders.Filter .Eq("_id", ObjectId.Parse(id)); var update = Builders.Update .Set(v => v.Status, StatusEnum.Inactive); await database.GetCollection(CollectionNames.User).UpdateOneAsync(filter, update); var deletedUser = await database.GetCollection(CollectionNames.User).Find(filter).FirstAsync(); return deletedUser; } catch (Exception ex) { logger.LogError(ex, $"DeleteUserService: Error in getting data - {ex.Message}"); throw new Exception(ex.Message, ex); } } /// /// Changes the status of the user. /// /// The user identifier. /// The new status of the user. /// A representing /// the asynchronous execution of the service. public async Task ChangeUserStatusService(string id, StatusEnum newStatus) { try { var filter = Builders.Filter .Eq("_id", ObjectId.Parse(id)); var update = Builders.Update .Set(v => v.Status, newStatus) .Set(v => v.UpdatedBy, Helper.GetEmail(httpContextAccessor)) .Set(v => v.UpdatedAt, DateTime.UtcNow); await database.GetCollection(CollectionNames.User).UpdateOneAsync(filter, update); var updatedUser = await database.GetCollection(CollectionNames.User) .Find(filter) .FirstOrDefaultAsync(); return updatedUser; } catch (Exception ex) { logger.LogError(ex, $"ChangeUserStatusService: Error in getting data - {ex.Message}"); throw new Exception(ex.Message, ex); } } /// /// Updates a User by id. /// /// The User to be updated. /// The User identifier. /// A representing /// the asynchronous execution of the service. public async Task UpdateUserService(UserAdapter entity, string id) { try { var filter = Builders.Filter .Eq("_id", ObjectId.Parse(id)); var update = Builders.Update .Set(v => v.Email, entity.Email) .Set(v => v.Name, entity.Name) .Set(v => v.MiddleName, entity.MiddleName) .Set(v => v.LastName, entity.LastName) .Set(v => v.DisplayName, $"{entity.Name} {entity.MiddleName} {entity.LastName}") .Set(v => v.RoleId, entity.RoleId) .Set(v => v.Companies, entity.Companies) .Set(v => v.Projects, entity.Projects) .Set(v => v.Status, entity.Status) .Set(v => v.UpdatedBy, Helper.GetEmail(httpContextAccessor)) .Set(v => v.UpdatedAt, DateTime.UtcNow); await database.GetCollection(CollectionNames.User).UpdateOneAsync(filter, update); var updatedUser = await GetUserByIdService(id); return updatedUser; } catch (Exception ex) { logger.LogError(ex, $"UpdateUserService: Error in getting data - {ex.Message}"); throw new Exception(ex.Message, ex); } } /// /// Logs in the user. /// /// The User identifier. /// A representing /// the asynchronous execution of the service. public async Task LogInUserService(string email) { try { var filter = Builders.Filter .Regex("email", new BsonRegularExpression($"^{Regex.Escape(email ?? "")}$", "i")); var update = Builders.Update .Set(v => v.LastLogIn, DateTime.UtcNow); await database.GetCollection(CollectionNames.User).UpdateOneAsync(filter, update); var user = await database.GetCollection(CollectionNames.User) .Find(filter) .FirstAsync(); return user; } catch (Exception ex) { logger.LogError(ex, $"LogInUserService: Error in getting data - {ex.Message}"); throw new Exception(ex.Message, ex); } } /// /// Logs out the user's session. /// /// The User email. /// A representing /// the asynchronous execution of the service. public async Task LogOutUserSessionService(string email) { try { var filter = Builders.Filter .Regex("email", new BsonRegularExpression($"^{Regex.Escape(email ?? "")}$", "i")); var update = Builders.Update .Set(v => v.LastLogOut, DateTime.UtcNow); await database.GetCollection(CollectionNames.User).UpdateOneAsync(filter, update); var user = await database.GetCollection(CollectionNames.User) .Find(filter) .FirstAsync(); return user; } catch (Exception ex) { logger.LogError(ex, $"LogOutUserSessionService: Error in getting data - {ex.Message}"); throw new Exception(ex.Message, ex); } } /// /// Adds a company to the user's list of companies. /// /// The identifier of the user to whom the company will be added. /// The identifier of the company to add. /// A representing the asynchronous operation, with the updated user object. public async Task AddCompanyToUserService(string userId, string companyId) { try { var filter = Builders.Filter.Eq("_id", ObjectId.Parse(userId)); var update = Builders.Update.AddToSet(v => v.Companies, companyId); await database.GetCollection(CollectionNames.User).UpdateOneAsync(filter, update); var updatedUser = await database.GetCollection(CollectionNames.User) .Find(filter) .FirstOrDefaultAsync(); return updatedUser; } catch (Exception ex) { logger.LogError(ex, $"AddCompanyToUserService: Error in getting data - {ex.Message}"); throw new Exception(ex.Message, ex); } } /// /// Removes a company from the user's list of companies. /// /// The identifier of the user from whom the company will be removed. /// The identifier of the company to remove. /// A representing the asynchronous operation, with the updated user object. public async Task RemoveCompanyFromUserService(string userId, string companyId) { try { var filter = Builders.Filter.Eq("_id", ObjectId.Parse(userId)); var update = Builders.Update.Pull(v => v.Companies, companyId); await database.GetCollection(CollectionNames.User).UpdateOneAsync(filter, update); var updatedUser = await database.GetCollection(CollectionNames.User) .Find(filter) .FirstOrDefaultAsync(); return updatedUser; } catch (Exception ex) { logger.LogError(ex, $"RemoveCompanyFromUserService: Error in getting data - {ex.Message}"); throw new Exception(ex.Message, ex); } } /// /// Adds a project to the user's list of projects. /// /// The identifier of the user to whom the project will be added. /// The identifier of the project to add. /// A representing the asynchronous operation, with the updated user object. public async Task AddProjectToUserService(string userId, string projectId) { try { var filter = Builders.Filter.Eq("_id", ObjectId.Parse(userId)); var update = Builders.Update.AddToSet(v => v.Projects, projectId); await database.GetCollection(CollectionNames.User).UpdateOneAsync(filter, update); var updatedUser = await database.GetCollection(CollectionNames.User) .Find(filter) .FirstOrDefaultAsync(); return updatedUser; } catch (Exception ex) { logger.LogError(ex, $"AddProjectToUserService: Error in getting data - {ex.Message}"); throw new Exception(ex.Message, ex); } } /// /// Removes a project from the user's list of projects. /// /// The identifier of the user from whom the project will be removed. /// The identifier of the project to remove. /// A representing the asynchronous operation, with the updated user object. public async Task RemoveProjectFromUserService(string userId, string projectId) { try { var filter = Builders.Filter.Eq("_id", ObjectId.Parse(userId)); var update = Builders.Update.Pull(v => v.Projects, projectId); await database.GetCollection(CollectionNames.User).UpdateOneAsync(filter, update); var updatedUser = await database.GetCollection(CollectionNames.User) .Find(filter) .FirstOrDefaultAsync(); return updatedUser; } catch (Exception ex) { logger.LogError(ex, $"RemoveProjectFromUserService: Error in getting data - {ex.Message}"); throw new Exception(ex.Message, ex); } } /// /// Gets the token adapter for a user. /// /// The user's email. /// A representing the asynchronous execution of the service. public async Task GetTokenAdapter(string email) { try { var pipeline = new[] { new BsonDocument("$match", new BsonDocument { { "email", new BsonDocument { { "$regex", $"^{Regex.Escape(email)}$" }, { "$options", "i" } } }, { "status", StatusEnum.Active.ToString() } }), new BsonDocument("$lookup", new BsonDocument { { "from", "Roles" }, { "localField", "roleId" }, { "foreignField", "_id" }, { "as", "role" } }), new BsonDocument("$unwind", "$role"), new BsonDocument("$match", new BsonDocument("role.status", StatusEnum.Active.ToString())), new BsonDocument("$addFields", new BsonDocument { { "role.permissions", new BsonDocument("$map", new BsonDocument { { "input", "$role.permissions" }, { "as", "perm" }, { "in", new BsonDocument("$toObjectId", "$$perm") } }) }, { "role.modules", new BsonDocument("$map", new BsonDocument { { "input", "$role.modules" }, { "as", "mod" }, { "in", new BsonDocument("$toObjectId", "$$mod") } }) } }), new BsonDocument("$lookup", new BsonDocument { { "from", "Permissions" }, { "localField", "role.permissions" }, { "foreignField", "_id" }, { "as", "permissions" } }), new BsonDocument("$lookup", new BsonDocument { { "from", "Modules" }, { "localField", "role.modules" }, { "foreignField", "_id" }, { "as", "modules" } }), new BsonDocument("$project", new BsonDocument { { "_id", 1 }, { "guid", 1 }, { "email", 1 }, { "name", 1 }, { "middleName", 1 }, { "lastName", 1 }, { "displayName", 1 }, { "roleId", 1 }, { "companies", 1 }, { "projects", 1 }, { "lastLogIn", 1 }, { "lastLogOut", 1 }, { "createdBy", 1 }, { "updatedBy", 1 }, { "status", 1 }, { "createdAt", 1 }, { "updatedAt", 1 }, { "role._id", 1 }, { "role.name", 1 }, { "role.description", 1 }, { "role.applications", 1 }, { "role.permissions", 1 }, { "role.modules", 1 }, { "role.status", 1 }, { "role.createdAt", 1 }, { "role.updatedAt", 1 }, { "role.createdBy", 1 }, { "role.updatedBy", 1 }, { "permissions", 1 }, { "modules", 1 } }) }; var result = await database.GetCollection(CollectionNames.User) .Aggregate(pipeline) .FirstOrDefaultAsync(); if (result is null) return null; var tokenAdapter = new TokenAdapter { User = new UserAdapter { Id = result["_id"]?.ToString() ?? "", Guid = result["guid"].AsString, Email = result["email"].AsString, Name = result["name"].AsString, MiddleName = result["middleName"].AsString, LastName = result["lastName"].AsString, DisplayName = result["displayName"].AsString, RoleId = result["roleId"]?.ToString() ?? "", Companies = result["companies"].AsBsonArray .Select(c => c.AsString) .ToArray(), Projects = result["projects"].AsBsonArray .Select(c => c.AsString) .ToArray(), LastLogIn = result["lastLogIn"].ToUniversalTime(), LastLogOut = result["lastLogOut"].ToUniversalTime(), CreatedAt = result["createdAt"].ToUniversalTime(), CreatedBy = result["createdBy"].AsString, UpdatedAt = result["updatedAt"].ToUniversalTime(), UpdatedBy = result["updatedBy"].AsString, Status = (StatusEnum)Enum.Parse(typeof(StatusEnum), result["status"].AsString), }, Role = new RoleAdapter { Id = result["role"]["_id"]?.ToString() ?? "", Name = result["role"]["name"].AsString, Description = result["role"]["description"].AsString, Applications = result["role"]["applications"].AsBsonArray .Select(c => (ApplicationsEnum)c.AsInt32) .ToArray(), Modules = result["role"]["modules"].AsBsonArray .Select(c => c.ToString() ?? "") .ToArray(), Permissions = result["role"]["permissions"].AsBsonArray .Select(c => c.ToString() ?? "") .ToArray(), Status = (StatusEnum)Enum.Parse(typeof(StatusEnum), result["role"]["status"].AsString), CreatedAt = result["role"]["createdAt"].ToUniversalTime(), UpdatedAt = result["role"]["updatedAt"].ToUniversalTime(), CreatedBy = result["role"]["createdBy"].AsString, UpdatedBy = result["role"]["updatedBy"].AsString }, Permissions = result["permissions"].AsBsonArray .Select(permission => BsonSerializer.Deserialize(permission.AsBsonDocument)) .Where(permission => permission.Status == StatusEnum.Active) .ToList() }; return tokenAdapter; } catch (Exception ex) { logger.LogError(ex, $"GetTokenAdapter: Error in getting data - {ex.Message}"); throw new Exception(ex.Message, ex); } } } }