Compare commits

..

No commits in common. "64e6255458cde1b187601d10eea05d32f306cd71" and "b069e9f07dff662649b537afc88f1f6f929335b5" have entirely different histories.

8 changed files with 83 additions and 270 deletions

View File

@ -1420,14 +1420,15 @@ namespace Marco.Pms.Services.Controllers
} }
// Check if the logged in employee has permission to delete OR is the owner of the document attachment // Check if the logged in employee has permission to delete OR is the owner of the document attachment
var hasDeletePermission = false; var hasDeletePermission = await _permission.HasPermission(PermissionsMaster.DeleteDocument, loggedInEmployee.Id);
var hasViewPermission = false;
if (ProjectEntity == documentAttachment.DocumentType?.DocumentCategory?.EntityTypeId) if (ProjectEntity == documentAttachment.DocumentType?.DocumentCategory?.EntityTypeId)
{ {
hasDeletePermission = await _permission.HasPermission(PermissionsMaster.DeleteDocument, loggedInEmployee.Id, documentAttachment.EntityId); hasViewPermission = await _permission.HasPermission(PermissionsMaster.ViewDocument, loggedInEmployee.Id, documentAttachment.EntityId);
} }
else if (EmployeeEntity == documentAttachment.DocumentType?.DocumentCategory?.EntityTypeId) else if (EmployeeEntity == documentAttachment.DocumentType?.DocumentCategory?.EntityTypeId)
{ {
hasDeletePermission = await _permission.HasPermission(PermissionsMaster.DeleteDocument, loggedInEmployee.Id); hasViewPermission = await _permission.HasPermission(PermissionsMaster.ViewDocument, loggedInEmployee.Id);
} }
if (!hasDeletePermission && loggedInEmployee.Id != documentAttachment.EntityId) if (!hasDeletePermission && loggedInEmployee.Id != documentAttachment.EntityId)
{ {

View File

@ -148,14 +148,13 @@ namespace MarcoBMS.Services.Controllers
.Distinct() .Distinct()
.ToListAsync(); .ToListAsync();
var employees = await _context.Employees result = await _context.Employees
.Include(fp => fp.JobRole) .Include(fp => fp.JobRole)
.Where(e => employeeIds.Contains(e.Id) && e.JobRole != null && e.IsActive && e.TenantId == tenantId) .Where(e => employeeIds.Contains(e.Id) && e.IsActive && e.TenantId == tenantId)
.Select(e => e.ToEmployeeVMFromEmployee())
.Distinct() .Distinct()
.ToListAsync(); .ToListAsync();
result = employees.Select(e => e.ToEmployeeVMFromEmployee()).ToList();
_logger.LogInfo("Employee list fetched using limited access (active only)."); _logger.LogInfo("Employee list fetched using limited access (active only).");
} }
else else

View File

@ -1,12 +1,9 @@
using AutoMapper; using Marco.Pms.DataAccess.Data;
using Marco.Pms.DataAccess.Data;
using Marco.Pms.Helpers.Utility;
using Marco.Pms.Model.Dtos.Util; using Marco.Pms.Model.Dtos.Util;
using Marco.Pms.Model.Mapper; using Marco.Pms.Model.Mapper;
using Marco.Pms.Model.Master; using Marco.Pms.Model.Master;
using Marco.Pms.Model.TenantModels;
using Marco.Pms.Model.Utilities; using Marco.Pms.Model.Utilities;
using Marco.Pms.Model.ViewModels.Tenant; using MarcoBMS.Services.Helpers;
using MarcoBMS.Services.Service; using MarcoBMS.Services.Service;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@ -17,30 +14,24 @@ namespace Marco.Pms.Services.Controllers
[ApiController] [ApiController]
public class MarketController : ControllerBase public class MarketController : ControllerBase
{ {
private readonly IDbContextFactory<ApplicationDbContext> _dbContextFactory; private readonly ApplicationDbContext _context;
private readonly FeatureDetailsHelper _featureDetailsHelper; private readonly UserHelper _userHelper;
private readonly IServiceScopeFactory _serviceScopeFactory;
private readonly IEmailSender _emailSender; private readonly IEmailSender _emailSender;
private readonly IConfiguration _configuration; private readonly IConfiguration _configuration;
public MarketController(IDbContextFactory<ApplicationDbContext> dbContextFactory, public MarketController(ApplicationDbContext context, UserHelper userHelper, RefreshTokenService refreshTokenService,
IServiceScopeFactory serviceScopeFactory, IEmailSender emailSender, IConfiguration configuration, EmployeeHelper employeeHelper)
IEmailSender emailSender,
IConfiguration configuration,
FeatureDetailsHelper featureDetailsHelper)
{ {
_dbContextFactory = dbContextFactory ?? throw new ArgumentNullException(nameof(dbContextFactory)); _context = context;
_serviceScopeFactory = serviceScopeFactory ?? throw new ArgumentNullException(nameof(serviceScopeFactory)); _userHelper = userHelper;
_emailSender = emailSender ?? throw new ArgumentNullException(nameof(emailSender)); _emailSender = emailSender;
_configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); _configuration = configuration;
_featureDetailsHelper = featureDetailsHelper ?? throw new ArgumentNullException(nameof(featureDetailsHelper));
} }
[HttpGet] [HttpGet]
[Route("industries")] [Route("industries")]
public async Task<IActionResult> GetIndustries() public async Task<IActionResult> GetIndustries()
{ {
await using var _context = await _dbContextFactory.CreateDbContextAsync(); var tenantId = _userHelper.GetTenantId();
var industries = await _context.Industries.ToListAsync(); var industries = await _context.Industries.ToListAsync();
return Ok(ApiResponse<object>.SuccessResponse(industries, "Success.", 200)); return Ok(ApiResponse<object>.SuccessResponse(industries, "Success.", 200));
@ -49,8 +40,6 @@ namespace Marco.Pms.Services.Controllers
[HttpPost("enquire")] [HttpPost("enquire")]
public async Task<IActionResult> RequestDemo([FromBody] InquiryDto inquiryDto) public async Task<IActionResult> RequestDemo([FromBody] InquiryDto inquiryDto)
{ {
await using var _context = await _dbContextFactory.CreateDbContextAsync();
Inquiries inquiry = inquiryDto.ToInquiriesFromInquiriesDto(); Inquiries inquiry = inquiryDto.ToInquiriesFromInquiriesDto();
_context.Inquiries.Add(inquiry); _context.Inquiries.Add(inquiry);
await _context.SaveChangesAsync(); await _context.SaveChangesAsync();
@ -69,66 +58,5 @@ namespace Marco.Pms.Services.Controllers
} }
return NotFound(ApiResponse<object>.ErrorResponse("Industry not found.", "Industry not found.", 404)); return NotFound(ApiResponse<object>.ErrorResponse("Industry not found.", "Industry not found.", 404));
} }
[HttpGet("list/subscription-plan")]
public async Task<IActionResult> GetSubscriptionPlanList([FromQuery] PLAN_FREQUENCY? frequency)
{
using var scope = _serviceScopeFactory.CreateScope();
var _logger = scope.ServiceProvider.GetRequiredService<ILoggingService>();
var _mapper = scope.ServiceProvider.GetRequiredService<IMapper>();
_logger.LogInfo("GetSubscriptionPlanList called with frequency: {Frequency}", frequency ?? PLAN_FREQUENCY.MONTHLY);
// Initialize the list to store subscription plan view models
List<SubscriptionPlanVM> detailsVM = new List<SubscriptionPlanVM>();
try
{
// Create DbContext
await using var _context = await _dbContextFactory.CreateDbContextAsync();
// Load subscription plans with optional frequency filtering
IQueryable<SubscriptionPlanDetails> query = _context.SubscriptionPlanDetails.Include(sp => sp.Plan).Include(sp => sp.Currency);
if (frequency.HasValue)
{
query = query.Where(sp => sp.Frequency == frequency.Value);
_logger.LogInfo("Filtering subscription plans by frequency: {Frequency}", frequency);
}
else
{
_logger.LogInfo("Fetching all subscription plans without frequency filter");
}
var subscriptionPlans = await query.ToListAsync();
// Map and fetch feature details for each subscription plan
foreach (var subscriptionPlan in subscriptionPlans)
{
var response = _mapper.Map<SubscriptionPlanVM>(subscriptionPlan);
try
{
response.Features = await _featureDetailsHelper.GetFeatureDetails(subscriptionPlan.FeaturesId);
}
catch (Exception exFeature)
{
_logger.LogError(exFeature, "Failed to fetch features for FeaturesId: {FeaturesId}", subscriptionPlan.FeaturesId);
response.Features = null; // or set to a default/fallback value
}
detailsVM.Add(response);
}
_logger.LogInfo("Successfully fetched {Count} subscription plans", detailsVM.Count);
return Ok(ApiResponse<object>.SuccessResponse(detailsVM, "List of plans fetched successfully", 200));
}
catch (Exception ex)
{
_logger.LogError(ex, "Error occurred while fetching subscription plans");
return StatusCode(500, ApiResponse<object>.ErrorResponse("An error occurred while fetching subscription plans."));
}
}
} }
} }

View File

@ -470,13 +470,6 @@ namespace MarcoBMS.Services.Controllers
var response = await _projectServices.GetAssignedProjectLevelPermissionAsync(employeeId, projectId, tenantId, loggedInEmployee); var response = await _projectServices.GetAssignedProjectLevelPermissionAsync(employeeId, projectId, tenantId, loggedInEmployee);
return StatusCode(response.StatusCode, response); return StatusCode(response.StatusCode, response);
} }
[HttpGet("get/all/project-level-permission/{projectId}")]
public async Task<IActionResult> GetAllPermissionFroProject(Guid projectId)
{
Employee loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
var response = await _projectServices.GetAllPermissionFroProjectAsync(projectId, loggedInEmployee, tenantId);
return StatusCode(response.StatusCode, response);
}
[HttpGet("get/proejct-level/modules")] [HttpGet("get/proejct-level/modules")]
public async Task<IActionResult> AssignProjectLevelModules() public async Task<IActionResult> AssignProjectLevelModules()
{ {

View File

@ -85,12 +85,12 @@ namespace MarcoBMS.Services.Helpers
.Distinct() .Distinct()
.ToListAsync(); .ToListAsync();
var employees = await _context.Employees result = await _context.Employees
.Include(fp => fp.JobRole) .Include(fp => fp.JobRole)
.Where(e => employeeIds.Contains(e.Id) && e.JobRole != null && e.IsActive && e.TenantId == tenantId) .Where(e => employeeIds.Contains(e.Id) && e.IsActive && e.TenantId == tenantId)
.Select(e => e.ToEmployeeVMFromEmployee())
.Distinct() .Distinct()
.ToListAsync(); .ToListAsync();
result = employees.Select(e => e.ToEmployeeVMFromEmployee()).ToList();
} }
else if (ShowInActive) else if (ShowInActive)

View File

@ -88,6 +88,7 @@ namespace Marco.Pms.Services.Service
{ {
Guid.Parse("53176ebf-c75d-42e5-839f-4508ffac3def"), Guid.Parse("53176ebf-c75d-42e5-839f-4508ffac3def"),
Guid.Parse("9d4b5489-2079-40b9-bd77-6e1bf90bc19f"), Guid.Parse("9d4b5489-2079-40b9-bd77-6e1bf90bc19f"),
Guid.Parse("81ab8a87-8ccd-4015-a917-0627cee6a100"),
Guid.Parse("52c9cf54-1eb2-44d2-81bb-524cf29c0a94"), Guid.Parse("52c9cf54-1eb2-44d2-81bb-524cf29c0a94"),
Guid.Parse("a8cf4331-8f04-4961-8360-a3f7c3cc7462") Guid.Parse("a8cf4331-8f04-4961-8360-a3f7c3cc7462")
}; };

View File

@ -16,7 +16,6 @@ using Marco.Pms.Model.ViewModels.Master;
using Marco.Pms.Model.ViewModels.Projects; using Marco.Pms.Model.ViewModels.Projects;
using Marco.Pms.Services.Helpers; using Marco.Pms.Services.Helpers;
using Marco.Pms.Services.Service.ServiceInterfaces; using Marco.Pms.Services.Service.ServiceInterfaces;
using MarcoBMS.Services.Helpers;
using MarcoBMS.Services.Service; using MarcoBMS.Services.Service;
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@ -30,6 +29,7 @@ namespace Marco.Pms.Services.Service
private readonly IServiceScopeFactory _serviceScopeFactory; private readonly IServiceScopeFactory _serviceScopeFactory;
private readonly ApplicationDbContext _context; // Keeping this for direct scoped context use where appropriate private readonly ApplicationDbContext _context; // Keeping this for direct scoped context use where appropriate
private readonly ILoggingService _logger; private readonly ILoggingService _logger;
private readonly PermissionServices _permission;
private readonly CacheUpdateHelper _cache; private readonly CacheUpdateHelper _cache;
private readonly IMapper _mapper; private readonly IMapper _mapper;
public ProjectServices( public ProjectServices(
@ -37,15 +37,17 @@ namespace Marco.Pms.Services.Service
IServiceScopeFactory serviceScopeFactory, IServiceScopeFactory serviceScopeFactory,
ApplicationDbContext context, ApplicationDbContext context,
ILoggingService logger, ILoggingService logger,
PermissionServices permission,
CacheUpdateHelper cache, CacheUpdateHelper cache,
IMapper mapper) IMapper mapper)
{ {
_dbContextFactory = dbContextFactory ?? throw new ArgumentNullException(nameof(dbContextFactory)); _dbContextFactory = dbContextFactory ?? throw new ArgumentNullException(nameof(dbContextFactory));
_serviceScopeFactory = serviceScopeFactory ?? throw new ArgumentNullException(nameof(serviceScopeFactory));
_context = context ?? throw new ArgumentNullException(nameof(context)); _context = context ?? throw new ArgumentNullException(nameof(context));
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); _logger = logger ?? throw new ArgumentNullException(nameof(logger));
_permission = permission ?? throw new ArgumentNullException(nameof(permission));
_cache = cache ?? throw new ArgumentNullException(nameof(cache)); _cache = cache ?? throw new ArgumentNullException(nameof(cache));
_mapper = mapper ?? throw new ArgumentNullException(nameof(mapper)); _mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
_serviceScopeFactory = serviceScopeFactory ?? throw new ArgumentNullException(nameof(serviceScopeFactory));
} }
#region =================================================================== Project Get APIs =================================================================== #region =================================================================== Project Get APIs ===================================================================
@ -85,6 +87,7 @@ namespace Marco.Pms.Services.Service
return ApiResponse<object>.ErrorResponse("An internal server error occurred. Please try again later.", null, 500); return ApiResponse<object>.ErrorResponse("An internal server error occurred. Please try again later.", null, 500);
} }
} }
public async Task<ApiResponse<object>> GetAllProjectsAsync(Guid tenantId, Employee loggedInEmployee) public async Task<ApiResponse<object>> GetAllProjectsAsync(Guid tenantId, Employee loggedInEmployee)
{ {
try try
@ -143,12 +146,11 @@ namespace Marco.Pms.Services.Service
return ApiResponse<object>.ErrorResponse("An internal server error occurred. Please try again later.", null, 500); return ApiResponse<object>.ErrorResponse("An internal server error occurred. Please try again later.", null, 500);
} }
} }
public async Task<ApiResponse<object>> GetProjectAsync(Guid id, Guid tenantId, Employee loggedInEmployee) public async Task<ApiResponse<object>> GetProjectAsync(Guid id, Guid tenantId, Employee loggedInEmployee)
{ {
try try
{ {
using var scope = _serviceScopeFactory.CreateScope();
var _permission = scope.ServiceProvider.GetRequiredService<PermissionServices>();
// --- Step 1: Run independent operations in PARALLEL --- // --- Step 1: Run independent operations in PARALLEL ---
// We can check permissions and fetch data at the same time to reduce latency. // We can check permissions and fetch data at the same time to reduce latency.
var permissionTask = _permission.HasProjectPermission(loggedInEmployee, id); var permissionTask = _permission.HasProjectPermission(loggedInEmployee, id);
@ -188,15 +190,13 @@ namespace Marco.Pms.Services.Service
return ApiResponse<object>.ErrorResponse("An internal server error occurred.", null, 500); return ApiResponse<object>.ErrorResponse("An internal server error occurred.", null, 500);
} }
} }
public async Task<ApiResponse<object>> GetProjectDetailsAsync(Guid id, Guid tenantId, Employee loggedInEmployee) public async Task<ApiResponse<object>> GetProjectDetailsAsync(Guid id, Guid tenantId, Employee loggedInEmployee)
{ {
try try
{ {
_logger.LogInfo("Details requested by EmployeeId: {EmployeeId} for ProjectId: {ProjectId}", loggedInEmployee.Id, id); _logger.LogInfo("Details requested by EmployeeId: {EmployeeId} for ProjectId: {ProjectId}", loggedInEmployee.Id, id);
using var scope = _serviceScopeFactory.CreateScope();
var _permission = scope.ServiceProvider.GetRequiredService<PermissionServices>();
// Step 1: Check global view project permission // Step 1: Check global view project permission
var hasViewProjectPermission = await _permission.HasPermission(PermissionsMaster.ViewProject, loggedInEmployee.Id, id); var hasViewProjectPermission = await _permission.HasPermission(PermissionsMaster.ViewProject, loggedInEmployee.Id, id);
if (!hasViewProjectPermission) if (!hasViewProjectPermission)
@ -252,6 +252,7 @@ namespace Marco.Pms.Services.Service
return ApiResponse<object>.ErrorResponse("An internal server error occurred. Please try again later.", null, 500); return ApiResponse<object>.ErrorResponse("An internal server error occurred. Please try again later.", null, 500);
} }
} }
public async Task<ApiResponse<object>> GetProjectDetailsOldAsync(Guid id, Guid tenantId, Employee loggedInEmployee) public async Task<ApiResponse<object>> GetProjectDetailsOldAsync(Guid id, Guid tenantId, Employee loggedInEmployee)
{ {
var project = await _context.Projects var project = await _context.Projects
@ -418,7 +419,6 @@ namespace Marco.Pms.Services.Service
try try
{ {
var _permission = scope.ServiceProvider.GetRequiredService<PermissionServices>();
// --- Step 1: Fetch the Existing Entity from the Database --- // --- Step 1: Fetch the Existing Entity from the Database ---
// This is crucial to avoid the data loss bug. We only want to modify an existing record. // This is crucial to avoid the data loss bug. We only want to modify an existing record.
var existingProject = await _context.Projects var existingProject = await _context.Projects
@ -519,9 +519,6 @@ namespace Marco.Pms.Services.Service
try try
{ {
using var scope = _serviceScopeFactory.CreateScope();
var _permission = scope.ServiceProvider.GetRequiredService<PermissionServices>();
// --- CRITICAL: Security Check --- // --- CRITICAL: Security Check ---
// Before fetching data, you MUST verify the user has permission to see it. // Before fetching data, you MUST verify the user has permission to see it.
// This is a placeholder for your actual permission logic. // This is a placeholder for your actual permission logic.
@ -592,9 +589,6 @@ namespace Marco.Pms.Services.Service
try try
{ {
using var scope = _serviceScopeFactory.CreateScope();
var _permission = scope.ServiceProvider.GetRequiredService<PermissionServices>();
// --- Step 2: Security and Existence Checks --- // --- Step 2: Security and Existence Checks ---
// Before fetching data, you MUST verify the user has permission to see it. // Before fetching data, you MUST verify the user has permission to see it.
// This is a placeholder for your actual permission logic. // This is a placeholder for your actual permission logic.
@ -666,14 +660,11 @@ namespace Marco.Pms.Services.Service
_logger.LogInfo("Starting to manage {AllocationCount} allocations for user {UserId}.", allocationsDto.Count, loggedInEmployee.Id); _logger.LogInfo("Starting to manage {AllocationCount} allocations for user {UserId}.", allocationsDto.Count, loggedInEmployee.Id);
var _permission = scope.ServiceProvider.GetRequiredService<PermissionServices>();
// --- (Placeholder) Security Check --- // --- (Placeholder) Security Check ---
// In a real application, you would check if the loggedInEmployee has permission // In a real application, you would check if the loggedInEmployee has permission
// to manage allocations for ALL projects involved in this batch. // to manage allocations for ALL projects involved in this batch.
var projectIdsInBatch = allocationsDto.Select(a => a.ProjectId).Distinct().ToList(); var projectIdsInBatch = allocationsDto.Select(a => a.ProjectId).Distinct().ToList();
var projectId = projectIdsInBatch.FirstOrDefault(); var hasPermission = await _permission.HasPermission(PermissionsMaster.ManageTeam, loggedInEmployee.Id);
var hasPermission = await _permission.HasPermission(PermissionsMaster.ManageTeam, loggedInEmployee.Id, projectId);
if (!hasPermission) if (!hasPermission)
{ {
_logger.LogWarning("Access DENIED for user {UserId} trying to manage allocations for projects.", loggedInEmployee.Id); _logger.LogWarning("Access DENIED for user {UserId} trying to manage allocations for projects.", loggedInEmployee.Id);
@ -788,9 +779,6 @@ namespace Marco.Pms.Services.Service
try try
{ {
using var scope = _serviceScopeFactory.CreateScope();
var _permission = scope.ServiceProvider.GetRequiredService<PermissionServices>();
// --- Step 2: Clarified Security Check --- // --- Step 2: Clarified Security Check ---
// The permission should be about viewing another employee's assignments, not a generic "Manage Team". // The permission should be about viewing another employee's assignments, not a generic "Manage Team".
// This is a placeholder for your actual, more specific permission logic. // This is a placeholder for your actual, more specific permission logic.
@ -867,19 +855,14 @@ namespace Marco.Pms.Services.Service
_logger.LogInfo("Starting to manage {AllocationCount} project assignments for Employee {EmployeeId}.", allocationsDto.Count, employeeId); _logger.LogInfo("Starting to manage {AllocationCount} project assignments for Employee {EmployeeId}.", allocationsDto.Count, employeeId);
var _permission = scope.ServiceProvider.GetRequiredService<PermissionServices>();
// --- (Placeholder) Security Check --- // --- (Placeholder) Security Check ---
// You MUST verify that the loggedInEmployee has permission to modify the assignments for the target employeeId. // You MUST verify that the loggedInEmployee has permission to modify the assignments for the target employeeId.
foreach (var allocation in allocationsDto) var hasPermission = await _permission.HasPermission(PermissionsMaster.ManageTeam, loggedInEmployee.Id);
{ if (!hasPermission)
if (!await _permission.HasPermission(PermissionsMaster.ManageTeam, loggedInEmployee.Id, allocation.ProjectId))
{ {
_logger.LogWarning("Access DENIED for user {UserId} trying to manage assignments for employee {TargetEmployeeId}.", loggedInEmployee.Id, employeeId); _logger.LogWarning("Access DENIED for user {UserId} trying to manage assignments for employee {TargetEmployeeId}.", loggedInEmployee.Id, employeeId);
return ApiResponse<List<ProjectAllocationVM>>.ErrorResponse("Access Denied.", "You do not have permission to manage this employee's assignments.", 403); return ApiResponse<List<ProjectAllocationVM>>.ErrorResponse("Access Denied.", "You do not have permission to manage this employee's assignments.", 403);
} }
}
// --- Step 2: Fetch all relevant existing data in ONE database call --- // --- Step 2: Fetch all relevant existing data in ONE database call ---
var projectIdsInDto = allocationsDto.Select(p => p.ProjectId).ToList(); var projectIdsInDto = allocationsDto.Select(p => p.ProjectId).ToList();
@ -1055,34 +1038,18 @@ namespace Marco.Pms.Services.Service
try try
{ {
var _permission = scope.ServiceProvider.GetRequiredService<PermissionServices>();
// --- Step 1: Run independent permission checks in PARALLEL --- // --- Step 1: Run independent permission checks in PARALLEL ---
var projectPermissionTask = _permission.HasProjectPermission(loggedInEmployee, projectId); var projectPermissionTask = _permission.HasProjectPermission(loggedInEmployee, projectId);
var viewInfraPermissionTask = Task.Run(async () => var viewInfraPermissionTask = _permission.HasPermission(PermissionsMaster.ViewProjectInfra, loggedInEmployee.Id, projectId);
{
using var newScope = _serviceScopeFactory.CreateScope();
var permission = newScope.ServiceProvider.GetRequiredService<PermissionServices>();
return await permission.HasPermission(PermissionsMaster.ViewProjectInfra, loggedInEmployee.Id, projectId);
});
var manageInfraPermissionTask = Task.Run(async () =>
{
using var newScope = _serviceScopeFactory.CreateScope();
var permission = newScope.ServiceProvider.GetRequiredService<PermissionServices>();
return await permission.HasPermission(PermissionsMaster.ManageProjectInfra, loggedInEmployee.Id, projectId);
});
await Task.WhenAll(projectPermissionTask, viewInfraPermissionTask, manageInfraPermissionTask); await Task.WhenAll(projectPermissionTask, viewInfraPermissionTask);
var hasProjectPermission = projectPermissionTask.Result; if (!await projectPermissionTask)
var hasViewInfraPermission = viewInfraPermissionTask.Result;
var hasManageInfraPermission = manageInfraPermissionTask.Result;
if (!hasProjectPermission)
{ {
_logger.LogWarning("Project access denied for EmployeeId: {EmployeeId} on ProjectId: {ProjectId}", loggedInEmployee.Id, projectId); _logger.LogWarning("Project access denied for EmployeeId: {EmployeeId} on ProjectId: {ProjectId}", loggedInEmployee.Id, projectId);
return ApiResponse<object>.ErrorResponse("Access denied", "You don't have access to this project", 403); return ApiResponse<object>.ErrorResponse("Access denied", "You don't have access to this project", 403);
} }
if (!hasViewInfraPermission && !hasManageInfraPermission) if (!await viewInfraPermissionTask)
{ {
_logger.LogWarning("ViewInfra permission denied for EmployeeId: {EmployeeId}", loggedInEmployee.Id); _logger.LogWarning("ViewInfra permission denied for EmployeeId: {EmployeeId}", loggedInEmployee.Id);
return ApiResponse<object>.ErrorResponse("Access denied", "You don't have access to view this project's infrastructure", 403); return ApiResponse<object>.ErrorResponse("Access denied", "You don't have access to view this project's infrastructure", 403);
@ -1130,7 +1097,6 @@ namespace Marco.Pms.Services.Service
try try
{ {
var _permission = scope.ServiceProvider.GetRequiredService<PermissionServices>();
// --- Step 1: Cache-First Strategy --- // --- Step 1: Cache-First Strategy ---
var cachedWorkItems = await _cache.GetWorkItemDetailsByWorkArea(workAreaId); var cachedWorkItems = await _cache.GetWorkItemDetailsByWorkArea(workAreaId);
if (cachedWorkItems != null) if (cachedWorkItems != null)
@ -1369,8 +1335,6 @@ namespace Marco.Pms.Services.Service
using var scope = _serviceScopeFactory.CreateScope(); using var scope = _serviceScopeFactory.CreateScope();
var _firebase = scope.ServiceProvider.GetRequiredService<IFirebaseService>(); var _firebase = scope.ServiceProvider.GetRequiredService<IFirebaseService>();
var _permission = scope.ServiceProvider.GetRequiredService<PermissionServices>();
// --- Step 1: Input Validation --- // --- Step 1: Input Validation ---
if (workItemDtos == null || !workItemDtos.Any()) if (workItemDtos == null || !workItemDtos.Any())
{ {
@ -1508,6 +1472,7 @@ namespace Marco.Pms.Services.Service
return ApiResponse<List<WorkItemVM>>.SuccessResponse(responseList, message, 200); return ApiResponse<List<WorkItemVM>>.SuccessResponse(responseList, message, 200);
} }
public async Task<ServiceResponse> DeleteProjectTaskAsync(Guid id, Guid tenantId, Employee loggedInEmployee) public async Task<ServiceResponse> DeleteProjectTaskAsync(Guid id, Guid tenantId, Employee loggedInEmployee)
{ {
using var scope = _serviceScopeFactory.CreateScope(); using var scope = _serviceScopeFactory.CreateScope();
@ -1621,9 +1586,6 @@ namespace Marco.Pms.Services.Service
_logger.LogInfo("ManageProjectLevelPermissionAsync started for EmployeeId: {EmployeeId}, ProjectId: {ProjectId}, TenantId: {TenantId}", _logger.LogInfo("ManageProjectLevelPermissionAsync started for EmployeeId: {EmployeeId}, ProjectId: {ProjectId}, TenantId: {TenantId}",
model.EmployeeId, model.ProjectId, tenantId); model.EmployeeId, model.ProjectId, tenantId);
using var scope = _serviceScopeFactory.CreateScope();
var _permission = scope.ServiceProvider.GetRequiredService<PermissionServices>();
var hasTeamPermission = await _permission.HasPermission(PermissionsMaster.ManageTeam, loggedInEmployee.Id, model.ProjectId); var hasTeamPermission = await _permission.HasPermission(PermissionsMaster.ManageTeam, loggedInEmployee.Id, model.ProjectId);
if (!hasTeamPermission) if (!hasTeamPermission)
{ {
@ -1762,30 +1724,31 @@ namespace Marco.Pms.Services.Service
employeeId, projectId, tenantId, loggedInEmployee.Id); employeeId, projectId, tenantId, loggedInEmployee.Id);
// Query the database for relevant project-level permission mappings // Query the database for relevant project-level permission mappings
var employeeTask = Task.Run(async () => var permissionMappings = await _context.ProjectLevelPermissionMappings
{ .Include(p => p.Employee)
await using var context = await _dbContextFactory.CreateDbContextAsync(); .ThenInclude(e => e!.JobRole)
return await context.Employees.FirstOrDefaultAsync(e => e.Id == employeeId); .Include(p => p.Project)
}); .Include(p => p.Permission)
var projectTask = Task.Run(async () => .ThenInclude(fp => fp!.Feature)
{ .AsNoTracking()
await using var context = await _dbContextFactory.CreateDbContextAsync(); .Where(p => p.EmployeeId == employeeId
return await context.Projects.FirstOrDefaultAsync(p => p.Id == projectId && p.TenantId == tenantId); && p.ProjectId == projectId
}); && p.TenantId == tenantId)
var permissionIdsTask = Task.Run(async () => .ToListAsync();
{
return await GetPermissionIdsByProject(projectId, employeeId, tenantId);
});
await Task.WhenAll(employeeTask, projectTask, permissionIdsTask); if (permissionMappings == null || !permissionMappings.Any())
{
_logger.LogWarning("No project-level permissions found for EmployeeId: {EmployeeId}, ProjectId: {ProjectId}, TenantId: {TenantId}",
employeeId, projectId, tenantId);
return ApiResponse<object>.ErrorResponse("Project-Level Permissions not found", "Project-Level Permissions not found in database", 404);
}
var employee = employeeTask.Result; // Map the employee, project, and permissions.
var project = projectTask.Result; var employee = _mapper.Map<BasicEmployeeVM>(permissionMappings.First().Employee);
var permissionIds = permissionIdsTask.Result; var project = _mapper.Map<BasicProjectVM>(permissionMappings.First().Project);
var permissions = permissionMappings
var permissions = await _context.FeaturePermissions .Select(p => _mapper.Map<FeaturePermissionVM>(p.Permission))
.Include(fp => fp.Feature) .ToList();
.Where(fp => permissionIds.Contains(fp.Id)).ToListAsync();
if (employee == null) if (employee == null)
{ {
@ -1798,26 +1761,12 @@ namespace Marco.Pms.Services.Service
return ApiResponse<object>.ErrorResponse("Project not found", "Project not found in database", 404); return ApiResponse<object>.ErrorResponse("Project not found", "Project not found in database", 404);
} }
if (permissions == null || !permissions.Any())
{
_logger.LogWarning("No project-level permissions found for EmployeeId: {EmployeeId}, ProjectId: {ProjectId}, TenantId: {TenantId}",
employeeId, projectId, tenantId);
return ApiResponse<object>.ErrorResponse("Project-Level Permissions not found", "Project-Level Permissions not found in database", 404);
}
// Map the employee, project, and permissions.
var employeeVm = _mapper.Map<BasicEmployeeVM>(employee);
var projectVm = _mapper.Map<BasicProjectVM>(project);
var permissionVms = permissions
.Select(p => _mapper.Map<FeaturePermissionVM>(p))
.ToList();
// Prepare the result object. // Prepare the result object.
var result = new var result = new
{ {
Employee = employeeVm, Employee = employee,
Project = projectVm, Project = project,
Permissions = permissionVms Permissions = permissions
}; };
_logger.LogInfo("Project-level permissions fetched successfully for EmployeeId: {EmployeeId}, ProjectId: {ProjectId}, TenantId: {TenantId}", _logger.LogInfo("Project-level permissions fetched successfully for EmployeeId: {EmployeeId}, ProjectId: {ProjectId}, TenantId: {TenantId}",
@ -1842,6 +1791,7 @@ namespace Marco.Pms.Services.Service
{ {
Guid.Parse("53176ebf-c75d-42e5-839f-4508ffac3def"), Guid.Parse("53176ebf-c75d-42e5-839f-4508ffac3def"),
Guid.Parse("9d4b5489-2079-40b9-bd77-6e1bf90bc19f"), Guid.Parse("9d4b5489-2079-40b9-bd77-6e1bf90bc19f"),
Guid.Parse("81ab8a87-8ccd-4015-a917-0627cee6a100"),
Guid.Parse("52c9cf54-1eb2-44d2-81bb-524cf29c0a94"), Guid.Parse("52c9cf54-1eb2-44d2-81bb-524cf29c0a94"),
Guid.Parse("a8cf4331-8f04-4961-8360-a3f7c3cc7462") Guid.Parse("a8cf4331-8f04-4961-8360-a3f7c3cc7462")
}; };
@ -1921,11 +1871,6 @@ namespace Marco.Pms.Services.Service
return ApiResponse<object>.ErrorResponse("An error occurred while retrieving employees with project-level permissions.", 500); return ApiResponse<object>.ErrorResponse("An error occurred while retrieving employees with project-level permissions.", 500);
} }
} }
public async Task<ApiResponse<object>> GetAllPermissionFroProjectAsync(Guid projectId, Employee loggedInEmployee, Guid tenantId)
{
var featurePermissionIds = await GetPermissionIdsByProject(projectId, loggedInEmployee.Id, tenantId);
return ApiResponse<object>.SuccessResponse(featurePermissionIds, "Successfully featched the permission ids", 200);
}
#endregion #endregion
#region =================================================================== Helper Functions =================================================================== #region =================================================================== Helper Functions ===================================================================
@ -1935,11 +1880,13 @@ namespace Marco.Pms.Services.Service
List<Project> alloc = await _context.Projects.Where(c => c.TenantId == tanentId).ToListAsync(); List<Project> alloc = await _context.Projects.Where(c => c.TenantId == tanentId).ToListAsync();
return alloc; return alloc;
} }
public async Task<List<ProjectAllocation>> GetProjectByEmployeeID(Guid employeeId) public async Task<List<ProjectAllocation>> GetProjectByEmployeeID(Guid employeeId)
{ {
List<ProjectAllocation> alloc = await _context.ProjectAllocations.Where(c => c.EmployeeId == employeeId && c.IsActive == true).Include(c => c.Project).ToListAsync(); List<ProjectAllocation> alloc = await _context.ProjectAllocations.Where(c => c.EmployeeId == employeeId && c.IsActive == true).Include(c => c.Project).ToListAsync();
return alloc; return alloc;
} }
public async Task<List<ProjectAllocation>> GetTeamByProject(Guid TenantId, Guid ProjectId, bool IncludeInactive) public async Task<List<ProjectAllocation>> GetTeamByProject(Guid TenantId, Guid ProjectId, bool IncludeInactive)
{ {
if (IncludeInactive) if (IncludeInactive)
@ -1956,11 +1903,9 @@ namespace Marco.Pms.Services.Service
return employees; return employees;
} }
} }
public async Task<List<Guid>> GetMyProjects(Guid tenantId, Employee LoggedInEmployee) public async Task<List<Guid>> GetMyProjects(Guid tenantId, Employee LoggedInEmployee)
{ {
using var scope = _serviceScopeFactory.CreateScope();
var _permission = scope.ServiceProvider.GetRequiredService<PermissionServices>();
var projectIds = await _cache.GetProjects(LoggedInEmployee.Id); var projectIds = await _cache.GetProjects(LoggedInEmployee.Id);
if (projectIds == null) if (projectIds == null)
@ -1984,11 +1929,9 @@ namespace Marco.Pms.Services.Service
} }
return projectIds; return projectIds;
} }
public async Task<List<Guid>> GetMyProjectIdsAsync(Guid tenantId, Employee loggedInEmployee) public async Task<List<Guid>> GetMyProjectIdsAsync(Guid tenantId, Employee loggedInEmployee)
{ {
using var scope = _serviceScopeFactory.CreateScope();
var _permission = scope.ServiceProvider.GetRequiredService<PermissionServices>();
// 1. Attempt to retrieve the list of project IDs from the cache first. // 1. Attempt to retrieve the list of project IDs from the cache first.
// This is the "happy path" and should be as fast as possible. // This is the "happy path" and should be as fast as possible.
List<Guid>? projectIds = await _cache.GetProjects(loggedInEmployee.Id); List<Guid>? projectIds = await _cache.GetProjects(loggedInEmployee.Id);
@ -2035,6 +1978,7 @@ namespace Marco.Pms.Services.Service
return newProjectIds; return newProjectIds;
} }
/// <summary> /// <summary>
/// Retrieves a list of ProjectInfoVMs by their IDs, using an efficient partial cache-hit strategy. /// Retrieves a list of ProjectInfoVMs by their IDs, using an efficient partial cache-hit strategy.
/// It fetches what it can from the cache (as ProjectMongoDB), gets the rest from the /// It fetches what it can from the cache (as ProjectMongoDB), gets the rest from the
@ -2085,6 +2029,7 @@ namespace Marco.Pms.Services.Service
return finalViewModels; return finalViewModels;
} }
private async Task<ProjectDetailsVM> GetProjectViewModel(Guid? id, Project project) private async Task<ProjectDetailsVM> GetProjectViewModel(Guid? id, Project project)
{ {
ProjectDetailsVM vm = new ProjectDetailsVM(); ProjectDetailsVM vm = new ProjectDetailsVM();
@ -2234,6 +2179,7 @@ namespace Marco.Pms.Services.Service
// Map from the database entity to the response ViewModel. // Map from the database entity to the response ViewModel.
return dbProject; return dbProject;
} }
private async Task UpdateCacheInBackground(Project project) private async Task UpdateCacheInBackground(Project project)
{ {
try try
@ -2251,6 +2197,7 @@ namespace Marco.Pms.Services.Service
_logger.LogError(ex, "Background cache update failed for project {ProjectId} ", project.Id); _logger.LogError(ex, "Background cache update failed for project {ProjectId} ", project.Id);
} }
} }
private async Task UpdateCacheAndNotify(Dictionary<Guid, (double Planned, double Completed)> workDelta, List<WorkItem> affectedItems) private async Task UpdateCacheAndNotify(Dictionary<Guid, (double Planned, double Completed)> workDelta, List<WorkItem> affectedItems)
{ {
try try
@ -2272,6 +2219,7 @@ namespace Marco.Pms.Services.Service
_logger.LogError(ex, "An error occurred during background cache update/notification."); _logger.LogError(ex, "An error occurred during background cache update/notification.");
} }
} }
private void ProcessBuilding(BuildingDto dto, Guid tenantId, InfraVM responseData, List<string> messages, ISet<Guid> projectIds, List<Task> cacheTasks, Employee loggedInEmployee) private void ProcessBuilding(BuildingDto dto, Guid tenantId, InfraVM responseData, List<string> messages, ISet<Guid> projectIds, List<Task> cacheTasks, Employee loggedInEmployee)
{ {
using var scope = _serviceScopeFactory.CreateScope(); using var scope = _serviceScopeFactory.CreateScope();
@ -2308,8 +2256,8 @@ namespace Marco.Pms.Services.Service
}); });
} }
private void ProcessFloor(FloorDto dto, Guid tenantId, InfraVM responseData, List<string> messages, ISet<Guid> projectIds, List<Task> cacheTasks, IDictionary<Guid, Building> buildings,
Employee loggedInEmployee) private void ProcessFloor(FloorDto dto, Guid tenantId, InfraVM responseData, List<string> messages, ISet<Guid> projectIds, List<Task> cacheTasks, IDictionary<Guid, Building> buildings, Employee loggedInEmployee)
{ {
using var scope = _serviceScopeFactory.CreateScope(); using var scope = _serviceScopeFactory.CreateScope();
var _firebase = scope.ServiceProvider.GetRequiredService<IFirebaseService>(); var _firebase = scope.ServiceProvider.GetRequiredService<IFirebaseService>();
@ -2349,8 +2297,8 @@ namespace Marco.Pms.Services.Service
}); });
} }
private void ProcessWorkArea(WorkAreaDto dto, Guid tenantId, InfraVM responseData, List<string> messages, ISet<Guid> projectIds, List<Task> cacheTasks, IDictionary<Guid, Floor> floors,
Employee loggedInEmployee) private void ProcessWorkArea(WorkAreaDto dto, Guid tenantId, InfraVM responseData, List<string> messages, ISet<Guid> projectIds, List<Task> cacheTasks, IDictionary<Guid, Floor> floors, Employee loggedInEmployee)
{ {
using var scope = _serviceScopeFactory.CreateScope(); using var scope = _serviceScopeFactory.CreateScope();
var _firebase = scope.ServiceProvider.GetRequiredService<IFirebaseService>(); var _firebase = scope.ServiceProvider.GetRequiredService<IFirebaseService>();
@ -2391,62 +2339,6 @@ namespace Marco.Pms.Services.Service
}); });
} }
private async Task<List<Guid>> GetPermissionIdsByProject(Guid projectId, Guid EmployeeId, Guid tenantId)
{
await using var context = await _dbContextFactory.CreateDbContextAsync();
using var scope = _serviceScopeFactory.CreateScope();
var _rolesHelper = scope.ServiceProvider.GetRequiredService<RolesHelper>();
// 1. Try fetching permissions from cache (fast-path lookup).
var featurePermissionIds = await _cache.GetPermissions(EmployeeId);
// If not found in cache, fallback to database (slower).
if (featurePermissionIds == null)
{
var featurePermissions = await _rolesHelper.GetFeaturePermissionByEmployeeId(EmployeeId);
featurePermissionIds = featurePermissions.Select(fp => fp.Id).ToList();
}
// 2. Handle project-level permission overrides if a project is specified.
if (projectId != Guid.Empty)
{
// Fetch permissions explicitly assigned to this employee in the project.
var projectLevelPermissionIds = await context.ProjectLevelPermissionMappings
.Where(pl => pl.ProjectId == projectId && pl.EmployeeId == EmployeeId && pl.TenantId == tenantId)
.Select(pl => pl.PermissionId)
.ToListAsync();
if (projectLevelPermissionIds?.Any() ?? false)
{
// Define modules where project-level overrides apply.
var projectLevelModuleIds = new HashSet<Guid>
{
Guid.Parse("53176ebf-c75d-42e5-839f-4508ffac3def"),
Guid.Parse("9d4b5489-2079-40b9-bd77-6e1bf90bc19f"),
Guid.Parse("52c9cf54-1eb2-44d2-81bb-524cf29c0a94"),
Guid.Parse("a8cf4331-8f04-4961-8360-a3f7c3cc7462")
};
// Get all feature permissions under those modules where the user didn't have explicit project-level grants.
var allOverriddenPermissions = await context.FeaturePermissions
.Where(fp => projectLevelModuleIds.Contains(fp.FeatureId) &&
!projectLevelPermissionIds.Contains(fp.Id))
.Select(fp => fp.Id)
.ToListAsync();
// Apply overrides:
// - Remove global permissions overridden by project-level rules.
// - Add explicit project-level permissions.
featurePermissionIds = featurePermissionIds
.Except(allOverriddenPermissions) // Remove overridden
.Concat(projectLevelPermissionIds) // Add project-level
.Distinct() // Ensure no duplicates
.ToList();
}
}
return featurePermissionIds;
}
#endregion #endregion
} }

View File

@ -40,7 +40,6 @@ namespace Marco.Pms.Services.Service.ServiceInterfaces
Task<ApiResponse<object>> GetAssignedProjectLevelPermissionAsync(Guid employeeId, Guid projectId, Guid tenantId, Employee loggedInEmployee); Task<ApiResponse<object>> GetAssignedProjectLevelPermissionAsync(Guid employeeId, Guid projectId, Guid tenantId, Employee loggedInEmployee);
Task<ApiResponse<object>> AssignProjectLevelModulesAsync(Guid tenantId, Employee loggedInEmployee); Task<ApiResponse<object>> AssignProjectLevelModulesAsync(Guid tenantId, Employee loggedInEmployee);
Task<ApiResponse<object>> GetEmployeeToWhomProjectLevelAssignedAsync(Guid projectId, Guid tenantId, Employee loggedInEmployee); Task<ApiResponse<object>> GetEmployeeToWhomProjectLevelAssignedAsync(Guid projectId, Guid tenantId, Employee loggedInEmployee);
Task<ApiResponse<object>> GetAllPermissionFroProjectAsync(Guid projectId, Employee loggedInEmployee, Guid tenantId);
} }
} }