From bb76c45195aee9d63c6412224055f0e1709a34c9 Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Thu, 10 Jul 2025 15:57:08 +0530 Subject: [PATCH] Removing the project stored in cache for employee who have the project manage permission --- Marco.Pms.CacheHelper/EmployeeCache.cs | 14 +++++ .../Controllers/ProjectController.cs | 59 ++++++++++++++----- .../Helpers/CacheUpdateHelper.cs | 11 ++++ 3 files changed, 68 insertions(+), 16 deletions(-) diff --git a/Marco.Pms.CacheHelper/EmployeeCache.cs b/Marco.Pms.CacheHelper/EmployeeCache.cs index 5c86e6f..c2a1f7b 100644 --- a/Marco.Pms.CacheHelper/EmployeeCache.cs +++ b/Marco.Pms.CacheHelper/EmployeeCache.cs @@ -137,6 +137,20 @@ namespace Marco.Pms.CacheHelper return true; } + public async Task ClearAllProjectIdsByPermissionIdFromCache(Guid permissionId) + { + var filter = Builders.Filter.AnyEq(e => e.PermissionIds, permissionId.ToString()); + + var update = Builders.Update + .Set(e => e.ProjectIds, new List()); + + var result = await _collection.UpdateOneAsync(filter, update); + + if (result.MatchedCount == 0) + return false; + + return true; + } public async Task RemoveRoleIdFromCache(Guid employeeId, Guid roleId) { var filter = Builders.Filter diff --git a/Marco.Pms.Services/Controllers/ProjectController.cs b/Marco.Pms.Services/Controllers/ProjectController.cs index 09858d5..07ddbfd 100644 --- a/Marco.Pms.Services/Controllers/ProjectController.cs +++ b/Marco.Pms.Services/Controllers/ProjectController.cs @@ -36,6 +36,7 @@ namespace MarcoBMS.Services.Controllers private readonly IHubContext _signalR; private readonly PermissionServices _permission; private readonly CacheUpdateHelper _cache; + private readonly IServiceScopeFactory _serviceScopeFactory; private readonly Guid ViewProjects; private readonly Guid ManageProject; private readonly Guid ViewInfra; @@ -44,7 +45,7 @@ namespace MarcoBMS.Services.Controllers public ProjectController(ApplicationDbContext context, UserHelper userHelper, ILoggingService logger, RolesHelper rolesHelper, ProjectsHelper projectHelper, - IHubContext signalR, PermissionServices permission, CacheUpdateHelper cache) + IHubContext signalR, PermissionServices permission, CacheUpdateHelper cache, IServiceScopeFactory serviceScopeFactory) { _context = context; _userHelper = userHelper; @@ -59,6 +60,7 @@ namespace MarcoBMS.Services.Controllers ViewInfra = Guid.Parse("8d7cc6e3-9147-41f7-aaa7-fa507e450bd4"); ManageInfra = Guid.Parse("f2aee20a-b754-4537-8166-f9507b44585b"); tenantId = _userHelper.GetTenantId(); + _serviceScopeFactory = serviceScopeFactory; } [HttpGet("list/basic")] @@ -436,31 +438,56 @@ namespace MarcoBMS.Services.Controllers [HttpPost] public async Task Create([FromBody] CreateProjectDto projectDto) { - var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); + // 1. Validate input first (early exit) if (!ModelState.IsValid) { - var errors = ModelState.Values - .SelectMany(v => v.Errors) - .Select(e => e.ErrorMessage) - .ToList(); + var errors = ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage).ToList(); return BadRequest(ApiResponse.ErrorResponse("Invalid data", errors, 400)); - } - Guid TenantId = GetTenantId(); - var project = projectDto.ToProjectFromCreateProjectDto(TenantId); + // 2. Prepare data without I/O + Guid tenantId = _userHelper.GetTenantId(); // Assuming this is fast and from claims + Employee loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); + var loggedInUserId = loggedInEmployee.Id; + var project = projectDto.ToProjectFromCreateProjectDto(tenantId); - _context.Projects.Add(project); + // 3. Store it to database + try + { + _context.Projects.Add(project); + await _context.SaveChangesAsync(); + } + catch (Exception ex) + { + // Log the detailed exception + _logger.LogError("Failed to create project in database. Rolling back transaction. : {Error}", ex.Message); + // Return a server error as the primary operation failed + return StatusCode(500, ApiResponse.ErrorResponse("An error occurred while saving the project.", ex.Message, 500)); + } - await _context.SaveChangesAsync(); + // 4. Perform non-critical side-effects (caching, notifications) concurrently + try + { + // These operations do not depend on each other, so they can run in parallel. + Task cacheAddDetailsTask = _cache.AddProjectDetails(project); + Task cacheClearListTask = _cache.ClearAllProjectIdsByPermissionId(ManageProject); - await _cache.AddProjectDetails(project); + var notification = new { LoggedInUserId = loggedInUserId, Keyword = "Create_Project", Response = project.ToProjectDto() }; + // Send notification only to the relevant group (e.g., users in the same tenant) + Task notificationTask = _signalR.Clients.Group(tenantId.ToString()).SendAsync("NotificationEventHandler", notification); - var notification = new { LoggedInUserId = LoggedInEmployee.Id, Keyword = "Create_Project", Response = project.ToProjectDto() }; + // Await all side-effect tasks to complete in parallel + await Task.WhenAll(cacheAddDetailsTask, cacheClearListTask, notificationTask); + } + catch (Exception ex) + { + // The project was created successfully, but a side-effect failed. + // Log this as a warning, as the primary operation succeeded. Don't return an error to the user. + _logger.LogWarning("Project {ProjectId} was created, but a post-creation side-effect (caching/notification) failed. : {Error}", project.Id, ex.Message); + } - await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification); - - return Ok(ApiResponse.SuccessResponse(project.ToProjectDto(), "Success.", 200)); + // 5. Return a success response to the user as soon as the critical data is saved. + return Ok(ApiResponse.SuccessResponse(project.ToProjectDto(), "Project created successfully.", 200)); } [HttpPut] diff --git a/Marco.Pms.Services/Helpers/CacheUpdateHelper.cs b/Marco.Pms.Services/Helpers/CacheUpdateHelper.cs index 216ec6e..ae6264e 100644 --- a/Marco.Pms.Services/Helpers/CacheUpdateHelper.cs +++ b/Marco.Pms.Services/Helpers/CacheUpdateHelper.cs @@ -298,6 +298,17 @@ namespace Marco.Pms.Services.Helpers _logger.LogWarning("Error occured while deleting projectIds from Cache for Application Role {RoleId}: {Error}", roleId, ex.Message); } } + public async Task ClearAllProjectIdsByPermissionId(Guid permissionId) + { + try + { + await _employeeCache.ClearAllProjectIdsByPermissionIdFromCache(permissionId); + } + catch (Exception ex) + { + _logger.LogWarning("Error occured while deleting projectIds from Cache for Permission {PermissionId}: {Error}", permissionId, ex.Message); + } + } public async Task ClearAllPermissionIdsByEmployeeID(Guid employeeId) { try