Removing the project stored in cache for employee who have the project manage permission

This commit is contained in:
ashutosh.nehete 2025-07-10 15:57:08 +05:30
parent 40ce4ced42
commit bb76c45195
3 changed files with 68 additions and 16 deletions

View File

@ -137,6 +137,20 @@ namespace Marco.Pms.CacheHelper
return true; return true;
} }
public async Task<bool> ClearAllProjectIdsByPermissionIdFromCache(Guid permissionId)
{
var filter = Builders<EmployeePermissionMongoDB>.Filter.AnyEq(e => e.PermissionIds, permissionId.ToString());
var update = Builders<EmployeePermissionMongoDB>.Update
.Set(e => e.ProjectIds, new List<string>());
var result = await _collection.UpdateOneAsync(filter, update);
if (result.MatchedCount == 0)
return false;
return true;
}
public async Task<bool> RemoveRoleIdFromCache(Guid employeeId, Guid roleId) public async Task<bool> RemoveRoleIdFromCache(Guid employeeId, Guid roleId)
{ {
var filter = Builders<EmployeePermissionMongoDB>.Filter var filter = Builders<EmployeePermissionMongoDB>.Filter

View File

@ -36,6 +36,7 @@ namespace MarcoBMS.Services.Controllers
private readonly IHubContext<MarcoHub> _signalR; private readonly IHubContext<MarcoHub> _signalR;
private readonly PermissionServices _permission; private readonly PermissionServices _permission;
private readonly CacheUpdateHelper _cache; private readonly CacheUpdateHelper _cache;
private readonly IServiceScopeFactory _serviceScopeFactory;
private readonly Guid ViewProjects; private readonly Guid ViewProjects;
private readonly Guid ManageProject; private readonly Guid ManageProject;
private readonly Guid ViewInfra; private readonly Guid ViewInfra;
@ -44,7 +45,7 @@ namespace MarcoBMS.Services.Controllers
public ProjectController(ApplicationDbContext context, UserHelper userHelper, ILoggingService logger, RolesHelper rolesHelper, ProjectsHelper projectHelper, public ProjectController(ApplicationDbContext context, UserHelper userHelper, ILoggingService logger, RolesHelper rolesHelper, ProjectsHelper projectHelper,
IHubContext<MarcoHub> signalR, PermissionServices permission, CacheUpdateHelper cache) IHubContext<MarcoHub> signalR, PermissionServices permission, CacheUpdateHelper cache, IServiceScopeFactory serviceScopeFactory)
{ {
_context = context; _context = context;
_userHelper = userHelper; _userHelper = userHelper;
@ -59,6 +60,7 @@ namespace MarcoBMS.Services.Controllers
ViewInfra = Guid.Parse("8d7cc6e3-9147-41f7-aaa7-fa507e450bd4"); ViewInfra = Guid.Parse("8d7cc6e3-9147-41f7-aaa7-fa507e450bd4");
ManageInfra = Guid.Parse("f2aee20a-b754-4537-8166-f9507b44585b"); ManageInfra = Guid.Parse("f2aee20a-b754-4537-8166-f9507b44585b");
tenantId = _userHelper.GetTenantId(); tenantId = _userHelper.GetTenantId();
_serviceScopeFactory = serviceScopeFactory;
} }
[HttpGet("list/basic")] [HttpGet("list/basic")]
@ -436,31 +438,56 @@ namespace MarcoBMS.Services.Controllers
[HttpPost] [HttpPost]
public async Task<IActionResult> Create([FromBody] CreateProjectDto projectDto) public async Task<IActionResult> Create([FromBody] CreateProjectDto projectDto)
{ {
var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); // 1. Validate input first (early exit)
if (!ModelState.IsValid) if (!ModelState.IsValid)
{ {
var errors = ModelState.Values var errors = ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage).ToList();
.SelectMany(v => v.Errors)
.Select(e => e.ErrorMessage)
.ToList();
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid data", errors, 400)); return BadRequest(ApiResponse<object>.ErrorResponse("Invalid data", errors, 400));
} }
Guid TenantId = GetTenantId(); // 2. Prepare data without I/O
var project = projectDto.ToProjectFromCreateProjectDto(TenantId); 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<object>.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); // 5. Return a success response to the user as soon as the critical data is saved.
return Ok(ApiResponse<object>.SuccessResponse(project.ToProjectDto(), "Project created successfully.", 200));
return Ok(ApiResponse<object>.SuccessResponse(project.ToProjectDto(), "Success.", 200));
} }
[HttpPut] [HttpPut]

View File

@ -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); _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) public async Task ClearAllPermissionIdsByEmployeeID(Guid employeeId)
{ {
try try