Optimized the side menu APIs
This commit is contained in:
parent
0960702f0c
commit
852ddc7e02
@ -248,7 +248,6 @@ namespace Marco.Pms.DataAccess.Data
|
||||
public DbSet<InvoiceAttachmentType> InvoiceAttachmentTypes { get; set; }
|
||||
#endregion
|
||||
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
base.OnModelCreating(modelBuilder);
|
||||
|
||||
@ -9,6 +9,7 @@ namespace Marco.Pms.CacheHelper
|
||||
public class SidebarMenuHelper
|
||||
{
|
||||
private readonly IMongoCollection<MenuSection> _collection;
|
||||
private readonly IMongoCollection<WebMenuSection> _webCollection;
|
||||
private readonly ILogger<SidebarMenuHelper> _logger;
|
||||
|
||||
public SidebarMenuHelper(IConfiguration configuration, ILogger<SidebarMenuHelper> logger)
|
||||
@ -19,8 +20,75 @@ namespace Marco.Pms.CacheHelper
|
||||
var client = new MongoClient(mongoUrl);
|
||||
var database = client.GetDatabase(mongoUrl.DatabaseName);
|
||||
_collection = database.GetCollection<MenuSection>("Menus");
|
||||
_webCollection = database.GetCollection<WebMenuSection>("WebSideMenus");
|
||||
}
|
||||
|
||||
public async Task<List<WebMenuSection>> GetAllWebMenuSectionsAsync(Guid tenantId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var filter = Builders<WebMenuSection>.Filter.Eq(e => e.TenantId, tenantId);
|
||||
|
||||
var result = await _webCollection
|
||||
.Find(filter)
|
||||
.ToListAsync();
|
||||
if (result.Any())
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
tenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26");
|
||||
filter = Builders<WebMenuSection>.Filter.Eq(e => e.TenantId, tenantId);
|
||||
|
||||
result = await _webCollection
|
||||
.Find(filter)
|
||||
.ToListAsync();
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error occurred while fetching Web Menu Sections.");
|
||||
return new List<WebMenuSection>();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<WebMenuSection?> CreateWebMenuSectionAsync(WebMenuSection section)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _webCollection.InsertOneAsync(section);
|
||||
return section;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error occurred while adding Web Menu Section.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
public async Task<WebMenuSection?> AddWebMenuItemAsync(Guid sectionId, List<WebSideMenuItem> newItems)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
var filter = Builders<WebMenuSection>.Filter.Eq(s => s.Id, sectionId);
|
||||
|
||||
var update = Builders<WebMenuSection>.Update.PushEach(s => s.Items, newItems);
|
||||
|
||||
var result = await _webCollection.UpdateOneAsync(filter, update);
|
||||
|
||||
if (result.ModifiedCount > 0)
|
||||
{
|
||||
return await _webCollection.Find(s => s.Id == sectionId).FirstOrDefaultAsync();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error adding menu item.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
public async Task<MenuSection?> CreateMenuSectionAsync(MenuSection section)
|
||||
{
|
||||
try
|
||||
|
||||
19
Marco.Pms.Model/AppMenu/WebMenuSection.cs
Normal file
19
Marco.Pms.Model/AppMenu/WebMenuSection.cs
Normal file
@ -0,0 +1,19 @@
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Bson.Serialization.Attributes;
|
||||
|
||||
namespace Marco.Pms.Model.AppMenu
|
||||
{
|
||||
public class WebMenuSection
|
||||
{
|
||||
[BsonId]
|
||||
[BsonRepresentation(BsonType.String)]
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
|
||||
public string? Header { get; set; }
|
||||
public string? Title { get; set; }
|
||||
public List<WebSideMenuItem> Items { get; set; } = new List<WebSideMenuItem>();
|
||||
|
||||
[BsonRepresentation(BsonType.String)]
|
||||
public Guid TenantId { get; set; }
|
||||
}
|
||||
}
|
||||
22
Marco.Pms.Model/AppMenu/WebSideMenuItem.cs
Normal file
22
Marco.Pms.Model/AppMenu/WebSideMenuItem.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Bson.Serialization.Attributes;
|
||||
|
||||
namespace Marco.Pms.Model.AppMenu
|
||||
{
|
||||
public class WebSideMenuItem
|
||||
{
|
||||
[BsonId]
|
||||
[BsonRepresentation(BsonType.String)]
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
|
||||
[BsonRepresentation(BsonType.String)]
|
||||
public Guid? ParentMenuId { get; set; }
|
||||
public string? Text { get; set; }
|
||||
public string? Icon { get; set; }
|
||||
public bool Available { get; set; } = true;
|
||||
public string Link { get; set; } = string.Empty;
|
||||
|
||||
[BsonRepresentation(BsonType.String)]
|
||||
public List<Guid> PermissionIds { get; set; } = new List<Guid>();
|
||||
}
|
||||
}
|
||||
9
Marco.Pms.Model/Dtos/AppMenu/CreateWebMenuSectionDto.cs
Normal file
9
Marco.Pms.Model/Dtos/AppMenu/CreateWebMenuSectionDto.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace Marco.Pms.Model.Dtos.AppMenu
|
||||
{
|
||||
public class CreateWebMenuSectionDto
|
||||
{
|
||||
public required string Header { get; set; }
|
||||
public required string Title { get; set; }
|
||||
public List<CreateWebSideMenuItemDto> Items { get; set; } = new List<CreateWebSideMenuItemDto>();
|
||||
}
|
||||
}
|
||||
15
Marco.Pms.Model/Dtos/AppMenu/CreateWebSideMenuItemDto.cs
Normal file
15
Marco.Pms.Model/Dtos/AppMenu/CreateWebSideMenuItemDto.cs
Normal file
@ -0,0 +1,15 @@
|
||||
namespace Marco.Pms.Model.Dtos.AppMenu
|
||||
{
|
||||
public class CreateWebSideMenuItemDto
|
||||
{
|
||||
public Guid? Id { get; set; }
|
||||
public Guid? ParentMenuId { get; set; }
|
||||
public string? Text { get; set; }
|
||||
public string? Icon { get; set; }
|
||||
public bool Available { get; set; } = true;
|
||||
public string Link { get; set; } = string.Empty;
|
||||
|
||||
// Changed from string → List<string>
|
||||
public List<Guid> PermissionIds { get; set; } = new List<Guid>();
|
||||
}
|
||||
}
|
||||
12
Marco.Pms.Model/ViewModels/AppMenu/WebMenuSectionVM.cs
Normal file
12
Marco.Pms.Model/ViewModels/AppMenu/WebMenuSectionVM.cs
Normal file
@ -0,0 +1,12 @@
|
||||
namespace Marco.Pms.Model.ViewModels.AppMenu
|
||||
{
|
||||
public class WebMenuSectionVM
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
|
||||
public string? Header { get; set; }
|
||||
public string? Name { get; set; }
|
||||
public List<WebSideMenuItemVM> Items { get; set; } = new List<WebSideMenuItemVM>();
|
||||
public Guid TenantId { get; set; }
|
||||
}
|
||||
}
|
||||
12
Marco.Pms.Model/ViewModels/AppMenu/WebSideMenuItemVM.cs
Normal file
12
Marco.Pms.Model/ViewModels/AppMenu/WebSideMenuItemVM.cs
Normal file
@ -0,0 +1,12 @@
|
||||
namespace Marco.Pms.Model.ViewModels.AppMenu
|
||||
{
|
||||
public class WebSideMenuItemVM
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string? Name { get; set; }
|
||||
public string? Icon { get; set; }
|
||||
public bool Available { get; set; }
|
||||
public string? Link { get; set; }
|
||||
public List<WebSideMenuItemVM> Submenu { get; set; } = new List<WebSideMenuItemVM>();
|
||||
}
|
||||
}
|
||||
@ -53,16 +53,123 @@ namespace Marco.Pms.Services.Controllers
|
||||
tenantId = userHelper.GetTenantId();
|
||||
}
|
||||
|
||||
[HttpGet("get/menu")]
|
||||
public async Task<IActionResult> GetAppSideBarMenuAsync()
|
||||
{
|
||||
// Correlation ID for distributed tracing across services and logs.
|
||||
var correlationId = HttpContext.TraceIdentifier;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new sidebar menu section for the tenant.
|
||||
/// Only accessible by root users or for the super tenant.
|
||||
/// </summary>
|
||||
/// <param name="menuSectionDto">The data for the new menu section.</param>
|
||||
/// <returns>HTTP response with result of the operation.</returns>
|
||||
_logger.LogInfo("GetAppSideBarMenuAsync started. TenantId: {TenantId}, CorrelationId: {CorrelationId}", tenantId, correlationId);
|
||||
|
||||
try
|
||||
{
|
||||
// Step 1: Validate tenant context
|
||||
if (tenantId == Guid.Empty)
|
||||
{
|
||||
_logger.LogWarning("GetAppSideBarMenuAsync rejected: Invalid TenantId. CorrelationId: {CorrelationId}", correlationId);
|
||||
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid TenantId", "The tenant identifier provided is invalid or missing.", 400));
|
||||
}
|
||||
|
||||
// Step 2: Resolve current employee context for permission checks
|
||||
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
|
||||
var employeeId = loggedInEmployee.Id;
|
||||
|
||||
// Step 3: Create scoped permission service (avoid capturing scoped services in controller)
|
||||
using var scope = _serviceScopeFactory.CreateScope();
|
||||
var permissions = scope.ServiceProvider.GetRequiredService<PermissionServices>();
|
||||
|
||||
_logger.LogDebug("GetAppSideBarMenuAsync resolved employee context. EmployeeId: {EmployeeId}, TenantId: {TenantId}, CorrelationId: {CorrelationId}",
|
||||
employeeId, tenantId, correlationId);
|
||||
|
||||
// Step 4: Fetch all menu sections for tenant (null-safe check)
|
||||
var menus = await _sideBarMenuHelper.GetAllWebMenuSectionsAsync(tenantId);
|
||||
if (menus == null || !menus.Any())
|
||||
{
|
||||
_logger.LogInfo("GetAppSideBarMenuAsync: No menu sections found. TenantId: {TenantId}, CorrelationId: {CorrelationId}",
|
||||
tenantId, correlationId);
|
||||
return Ok(ApiResponse<object>.SuccessResponse(new List<WebMenuSectionVM>(), "No sidebar menu sections configured for this tenant.", 200));
|
||||
}
|
||||
|
||||
_logger.LogDebug("GetAppSideBarMenuAsync loaded {MenuSectionCount} menu sections. TenantId: {TenantId}, CorrelationId: {CorrelationId}",
|
||||
menus.Count, tenantId, correlationId);
|
||||
|
||||
// Step 5: Filter and build menu response with permission checks
|
||||
var response = new List<WebMenuSectionVM>();
|
||||
|
||||
foreach (var menuSection in menus)
|
||||
{
|
||||
var sectionVM = _mapper.Map<WebMenuSectionVM>(menuSection);
|
||||
if (menuSection.Items == null)
|
||||
{
|
||||
_logger.LogDebug("Skipping menu section with null items. SectionId: {SectionId}, TenantId: {TenantId}", menuSection.Id, tenantId);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
foreach (var menuItem in menuSection.Items)
|
||||
{
|
||||
// Skip items without permission check if required
|
||||
if (menuItem.PermissionIds.Any())
|
||||
{
|
||||
var hasPermission = await permissions.HasPermissionAny(menuItem.PermissionIds, employeeId);
|
||||
if (!hasPermission)
|
||||
{
|
||||
_logger.LogDebug("Menu item access denied due to missing permissions. ItemId: {ItemId}, EmployeeId: {EmployeeId}, TenantId: {TenantId}",
|
||||
menuItem.Id, employeeId, tenantId);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
var itemVM = _mapper.Map<WebSideMenuItemVM>(menuItem);
|
||||
if (menuItem.ParentMenuId.HasValue)
|
||||
{
|
||||
sectionVM.Items.Where(i => i.Id == menuItem.ParentMenuId.Value).FirstOrDefault()?.Submenu.Add(itemVM);
|
||||
}
|
||||
else
|
||||
{
|
||||
sectionVM.Items.Add(itemVM);
|
||||
}
|
||||
}
|
||||
|
||||
// Only include sections with at least one accessible item
|
||||
if (sectionVM.Items.Any())
|
||||
{
|
||||
response.Add(sectionVM);
|
||||
}
|
||||
}
|
||||
|
||||
_logger.LogInfo("GetAppSideBarMenuAsync completed successfully. TenantId: {TenantId}, EmployeeId: {EmployeeId}, OriginalSections: {OriginalCount}, FilteredSections: {FilteredCount}, CorrelationId: {CorrelationId}",
|
||||
tenantId, employeeId, menus.Count, response.Count, correlationId);
|
||||
|
||||
return Ok(ApiResponse<object>.SuccessResponse(response, $"Sidebar menu fetched successfully. {response.Count} sections returned.", 200));
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
_logger.LogWarning("GetAppSideBarMenuAsync cancelled. TenantId: {TenantId}, CorrelationId: {CorrelationId}",
|
||||
tenantId, correlationId);
|
||||
|
||||
return StatusCode(499, ApiResponse<object>.ErrorResponse("Request Cancelled", "The request was cancelled by the client.", 499));
|
||||
}
|
||||
catch (UnauthorizedAccessException ex)
|
||||
{
|
||||
_logger.LogError(ex, "GetAppSideBarMenuAsync authorization failed. TenantId: {TenantId}, CorrelationId: {CorrelationId}",
|
||||
tenantId, correlationId);
|
||||
|
||||
return StatusCode(403, (ApiResponse<object>.ErrorResponse("Access Denied", "Insufficient permissions to access menu sections.", 403)));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Unexpected error in GetAppSideBarMenuAsync. TenantId: {TenantId}, CorrelationId: {CorrelationId}",
|
||||
tenantId, correlationId);
|
||||
|
||||
return StatusCode(500, ApiResponse<object>.ErrorResponse("Internal Server Error", "An unexpected error occurred while fetching the sidebar menu.", 500));
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("add/sidebar/menu-section")]
|
||||
public async Task<IActionResult> CreateAppSideBarMenu([FromBody] CreateMenuSectionDto menuSectionDto)
|
||||
public async Task<IActionResult> CreateAppSideBarMenuAsync([FromBody] CreateWebMenuSectionDto model)
|
||||
{
|
||||
// Step 1: Fetch logged-in user
|
||||
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
@ -76,17 +183,17 @@ namespace Marco.Pms.Services.Controllers
|
||||
}
|
||||
|
||||
// Step 3: Map DTO to entity
|
||||
var sideMenuSection = _mapper.Map<MenuSection>(menuSectionDto);
|
||||
var sideMenuSection = _mapper.Map<WebMenuSection>(model);
|
||||
sideMenuSection.TenantId = tenantId;
|
||||
|
||||
try
|
||||
{
|
||||
// Step 4: Save entity using helper
|
||||
sideMenuSection = await _sideBarMenuHelper.CreateMenuSectionAsync(sideMenuSection);
|
||||
sideMenuSection = await _sideBarMenuHelper.CreateWebMenuSectionAsync(sideMenuSection);
|
||||
|
||||
if (sideMenuSection == null)
|
||||
{
|
||||
_logger.LogWarning("Failed to create sidebar menu section. Tenant: {TenantId}, Request: {@MenuSectionDto}", tenantId, menuSectionDto);
|
||||
_logger.LogWarning("Failed to create sidebar menu section. Tenant: {TenantId}, Request: {@MenuSectionDto}", tenantId, model);
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid MenuSection", 400));
|
||||
}
|
||||
|
||||
@ -100,86 +207,14 @@ namespace Marco.Pms.Services.Controllers
|
||||
{
|
||||
// Step 6: Handle and log unexpected server errors
|
||||
_logger.LogError(ex, "Unexpected error occurred while creating sidebar menu. Tenant: {TenantId}, EmployeeId: {EmployeeId}, Request: {@MenuSectionDto}",
|
||||
tenantId, loggedInEmployee.Id, menuSectionDto);
|
||||
tenantId, loggedInEmployee.Id, model);
|
||||
|
||||
return StatusCode(500, ApiResponse<object>.ErrorResponse("Server Error", "An unexpected error occurred.", 500));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates an existing sidebar menu section for the tenant.
|
||||
/// Only accessible by root users or for the super tenant.
|
||||
/// </summary>
|
||||
/// <param name="sectionId">The unique identifier of the section to update.</param>
|
||||
/// <param name="updatedSection">The updated data for the sidebar menu section.</param>
|
||||
/// <returns>HTTP response with the result of the operation.</returns>
|
||||
|
||||
[HttpPut("edit/sidebar/menu-section/{sectionId}")]
|
||||
public async Task<IActionResult> UpdateMenuSection(Guid sectionId, [FromBody] UpdateMenuSectionDto updatedSection)
|
||||
{
|
||||
// Step 1: Fetch logged-in user
|
||||
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
var isRootUser = loggedInEmployee.ApplicationUser?.IsRootUser ?? false;
|
||||
|
||||
// Step 2: Authorization check
|
||||
if (!isRootUser && tenantId != superTenantId)
|
||||
{
|
||||
_logger.LogWarning("Access denied: User {UserId} attempted to update sidebar menu section {SectionId} in Tenant {TenantId}",
|
||||
loggedInEmployee.Id, sectionId, tenantId);
|
||||
|
||||
return StatusCode(403, ApiResponse<object>.ErrorResponse("Access Denied", "User does not have permission.", 403));
|
||||
}
|
||||
|
||||
// Step 3: Validate request
|
||||
if (sectionId == Guid.Empty || sectionId != updatedSection.Id)
|
||||
{
|
||||
_logger.LogWarning("Invalid update request. Tenant: {TenantId}, SectionId: {SectionId}, PayloadId: {PayloadId}, UserId: {UserId}",
|
||||
tenantId, sectionId, updatedSection.Id, loggedInEmployee.Id);
|
||||
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid section ID or mismatched payload.", 400));
|
||||
}
|
||||
|
||||
// Step 4: Map DTO to entity
|
||||
var menuSectionEntity = _mapper.Map<MenuSection>(updatedSection);
|
||||
|
||||
try
|
||||
{
|
||||
// Step 5: Perform update operation
|
||||
var result = await _sideBarMenuHelper.UpdateMenuSectionAsync(sectionId, menuSectionEntity);
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
_logger.LogWarning("Menu section not found for update. SectionId: {SectionId}, TenantId: {TenantId}, UserId: {UserId}",
|
||||
sectionId, tenantId, loggedInEmployee.Id);
|
||||
return NotFound(ApiResponse<object>.ErrorResponse("Menu section not found", 404));
|
||||
}
|
||||
|
||||
// Step 6: Successful update
|
||||
_logger.LogInfo("Menu section updated successfully. SectionId: {SectionId}, TenantId: {TenantId}, UserId: {UserId}",
|
||||
sectionId, tenantId, loggedInEmployee.Id);
|
||||
|
||||
return Ok(ApiResponse<object>.SuccessResponse(result, "Menu section updated successfully"));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Step 7: Unexpected server error
|
||||
_logger.LogError(ex, "Failed to update menu section. SectionId: {SectionId}, TenantId: {TenantId}, UserId: {UserId}, Payload: {@UpdatedSection}",
|
||||
sectionId, tenantId, loggedInEmployee.Id, updatedSection);
|
||||
|
||||
return StatusCode(500, ApiResponse<object>.ErrorResponse("Server error", "An unexpected error occurred while updating the menu section.", 500));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new menu item to an existing sidebar menu section.
|
||||
/// Only accessible by root users or for the super tenant.
|
||||
/// </summary>
|
||||
/// <param name="sectionId">The unique identifier of the section the item will be added to.</param>
|
||||
/// <param name="newItemDto">The details of the new menu item.</param>
|
||||
/// <returns>HTTP response with the result of the operation.</returns>
|
||||
|
||||
[HttpPost("add/sidebar/menus/{sectionId}/items")]
|
||||
public async Task<IActionResult> AddMenuItem(Guid sectionId, [FromBody] CreateMenuItemDto newItemDto)
|
||||
[HttpPost("add/sidebar/section/{sectionId}/items")]
|
||||
public async Task<IActionResult> AddMenuItemAsync(Guid sectionId, [FromBody] List<CreateWebSideMenuItemDto> model)
|
||||
{
|
||||
// Step 1: Fetch logged-in user
|
||||
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
@ -195,7 +230,7 @@ namespace Marco.Pms.Services.Controllers
|
||||
}
|
||||
|
||||
// Step 3: Input validation
|
||||
if (sectionId == Guid.Empty || newItemDto == null)
|
||||
if (sectionId == Guid.Empty || model == null)
|
||||
{
|
||||
_logger.LogWarning("Invalid AddMenuItem request. Tenant: {TenantId}, SectionId: {SectionId}, UserId: {UserId}",
|
||||
tenantId, sectionId, loggedInEmployee.Id);
|
||||
@ -206,10 +241,10 @@ namespace Marco.Pms.Services.Controllers
|
||||
try
|
||||
{
|
||||
// Step 4: Map DTO to entity
|
||||
var menuItemEntity = _mapper.Map<MenuItem>(newItemDto);
|
||||
var menuItemEntity = _mapper.Map<List<WebSideMenuItem>>(model);
|
||||
|
||||
// Step 5: Perform Add operation
|
||||
var result = await _sideBarMenuHelper.AddMenuItemAsync(sectionId, menuItemEntity);
|
||||
var result = await _sideBarMenuHelper.AddWebMenuItemAsync(sectionId, menuItemEntity);
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
@ -229,318 +264,12 @@ namespace Marco.Pms.Services.Controllers
|
||||
{
|
||||
// Step 7: Handle unexpected errors
|
||||
_logger.LogError(ex, "Error occurred while adding menu item. SectionId: {SectionId}, TenantId: {TenantId}, UserId: {UserId}, Payload: {@NewItemDto}",
|
||||
sectionId, tenantId, loggedInEmployee.Id, newItemDto);
|
||||
sectionId, tenantId, loggedInEmployee.Id, model);
|
||||
|
||||
return StatusCode(500, ApiResponse<object>.ErrorResponse("Server error", "An unexpected error occurred while adding the menu item.", 500));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates an existing menu item inside a sidebar menu section.
|
||||
/// Only accessible by root users or within the super tenant.
|
||||
/// </summary>
|
||||
/// <param name="sectionId">The ID of the sidebar menu section.</param>
|
||||
/// <param name="itemId">The ID of the menu item to update.</param>
|
||||
/// <param name="updatedMenuItem">The updated menu item details.</param>
|
||||
/// <returns>HTTP response with the result of the update operation.</returns>
|
||||
|
||||
[HttpPut("edit/sidebar/{sectionId}/items/{itemId}")]
|
||||
public async Task<IActionResult> UpdateMenuItem(Guid sectionId, Guid itemId, [FromBody] UpdateMenuItemDto updatedMenuItem)
|
||||
{
|
||||
// Step 1: Fetch logged-in user
|
||||
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
var isRootUser = loggedInEmployee.ApplicationUser?.IsRootUser ?? false;
|
||||
|
||||
// Step 2: Authorization check
|
||||
if (!isRootUser && tenantId != superTenantId)
|
||||
{
|
||||
_logger.LogWarning("Access denied: User {UserId} attempted to update menu item {ItemId} in Section {SectionId}, Tenant {TenantId}",
|
||||
loggedInEmployee.Id, itemId, sectionId, tenantId);
|
||||
|
||||
return StatusCode(403, ApiResponse<object>.ErrorResponse("Access Denied", "User does not have permission.", 403));
|
||||
}
|
||||
|
||||
// Step 3: Input validation
|
||||
if (sectionId == Guid.Empty || itemId == Guid.Empty || updatedMenuItem == null || updatedMenuItem.Id != itemId)
|
||||
{
|
||||
_logger.LogWarning("Invalid UpdateMenuItem request. Tenant: {TenantId}, SectionId: {SectionId}, ItemId: {ItemId}, UserId: {UserId}",
|
||||
tenantId, sectionId, itemId, loggedInEmployee.Id);
|
||||
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid section ID, item ID, or menu item payload.", 400));
|
||||
}
|
||||
|
||||
// Step 4: Map DTO to entity
|
||||
var menuItemEntity = _mapper.Map<MenuItem>(updatedMenuItem);
|
||||
|
||||
try
|
||||
{
|
||||
// Step 5: Perform update operation
|
||||
var result = await _sideBarMenuHelper.UpdateMenuItemAsync(sectionId, itemId, menuItemEntity);
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
_logger.LogWarning("Menu item not found or update failed. Tenant: {TenantId}, SectionId: {SectionId}, ItemId: {ItemId}, UserId: {UserId}",
|
||||
tenantId, sectionId, itemId, loggedInEmployee.Id);
|
||||
return NotFound(ApiResponse<object>.ErrorResponse("Menu item not found or update failed.", 404));
|
||||
}
|
||||
|
||||
// Step 6: Success log
|
||||
_logger.LogInfo("Menu item updated successfully. Tenant: {TenantId}, SectionId: {SectionId}, ItemId: {ItemId}, UserId: {UserId}",
|
||||
tenantId, sectionId, itemId, loggedInEmployee.Id);
|
||||
|
||||
return Ok(ApiResponse<object>.SuccessResponse(result, "Sidebar menu item updated successfully."));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// ✅ Step 7: Handle server errors
|
||||
_logger.LogError(ex, "Error occurred while updating menu item. Tenant: {TenantId}, SectionId: {SectionId}, ItemId: {ItemId}, UserId: {UserId}, Payload: {@UpdatedMenuItem}",
|
||||
tenantId, sectionId, itemId, loggedInEmployee.Id, updatedMenuItem);
|
||||
|
||||
return StatusCode(
|
||||
500,
|
||||
ApiResponse<object>.ErrorResponse("Server Error", "An unexpected error occurred while updating the menu item.", 500)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new sub-menu item to an existing menu item inside a sidebar menu section.
|
||||
/// Only accessible by root users or within the super tenant.
|
||||
/// </summary>
|
||||
/// <param name="sectionId">The ID of the sidebar menu section.</param>
|
||||
/// <param name="itemId">The ID of the parent menu item.</param>
|
||||
/// <param name="newSubItem">The details of the new sub-menu item.</param>
|
||||
/// <returns>HTTP response with the result of the add operation.</returns>
|
||||
|
||||
[HttpPost("add/sidebar/menus/{sectionId}/items/{itemId}/subitems")]
|
||||
public async Task<IActionResult> AddSubMenuItem(Guid sectionId, Guid itemId, [FromBody] CreateSubMenuItemDto newSubItem)
|
||||
{
|
||||
// Step 1: Fetch logged-in user
|
||||
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
var isRootUser = loggedInEmployee.ApplicationUser?.IsRootUser ?? false;
|
||||
|
||||
// Step 2: Authorization check
|
||||
if (!isRootUser && tenantId != superTenantId)
|
||||
{
|
||||
_logger.LogWarning("Access denied: User {UserId} attempted to add sub-menu item in Section {SectionId}, MenuItem {ItemId}, Tenant {TenantId}",
|
||||
loggedInEmployee.Id, sectionId, itemId, tenantId);
|
||||
|
||||
return StatusCode(403, ApiResponse<object>.ErrorResponse("Access Denied", "User does not have permission.", 403));
|
||||
}
|
||||
|
||||
// Step 3: Validate input
|
||||
if (sectionId == Guid.Empty || itemId == Guid.Empty || newSubItem == null)
|
||||
{
|
||||
_logger.LogWarning("Invalid AddSubMenuItem request. Tenant: {TenantId}, SectionId: {SectionId}, ItemId: {ItemId}, UserId: {UserId}",
|
||||
tenantId, sectionId, itemId, loggedInEmployee.Id);
|
||||
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid section ID, item ID, or sub-menu item payload.", 400));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Step 4: Map DTO to entity
|
||||
var subMenuItemEntity = _mapper.Map<SubMenuItem>(newSubItem);
|
||||
|
||||
// Step 5: Perform add operation
|
||||
var result = await _sideBarMenuHelper.AddSubMenuItemAsync(sectionId, itemId, subMenuItemEntity);
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
_logger.LogWarning("Parent menu item not found. Failed to add sub-menu item. Tenant: {TenantId}, SectionId: {SectionId}, ItemId: {ItemId}, UserId: {UserId}",
|
||||
tenantId, sectionId, itemId, loggedInEmployee.Id);
|
||||
|
||||
return NotFound(ApiResponse<object>.ErrorResponse("Parent menu item not found.", 404));
|
||||
}
|
||||
|
||||
// Step 6: Success logging
|
||||
_logger.LogInfo("Sub-menu item added successfully. Tenant: {TenantId}, SectionId: {SectionId}, ParentItemId: {ItemId}, SubItemId: {SubItemId}, UserId: {UserId}",
|
||||
tenantId, sectionId, itemId, result.Id, loggedInEmployee.Id);
|
||||
|
||||
return Ok(ApiResponse<object>.SuccessResponse(result, "Sub-menu item added successfully."));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Step 7: Handle unexpected errors
|
||||
_logger.LogError(ex, "Error occurred while adding sub-menu item. Tenant: {TenantId}, SectionId: {SectionId}, ItemId: {ItemId}, UserId: {UserId}, Payload: {@NewSubItem}",
|
||||
tenantId, sectionId, itemId, loggedInEmployee.Id, newSubItem);
|
||||
|
||||
return StatusCode(500, ApiResponse<object>.ErrorResponse("Server Error", "An unexpected error occurred while adding the sub-menu item.", 500));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates an existing sub-menu item inside a sidebar menu section.
|
||||
/// Only accessible by root users or within the super tenant.
|
||||
/// </summary>
|
||||
/// <param name="sectionId">The ID of the sidebar menu section.</param>
|
||||
/// <param name="itemId">The ID of the parent menu item.</param>
|
||||
/// <param name="subItemId">The ID of the sub-menu item to update.</param>
|
||||
/// <param name="updatedSubMenuItem">The updated sub-menu item details.</param>
|
||||
/// <returns>HTTP response with the result of the update operation.</returns>
|
||||
|
||||
[HttpPut("edit/sidebar/{sectionId}/items/{itemId}/subitems/{subItemId}")]
|
||||
public async Task<IActionResult> UpdateSubmenuItem(Guid sectionId, Guid itemId, Guid subItemId, [FromBody] UpdateSubMenuItemDto updatedSubMenuItem)
|
||||
{
|
||||
// Step 1: Fetch logged-in user
|
||||
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
var isRootUser = loggedInEmployee.ApplicationUser?.IsRootUser ?? false;
|
||||
|
||||
// Step 2: Authorization check
|
||||
if (!isRootUser && tenantId != superTenantId)
|
||||
{
|
||||
_logger.LogWarning("Access denied: User {UserId} attempted to update sub-menu {SubItemId} under MenuItem {ItemId} in Section {SectionId}, Tenant {TenantId}",
|
||||
loggedInEmployee.Id, subItemId, itemId, sectionId, tenantId);
|
||||
|
||||
return StatusCode(403, ApiResponse<object>.ErrorResponse("Access Denied", "User does not have permission.", 403));
|
||||
}
|
||||
|
||||
// Step 3: Input validation
|
||||
if (sectionId == Guid.Empty || itemId == Guid.Empty || subItemId == Guid.Empty || updatedSubMenuItem == null || updatedSubMenuItem.Id != subItemId)
|
||||
{
|
||||
_logger.LogWarning("Invalid UpdateSubMenuItem request. Tenant: {TenantId}, SectionId: {SectionId}, ItemId: {ItemId}, SubItemId: {SubItemId}, UserId: {UserId}",
|
||||
tenantId, sectionId, itemId, subItemId, loggedInEmployee.Id);
|
||||
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid section ID, menu item ID, sub-item ID, or payload mismatch.", 400));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Step 4: Map DTO to entity
|
||||
var subMenuEntity = _mapper.Map<SubMenuItem>(updatedSubMenuItem);
|
||||
|
||||
// Step 5: Perform update operation
|
||||
var result = await _sideBarMenuHelper.UpdateSubmenuItemAsync(sectionId, itemId, subItemId, subMenuEntity);
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
_logger.LogWarning("Sub-menu item not found or update failed. Tenant: {TenantId}, SectionId: {SectionId}, ItemId: {ItemId}, SubItemId: {SubItemId}, UserId: {UserId}",
|
||||
tenantId, sectionId, itemId, subItemId, loggedInEmployee.Id);
|
||||
|
||||
return NotFound(ApiResponse<object>.ErrorResponse("Sub-menu item not found.", 404));
|
||||
}
|
||||
|
||||
// Step 6: Log success
|
||||
_logger.LogInfo("Sub-menu item updated successfully. Tenant: {TenantId}, SectionId: {SectionId}, MenuItemId: {ItemId}, SubItemId: {SubItemId}, UserId: {UserId}",
|
||||
tenantId, sectionId, itemId, subItemId, loggedInEmployee.Id);
|
||||
|
||||
return Ok(ApiResponse<object>.SuccessResponse(result, "Sub-menu item updated successfully."));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Step 7: Handle unexpected errors
|
||||
_logger.LogError(ex, "Error occurred while updating sub-menu item. Tenant: {TenantId}, SectionId: {SectionId}, MenuItemId: {ItemId}, SubItemId: {SubItemId}, UserId: {UserId}, Payload: {@UpdatedSubMenuItem}",
|
||||
tenantId, sectionId, itemId, subItemId, loggedInEmployee.Id, updatedSubMenuItem);
|
||||
|
||||
return StatusCode(500, ApiResponse<object>.ErrorResponse("Server Error", "An unexpected error occurred while updating the sub-menu item.", 500));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetches the sidebar menu for the current tenant and filters items based on employee permissions.
|
||||
/// </summary>
|
||||
/// <returns>The sidebar menu with only the items/sub-items the employee has access to.</returns>
|
||||
|
||||
[HttpGet("get/menu")]
|
||||
public async Task<IActionResult> GetAppSideBarMenu()
|
||||
{
|
||||
// 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
|
||||
};
|
||||
}
|
||||
|
||||
foreach (var menu in menus)
|
||||
{
|
||||
var allowedItems = new List<MenuItem>();
|
||||
|
||||
foreach (var item in menu.Items)
|
||||
{
|
||||
// --- Item permission check ---
|
||||
if (!item.PermissionIds.Any())
|
||||
{
|
||||
allowedItems.Add(item);
|
||||
}
|
||||
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())
|
||||
{
|
||||
allowedSubmenus.Add(subItem);
|
||||
continue;
|
||||
}
|
||||
|
||||
var subMenuPermissionIds = subItem.PermissionIds
|
||||
.Select(Guid.Parse)
|
||||
.ToList();
|
||||
|
||||
bool isSubItemAllowed = await _permissions.HasPermissionAny(subMenuPermissionIds, employeeId);
|
||||
|
||||
if (isSubItemAllowed)
|
||||
{
|
||||
allowedSubmenus.Add(subItem);
|
||||
}
|
||||
}
|
||||
|
||||
// Replace with filtered submenus
|
||||
item.Submenu = allowedSubmenus;
|
||||
}
|
||||
|
||||
allowedItems.Add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Replace with filtered items
|
||||
menu.Items = allowedItems;
|
||||
}
|
||||
|
||||
// Step 3: Log success
|
||||
_logger.LogInfo("Fetched sidebar menu successfully. Tenant: {TenantId}, EmployeeId: {EmployeeId}, SectionsReturned: {Count}",
|
||||
tenantId, employeeId, menus.Count);
|
||||
|
||||
var response = _mapper.Map<List<MenuSectionVM>>(menus);
|
||||
return Ok(ApiResponse<object>.SuccessResponse(response, "Sidebar menu fetched successfully"));
|
||||
}
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the master menu list based on enabled features for the current tenant.
|
||||
/// </summary>
|
||||
|
||||
@ -34,6 +34,7 @@ using Marco.Pms.Model.ServiceProject;
|
||||
using Marco.Pms.Model.TenantModels;
|
||||
using Marco.Pms.Model.TenantModels.MongoDBModel;
|
||||
using Marco.Pms.Model.ViewModels.Activities;
|
||||
using Marco.Pms.Model.ViewModels.AppMenu;
|
||||
using Marco.Pms.Model.ViewModels.Collection;
|
||||
using Marco.Pms.Model.ViewModels.Directory;
|
||||
using Marco.Pms.Model.ViewModels.DocumentManager;
|
||||
@ -564,19 +565,38 @@ namespace Marco.Pms.Services.MappingProfiles
|
||||
|
||||
#region ======================================================= AppMenu =======================================================
|
||||
CreateMap<CreateMenuSectionDto, MenuSection>();
|
||||
CreateMap<CreateWebMenuSectionDto, WebMenuSection>();
|
||||
CreateMap<UpdateMenuSectionDto, MenuSection>();
|
||||
CreateMap<MenuSection, MenuSectionVM>()
|
||||
.ForMember(
|
||||
dest => dest.Name,
|
||||
opt => opt.MapFrom(src => src.Title));
|
||||
|
||||
CreateMap<WebMenuSection, WebMenuSectionVM>()
|
||||
.ForMember(
|
||||
dest => dest.Name,
|
||||
opt => opt.MapFrom(src => src.Title))
|
||||
.ForMember(
|
||||
dest => dest.Items,
|
||||
opt => opt.MapFrom(src => new List<WebSideMenuItem>()));
|
||||
|
||||
CreateMap<CreateMenuItemDto, MenuItem>();
|
||||
CreateMap<CreateWebSideMenuItemDto, WebSideMenuItem>()
|
||||
.ForMember(
|
||||
dest => dest.Id,
|
||||
opt => opt.MapFrom(src => src.Id.HasValue ? src.Id.Value : Guid.NewGuid()));
|
||||
|
||||
CreateMap<UpdateMenuItemDto, MenuItem>();
|
||||
CreateMap<MenuItem, MenuItemVM>()
|
||||
.ForMember(
|
||||
dest => dest.Name,
|
||||
opt => opt.MapFrom(src => src.Text));
|
||||
|
||||
CreateMap<WebSideMenuItem, WebSideMenuItemVM>()
|
||||
.ForMember(
|
||||
dest => dest.Name,
|
||||
opt => opt.MapFrom(src => src.Text));
|
||||
|
||||
CreateMap<CreateSubMenuItemDto, SubMenuItem>();
|
||||
CreateMap<UpdateSubMenuItemDto, SubMenuItem>();
|
||||
CreateMap<SubMenuItem, SubMenuItemVM>()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user