480 lines
25 KiB
C#
480 lines
25 KiB
C#
using Marco.Pms.DataAccess.Data;
|
|
using Marco.Pms.Model.Dtos.Mail;
|
|
using Marco.Pms.Model.Mail;
|
|
using Marco.Pms.Model.MongoDBModels;
|
|
using Marco.Pms.Model.Utilities;
|
|
using Marco.Pms.Services.Helpers;
|
|
using MarcoBMS.Services.Helpers;
|
|
using MarcoBMS.Services.Service;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.CodeAnalysis;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using MongoDB.Driver;
|
|
using System.Data;
|
|
using System.Globalization;
|
|
using System.Net.Mail;
|
|
|
|
namespace Marco.Pms.Services.Controllers
|
|
{
|
|
[Route("api/[controller]")]
|
|
[ApiController]
|
|
[Authorize]
|
|
public class ReportController : ControllerBase
|
|
{
|
|
private readonly ApplicationDbContext _context;
|
|
private readonly IEmailSender _emailSender;
|
|
private readonly ILoggingService _logger;
|
|
private readonly UserHelper _userHelper;
|
|
private readonly IWebHostEnvironment _env;
|
|
private readonly ReportHelper _reportHelper;
|
|
private readonly IConfiguration _configuration;
|
|
private readonly CacheUpdateHelper _cache;
|
|
private readonly IServiceScopeFactory _serviceScopeFactory;
|
|
public ReportController(ApplicationDbContext context, IEmailSender emailSender, ILoggingService logger, UserHelper userHelper,
|
|
IWebHostEnvironment env, ReportHelper reportHelper, IConfiguration configuration, CacheUpdateHelper cache, IServiceScopeFactory serviceScopeFactory)
|
|
{
|
|
_context = context;
|
|
_emailSender = emailSender;
|
|
_logger = logger;
|
|
_userHelper = userHelper;
|
|
_env = env;
|
|
_reportHelper = reportHelper;
|
|
_configuration = configuration;
|
|
_cache = cache;
|
|
_serviceScopeFactory = serviceScopeFactory;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds new mail details for a project report.
|
|
/// </summary>
|
|
/// <param name="mailDetailsDto">The mail details data.</param>
|
|
/// <returns>An API response indicating success or failure.</returns>
|
|
[HttpPost("mail-details")] // More specific route for adding mail details
|
|
public async Task<IActionResult> AddMailDetails([FromBody] MailDetailsDto mailDetailsDto)
|
|
{
|
|
// 1. Get Tenant ID and Basic Authorization Check
|
|
Guid tenantId = _userHelper.GetTenantId();
|
|
if (tenantId == Guid.Empty)
|
|
{
|
|
_logger.LogWarning("Authorization Error: Attempt to add mail details with an empty or invalid tenant ID.");
|
|
return Unauthorized(ApiResponse<object>.ErrorResponse("Unauthorized", "Tenant ID not found or invalid.", 401));
|
|
}
|
|
|
|
// 2. Input Validation (Leverage Model Validation attributes on DTO)
|
|
if (mailDetailsDto == null)
|
|
{
|
|
_logger.LogWarning("Validation Error: MailDetails DTO is null. TenantId: {TenantId}", tenantId);
|
|
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid Data", "Request body is empty.", 400));
|
|
}
|
|
|
|
// Ensure ProjectId and Recipient are not empty
|
|
if (mailDetailsDto.ProjectId == Guid.Empty)
|
|
{
|
|
_logger.LogWarning("Validation Error: Project ID is empty. TenantId: {TenantId}", tenantId);
|
|
return BadRequest(ApiResponse<object>.ErrorResponse("Validation Failed", "Project ID cannot be empty.", 400));
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(mailDetailsDto.Recipient))
|
|
{
|
|
_logger.LogWarning("Validation Error: Recipient email is empty. ProjectId: {ProjectId}, TenantId: {TenantId}", mailDetailsDto.ProjectId, tenantId);
|
|
return BadRequest(ApiResponse<object>.ErrorResponse("Validation Failed", "Recipient email cannot be empty.", 400));
|
|
}
|
|
|
|
// Optional: Validate email format using regex or System.Net.Mail.MailAddress
|
|
try
|
|
{
|
|
var mailAddress = new MailAddress(mailDetailsDto.Recipient);
|
|
_logger.LogInfo("nothing");
|
|
}
|
|
catch (FormatException)
|
|
{
|
|
_logger.LogWarning("Validation Error: Invalid recipient email format '{Recipient}'. ProjectId: {ProjectId}, TenantId: {TenantId}", mailDetailsDto.Recipient, mailDetailsDto.ProjectId, tenantId);
|
|
return BadRequest(ApiResponse<object>.ErrorResponse("Validation Failed", "Invalid recipient email format.", 400));
|
|
}
|
|
|
|
// 3. Validate MailListId (Foreign Key Check)
|
|
// Ensure the MailListId refers to an existing MailBody (template) within the same tenant.
|
|
if (mailDetailsDto.MailListId != Guid.Empty) // Only validate if a MailListId is provided
|
|
{
|
|
bool mailTemplateExists;
|
|
try
|
|
{
|
|
mailTemplateExists = await _context.MailingList
|
|
.AsNoTracking()
|
|
.AnyAsync(m => m.Id == mailDetailsDto.MailListId && m.TenantId == tenantId);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Database Error: Failed to check existence of MailListId '{MailListId}' for TenantId: {TenantId}.", mailDetailsDto.MailListId, tenantId);
|
|
return StatusCode(500, ApiResponse<object>.ErrorResponse("Internal Server Error", "An error occurred while validating mail template.", 500));
|
|
}
|
|
|
|
if (!mailTemplateExists)
|
|
{
|
|
_logger.LogWarning("Validation Error: Provided MailListId '{MailListId}' does not exist or does not belong to TenantId: {TenantId}.", mailDetailsDto.MailListId, tenantId);
|
|
return NotFound(ApiResponse<object>.ErrorResponse("Invalid Mail Template", "The specified mail template (MailListId) was not found or accessible.", 404));
|
|
}
|
|
}
|
|
// If MailListId can be null/empty and implies no specific template, adjust logic accordingly.
|
|
// Currently assumes it must exist if provided.
|
|
|
|
// 4. Create and Add New Mail Details
|
|
var newMailDetails = new MailDetails
|
|
{
|
|
ProjectId = mailDetailsDto.ProjectId,
|
|
Recipient = mailDetailsDto.Recipient,
|
|
Schedule = mailDetailsDto.Schedule,
|
|
MailListId = mailDetailsDto.MailListId,
|
|
TenantId = tenantId,
|
|
};
|
|
|
|
try
|
|
{
|
|
_context.MailDetails.Add(newMailDetails);
|
|
await _context.SaveChangesAsync();
|
|
_logger.LogInfo("Successfully added new mail details with ID {MailDetailsId} for ProjectId: {ProjectId}, Recipient: '{Recipient}', TenantId: {TenantId}.", newMailDetails.Id, newMailDetails.ProjectId, newMailDetails.Recipient, tenantId);
|
|
|
|
// 5. Return Success Response (201 Created is ideal for resource creation)
|
|
return StatusCode(201, ApiResponse<MailDetails>.SuccessResponse(
|
|
newMailDetails, // Return the newly created object (or a DTO of it)
|
|
"Mail details added successfully.",
|
|
201));
|
|
}
|
|
catch (DbUpdateException dbEx)
|
|
{
|
|
_logger.LogError(dbEx, "Database Error: Failed to save new mail details for ProjectId: {ProjectId}, Recipient: '{Recipient}', TenantId: {TenantId}.", newMailDetails.ProjectId, newMailDetails.Recipient, tenantId);
|
|
// Check for specific constraint violations if applicable (e.g., duplicate recipient for a project)
|
|
return StatusCode(500, ApiResponse<object>.ErrorResponse("Internal Server Error", "An error occurred while saving the mail details.", 500));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Unexpected Error: An unhandled exception occurred while adding mail details for ProjectId: {ProjectId}, Recipient: '{Recipient}', TenantId: {TenantId}.", newMailDetails.ProjectId, newMailDetails.Recipient, tenantId);
|
|
return StatusCode(500, ApiResponse<object>.ErrorResponse("Internal Server Error", "An unexpected error occurred.", 500));
|
|
}
|
|
}
|
|
|
|
[HttpPost("mail-template1")]
|
|
public async Task<IActionResult> AddMailTemplate1([FromBody] MailTemeplateDto mailTemeplateDto)
|
|
{
|
|
Guid tenantId = _userHelper.GetTenantId();
|
|
if (string.IsNullOrWhiteSpace(mailTemeplateDto.Body) && string.IsNullOrWhiteSpace(mailTemeplateDto.Title))
|
|
{
|
|
_logger.LogWarning("User tries to set email template but send invalid data");
|
|
return BadRequest(ApiResponse<object>.ErrorResponse("Provided Invalid data", "Provided Invalid data", 400));
|
|
}
|
|
var existngTemalate = await _context.MailingList.FirstOrDefaultAsync(t => t.Title.ToLower() == mailTemeplateDto.Title.ToLower());
|
|
if (existngTemalate != null)
|
|
{
|
|
_logger.LogWarning("User tries to set email template, but title already existed in database");
|
|
return BadRequest(ApiResponse<object>.ErrorResponse("Email title is already existed", "Email title is already existed", 400));
|
|
}
|
|
MailingList mailingList = new MailingList
|
|
{
|
|
Title = mailTemeplateDto.Title,
|
|
Body = mailTemeplateDto.Body,
|
|
Subject = mailTemeplateDto.Subject,
|
|
Keywords = mailTemeplateDto.Keywords,
|
|
TenantId = tenantId
|
|
};
|
|
_context.MailingList.Add(mailingList);
|
|
await _context.SaveChangesAsync();
|
|
return Ok("Success");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds a new mail template.
|
|
/// </summary>
|
|
/// <param name="mailTemplateDto">The mail template data.</param>
|
|
/// <returns>An API response indicating success or failure.</returns>
|
|
[HttpPost("mail-template")] // More specific route for adding a template
|
|
public async Task<IActionResult> AddMailTemplate([FromBody] MailTemeplateDto mailTemplateDto) // Renamed parameter for consistency
|
|
{
|
|
// 1. Get Tenant ID and Basic Authorization Check
|
|
Guid tenantId = _userHelper.GetTenantId();
|
|
if (tenantId == Guid.Empty)
|
|
{
|
|
_logger.LogWarning("Authorization Error: Attempt to add mail template with an empty or invalid tenant ID.");
|
|
return Unauthorized(ApiResponse<object>.ErrorResponse("Unauthorized", "Tenant ID not found or invalid.", 401));
|
|
}
|
|
|
|
// 2. Input Validation (Moved to model validation if possible, or keep explicit)
|
|
// Use proper model validation attributes ([Required], [StringLength]) on MailTemeplateDto
|
|
// and rely on ASP.NET Core's automatic model validation if possible.
|
|
// If not, these checks are good.
|
|
if (mailTemplateDto == null)
|
|
{
|
|
_logger.LogWarning("Validation Error: Mail template DTO is null.");
|
|
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid Data", "Request body is empty.", 400));
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(mailTemplateDto.Title))
|
|
{
|
|
_logger.LogWarning("Validation Error: Mail template title is empty or whitespace. TenantId: {TenantId}", tenantId);
|
|
return BadRequest(ApiResponse<object>.ErrorResponse("Validation Failed", "Mail template title cannot be empty.", 400));
|
|
}
|
|
|
|
// The original logic checked both body and title, but often a template needs at least a title.
|
|
// Re-evalute if body can be empty. If so, remove the body check. Assuming title is always mandatory.
|
|
// If both body and title are empty, it's definitely invalid.
|
|
if (string.IsNullOrWhiteSpace(mailTemplateDto.Body) && string.IsNullOrWhiteSpace(mailTemplateDto.Subject))
|
|
{
|
|
_logger.LogWarning("Validation Error: Mail template body and subject are both empty or whitespace for title '{Title}'. TenantId: {TenantId}", mailTemplateDto.Title, tenantId);
|
|
return BadRequest(ApiResponse<object>.ErrorResponse("Validation Failed", "Mail template body or subject must be provided.", 400));
|
|
}
|
|
|
|
// 3. Check for Existing Template Title (Case-Insensitive)
|
|
// Use AsNoTracking() for read-only query
|
|
MailingList? existingTemplate;
|
|
try
|
|
{
|
|
existingTemplate = await _context.MailingList
|
|
.AsNoTracking() // Important for read-only checks
|
|
.FirstOrDefaultAsync(t => t.Title.ToLower() == mailTemplateDto.Title.ToLower() && t.TenantId == tenantId); // IMPORTANT: Filter by TenantId!
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Database Error: Failed to check for existing mail template with title '{Title}' for TenantId: {TenantId}.", mailTemplateDto.Title, tenantId);
|
|
return StatusCode(500, ApiResponse<object>.ErrorResponse("Internal Server Error", "An error occurred while checking for existing templates.", 500));
|
|
}
|
|
|
|
|
|
if (existingTemplate != null)
|
|
{
|
|
_logger.LogWarning("Conflict Error: User tries to add email template with title '{Title}' which already exists for TenantId: {TenantId}.", mailTemplateDto.Title, tenantId);
|
|
return Conflict(ApiResponse<object>.ErrorResponse("Conflict", $"Email template with title '{mailTemplateDto.Title}' already exists.", 409));
|
|
}
|
|
|
|
// 4. Create and Add New Template
|
|
var newMailingList = new MailingList
|
|
{
|
|
Title = mailTemplateDto.Title,
|
|
Body = mailTemplateDto.Body,
|
|
Subject = mailTemplateDto.Subject,
|
|
Keywords = mailTemplateDto.Keywords,
|
|
TenantId = tenantId,
|
|
};
|
|
|
|
try
|
|
{
|
|
_context.MailingList.Add(newMailingList);
|
|
await _context.SaveChangesAsync();
|
|
_logger.LogInfo("Successfully added new mail template with ID {TemplateId} and title '{Title}' for TenantId: {TenantId}.", newMailingList.Id, newMailingList.Title, tenantId);
|
|
|
|
// 5. Return Success Response (201 Created is ideal for resource creation)
|
|
// It's good practice to return the created resource or its ID.
|
|
return StatusCode(201, ApiResponse<MailingList>.SuccessResponse(
|
|
newMailingList, // Return the newly created object (or a DTO of it)
|
|
"Mail template added successfully.",
|
|
201));
|
|
}
|
|
catch (DbUpdateException dbEx)
|
|
{
|
|
_logger.LogError(dbEx, "Database Error: Failed to save new mail template '{Title}' for TenantId: {TenantId}. : {Error}", mailTemplateDto.Title, tenantId);
|
|
return StatusCode(500, ApiResponse<object>.ErrorResponse("Internal Server Error", "An error occurred while saving the mail template.", 500));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Unexpected Error: An unhandled exception occurred while adding mail template '{Title}' for TenantId: {TenantId}.", mailTemplateDto.Title, tenantId);
|
|
return StatusCode(500, ApiResponse<object>.ErrorResponse("Internal Server Error", "An unexpected error occurred.", 500));
|
|
}
|
|
}
|
|
|
|
[HttpGet("project-statistics")]
|
|
public async Task<IActionResult> SendProjectReport()
|
|
{
|
|
Guid tenantId = _userHelper.GetTenantId();
|
|
|
|
// 1. OPTIMIZATION: Perform grouping and projection on the database server.
|
|
// This is far more efficient than loading all entities into memory.
|
|
var projectMailGroups = await _context.MailDetails
|
|
.AsNoTracking()
|
|
.Where(m => m.TenantId == tenantId)
|
|
.GroupBy(m => new { m.ProjectId, m.MailListId })
|
|
.Select(g => new
|
|
{
|
|
ProjectId = g.Key.ProjectId,
|
|
Recipients = g.Select(m => m.Recipient).Distinct().ToList(),
|
|
// Project the mail body and subject from the first record in the group
|
|
MailInfo = g.Select(m => new { Body = m.MailBody != null ? m.MailBody.Body : "", Subject = m.MailBody != null ? m.MailBody.Subject : "" }).FirstOrDefault()
|
|
})
|
|
.ToListAsync();
|
|
|
|
if (!projectMailGroups.Any())
|
|
{
|
|
return Ok(ApiResponse<object>.SuccessResponse(new { }, "No projects found to send reports for.", 200));
|
|
}
|
|
|
|
int successCount = 0;
|
|
int notFoundCount = 0;
|
|
int invalidIdCount = 0;
|
|
int failureCount = 0;
|
|
|
|
// 2. OPTIMIZATION: Use true concurrency by removing SemaphoreSlim(1)
|
|
// and giving each task its own isolated set of services (including DbContext).
|
|
var sendTasks = projectMailGroups.Select(async mailGroup =>
|
|
{
|
|
// SOLUTION: Create a new Dependency Injection scope for each parallel task.
|
|
using (var scope = _serviceScopeFactory.CreateScope())
|
|
{
|
|
// Resolve a new instance of the helper from this isolated scope.
|
|
// This ensures each task gets its own thread-safe DbContext.
|
|
var reportHelper = scope.ServiceProvider.GetRequiredService<ReportHelper>();
|
|
|
|
try
|
|
{
|
|
// Ensure MailInfo and ProjectId are valid before proceeding
|
|
if (mailGroup.MailInfo == null || mailGroup.ProjectId == Guid.Empty)
|
|
{
|
|
Interlocked.Increment(ref invalidIdCount);
|
|
return;
|
|
}
|
|
|
|
var response = await reportHelper.GetProjectStatistics(
|
|
mailGroup.ProjectId,
|
|
mailGroup.Recipients,
|
|
mailGroup.MailInfo.Body,
|
|
mailGroup.MailInfo.Subject,
|
|
tenantId);
|
|
|
|
// Use a switch expression for cleaner counting
|
|
switch (response.StatusCode)
|
|
{
|
|
case 200: Interlocked.Increment(ref successCount); break;
|
|
case 404: Interlocked.Increment(ref notFoundCount); break;
|
|
case 400: Interlocked.Increment(ref invalidIdCount); break;
|
|
default: Interlocked.Increment(ref failureCount); break;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
// 3. OPTIMIZATION: Make the process resilient.
|
|
// If one task fails unexpectedly, log it and continue with others.
|
|
_logger.LogError(ex, "Failed to send report for project {ProjectId}", mailGroup.ProjectId);
|
|
Interlocked.Increment(ref failureCount);
|
|
}
|
|
}
|
|
}).ToList();
|
|
|
|
await Task.WhenAll(sendTasks);
|
|
|
|
var summaryMessage = $"Processing complete. Success: {successCount}, Not Found: {notFoundCount}, Invalid ID: {invalidIdCount}, Failures: {failureCount}.";
|
|
|
|
_logger.LogInfo(
|
|
"Project report sending complete for tenant {TenantId}. Success: {SuccessCount}, Not Found: {NotFoundCount}, Invalid ID: {InvalidIdCount}, Failures: {FailureCount}",
|
|
tenantId, successCount, notFoundCount, invalidIdCount, failureCount);
|
|
|
|
return Ok(ApiResponse<object>.SuccessResponse(
|
|
new { successCount, notFoundCount, invalidIdCount, failureCount },
|
|
summaryMessage,
|
|
200));
|
|
}
|
|
|
|
[HttpPost("add-report-mail")]
|
|
public async Task<IActionResult> StoreProjectStatistics()
|
|
{
|
|
Guid tenantId = _userHelper.GetTenantId();
|
|
|
|
// 1. Database-Side Grouping (Still the most efficient way to get initial data)
|
|
var projectMailGroups = await _context.MailDetails
|
|
.AsNoTracking()
|
|
.Where(m => m.TenantId == tenantId && m.ProjectId != Guid.Empty)
|
|
.GroupBy(m => new { m.ProjectId, m.MailListId })
|
|
.Select(g => new
|
|
{
|
|
g.Key.ProjectId,
|
|
Recipients = g.Select(m => m.Recipient).Distinct().ToList(),
|
|
MailInfo = g.Select(m => new { Body = m.MailBody != null ? m.MailBody.Body : "", Subject = m.MailBody != null ? m.MailBody.Subject : "" }).FirstOrDefault()
|
|
})
|
|
.ToListAsync();
|
|
|
|
if (!projectMailGroups.Any())
|
|
{
|
|
_logger.LogInfo("No project mail details found for tenant {TenantId} to process.", tenantId);
|
|
return Ok(ApiResponse<object>.SuccessResponse("No project reports to generate.", "No project reports to generate.", 200));
|
|
}
|
|
|
|
string env = _configuration["environment:Title"] ?? string.Empty;
|
|
|
|
// 2. Process each group concurrently, but with isolated DBContexts.
|
|
var processingTasks = projectMailGroups.Select(async group =>
|
|
{
|
|
// SOLUTION: Create a new DI scope for each parallel task.
|
|
using (var scope = _serviceScopeFactory.CreateScope())
|
|
{
|
|
// Resolve services from this new, isolated scope.
|
|
// These helpers will get their own fresh DbContext instance.
|
|
var reportHelper = scope.ServiceProvider.GetRequiredService<ReportHelper>();
|
|
var emailSender = scope.ServiceProvider.GetRequiredService<IEmailSender>();
|
|
var cache = scope.ServiceProvider.GetRequiredService<CacheUpdateHelper>(); // e.g., IProjectReportCache
|
|
|
|
// The rest of the logic is the same, but now it's thread-safe.
|
|
try
|
|
{
|
|
var projectId = group.ProjectId;
|
|
var statisticReport = await reportHelper.GetDailyProjectReport(projectId, tenantId);
|
|
|
|
if (statisticReport == null)
|
|
{
|
|
_logger.LogWarning("Statistic report for project ID {ProjectId} not found. Skipping.", projectId);
|
|
return;
|
|
}
|
|
|
|
if (group.MailInfo == null)
|
|
{
|
|
_logger.LogWarning("MailBody info for project ID {ProjectId} not found. Skipping.", projectId);
|
|
return;
|
|
}
|
|
|
|
var date = statisticReport.Date.ToString("dd-MMM-yyyy", CultureInfo.InvariantCulture);
|
|
// Assuming the first param to SendProjectStatisticsEmail was just a placeholder
|
|
var emailBody = await emailSender.SendProjectStatisticsEmail(new List<string>(), group.MailInfo.Body, string.Empty, statisticReport);
|
|
|
|
string subject = group.MailInfo.Subject
|
|
.Replace("{{DATE}}", date)
|
|
.Replace("{{PROJECT_NAME}}", statisticReport.ProjectName);
|
|
|
|
subject = string.IsNullOrWhiteSpace(env) ? subject : $"({env}) {subject}";
|
|
|
|
var mail = new ProjectReportEmailMongoDB
|
|
{
|
|
IsSent = false,
|
|
Body = emailBody,
|
|
Receivers = group.Recipients,
|
|
Subject = subject,
|
|
};
|
|
|
|
await cache.AddProjectReportMail(mail);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
// It's good practice to log any unexpected errors within a concurrent task.
|
|
_logger.LogError(ex, "Failed to process project report for ProjectId {ProjectId}", group.ProjectId);
|
|
}
|
|
}
|
|
});
|
|
|
|
// Await all the concurrent, now thread-safe, tasks.
|
|
await Task.WhenAll(processingTasks);
|
|
|
|
return Ok(ApiResponse<object>.SuccessResponse(
|
|
$"{projectMailGroups.Count} Project Report Mail(s) are queued for storage.",
|
|
"Project Report Mail processing initiated.",
|
|
200));
|
|
}
|
|
|
|
|
|
[HttpGet("report-mail")]
|
|
public async Task<IActionResult> GetProjectStatisticsFromCache()
|
|
{
|
|
var mailList = await _cache.GetProjectReportMail(false);
|
|
if (mailList == null)
|
|
{
|
|
return NotFound(ApiResponse<object>.ErrorResponse("Not mail found", "Not mail found", 404));
|
|
}
|
|
|
|
return Ok(ApiResponse<object>.SuccessResponse(mailList, "Fetched list of mail body successfully", 200));
|
|
}
|
|
}
|
|
}
|