diff --git a/Marco.Pms.Services/Controllers/MasterController.cs b/Marco.Pms.Services/Controllers/MasterController.cs index 9000cdf..f115bde 100644 --- a/Marco.Pms.Services/Controllers/MasterController.cs +++ b/Marco.Pms.Services/Controllers/MasterController.cs @@ -10,6 +10,7 @@ using Marco.Pms.Model.ViewModels.Activities; using Marco.Pms.Model.ViewModels.Forum; using Marco.Pms.Model.ViewModels.Master; using Marco.Pms.Services.Helpers; +using Marco.Pms.Services.Service.ServiceInterfaces; using MarcoBMS.Services.Helpers; using MarcoBMS.Services.Service; using Microsoft.AspNetCore.Authorization; @@ -27,12 +28,14 @@ namespace Marco.Pms.Services.Controllers private readonly UserHelper _userHelper; private readonly ILoggingService _logger; private readonly MasterHelper _masterHelper; - public MasterController(ApplicationDbContext context, UserHelper userHelper, ILoggingService logger, MasterHelper masterHelper) + private readonly IMasterService _masterService; + public MasterController(ApplicationDbContext context, UserHelper userHelper, ILoggingService logger, MasterHelper masterHelper, IMasterService masterService) { _context = context; _userHelper = userHelper; _logger = logger; _masterHelper = masterHelper; + _masterService = masterService; } // -------------------------------- Activity -------------------------------- @@ -846,5 +849,41 @@ namespace Marco.Pms.Services.Controllers var response = await _masterHelper.DeleteContactTag(id); return Ok(response); } + #region =================================================================== Expenses Type APIs =================================================================== + [HttpGet("expenses-types")] + public async Task GetExpenseTypeList() + { + var response = await _masterService.GetExpenseTypeListAsync(); + return StatusCode(response.StatusCode, response); + } + public async Task CreateExpenseType(ExpensesTypeMasterDto dto) + { + var response = await _masterService.GetExpenseTypeListAsync(); + return StatusCode(response.StatusCode, response); + } + + #endregion + + #region =================================================================== Expenses Status APIs =================================================================== + [HttpGet("expenses-status")] + public async Task GetExpenseStatusList() + { + var response = await _masterService.GetExpenseStatusListAsync(); + return StatusCode(response.StatusCode, response); + } + + + #endregion + + #region =================================================================== Payment mode APIs =================================================================== + [HttpGet("payment-modes")] + public async Task GetPaymentModeList() + { + var response = await _masterService.GetPaymentModeListAsync(); + return StatusCode(response.StatusCode, response); + } + + + #endregion } } diff --git a/Marco.Pms.Services/Helpers/MasterHelper.cs b/Marco.Pms.Services/Helpers/MasterHelper.cs index 83bc007..d50a603 100644 --- a/Marco.Pms.Services/Helpers/MasterHelper.cs +++ b/Marco.Pms.Services/Helpers/MasterHelper.cs @@ -19,20 +19,21 @@ namespace Marco.Pms.Services.Helpers private readonly ApplicationDbContext _context; private readonly ILoggingService _logger; private readonly UserHelper _userHelper; - private readonly PermissionServices _permissionService; + private readonly PermissionServices _permission; + private readonly Guid tenantId; - - public MasterHelper(ApplicationDbContext context, ILoggingService logger, UserHelper userHelper, PermissionServices permissionServices) + public MasterHelper(ApplicationDbContext context, ILoggingService logger, UserHelper userHelper, PermissionServices permission) { _context = context; _logger = logger; _userHelper = userHelper; - _permissionService = permissionServices; + _permission = permission; + tenantId = userHelper.GetTenantId(); } - // -------------------------------- Contact Category -------------------------------- + #region =================================================================== Contact Category APIs =================================================================== + public async Task> CreateContactCategory(CreateContactCategoryDto contactCategoryDto) { - Guid tenantId = _userHelper.GetTenantId(); var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); if (contactCategoryDto != null) { @@ -55,7 +56,6 @@ namespace Marco.Pms.Services.Helpers } public async Task> UpdateContactCategory(Guid id, UpdateContactCategoryDto contactCategoryDto) { - Guid tenantId = _userHelper.GetTenantId(); var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); if (contactCategoryDto != null && id == contactCategoryDto.Id) { @@ -86,7 +86,6 @@ namespace Marco.Pms.Services.Helpers } public async Task> GetContactCategoriesList() { - Guid tenantId = _userHelper.GetTenantId(); var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); var categoryList = await _context.ContactCategoryMasters.Where(c => c.TenantId == tenantId).ToListAsync(); @@ -101,7 +100,6 @@ namespace Marco.Pms.Services.Helpers } public async Task> GetContactCategoryById(Guid id) { - Guid tenantId = _userHelper.GetTenantId(); var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); var category = await _context.ContactCategoryMasters.FirstOrDefaultAsync(c => c.Id == id && c.TenantId == tenantId); @@ -117,7 +115,6 @@ namespace Marco.Pms.Services.Helpers } public async Task> DeleteContactCategory(Guid id) { - Guid tenantId = _userHelper.GetTenantId(); var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); ContactCategoryMaster? contactCategory = await _context.ContactCategoryMasters.FirstOrDefaultAsync(c => c.Id == id && c.TenantId == tenantId); if (contactCategory != null) @@ -148,14 +145,12 @@ namespace Marco.Pms.Services.Helpers _logger.LogWarning("Employee {EmployeeId} tries to delete Category {CategoryId} but not found in database", LoggedInEmployee.Id, id); return ApiResponse.SuccessResponse(new { }, "Category deleted successfully", 200); } + #endregion - // -------------------------------- Contact Tag -------------------------------- - + #region =================================================================== Contact Tag APIs =================================================================== public async Task> GetContactTags() { - Guid tenantId = _userHelper.GetTenantId(); - var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); var taglist = await _context.ContactTagMasters.Where(t => t.TenantId == tenantId).ToListAsync(); @@ -170,7 +165,6 @@ namespace Marco.Pms.Services.Helpers } public async Task> CreateContactTag(CreateContactTagDto contactTagDto) { - Guid tenantId = _userHelper.GetTenantId(); var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); if (contactTagDto != null) { @@ -193,7 +187,6 @@ namespace Marco.Pms.Services.Helpers } public async Task> UpdateContactTag(Guid id, UpdateContactTagDto contactTagDto) { - var tenantId = _userHelper.GetTenantId(); Employee LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); if (contactTagDto != null && contactTagDto.Id == id) { @@ -226,7 +219,6 @@ namespace Marco.Pms.Services.Helpers } public async Task> DeleteContactTag(Guid id) { - Guid tenantId = _userHelper.GetTenantId(); var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); ContactTagMaster? contactTag = await _context.ContactTagMasters.FirstOrDefaultAsync(c => c.Id == id && c.TenantId == tenantId); if (contactTag != null) @@ -252,19 +244,21 @@ namespace Marco.Pms.Services.Helpers return ApiResponse.SuccessResponse(new { }, "Tag deleted successfully", 200); } - // -------------------------------- Work Status -------------------------------- + #endregion + + #region =================================================================== Work Status APIs =================================================================== + public async Task> GetWorkStatusList() { _logger.LogInfo("GetWorkStatusList called."); try { - // Step 1: Get tenant and logged-in employee info - Guid tenantId = _userHelper.GetTenantId(); + // Step 1: Get logged-in employee info var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); // Step 2: Check permission to view master data - bool hasViewPermission = await _permissionService.HasPermission(PermissionsMaster.ViewMasters, loggedInEmployee.Id); + bool hasViewPermission = await _permission.HasPermission(PermissionsMaster.ViewMasters, loggedInEmployee.Id); if (!hasViewPermission) { _logger.LogWarning("Access denied for employeeId: {EmployeeId}", loggedInEmployee.Id); @@ -294,7 +288,7 @@ namespace Marco.Pms.Services.Helpers } catch (Exception ex) { - _logger.LogWarning("Error occurred while fetching work status list : {Error}", ex.Message); + _logger.LogError(ex, "Error occurred while fetching work status list"); return ApiResponse.ErrorResponse("An error occurred", "Unable to fetch work status list", 500); } } @@ -304,12 +298,11 @@ namespace Marco.Pms.Services.Helpers try { - // Step 1: Get tenant and logged-in employee - Guid tenantId = _userHelper.GetTenantId(); + // Step 1: Get logged-in employee var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); // Step 2: Check if user has permission to manage master data - var hasManageMasterPermission = await _permissionService.HasPermission(PermissionsMaster.ManageMasters, loggedInEmployee.Id); + var hasManageMasterPermission = await _permission.HasPermission(PermissionsMaster.ManageMasters, loggedInEmployee.Id); if (!hasManageMasterPermission) { _logger.LogWarning("Access denied for employeeId: {EmployeeId}", loggedInEmployee.Id); @@ -343,7 +336,7 @@ namespace Marco.Pms.Services.Helpers } catch (Exception ex) { - _logger.LogWarning("Error occurred while creating work status : {Error}", ex.Message); + _logger.LogError(ex, "Error occurred while creating work status"); return ApiResponse.ErrorResponse("An error occurred", "Unable to create work status", 500); } } @@ -360,12 +353,11 @@ namespace Marco.Pms.Services.Helpers return ApiResponse.ErrorResponse("Invalid data provided", "The provided work status ID is invalid", 400); } - // Step 2: Get tenant and logged-in employee - Guid tenantId = _userHelper.GetTenantId(); + // Step 2: Get logged-in employee var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); // Step 3: Check permissions - var hasManageMasterPermission = await _permissionService.HasPermission(PermissionsMaster.ManageMasters, loggedInEmployee.Id); + var hasManageMasterPermission = await _permission.HasPermission(PermissionsMaster.ManageMasters, loggedInEmployee.Id); if (!hasManageMasterPermission) { _logger.LogWarning("Access denied. EmployeeId: {EmployeeId} does not have Manage Master permission.", loggedInEmployee.Id); @@ -413,12 +405,11 @@ namespace Marco.Pms.Services.Helpers try { - // Step 1: Get current tenant and logged-in employee - Guid tenantId = _userHelper.GetTenantId(); + // Step 1: Get logged-in employee var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); // Step 2: Check permission to manage master data - var hasManageMasterPermission = await _permissionService.HasPermission(PermissionsMaster.ManageMasters, loggedInEmployee.Id); + var hasManageMasterPermission = await _permission.HasPermission(PermissionsMaster.ManageMasters, loggedInEmployee.Id); if (!hasManageMasterPermission) { _logger.LogWarning("Delete denied. EmployeeId: {EmployeeId} lacks Manage_Master permission.", loggedInEmployee.Id); @@ -462,5 +453,7 @@ namespace Marco.Pms.Services.Helpers return ApiResponse.ErrorResponse("An error occurred", "Unable to delete work status", 500); } } + + #endregion } } diff --git a/Marco.Pms.Services/MappingProfiles/MappingProfile.cs b/Marco.Pms.Services/MappingProfiles/MappingProfile.cs index bf3777c..2706083 100644 --- a/Marco.Pms.Services/MappingProfiles/MappingProfile.cs +++ b/Marco.Pms.Services/MappingProfiles/MappingProfile.cs @@ -1,4 +1,5 @@ using AutoMapper; +using Marco.Pms.Model.Dtos.Master; using Marco.Pms.Model.Dtos.Project; using Marco.Pms.Model.Employees; using Marco.Pms.Model.Master; @@ -60,9 +61,18 @@ namespace Marco.Pms.Services.MappingProfiles opt => opt.MapFrom(src => src.Comment)); #endregion - #region ======================================================= Projects ======================================================= + #region ======================================================= Employee ======================================================= CreateMap(); #endregion + + #region ======================================================= Master ======================================================= + CreateMap() + .ForMember( + dest => dest.Id, + // Explicitly and safely convert nullable Guid to non-nullable Guid + opt => opt.MapFrom(src => src.Id != null ? src.Id : Guid.Empty) + ); + #endregion } } } diff --git a/Marco.Pms.Services/Program.cs b/Marco.Pms.Services/Program.cs index e67ed7a..a43af8b 100644 --- a/Marco.Pms.Services/Program.cs +++ b/Marco.Pms.Services/Program.cs @@ -172,6 +172,7 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); #endregion #region Helpers diff --git a/Marco.Pms.Services/Service/MasterService.cs b/Marco.Pms.Services/Service/MasterService.cs new file mode 100644 index 0000000..bd74bce --- /dev/null +++ b/Marco.Pms.Services/Service/MasterService.cs @@ -0,0 +1,80 @@ +using AutoMapper; +using Marco.Pms.DataAccess.Data; +using Marco.Pms.Model.Dtos.Master; +using Marco.Pms.Model.Entitlements; +using Marco.Pms.Model.Master; +using Marco.Pms.Model.Utilities; +using Marco.Pms.Services.Service.ServiceInterfaces; +using MarcoBMS.Services.Helpers; +using MarcoBMS.Services.Service; +using Microsoft.EntityFrameworkCore; + +namespace Marco.Pms.Services.Service +{ + public class MasterService : IMasterService + { + private readonly ApplicationDbContext _context; + private readonly ILoggingService _logger; + private readonly UserHelper _userHelper; + private readonly PermissionServices _permission; + private readonly IMapper _mapper; + private readonly Guid tenantId; + + public MasterService( + ApplicationDbContext context, + ILoggingService logger, + UserHelper userHelper, + PermissionServices permission, + IMapper mapper) + { + _context = context; + _logger = logger; + _userHelper = userHelper; + _permission = permission; + _mapper = mapper; + tenantId = userHelper.GetTenantId(); + } + + #region =================================================================== Expenses Type APIs =================================================================== + + public async Task> GetExpenseTypeListAsync() + { + var typeList = await _context.ExpensesTypeMaster.Where(et => et.TenantId == tenantId).ToListAsync(); + return ApiResponse.SuccessResponse(typeList); + } + public async Task> CreateExpenseTypeAsync(ExpensesTypeMasterDto dto) + { + var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); + var hasManagePermission = await _permission.HasPermission(PermissionsMaster.ManageMasters, loggedInEmployee.Id); + if (!hasManagePermission) + { + _logger.LogWarning("Access DENIED for employee {EmployeeId} for managing EXPANSES TYPE MASTER.", loggedInEmployee.Id); + return ApiResponse.ErrorResponse("Access Denied.", "You do not have permission to Upload expenses for this project", 403); + } + var expensesType = _mapper.Map(dto); + return ApiResponse.SuccessResponse(expensesType); + } + + #endregion + + #region =================================================================== Expenses Status APIs =================================================================== + public async Task> GetExpenseStatusListAsync() + { + var typeList = await _context.ExpensesStatusMaster.Where(et => et.TenantId == tenantId).ToListAsync(); + return ApiResponse.SuccessResponse(typeList); + } + + + #endregion + + #region =================================================================== Payment mode APIs =================================================================== + public async Task> GetPaymentModeListAsync() + { + var typeList = await _context.PaymentModeMatser.Where(et => et.TenantId == tenantId).ToListAsync(); + return ApiResponse.SuccessResponse(typeList); + } + + + #endregion + } +} diff --git a/Marco.Pms.Services/Service/S3UploadService.cs b/Marco.Pms.Services/Service/S3UploadService.cs index 4ce7a4b..1d98a33 100644 --- a/Marco.Pms.Services/Service/S3UploadService.cs +++ b/Marco.Pms.Services/Service/S3UploadService.cs @@ -5,6 +5,7 @@ using Marco.Pms.Model.Utilities; using MarcoBMS.Services.Service; using Microsoft.Extensions.Options; using MimeDetective; +using System.Text.RegularExpressions; namespace Marco.Pms.Services.Service { @@ -12,7 +13,7 @@ namespace Marco.Pms.Services.Service public class S3UploadService { private readonly IAmazonS3 _s3Client; - private readonly string _bucketName = "your-bucket-name"; + private readonly string _bucketName; private readonly ILoggingService _logger; private readonly IConfiguration _configuration; @@ -64,7 +65,7 @@ namespace Marco.Pms.Services.Service } catch (Exception ex) { - _logger.LogError(ex, "error occured while uploading file to S3"); + _logger.LogError(ex, "Error ocurred while uploading file to S3", ex.Message); } @@ -87,7 +88,7 @@ namespace Marco.Pms.Services.Service } catch (Exception ex) { - _logger.LogError(ex, "error occured while requesting presigned url from Amazon S3", ex.Message); + _logger.LogError(ex, "Error occured while requesting presigned url from Amazon S3"); return string.Empty; } } @@ -107,17 +108,17 @@ namespace Marco.Pms.Services.Service } catch (Exception ex) { - _logger.LogError(ex, "error ocured while deleting from Amazon S3"); + _logger.LogError(ex, "while deleting from Amazon S3"); return false; } } - public string GenerateFileName(string contentType, Guid tenantId, string? name) + public string GenerateFileName(string contentType, Guid entityId, string? name) { string extenstion = GetExtensionFromMimeType(contentType); if (string.IsNullOrEmpty(name)) - return $"{tenantId}_{DateTime.UtcNow:yyyyMMddHHmmssfff}{extenstion}"; - return $"{name}_{tenantId}_{DateTime.UtcNow:yyyyMMddHHmmssfff}{extenstion}"; + return $"{entityId}_{DateTime.UtcNow:yyyyMMddHHmmssfff}{extenstion}"; + return $"{name}_{entityId}_{DateTime.UtcNow:yyyyMMddHHmmssfff}{extenstion}"; } public string GetExtensionFromMimeType(string contentType) @@ -220,9 +221,38 @@ namespace Marco.Pms.Services.Service catch (Exception ex) { // Handle other potential errors during decoding or inspection - _logger.LogError(ex, "errors during decoding or inspection"); + _logger.LogError(ex, "An error occurred while decoding base64"); return string.Empty; } } + public bool IsBase64String(string? input) + { + if (string.IsNullOrWhiteSpace(input)) + return false; + + // Normalize string + input = input.Trim(); + + // Length must be multiple of 4 + if (input.Length % 4 != 0) + return false; + + // Valid Base64 characters with correct padding + var base64Regex = new Regex(@"^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$"); + if (!base64Regex.IsMatch(input)) + return false; + + try + { + // Decode and re-encode to confirm validity + var bytes = Convert.FromBase64String(input); + var reEncoded = Convert.ToBase64String(bytes); + return input == reEncoded; + } + catch + { + return false; + } + } } } \ No newline at end of file diff --git a/Marco.Pms.Services/Service/ServiceInterfaces/IMasterService.cs b/Marco.Pms.Services/Service/ServiceInterfaces/IMasterService.cs new file mode 100644 index 0000000..1b970ca --- /dev/null +++ b/Marco.Pms.Services/Service/ServiceInterfaces/IMasterService.cs @@ -0,0 +1,11 @@ +using Marco.Pms.Model.Utilities; + +namespace Marco.Pms.Services.Service.ServiceInterfaces +{ + public interface IMasterService + { + Task> GetExpenseTypeListAsync(); + Task> GetExpenseStatusListAsync(); + Task> GetPaymentModeListAsync(); + } +}