refactoring some of codes
All checks were successful
Build and Deploy LSFE / build-and-deploy (push) Successful in 1m43s

This commit is contained in:
rowell_m_soriano 2026-07-30 16:44:17 +08:00
parent 15656becac
commit 015422a507
17 changed files with 235 additions and 158 deletions

2
.gitignore vendored
View File

@ -361,3 +361,5 @@ MigrationBackup/
# Fody - auto-generated XML schema # Fody - auto-generated XML schema
FodyWeavers.xsd FodyWeavers.xsd
**/appsettings.Production.json

View File

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace LSFE.Application.Common.Exceptions
{
public class BusinessRuleException : Exception
{
public BusinessRuleException(string message)
: base(message) { }
}
}

View File

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace LSFE.Application.Common.Exceptions
{
public class ForbiddenException : Exception
{
public ForbiddenException()
: base("You do not have permission to perform this action.") { }
}
}

View File

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace LSFE.Application.Common.Exceptions
{
public class NotFoundException : Exception
{
public NotFoundException(string name, object key)
: base($"Entity '{name}' with key '{key}' was not found.") { }
}
}

View File

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace LSFE.Application.Common.Exceptions
{
public class UnauthorizedException : Exception
{
public UnauthorizedException(string message)
: base(message) { }
}
}

View File

@ -0,0 +1,20 @@
using FluentValidation.Results;
using System;
using System.Collections.Generic;
using System.Text;
namespace LSFE.Application.Common.Exceptions
{
public class ValidationException : Exception
{
public IDictionary<string, string[]> Errors { get; }
public ValidationException(IEnumerable<ValidationFailure> failures)
: base("One or more validation errors occurred.")
{
Errors = failures
.GroupBy(f => f.PropertyName, f => f.ErrorMessage)
.ToDictionary(g => g.Key, g => g.ToArray());
}
}
}

View File

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace LSFE.Application.Common.Models
{
public class ErrorResponse
{
public int StatusCode { get; set; }
public string Message { get; set; } = string.Empty;
public IDictionary<string, string[]>? Errors { get; set; }
public string? StackTrace { get; set; }
}
}

View File

@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentValidation" Version="12.1.1" />
</ItemGroup>
</Project>

View File

@ -30,7 +30,6 @@ namespace LSFE.Infrastructure.Converter
} }
else if (reader.TokenType == JsonTokenType.PropertyName) else if (reader.TokenType == JsonTokenType.PropertyName)
{ {
// Handle object format if needed
} }
} }

View File

@ -17,6 +17,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Sql", "Sql", "{F7832112-A88
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LSFE.MobApp", "LSFE.MobApp\LSFE.MobApp.csproj", "{7AD0D34F-BE46-4141-8D95-9870FE8C1FEC}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LSFE.MobApp", "LSFE.MobApp\LSFE.MobApp.csproj", "{7AD0D34F-BE46-4141-8D95-9870FE8C1FEC}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LSFE.Application", "LSFE.Application\LSFE.Application.csproj", "{478AA6B2-E2AA-4ED5-B266-936F913D4996}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -41,6 +43,10 @@ Global
{7AD0D34F-BE46-4141-8D95-9870FE8C1FEC}.Release|Any CPU.ActiveCfg = Release|Any CPU {7AD0D34F-BE46-4141-8D95-9870FE8C1FEC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7AD0D34F-BE46-4141-8D95-9870FE8C1FEC}.Release|Any CPU.Build.0 = Release|Any CPU {7AD0D34F-BE46-4141-8D95-9870FE8C1FEC}.Release|Any CPU.Build.0 = Release|Any CPU
{7AD0D34F-BE46-4141-8D95-9870FE8C1FEC}.Release|Any CPU.Deploy.0 = Release|Any CPU {7AD0D34F-BE46-4141-8D95-9870FE8C1FEC}.Release|Any CPU.Deploy.0 = Release|Any CPU
{478AA6B2-E2AA-4ED5-B266-936F913D4996}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{478AA6B2-E2AA-4ED5-B266-936F913D4996}.Debug|Any CPU.Build.0 = Debug|Any CPU
{478AA6B2-E2AA-4ED5-B266-936F913D4996}.Release|Any CPU.ActiveCfg = Release|Any CPU
{478AA6B2-E2AA-4ED5-B266-936F913D4996}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

View File

@ -46,57 +46,43 @@ namespace LSFE.API.Controllers.Account
public async Task<IActionResult> Login([FromBody] Users model, public async Task<IActionResult> Login([FromBody] Users model,
[FromServices] IAccountService tokenService) [FromServices] IAccountService tokenService)
{ {
try var user = await _userManager.FindByNameAsync(model.UserName.ToLower());
{ if (user == null)
var user = await _userManager.FindByNameAsync(model.UserName.ToLower());
if (user == null)
return BadRequest(new Infrastructure.Model.Response
{
Success = false,
MessCode = 0,
Message = "Invalid username or password."
});
var signInResult = await _signInManager.CheckPasswordSignInAsync(user, model.Password, false);
if (signInResult.Succeeded)
{
await HandleSuccessfulLogin(user);
var token = await tokenService.CreateToken(user);
return Ok(new
{
token,
expiration = DateTime.UtcNow.AddMinutes(30),
userId = user.Id,
userName = user.UserName,
fullName = user.FullName,
email = user.Email,
phoneNumber = user.PhoneNumber,
employeeId = user.EmployeeId,
company = user.Company,
Success = true,
MessCode = 1,
Message = "Yehey!"
});
}
return await HandleFailedLogin(user, signInResult);
}
catch (Exception ex)
{
var message = ex.InnerException?.Message ?? ex.Message;
return BadRequest(new Infrastructure.Model.Response return BadRequest(new Infrastructure.Model.Response
{ {
Success = false, Success = false,
MessCode = 0, MessCode = 0,
Message = message Message = "Invalid username or password."
});
var signInResult = await _signInManager.CheckPasswordSignInAsync(user, model.Password, false);
if (signInResult.Succeeded)
{
await HandleSuccessfulLogin(user);
var token = await tokenService.CreateToken(user);
return Ok(new
{
token,
expiration = DateTime.Now.AddMinutes(30),
userId = user.Id,
userName = user.UserName,
fullName = user.FullName,
email = user.Email,
phoneNumber = user.PhoneNumber,
employeeId = user.EmployeeId,
company = user.Company,
Success = true,
MessCode = 1,
Message = "Yehey!"
}); });
} }
return await HandleFailedLogin(user, signInResult);
} }
protected async Task HandleSuccessfulLogin(AppsUsers user) protected async Task HandleSuccessfulLogin(AppsUsers user)
{ {
// Unlock if necessary
if (user.LockoutEnabled || user.LockoutEnd != null) if (user.LockoutEnabled || user.LockoutEnd != null)
{ {
await _userManager.SetLockoutEnabledAsync(user, false); await _userManager.SetLockoutEnabledAsync(user, false);
@ -104,13 +90,11 @@ namespace LSFE.API.Controllers.Account
await _userManager.UpdateAsync(user); await _userManager.UpdateAsync(user);
} }
// Reset failed attempts
await _userManager.ResetAccessFailedCountAsync(user); await _userManager.ResetAccessFailedCountAsync(user);
} }
protected async Task<IActionResult> HandleFailedLogin(AppsUsers user, protected async Task<IActionResult> HandleFailedLogin(AppsUsers user,
Microsoft.AspNetCore.Identity.SignInResult signInResult) Microsoft.AspNetCore.Identity.SignInResult signInResult)
{ {
// Increment failed attempts
await _userManager.AccessFailedAsync(user); await _userManager.AccessFailedAsync(user);
if (user.AccessFailedCount > 3 || signInResult.IsLockedOut) if (user.AccessFailedCount > 3 || signInResult.IsLockedOut)

View File

@ -15,92 +15,24 @@ namespace LSFE.API.Controllers
{ {
private readonly IWebHostEnvironment _env; private readonly IWebHostEnvironment _env;
private readonly IUnitOfWork _unitOfWork; private readonly IUnitOfWork _unitOfWork;
// public IConfiguration _configuration;
public BaseController(IUnitOfWork unitOfWork, public BaseController(IUnitOfWork unitOfWork,
IWebHostEnvironment env) IWebHostEnvironment env)
{ {
_unitOfWork = unitOfWork; _unitOfWork = unitOfWork;
_env = env; _env = env;
// _configuration = configuration;
} }
/* [NonAction]
[AllowAnonymous]
[HttpPost("{EMailTemplate}")]
public string EMailTemplate(string relativePath, string emailTemplate)
{
try
{
string templateFolderPath = Path.Combine(_webHostEnvironment.ContentRootPath, relativePath);
string templateFilePath = Path.Combine(templateFolderPath, emailTemplate);
if (System.IO.File.Exists(templateFilePath))
{
string body = System.IO.File.ReadAllText(templateFilePath);
return body;
}
else
{
Console.WriteLine($"File not found: {templateFilePath}");
return "Template file not found";
}
}
catch (Exception ex)
{
var errorMessage = ex.ToString() ?? ex.InnerException.ToString();
*//* PostErrorMessage(errorMessage, "WebApi");*//*
throw;
}
}*/
/* [NonAction]
[AllowAnonymous]
[HttpPost("{GetRelativePath}")]
public string GetRelativePath(string relativePath)
{
try
{
string templateFolderPath = Path.Combine(_webHostEnvironment.ContentRootPath, relativePath);
return templateFolderPath;
}
catch (Exception)
{
throw;
}
}*/
/* [NonAction]
[HttpPost("{ErrMessage}")]
public async Task PostErrorMessage(string errMessage, string appName)
{
var errorMessage = new ErrorMessage
{
CreatedDate = DateTime.Now,
Message = errMessage,
Application = appName,
CreatedBy = appName
};
await ErrorMessageService.PostErrorMessage(errorMessage);
}*/
[NonAction] [NonAction]
[HttpPost("{ErrorHandling}")] [HttpPost("{ErrorHandling}")]
protected async Task<IActionResult> ExecuteWithErrorHandling<T>( protected async Task<IActionResult> ExecuteWithErrorHandling<T>(
Func<Task<T>> operation, string methodName, bool isPost) Func<Task<T>> operation, string methodName, bool isPost)
{ {
try var result = await operation();
if (isPost)
{ {
var result = await operation(); return Ok(new { success = true, messCode = 1, message = "Operation completed successfully", data = result });
if (isPost)
{
return Ok(new { success = true, messCode = 1, message = "Operation completed successfully", data = result });
}
return Ok(result);
}
catch (Exception ex)
{
var errorMessage = ex.InnerException?.ToString() ?? ex.Message.ToString();
// await PostErrorMessage(errorMessage, $"WebApi {methodName}");
return BadRequest(new { success = false, messCode = 0, message = errorMessage });
} }
return Ok(result);
} }
[NonAction] [NonAction]
public async Task<IActionResult> LogAndReturnBadRequest(string message, string? userId) public async Task<IActionResult> LogAndReturnBadRequest(string message, string? userId)

View File

@ -110,21 +110,12 @@ namespace LSFE.API.Controllers
[HttpGet("inventory")] [HttpGet("inventory")]
public async Task<IActionResult> GetInventory([FromQuery] InventoryDto dto) public async Task<IActionResult> GetInventory([FromQuery] InventoryDto dto)
{ {
try var inventory = await _unitOfWork.Inventory.GetAllAsync();
{ var filtered = dto.UserRole == "ADMIN" || dto.UserRole == "DSM"
var inventory = await _unitOfWork.Inventory.GetAllAsync(); ? inventory.Where(i => i.IsActive)
var filtered = dto.UserRole == "ADMIN" || dto.UserRole == "DSM" : inventory.Where(i => i.IsActive && i.MedRepName == dto.UpdatedBy);
? inventory.Where(i => i.IsActive)
: inventory.Where(i => i.IsActive && i.MedRepName == dto.UpdatedBy);
return Ok(filtered);
}
catch (Exception ex)
{
ex.ToString();
throw;
}
return Ok(filtered);
} }
#endregion #endregion
} }

View File

@ -99,10 +99,8 @@ namespace LSFE.API.Controllers
} }
else if (dto.ProductId == 0 || dto.ProductId == null) else if (dto.ProductId == 0 || dto.ProductId == null)
{ {
// Only generate GUID for new products without file
dto.ProductLink = Guid.NewGuid().ToString(); dto.ProductLink = Guid.NewGuid().ToString();
} }
// For updates without new file, keep existing ProductLink
var response = await _unitOfWork.Products.AddOrUpdateAsync(dto); var response = await _unitOfWork.Products.AddOrUpdateAsync(dto);
if (response.Success) if (response.Success)
@ -215,32 +213,22 @@ namespace LSFE.API.Controllers
} }
[HttpGet("productFile/{fileName}")] [HttpGet("productFile/{fileName}")]
[ResponseCache(Duration = 3600)] // Cache for 1 hour [ResponseCache(Duration = 3600)]
public async Task<IActionResult> GetProductFile(string fileName) public async Task<IActionResult> GetProductFile(string fileName)
{ {
try if (string.IsNullOrWhiteSpace(fileName) || fileName.Contains(".."))
{ {
// Validate filename return BadRequest("Invalid file name");
if (string.IsNullOrWhiteSpace(fileName) || fileName.Contains(".."))
{
return BadRequest("Invalid file name");
}
var uploadsPath = Path.Combine(_env.WebRootPath, "Content/Uploads", "Materials");
var filePath = Path.Combine(uploadsPath, fileName);
ContentTypeHelper.ValidateFile(filePath, uploadsPath);
// Stream the file instead of loading entirely into memory
var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
var contentType = ContentTypeHelper.GetContentType(fileName);
return File(fileStream, contentType, fileName, enableRangeProcessing: true);
}
catch (Exception ex)
{
return StatusCode(500, "Error retrieving file");
} }
var uploadsPath = Path.Combine(_env.WebRootPath, "Content/Uploads", "Materials");
var filePath = Path.Combine(uploadsPath, fileName);
ContentTypeHelper.ValidateFile(filePath, uploadsPath);
var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
var contentType = ContentTypeHelper.GetContentType(fileName);
return File(fileStream, contentType, fileName, enableRangeProcessing: true);
} }
[HttpGet("areas/{id:int}")] [HttpGet("areas/{id:int}")]

View File

@ -1,6 +1,5 @@
using LSFE.Domain.Contracts; using LSFE.Domain.Contracts;
using LSFE.Domain.Helpers; using LSFE.Domain.Helpers;
using LSFE.Infrastructure.Dto;
using LSFE.Infrastructure.Dto.Reports; using LSFE.Infrastructure.Dto.Reports;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;

View File

@ -18,6 +18,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\LSFE.Application\LSFE.Application.csproj" />
<ProjectReference Include="..\LSFE.Domain\LSFE.Domain.csproj" /> <ProjectReference Include="..\LSFE.Domain\LSFE.Domain.csproj" />
<ProjectReference Include="..\LSFE.Infrastructure\LSFE.Infrastructure.csproj" /> <ProjectReference Include="..\LSFE.Infrastructure\LSFE.Infrastructure.csproj" />
</ItemGroup> </ItemGroup>

View File

@ -0,0 +1,80 @@
using LSFE.Application.Common.Exceptions;
using LSFE.Application.Common.Models;
using System.Security.Claims;
using System.Text.Json;
namespace LSFE.API.Middleware
{
public class GlobalExceptionMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<GlobalExceptionMiddleware> _logger;
private readonly IHostEnvironment _env;
public GlobalExceptionMiddleware(
RequestDelegate next,
ILogger<GlobalExceptionMiddleware> logger,
IHostEnvironment env)
{
_next = next;
_logger = logger;
_env = env;
}
public async Task InvokeAsync(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception ex)
{
_logger.LogError(ex, "Unhandled exception: {Message}", ex.Message);
await HandleExceptionAsync(context, ex);
}
}
private async Task HandleExceptionAsync(HttpContext context, Exception ex)
{
var (statusCode, message, errors) = ex switch
{
ValidationException e => (StatusCodes.Status422UnprocessableEntity,
e.Message, e.Errors),
NotFoundException e => (StatusCodes.Status404NotFound,
e.Message, null),
UnauthorizedException e => (StatusCodes.Status401Unauthorized,
e.Message, null),
ForbiddenException e => (StatusCodes.Status403Forbidden,
e.Message, null),
BusinessRuleException e => (StatusCodes.Status409Conflict,
e.Message, null),
UnauthorizedAccessException e => (StatusCodes.Status401Unauthorized,
e.Message, null),
OperationCanceledException _ => (StatusCodes.Status499ClientClosedRequest,
"Request was cancelled.", null),
_ => (StatusCodes.Status500InternalServerError,
"An unexpected error occurred. Please try again later.", null)
};
var response = new ErrorResponse
{
StatusCode = statusCode,
Message = message,
Errors = errors,
StackTrace = _env.IsDevelopment() ? ex.StackTrace : null
};
context.Response.ContentType = "application/json";
context.Response.StatusCode = statusCode;
await context.Response.WriteAsJsonAsync(response);
}
}
}