diff --git a/Marco.Pms.DataAccess/Data/ApplicationDbContext.cs b/Marco.Pms.DataAccess/Data/ApplicationDbContext.cs index 4dc5793..af5b8ab 100644 --- a/Marco.Pms.DataAccess/Data/ApplicationDbContext.cs +++ b/Marco.Pms.DataAccess/Data/ApplicationDbContext.cs @@ -248,7 +248,6 @@ namespace Marco.Pms.DataAccess.Data public DbSet InvoiceAttachmentTypes { get; set; } #endregion - protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); diff --git a/Marco.Pms.Helpers/Utility/SidebarMenuHelper.cs b/Marco.Pms.Helpers/Utility/SidebarMenuHelper.cs index 5aa761d..9a17110 100644 --- a/Marco.Pms.Helpers/Utility/SidebarMenuHelper.cs +++ b/Marco.Pms.Helpers/Utility/SidebarMenuHelper.cs @@ -9,6 +9,7 @@ namespace Marco.Pms.CacheHelper public class SidebarMenuHelper { private readonly IMongoCollection _collection; + private readonly IMongoCollection _webCollection; private readonly ILogger _logger; public SidebarMenuHelper(IConfiguration configuration, ILogger logger) @@ -19,8 +20,75 @@ namespace Marco.Pms.CacheHelper var client = new MongoClient(mongoUrl); var database = client.GetDatabase(mongoUrl.DatabaseName); _collection = database.GetCollection("Menus"); + _webCollection = database.GetCollection("WebSideMenus"); } + public async Task> GetAllWebMenuSectionsAsync(Guid tenantId) + { + try + { + var filter = Builders.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.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(); + } + } + + public async Task 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 AddWebMenuItemAsync(Guid sectionId, List newItems) + { + try + { + + var filter = Builders.Filter.Eq(s => s.Id, sectionId); + + var update = Builders.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 CreateMenuSectionAsync(MenuSection section) { try diff --git a/Marco.Pms.Model/AppMenu/WebMenuSection.cs b/Marco.Pms.Model/AppMenu/WebMenuSection.cs new file mode 100644 index 0000000..121d918 --- /dev/null +++ b/Marco.Pms.Model/AppMenu/WebMenuSection.cs @@ -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 Items { get; set; } = new List(); + + [BsonRepresentation(BsonType.String)] + public Guid TenantId { get; set; } + } +} diff --git a/Marco.Pms.Model/AppMenu/WebSideMenuItem.cs b/Marco.Pms.Model/AppMenu/WebSideMenuItem.cs new file mode 100644 index 0000000..f45bf6f --- /dev/null +++ b/Marco.Pms.Model/AppMenu/WebSideMenuItem.cs @@ -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 PermissionIds { get; set; } = new List(); + } +} diff --git a/Marco.Pms.Model/Dtos/AppMenu/CreateWebMenuSectionDto.cs b/Marco.Pms.Model/Dtos/AppMenu/CreateWebMenuSectionDto.cs new file mode 100644 index 0000000..b5d432b --- /dev/null +++ b/Marco.Pms.Model/Dtos/AppMenu/CreateWebMenuSectionDto.cs @@ -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 Items { get; set; } = new List(); + } +} diff --git a/Marco.Pms.Model/Dtos/AppMenu/CreateWebSideMenuItemDto.cs b/Marco.Pms.Model/Dtos/AppMenu/CreateWebSideMenuItemDto.cs new file mode 100644 index 0000000..e07244c --- /dev/null +++ b/Marco.Pms.Model/Dtos/AppMenu/CreateWebSideMenuItemDto.cs @@ -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 + public List PermissionIds { get; set; } = new List(); + } +} diff --git a/Marco.Pms.Model/ViewModels/AppMenu/WebMenuSectionVM.cs b/Marco.Pms.Model/ViewModels/AppMenu/WebMenuSectionVM.cs new file mode 100644 index 0000000..d233cf8 --- /dev/null +++ b/Marco.Pms.Model/ViewModels/AppMenu/WebMenuSectionVM.cs @@ -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 Items { get; set; } = new List(); + public Guid TenantId { get; set; } + } +} diff --git a/Marco.Pms.Model/ViewModels/AppMenu/WebSideMenuItemVM.cs b/Marco.Pms.Model/ViewModels/AppMenu/WebSideMenuItemVM.cs new file mode 100644 index 0000000..11446fa --- /dev/null +++ b/Marco.Pms.Model/ViewModels/AppMenu/WebSideMenuItemVM.cs @@ -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 Submenu { get; set; } = new List(); + } +} diff --git a/Marco.Pms.Services/Controllers/AppMenuController.cs b/Marco.Pms.Services/Controllers/AppMenuController.cs index 82b122f..f42d1f5 100644 --- a/Marco.Pms.Services/Controllers/AppMenuController.cs +++ b/Marco.Pms.Services/Controllers/AppMenuController.cs @@ -53,16 +53,123 @@ namespace Marco.Pms.Services.Controllers tenantId = userHelper.GetTenantId(); } + [HttpGet("get/menu")] + public async Task GetAppSideBarMenuAsync() + { + // Correlation ID for distributed tracing across services and logs. + var correlationId = HttpContext.TraceIdentifier; - /// - /// Creates a new sidebar menu section for the tenant. - /// Only accessible by root users or for the super tenant. - /// - /// The data for the new menu section. - /// HTTP response with result of the operation. + _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.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(); + + _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.SuccessResponse(new List(), "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(); + + foreach (var menuSection in menus) + { + var sectionVM = _mapper.Map(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(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.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.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.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.ErrorResponse("Internal Server Error", "An unexpected error occurred while fetching the sidebar menu.", 500)); + } + } [HttpPost("add/sidebar/menu-section")] - public async Task CreateAppSideBarMenu([FromBody] CreateMenuSectionDto menuSectionDto) + public async Task 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(menuSectionDto); + var sideMenuSection = _mapper.Map(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.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.ErrorResponse("Server Error", "An unexpected error occurred.", 500)); } } - /// - /// Updates an existing sidebar menu section for the tenant. - /// Only accessible by root users or for the super tenant. - /// - /// The unique identifier of the section to update. - /// The updated data for the sidebar menu section. - /// HTTP response with the result of the operation. - - [HttpPut("edit/sidebar/menu-section/{sectionId}")] - public async Task 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.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.ErrorResponse("Invalid section ID or mismatched payload.", 400)); - } - - // Step 4: Map DTO to entity - var menuSectionEntity = _mapper.Map(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.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.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.ErrorResponse("Server error", "An unexpected error occurred while updating the menu section.", 500)); - } - } - - /// - /// Adds a new menu item to an existing sidebar menu section. - /// Only accessible by root users or for the super tenant. - /// - /// The unique identifier of the section the item will be added to. - /// The details of the new menu item. - /// HTTP response with the result of the operation. - - [HttpPost("add/sidebar/menus/{sectionId}/items")] - public async Task AddMenuItem(Guid sectionId, [FromBody] CreateMenuItemDto newItemDto) + [HttpPost("add/sidebar/section/{sectionId}/items")] + public async Task AddMenuItemAsync(Guid sectionId, [FromBody] List 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(newItemDto); + var menuItemEntity = _mapper.Map>(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.ErrorResponse("Server error", "An unexpected error occurred while adding the menu item.", 500)); } } - /// - /// Updates an existing menu item inside a sidebar menu section. - /// Only accessible by root users or within the super tenant. - /// - /// The ID of the sidebar menu section. - /// The ID of the menu item to update. - /// The updated menu item details. - /// HTTP response with the result of the update operation. - - [HttpPut("edit/sidebar/{sectionId}/items/{itemId}")] - public async Task 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.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.ErrorResponse("Invalid section ID, item ID, or menu item payload.", 400)); - } - - // Step 4: Map DTO to entity - var menuItemEntity = _mapper.Map(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.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.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.ErrorResponse("Server Error", "An unexpected error occurred while updating the menu item.", 500) - ); - } - } - - /// - /// 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. - /// - /// The ID of the sidebar menu section. - /// The ID of the parent menu item. - /// The details of the new sub-menu item. - /// HTTP response with the result of the add operation. - - [HttpPost("add/sidebar/menus/{sectionId}/items/{itemId}/subitems")] - public async Task 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.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.ErrorResponse("Invalid section ID, item ID, or sub-menu item payload.", 400)); - } - - try - { - // Step 4: Map DTO to entity - var subMenuItemEntity = _mapper.Map(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.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.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.ErrorResponse("Server Error", "An unexpected error occurred while adding the sub-menu item.", 500)); - } - } - - /// - /// Updates an existing sub-menu item inside a sidebar menu section. - /// Only accessible by root users or within the super tenant. - /// - /// The ID of the sidebar menu section. - /// The ID of the parent menu item. - /// The ID of the sub-menu item to update. - /// The updated sub-menu item details. - /// HTTP response with the result of the update operation. - - [HttpPut("edit/sidebar/{sectionId}/items/{itemId}/subitems/{subItemId}")] - public async Task 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.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.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(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.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.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.ErrorResponse("Server Error", "An unexpected error occurred while updating the sub-menu item.", 500)); - } - } - - /// - /// Fetches the sidebar menu for the current tenant and filters items based on employee permissions. - /// - /// The sidebar menu with only the items/sub-items the employee has access to. - - [HttpGet("get/menu")] - public async Task 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(); - - try - { - // Step 2: Fetch all menu sections for the tenant - var menus = await _sideBarMenuHelper.GetAllMenuSectionsAsync(tenantId); - - if (!(menus?.Any() ?? false)) - { - menus = new List - { - MenuStaticMaster.menu - }; - } - - foreach (var menu in menus) - { - var allowedItems = new List(); - - 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(); - - 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>(menus); - return Ok(ApiResponse.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.ErrorResponse("Server Error", "An unexpected error occurred while fetching the sidebar menu.", 500)); - } - } - /// /// Retrieves the master menu list based on enabled features for the current tenant. /// diff --git a/Marco.Pms.Services/MappingProfiles/MappingProfile.cs b/Marco.Pms.Services/MappingProfiles/MappingProfile.cs index 9a9dac8..b190bc0 100644 --- a/Marco.Pms.Services/MappingProfiles/MappingProfile.cs +++ b/Marco.Pms.Services/MappingProfiles/MappingProfile.cs @@ -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(); + CreateMap(); CreateMap(); CreateMap() .ForMember( dest => dest.Name, opt => opt.MapFrom(src => src.Title)); + CreateMap() + .ForMember( + dest => dest.Name, + opt => opt.MapFrom(src => src.Title)) + .ForMember( + dest => dest.Items, + opt => opt.MapFrom(src => new List())); + CreateMap(); + CreateMap() + .ForMember( + dest => dest.Id, + opt => opt.MapFrom(src => src.Id.HasValue ? src.Id.Value : Guid.NewGuid())); + CreateMap(); CreateMap() .ForMember( dest => dest.Name, opt => opt.MapFrom(src => src.Text)); + CreateMap() + .ForMember( + dest => dest.Name, + opt => opt.MapFrom(src => src.Text)); + CreateMap(); CreateMap(); CreateMap()