Only sending the feature that tenant has permission of
This commit is contained in:
parent
288c0fe492
commit
cf161e4a04
@ -1,9 +1,11 @@
|
||||
using Marco.Pms.DataAccess.Data;
|
||||
using Marco.Pms.Model.Entitlements;
|
||||
using Marco.Pms.Model.Mapper;
|
||||
using AutoMapper;
|
||||
using Marco.Pms.DataAccess.Data;
|
||||
using Marco.Pms.Model.Master;
|
||||
using Marco.Pms.Model.Utilities;
|
||||
using Marco.Pms.Model.ViewModels.Master;
|
||||
using Marco.Pms.Services.Helpers;
|
||||
using MarcoBMS.Services.Helpers;
|
||||
using MarcoBMS.Services.Service;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
@ -15,43 +17,133 @@ namespace MarcoBMS.Services.Controllers
|
||||
public class FeatureController : ControllerBase
|
||||
{
|
||||
private readonly ApplicationDbContext _context;
|
||||
private readonly GeneralHelper _generalHelper;
|
||||
//private readonly UserHelper _userHelper;
|
||||
private readonly IMapper _mapper;
|
||||
private readonly ILoggingService _logger;
|
||||
private readonly Guid tenantId;
|
||||
|
||||
|
||||
public FeatureController(ApplicationDbContext context)
|
||||
public FeatureController(ApplicationDbContext context, GeneralHelper generalHelper, UserHelper userHelper, IMapper mapper, ILoggingService logger)
|
||||
{
|
||||
_context = context;
|
||||
_generalHelper = generalHelper;
|
||||
//_userHelper = userHelper;
|
||||
_mapper = mapper;
|
||||
_logger = logger;
|
||||
tenantId = userHelper.GetTenantId();
|
||||
}
|
||||
|
||||
private ICollection<FeaturePermissionVM> GetFeaturePermissionVMs(Feature model)
|
||||
private ICollection<FeaturePermissionVM> GetFeaturePermissionVM(Feature model)
|
||||
{
|
||||
ICollection<FeaturePermissionVM> features = [];
|
||||
if (model.FeaturePermissions != null)
|
||||
if (model.FeaturePermissions == null)
|
||||
{
|
||||
foreach (FeaturePermission permission in model.FeaturePermissions)
|
||||
{
|
||||
FeaturePermissionVM item = permission.ToFeaturePermissionVMFromFeaturePermission();
|
||||
features.Add(item);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
return features.OrderBy(f => f.Name).ToList();
|
||||
|
||||
ICollection<FeaturePermissionVM> features = model.FeaturePermissions.Select(p => _mapper.Map<FeaturePermissionVM>(p)).OrderBy(f => f.Name).ToList();
|
||||
return features;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[HttpGet("features")]
|
||||
public async Task<IActionResult> GetAllFeatures()
|
||||
{
|
||||
var roles = await _context.Features.Include("FeaturePermissions").Include("Module").ToListAsync();
|
||||
List<Guid> featureIds = await _generalHelper.GetFeatureIdsByTenentId(tenantId);
|
||||
var roles = await _context.Features
|
||||
.Include(f => f.FeaturePermissions)
|
||||
.Include(f => f.Module)
|
||||
.Where(f => featureIds.Contains(f.Id))
|
||||
.ToListAsync();
|
||||
|
||||
var rolesVM = roles.Select(c => new FeatureVM()
|
||||
{
|
||||
Id = c.Id,
|
||||
Name = c.Name,
|
||||
Description = c.Description,
|
||||
FeaturePermissions = GetFeaturePermissionVMs(c),
|
||||
FeaturePermissions = GetFeaturePermissionVM(c),
|
||||
ModuleId = c.ModuleId,
|
||||
ModuleName = c.Module != null ? c.Module.Name : string.Empty,
|
||||
IsActive = c.IsActive
|
||||
}).OrderBy(f => f.Name).ToList();
|
||||
return Ok(ApiResponse<object>.SuccessResponse(rolesVM, "Success.", 200));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts FeaturePermissions from Feature entity into FeaturePermissionVM collection.
|
||||
/// </summary>
|
||||
/// <param name="model">Feature entity from DB</param>
|
||||
/// <returns>Collection of FeaturePermissionVM, ordered by Name</returns>
|
||||
private ICollection<FeaturePermissionVM> GetFeaturePermissionVMs(Feature model)
|
||||
{
|
||||
if (model.FeaturePermissions == null || !model.FeaturePermissions.Any())
|
||||
{
|
||||
_logger.LogInfo("No feature permissions found for Feature: {FeatureId}", model.Id);
|
||||
return new List<FeaturePermissionVM>();
|
||||
}
|
||||
|
||||
// Project and order feature permissions
|
||||
var features = model.FeaturePermissions
|
||||
.Select(p => _mapper.Map<FeaturePermissionVM>(p))
|
||||
.OrderBy(f => f.Name)
|
||||
.ToList();
|
||||
|
||||
_logger.LogDebug("Mapped {Count} feature permissions for Feature: {FeatureId}", features.Count, model.Id);
|
||||
return features;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// API endpoint to fetch all features and their permissions for the given tenant.
|
||||
/// </summary>
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetAllFeaturesAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInfo("Fetching all features for tenant: {TenantId}", tenantId);
|
||||
|
||||
// Step 1: Get tenant-specific FeatureIds
|
||||
List<Guid> featureIds = await _generalHelper.GetFeatureIdsByTenentIdAsync(tenantId);
|
||||
if (featureIds == null || !featureIds.Any())
|
||||
{
|
||||
_logger.LogWarning("No features found for tenant: {TenantId}", tenantId);
|
||||
return Ok(ApiResponse<object>.SuccessResponse(new List<FeatureVM>(), "No features found.", 200));
|
||||
}
|
||||
|
||||
_logger.LogDebug("Retrieved {Count} feature IDs for tenant: {TenantId}", featureIds.Count, tenantId);
|
||||
|
||||
// Step 2: Query Features with related FeaturePermissions & Module
|
||||
var features = await _context.Features
|
||||
.AsNoTracking() // Optimization: Read-only query
|
||||
.Include(f => f.FeaturePermissions)
|
||||
.Include(f => f.Module)
|
||||
.Where(f => featureIds.Contains(f.Id))
|
||||
.ToListAsync();
|
||||
|
||||
_logger.LogDebug("Fetched {Count} features from DB for tenant: {TenantId}", features.Count, tenantId);
|
||||
|
||||
// Step 3: Map features to ViewModels
|
||||
var featureVMs = features
|
||||
.Select(c => new FeatureVM
|
||||
{
|
||||
Id = c.Id,
|
||||
Name = c.Name,
|
||||
Description = c.Description,
|
||||
FeaturePermissions = GetFeaturePermissionVMs(c),
|
||||
ModuleId = c.ModuleId,
|
||||
ModuleName = c.Module?.Name ?? string.Empty,
|
||||
IsActive = c.IsActive
|
||||
})
|
||||
.OrderBy(f => f.Name)
|
||||
.ToList();
|
||||
|
||||
_logger.LogInfo("Returning {Count} features for tenant: {TenantId}", featureVMs.Count, tenantId);
|
||||
|
||||
return Ok(ApiResponse<object>.SuccessResponse(featureVMs, "Success.", 200));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error while fetching features for tenant: {TenantId}", tenantId);
|
||||
return StatusCode(500, ApiResponse<object>.ErrorResponse("An unexpected error occurred.", 500));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -57,13 +57,13 @@ namespace Marco.Pms.Services.Controllers
|
||||
UserHelper userHelper,
|
||||
FeatureDetailsHelper featureDetailsHelper)
|
||||
{
|
||||
_dbContextFactory = dbContextFactory ?? throw new ArgumentNullException(nameof(dbContextFactory)); ;
|
||||
_serviceScopeFactory = serviceScopeFactory ?? throw new ArgumentNullException(nameof(serviceScopeFactory)); ;
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); ;
|
||||
_userManager = userManager ?? throw new ArgumentNullException(nameof(userManager)); ;
|
||||
_mapper = mapper ?? throw new ArgumentNullException(nameof(mapper)); ;
|
||||
_userHelper = userHelper ?? throw new ArgumentNullException(nameof(userHelper)); ;
|
||||
_featureDetailsHelper = featureDetailsHelper ?? throw new ArgumentNullException(nameof(featureDetailsHelper)); ;
|
||||
_dbContextFactory = dbContextFactory ?? throw new ArgumentNullException(nameof(dbContextFactory));
|
||||
_serviceScopeFactory = serviceScopeFactory ?? throw new ArgumentNullException(nameof(serviceScopeFactory));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_userManager = userManager ?? throw new ArgumentNullException(nameof(userManager));
|
||||
_mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
|
||||
_userHelper = userHelper ?? throw new ArgumentNullException(nameof(userHelper));
|
||||
_featureDetailsHelper = featureDetailsHelper ?? throw new ArgumentNullException(nameof(featureDetailsHelper));
|
||||
}
|
||||
#region =================================================================== Tenant APIs ===================================================================
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
using Marco.Pms.DataAccess.Data;
|
||||
using Marco.Pms.Helpers.Utility;
|
||||
using Marco.Pms.Model.MongoDBModels.Masters;
|
||||
using Marco.Pms.Model.MongoDBModels.Project;
|
||||
using MarcoBMS.Services.Service;
|
||||
@ -11,13 +12,16 @@ namespace Marco.Pms.Services.Helpers
|
||||
private readonly IDbContextFactory<ApplicationDbContext> _dbContextFactory;
|
||||
private readonly ApplicationDbContext _context; // Keeping this for direct scoped context use where appropriate
|
||||
private readonly ILoggingService _logger;
|
||||
private readonly FeatureDetailsHelper _featureDetailsHelper;
|
||||
public GeneralHelper(IDbContextFactory<ApplicationDbContext> dbContextFactory,
|
||||
ApplicationDbContext context,
|
||||
ILoggingService logger)
|
||||
ILoggingService logger,
|
||||
FeatureDetailsHelper featureDetailsHelper)
|
||||
{
|
||||
_dbContextFactory = dbContextFactory ?? throw new ArgumentNullException(nameof(dbContextFactory));
|
||||
_context = context ?? throw new ArgumentNullException(nameof(context));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_featureDetailsHelper = featureDetailsHelper ?? throw new ArgumentNullException(nameof(featureDetailsHelper));
|
||||
}
|
||||
public async Task<List<BuildingMongoDB>> GetProjectInfraFromDB(Guid projectId)
|
||||
{
|
||||
@ -211,5 +215,122 @@ namespace Marco.Pms.Services.Helpers
|
||||
return new List<WorkItemMongoDB>();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<Guid>> GetFeatureIdsByTenentId(Guid tenantId)
|
||||
{
|
||||
var tenantSubscription = await _context.TenantSubscriptions.Include(ts => ts.Plan)
|
||||
.FirstOrDefaultAsync(ts => ts.TenantId == tenantId && ts.Plan != null && !ts.IsCancelled && ts.EndDate.Date < DateTime.UtcNow.Date);
|
||||
|
||||
if (tenantSubscription == null)
|
||||
{
|
||||
_logger.LogWarning("Cannot found the tenant subscription for tenant {TenantId}", tenantId);
|
||||
return new List<Guid>();
|
||||
}
|
||||
var featureDetails = await _featureDetailsHelper.GetFeatureDetails(tenantSubscription.Plan!.FeaturesId);
|
||||
if (featureDetails == null)
|
||||
{
|
||||
_logger.LogWarning("Cannot found the feature details for tenant {TenantId}", tenantId);
|
||||
return new List<Guid>();
|
||||
}
|
||||
var featureIds = new List<Guid>();
|
||||
if (featureDetails.Modules!.Attendance!.Enabled)
|
||||
{
|
||||
featureIds.AddRange(featureDetails.Modules.Attendance.FeatureId);
|
||||
}
|
||||
if (featureDetails.Modules.ProjectManagement!.Enabled)
|
||||
{
|
||||
featureIds.AddRange(featureDetails.Modules.ProjectManagement.FeatureId);
|
||||
}
|
||||
if (featureDetails.Modules.Directory!.Enabled)
|
||||
{
|
||||
featureIds.AddRange(featureDetails.Modules.Directory.FeatureId);
|
||||
}
|
||||
if (featureDetails.Modules.Expense!.Enabled)
|
||||
{
|
||||
featureIds.AddRange(featureDetails.Modules.Expense.FeatureId);
|
||||
}
|
||||
|
||||
return featureIds;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves all enabled feature IDs for a given tenant based on their active subscription.
|
||||
/// </summary>
|
||||
/// <param name="tenantId">The unique identifier of the tenant.</param>
|
||||
/// <returns>A list of feature IDs available for the tenant.</returns>
|
||||
public async Task<List<Guid>> GetFeatureIdsByTenentIdAsync(Guid tenantId)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInfo("Fetching feature IDs for tenant: {TenantId}", tenantId);
|
||||
|
||||
// Step 1: Get active tenant subscription with plan
|
||||
var tenantSubscription = await _context.TenantSubscriptions
|
||||
.Include(ts => ts.Plan)
|
||||
.AsNoTracking() // Optimization: Read-only query, no need to track
|
||||
.FirstOrDefaultAsync(ts =>
|
||||
ts.TenantId == tenantId &&
|
||||
ts.Plan != null &&
|
||||
!ts.IsCancelled &&
|
||||
ts.EndDate.Date >= DateTime.UtcNow.Date); // FIX: Subscription should not be expired
|
||||
|
||||
if (tenantSubscription == null)
|
||||
{
|
||||
_logger.LogWarning("No active subscription found for tenant: {TenantId}", tenantId);
|
||||
return new List<Guid>();
|
||||
}
|
||||
|
||||
_logger.LogDebug("Active subscription found for tenant: {TenantId}, PlanId: {PlanId}",
|
||||
tenantId, tenantSubscription.Plan!.Id);
|
||||
|
||||
// Step 2: Get feature details from Plan
|
||||
var featureDetails = await _featureDetailsHelper.GetFeatureDetails(tenantSubscription.Plan!.FeaturesId);
|
||||
|
||||
if (featureDetails == null)
|
||||
{
|
||||
_logger.LogWarning("No feature details found for tenant: {TenantId}, PlanId: {PlanId}",
|
||||
tenantId, tenantSubscription.Plan!.Id);
|
||||
return new List<Guid>();
|
||||
}
|
||||
|
||||
// Step 3: Collect all enabled feature IDs from modules
|
||||
var featureIds = new List<Guid> { new Guid("2f3509b7-160d-410a-b9b6-daadd96c986d"), new Guid("be3b3afc-6ccf-4566-b9b6-aafcb65546be") };
|
||||
|
||||
if (featureDetails.Modules?.Attendance?.Enabled == true)
|
||||
{
|
||||
featureIds.AddRange(featureDetails.Modules.Attendance.FeatureId);
|
||||
_logger.LogDebug("Added Attendance module features for tenant: {TenantId}", tenantId);
|
||||
}
|
||||
|
||||
if (featureDetails.Modules?.ProjectManagement?.Enabled == true)
|
||||
{
|
||||
featureIds.AddRange(featureDetails.Modules.ProjectManagement.FeatureId);
|
||||
_logger.LogDebug("Added Project Management module features for tenant: {TenantId}", tenantId);
|
||||
}
|
||||
|
||||
if (featureDetails.Modules?.Directory?.Enabled == true)
|
||||
{
|
||||
featureIds.AddRange(featureDetails.Modules.Directory.FeatureId);
|
||||
_logger.LogDebug("Added Directory module features for tenant: {TenantId}", tenantId);
|
||||
}
|
||||
|
||||
if (featureDetails.Modules?.Expense?.Enabled == true)
|
||||
{
|
||||
featureIds.AddRange(featureDetails.Modules.Expense.FeatureId);
|
||||
_logger.LogDebug("Added Expense module features for tenant: {TenantId}", tenantId);
|
||||
}
|
||||
|
||||
_logger.LogInfo("Returning {Count} feature IDs for tenant: {TenantId}", featureIds.Count, tenantId);
|
||||
|
||||
return featureIds.Distinct().ToList();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Step 4: Handle unexpected errors
|
||||
_logger.LogError(ex, "Error retrieving feature IDs for tenant: {TenantId}", tenantId);
|
||||
return new List<Guid>();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ using Marco.Pms.Model.Dtos.Master;
|
||||
using Marco.Pms.Model.Dtos.Project;
|
||||
using Marco.Pms.Model.Dtos.Tenant;
|
||||
using Marco.Pms.Model.Employees;
|
||||
using Marco.Pms.Model.Entitlements;
|
||||
using Marco.Pms.Model.Expenses;
|
||||
using Marco.Pms.Model.Master;
|
||||
using Marco.Pms.Model.MongoDBModels;
|
||||
@ -221,6 +222,8 @@ namespace Marco.Pms.Services.MappingProfiles
|
||||
|
||||
#region ======================================================= Master =======================================================
|
||||
|
||||
CreateMap<FeaturePermission, FeaturePermissionVM>();
|
||||
|
||||
#region ======================================================= Expenses Type Master =======================================================
|
||||
|
||||
CreateMap<ExpensesTypeMasterDto, ExpensesTypeMaster>()
|
||||
|
Loading…
x
Reference in New Issue
Block a user