ProjectDetails_Split_API #103

Merged
admin merged 47 commits from ProjectDetails_Split_API into main 2025-07-11 11:32:34 +00:00
3 changed files with 68 additions and 16 deletions
Showing only changes of commit bb76c45195 - Show all commits

View File

@ -137,6 +137,20 @@ namespace Marco.Pms.CacheHelper
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)
{
var filter = Builders<EmployeePermissionMongoDB>.Filter

View File

@ -36,6 +36,7 @@ namespace MarcoBMS.Services.Controllers
private readonly IHubContext<MarcoHub> _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<MarcoHub> signalR, PermissionServices permission, CacheUpdateHelper cache)
IHubContext<MarcoHub> 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<IActionResult> 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<object>.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<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);
return Ok(ApiResponse<object>.SuccessResponse(project.ToProjectDto(), "Success.", 200));
// 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));
}
[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);
}
}
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