diff --git a/.gitignore b/.gitignore index c5c41a2..0f88a91 100644 --- a/.gitignore +++ b/.gitignore @@ -352,5 +352,4 @@ ASALocalRun/ /Core.Cerberos.DAL.API/CerberosDALSettings.development.json /Core.Cerberos.DAL.API/cerberosprivkey.pem -/Core.Cerberos.DAL.API/cerberospubkey.pem -/Core.Cerberos.Provider/Providers/Onboarding/UserService.cs +/Core.Cerberos.DAL.API/cerberospubkey.pem \ No newline at end of file diff --git a/Core.Cerberos.DAL.API/Core.Cerberos.DAL.API.csproj b/Core.Cerberos.DAL.API/Core.Cerberos.DAL.API.csproj index 16684f3..90c1460 100644 --- a/Core.Cerberos.DAL.API/Core.Cerberos.DAL.API.csproj +++ b/Core.Cerberos.DAL.API/Core.Cerberos.DAL.API.csproj @@ -15,6 +15,7 @@ + diff --git a/Core.Cerberos.DAL.API/Program.cs b/Core.Cerberos.DAL.API/Program.cs index 029857b..edfa280 100644 --- a/Core.Cerberos.DAL.API/Program.cs +++ b/Core.Cerberos.DAL.API/Program.cs @@ -1,3 +1,4 @@ +using Core.Blueprint.Logging.Configuration; using Core.Cerberos.Adapters.Extensions; using Core.Cerberos.Adapters.Helpers; using Core.Cerberos.Provider; @@ -21,6 +22,8 @@ builder.Services.AddControllers(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddLogs(builder); + builder.Services.AddCors(options => { options.AddPolicy("AllowAll", policyBuilder => @@ -79,6 +82,7 @@ var app = builder.Build(); app.UseSwaggerUI(builder.Configuration, authSettings); app.ConfigureSwagger(builder.Configuration); +app.UseLogging(builder.Configuration); app.UseHttpsRedirection(); app.UseAuthorization(); app.MapControllers(); diff --git a/Core.Cerberos.Provider/Providers/Onboarding/UserService.cs b/Core.Cerberos.Provider/Providers/Onboarding/UserService.cs new file mode 100644 index 0000000..4b3920c --- /dev/null +++ b/Core.Cerberos.Provider/Providers/Onboarding/UserService.cs @@ -0,0 +1,627 @@ +// *********************************************************************** +// +// 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); + } + } + } +}