Added an API to get basic list of organizations
This commit is contained in:
parent
bbe36ed535
commit
b6baff7d00
@ -45,6 +45,14 @@ namespace Marco.Pms.Services.Controllers
|
|||||||
return StatusCode(response.StatusCode, response);
|
return StatusCode(response.StatusCode, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpGet("list/basic")]
|
||||||
|
public async Task<IActionResult> GetOrganizationBasicList([FromQuery] string? searchString, CancellationToken ct, [FromQuery] int pageNumber = 1, [FromQuery] int pageSize = 20)
|
||||||
|
{
|
||||||
|
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||||
|
var response = await _organizationService.GetOrganizationBasicListAsync(searchString, pageNumber, pageSize, loggedInEmployee, ct);
|
||||||
|
return StatusCode(response.StatusCode, response);
|
||||||
|
}
|
||||||
|
|
||||||
[HttpGet("details/{id}")]
|
[HttpGet("details/{id}")]
|
||||||
public async Task<IActionResult> GetOrganizationDetails(Guid id)
|
public async Task<IActionResult> GetOrganizationDetails(Guid id)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
using AutoMapper;
|
using AutoMapper;
|
||||||
|
using AutoMapper.QueryableExtensions;
|
||||||
using Marco.Pms.DataAccess.Data;
|
using Marco.Pms.DataAccess.Data;
|
||||||
using Marco.Pms.Model.Dtos.Organization;
|
using Marco.Pms.Model.Dtos.Organization;
|
||||||
using Marco.Pms.Model.Employees;
|
using Marco.Pms.Model.Employees;
|
||||||
@ -13,6 +14,7 @@ using Marco.Pms.Services.Helpers;
|
|||||||
using Marco.Pms.Services.Service.ServiceInterfaces;
|
using Marco.Pms.Services.Service.ServiceInterfaces;
|
||||||
using MarcoBMS.Services.Service;
|
using MarcoBMS.Services.Service;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using System.Linq.Dynamic.Core;
|
||||||
|
|
||||||
namespace Marco.Pms.Services.Service
|
namespace Marco.Pms.Services.Service
|
||||||
{
|
{
|
||||||
@ -150,6 +152,91 @@ namespace Marco.Pms.Services.Service
|
|||||||
|
|
||||||
return ApiResponse<object>.SuccessResponse(response, "Successfully fetched the organization list", 200);
|
return ApiResponse<object>.SuccessResponse(response, "Successfully fetched the organization list", 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves a paginated, searchable list of organizations.
|
||||||
|
/// Optimized for performance using DB Projections and AsNoTracking.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="searchString">Optional keyword to filter organizations by name.</param>
|
||||||
|
/// <param name="pageNumber">The requested page number (1-based).</param>
|
||||||
|
/// <param name="pageSize">The number of records per page (Max 50).</param>
|
||||||
|
/// <param name="loggedInEmployee">The current user context for security filtering.</param>
|
||||||
|
/// <param name="ct">Cancellation token to cancel operations if the client disconnects.</param>
|
||||||
|
/// <returns>A paginated list of BasicOrganizationVm.</returns>
|
||||||
|
public async Task<ApiResponse<object>> GetOrganizationBasicListAsync(string? searchString, int pageNumber, int pageSize, Employee loggedInEmployee, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 1. INPUT SANITIZATION
|
||||||
|
// Ensure valid pagination limits to prevent performance degradation or DoS attacks.
|
||||||
|
int actualPage = pageNumber < 1 ? 1 : pageNumber;
|
||||||
|
int actualSize = pageSize > 50 ? 50 : (pageSize < 1 ? 10 : pageSize);
|
||||||
|
|
||||||
|
_logger.LogInfo("Fetching Organization list. Page: {Page}, Size: {Size}, Search: {Search}, User: {UserId}",
|
||||||
|
actualPage, actualSize, searchString ?? "<empty>", loggedInEmployee.Id);
|
||||||
|
|
||||||
|
// 2. QUERY BUILDING
|
||||||
|
// Use AsNoTracking() for read-only scenarios to reduce overhead.
|
||||||
|
var query = _context.Organizations.AsNoTracking()
|
||||||
|
.Where(o => o.IsActive);
|
||||||
|
|
||||||
|
// 3. SECURITY FILTER (Multi-Tenancy)
|
||||||
|
// Enterprise Rule: Always filter by the logged-in user's Tenant/Permissions.
|
||||||
|
// Assuming loggedInEmployee has a TenantId or OrganizationId
|
||||||
|
// query = query.Where(o => o.TenantId == loggedInEmployee.TenantId);
|
||||||
|
|
||||||
|
// 4. DYNAMIC FILTERING
|
||||||
|
if (!string.IsNullOrWhiteSpace(searchString))
|
||||||
|
{
|
||||||
|
var searchTrimmed = searchString.Trim();
|
||||||
|
query = query.Where(o => o.Name.Contains(searchTrimmed));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. COUNT TOTALS (Efficiently)
|
||||||
|
// Count the total records matching the filter BEFORE applying pagination
|
||||||
|
var totalCount = await query.CountAsync(ct);
|
||||||
|
|
||||||
|
// 6. FETCH DATA (With Projection)
|
||||||
|
// CRITICAL OPTIMIZATION: Use .ProjectTo or .Select BEFORE .ToListAsync.
|
||||||
|
// This ensures SQL only fetches the columns needed for BasicOrganizationVm,
|
||||||
|
// rather than fetching the whole Entity and discarding data in memory.
|
||||||
|
var items = await query
|
||||||
|
.OrderBy(o => o.Name)
|
||||||
|
.Skip((actualPage - 1) * actualSize)
|
||||||
|
.Take(actualSize)
|
||||||
|
.ProjectTo<BasicOrganizationVm>(_mapper.ConfigurationProvider) // Requires AutoMapper.QueryableExtensions
|
||||||
|
.ToListAsync(ct);
|
||||||
|
|
||||||
|
// 7. PREPARE RESPONSE
|
||||||
|
var totalPages = (int)Math.Ceiling((double)totalCount / actualSize);
|
||||||
|
|
||||||
|
var pagedResult = new
|
||||||
|
{
|
||||||
|
CurrentPage = actualPage,
|
||||||
|
PageSize = actualSize,
|
||||||
|
TotalPages = totalPages,
|
||||||
|
TotalCount = totalCount,
|
||||||
|
HasPrevious = actualPage > 1,
|
||||||
|
HasNext = actualPage < totalPages,
|
||||||
|
Data = items
|
||||||
|
};
|
||||||
|
|
||||||
|
_logger.LogInfo("Successfully fetched {Count} organizations out of {Total}.", items.Count, totalCount);
|
||||||
|
|
||||||
|
return ApiResponse<object>.SuccessResponse(pagedResult, "Organization list fetched successfully.", 200);
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
// Handle client disconnection gracefully
|
||||||
|
_logger.LogWarning("Organization list fetch was cancelled by the client.");
|
||||||
|
return ApiResponse<object>.ErrorResponse("Request Cancelled", "The operation was cancelled.", 499);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Failed to fetch organization list. User: {UserId}, Error: {Message}", loggedInEmployee.Id, ex.Message);
|
||||||
|
return ApiResponse<object>.ErrorResponse("Data Fetch Failed", "An unexpected error occurred while retrieving the organization list.", 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
public async Task<ApiResponse<object>> GetOrganizationDetailsAsync(Guid id, Employee loggedInEmployee, Guid tenantId, Guid loggedOrganizationId)
|
public async Task<ApiResponse<object>> GetOrganizationDetailsAsync(Guid id, Employee loggedInEmployee, Guid tenantId, Guid loggedOrganizationId)
|
||||||
{
|
{
|
||||||
_logger.LogDebug("Started fetching details for OrganizationId: {OrganizationId}", id);
|
_logger.LogDebug("Started fetching details for OrganizationId: {OrganizationId}", id);
|
||||||
|
|||||||
@ -8,6 +8,7 @@ namespace Marco.Pms.Services.Service.ServiceInterfaces
|
|||||||
{
|
{
|
||||||
#region =================================================================== Get Functions ===================================================================
|
#region =================================================================== Get Functions ===================================================================
|
||||||
Task<ApiResponse<object>> GetOrganizarionListAsync(string? searchString, long? sprid, bool active, int pageNumber, int pageSize, Employee loggedInEmployee, Guid tenantId, Guid loggedOrganizationId);
|
Task<ApiResponse<object>> GetOrganizarionListAsync(string? searchString, long? sprid, bool active, int pageNumber, int pageSize, Employee loggedInEmployee, Guid tenantId, Guid loggedOrganizationId);
|
||||||
|
Task<ApiResponse<object>> GetOrganizationBasicListAsync(string? searchString, int pageNumber, int pageSize, Employee loggedInEmployee, CancellationToken ct);
|
||||||
Task<ApiResponse<object>> GetOrganizationDetailsAsync(Guid id, Employee loggedInEmployee, Guid tenantId, Guid loggedOrganizationId);
|
Task<ApiResponse<object>> GetOrganizationDetailsAsync(Guid id, Employee loggedInEmployee, Guid tenantId, Guid loggedOrganizationId);
|
||||||
Task<ApiResponse<object>> GetOrganizationHierarchyListAsync(Guid employeeId, Employee loggedInEmployee, Guid tenantId, Guid loggedOrganizationId);
|
Task<ApiResponse<object>> GetOrganizationHierarchyListAsync(Guid employeeId, Employee loggedInEmployee, Guid tenantId, Guid loggedOrganizationId);
|
||||||
#endregion
|
#endregion
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user