Added API to get side menu for mobile
This commit is contained in:
parent
8470223f98
commit
20df833c48
@ -7,8 +7,8 @@ namespace Marco.Pms.CacheHelper
|
|||||||
{
|
{
|
||||||
public class SidebarMenuHelper
|
public class SidebarMenuHelper
|
||||||
{
|
{
|
||||||
private readonly IMongoCollection<MenuSection> _oldCollection;
|
private readonly IMongoCollection<WebSideMenuItem> _webCollection;
|
||||||
private readonly IMongoCollection<WebSideMenuItem> _collection;
|
private readonly IMongoCollection<MobileMenu> _mobileCollection;
|
||||||
private readonly ILogger<SidebarMenuHelper> _logger;
|
private readonly ILogger<SidebarMenuHelper> _logger;
|
||||||
|
|
||||||
public SidebarMenuHelper(IConfiguration configuration, ILogger<SidebarMenuHelper> logger)
|
public SidebarMenuHelper(IConfiguration configuration, ILogger<SidebarMenuHelper> logger)
|
||||||
@ -18,8 +18,9 @@ namespace Marco.Pms.CacheHelper
|
|||||||
var mongoUrl = new MongoUrl(connectionString);
|
var mongoUrl = new MongoUrl(connectionString);
|
||||||
var client = new MongoClient(mongoUrl);
|
var client = new MongoClient(mongoUrl);
|
||||||
var database = client.GetDatabase(mongoUrl.DatabaseName);
|
var database = client.GetDatabase(mongoUrl.DatabaseName);
|
||||||
_oldCollection = database.GetCollection<MenuSection>("Menus");
|
_webCollection = database.GetCollection<WebSideMenuItem>("WebSideMenus");
|
||||||
_collection = database.GetCollection<WebSideMenuItem>("WebSideMenus");
|
_mobileCollection = database.GetCollection<MobileMenu>("MobileSideMenus");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<WebSideMenuItem>> GetAllWebMenuSectionsAsync(Guid tenantId)
|
public async Task<List<WebSideMenuItem>> GetAllWebMenuSectionsAsync(Guid tenantId)
|
||||||
@ -28,7 +29,7 @@ namespace Marco.Pms.CacheHelper
|
|||||||
{
|
{
|
||||||
var filter = Builders<WebSideMenuItem>.Filter.Eq(e => e.TenantId, tenantId);
|
var filter = Builders<WebSideMenuItem>.Filter.Eq(e => e.TenantId, tenantId);
|
||||||
|
|
||||||
var result = await _collection
|
var result = await _webCollection
|
||||||
.Find(filter)
|
.Find(filter)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
if (result.Any())
|
if (result.Any())
|
||||||
@ -39,7 +40,7 @@ namespace Marco.Pms.CacheHelper
|
|||||||
tenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26");
|
tenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26");
|
||||||
filter = Builders<WebSideMenuItem>.Filter.Eq(e => e.TenantId, tenantId);
|
filter = Builders<WebSideMenuItem>.Filter.Eq(e => e.TenantId, tenantId);
|
||||||
|
|
||||||
result = await _collection
|
result = await _webCollection
|
||||||
.Find(filter)
|
.Find(filter)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
return result;
|
return result;
|
||||||
@ -50,12 +51,11 @@ namespace Marco.Pms.CacheHelper
|
|||||||
return new List<WebSideMenuItem>();
|
return new List<WebSideMenuItem>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<WebSideMenuItem>> AddWebMenuItemAsync(List<WebSideMenuItem> newItems)
|
public async Task<List<WebSideMenuItem>> AddWebMenuItemAsync(List<WebSideMenuItem> newItems)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await _collection.InsertManyAsync(newItems);
|
await _webCollection.InsertManyAsync(newItems);
|
||||||
return newItems;
|
return newItems;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@ -64,27 +64,48 @@ namespace Marco.Pms.CacheHelper
|
|||||||
return new List<WebSideMenuItem>();
|
return new List<WebSideMenuItem>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public async Task<List<MobileMenu>> GetAllMobileMenuSectionsAsync(Guid tenantId)
|
||||||
public async Task<List<MenuSection>> GetAllMenuSectionsAsync(Guid tenantId)
|
|
||||||
{
|
{
|
||||||
var filter = Builders<MenuSection>.Filter.Eq(e => e.TenantId, tenantId);
|
try
|
||||||
|
|
||||||
var result = await _oldCollection
|
|
||||||
.Find(filter)
|
|
||||||
.ToListAsync();
|
|
||||||
if (result.Any())
|
|
||||||
{
|
{
|
||||||
|
var filter = Builders<MobileMenu>.Filter.Eq(e => e.TenantId, tenantId);
|
||||||
|
|
||||||
|
var result = await _mobileCollection
|
||||||
|
.Find(filter)
|
||||||
|
.ToListAsync();
|
||||||
|
if (result.Any())
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
tenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26");
|
||||||
|
filter = Builders<MobileMenu>.Filter.Eq(e => e.TenantId, tenantId);
|
||||||
|
|
||||||
|
result = await _mobileCollection
|
||||||
|
.Find(filter)
|
||||||
|
.ToListAsync();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
tenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26");
|
{
|
||||||
filter = Builders<MenuSection>.Filter.Eq(e => e.TenantId, tenantId);
|
_logger.LogError(ex, "Error occurred while fetching Web Menu Sections.");
|
||||||
|
return new List<MobileMenu>();
|
||||||
result = await _oldCollection
|
}
|
||||||
.Find(filter)
|
|
||||||
.ToListAsync();
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
public async Task<List<MobileMenu>> AddMobileMenuItemAsync(List<MobileMenu> newItems)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _mobileCollection.InsertManyAsync(newItems);
|
||||||
|
return newItems;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error occurred while adding Mobile Menu Section.");
|
||||||
|
return new List<MobileMenu>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
21
Marco.Pms.Model/AppMenu/MobileMenu.cs
Normal file
21
Marco.Pms.Model/AppMenu/MobileMenu.cs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
using MongoDB.Bson;
|
||||||
|
using MongoDB.Bson.Serialization.Attributes;
|
||||||
|
|
||||||
|
namespace Marco.Pms.Model.AppMenu
|
||||||
|
{
|
||||||
|
public class MobileMenu
|
||||||
|
{
|
||||||
|
[BsonId]
|
||||||
|
[BsonRepresentation(BsonType.String)]
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
public string? Name { get; set; }
|
||||||
|
public bool Available { get; set; }
|
||||||
|
public string? MobileLink { get; set; }
|
||||||
|
|
||||||
|
[BsonRepresentation(BsonType.String)]
|
||||||
|
public List<Guid> PermissionIds { get; set; } = new List<Guid>();
|
||||||
|
|
||||||
|
[BsonRepresentation(BsonType.String)]
|
||||||
|
public Guid TenantId { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
13
Marco.Pms.Model/Dtos/AppMenu/CreateMobileSideMenuItemDto.cs
Normal file
13
Marco.Pms.Model/Dtos/AppMenu/CreateMobileSideMenuItemDto.cs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
using MongoDB.Bson;
|
||||||
|
using MongoDB.Bson.Serialization.Attributes;
|
||||||
|
|
||||||
|
namespace Marco.Pms.Model.Dtos.AppMenu
|
||||||
|
{
|
||||||
|
public class CreateMobileSideMenuItemDto
|
||||||
|
{
|
||||||
|
public string? Name { get; set; }
|
||||||
|
public bool Available { get; set; }
|
||||||
|
public string? MobileLink { get; set; }
|
||||||
|
public List<Guid> PermissionIds { get; set; } = new List<Guid>();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -4,7 +4,7 @@
|
|||||||
{
|
{
|
||||||
public Guid? Id { get; set; }
|
public Guid? Id { get; set; }
|
||||||
public Guid? ParentMenuId { get; set; }
|
public Guid? ParentMenuId { get; set; }
|
||||||
public string? Text { get; set; }
|
public string? Name { get; set; }
|
||||||
public string? Icon { get; set; }
|
public string? Icon { get; set; }
|
||||||
public bool Available { get; set; } = true;
|
public bool Available { get; set; } = true;
|
||||||
public string Link { get; set; } = string.Empty;
|
public string Link { get; set; } = string.Empty;
|
||||||
|
|||||||
@ -2,7 +2,6 @@
|
|||||||
using Marco.Pms.CacheHelper;
|
using Marco.Pms.CacheHelper;
|
||||||
using Marco.Pms.Model.AppMenu;
|
using Marco.Pms.Model.AppMenu;
|
||||||
using Marco.Pms.Model.Dtos.AppMenu;
|
using Marco.Pms.Model.Dtos.AppMenu;
|
||||||
using Marco.Pms.Model.Entitlements;
|
|
||||||
using Marco.Pms.Model.Utilities;
|
using Marco.Pms.Model.Utilities;
|
||||||
using Marco.Pms.Model.ViewModels.AppMenu;
|
using Marco.Pms.Model.ViewModels.AppMenu;
|
||||||
using Marco.Pms.Model.ViewModels.DocumentManager;
|
using Marco.Pms.Model.ViewModels.DocumentManager;
|
||||||
@ -26,6 +25,7 @@ namespace Marco.Pms.Services.Controllers
|
|||||||
private readonly IMapper _mapper;
|
private readonly IMapper _mapper;
|
||||||
private readonly ILoggingService _logger;
|
private readonly ILoggingService _logger;
|
||||||
private readonly IServiceScopeFactory _serviceScopeFactory;
|
private readonly IServiceScopeFactory _serviceScopeFactory;
|
||||||
|
private readonly PermissionServices _permissionService;
|
||||||
|
|
||||||
private readonly Guid tenantId;
|
private readonly Guid tenantId;
|
||||||
private static readonly Guid superTenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26");
|
private static readonly Guid superTenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26");
|
||||||
@ -42,7 +42,8 @@ namespace Marco.Pms.Services.Controllers
|
|||||||
SidebarMenuHelper sideBarMenuHelper,
|
SidebarMenuHelper sideBarMenuHelper,
|
||||||
IMapper mapper,
|
IMapper mapper,
|
||||||
ILoggingService logger,
|
ILoggingService logger,
|
||||||
IServiceScopeFactory serviceScopeFactory)
|
IServiceScopeFactory serviceScopeFactory,
|
||||||
|
PermissionServices permissionService)
|
||||||
{
|
{
|
||||||
|
|
||||||
_userHelper = userHelper;
|
_userHelper = userHelper;
|
||||||
@ -51,6 +52,7 @@ namespace Marco.Pms.Services.Controllers
|
|||||||
_logger = logger;
|
_logger = logger;
|
||||||
_serviceScopeFactory = serviceScopeFactory;
|
_serviceScopeFactory = serviceScopeFactory;
|
||||||
tenantId = userHelper.GetTenantId();
|
tenantId = userHelper.GetTenantId();
|
||||||
|
_permissionService = permissionService;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -99,18 +101,13 @@ namespace Marco.Pms.Services.Controllers
|
|||||||
_logger.LogDebug("GetAppSideBarMenuAsync resolved employee context. EmployeeId: {EmployeeId}, TenantId: {TenantId}, CorrelationId: {CorrelationId}",
|
_logger.LogDebug("GetAppSideBarMenuAsync resolved employee context. EmployeeId: {EmployeeId}, TenantId: {TenantId}, CorrelationId: {CorrelationId}",
|
||||||
employeeId, tenantId, correlationId);
|
employeeId, tenantId, correlationId);
|
||||||
|
|
||||||
// 3. Create scoped permission service
|
// 3. Preload all permission ids for the employee for efficient in-memory checks
|
||||||
// - Avoid capturing scoped services directly in controller ctor when they depend on per-request state.
|
var permissionIds = await _permissionService.GetPermissionIdsByEmployeeId(employeeId);
|
||||||
using var scope = _serviceScopeFactory.CreateScope();
|
|
||||||
var permissionService = scope.ServiceProvider.GetRequiredService<PermissionServices>();
|
|
||||||
|
|
||||||
// 4. Preload all permission ids for the employee for efficient in-memory checks
|
|
||||||
var permissionIds = await permissionService.GetPermissionIdsByEmployeeId(employeeId);
|
|
||||||
|
|
||||||
_logger.LogDebug("GetAppSideBarMenuAsync loaded {PermissionCount} permissions for EmployeeId: {EmployeeId}, CorrelationId: {CorrelationId}",
|
_logger.LogDebug("GetAppSideBarMenuAsync loaded {PermissionCount} permissions for EmployeeId: {EmployeeId}, CorrelationId: {CorrelationId}",
|
||||||
permissionIds.Count, employeeId, correlationId);
|
permissionIds.Count, employeeId, correlationId);
|
||||||
|
|
||||||
// 5. Fetch all menu entries configured for this tenant
|
// 4. Fetch all menu entries configured for this tenant
|
||||||
var menus = await _sideBarMenuHelper.GetAllWebMenuSectionsAsync(tenantId);
|
var menus = await _sideBarMenuHelper.GetAllWebMenuSectionsAsync(tenantId);
|
||||||
|
|
||||||
if (menus == null || !menus.Any())
|
if (menus == null || !menus.Any())
|
||||||
@ -126,7 +123,7 @@ namespace Marco.Pms.Services.Controllers
|
|||||||
_logger.LogDebug("GetAppSideBarMenuAsync loaded {MenuSectionCount} raw menu records. TenantId: {TenantId}, CorrelationId: {CorrelationId}",
|
_logger.LogDebug("GetAppSideBarMenuAsync loaded {MenuSectionCount} raw menu records. TenantId: {TenantId}, CorrelationId: {CorrelationId}",
|
||||||
menus.Count, tenantId, correlationId);
|
menus.Count, tenantId, correlationId);
|
||||||
|
|
||||||
// 6. Build logical menu sections (root + children) and apply permission filtering
|
// 5. Build logical menu sections (root + children) and apply permission filtering
|
||||||
var responseSections = new List<WebMenuSectionVM>();
|
var responseSections = new List<WebMenuSectionVM>();
|
||||||
|
|
||||||
// Root container section for the web UI.
|
// Root container section for the web UI.
|
||||||
@ -153,7 +150,7 @@ namespace Marco.Pms.Services.Controllers
|
|||||||
// If the menu requires any permission, check now.
|
// If the menu requires any permission, check now.
|
||||||
if (menu.PermissionIds.Any())
|
if (menu.PermissionIds.Any())
|
||||||
{
|
{
|
||||||
var hasMenuPermission = permissionService.HasPermissionAny(permissionIds, menu.PermissionIds, employeeId);
|
var hasMenuPermission = _permissionService.HasPermissionAny(permissionIds, menu.PermissionIds, employeeId);
|
||||||
|
|
||||||
if (!hasMenuPermission)
|
if (!hasMenuPermission)
|
||||||
{
|
{
|
||||||
@ -175,7 +172,7 @@ namespace Marco.Pms.Services.Controllers
|
|||||||
// If the submenu requires permissions, validate before adding.
|
// If the submenu requires permissions, validate before adding.
|
||||||
if (subMenu.PermissionIds.Any())
|
if (subMenu.PermissionIds.Any())
|
||||||
{
|
{
|
||||||
var hasSubPermission = permissionService.HasPermissionAny(permissionIds, subMenu.PermissionIds, employeeId);
|
var hasSubPermission = _permissionService.HasPermissionAny(permissionIds, subMenu.PermissionIds, employeeId);
|
||||||
|
|
||||||
if (!hasSubPermission)
|
if (!hasSubPermission)
|
||||||
{
|
{
|
||||||
@ -240,7 +237,7 @@ namespace Marco.Pms.Services.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("add/side-menu")]
|
[HttpPost("add/side-menu")]
|
||||||
public async Task<IActionResult> AddMenuItemAsync([FromBody] List<CreateWebSideMenuItemDto> model)
|
public async Task<IActionResult> AddWebMenuItemAsync([FromBody] List<CreateWebSideMenuItemDto> model)
|
||||||
{
|
{
|
||||||
// Step 1: Validate tenant context
|
// Step 1: Validate tenant context
|
||||||
if (tenantId == Guid.Empty)
|
if (tenantId == Guid.Empty)
|
||||||
@ -306,9 +303,194 @@ namespace Marco.Pms.Services.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieves the master menu list based on enabled features for the current tenant.
|
/// Retrieves the mobile sidebar menu sections for the authenticated employee within the current tenant,
|
||||||
|
/// filtered by employee permissions and structured for mobile application consumption.
|
||||||
|
/// Supports permission-based access control and tenant isolation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>List of master menu items available for the tenant</returns>
|
/// <returns>A filtered list of accessible mobile menu sections or appropriate error response.</returns>
|
||||||
|
/// <response code="200">Returns filtered mobile menu sections successfully.</response>
|
||||||
|
/// <response code="400">Invalid tenant identifier provided.</response>
|
||||||
|
/// <response code="403">Employee context not resolved or insufficient permissions.</response>
|
||||||
|
/// <response code="500">Internal server error during menu retrieval or processing.</response>
|
||||||
|
|
||||||
|
[HttpGet("get/menu-mobile")]
|
||||||
|
public async Task<IActionResult> GetAppSideBarMenuForMobileAsync()
|
||||||
|
{
|
||||||
|
// Correlation ID enables distributed tracing across services, middleware, and structured logs.
|
||||||
|
var correlationId = HttpContext.TraceIdentifier;
|
||||||
|
|
||||||
|
_logger.LogInfo("GetAppSideBarMenuForMobileAsync started. TenantId: {TenantId}, CorrelationId: {CorrelationId}",
|
||||||
|
tenantId, correlationId);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 1. Validate tenant isolation - critical for multi-tenant security
|
||||||
|
if (tenantId == Guid.Empty)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("GetAppSideBarMenuForMobileAsync rejected: invalid tenant context. TenantId: {TenantId}, CorrelationId: {CorrelationId}",
|
||||||
|
tenantId, correlationId);
|
||||||
|
|
||||||
|
var error = ApiResponse<object>.ErrorResponse(
|
||||||
|
"InvalidTenantContext",
|
||||||
|
"Tenant identifier is missing or invalid. Verify tenant context and retry.",
|
||||||
|
400);
|
||||||
|
|
||||||
|
return BadRequest(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Resolve authenticated employee context with tenant isolation
|
||||||
|
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||||
|
if (loggedInEmployee is null)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("GetAppSideBarMenuForMobileAsync failed: employee context not resolved. TenantId: {TenantId}, CorrelationId: {CorrelationId}",
|
||||||
|
tenantId, correlationId);
|
||||||
|
|
||||||
|
var error = ApiResponse<object>.ErrorResponse("EmployeeContextNotFound", "Current employee context could not be resolved. Please authenticate and retry.", 403);
|
||||||
|
|
||||||
|
return StatusCode(403, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
var employeeId = loggedInEmployee.Id;
|
||||||
|
_logger.LogDebug("GetAppSideBarMenuForMobileAsync resolved employee: EmployeeId: {EmployeeId}, TenantId: {TenantId}, CorrelationId: {CorrelationId}",
|
||||||
|
employeeId, tenantId, correlationId);
|
||||||
|
|
||||||
|
// 3. Bulk-load employee permissions for efficient in-memory permission checks (avoids N+1 queries)
|
||||||
|
var permissionIds = await _permissionService.GetPermissionIdsByEmployeeId(employeeId);
|
||||||
|
_logger.LogDebug("GetAppSideBarMenuForMobileAsync loaded {PermissionCount} permissions for EmployeeId: {EmployeeId}, TenantId: {TenantId}, CorrelationId: {CorrelationId}",
|
||||||
|
permissionIds?.Count ?? 0, employeeId, tenantId, correlationId);
|
||||||
|
|
||||||
|
// 4. Fetch tenant-specific mobile menu configuration
|
||||||
|
var allMenus = await _sideBarMenuHelper.GetAllMobileMenuSectionsAsync(tenantId);
|
||||||
|
if (allMenus == null || !allMenus.Any())
|
||||||
|
{
|
||||||
|
_logger.LogInfo("GetAppSideBarMenuForMobileAsync: no mobile menu sections configured. TenantId: {TenantId}, CorrelationId: {CorrelationId}",
|
||||||
|
tenantId, correlationId);
|
||||||
|
|
||||||
|
var emptyResponse = new List<MenuSectionApplicationVM>();
|
||||||
|
return Ok(ApiResponse<object>.SuccessResponse(emptyResponse,
|
||||||
|
"No mobile sidebar menu sections configured for this tenant.", 200));
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogDebug("GetAppSideBarMenuForMobileAsync loaded {MenuCount} raw sections. TenantId: {TenantId}, CorrelationId: {CorrelationId}",
|
||||||
|
allMenus.Count, tenantId, correlationId);
|
||||||
|
|
||||||
|
// 5. Filter menus by employee permissions (in-memory for performance)
|
||||||
|
var accessibleMenus = new List<MobileMenu>();
|
||||||
|
foreach (var menuSection in allMenus)
|
||||||
|
{
|
||||||
|
// Skip permission check for public menu items
|
||||||
|
if (!menuSection.PermissionIds.Any())
|
||||||
|
{
|
||||||
|
accessibleMenus.Add(menuSection);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform permission intersection check
|
||||||
|
var hasAccess = _permissionService.HasPermissionAny(permissionIds ?? new List<Guid>(),
|
||||||
|
menuSection.PermissionIds, employeeId);
|
||||||
|
|
||||||
|
if (hasAccess)
|
||||||
|
{
|
||||||
|
accessibleMenus.Add(menuSection);
|
||||||
|
_logger.LogDebug("GetAppSideBarMenuForMobileAsync granted menu access. MenuId: {MenuId}, EmployeeId: {EmployeeId}, CorrelationId: {CorrelationId}",
|
||||||
|
menuSection.Id, employeeId, correlationId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogDebug("GetAppSideBarMenuForMobileAsync denied menu access. MenuId: {MenuId}, EmployeeId: {EmployeeId}, CorrelationId: {CorrelationId}",
|
||||||
|
menuSection.Id, employeeId, correlationId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. Defensive mapping with null-safety
|
||||||
|
var response = _mapper.Map<List<MenuSectionApplicationVM>>(accessibleMenus);
|
||||||
|
_logger.LogInfo("GetAppSideBarMenuForMobileAsync completed successfully. AccessibleMenus: {AccessibleCount}/{TotalCount}, EmployeeId: {EmployeeId}, TenantId: {TenantId}, CorrelationId: {CorrelationId}",
|
||||||
|
accessibleMenus.Count, allMenus.Count, employeeId, tenantId, correlationId);
|
||||||
|
|
||||||
|
return Ok(ApiResponse<object>.SuccessResponse(response,
|
||||||
|
$"Mobile sidebar menu fetched successfully ({response?.Count ?? 0} sections accessible).", 200));
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("GetAppSideBarMenuForMobileAsync cancelled. TenantId: {TenantId}, CorrelationId: {CorrelationId}",
|
||||||
|
tenantId, correlationId);
|
||||||
|
|
||||||
|
return StatusCode(499, ApiResponse<object>.ErrorResponse("RequestCancelled",
|
||||||
|
"Request was cancelled by client or timeout.", 499));
|
||||||
|
}
|
||||||
|
catch (UnauthorizedAccessException ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "GetAppSideBarMenuForMobileAsync authorization failed. TenantId: {TenantId}, CorrelationId: {CorrelationId}",
|
||||||
|
tenantId, correlationId);
|
||||||
|
|
||||||
|
return StatusCode(403, ApiResponse<object>.ErrorResponse("AuthorizationFailed",
|
||||||
|
"Insufficient permissions to access mobile menu sections.", 403));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "GetAppSideBarMenuForMobileAsync failed unexpectedly. TenantId: {TenantId}, CorrelationId: {CorrelationId}",
|
||||||
|
tenantId, correlationId);
|
||||||
|
|
||||||
|
return StatusCode(500, ApiResponse<object>.ErrorResponse("InternalServerError",
|
||||||
|
"An unexpected error occurred while fetching the mobile sidebar menu. Please contact support if issue persists.", 500));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[HttpPost("add/mobile/side-menu")]
|
||||||
|
public async Task<IActionResult> AddMobileMenuItemAsync([FromBody] List<CreateMobileSideMenuItemDto> model)
|
||||||
|
{
|
||||||
|
// Step 1: Validate tenant context
|
||||||
|
if (tenantId == Guid.Empty)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("GetAppSideBarMenuAsync rejected: Invalid TenantId.");
|
||||||
|
|
||||||
|
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid TenantId", "The tenant identifier provided is invalid or missing.", 400));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 2: Fetch logged-in user
|
||||||
|
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||||
|
var isRootUser = loggedInEmployee.ApplicationUser?.IsRootUser ?? false;
|
||||||
|
|
||||||
|
// Step 3: Authorization check
|
||||||
|
if (!isRootUser && tenantId != superTenantId)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Access denied: User {UserId} attempted to add menu item in Tenant {TenantId}",
|
||||||
|
loggedInEmployee.Id, tenantId);
|
||||||
|
|
||||||
|
return StatusCode(403, ApiResponse<object>.ErrorResponse("Access Denied", "User does not have permission.", 403));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 4: Input validation
|
||||||
|
if (model == null)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Invalid AddMenuItem request. Tenant: {TenantId}, UserId: {UserId}",
|
||||||
|
tenantId, loggedInEmployee.Id);
|
||||||
|
|
||||||
|
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid section ID or menu item payload.", 400));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 5: Map DTO to entity
|
||||||
|
var menuItemEntity = _mapper.Map<List<MobileMenu>>(model);
|
||||||
|
|
||||||
|
menuItemEntity.ForEach(m => m.TenantId = tenantId);
|
||||||
|
|
||||||
|
// Step 6: Perform Add operation
|
||||||
|
var result = await _sideBarMenuHelper.AddMobileMenuItemAsync(menuItemEntity);
|
||||||
|
|
||||||
|
if (result == null)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Menu section not found. Unable to add menu item. TenantId: {TenantId}, UserId: {UserId}", tenantId, loggedInEmployee.Id);
|
||||||
|
|
||||||
|
return NotFound(ApiResponse<object>.ErrorResponse("Menu section not found", 404));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 7: Successful addition
|
||||||
|
_logger.LogInfo("Menu items added successfully TenantId: {TenantId}, UserId: {UserId}",
|
||||||
|
tenantId, loggedInEmployee.Id);
|
||||||
|
|
||||||
|
return Ok(ApiResponse<object>.SuccessResponse(result, "Menu item added successfully"));
|
||||||
|
}
|
||||||
|
|
||||||
[HttpGet("get/master-list")]
|
[HttpGet("get/master-list")]
|
||||||
public async Task<IActionResult> GetMasterList()
|
public async Task<IActionResult> GetMasterList()
|
||||||
@ -390,236 +572,6 @@ namespace Marco.Pms.Services.Controllers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("get/menu-mobile")]
|
|
||||||
public async Task<IActionResult> GetAppSideBarMenuForobile()
|
|
||||||
{
|
|
||||||
// Step 1: Get logged-in employee
|
|
||||||
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
|
||||||
var employeeId = loggedInEmployee.Id;
|
|
||||||
|
|
||||||
using var scope = _serviceScopeFactory.CreateScope();
|
|
||||||
var _permissions = scope.ServiceProvider.GetRequiredService<PermissionServices>();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Step 2: Fetch all menu sections for the tenant
|
|
||||||
var menus = await _sideBarMenuHelper.GetAllMenuSectionsAsync(tenantId);
|
|
||||||
if (!(menus?.Any() ?? false))
|
|
||||||
{
|
|
||||||
menus = new List<MenuSection>
|
|
||||||
{
|
|
||||||
MenuStaticMaster.menu
|
|
||||||
};
|
|
||||||
}
|
|
||||||
List<MenuSectionApplicationVM> response = new List<MenuSectionApplicationVM>();
|
|
||||||
|
|
||||||
foreach (var menu in menus)
|
|
||||||
{
|
|
||||||
var allowedItems = new List<MenuItem>();
|
|
||||||
|
|
||||||
foreach (var item in menu.Items)
|
|
||||||
{
|
|
||||||
// --- Item permission check ---
|
|
||||||
if (!item.PermissionIds.Any())
|
|
||||||
{
|
|
||||||
MenuSectionApplicationVM menuVM = new MenuSectionApplicationVM
|
|
||||||
{
|
|
||||||
Id = item.Id,
|
|
||||||
Name = item.Text,
|
|
||||||
Available = true,
|
|
||||||
MobileLink = item.MobileLink,
|
|
||||||
};
|
|
||||||
response.Add(menuVM);
|
|
||||||
|
|
||||||
if (item.Submenu?.Any() == true)
|
|
||||||
{
|
|
||||||
var allowedSubmenus = new List<SubMenuItem>();
|
|
||||||
|
|
||||||
foreach (var subItem in item.Submenu)
|
|
||||||
{
|
|
||||||
if (!subItem.PermissionIds.Any())
|
|
||||||
{
|
|
||||||
MenuSectionApplicationVM subMenuVM = new MenuSectionApplicationVM
|
|
||||||
{
|
|
||||||
Id = subItem.Id,
|
|
||||||
Name = subItem.Text,
|
|
||||||
Available = true,
|
|
||||||
MobileLink = subItem.MobileLink
|
|
||||||
};
|
|
||||||
response.Add(subMenuVM);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var subMenuPermissionIds = subItem.PermissionIds
|
|
||||||
.Select(Guid.Parse)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
bool isSubItemAllowed = await _permissions.HasPermissionAny(subMenuPermissionIds, employeeId);
|
|
||||||
|
|
||||||
if (isSubItemAllowed)
|
|
||||||
{
|
|
||||||
MenuSectionApplicationVM subMenuVM = new MenuSectionApplicationVM
|
|
||||||
{
|
|
||||||
Id = subItem.Id,
|
|
||||||
Name = subItem.Text,
|
|
||||||
Available = true,
|
|
||||||
MobileLink = subItem.MobileLink
|
|
||||||
};
|
|
||||||
response.Add(subMenuVM);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replace with filtered submenus
|
|
||||||
item.Submenu = allowedSubmenus;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Convert permission string IDs to GUIDs
|
|
||||||
var menuPermissionIds = item.PermissionIds
|
|
||||||
.Select(Guid.Parse)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
bool isAllowed = await _permissions.HasPermissionAny(menuPermissionIds, employeeId);
|
|
||||||
|
|
||||||
// If allowed, filter its submenus as well
|
|
||||||
if (isAllowed)
|
|
||||||
{
|
|
||||||
if (item.Submenu?.Any() == true)
|
|
||||||
{
|
|
||||||
var allowedSubmenus = new List<SubMenuItem>();
|
|
||||||
|
|
||||||
foreach (var subItem in item.Submenu)
|
|
||||||
{
|
|
||||||
if (!subItem.PermissionIds.Any())
|
|
||||||
{
|
|
||||||
MenuSectionApplicationVM subMenuVM = new MenuSectionApplicationVM
|
|
||||||
{
|
|
||||||
Id = subItem.Id,
|
|
||||||
Name = subItem.Text,
|
|
||||||
Available = true,
|
|
||||||
MobileLink = subItem.MobileLink
|
|
||||||
};
|
|
||||||
response.Add(subMenuVM);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var subMenuPermissionIds = subItem.PermissionIds
|
|
||||||
.Select(Guid.Parse)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
bool isSubItemAllowed = await _permissions.HasPermissionAny(subMenuPermissionIds, employeeId);
|
|
||||||
|
|
||||||
if (isSubItemAllowed)
|
|
||||||
{
|
|
||||||
MenuSectionApplicationVM subMenuVM = new MenuSectionApplicationVM
|
|
||||||
{
|
|
||||||
Id = subItem.Id,
|
|
||||||
Name = subItem.Text,
|
|
||||||
Available = true,
|
|
||||||
MobileLink = subItem.MobileLink,
|
|
||||||
};
|
|
||||||
response.Add(subMenuVM);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replace with filtered submenus
|
|
||||||
item.Submenu = allowedSubmenus;
|
|
||||||
}
|
|
||||||
|
|
||||||
MenuSectionApplicationVM menuVM = new MenuSectionApplicationVM
|
|
||||||
{
|
|
||||||
Id = item.Id,
|
|
||||||
Name = item.Text,
|
|
||||||
Available = true,
|
|
||||||
MobileLink = item.MobileLink
|
|
||||||
};
|
|
||||||
response.Add(menuVM);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replace with filtered items
|
|
||||||
menu.Items = allowedItems;
|
|
||||||
}
|
|
||||||
|
|
||||||
var viewDocumentTask = Task.Run(async () =>
|
|
||||||
{
|
|
||||||
using var taskScope = _serviceScopeFactory.CreateScope();
|
|
||||||
var permissions = taskScope.ServiceProvider.GetRequiredService<PermissionServices>();
|
|
||||||
return await permissions.HasPermission(PermissionsMaster.ViewDocument, employeeId);
|
|
||||||
});
|
|
||||||
|
|
||||||
var uploadDocumentTask = Task.Run(async () =>
|
|
||||||
{
|
|
||||||
using var taskScope = _serviceScopeFactory.CreateScope();
|
|
||||||
var permissions = taskScope.ServiceProvider.GetRequiredService<PermissionServices>();
|
|
||||||
return await permissions.HasPermission(PermissionsMaster.UploadDocument, employeeId);
|
|
||||||
});
|
|
||||||
|
|
||||||
var verifyDocumentTask = Task.Run(async () =>
|
|
||||||
{
|
|
||||||
using var taskScope = _serviceScopeFactory.CreateScope();
|
|
||||||
var permissions = taskScope.ServiceProvider.GetRequiredService<PermissionServices>();
|
|
||||||
return await permissions.HasPermission(PermissionsMaster.VerifyDocument, employeeId);
|
|
||||||
});
|
|
||||||
|
|
||||||
var downloadDocumentTask = Task.Run(async () =>
|
|
||||||
{
|
|
||||||
using var taskScope = _serviceScopeFactory.CreateScope();
|
|
||||||
var permissions = taskScope.ServiceProvider.GetRequiredService<PermissionServices>();
|
|
||||||
return await permissions.HasPermission(PermissionsMaster.DownloadDocument, employeeId);
|
|
||||||
});
|
|
||||||
|
|
||||||
await Task.WhenAll(viewDocumentTask, uploadDocumentTask, verifyDocumentTask, downloadDocumentTask);
|
|
||||||
|
|
||||||
var viewDocument = viewDocumentTask.Result;
|
|
||||||
var uploadDocument = uploadDocumentTask.Result;
|
|
||||||
var verifyDocument = verifyDocumentTask.Result;
|
|
||||||
var downloadDocument = downloadDocumentTask.Result;
|
|
||||||
|
|
||||||
if (viewDocument || uploadDocument || verifyDocument || downloadDocument)
|
|
||||||
{
|
|
||||||
response.Add(new MenuSectionApplicationVM
|
|
||||||
{
|
|
||||||
Id = Guid.Parse("443d6444-250b-4164-89fd-bcd7cedd9e43"),
|
|
||||||
Name = "Documents",
|
|
||||||
Available = true,
|
|
||||||
MobileLink = "/dashboard/document-main-page"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
response.Add(new MenuSectionApplicationVM
|
|
||||||
{
|
|
||||||
Id = Guid.Parse("7faddfe7-994b-4712-91c2-32ba44129d9b"),
|
|
||||||
Name = "Service Projects",
|
|
||||||
Available = true,
|
|
||||||
MobileLink = "/dashboard/service-projects"
|
|
||||||
});
|
|
||||||
response.Add(new MenuSectionApplicationVM
|
|
||||||
{
|
|
||||||
Id = Guid.Parse("5fab4b88-c9a0-417b-aca2-130980fdb0cf"),
|
|
||||||
Name = "Infra Projects",
|
|
||||||
Available = true,
|
|
||||||
MobileLink = "/dashboard/infra-projects"
|
|
||||||
});
|
|
||||||
|
|
||||||
// Step 3: Log success
|
|
||||||
response = response.Where(ms => !string.IsNullOrWhiteSpace(ms.MobileLink)).ToList();
|
|
||||||
_logger.LogInfo("Fetched sidebar menu successfully. Tenant: {TenantId}, EmployeeId: {EmployeeId}, SectionsReturned: {Count}",
|
|
||||||
tenantId, employeeId, menus.Count);
|
|
||||||
return Ok(ApiResponse<object>.SuccessResponse(response, "Sidebar menu fetched successfully", 200));
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
// Step 4: Handle unexpected errors
|
|
||||||
_logger.LogError(ex, "Error occurred while fetching sidebar menu. Tenant: {TenantId}, EmployeeId: {EmployeeId}",
|
|
||||||
tenantId, employeeId);
|
|
||||||
|
|
||||||
return StatusCode(500, ApiResponse<object>.ErrorResponse("Server Error", "An unexpected error occurred while fetching the sidebar menu.", 500));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -580,6 +580,9 @@ namespace Marco.Pms.Services.MappingProfiles
|
|||||||
|
|
||||||
CreateMap<WebSideMenuItem, WebSideMenuItemVM>();
|
CreateMap<WebSideMenuItem, WebSideMenuItemVM>();
|
||||||
|
|
||||||
|
CreateMap<CreateMobileSideMenuItemDto, MobileMenu>();
|
||||||
|
CreateMap<MobileMenu, MenuSectionApplicationVM>();
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region ======================================================= Directory =======================================================
|
#region ======================================================= Directory =======================================================
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user